diff options
Diffstat (limited to 'sound')
223 files changed, 19170 insertions, 3258 deletions
diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index f01bffb702bc..401107b85d30 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -241,7 +241,7 @@ static struct snd_kcontrol_new inputgain_control = { static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - static char *texts[] = { "Line-In", "Microphone" }; + static const char * const texts[] = { "Line-In", "Microphone" }; uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; diff --git a/sound/arm/pxa2xx-ac97-lib.c b/sound/arm/pxa2xx-ac97-lib.c index 66de90ed30ca..39c3969ac1c7 100644 --- a/sound/arm/pxa2xx-ac97-lib.c +++ b/sound/arm/pxa2xx-ac97-lib.c @@ -152,9 +152,9 @@ static inline void pxa_ac97_cold_pxa27x(void) gsr_bits = 0; /* PXA27x Developers Manual section 13.5.2.2.1 */ - clk_enable(ac97conf_clk); + clk_prepare_enable(ac97conf_clk); udelay(5); - clk_disable(ac97conf_clk); + clk_disable_unprepare(ac97conf_clk); GCR = GCR_COLD_RST | GCR_WARM_RST; } #endif @@ -299,14 +299,14 @@ static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id) int pxa2xx_ac97_hw_suspend(void) { GCR |= GCR_ACLINK_OFF; - clk_disable(ac97_clk); + clk_disable_unprepare(ac97_clk); return 0; } EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_suspend); int pxa2xx_ac97_hw_resume(void) { - clk_enable(ac97_clk); + clk_prepare_enable(ac97_clk); return 0; } EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_resume); @@ -368,7 +368,7 @@ int pxa2xx_ac97_hw_probe(struct platform_device *dev) goto err_clk; } - ret = clk_enable(ac97_clk); + ret = clk_prepare_enable(ac97_clk); if (ret) goto err_clk2; @@ -403,7 +403,7 @@ void pxa2xx_ac97_hw_remove(struct platform_device *dev) clk_put(ac97conf_clk); ac97conf_clk = NULL; } - clk_disable(ac97_clk); + clk_disable_unprepare(ac97_clk); clk_put(ac97_clk); ac97_clk = NULL; } diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c index 05ec049c9faf..a04d23174dc2 100644 --- a/sound/atmel/ac97c.c +++ b/sound/atmel/ac97c.c @@ -1198,6 +1198,7 @@ static int atmel_ac97c_remove(struct platform_device *pdev) } static struct platform_driver atmel_ac97c_driver = { + .probe = atmel_ac97c_probe, .remove = atmel_ac97c_remove, .driver = { .name = "atmel_ac97c", @@ -1205,19 +1206,7 @@ static struct platform_driver atmel_ac97c_driver = { .pm = ATMEL_AC97C_PM_OPS, }, }; - -static int __init atmel_ac97c_init(void) -{ - return platform_driver_probe(&atmel_ac97c_driver, - atmel_ac97c_probe); -} -module_init(atmel_ac97c_init); - -static void __exit atmel_ac97c_exit(void) -{ - platform_driver_unregister(&atmel_ac97c_driver); -} -module_exit(atmel_ac97c_exit); +module_platform_driver(atmel_ac97c_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Driver for Atmel AC97 controller"); diff --git a/sound/core/control.c b/sound/core/control.c index f038f5afafe2..f0b0e14497a5 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -288,6 +288,10 @@ static bool snd_ctl_remove_numid_conflict(struct snd_card *card, { struct snd_kcontrol *kctl; + /* Make sure that the ids assigned to the control do not wrap around */ + if (card->last_numid >= UINT_MAX - count) + card->last_numid = 0; + list_for_each_entry(kctl, &card->controls, list) { if (kctl->id.numid < card->last_numid + 1 + count && kctl->id.numid + kctl->count > card->last_numid + 1) { @@ -330,6 +334,7 @@ int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) { struct snd_ctl_elem_id id; unsigned int idx; + unsigned int count; int err = -EINVAL; if (! kcontrol) @@ -337,6 +342,9 @@ int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) if (snd_BUG_ON(!card || !kcontrol->info)) goto error; id = kcontrol->id; + if (id.index > UINT_MAX - kcontrol->count) + goto error; + down_write(&card->controls_rwsem); if (snd_ctl_find_id(card, &id)) { up_write(&card->controls_rwsem); @@ -358,8 +366,9 @@ int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) card->controls_count += kcontrol->count; kcontrol->id.numid = card->last_numid + 1; card->last_numid += kcontrol->count; + count = kcontrol->count; up_write(&card->controls_rwsem); - for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) + for (idx = 0; idx < count; idx++, id.index++, id.numid++) snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); return 0; @@ -388,6 +397,7 @@ int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, bool add_on_replace) { struct snd_ctl_elem_id id; + unsigned int count; unsigned int idx; struct snd_kcontrol *old; int ret; @@ -423,8 +433,9 @@ add: card->controls_count += kcontrol->count; kcontrol->id.numid = card->last_numid + 1; card->last_numid += kcontrol->count; + count = kcontrol->count; up_write(&card->controls_rwsem); - for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) + for (idx = 0; idx < count; idx++, id.index++, id.numid++) snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); return 0; @@ -897,9 +908,9 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, result = kctl->put(kctl, control); } if (result > 0) { + struct snd_ctl_elem_id id = control->id; up_read(&card->controls_rwsem); - snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, - &control->id); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id); return 0; } } @@ -991,6 +1002,7 @@ static int snd_ctl_elem_unlock(struct snd_ctl_file *file, struct user_element { struct snd_ctl_elem_info info; + struct snd_card *card; void *elem_data; /* element data */ unsigned long elem_data_size; /* size of element data in bytes */ void *tlv_data; /* TLV data */ @@ -1034,7 +1046,9 @@ static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, { struct user_element *ue = kcontrol->private_data; + mutex_lock(&ue->card->user_ctl_lock); memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size); + mutex_unlock(&ue->card->user_ctl_lock); return 0; } @@ -1043,10 +1057,12 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, { int change; struct user_element *ue = kcontrol->private_data; - + + mutex_lock(&ue->card->user_ctl_lock); change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0; if (change) memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size); + mutex_unlock(&ue->card->user_ctl_lock); return change; } @@ -1066,19 +1082,32 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol, new_data = memdup_user(tlv, size); if (IS_ERR(new_data)) return PTR_ERR(new_data); + mutex_lock(&ue->card->user_ctl_lock); change = ue->tlv_data_size != size; if (!change) change = memcmp(ue->tlv_data, new_data, size); kfree(ue->tlv_data); ue->tlv_data = new_data; ue->tlv_data_size = size; + mutex_unlock(&ue->card->user_ctl_lock); } else { - if (! ue->tlv_data_size || ! ue->tlv_data) - return -ENXIO; - if (size < ue->tlv_data_size) - return -ENOSPC; + int ret = 0; + + mutex_lock(&ue->card->user_ctl_lock); + if (!ue->tlv_data_size || !ue->tlv_data) { + ret = -ENXIO; + goto err_unlock; + } + if (size < ue->tlv_data_size) { + ret = -ENOSPC; + goto err_unlock; + } if (copy_to_user(tlv, ue->tlv_data, ue->tlv_data_size)) - return -EFAULT; + ret = -EFAULT; +err_unlock: + mutex_unlock(&ue->card->user_ctl_lock); + if (ret) + return ret; } return change; } @@ -1136,8 +1165,6 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, struct user_element *ue; int idx, err; - if (!replace && card->user_ctl_count >= MAX_USER_CONTROLS) - return -ENOMEM; if (info->count < 1) return -EINVAL; access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : @@ -1146,21 +1173,16 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)); info->id.numid = 0; memset(&kctl, 0, sizeof(kctl)); - down_write(&card->controls_rwsem); - _kctl = snd_ctl_find_id(card, &info->id); - err = 0; - if (_kctl) { - if (replace) - err = snd_ctl_remove(card, _kctl); - else - err = -EBUSY; - } else { - if (replace) - err = -ENOENT; + + if (replace) { + err = snd_ctl_remove_user_ctl(file, &info->id); + if (err) + return err; } - up_write(&card->controls_rwsem); - if (err < 0) - return err; + + if (card->user_ctl_count >= MAX_USER_CONTROLS) + return -ENOMEM; + memcpy(&kctl.id, &info->id, sizeof(info->id)); kctl.count = info->owner ? info->owner : 1; access |= SNDRV_CTL_ELEM_ACCESS_USER; @@ -1210,6 +1232,7 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, ue = kzalloc(sizeof(struct user_element) + private_size, GFP_KERNEL); if (ue == NULL) return -ENOMEM; + ue->card = card; ue->info = *info; ue->info.access = 0; ue->elem_data = (char *)ue + sizeof(*ue); @@ -1321,8 +1344,9 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, } err = kctl->tlv.c(kctl, op_flag, tlv.length, _tlv->tlv); if (err > 0) { + struct snd_ctl_elem_id id = kctl->id; up_read(&card->controls_rwsem); - snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &id); return 0; } } else { diff --git a/sound/core/init.c b/sound/core/init.c index 5ee83845c5de..7bdfd19e24a8 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -232,6 +232,7 @@ int snd_card_new(struct device *parent, int idx, const char *xid, INIT_LIST_HEAD(&card->devices); init_rwsem(&card->controls_rwsem); rwlock_init(&card->ctl_files_rwlock); + mutex_init(&card->user_ctl_lock); INIT_LIST_HEAD(&card->controls); INIT_LIST_HEAD(&card->ctl_files); spin_lock_init(&card->files_lock); diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 94d08733cb38..6542c4083594 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -65,13 +65,15 @@ int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream, enum dma_slave_buswidth buswidth; int bits; - bits = snd_pcm_format_physical_width(params_format(params)); + bits = params_physical_width(params); if (bits < 8 || bits > 64) return -EINVAL; else if (bits == 8) buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; else if (bits == 16) buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else if (bits == 24) + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; else if (bits <= 32) buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; else @@ -182,6 +184,7 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; int ret; switch (cmd) { @@ -196,6 +199,11 @@ int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd) dmaengine_resume(prtd->dma_chan); break; case SNDRV_PCM_TRIGGER_SUSPEND: + if (runtime->info & SNDRV_PCM_INFO_PAUSE) + dmaengine_pause(prtd->dma_chan); + else + dmaengine_terminate_all(prtd->dma_chan); + break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: dmaengine_pause(prtd->dma_chan); break; diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index ce83def9f43b..9acc77eae487 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -345,7 +345,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, snd_pcm_debug_name(substream, name, sizeof(name)); xrun_log_show(substream); pcm_err(substream->pcm, - "BUG: %s, pos = %ld, buffer size = %ld, period size = %ld\n", + "XRUN: %s, pos = %ld, buffer size = %ld, period size = %ld\n", name, pos, runtime->buffer_size, runtime->period_size); } diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 9ca5e647e54b..225c73152ee9 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -660,7 +660,7 @@ static int deliver_to_subscribers(struct snd_seq_client *client, int atomic, int hop) { struct snd_seq_subscribers *subs; - int err = 0, num_ev = 0; + int err, result = 0, num_ev = 0; struct snd_seq_event event_saved; struct snd_seq_client_port *src_port; struct snd_seq_port_subs_info *grp; @@ -685,8 +685,12 @@ static int deliver_to_subscribers(struct snd_seq_client *client, subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL); err = snd_seq_deliver_single_event(client, event, 0, atomic, hop); - if (err < 0) - break; + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } num_ev++; /* restore original event record */ *event = event_saved; @@ -697,7 +701,7 @@ static int deliver_to_subscribers(struct snd_seq_client *client, up_read(&grp->list_mutex); *event = event_saved; /* restore */ snd_seq_port_unlock(src_port); - return (err < 0) ? err : num_ev; + return (result < 0) ? result : num_ev; } @@ -709,7 +713,7 @@ static int port_broadcast_event(struct snd_seq_client *client, struct snd_seq_event *event, int atomic, int hop) { - int num_ev = 0, err = 0; + int num_ev = 0, err, result = 0; struct snd_seq_client *dest_client; struct snd_seq_client_port *port; @@ -724,14 +728,18 @@ static int port_broadcast_event(struct snd_seq_client *client, err = snd_seq_deliver_single_event(NULL, event, SNDRV_SEQ_FILTER_BROADCAST, atomic, hop); - if (err < 0) - break; + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } num_ev++; } read_unlock(&dest_client->ports_lock); snd_seq_client_unlock(dest_client); event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */ - return (err < 0) ? err : num_ev; + return (result < 0) ? result : num_ev; } /* @@ -741,7 +749,7 @@ static int port_broadcast_event(struct snd_seq_client *client, static int broadcast_event(struct snd_seq_client *client, struct snd_seq_event *event, int atomic, int hop) { - int err = 0, num_ev = 0; + int err, result = 0, num_ev = 0; int dest; struct snd_seq_addr addr; @@ -760,12 +768,16 @@ static int broadcast_event(struct snd_seq_client *client, err = snd_seq_deliver_single_event(NULL, event, SNDRV_SEQ_FILTER_BROADCAST, atomic, hop); - if (err < 0) - break; + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } num_ev += err; } event->dest = addr; /* restore */ - return (err < 0) ? err : num_ev; + return (result < 0) ? result : num_ev; } diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c index 559989992bef..53a403e17c5b 100644 --- a/sound/core/seq/seq_fifo.c +++ b/sound/core/seq/seq_fifo.c @@ -124,7 +124,7 @@ int snd_seq_fifo_event_in(struct snd_seq_fifo *f, snd_use_lock_use(&f->use_lock); err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL); /* always non-blocking */ if (err < 0) { - if (err == -ENOMEM) + if ((err == -ENOMEM) || (err == -EAGAIN)) atomic_inc(&f->overflow); snd_use_lock_free(&f->use_lock); return err; diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index 3e05c55a2880..a1fd77af6059 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -362,13 +362,13 @@ snd_seq_midisynth_register_port(struct snd_seq_device *dev) if (! port->name[0]) { if (info->name[0]) { if (ports > 1) - snprintf(port->name, sizeof(port->name), "%s-%d", info->name, p); + snprintf(port->name, sizeof(port->name), "%s-%u", info->name, p); else snprintf(port->name, sizeof(port->name), "%s", info->name); } else { /* last resort */ if (ports > 1) - sprintf(port->name, "MIDI %d-%d-%d", card->number, device, p); + sprintf(port->name, "MIDI %d-%d-%u", card->number, device, p); else sprintf(port->name, "MIDI %d-%d", card->number, device); } diff --git a/sound/core/timer.c b/sound/core/timer.c index cfd455a8ac1a..777a45e08e53 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -390,7 +390,7 @@ static void snd_timer_notify1(struct snd_timer_instance *ti, int event) struct timespec tstamp; if (timer_tstamp_monotonic) - do_posix_clock_monotonic_gettime(&tstamp); + ktime_get_ts(&tstamp); else getnstimeofday(&tstamp); if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_START || @@ -1203,7 +1203,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, } if (tu->last_resolution != resolution || ticks > 0) { if (timer_tstamp_monotonic) - do_posix_clock_monotonic_gettime(&tstamp); + ktime_get_ts(&tstamp); else getnstimeofday(&tstamp); } diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b3e274fe4a77..775ef2efc296 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -9,12 +9,12 @@ if SND_FIREWIRE && FIREWIRE config SND_FIREWIRE_LIB tristate - depends on SND_PCM + select SND_PCM + select SND_RAWMIDI config SND_DICE tristate "DICE-based DACs (EXPERIMENTAL)" select SND_HWDEP - select SND_PCM select SND_FIREWIRE_LIB help Say Y here to include support for many DACs based on the DICE @@ -28,7 +28,6 @@ config SND_DICE config SND_FIREWIRE_SPEAKERS tristate "FireWire speakers" - select SND_PCM select SND_FIREWIRE_LIB help Say Y here to include support for the Griffin FireWave Surround @@ -39,7 +38,6 @@ config SND_FIREWIRE_SPEAKERS config SND_ISIGHT tristate "Apple iSight microphone" - select SND_PCM select SND_FIREWIRE_LIB help Say Y here to include support for the front and rear microphones @@ -50,8 +48,6 @@ config SND_ISIGHT config SND_SCS1X tristate "Stanton Control System 1 MIDI" - select SND_PCM - select SND_RAWMIDI select SND_FIREWIRE_LIB help Say Y here to include support for the MIDI ports of the Stanton @@ -61,4 +57,59 @@ config SND_SCS1X To compile this driver as a module, choose M here: the module will be called snd-scs1x. +config SND_FIREWORKS + tristate "Echo Fireworks board module support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based + on Echo Digital Audio Fireworks board: + * Mackie Onyx 400F/1200F + * Echo AudioFire12/8(until 2009 July) + * Echo AudioFire2/4/Pre8/8(since 2009 July) + * Echo Fireworks 8/HDMI + * Gibson Robot Interface Pack/GoldTop + + To compile this driver as a module, choose M here: the module + will be called snd-fireworks. + +config SND_BEBOB + tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based + on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware: + * Edirol FA-66/FA-101 + * PreSonus FIREBOX/FIREPOD/FP10/Inspire1394 + * BridgeCo RDAudio1/Audio5 + * Mackie Onyx 1220/1620/1640 (Firewire I/O Card) + * Mackie d.2 (Firewire Option) + * Stanton FinalScratch 2 (ScratchAmp) + * Tascam IF-FW/DM + * Behringer XENIX UFX 1204/1604 + * Behringer Digital Mixer X32 series (X-UF Card) + * Apogee Rosetta 200/400 (X-FireWire card) + * Apogee DA/AD/DD-16X (X-FireWire card) + * Apogee Ensemble + * ESI Quotafire610 + * AcousticReality eARMasterOne + * CME MatrixKFW + * Phonic Helix Board 12 MkII/18 MkII/24 MkII + * Phonic Helix Board 12 Universal/18 Universal/24 Universal + * Lynx Aurora 8/16 (LT-FW) + * ICON FireXon + * PrismSound Orpheus/ADA-8XR + * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW + * Terratec EWS MIC2/EWS MIC4 + * Terratec Aureon 7.1 Firewire + * Yamaha GO44/GO46 + * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO + * M-Audio Firewire410/AudioPhile/Solo + * M-Audio Ozonic/NRV10/ProfireLightBridge + * M-Audio Firewire 1814/ProjectMix IO + + To compile this driver as a module, choose M here: the module + will be called snd-bebob. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 509955061d30..fad8d49306ab 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_SND_DICE) += snd-dice.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o +obj-$(CONFIG_SND_FIREWORKS) += fireworks/ +obj-$(CONFIG_SND_BEBOB) += bebob/ diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 9048777228e2..f96bf4c7c232 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -11,7 +11,10 @@ #include <linux/firewire.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/sched.h> #include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rawmidi.h> #include "amdtp.h" #define TICKS_PER_CYCLE 3072 @@ -20,50 +23,78 @@ #define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 µs */ +/* isochronous header parameters */ +#define ISO_DATA_LENGTH_SHIFT 16 #define TAG_CIP 1 +/* common isochronous packet header parameters */ #define CIP_EOH (1u << 31) +#define CIP_EOH_MASK 0x80000000 #define CIP_FMT_AM (0x10 << 24) -#define AMDTP_FDF_AM824 (0 << 19) -#define AMDTP_FDF_SFC_SHIFT 16 +#define CIP_FMT_MASK 0x3f000000 +#define CIP_SYT_MASK 0x0000ffff +#define CIP_SYT_NO_INFO 0xffff +#define CIP_FDF_MASK 0x00ff0000 +#define CIP_FDF_SFC_SHIFT 16 + +/* + * Audio and Music transfer protocol specific parameters + * only "Clock-based rate control mode" is supported + */ +#define AMDTP_FDF_AM824 (0 << (CIP_FDF_SFC_SHIFT + 3)) +#define AMDTP_FDF_NO_DATA 0xff +#define AMDTP_DBS_MASK 0x00ff0000 +#define AMDTP_DBS_SHIFT 16 +#define AMDTP_DBC_MASK 0x000000ff /* TODO: make these configurable */ #define INTERRUPT_INTERVAL 16 #define QUEUE_LENGTH 48 +#define IN_PACKET_HEADER_SIZE 4 +#define OUT_PACKET_HEADER_SIZE 0 + static void pcm_period_tasklet(unsigned long data); /** - * amdtp_out_stream_init - initialize an AMDTP output stream structure - * @s: the AMDTP output stream to initialize + * amdtp_stream_init - initialize an AMDTP stream structure + * @s: the AMDTP stream to initialize * @unit: the target of the stream + * @dir: the direction of stream * @flags: the packet transmission method to use */ -int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, - enum cip_out_flags flags) +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags) { s->unit = fw_unit_get(unit); + s->direction = dir; s->flags = flags; s->context = ERR_PTR(-1); mutex_init(&s->mutex); tasklet_init(&s->period_tasklet, pcm_period_tasklet, (unsigned long)s); s->packet_index = 0; + init_waitqueue_head(&s->callback_wait); + s->callbacked = false; + s->sync_slave = NULL; + + s->rx_blocks_for_midi = UINT_MAX; + return 0; } -EXPORT_SYMBOL(amdtp_out_stream_init); +EXPORT_SYMBOL(amdtp_stream_init); /** - * amdtp_out_stream_destroy - free stream resources - * @s: the AMDTP output stream to destroy + * amdtp_stream_destroy - free stream resources + * @s: the AMDTP stream to destroy */ -void amdtp_out_stream_destroy(struct amdtp_out_stream *s) +void amdtp_stream_destroy(struct amdtp_stream *s) { - WARN_ON(amdtp_out_stream_running(s)); + WARN_ON(amdtp_stream_running(s)); mutex_destroy(&s->mutex); fw_unit_put(s->unit); } -EXPORT_SYMBOL(amdtp_out_stream_destroy); +EXPORT_SYMBOL(amdtp_stream_destroy); const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { [CIP_SFC_32000] = 8, @@ -76,9 +107,75 @@ const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { }; EXPORT_SYMBOL(amdtp_syt_intervals); +const unsigned int amdtp_rate_table[CIP_SFC_COUNT] = { + [CIP_SFC_32000] = 32000, + [CIP_SFC_44100] = 44100, + [CIP_SFC_48000] = 48000, + [CIP_SFC_88200] = 88200, + [CIP_SFC_96000] = 96000, + [CIP_SFC_176400] = 176400, + [CIP_SFC_192000] = 192000, +}; +EXPORT_SYMBOL(amdtp_rate_table); + +/** + * amdtp_stream_add_pcm_hw_constraints - add hw constraints for PCM substream + * @s: the AMDTP stream, which must be initialized. + * @runtime: the PCM substream runtime + */ +int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* AM824 in IEC 61883-6 can deliver 24bit data */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + goto end; + + /* + * Currently firewire-lib processes 16 packets in one software + * interrupt callback. This equals to 2msec but actually the + * interval of the interrupts has a jitter. + * Additionally, even if adding a constraint to fit period size to + * 2msec, actual calculated frames per period doesn't equal to 2msec, + * depending on sampling rate. + * Anyway, the interval to call snd_pcm_period_elapsed() cannot 2msec. + * Here let us use 5msec for safe period interrupt. + */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5000, UINT_MAX); + if (err < 0) + goto end; + + /* Non-Blocking stream has no more constraints */ + if (!(s->flags & CIP_BLOCKING)) + goto end; + + /* + * One AMDTP packet can include some frames. In blocking mode, the + * number equals to SYT_INTERVAL. So the number is 8, 16 or 32, + * depending on its sampling rate. For accurate period interrupt, it's + * preferrable to aligh period/buffer sizes to current SYT_INTERVAL. + * + * TODO: These constraints can be improved with propper rules. + * Currently apply LCM of SYT_INTEVALs. + */ + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); + if (err < 0) + goto end; + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); +end: + return err; +} +EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints); + /** - * amdtp_out_stream_set_parameters - set stream parameters - * @s: the AMDTP output stream to configure + * amdtp_stream_set_parameters - set stream parameters + * @s: the AMDTP stream to configure * @rate: the sample rate * @pcm_channels: the number of PCM samples in each data block, to be encoded * as AM824 multi-bit linear audio @@ -87,41 +184,30 @@ EXPORT_SYMBOL(amdtp_syt_intervals); * The parameters must be set before the stream is started, and must not be * changed while the stream is running. */ -void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s, - unsigned int rate, - unsigned int pcm_channels, - unsigned int midi_ports) +void amdtp_stream_set_parameters(struct amdtp_stream *s, + unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports) { - static const unsigned int rates[] = { - [CIP_SFC_32000] = 32000, - [CIP_SFC_44100] = 44100, - [CIP_SFC_48000] = 48000, - [CIP_SFC_88200] = 88200, - [CIP_SFC_96000] = 96000, - [CIP_SFC_176400] = 176400, - [CIP_SFC_192000] = 192000, - }; - unsigned int sfc; + unsigned int i, sfc, midi_channels; + + midi_channels = DIV_ROUND_UP(midi_ports, 8); - if (WARN_ON(amdtp_out_stream_running(s))) + if (WARN_ON(amdtp_stream_running(s)) | + WARN_ON(pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM) | + WARN_ON(midi_channels > AMDTP_MAX_CHANNELS_FOR_MIDI)) return; - for (sfc = 0; sfc < CIP_SFC_COUNT; ++sfc) - if (rates[sfc] == rate) + for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc) + if (amdtp_rate_table[sfc] == rate) goto sfc_found; WARN_ON(1); return; sfc_found: - s->dual_wire = (s->flags & CIP_HI_DUALWIRE) && sfc > CIP_SFC_96000; - if (s->dual_wire) { - sfc -= 2; - rate /= 2; - pcm_channels *= 2; - } - s->sfc = sfc; - s->data_block_quadlets = pcm_channels + DIV_ROUND_UP(midi_ports, 8); s->pcm_channels = pcm_channels; + s->sfc = sfc; + s->data_block_quadlets = s->pcm_channels + midi_channels; s->midi_ports = midi_ports; s->syt_interval = amdtp_syt_intervals[sfc]; @@ -131,48 +217,50 @@ sfc_found: if (s->flags & CIP_BLOCKING) /* additional buffering needed to adjust for no-data packets */ s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate; + + /* init the position map for PCM and MIDI channels */ + for (i = 0; i < pcm_channels; i++) + s->pcm_positions[i] = i; + s->midi_position = s->pcm_channels; } -EXPORT_SYMBOL(amdtp_out_stream_set_parameters); +EXPORT_SYMBOL(amdtp_stream_set_parameters); /** - * amdtp_out_stream_get_max_payload - get the stream's packet size - * @s: the AMDTP output stream + * amdtp_stream_get_max_payload - get the stream's packet size + * @s: the AMDTP stream * * This function must not be called before the stream has been configured - * with amdtp_out_stream_set_parameters(). + * with amdtp_stream_set_parameters(). */ -unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s) +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) { return 8 + s->syt_interval * s->data_block_quadlets * 4; } -EXPORT_SYMBOL(amdtp_out_stream_get_max_payload); +EXPORT_SYMBOL(amdtp_stream_get_max_payload); -static void amdtp_write_s16(struct amdtp_out_stream *s, +static void amdtp_write_s16(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); -static void amdtp_write_s32(struct amdtp_out_stream *s, +static void amdtp_write_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); -static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); -static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); +static void amdtp_read_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); /** - * amdtp_out_stream_set_pcm_format - set the PCM format - * @s: the AMDTP output stream to configure + * amdtp_stream_set_pcm_format - set the PCM format + * @s: the AMDTP stream to configure * @format: the format of the ALSA PCM device * * The sample format must be set after the other paramters (rate/PCM channels/ * MIDI) and before the stream is started, and must not be changed while the * stream is running. */ -void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, - snd_pcm_format_t format) +void amdtp_stream_set_pcm_format(struct amdtp_stream *s, + snd_pcm_format_t format) { - if (WARN_ON(amdtp_out_stream_running(s))) + if (WARN_ON(amdtp_stream_pcm_running(s))) return; switch (format) { @@ -180,41 +268,44 @@ void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, WARN_ON(1); /* fall through */ case SNDRV_PCM_FORMAT_S16: - if (s->dual_wire) - s->transfer_samples = amdtp_write_s16_dualwire; - else + if (s->direction == AMDTP_OUT_STREAM) { s->transfer_samples = amdtp_write_s16; - break; + break; + } + WARN_ON(1); + /* fall through */ case SNDRV_PCM_FORMAT_S32: - if (s->dual_wire) - s->transfer_samples = amdtp_write_s32_dualwire; - else + if (s->direction == AMDTP_OUT_STREAM) s->transfer_samples = amdtp_write_s32; + else + s->transfer_samples = amdtp_read_s32; break; } } -EXPORT_SYMBOL(amdtp_out_stream_set_pcm_format); +EXPORT_SYMBOL(amdtp_stream_set_pcm_format); /** - * amdtp_out_stream_pcm_prepare - prepare PCM device for running - * @s: the AMDTP output stream + * amdtp_stream_pcm_prepare - prepare PCM device for running + * @s: the AMDTP stream * * This function should be called from the PCM device's .prepare callback. */ -void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s) +void amdtp_stream_pcm_prepare(struct amdtp_stream *s) { tasklet_kill(&s->period_tasklet); s->pcm_buffer_pointer = 0; s->pcm_period_pointer = 0; s->pointer_flush = true; } -EXPORT_SYMBOL(amdtp_out_stream_pcm_prepare); +EXPORT_SYMBOL(amdtp_stream_pcm_prepare); -static unsigned int calculate_data_blocks(struct amdtp_out_stream *s) +static unsigned int calculate_data_blocks(struct amdtp_stream *s) { unsigned int phase, data_blocks; - if (!cip_sfc_is_base_44100(s->sfc)) { + if (s->flags & CIP_BLOCKING) + data_blocks = s->syt_interval; + else if (!cip_sfc_is_base_44100(s->sfc)) { /* Sample_rate / 8000 is an integer, and precomputed. */ data_blocks = s->data_block_state; } else { @@ -243,7 +334,7 @@ static unsigned int calculate_data_blocks(struct amdtp_out_stream *s) return data_blocks; } -static unsigned int calculate_syt(struct amdtp_out_stream *s, +static unsigned int calculate_syt(struct amdtp_stream *s, unsigned int cycle) { unsigned int syt_offset, phase, index, syt; @@ -280,175 +371,228 @@ static unsigned int calculate_syt(struct amdtp_out_stream *s, syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12; syt += syt_offset % TICKS_PER_CYCLE; - return syt & 0xffff; + return syt & CIP_SYT_MASK; } else { - return 0xffff; /* no info */ + return CIP_SYT_NO_INFO; } } -static void amdtp_write_s32(struct amdtp_out_stream *s, +static void amdtp_write_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames) { struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, frame_step, i, c; + unsigned int channels, remaining_frames, i, c; const u32 *src; channels = s->pcm_channels; src = (void *)runtime->dma_area + frames_to_bytes(runtime, s->pcm_buffer_pointer); remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; - frame_step = s->data_block_quadlets - channels; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src >> 8) | 0x40000000); + buffer[s->pcm_positions[c]] = + cpu_to_be32((*src >> 8) | 0x40000000); src++; - buffer++; } - buffer += frame_step; + buffer += s->data_block_quadlets; if (--remaining_frames == 0) src = (void *)runtime->dma_area; } } -static void amdtp_write_s16(struct amdtp_out_stream *s, +static void amdtp_write_s16(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames) { struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, frame_step, i, c; + unsigned int channels, remaining_frames, i, c; const u16 *src; channels = s->pcm_channels; src = (void *)runtime->dma_area + frames_to_bytes(runtime, s->pcm_buffer_pointer); remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; - frame_step = s->data_block_quadlets - channels; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src << 8) | 0x40000000); + buffer[s->pcm_positions[c]] = + cpu_to_be32((*src << 8) | 0x42000000); src++; - buffer++; } - buffer += frame_step; + buffer += s->data_block_quadlets; if (--remaining_frames == 0) src = (void *)runtime->dma_area; } } -static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) +static void amdtp_read_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) { struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, frame_adjust_1, frame_adjust_2, i, c; - const u32 *src; + unsigned int channels, remaining_frames, i, c; + u32 *dst; channels = s->pcm_channels; - src = (void *)runtime->dma_area + - s->pcm_buffer_pointer * (runtime->frame_bits / 8); - frame_adjust_1 = channels - 1; - frame_adjust_2 = 1 - (s->data_block_quadlets - channels); + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; - channels /= 2; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src >> 8) | 0x40000000); - src++; - buffer += 2; - } - buffer -= frame_adjust_1; - for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src >> 8) | 0x40000000); - src++; - buffer += 2; + *dst = be32_to_cpu(buffer[s->pcm_positions[c]]) << 8; + dst++; } - buffer -= frame_adjust_2; + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; } } -static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) +static void amdtp_fill_pcm_silence(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) { - struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, frame_adjust_1, frame_adjust_2, i, c; - const u16 *src; - - channels = s->pcm_channels; - src = (void *)runtime->dma_area + - s->pcm_buffer_pointer * (runtime->frame_bits / 8); - frame_adjust_1 = channels - 1; - frame_adjust_2 = 1 - (s->data_block_quadlets - channels); + unsigned int i, c; - channels /= 2; for (i = 0; i < frames; ++i) { - for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src << 8) | 0x40000000); - src++; - buffer += 2; - } - buffer -= frame_adjust_1; - for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src << 8) | 0x40000000); - src++; - buffer += 2; - } - buffer -= frame_adjust_2; + for (c = 0; c < s->pcm_channels; ++c) + buffer[s->pcm_positions[c]] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; } } -static void amdtp_fill_pcm_silence(struct amdtp_out_stream *s, - __be32 *buffer, unsigned int frames) +static void amdtp_fill_midi(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) { - unsigned int i, c; + unsigned int f, port; + u8 *b; + + for (f = 0; f < frames; f++) { + buffer[s->midi_position] = 0; + b = (u8 *)&buffer[s->midi_position]; + + port = (s->data_block_counter + f) % 8; + if ((f >= s->rx_blocks_for_midi) || + (s->midi[port] == NULL) || + (snd_rawmidi_transmit(s->midi[port], b + 1, 1) <= 0)) + b[0] = 0x80; + else + b[0] = 0x81; - for (i = 0; i < frames; ++i) { - for (c = 0; c < s->pcm_channels; ++c) - buffer[c] = cpu_to_be32(0x40000000); buffer += s->data_block_quadlets; } } -static void amdtp_fill_midi(struct amdtp_out_stream *s, +static void amdtp_pull_midi(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { - unsigned int i; + unsigned int f, port; + int len; + u8 *b; + + for (f = 0; f < frames; f++) { + port = (s->data_block_counter + f) % 8; + b = (u8 *)&buffer[s->midi_position]; + + len = b[0] - 0x80; + if ((1 <= len) && (len <= 3) && (s->midi[port])) + snd_rawmidi_receive(s->midi[port], b + 1, len); + + buffer += s->data_block_quadlets; + } +} + +static void update_pcm_pointers(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + unsigned int frames) +{ unsigned int ptr; + + ptr = s->pcm_buffer_pointer + frames; + if (ptr >= pcm->runtime->buffer_size) + ptr -= pcm->runtime->buffer_size; + ACCESS_ONCE(s->pcm_buffer_pointer) = ptr; + + s->pcm_period_pointer += frames; + if (s->pcm_period_pointer >= pcm->runtime->period_size) { + s->pcm_period_pointer -= pcm->runtime->period_size; + s->pointer_flush = false; + tasklet_hi_schedule(&s->period_tasklet); + } +} + +static void pcm_period_tasklet(unsigned long data) +{ + struct amdtp_stream *s = (void *)data; + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); - for (i = 0; i < frames; ++i) - buffer[s->pcm_channels + i * s->data_block_quadlets] = - cpu_to_be32(0x80000000); + if (pcm) + snd_pcm_period_elapsed(pcm); } -static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle) +static int queue_packet(struct amdtp_stream *s, + unsigned int header_length, + unsigned int payload_length, bool skip) +{ + struct fw_iso_packet p = {0}; + int err = 0; + + if (IS_ERR(s->context)) + goto end; + + p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); + p.tag = TAG_CIP; + p.header_length = header_length; + p.payload_length = (!skip) ? payload_length : 0; + p.skip = skip; + err = fw_iso_context_queue(s->context, &p, &s->buffer.iso_buffer, + s->buffer.packets[s->packet_index].offset); + if (err < 0) { + dev_err(&s->unit->device, "queueing error: %d\n", err); + goto end; + } + + if (++s->packet_index >= QUEUE_LENGTH) + s->packet_index = 0; +end: + return err; +} + +static inline int queue_out_packet(struct amdtp_stream *s, + unsigned int payload_length, bool skip) +{ + return queue_packet(s, OUT_PACKET_HEADER_SIZE, + payload_length, skip); +} + +static inline int queue_in_packet(struct amdtp_stream *s) +{ + return queue_packet(s, IN_PACKET_HEADER_SIZE, + amdtp_stream_get_max_payload(s), false); +} + +static void handle_out_packet(struct amdtp_stream *s, unsigned int syt) { __be32 *buffer; - unsigned int index, data_blocks, syt, ptr; + unsigned int data_blocks, payload_length; struct snd_pcm_substream *pcm; - struct fw_iso_packet packet; - int err; if (s->packet_index < 0) return; - index = s->packet_index; /* this module generate empty packet for 'no data' */ - syt = calculate_syt(s, cycle); - if (!(s->flags & CIP_BLOCKING)) + if (!(s->flags & CIP_BLOCKING) || (syt != CIP_SYT_NO_INFO)) data_blocks = calculate_data_blocks(s); - else if (syt != 0xffff) - data_blocks = s->syt_interval; else data_blocks = 0; - buffer = s->buffer.packets[index].buffer; + buffer = s->buffer.packets[s->packet_index].buffer; buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | - (s->data_block_quadlets << 16) | + (s->data_block_quadlets << AMDTP_DBS_SHIFT) | s->data_block_counter); buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 | - (s->sfc << AMDTP_FDF_SFC_SHIFT) | syt); + (s->sfc << CIP_FDF_SFC_SHIFT) | syt); buffer += 2; pcm = ACCESS_ONCE(s->pcm); @@ -461,58 +605,127 @@ static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle) s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; - packet.payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; - packet.interrupt = IS_ALIGNED(index + 1, INTERRUPT_INTERVAL); - packet.skip = 0; - packet.tag = TAG_CIP; - packet.sy = 0; - packet.header_length = 0; - - err = fw_iso_context_queue(s->context, &packet, &s->buffer.iso_buffer, - s->buffer.packets[index].offset); - if (err < 0) { - dev_err(&s->unit->device, "queueing error: %d\n", err); + payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; + if (queue_out_packet(s, payload_length, false) < 0) { s->packet_index = -1; - amdtp_out_stream_pcm_abort(s); + amdtp_stream_pcm_abort(s); return; } - if (++index >= QUEUE_LENGTH) - index = 0; - s->packet_index = index; + if (pcm) + update_pcm_pointers(s, pcm, data_blocks); +} - if (pcm) { - if (s->dual_wire) - data_blocks *= 2; - - ptr = s->pcm_buffer_pointer + data_blocks; - if (ptr >= pcm->runtime->buffer_size) - ptr -= pcm->runtime->buffer_size; - ACCESS_ONCE(s->pcm_buffer_pointer) = ptr; - - s->pcm_period_pointer += data_blocks; - if (s->pcm_period_pointer >= pcm->runtime->period_size) { - s->pcm_period_pointer -= pcm->runtime->period_size; - s->pointer_flush = false; - tasklet_hi_schedule(&s->period_tasklet); +static void handle_in_packet(struct amdtp_stream *s, + unsigned int payload_quadlets, + __be32 *buffer) +{ + u32 cip_header[2]; + unsigned int data_blocks, data_block_quadlets, data_block_counter, + dbc_interval; + struct snd_pcm_substream *pcm = NULL; + bool lost; + + cip_header[0] = be32_to_cpu(buffer[0]); + cip_header[1] = be32_to_cpu(buffer[1]); + + /* + * This module supports 'Two-quadlet CIP header with SYT field'. + * For convenience, also check FMT field is AM824 or not. + */ + if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH) || + ((cip_header[1] & CIP_FMT_MASK) != CIP_FMT_AM)) { + dev_info_ratelimited(&s->unit->device, + "Invalid CIP header for AMDTP: %08X:%08X\n", + cip_header[0], cip_header[1]); + goto end; + } + + /* Calculate data blocks */ + if (payload_quadlets < 3 || + ((cip_header[1] & CIP_FDF_MASK) == + (AMDTP_FDF_NO_DATA << CIP_FDF_SFC_SHIFT))) { + data_blocks = 0; + } else { + data_block_quadlets = + (cip_header[0] & AMDTP_DBS_MASK) >> AMDTP_DBS_SHIFT; + /* avoid division by zero */ + if (data_block_quadlets == 0) { + dev_info_ratelimited(&s->unit->device, + "Detect invalid value in dbs field: %08X\n", + cip_header[0]); + goto err; } + if (s->flags & CIP_WRONG_DBS) + data_block_quadlets = s->data_block_quadlets; + + data_blocks = (payload_quadlets - 2) / data_block_quadlets; } -} -static void pcm_period_tasklet(unsigned long data) -{ - struct amdtp_out_stream *s = (void *)data; - struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + /* Check data block counter continuity */ + data_block_counter = cip_header[0] & AMDTP_DBC_MASK; + if (data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) && + s->data_block_counter != UINT_MAX) + data_block_counter = s->data_block_counter; + + if (((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && data_block_counter == 0) || + (s->data_block_counter == UINT_MAX)) { + lost = false; + } else if (!(s->flags & CIP_DBC_IS_END_EVENT)) { + lost = data_block_counter != s->data_block_counter; + } else { + if ((data_blocks > 0) && (s->tx_dbc_interval > 0)) + dbc_interval = s->tx_dbc_interval; + else + dbc_interval = data_blocks; + + lost = data_block_counter != + ((s->data_block_counter + dbc_interval) & 0xff); + } + + if (lost) { + dev_info(&s->unit->device, + "Detect discontinuity of CIP: %02X %02X\n", + s->data_block_counter, data_block_counter); + goto err; + } + + if (data_blocks > 0) { + buffer += 2; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + s->transfer_samples(s, pcm, buffer, data_blocks); + + if (s->midi_ports) + amdtp_pull_midi(s, buffer, data_blocks); + } + + if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = data_block_counter; + else + s->data_block_counter = + (data_block_counter + data_blocks) & 0xff; +end: + if (queue_in_packet(s) < 0) + goto err; if (pcm) - snd_pcm_period_elapsed(pcm); + update_pcm_pointers(s, pcm, data_blocks); + + return; +err: + s->packet_index = -1; + amdtp_stream_pcm_abort(s); } -static void out_packet_callback(struct fw_iso_context *context, u32 cycle, - size_t header_length, void *header, void *data) +static void out_stream_callback(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, + void *private_data) { - struct amdtp_out_stream *s = data; - unsigned int i, packets = header_length / 4; + struct amdtp_stream *s = private_data; + unsigned int i, syt, packets = header_length / 4; /* * Compute the cycle of the last queued packet. @@ -521,43 +734,102 @@ static void out_packet_callback(struct fw_iso_context *context, u32 cycle, */ cycle += QUEUE_LENGTH - packets; - for (i = 0; i < packets; ++i) - queue_out_packet(s, ++cycle); + for (i = 0; i < packets; ++i) { + syt = calculate_syt(s, ++cycle); + handle_out_packet(s, syt); + } fw_iso_context_queue_flush(s->context); } -static int queue_initial_skip_packets(struct amdtp_out_stream *s) +static void in_stream_callback(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, + void *private_data) { - struct fw_iso_packet skip_packet = { - .skip = 1, - }; - unsigned int i; - int err; + struct amdtp_stream *s = private_data; + unsigned int p, syt, packets, payload_quadlets; + __be32 *buffer, *headers = header; - for (i = 0; i < QUEUE_LENGTH; ++i) { - skip_packet.interrupt = IS_ALIGNED(s->packet_index + 1, - INTERRUPT_INTERVAL); - err = fw_iso_context_queue(s->context, &skip_packet, NULL, 0); - if (err < 0) - return err; - if (++s->packet_index >= QUEUE_LENGTH) - s->packet_index = 0; + /* The number of packets in buffer */ + packets = header_length / IN_PACKET_HEADER_SIZE; + + for (p = 0; p < packets; p++) { + if (s->packet_index < 0) + break; + + buffer = s->buffer.packets[s->packet_index].buffer; + + /* Process sync slave stream */ + if (s->sync_slave && s->sync_slave->callbacked) { + syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK; + handle_out_packet(s->sync_slave, syt); + } + + /* The number of quadlets in this packet */ + payload_quadlets = + (be32_to_cpu(headers[p]) >> ISO_DATA_LENGTH_SHIFT) / 4; + handle_in_packet(s, payload_quadlets, buffer); } - return 0; + /* Queueing error or detecting discontinuity */ + if (s->packet_index < 0) { + /* Abort sync slave. */ + if (s->sync_slave) { + s->sync_slave->packet_index = -1; + amdtp_stream_pcm_abort(s->sync_slave); + } + return; + } + + /* when sync to device, flush the packets for slave stream */ + if (s->sync_slave && s->sync_slave->callbacked) + fw_iso_context_queue_flush(s->sync_slave->context); + + fw_iso_context_queue_flush(s->context); +} + +/* processing is done by master callback */ +static void slave_stream_callback(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, + void *private_data) +{ + return; +} + +/* this is executed one time */ +static void amdtp_stream_first_callback(struct fw_iso_context *context, + u32 cycle, size_t header_length, + void *header, void *private_data) +{ + struct amdtp_stream *s = private_data; + + /* + * For in-stream, first packet has come. + * For out-stream, prepared to transmit first packet + */ + s->callbacked = true; + wake_up(&s->callback_wait); + + if (s->direction == AMDTP_IN_STREAM) + context->callback.sc = in_stream_callback; + else if ((s->flags & CIP_BLOCKING) && (s->flags & CIP_SYNC_TO_DEVICE)) + context->callback.sc = slave_stream_callback; + else + context->callback.sc = out_stream_callback; + + context->callback.sc(context, cycle, header_length, header, s); } /** - * amdtp_out_stream_start - start sending packets - * @s: the AMDTP output stream to start + * amdtp_stream_start - start transferring packets + * @s: the AMDTP stream to start * @channel: the isochronous channel on the bus * @speed: firewire speed code * * The stream cannot be started until it has been configured with - * amdtp_out_stream_set_parameters() and amdtp_out_stream_set_pcm_format(), - * and it must be started before any PCM or MIDI device can be started. + * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI + * device can be started. */ -int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) +int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) { static const struct { unsigned int data_block; @@ -571,47 +843,72 @@ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) [CIP_SFC_88200] = { 0, 67 }, [CIP_SFC_176400] = { 0, 67 }, }; - int err; + unsigned int header_size; + enum dma_data_direction dir; + int type, tag, err; mutex_lock(&s->mutex); - if (WARN_ON(amdtp_out_stream_running(s) || - (!s->pcm_channels && !s->midi_ports))) { + if (WARN_ON(amdtp_stream_running(s) || + (s->data_block_quadlets < 1))) { err = -EBADFD; goto err_unlock; } + if (s->direction == AMDTP_IN_STREAM && + s->flags & CIP_SKIP_INIT_DBC_CHECK) + s->data_block_counter = UINT_MAX; + else + s->data_block_counter = 0; s->data_block_state = initial_state[s->sfc].data_block; s->syt_offset_state = initial_state[s->sfc].syt_offset; s->last_syt_offset = TICKS_PER_CYCLE; + /* initialize packet buffer */ + if (s->direction == AMDTP_IN_STREAM) { + dir = DMA_FROM_DEVICE; + type = FW_ISO_CONTEXT_RECEIVE; + header_size = IN_PACKET_HEADER_SIZE; + } else { + dir = DMA_TO_DEVICE; + type = FW_ISO_CONTEXT_TRANSMIT; + header_size = OUT_PACKET_HEADER_SIZE; + } err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH, - amdtp_out_stream_get_max_payload(s), - DMA_TO_DEVICE); + amdtp_stream_get_max_payload(s), dir); if (err < 0) goto err_unlock; s->context = fw_iso_context_create(fw_parent_device(s->unit)->card, - FW_ISO_CONTEXT_TRANSMIT, - channel, speed, 0, - out_packet_callback, s); + type, channel, speed, header_size, + amdtp_stream_first_callback, s); if (IS_ERR(s->context)) { err = PTR_ERR(s->context); if (err == -EBUSY) dev_err(&s->unit->device, - "no free output stream on this controller\n"); + "no free stream on this controller\n"); goto err_buffer; } - amdtp_out_stream_update(s); + amdtp_stream_update(s); s->packet_index = 0; - s->data_block_counter = 0; - err = queue_initial_skip_packets(s); - if (err < 0) - goto err_context; + do { + if (s->direction == AMDTP_IN_STREAM) + err = queue_in_packet(s); + else + err = queue_out_packet(s, 0, true); + if (err < 0) + goto err_context; + } while (s->packet_index > 0); - err = fw_iso_context_start(s->context, -1, 0, 0); + /* NOTE: TAG1 matches CIP. This just affects in stream. */ + tag = FW_ISO_CONTEXT_MATCH_TAG1; + if (s->flags & CIP_EMPTY_WITH_TAG0) + tag |= FW_ISO_CONTEXT_MATCH_TAG0; + + s->callbacked = false; + err = fw_iso_context_start(s->context, -1, 0, tag); if (err < 0) goto err_context; @@ -629,49 +926,49 @@ err_unlock: return err; } -EXPORT_SYMBOL(amdtp_out_stream_start); +EXPORT_SYMBOL(amdtp_stream_start); /** - * amdtp_out_stream_pcm_pointer - get the PCM buffer position - * @s: the AMDTP output stream that transports the PCM data + * amdtp_stream_pcm_pointer - get the PCM buffer position + * @s: the AMDTP stream that transports the PCM data * * Returns the current buffer position, in frames. */ -unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s) +unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s) { /* this optimization is allowed to be racy */ - if (s->pointer_flush) + if (s->pointer_flush && amdtp_stream_running(s)) fw_iso_context_flush_completions(s->context); else s->pointer_flush = true; return ACCESS_ONCE(s->pcm_buffer_pointer); } -EXPORT_SYMBOL(amdtp_out_stream_pcm_pointer); +EXPORT_SYMBOL(amdtp_stream_pcm_pointer); /** - * amdtp_out_stream_update - update the stream after a bus reset - * @s: the AMDTP output stream + * amdtp_stream_update - update the stream after a bus reset + * @s: the AMDTP stream */ -void amdtp_out_stream_update(struct amdtp_out_stream *s) +void amdtp_stream_update(struct amdtp_stream *s) { ACCESS_ONCE(s->source_node_id_field) = (fw_parent_device(s->unit)->card->node_id & 0x3f) << 24; } -EXPORT_SYMBOL(amdtp_out_stream_update); +EXPORT_SYMBOL(amdtp_stream_update); /** - * amdtp_out_stream_stop - stop sending packets - * @s: the AMDTP output stream to stop + * amdtp_stream_stop - stop sending packets + * @s: the AMDTP stream to stop * * All PCM and MIDI devices of the stream must be stopped before the stream * itself can be stopped. */ -void amdtp_out_stream_stop(struct amdtp_out_stream *s) +void amdtp_stream_stop(struct amdtp_stream *s) { mutex_lock(&s->mutex); - if (!amdtp_out_stream_running(s)) { + if (!amdtp_stream_running(s)) { mutex_unlock(&s->mutex); return; } @@ -682,18 +979,20 @@ void amdtp_out_stream_stop(struct amdtp_out_stream *s) s->context = ERR_PTR(-1); iso_packets_buffer_destroy(&s->buffer, s->unit); + s->callbacked = false; + mutex_unlock(&s->mutex); } -EXPORT_SYMBOL(amdtp_out_stream_stop); +EXPORT_SYMBOL(amdtp_stream_stop); /** - * amdtp_out_stream_pcm_abort - abort the running PCM device + * amdtp_stream_pcm_abort - abort the running PCM device * @s: the AMDTP stream about to be stopped * * If the isochronous stream needs to be stopped asynchronously, call this * function first to stop the PCM device. */ -void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s) +void amdtp_stream_pcm_abort(struct amdtp_stream *s) { struct snd_pcm_substream *pcm; @@ -705,4 +1004,4 @@ void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s) snd_pcm_stream_unlock_irq(pcm); } } -EXPORT_SYMBOL(amdtp_out_stream_pcm_abort); +EXPORT_SYMBOL(amdtp_stream_pcm_abort); diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 2746ecd291af..d8ee7b0e9386 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -8,7 +8,7 @@ #include "packets-buffer.h" /** - * enum cip_out_flags - describes details of the streaming protocol + * enum cip_flags - describes details of the streaming protocol * @CIP_NONBLOCKING: In non-blocking mode, each packet contains * sample_rate/8000 samples, with rounding up or down to adjust * for clock skew and left-over fractional samples. This should @@ -16,15 +16,30 @@ * @CIP_BLOCKING: In blocking mode, each packet contains either zero or * SYT_INTERVAL samples, with these two types alternating so that * the overall sample rate comes out right. - * @CIP_HI_DUALWIRE: At rates above 96 kHz, pretend that the stream runs - * at half the actual sample rate with twice the number of channels; - * two samples of a channel are stored consecutively in the packet. - * Requires blocking mode and SYT_INTERVAL-aligned PCM buffer size. + * @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is + * generated by in packets. Defaultly this driver generates timestamp. + * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. + * @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet + * corresponds to the end of event in the packet. Out of IEC 61883. + * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. + * The value of data_block_quadlets is used instead of reported value. + * @SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is + * skipped for detecting discontinuity. + * @CIP_SKIP_INIT_DBC_CHECK: Only for in-stream. The value of dbc in first + * packet is not continuous from an initial value. + * @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty + * packet is wrong but the others are correct. */ -enum cip_out_flags { - CIP_NONBLOCKING = 0x00, - CIP_BLOCKING = 0x01, - CIP_HI_DUALWIRE = 0x02, +enum cip_flags { + CIP_NONBLOCKING = 0x00, + CIP_BLOCKING = 0x01, + CIP_SYNC_TO_DEVICE = 0x02, + CIP_EMPTY_WITH_TAG0 = 0x04, + CIP_DBC_IS_END_EVENT = 0x08, + CIP_WRONG_DBS = 0x10, + CIP_SKIP_DBC_ZERO_CHECK = 0x20, + CIP_SKIP_INIT_DBC_CHECK = 0x40, + CIP_EMPTY_HAS_WRONG_DBC = 0x80, }; /** @@ -41,27 +56,55 @@ enum cip_sfc { CIP_SFC_COUNT }; +#define AMDTP_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32 + #define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \ SNDRV_PCM_FMTBIT_S32) + +/* + * This module supports maximum 64 PCM channels for one PCM stream + * This is for our convenience. + */ +#define AMDTP_MAX_CHANNELS_FOR_PCM 64 + +/* + * AMDTP packet can include channels for MIDI conformant data. + * Each MIDI conformant data channel includes 8 MPX-MIDI data stream. + * Each MPX-MIDI data stream includes one data stream from/to MIDI ports. + * + * This module supports maximum 1 MIDI conformant data channels. + * Then this AMDTP packets can transfer maximum 8 MIDI data streams. + */ +#define AMDTP_MAX_CHANNELS_FOR_MIDI 1 + struct fw_unit; struct fw_iso_context; struct snd_pcm_substream; +struct snd_pcm_runtime; +struct snd_rawmidi_substream; -struct amdtp_out_stream { +enum amdtp_stream_direction { + AMDTP_OUT_STREAM = 0, + AMDTP_IN_STREAM +}; + +struct amdtp_stream { struct fw_unit *unit; - enum cip_out_flags flags; + enum cip_flags flags; + enum amdtp_stream_direction direction; struct fw_iso_context *context; struct mutex mutex; enum cip_sfc sfc; - bool dual_wire; unsigned int data_block_quadlets; unsigned int pcm_channels; unsigned int midi_ports; - void (*transfer_samples)(struct amdtp_out_stream *s, + void (*transfer_samples)(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); + u8 pcm_positions[AMDTP_MAX_CHANNELS_FOR_PCM]; + u8 midi_position; unsigned int syt_interval; unsigned int transfer_delay; @@ -82,65 +125,148 @@ struct amdtp_out_stream { unsigned int pcm_buffer_pointer; unsigned int pcm_period_pointer; bool pointer_flush; + + struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8]; + + /* quirk: fixed interval of dbc between previos/current packets. */ + unsigned int tx_dbc_interval; + + /* quirk: the first count of data blocks in an rx packet for MIDI */ + unsigned int rx_blocks_for_midi; + + bool callbacked; + wait_queue_head_t callback_wait; + struct amdtp_stream *sync_slave; }; -int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, - enum cip_out_flags flags); -void amdtp_out_stream_destroy(struct amdtp_out_stream *s); +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + enum cip_flags flags); +void amdtp_stream_destroy(struct amdtp_stream *s); -void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s, - unsigned int rate, - unsigned int pcm_channels, - unsigned int midi_ports); -unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s); +void amdtp_stream_set_parameters(struct amdtp_stream *s, + unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports); +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s); -int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed); -void amdtp_out_stream_update(struct amdtp_out_stream *s); -void amdtp_out_stream_stop(struct amdtp_out_stream *s); +int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed); +void amdtp_stream_update(struct amdtp_stream *s); +void amdtp_stream_stop(struct amdtp_stream *s); -void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, - snd_pcm_format_t format); -void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s); -unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s); -void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s); +int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_stream_set_pcm_format(struct amdtp_stream *s, + snd_pcm_format_t format); +void amdtp_stream_pcm_prepare(struct amdtp_stream *s); +unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s); +void amdtp_stream_pcm_abort(struct amdtp_stream *s); extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT]; +extern const unsigned int amdtp_rate_table[CIP_SFC_COUNT]; -static inline bool amdtp_out_stream_running(struct amdtp_out_stream *s) +/** + * amdtp_stream_running - check stream is running or not + * @s: the AMDTP stream + * + * If this function returns true, the stream is running. + */ +static inline bool amdtp_stream_running(struct amdtp_stream *s) { return !IS_ERR(s->context); } /** - * amdtp_out_streaming_error - check for streaming error - * @s: the AMDTP output stream + * amdtp_streaming_error - check for streaming error + * @s: the AMDTP stream * * If this function returns true, the stream's packet queue has stopped due to * an asynchronous error. */ -static inline bool amdtp_out_streaming_error(struct amdtp_out_stream *s) +static inline bool amdtp_streaming_error(struct amdtp_stream *s) { return s->packet_index < 0; } /** - * amdtp_out_stream_pcm_trigger - start/stop playback from a PCM device - * @s: the AMDTP output stream + * amdtp_stream_pcm_running - check PCM substream is running or not + * @s: the AMDTP stream + * + * If this function returns true, PCM substream in the AMDTP stream is running. + */ +static inline bool amdtp_stream_pcm_running(struct amdtp_stream *s) +{ + return !!s->pcm; +} + +/** + * amdtp_stream_pcm_trigger - start/stop playback from a PCM device + * @s: the AMDTP stream * @pcm: the PCM device to be started, or %NULL to stop the current device * * Call this function on a running isochronous stream to enable the actual * transmission of PCM data. This function should be called from the PCM * device's .trigger callback. */ -static inline void amdtp_out_stream_pcm_trigger(struct amdtp_out_stream *s, - struct snd_pcm_substream *pcm) +static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s, + struct snd_pcm_substream *pcm) { ACCESS_ONCE(s->pcm) = pcm; } +/** + * amdtp_stream_midi_trigger - start/stop playback/capture with a MIDI device + * @s: the AMDTP stream + * @port: index of MIDI port + * @midi: the MIDI device to be started, or %NULL to stop the current device + * + * Call this function on a running isochronous stream to enable the actual + * transmission of MIDI data. This function should be called from the MIDI + * device's .trigger callback. + */ +static inline void amdtp_stream_midi_trigger(struct amdtp_stream *s, + unsigned int port, + struct snd_rawmidi_substream *midi) +{ + if (port < s->midi_ports) + ACCESS_ONCE(s->midi[port]) = midi; +} + static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc) { return sfc & 1; } +static inline void amdtp_stream_set_sync(enum cip_flags sync_mode, + struct amdtp_stream *master, + struct amdtp_stream *slave) +{ + if (sync_mode == CIP_SYNC_TO_DEVICE) { + master->flags |= CIP_SYNC_TO_DEVICE; + slave->flags |= CIP_SYNC_TO_DEVICE; + master->sync_slave = slave; + } else { + master->flags &= ~CIP_SYNC_TO_DEVICE; + slave->flags &= ~CIP_SYNC_TO_DEVICE; + master->sync_slave = NULL; + } + + slave->sync_slave = NULL; +} + +/** + * amdtp_stream_wait_callback - sleep till callbacked or timeout + * @s: the AMDTP stream + * @timeout: msec till timeout + * + * If this function return false, the AMDTP stream should be stopped. + */ +static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s, + unsigned int timeout) +{ + return wait_event_timeout(s->callback_wait, + s->callbacked == true, + msecs_to_jiffies(timeout)) > 0; +} + #endif diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile new file mode 100644 index 000000000000..6cf470c80d1f --- /dev/null +++ b/sound/firewire/bebob/Makefile @@ -0,0 +1,4 @@ +snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ + bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \ + bebob_focusrite.o bebob_maudio.o bebob.o +obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c new file mode 100644 index 000000000000..fc19c99654aa --- /dev/null +++ b/sound/firewire/bebob/bebob.c @@ -0,0 +1,471 @@ +/* + * bebob.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire + * devices with DM1000/DM1100/DM1500 chipset. It gives common way for host + * system to handle BeBoB based devices. + */ + +#include "bebob.h" + +MODULE_DESCRIPTION("BridgeCo BeBoB driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable BeBoB sound card"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +/* Offsets from information register. */ +#define INFO_OFFSET_GUID 0x10 +#define INFO_OFFSET_HW_MODEL_ID 0x18 +#define INFO_OFFSET_HW_MODEL_REVISION 0x1c + +#define VEN_EDIROL 0x000040ab +#define VEN_PRESONUS 0x00000a92 +#define VEN_BRIDGECO 0x000007f5 +#define VEN_MACKIE 0x0000000f +#define VEN_STANTON 0x00001260 +#define VEN_TASCAM 0x0000022e +#define VEN_BEHRINGER 0x00001564 +#define VEN_APOGEE 0x000003db +#define VEN_ESI 0x00000f1b +#define VEN_ACOUSTIC 0x00000002 +#define VEN_CME 0x0000000a +#define VEN_PHONIC 0x00001496 +#define VEN_LYNX 0x000019e5 +#define VEN_ICON 0x00001a9e +#define VEN_PRISMSOUND 0x00001198 +#define VEN_TERRATEC 0x00000aac +#define VEN_YAMAHA 0x0000a0de +#define VEN_FOCUSRITE 0x0000130e +#define VEN_MAUDIO1 0x00000d6c +#define VEN_MAUDIO2 0x000007f5 + +#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000 +#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 +#define MODEL_MAUDIO_FW1814 0x00010071 +#define MODEL_MAUDIO_PROJECTMIX 0x00010091 + +static int +name_device(struct snd_bebob *bebob, unsigned int vendor_id) +{ + struct fw_device *fw_dev = fw_parent_device(bebob->unit); + char vendor[24] = {0}; + char model[32] = {0}; + u32 hw_id; + u32 data[2] = {0}; + u32 revision; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(bebob->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + /* get hardware id */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID, + &hw_id); + if (err < 0) + goto end; + + /* get hardware revision */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION, + &revision); + if (err < 0) + goto end; + + /* get GUID */ + err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID, + data, sizeof(data)); + if (err < 0) + goto end; + + strcpy(bebob->card->driver, "BeBoB"); + strcpy(bebob->card->shortname, model); + strcpy(bebob->card->mixername, model); + snprintf(bebob->card->longname, sizeof(bebob->card->longname), + "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d", + vendor, model, hw_id, revision, + data[0], data[1], dev_name(&bebob->unit->device), + 100 << fw_dev->max_speed); +end: + return err; +} + +static void +bebob_card_free(struct snd_card *card) +{ + struct snd_bebob *bebob = card->private_data; + + if (bebob->card_index >= 0) { + mutex_lock(&devices_mutex); + clear_bit(bebob->card_index, devices_used); + mutex_unlock(&devices_mutex); + } + + mutex_destroy(&bebob->mutex); +} + +static const struct snd_bebob_spec * +get_saffire_spec(struct fw_unit *unit) +{ + char name[24] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return NULL; + + if (strcmp(name, "SaffireLE") == 0) + return &saffire_le_spec; + else + return &saffire_spec; +} + +static bool +check_audiophile_booted(struct fw_unit *unit) +{ + char name[24] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return false; + + return strncmp(name, "FW Audiophile Bootloader", 15) != 0; +} + +static int +bebob_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_bebob *bebob; + const struct snd_bebob_spec *spec; + unsigned int card_index; + int err; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; card_index++) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + err = -ENOENT; + goto end; + } + + if ((entry->vendor_id == VEN_FOCUSRITE) && + (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)) + spec = get_saffire_spec(unit); + else if ((entry->vendor_id == VEN_MAUDIO1) && + (entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH) && + !check_audiophile_booted(unit)) + spec = NULL; + else + spec = (const struct snd_bebob_spec *)entry->driver_data; + + if (spec == NULL) { + if ((entry->vendor_id == VEN_MAUDIO1) || + (entry->vendor_id == VEN_MAUDIO2)) + err = snd_bebob_maudio_load_firmware(unit); + else + err = -ENOSYS; + goto end; + } + + err = snd_card_new(&unit->device, index[card_index], id[card_index], + THIS_MODULE, sizeof(struct snd_bebob), &card); + if (err < 0) + goto end; + bebob = card->private_data; + bebob->card_index = card_index; + set_bit(card_index, devices_used); + card->private_free = bebob_card_free; + + bebob->card = card; + bebob->unit = unit; + bebob->spec = spec; + mutex_init(&bebob->mutex); + spin_lock_init(&bebob->lock); + init_waitqueue_head(&bebob->hwdep_wait); + + err = name_device(bebob, entry->vendor_id); + if (err < 0) + goto error; + + if ((entry->vendor_id == VEN_MAUDIO1) && + (entry->model_id == MODEL_MAUDIO_FW1814)) + err = snd_bebob_maudio_special_discover(bebob, true); + else if ((entry->vendor_id == VEN_MAUDIO1) && + (entry->model_id == MODEL_MAUDIO_PROJECTMIX)) + err = snd_bebob_maudio_special_discover(bebob, false); + else + err = snd_bebob_stream_discover(bebob); + if (err < 0) + goto error; + + snd_bebob_proc_init(bebob); + + if ((bebob->midi_input_ports > 0) || + (bebob->midi_output_ports > 0)) { + err = snd_bebob_create_midi_devices(bebob); + if (err < 0) + goto error; + } + + err = snd_bebob_create_pcm_devices(bebob); + if (err < 0) + goto error; + + err = snd_bebob_create_hwdep_device(bebob); + if (err < 0) + goto error; + + err = snd_bebob_stream_init_duplex(bebob); + if (err < 0) + goto error; + + if (!bebob->maudio_special_quirk) { + err = snd_card_register(card); + if (err < 0) { + snd_bebob_stream_destroy_duplex(bebob); + goto error; + } + } else { + /* + * This is a workaround. This bus reset seems to have an effect + * to make devices correctly handling transactions. Without + * this, the devices have gap_count mismatch. This causes much + * failure of transaction. + * + * Just after registration, user-land application receive + * signals from dbus and starts I/Os. To avoid I/Os till the + * future bus reset, registration is done in next update(). + */ + bebob->deferred_registration = true; + fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card, + false, true); + } + + dev_set_drvdata(&unit->device, bebob); +end: + mutex_unlock(&devices_mutex); + return err; +error: + mutex_unlock(&devices_mutex); + snd_card_free(card); + return err; +} + +static void +bebob_update(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + + fcp_bus_reset(bebob->unit); + snd_bebob_stream_update_duplex(bebob); + + if (bebob->deferred_registration) { + if (snd_card_register(bebob->card) < 0) { + snd_bebob_stream_destroy_duplex(bebob); + snd_card_free(bebob->card); + } + bebob->deferred_registration = false; + } +} + +static void bebob_remove(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + + kfree(bebob->maudio_special_quirk); + + snd_bebob_stream_destroy_duplex(bebob); + snd_card_disconnect(bebob->card); + snd_card_free_when_closed(bebob->card); +} + +static struct snd_bebob_rate_spec normal_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate +}; +static const struct snd_bebob_spec spec_normal = { + .clock = NULL, + .rate = &normal_rate_spec, + .meter = NULL +}; + +static const struct ieee1394_device_id bebob_id_table[] = { + /* Edirol, FA-66 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal), + /* Edirol, FA-101 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal), + /* Presonus, FIREBOX */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal), + /* PreSonus, FIREPOD/FP10 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal), + /* PreSonus, Inspire1394 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal), + /* BridgeCo, RDAudio1 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal), + /* BridgeCo, Audio5 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal), + /* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal), + /* Mackie, d.2 (Firewire Option) */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal), + /* Stanton, ScratchAmp */ + SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal), + /* Tascam, IF-FW DM */ + SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal), + /* Behringer, XENIX UFX 1204 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal), + /* Behringer, XENIX UFX 1604 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal), + /* Behringer, Digital Mixer X32 series (X-UF Card) */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal), + /* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */ + /* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal), + /* Apogee Electronics, Ensemble */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00001eee, &spec_normal), + /* ESI, Quatafire610 */ + SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal), + /* AcousticReality, eARMasterOne */ + SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, &spec_normal), + /* CME, MatrixKFW */ + SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal), + /* Phonic, Helix Board 12 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal), + /* Phonic, Helix Board 18 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal), + /* Phonic, Helix Board 24 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal), + /* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal), + /* Lynx, Aurora 8/16 (LT-FW) */ + SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal), + /* ICON, FireXon */ + SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal), + /* PrismSound, Orpheus */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal), + /* PrismSound, ADA-8XR */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal), + /* TerraTec Electronic GmbH, PHASE 88 Rack FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec), + /* TerraTec Electronic GmbH, PHASE 24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &phase24_series_spec), + /* TerraTec Electronic GmbH, Phase X24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &phase24_series_spec), + /* TerraTec Electronic GmbH, EWS MIC2/MIC8 */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal), + /* Terratec Electronic GmbH, Aureon 7.1 Firewire */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal), + /* Yamaha, GO44 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_go_spec), + /* YAMAHA, GO46 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_go_spec), + /* Focusrite, SaffirePro 26 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec), + /* Focusrite, SaffirePro 10 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000006, &saffirepro_10_spec), + /* Focusrite, Saffire(no label and LE) */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, + &saffire_spec), + /* M-Audio, Firewire 410 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, NULL), /* bootloader */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec), + /* M-Audio, Firewire Audiophile */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH, + &maudio_audiophile_spec), + /* M-Audio, Firewire Solo */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, &maudio_solo_spec), + /* M-Audio, Ozonic */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, &maudio_ozonic_spec), + /* M-Audio NRV10 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec), + /* M-Audio, ProFireLightbridge */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal), + /* Firewire 1814 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, NULL), /* bootloader */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814, + &maudio_special_spec), + /* M-Audio ProjectMix */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX, + &maudio_special_spec), + /* IDs are unknown but able to be supported */ + /* Apogee, Mini-ME Firewire */ + /* Apogee, Mini-DAC Firewire */ + /* Behringer, F-Control Audio 1616 */ + /* Behringer, F-Control Audio 610 */ + /* Cakawalk, Sonar Power Studio 66 */ + /* CME, UF400e */ + /* ESI, Quotafire XL */ + /* Infrasonic, DewX */ + /* Infrasonic, Windy6 */ + /* Mackie, Digital X Bus x.200 */ + /* Mackie, Digital X Bus x.400 */ + /* Phonic, HB 12 */ + /* Phonic, HB 24 */ + /* Phonic, HB 18 */ + /* Phonic, FireFly 202 */ + /* Phonic, FireFly 302 */ + /* Rolf Spuler, Firewire Guitar */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, bebob_id_table); + +static struct fw_driver bebob_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-bebob", + .bus = &fw_bus_type, + }, + .probe = bebob_probe, + .update = bebob_update, + .remove = bebob_remove, + .id_table = bebob_id_table, +}; + +static int __init +snd_bebob_init(void) +{ + return driver_register(&bebob_driver.driver); +} + +static void __exit +snd_bebob_exit(void) +{ + driver_unregister(&bebob_driver.driver); +} + +module_init(snd_bebob_init); +module_exit(snd_bebob_exit); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h new file mode 100644 index 000000000000..e13eef99c27a --- /dev/null +++ b/sound/firewire/bebob/bebob.h @@ -0,0 +1,255 @@ +/* + * bebob.h - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_BEBOB_H_INCLUDED +#define SOUND_BEBOB_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp.h" +#include "../cmp.h" + +/* basic register addresses on DM1000/DM1100/DM1500 */ +#define BEBOB_ADDR_REG_INFO 0xffffc8020000ULL +#define BEBOB_ADDR_REG_REQ 0xffffc8021000ULL + +struct snd_bebob; + +#define SND_BEBOB_STRM_FMT_ENTRIES 7 +struct snd_bebob_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +/* this is a lookup table for index of stream formations */ +extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES]; + +/* device specific operations */ +#define SND_BEBOB_CLOCK_INTERNAL "Internal" +struct snd_bebob_clock_spec { + unsigned int num; + char *const *labels; + int (*get)(struct snd_bebob *bebob, unsigned int *id); +}; +struct snd_bebob_rate_spec { + int (*get)(struct snd_bebob *bebob, unsigned int *rate); + int (*set)(struct snd_bebob *bebob, unsigned int rate); +}; +struct snd_bebob_meter_spec { + unsigned int num; + char *const *labels; + int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size); +}; +struct snd_bebob_spec { + struct snd_bebob_clock_spec *clock; + struct snd_bebob_rate_spec *rate; + struct snd_bebob_meter_spec *meter; +}; + +struct snd_bebob { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; + + const struct snd_bebob_spec *spec; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + /* for bus reset quirk */ + struct completion bus_reset; + bool connected; + + struct amdtp_stream *master; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + atomic_t capture_substreams; + atomic_t playback_substreams; + + struct snd_bebob_stream_formation + tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob_stream_formation + rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + + int sync_input_plug; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* for M-Audio special devices */ + void *maudio_special_quirk; + bool deferred_registration; +}; + +static inline int +snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size) +{ + return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + buf, size, 0); +} + +static inline int +snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf) +{ + return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + (void *)buf, sizeof(u32), 0); +} + +/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 1394TA) */ +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num); +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num); + +/* + * AVC command extensions, AV/C Unit and Subunit, Revision 17 + * (Nov 2003, BridgeCo) + */ +#define AVC_BRIDGECO_ADDR_BYTES 6 +enum avc_bridgeco_plug_dir { + AVC_BRIDGECO_PLUG_DIR_IN = 0x00, + AVC_BRIDGECO_PLUG_DIR_OUT = 0x01 +}; +enum avc_bridgeco_plug_mode { + AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00, + AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01, + AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02 +}; +enum avc_bridgeco_plug_unit { + AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00, + AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01, + AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02 +}; +enum avc_bridgeco_plug_type { + AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00, + AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01, + AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02, + AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03, + AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04, + AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05 +}; +static inline void +avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + enum avc_bridgeco_plug_unit unit, + unsigned int pid) +{ + buf[0] = 0xff; /* Unit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT; + buf[3] = unit; + buf[4] = 0xff & pid; + buf[5] = 0xff; /* reserved */ +} +static inline void +avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + unsigned int pid) +{ + buf[0] = 0x60; /* Music subunit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT; + buf[3] = 0xff & pid; + buf[4] = 0xff; /* reserved */ + buf[5] = 0xff; /* reserved */ +} +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len); +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type); +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type); +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 input[7]); +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid); + +/* for AMDTP streaming */ +int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate); +int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate); +int snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, + bool *internal); +int snd_bebob_stream_discover(struct snd_bebob *bebob); +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob); +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate); +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob); + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob); +int snd_bebob_stream_lock_try(struct snd_bebob *bebob); +void snd_bebob_stream_lock_release(struct snd_bebob *bebob); + +void snd_bebob_proc_init(struct snd_bebob *bebob); + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob); + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob); + +int snd_bebob_create_hwdep_device(struct snd_bebob *bebob); + +/* model specific operations */ +extern struct snd_bebob_spec phase88_rack_spec; +extern struct snd_bebob_spec phase24_series_spec; +extern struct snd_bebob_spec yamaha_go_spec; +extern struct snd_bebob_spec saffirepro_26_spec; +extern struct snd_bebob_spec saffirepro_10_spec; +extern struct snd_bebob_spec saffire_le_spec; +extern struct snd_bebob_spec saffire_spec; +extern struct snd_bebob_spec maudio_fw410_spec; +extern struct snd_bebob_spec maudio_audiophile_spec; +extern struct snd_bebob_spec maudio_solo_spec; +extern struct snd_bebob_spec maudio_ozonic_spec; +extern struct snd_bebob_spec maudio_nrv10_spec; +extern struct snd_bebob_spec maudio_special_spec; +int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814); +int snd_bebob_maudio_load_firmware(struct fw_unit *unit); + +#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor, \ + .model_id = model, \ + .driver_data = (kernel_ulong_t)data \ +} + +#endif diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c new file mode 100644 index 000000000000..9402cc15dbc1 --- /dev/null +++ b/sound/firewire/bebob/bebob_command.c @@ -0,0 +1,282 @@ +/* + * bebob_command.c - driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff & num; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err > 0 && err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (err > 0) + err = 0; + + kfree(buf); + return err; +} + +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(8)); + if (err > 0 && err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *num = buf[7]; + err = 0; +end: + kfree(buf); + return err; +} + +static inline void +avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr) +{ + buf[1] = addr[0]; + memcpy(buf + 4, addr + 1, 5); +} + +static inline void +avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr, + unsigned int itype) +{ + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */ + buf[3] = 0xc0; /* BridgeCo extension */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[9] = itype; /* info type */ +} + +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug type'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00); + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[10]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len) +{ + int err; + + /* Info type is 'channel position'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03); + + err = fcp_avc_transaction(unit, buf, 12, buf, 256, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(9)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* Pick up specific data. */ + memmove(buf, buf + 10, err - 10); + err = 0; +end: + return err; +} + +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type) +{ + u8 *buf; + int err; + + /* section info includes charactors but this module don't need it */ + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'section info'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07); + buf[10] = 0xff & ++id; /* section id */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9) | BIT(10)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[11]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7]) +{ + int err; + u8 *buf; + + buf = kzalloc(18, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug input'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05); + + err = fcp_avc_transaction(unit, buf, 16, buf, 16, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + memcpy(input, buf + 10, 5); + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid) +{ + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */ + buf[3] = 0xc1; /* Bridgeco extension - List Request */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[10] = 0xff & eid; /* Entry ID */ + + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(10)); + if ((err >= 0) && (err < 12)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + else if (buf[10] != eid) + err = -EIO; + if (err < 0) + goto end; + + /* Pick up 'stream format info'. */ + memmove(buf, buf + 11, err - 11); + *len = err - 11; + err = 0; +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c new file mode 100644 index 000000000000..45a0eed6d5b1 --- /dev/null +++ b/sound/firewire/bebob/bebob_focusrite.c @@ -0,0 +1,279 @@ +/* + * bebob_focusrite.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define ANA_IN "Analog In" +#define DIG_IN "Digital In" +#define ANA_OUT "Analog Out" +#define DIG_OUT "Digital Out" +#define STM_IN "Stream In" + +#define SAFFIRE_ADDRESS_BASE 0x000100000000ULL + +#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8 +#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174 + +/* whether sync to external device or not */ +#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c +#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432 +#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164 + +#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIRE_CLOCK_SOURCE_SPDIF 1 + +/* '1' is absent, why... */ +#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 +#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5 + +/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */ +#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4 + +/* saffirepro has its own parameter for sampling frequency */ +#define SAFFIREPRO_RATE_NOREBOOT 0x01cc +/* index is the value for this register */ +static const unsigned int rates[] = { + [0] = 0, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000 +}; + +/* saffire(no label)/saffire LE has metering */ +#define SAFFIRE_OFFSET_METER 0x0100 +#define SAFFIRE_LE_OFFSET_METER 0x0168 + +static inline int +saffire_read_block(struct snd_bebob *bebob, u64 offset, + u32 *buf, unsigned int size) +{ + unsigned int i; + int err; + __be32 *tmp = (__be32 *)buf; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + tmp, size, 0); + if (err < 0) + goto end; + + for (i = 0; i < size / sizeof(u32); i++) + buf[i] = be32_to_cpu(tmp[i]); +end: + return err; +} + +static inline int +saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value) +{ + int err; + __be32 tmp; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &tmp, sizeof(__be32), 0); + if (err < 0) + goto end; + + *value = be32_to_cpu(tmp); +end: + return err; +} + +static inline int +saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value) +{ + __be32 data = cpu_to_be32(value); + + return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &data, sizeof(__be32), 0); +} + +static char *const saffirepro_26_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "ADAT1", "ADAT2", "Word Clock" +}; + +static char *const saffirepro_10_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "Word Clock" +}; +static int +saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate) +{ + u32 id; + int err; + + err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id); + if (err < 0) + goto end; + if (id >= ARRAY_SIZE(rates)) + err = -EIO; + else + *rate = rates[id]; +end: + return err; +} +static int +saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate) +{ + u32 id; + + for (id = 0; id < ARRAY_SIZE(rates); id++) { + if (rates[id] == rate) + break; + } + if (id == ARRAY_SIZE(rates)) + return -EINVAL; + + return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id); +} +static int +saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value); + if (err < 0) + goto end; + + if (bebob->spec->clock->labels == saffirepro_10_clk_src_labels) { + if (value == SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK) + *id = 2; + else if (value == SAFFIREPRO_CLOCK_SOURCE_SPDIF) + *id = 1; + } else if (value > 1) { + *id = value - 1; + } +end: + return err; +} + +struct snd_bebob_spec saffire_le_spec; +static char *const saffire_both_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF" +}; +static int +saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value); + if (err >= 0) + *id = 0xff & value; + + return err; +}; +static char *const saffire_le_meter_labels[] = { + ANA_IN, ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + STM_IN, STM_IN +}; +static char *const saffire_meter_labels[] = { + ANA_IN, ANA_IN, + STM_IN, STM_IN, STM_IN, STM_IN, STM_IN, +}; +static int +saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int channels; + u64 offset; + int err; + + if (spec->labels == saffire_le_meter_labels) + offset = SAFFIRE_LE_OFFSET_METER; + else + offset = SAFFIRE_OFFSET_METER; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EIO; + + err = saffire_read_block(bebob, offset, buf, size); + if (err >= 0 && spec->labels == saffire_le_meter_labels) { + swap(buf[1], buf[3]); + swap(buf[2], buf[3]); + swap(buf[3], buf[4]); + + swap(buf[7], buf[10]); + swap(buf[8], buf[10]); + swap(buf[9], buf[11]); + swap(buf[11], buf[12]); + + swap(buf[15], buf[16]); + } + + return err; +} + +static struct snd_bebob_rate_spec saffirepro_both_rate_spec = { + .get = &saffirepro_both_clk_freq_get, + .set = &saffirepro_both_clk_freq_set, +}; +/* Saffire Pro 26 I/O */ +static struct snd_bebob_clock_spec saffirepro_26_clk_spec = { + .num = ARRAY_SIZE(saffirepro_26_clk_src_labels), + .labels = saffirepro_26_clk_src_labels, + .get = &saffirepro_both_clk_src_get, +}; +struct snd_bebob_spec saffirepro_26_spec = { + .clock = &saffirepro_26_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; +/* Saffire Pro 10 I/O */ +static struct snd_bebob_clock_spec saffirepro_10_clk_spec = { + .num = ARRAY_SIZE(saffirepro_10_clk_src_labels), + .labels = saffirepro_10_clk_src_labels, + .get = &saffirepro_both_clk_src_get, +}; +struct snd_bebob_spec saffirepro_10_spec = { + .clock = &saffirepro_10_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; + +static struct snd_bebob_rate_spec saffire_both_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static struct snd_bebob_clock_spec saffire_both_clk_spec = { + .num = ARRAY_SIZE(saffire_both_clk_src_labels), + .labels = saffire_both_clk_src_labels, + .get = &saffire_both_clk_src_get, +}; +/* Saffire LE */ +static struct snd_bebob_meter_spec saffire_le_meter_spec = { + .num = ARRAY_SIZE(saffire_le_meter_labels), + .labels = saffire_le_meter_labels, + .get = &saffire_meter_get, +}; +struct snd_bebob_spec saffire_le_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_le_meter_spec +}; +/* Saffire */ +static struct snd_bebob_meter_spec saffire_meter_spec = { + .num = ARRAY_SIZE(saffire_meter_labels), + .labels = saffire_meter_labels, + .get = &saffire_meter_get, +}; +struct snd_bebob_spec saffire_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_meter_spec +}; diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c new file mode 100644 index 000000000000..ce731f4d8b4f --- /dev/null +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -0,0 +1,199 @@ +/* + * bebob_hwdep.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node infomation + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "bebob.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_bebob *bebob = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&bebob->lock); + + while (!bebob->dev_lock_changed) { + prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&bebob->lock); + schedule(); + finish_wait(&bebob->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&bebob->lock); + } + + memset(&event, 0, sizeof(event)); + if (bebob->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (bebob->dev_lock_count > 0); + bebob->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&bebob->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_bebob *bebob = hwdep->private_data; + unsigned int events; + + poll_wait(file, &bebob->hwdep_wait, wait); + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&bebob->lock); + + return events; +} + +static int +hwdep_get_info(struct snd_bebob *bebob, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(bebob->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_BEBOB; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int +hwdep_lock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == 0) { + bebob->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == -1) { + bebob->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_bebob *bebob = hwdep->private_data; + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_count == -1) + bebob->dev_lock_count = 0; + spin_unlock_irq(&bebob->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_bebob *bebob = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(bebob, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(bebob); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(bebob); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, +}; + +int snd_bebob_create_hwdep_device(struct snd_bebob *bebob) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "BeBoB"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB; + hwdep->ops = hwdep_ops; + hwdep->private_data = bebob; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c new file mode 100644 index 000000000000..70faa3a32526 --- /dev/null +++ b/sound/firewire/bebob/bebob_maudio.c @@ -0,0 +1,813 @@ +/* + * bebob_maudio.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" +#include <sound/control.h> + +/* + * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to + * download firmware blob. To enable these devices, drivers should upload + * firmware blob and send a command to initialize configuration to factory + * settings when completing uploading. Then these devices generate bus reset + * and are recognized as new devices with the firmware. + * + * But with firmware version 5058 or later, the firmware is stored to flash + * memory in the device and drivers can tell bootloader to load the firmware + * by sending a cue. This cue must be sent one time. + * + * For streaming, both of output and input streams are needed for Firewire 410 + * and Ozonic. The single stream is OK for the other devices even if the clock + * source is not SYT-Match (I note no devices use SYT-Match). + * + * Without streaming, the devices except for Firewire Audiophile can mix any + * input and output. For this reason, Audiophile cannot be used as standalone + * mixer. + * + * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed + * when receiving any commands which the firmware can't understand. These + * devices utilize completely different system to control. It is some + * write-transaction directly into a certain address. All of addresses for mixer + * functionality is between 0xffc700700000 to 0xffc70070009c. + */ + +/* Offset from information register */ +#define INFO_OFFSET_SW_DATE 0x20 + +/* Bootloader Protocol Version 1 */ +#define MAUDIO_BOOTLOADER_CUE1 0x00000001 +/* + * Initializing configuration to factory settings (= 0x1101), (swapped in line), + * Command code is zero (= 0x00), + * the number of operands is zero (= 0x00)(at least significant byte) + */ +#define MAUDIO_BOOTLOADER_CUE2 0x01110000 +/* padding */ +#define MAUDIO_BOOTLOADER_CUE3 0x00000000 + +#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL + +#define METER_OFFSET 0x00600000 + +/* some device has sync info after metering data */ +#define METER_SIZE_SPECIAL 84 /* with sync info */ +#define METER_SIZE_FW410 76 /* with sync info */ +#define METER_SIZE_AUDIOPHILE 60 /* with sync info */ +#define METER_SIZE_SOLO 52 /* with sync info */ +#define METER_SIZE_OZONIC 48 +#define METER_SIZE_NRV10 80 + +/* labels for metering */ +#define ANA_IN "Analog In" +#define ANA_OUT "Analog Out" +#define DIG_IN "Digital In" +#define SPDIF_IN "S/PDIF In" +#define ADAT_IN "ADAT In" +#define DIG_OUT "Digital Out" +#define SPDIF_OUT "S/PDIF Out" +#define ADAT_OUT "ADAT Out" +#define STRM_IN "Stream In" +#define AUX_OUT "Aux Out" +#define HP_OUT "HP Out" +/* for NRV */ +#define UNKNOWN_METER "Unknown" + +struct special_params { + bool is1814; + unsigned int clk_src; + unsigned int dig_in_fmt; + unsigned int dig_out_fmt; + unsigned int clk_lock; + struct snd_ctl_elem_id *ctl_id_sync; +}; + +/* + * For some M-Audio devices, this module just send cue to load firmware. After + * loading, the device generates bus reset and newly detected. + * + * If we make any transactions to load firmware, the operation may failed. + */ +int snd_bebob_maudio_load_firmware(struct fw_unit *unit) +{ + struct fw_device *device = fw_parent_device(unit); + int err, rcode; + u64 date; + __be32 cues[3] = { + MAUDIO_BOOTLOADER_CUE1, + MAUDIO_BOOTLOADER_CUE2, + MAUDIO_BOOTLOADER_CUE3 + }; + + /* check date of software used to build */ + err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE, + &date, sizeof(u64)); + if (err < 0) + goto end; + /* + * firmware version 5058 or later has date later than "20070401", but + * 'date' is not null-terminated. + */ + if (date < 0x3230303730343031LL) { + dev_err(&unit->device, + "Use firmware version 5058 or later\n"); + err = -ENOSYS; + goto end; + } + + rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST, + device->node_id, device->generation, + device->max_speed, BEBOB_ADDR_REG_REQ, + cues, sizeof(cues)); + if (rcode != RCODE_COMPLETE) { + dev_err(&unit->device, + "Failed to send a cue to load firmware\n"); + err = -EIO; + } +end: + return err; +} + +static inline int +get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) +{ + return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET, + buf, size, 0); +} + +static int +check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync) +{ + int err; + u8 *buf; + + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, buf, size); + if (err < 0) + goto end; + + /* if synced, this value is the same as SFC of FDF in CIP header */ + *sync = (buf[size - 2] != 0xff); +end: + kfree(buf); + return err; +} + +/* + * dig_fmt: 0x00:S/PDIF, 0x01:ADAT + * clk_lock: 0x00:unlock, 0x01:lock + */ +static int +avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src, + unsigned int dig_in_fmt, unsigned int dig_out_fmt, + unsigned int clk_lock) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + u8 *buf; + + if (amdtp_stream_running(&bebob->rx_stream) || + amdtp_stream_running(&bebob->tx_stream)) + return -EBUSY; + + buf = kmalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x00; /* vendor dependent */ + buf[3] = 0x04; /* company ID high */ + buf[4] = 0x00; /* company ID middle */ + buf[5] = 0x04; /* company ID low */ + buf[6] = 0xff & clk_src; /* clock source */ + buf[7] = 0xff & dig_in_fmt; /* input digital format */ + buf[8] = 0xff & dig_out_fmt; /* output digital format */ + buf[9] = 0xff & clk_lock; /* lock these settings */ + buf[10] = 0x00; /* padding */ + buf[11] = 0x00; /* padding */ + + err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(8) | + BIT(9)); + if ((err > 0) && (err < 10)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + params->clk_src = buf[6]; + params->dig_in_fmt = buf[7]; + params->dig_out_fmt = buf[8]; + params->clk_lock = buf[9]; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); + + err = 0; +end: + kfree(buf); + return err; +} +static void +special_stream_formation_set(struct snd_bebob *bebob) +{ + static const unsigned int ch_table[2][2][3] = { + /* AMDTP_OUT_STREAM */ + { { 6, 6, 4 }, /* SPDIF */ + { 12, 8, 4 } }, /* ADAT */ + /* AMDTP_IN_STREAM */ + { { 10, 10, 2 }, /* SPDIF */ + { 16, 12, 2 } } /* ADAT */ + }; + struct special_params *params = bebob->maudio_special_quirk; + unsigned int i, max; + + max = SND_BEBOB_STRM_FMT_ENTRIES - 1; + if (!params->is1814) + max -= 2; + + for (i = 0; i < max; i++) { + bebob->tx_stream_formations[i + 1].pcm = + ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2]; + bebob->tx_stream_formations[i + 1].midi = 1; + + bebob->rx_stream_formations[i + 1].pcm = + ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2]; + bebob->rx_stream_formations[i + 1].midi = 1; + } +} + +static int add_special_controls(struct snd_bebob *bebob); +int +snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814) +{ + struct special_params *params; + int err; + + params = kzalloc(sizeof(struct special_params), GFP_KERNEL); + if (params == NULL) + return -ENOMEM; + + mutex_lock(&bebob->mutex); + + bebob->maudio_special_quirk = (void *)params; + params->is1814 = is1814; + + /* initialize these parameters because driver is not allowed to ask */ + bebob->rx_stream.context = ERR_PTR(-1); + bebob->tx_stream.context = ERR_PTR(-1); + err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to initialize clock params: %d\n", err); + goto end; + } + + err = add_special_controls(bebob); + if (err < 0) + goto end; + + special_stream_formation_set(bebob); + + if (params->is1814) { + bebob->midi_input_ports = 1; + bebob->midi_output_ports = 1; + } else { + bebob->midi_input_ports = 2; + bebob->midi_output_ports = 2; + } +end: + if (err < 0) { + kfree(params); + bebob->maudio_special_quirk = NULL; + } + mutex_unlock(&bebob->mutex); + return err; +} + +/* Input plug shows actual rate. Output plug is needless for this purpose. */ +static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate) +{ + int err, trials; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + + return err; +} +static int special_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* + * Just after changing sampling rate for output, a followed command + * for input is easy to fail. This is a workaround fot this issue. + */ + msleep(100); + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); +end: + return err; +} + +/* Clock source control for special firmware */ +static char *const special_clk_labels[] = { + SND_BEBOB_CLOCK_INTERNAL " with Digital Mute", "Digital", + "Word Clock", SND_BEBOB_CLOCK_INTERNAL}; +static int special_clk_get(struct snd_bebob *bebob, unsigned int *id) +{ + struct special_params *params = bebob->maudio_special_quirk; + *id = params->clk_src; + return 0; +} +static int special_clk_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = ARRAY_SIZE(special_clk_labels); + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + special_clk_labels[einf->value.enumerated.item]); + + return 0; +} +static int special_clk_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + uval->value.enumerated.item[0] = params->clk_src; + return 0; +} +static int special_clk_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + int err, id; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_clk_labels)) + return -EINVAL; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, id, + params->dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + mutex_unlock(&bebob->mutex); + + if (err >= 0) + err = 1; + + return err; +} +static struct snd_kcontrol_new special_clk_ctl = { + .name = "Clock Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_clk_ctl_info, + .get = special_clk_ctl_get, + .put = special_clk_ctl_put +}; + +/* Clock synchronization control for special firmware */ +static int special_sync_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + einf->count = 1; + einf->value.integer.min = 0; + einf->value.integer.max = 1; + + return 0; +} +static int special_sync_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + int err; + bool synced = 0; + + err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced); + if (err >= 0) + uval->value.integer.value[0] = synced; + + return 0; +} +static struct snd_kcontrol_new special_sync_ctl = { + .name = "Sync Status", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = special_sync_ctl_info, + .get = special_sync_ctl_get, +}; + +/* Digital input interface control for special firmware */ +static char *const special_dig_in_iface_labels[] = { + "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical" +}; +static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = ARRAY_SIZE(special_dig_in_iface_labels); + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + special_dig_in_iface_labels[einf->value.enumerated.item]); + + return 0; +} +static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int dig_in_iface; + int err, val; + + mutex_lock(&bebob->mutex); + + err = avc_audio_get_selector(bebob->unit, 0x00, 0x04, + &dig_in_iface); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get digital input interface: %d\n", err); + goto end; + } + + /* encoded id for user value */ + val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01); + + /* for ADAT Optical */ + if (val > 2) + val = 2; + + uval->value.enumerated.item[0] = val; +end: + mutex_unlock(&bebob->mutex); + return err; +} +static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id, dig_in_fmt, dig_in_iface; + int err; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_dig_in_iface_labels)) + return -EINVAL; + + /* decode user value */ + dig_in_fmt = (id >> 1) & 0x01; + dig_in_iface = id & 0x01; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + if (err < 0) + goto end; + + /* For ADAT, optical interface is only available. */ + if (params->dig_in_fmt > 0) { + err = 1; + goto end; + } + + /* For S/PDIF, optical/coaxial interfaces are selectable. */ + err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface); + if (err < 0) + dev_err(&bebob->unit->device, + "fail to set digital input interface: %d\n", err); + err = 1; +end: + special_stream_formation_set(bebob); + mutex_unlock(&bebob->mutex); + return err; +} +static struct snd_kcontrol_new special_dig_in_iface_ctl = { + .name = "Digital Input Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_in_iface_ctl_info, + .get = special_dig_in_iface_ctl_get, + .put = special_dig_in_iface_ctl_set +}; + +/* Digital output interface control for special firmware */ +static char *const special_dig_out_iface_labels[] = { + "S/PDIF Optical and Coaxial", "ADAT Optical" +}; +static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = ARRAY_SIZE(special_dig_out_iface_labels); + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + special_dig_out_iface_labels[einf->value.enumerated.item]); + + return 0; +} +static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + mutex_lock(&bebob->mutex); + uval->value.enumerated.item[0] = params->dig_out_fmt; + mutex_unlock(&bebob->mutex); + return 0; +} +static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id; + int err; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_dig_out_iface_labels)) + return -EINVAL; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + params->dig_in_fmt, + id, params->clk_lock); + if (err >= 0) { + special_stream_formation_set(bebob); + err = 1; + } + + mutex_unlock(&bebob->mutex); + return err; +} +static struct snd_kcontrol_new special_dig_out_iface_ctl = { + .name = "Digital Output Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_out_iface_ctl_info, + .get = special_dig_out_iface_ctl_get, + .put = special_dig_out_iface_ctl_set +}; + +static int add_special_controls(struct snd_bebob *bebob) +{ + struct snd_kcontrol *kctl; + struct special_params *params = bebob->maudio_special_quirk; + int err; + + kctl = snd_ctl_new1(&special_clk_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_sync_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + params->ctl_id_sync = &kctl->id; + + kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); +end: + return err; +} + +/* Hardware metering for special firmware */ +static char *const special_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + SPDIF_IN, + ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN, + ANA_OUT, ANA_OUT, + SPDIF_OUT, + ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT, + HP_OUT, HP_OUT, + AUX_OUT +}; +static int +special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size) +{ + u16 *buf; + unsigned int i, c, channels; + int err; + + channels = ARRAY_SIZE(special_meter_labels) * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + /* omit last 4 bytes because it's clock info. */ + buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4); + if (err < 0) + goto end; + + /* Its format is u16 and some channels are unknown. */ + i = 0; + for (c = 2; c < channels + 2; c++) + target[i++] = be16_to_cpu(buf[c]) << 16; +end: + kfree(buf); + return err; +} + +/* last 4 bytes are omitted because it's clock info. */ +static char *const fw410_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT +}; +static char *const audiophile_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT, AUX_OUT, +}; +static char *const solo_meter_labels[] = { + ANA_IN, DIG_IN, + STRM_IN, STRM_IN, + ANA_OUT, DIG_OUT +}; + +/* no clock info */ +static char *const ozonic_meter_labels[] = { + ANA_IN, ANA_IN, + STRM_IN, STRM_IN, + ANA_OUT, ANA_OUT +}; +/* TODO: need testers. these positions are based on authour's assumption */ +static char *const nrv10_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + DIG_IN +}; +static int +normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int c, channels; + int err; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + err = get_meter(bebob, (void *)buf, size); + if (err < 0) + goto end; + + for (c = 0; c < channels; c++) + be32_to_cpus(&buf[c]); + + /* swap stream channels because inverted */ + if (spec->labels == solo_meter_labels) { + swap(buf[4], buf[6]); + swap(buf[5], buf[7]); + } +end: + return err; +} + +/* for special customized devices */ +static struct snd_bebob_rate_spec special_rate_spec = { + .get = &special_get_rate, + .set = &special_set_rate, +}; +static struct snd_bebob_clock_spec special_clk_spec = { + .num = ARRAY_SIZE(special_clk_labels), + .labels = special_clk_labels, + .get = &special_clk_get, +}; +static struct snd_bebob_meter_spec special_meter_spec = { + .num = ARRAY_SIZE(special_meter_labels), + .labels = special_meter_labels, + .get = &special_meter_get +}; +struct snd_bebob_spec maudio_special_spec = { + .clock = &special_clk_spec, + .rate = &special_rate_spec, + .meter = &special_meter_spec +}; + +/* Firewire 410 specification */ +static struct snd_bebob_rate_spec usual_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static struct snd_bebob_meter_spec fw410_meter_spec = { + .num = ARRAY_SIZE(fw410_meter_labels), + .labels = fw410_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_fw410_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &fw410_meter_spec +}; + +/* Firewire Audiophile specification */ +static struct snd_bebob_meter_spec audiophile_meter_spec = { + .num = ARRAY_SIZE(audiophile_meter_labels), + .labels = audiophile_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_audiophile_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &audiophile_meter_spec +}; + +/* Firewire Solo specification */ +static struct snd_bebob_meter_spec solo_meter_spec = { + .num = ARRAY_SIZE(solo_meter_labels), + .labels = solo_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_solo_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &solo_meter_spec +}; + +/* Ozonic specification */ +static struct snd_bebob_meter_spec ozonic_meter_spec = { + .num = ARRAY_SIZE(ozonic_meter_labels), + .labels = ozonic_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_ozonic_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &ozonic_meter_spec +}; + +/* NRV10 specification */ +static struct snd_bebob_meter_spec nrv10_meter_spec = { + .num = ARRAY_SIZE(nrv10_meter_labels), + .labels = nrv10_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_nrv10_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &nrv10_meter_spec +}; diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c new file mode 100644 index 000000000000..63343d578df3 --- /dev/null +++ b/sound/firewire/bebob/bebob_midi.c @@ -0,0 +1,168 @@ +/* + * bebob_midi.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "bebob.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + + atomic_inc(&bebob->capture_substreams); + err = snd_bebob_stream_start_duplex(bebob, 0); + if (err < 0) + snd_bebob_stream_lock_release(bebob); +end: + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + + atomic_inc(&bebob->playback_substreams); + err = snd_bebob_stream_start_duplex(bebob, 0); + if (err < 0) + snd_bebob_stream_lock_release(bebob); +end: + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + atomic_dec(&bebob->capture_substreams); + snd_bebob_stream_stop_duplex(bebob); + + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + atomic_dec(&bebob->playback_substreams); + snd_bebob_stream_stop_duplex(bebob); + + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&bebob->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&bebob->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&bebob->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&bebob->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_bebob *bebob, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + bebob->card->shortname, subs->number + 1); + } +} + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0, + bebob->midi_output_ports, bebob->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", bebob->card->shortname); + rmidi->private_data = bebob; + + if (bebob->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(bebob, str); + } + + if (bebob->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(bebob, str); + } + + if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c new file mode 100644 index 000000000000..4a55561ed4ec --- /dev/null +++ b/sound/firewire/bebob/bebob_pcm.c @@ -0,0 +1,378 @@ +/* + * bebob_pcm.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(c, formations[i].pcm)) + continue; + + t.min = min(t.min, snd_bebob_rate_table[i]); + t.max = max(t.max, snd_bebob_rate_table[i]); + + } + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, snd_bebob_rate_table[i])) + continue; + + t.min = min(t.min, formations[i].pcm); + t.max = max(t.max, formations[i].pcm); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels_and_rates(struct snd_pcm_hardware *hw, + struct snd_bebob_stream_formation *formations) +{ + unsigned int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->channels_min = min(hw->channels_min, formations[i].pcm); + hw->channels_max = max(hw->channels_max, formations[i].pcm); + + hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]); + hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]); + } +} + +static void +limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int +pcm_init_hw_params(struct snd_bebob *bebob, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + struct snd_bebob_stream_formation *formations; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + s = &bebob->tx_stream; + formations = bebob->tx_stream_formations; + } else { + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + s = &bebob->rx_stream; + formations = bebob->rx_stream_formations; + } + + limit_channels_and_rates(&runtime->hw, formations); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formations, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formations, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_stream_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int +pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_bebob_rate_spec *spec = bebob->spec->rate; + unsigned int sampling_rate; + bool internal; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + + err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto err_locked; + + err = snd_bebob_stream_check_internal_clock(bebob, &internal); + if (err < 0) + goto err_locked; + + /* + * When source of clock is internal or any PCM stream are running, + * the available sampling rate is limited at current sampling rate. + */ + if (!internal || + amdtp_stream_pcm_running(&bebob->tx_stream) || + amdtp_stream_pcm_running(&bebob->rx_stream)) { + err = spec->get(bebob, &sampling_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto err_locked; + } + + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_bebob_stream_lock_release(bebob); + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static int +pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&bebob->capture_substreams); + amdtp_stream_set_pcm_format(&bebob->tx_stream, + params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} +static int +pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&bebob->playback_substreams); + amdtp_stream_set_pcm_format(&bebob->rx_stream, + params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int +pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&bebob->capture_substreams); + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int +pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&bebob->playback_substreams); + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int +pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->tx_stream); + + return err; +} +static int +pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->rx_stream); + + return err; +} + +static int +pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int +pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->tx_stream); +} +static snd_pcm_uframes_t +pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->rx_stream); +} + +static const struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; +static const struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = bebob; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", bebob->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c new file mode 100644 index 000000000000..335da64506e0 --- /dev/null +++ b/sound/firewire/bebob/bebob_proc.c @@ -0,0 +1,196 @@ +/* + * bebob_proc.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +/* contents of information register */ +struct hw_info { + u64 manufacturer; + u32 protocol_ver; + u32 bld_ver; + u32 guid[2]; + u32 model_id; + u32 model_rev; + u64 fw_date; + u64 fw_time; + u32 fw_id; + u32 fw_ver; + u32 base_addr; + u32 max_size; + u64 bld_date; + u64 bld_time; +/* may not used in product + u64 dbg_date; + u64 dbg_time; + u32 dbg_id; + u32 dbg_version; +*/ +} __packed; + +static void +proc_read_hw_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct hw_info *info; + + info = kzalloc(sizeof(struct hw_info), GFP_KERNEL); + if (info == NULL) + return; + + if (snd_bebob_read_block(bebob->unit, 0, + info, sizeof(struct hw_info)) < 0) + goto end; + + snd_iprintf(buffer, "Manufacturer:\t%.8s\n", + (char *)&info->manufacturer); + snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver); + snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver); + snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n", + info->guid[0], info->guid[1]); + snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id); + snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev); + snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date); + snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time); + snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id); + snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver); + snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr); + snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size); + snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date); + snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time); + +end: + kfree(info); +} + +static void +proc_read_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + u32 *buf; + unsigned int i, c, channels, size; + + if (spec == NULL) + return; + + channels = spec->num * 2; + size = channels * sizeof(u32); + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return; + + if (spec->get(bebob, buf, size) < 0) + goto end; + + for (i = 0, c = 1; i < channels; i++) { + snd_iprintf(buffer, "%s %d:\t%d\n", + spec->labels[i / 2], c++, buf[i]); + if ((i + 1 < channels - 1) && + (strcmp(spec->labels[i / 2], + spec->labels[(i + 1) / 2]) != 0)) + c = 1; + } +end: + kfree(buf); +} + +static void +proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_stream_formation *formation; + unsigned int i; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->tx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->rx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } +} + +static void +proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; + struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + unsigned int rate, id; + bool internal; + + if (rate_spec->get(bebob, &rate) >= 0) + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (clk_spec) { + if (clk_spec->get(bebob, &id) >= 0) + snd_iprintf(buffer, "Clock Source: %s\n", + clk_spec->labels[id]); + } else { + if (snd_bebob_stream_check_internal_clock(bebob, + &internal) >= 0) + snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n", + (internal) ? "Internal" : "External", + bebob->sync_input_plug); + } +} + +static void +add_node(struct snd_bebob *bebob, struct snd_info_entry *root, const char *name, + void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(bebob->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, bebob, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_bebob_proc_init(struct snd_bebob *bebob) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(bebob->card, "firewire", + bebob->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(bebob, root, "clock", proc_read_clock); + add_node(bebob, root, "firmware", proc_read_hw_info); + add_node(bebob, root, "formation", proc_read_formation); + + if (bebob->spec->meter != NULL) + add_node(bebob, root, "meter", proc_read_meters); +} diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c new file mode 100644 index 000000000000..ef4d0c9f6578 --- /dev/null +++ b/sound/firewire/bebob/bebob_stream.c @@ -0,0 +1,1021 @@ +/* + * bebob_stream.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define CALLBACK_TIMEOUT 1000 +#define FW_ISO_RESOURCE_DELAY 1000 + +/* + * NOTE; + * For BeBoB streams, Both of input and output CMP connection are important. + * + * For most devices, each CMP connection starts to transmit/receive a + * corresponding stream. But for a few devices, both of CMP connection needs + * to start transmitting stream. An example is 'M-Audio Firewire 410'. + */ + +/* 128 is an arbitrary length but it seems to be enough */ +#define FORMAT_MAXIMUM_LENGTH 128 + +const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000, +}; + +/* + * See: Table 51: Extended Stream Format Info ‘Sampling Frequency’ + * in Additional AVC commands (Nov 2003, BridgeCo) + */ +static const unsigned int bridgeco_freq_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x06, + [6] = 0x07, +}; + +static unsigned int +get_formation_index(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) { + if (snd_bebob_rate_table[i] == rate) + return i; + } + return -EINVAL; +} + +int +snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate) +{ + unsigned int tx_rate, rx_rate, trials; + int err; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &tx_rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + *curr_rate = rx_rate; + if (rx_rate == tx_rate) + goto end; + + /* synchronize receive stream rate to transmit stream rate */ + err = avc_general_set_sig_fmt(bebob->unit, rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); +end: + return err; +} + +int +snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + /* + * Some devices need a bit time for transition. + * 300msec is got by some experiments. + */ + msleep(300); +end: + return err; +} + +int +snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, bool *internal) +{ + struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; + unsigned int id; + int err = 0; + + *internal = false; + + /* 1.The device has its own operation to switch source of clock */ + if (clk_spec) { + err = clk_spec->get(bebob, &id); + if (err < 0) + dev_err(&bebob->unit->device, + "fail to get clock source: %d\n", err); + else if (strncmp(clk_spec->labels[id], SND_BEBOB_CLOCK_INTERNAL, + strlen(SND_BEBOB_CLOCK_INTERNAL)) == 0) + *internal = true; + goto end; + } + + /* + * 2.The device don't support to switch source of clock then assumed + * to use internal clock always + */ + if (bebob->sync_input_plug < 0) { + *internal = true; + goto end; + } + + /* + * 3.The device supports to switch source of clock by an usual way. + * Let's check input for 'Music Sub Unit Sync Input' plug. + */ + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + bebob->sync_input_plug); + err = avc_bridgeco_get_plug_input(bebob->unit, addr, input); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get an input for MSU in plug %d: %d\n", + bebob->sync_input_plug, err); + goto end; + } + + /* + * If there are no input plugs, all of fields are 0xff. + * Here check the first field. This field is used for direction. + */ + if (input[0] == 0xff) { + *internal = true; + goto end; + } + + /* + * If source of clock is internal CSR, Music Sub Unit Sync Input is + * a destination of Music Sub Unit Sync Output. + */ + *internal = ((input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) && + (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT) && + (input[2] == 0x0c) && + (input[3] == 0x00)); +end: + return err; +} + +static unsigned int +map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + unsigned int sec, sections, ch, channels; + unsigned int pcm, midi, location; + unsigned int stm_pos, sec_loc, pos; + u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type; + enum avc_bridgeco_plug_dir dir; + int err; + + /* + * The length of return value of this command cannot be expected. Here + * use the maximum length of FCP. + */ + buf = kzalloc(256, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (s == &bebob->tx_stream) + dir = AVC_BRIDGECO_PLUG_DIR_OUT; + else + dir = AVC_BRIDGECO_PLUG_DIR_IN; + + avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get channel position for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out", + err); + goto end; + } + pos = 0; + + /* positions in I/O buffer */ + pcm = 0; + midi = 0; + + /* the number of sections in AMDTP packet */ + sections = buf[pos++]; + + for (sec = 0; sec < sections; sec++) { + /* type of this section */ + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_section_type(bebob->unit, addr, + sec, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get section type for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + err); + goto end; + } + /* NoType */ + if (type == 0xff) { + err = -ENOSYS; + goto end; + } + + /* the number of channels in this section */ + channels = buf[pos++]; + + for (ch = 0; ch < channels; ch++) { + /* position of this channel in AMDTP packet */ + stm_pos = buf[pos++] - 1; + /* location of this channel in this section */ + sec_loc = buf[pos++] - 1; + + /* + * Basically the number of location is within the + * number of channels in this section. But some models + * of M-Audio don't follow this. Its location for MIDI + * is the position of MIDI channels in AMDTP packet. + */ + if (sec_loc >= channels) + sec_loc = ch; + + switch (type) { + /* for MIDI conformant data channel */ + case 0x0a: + /* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */ + if ((midi > 0) && (stm_pos != midi)) { + err = -ENOSYS; + goto end; + } + s->midi_position = stm_pos; + midi = stm_pos; + break; + /* for PCM data channel */ + case 0x01: /* Headphone */ + case 0x02: /* Microphone */ + case 0x03: /* Line */ + case 0x04: /* SPDIF */ + case 0x05: /* ADAT */ + case 0x06: /* TDIF */ + case 0x07: /* MADI */ + /* for undefined/changeable signal */ + case 0x08: /* Analog */ + case 0x09: /* Digital */ + default: + location = pcm + sec_loc; + if (location >= AMDTP_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + s->pcm_positions[location] = stm_pos; + break; + } + } + + if (type != 0x0a) + pcm += channels; + else + midi += channels; + } +end: + kfree(buf); + return err; +} + +static int +init_both_connections(struct snd_bebob *bebob) +{ + int err; + + err = cmp_connection_init(&bebob->in_conn, + bebob->unit, CMP_INPUT, 0); + if (err < 0) + goto end; + + err = cmp_connection_init(&bebob->out_conn, + bebob->unit, CMP_OUTPUT, 0); + if (err < 0) + cmp_connection_destroy(&bebob->in_conn); +end: + return err; +} + +static int +check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &bebob->tx_stream) + conn = &bebob->out_conn; + else + conn = &bebob->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&bebob->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +static int +make_both_connections(struct snd_bebob *bebob, unsigned int rate) +{ + int index, pcm_channels, midi_channels, err = 0; + + if (bebob->connected) + goto end; + + /* confirm params for both streams */ + index = get_formation_index(rate); + pcm_channels = bebob->tx_stream_formations[index].pcm; + midi_channels = bebob->tx_stream_formations[index].midi; + amdtp_stream_set_parameters(&bebob->tx_stream, + rate, pcm_channels, midi_channels * 8); + pcm_channels = bebob->rx_stream_formations[index].pcm; + midi_channels = bebob->rx_stream_formations[index].midi; + amdtp_stream_set_parameters(&bebob->rx_stream, + rate, pcm_channels, midi_channels * 8); + + /* establish connections for both streams */ + err = cmp_connection_establish(&bebob->out_conn, + amdtp_stream_get_max_payload(&bebob->tx_stream)); + if (err < 0) + goto end; + err = cmp_connection_establish(&bebob->in_conn, + amdtp_stream_get_max_payload(&bebob->rx_stream)); + if (err < 0) { + cmp_connection_break(&bebob->out_conn); + goto end; + } + + bebob->connected = true; +end: + return err; +} + +static void +break_both_connections(struct snd_bebob *bebob) +{ + cmp_connection_break(&bebob->in_conn); + cmp_connection_break(&bebob->out_conn); + + bebob->connected = false; + + /* These models seems to be in transition state for a longer time. */ + if (bebob->maudio_special_quirk != NULL) + msleep(200); +} + +static void +destroy_both_connections(struct snd_bebob *bebob) +{ + break_both_connections(bebob); + + cmp_connection_destroy(&bebob->in_conn); + cmp_connection_destroy(&bebob->out_conn); +} + +static int +get_sync_mode(struct snd_bebob *bebob, enum cip_flags *sync_mode) +{ + /* currently this module doesn't support SYT-Match mode */ + *sync_mode = CIP_SYNC_TO_DEVICE; + return 0; +} + +static int +start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream, + unsigned int rate) +{ + struct cmp_connection *conn; + int err = 0; + + if (stream == &bebob->rx_stream) + conn = &bebob->in_conn; + else + conn = &bebob->out_conn; + + /* channel mapping */ + if (bebob->maudio_special_quirk == NULL) { + err = map_data_channels(bebob, stream); + if (err < 0) + goto end; + } + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); +end: + return err; +} + +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) +{ + int err; + + err = init_both_connections(bebob); + if (err < 0) + goto end; + + err = amdtp_stream_init(&bebob->tx_stream, bebob->unit, + AMDTP_IN_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + destroy_both_connections(bebob); + goto end; + } + /* See comments in next function */ + init_completion(&bebob->bus_reset); + bebob->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK; + /* + * At high sampling rate, M-Audio special firmware transmits empty + * packet with the value of dbc incremented by 8 but the others are + * valid to IEC 61883-1. + */ + if (bebob->maudio_special_quirk) + bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC; + + err = amdtp_stream_init(&bebob->rx_stream, bebob->unit, + AMDTP_OUT_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + amdtp_stream_destroy(&bebob->rx_stream); + destroy_both_connections(bebob); + } + /* + * The firmware for these devices ignore MIDI messages in more than + * first 8 data blocks of an received AMDTP packet. + */ + if (bebob->spec == &maudio_fw410_spec || + bebob->spec == &maudio_special_spec) + bebob->rx_stream.rx_blocks_for_midi = 8; +end: + return err; +} + +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate) +{ + struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; + struct amdtp_stream *master, *slave; + atomic_t *slave_substreams; + enum cip_flags sync_mode; + unsigned int curr_rate; + bool updated = false; + int err = 0; + + /* + * Normal BeBoB firmware has a quirk at bus reset to transmits packets + * with discontinuous value in dbc field. + * + * This 'struct completion' is used to call .update() at first to update + * connections/streams. Next following codes handle streaming error. + */ + if (amdtp_streaming_error(&bebob->tx_stream)) { + if (completion_done(&bebob->bus_reset)) + reinit_completion(&bebob->bus_reset); + + updated = (wait_for_completion_interruptible_timeout( + &bebob->bus_reset, + msecs_to_jiffies(FW_ISO_RESOURCE_DELAY)) > 0); + } + + mutex_lock(&bebob->mutex); + + /* Need no substreams */ + if (atomic_read(&bebob->playback_substreams) == 0 && + atomic_read(&bebob->capture_substreams) == 0) + goto end; + + err = get_sync_mode(bebob, &sync_mode); + if (err < 0) + goto end; + if (sync_mode == CIP_SYNC_TO_DEVICE) { + master = &bebob->tx_stream; + slave = &bebob->rx_stream; + slave_substreams = &bebob->playback_substreams; + } else { + master = &bebob->rx_stream; + slave = &bebob->tx_stream; + slave_substreams = &bebob->capture_substreams; + } + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(bebob, master); + if (err < 0) + goto end; + + /* + * packet queueing error or detecting discontinuity + * + * At bus reset, connections should not be broken here. So streams need + * to be re-started. This is a reason to use SKIP_INIT_DBC_CHECK flag. + */ + if (amdtp_streaming_error(master)) + amdtp_stream_stop(master); + if (amdtp_streaming_error(slave)) + amdtp_stream_stop(slave); + if (!updated && + !amdtp_stream_running(master) && !amdtp_stream_running(slave)) + break_both_connections(bebob); + + /* stop streams if rate is different */ + err = rate_spec->get(bebob, &curr_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto end; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(master); + amdtp_stream_stop(slave); + break_both_connections(bebob); + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + amdtp_stream_set_sync(sync_mode, master, slave); + bebob->master = master; + + /* + * NOTE: + * If establishing connections at first, Yamaha GO46 + * (and maybe Terratec X24) don't generate sound. + * + * For firmware customized by M-Audio, refer to next NOTE. + */ + if (bebob->maudio_special_quirk == NULL) { + err = rate_spec->set(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to set sampling rate: %d\n", + err); + goto end; + } + } + + err = make_both_connections(bebob, rate); + if (err < 0) + goto end; + + err = start_stream(bebob, master, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP master stream:%d\n", err); + break_both_connections(bebob); + goto end; + } + + /* + * NOTE: + * The firmware customized by M-Audio uses these commands to + * start transmitting stream. This is not usual way. + */ + if (bebob->maudio_special_quirk != NULL) { + err = rate_spec->set(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to ensure sampling rate: %d\n", + err); + amdtp_stream_stop(master); + break_both_connections(bebob); + goto end; + } + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(master); + break_both_connections(bebob); + err = -ETIMEDOUT; + goto end; + } + } + + /* start slave if needed */ + if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) { + err = start_stream(bebob, slave, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP slave stream:%d\n", err); + amdtp_stream_stop(master); + break_both_connections(bebob); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(slave); + amdtp_stream_stop(master); + break_both_connections(bebob); + err = -ETIMEDOUT; + } + } +end: + mutex_unlock(&bebob->mutex); + return err; +} + +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) +{ + struct amdtp_stream *master, *slave; + atomic_t *master_substreams, *slave_substreams; + + if (bebob->master == &bebob->rx_stream) { + slave = &bebob->tx_stream; + master = &bebob->rx_stream; + slave_substreams = &bebob->capture_substreams; + master_substreams = &bebob->playback_substreams; + } else { + slave = &bebob->rx_stream; + master = &bebob->tx_stream; + slave_substreams = &bebob->playback_substreams; + master_substreams = &bebob->capture_substreams; + } + + mutex_lock(&bebob->mutex); + + if (atomic_read(slave_substreams) == 0) { + amdtp_stream_pcm_abort(slave); + amdtp_stream_stop(slave); + + if (atomic_read(master_substreams) == 0) { + amdtp_stream_pcm_abort(master); + amdtp_stream_stop(master); + break_both_connections(bebob); + } + } + + mutex_unlock(&bebob->mutex); +} + +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob) +{ + /* vs. XRUN recovery due to discontinuity at bus reset */ + mutex_lock(&bebob->mutex); + + if ((cmp_connection_update(&bebob->in_conn) < 0) || + (cmp_connection_update(&bebob->out_conn) < 0)) { + amdtp_stream_pcm_abort(&bebob->rx_stream); + amdtp_stream_pcm_abort(&bebob->tx_stream); + amdtp_stream_stop(&bebob->rx_stream); + amdtp_stream_stop(&bebob->tx_stream); + break_both_connections(bebob); + } else { + amdtp_stream_update(&bebob->rx_stream); + amdtp_stream_update(&bebob->tx_stream); + } + + /* wake up stream_start_duplex() */ + if (!completion_done(&bebob->bus_reset)) + complete_all(&bebob->bus_reset); + + mutex_unlock(&bebob->mutex); +} + +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) +{ + mutex_lock(&bebob->mutex); + + amdtp_stream_pcm_abort(&bebob->rx_stream); + amdtp_stream_pcm_abort(&bebob->tx_stream); + + amdtp_stream_stop(&bebob->rx_stream); + amdtp_stream_stop(&bebob->tx_stream); + + amdtp_stream_destroy(&bebob->rx_stream); + amdtp_stream_destroy(&bebob->tx_stream); + + destroy_both_connections(bebob); + + mutex_unlock(&bebob->mutex); +} + +/* + * See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’ + * in Additional AVC commands (Nov 2003, BridgeCo) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +static int +parse_stream_formation(u8 *buf, unsigned int len, + struct snd_bebob_stream_formation *formation) +{ + unsigned int i, e, channels, format; + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((buf[0] != 0x90) || (buf[1] != 0x40)) + return -ENOSYS; + + /* check sampling rate */ + for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) { + if (buf[2] == bridgeco_freq_table[i]) + break; + } + if (i == ARRAY_SIZE(bridgeco_freq_table)) + return -ENOSYS; + + /* Avoid double count by different entries for the same rate. */ + memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation)); + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* IEC 60958 Conformant, currently handled as MBLA */ + case 0x00: + /* Multi bit linear audio */ + case 0x06: /* Raw */ + formation[i].pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation[i].midi += channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi bit linear audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation[i].pcm > AMDTP_MAX_CHANNELS_FOR_PCM || + formation[i].midi > AMDTP_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; + + return 0; +} + +static int +fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir, + unsigned short pid) +{ + u8 *buf; + struct snd_bebob_stream_formation *formations; + unsigned int len, eid; + u8 addr[AVC_BRIDGECO_ADDR_BYTES]; + int err; + + buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_BRIDGECO_PLUG_DIR_IN) + formations = bebob->rx_stream_formations; + else + formations = bebob->tx_stream_formations; + + for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) { + len = FORMAT_MAXIMUM_LENGTH; + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, pid); + err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, + &len, eid); + /* No entries remained. */ + if (err == -EINVAL && eid > 0) { + err = 0; + break; + } else if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + + err = parse_stream_formation(buf, len, formations); + if (err < 0) + break; + } + + kfree(buf); + return err; +} + +static int +seek_msu_sync_input_plug(struct snd_bebob *bebob) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + unsigned int i; + enum avc_bridgeco_plug_type type; + int err; + + /* Get the number of Music Sub Unit for both direction. */ + err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for MSU in/out plugs: %d\n", + err); + goto end; + } + + /* seek destination plugs for 'MSU sync input' */ + bebob->sync_input_plug = -1; + for (i = 0; i < plugs[0]; i++) { + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for MSU in plug %d: %d\n", + i, err); + goto end; + } + + if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { + bebob->sync_input_plug = i; + break; + } + } +end: + return err; +} + +int snd_bebob_stream_discover(struct snd_bebob *bebob) +{ + struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + enum avc_bridgeco_plug_type type; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } + + /* + * This module supports at least one isoc input plug and one isoc + * output plug. + */ + if ((plugs[0] == 0) || (plugs[1] == 0)) { + err = -ENOSYS; + goto end; + } + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc in plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc out plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* count external input plugs for MIDI */ + bebob->midi_input_ports = 0; + for (i = 0; i < plugs[2]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external in plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_input_ports++; + } + } + + /* count external output plugs for MIDI */ + bebob->midi_output_ports = 0; + for (i = 0; i < plugs[3]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external out plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_output_ports++; + } + } + + /* for check source of clock later */ + if (!clk_spec) + err = seek_msu_sync_input_plug(bebob); +end: + return err; +} + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob) +{ + bebob->dev_lock_changed = true; + wake_up(&bebob->hwdep_wait); +} + +int snd_bebob_stream_lock_try(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + /* user land lock this */ + if (bebob->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (bebob->dev_lock_count++ == 0) + snd_bebob_stream_lock_changed(bebob); + err = 0; +end: + spin_unlock_irq(&bebob->lock); + return err; +} + +void snd_bebob_stream_lock_release(struct snd_bebob *bebob) +{ + spin_lock_irq(&bebob->lock); + + if (WARN_ON(bebob->dev_lock_count <= 0)) + goto end; + if (--bebob->dev_lock_count == 0) + snd_bebob_stream_lock_changed(bebob); +end: + spin_unlock_irq(&bebob->lock); +} diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c new file mode 100644 index 000000000000..eef8ea7d9b97 --- /dev/null +++ b/sound/firewire/bebob/bebob_terratec.c @@ -0,0 +1,68 @@ +/* + * bebob_terratec.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static char *const phase88_rack_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "Digital In", "Word Clock" +}; +static int +phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + unsigned int enable_ext, enable_word; + int err; + + err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_ext); + if (err < 0) + goto end; + err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_word); + if (err < 0) + goto end; + + *id = (enable_ext & 0x01) | ((enable_word & 0x01) << 1); +end: + return err; +} + +static char *const phase24_series_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "Digital In" +}; +static int +phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + return avc_audio_get_selector(bebob->unit, 0, 4, id); +} + +static struct snd_bebob_rate_spec phase_series_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; + +/* PHASE 88 Rack FW */ +static struct snd_bebob_clock_spec phase88_rack_clk = { + .num = ARRAY_SIZE(phase88_rack_clk_src_labels), + .labels = phase88_rack_clk_src_labels, + .get = &phase88_rack_clk_src_get, +}; +struct snd_bebob_spec phase88_rack_spec = { + .clock = &phase88_rack_clk, + .rate = &phase_series_rate_spec, + .meter = NULL +}; + +/* 'PHASE 24 FW' and 'PHASE X24 FW' */ +static struct snd_bebob_clock_spec phase24_series_clk = { + .num = ARRAY_SIZE(phase24_series_clk_src_labels), + .labels = phase24_series_clk_src_labels, + .get = &phase24_series_clk_src_get, +}; +struct snd_bebob_spec phase24_series_spec = { + .clock = &phase24_series_clk, + .rate = &phase_series_rate_spec, + .meter = NULL +}; diff --git a/sound/firewire/bebob/bebob_yamaha.c b/sound/firewire/bebob/bebob_yamaha.c new file mode 100644 index 000000000000..9b7e798180ff --- /dev/null +++ b/sound/firewire/bebob/bebob_yamaha.c @@ -0,0 +1,50 @@ +/* + * bebob_yamaha.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +/* + * NOTE: + * Yamaha GO44 is not designed to be used as stand-alone mixer. So any streams + * must be accompanied. If changing the state, a LED on the device starts to + * blink and its sync status is false. In this state, the device sounds nothing + * even if streaming. To start streaming at the current sampling rate is only + * way to revocer this state. GO46 is better for stand-alone mixer. + * + * Both of them have a capability to change its sampling rate up to 192.0kHz. + * At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out. + * But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use + * 'Extended Stream Format Information Command - Single Request' in 'Additional + * AVC commands' defined by BridgeCo. + * This ALSA driver don't do this because a bit tiresome. Then isochronous + * streaming with many asynchronous transactions brings sounds with noises. + * Unfortunately current 'ffado-mixer' generated many asynchronous transaction + * to observe device's state, mainly check cmp connection and signal format. I + * reccomend users to close ffado-mixer at 192.0kHz if mixer is needless. + */ + +static char *const clk_src_labels[] = {SND_BEBOB_CLOCK_INTERNAL, "SPDIF"}; +static int +clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + return avc_audio_get_selector(bebob->unit, 0, 4, id); +} +static struct snd_bebob_clock_spec clock_spec = { + .num = ARRAY_SIZE(clk_src_labels), + .labels = clk_src_labels, + .get = &clk_src_get, +}; +static struct snd_bebob_rate_spec rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +struct snd_bebob_spec yamaha_go_spec = { + .clock = &clock_spec, + .rate = &rate_spec, + .meter = NULL +}; diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c index efdbf585e404..ba8df5a1be39 100644 --- a/sound/firewire/cmp.c +++ b/sound/firewire/cmp.c @@ -14,18 +14,28 @@ #include "iso-resources.h" #include "cmp.h" -#define IMPR_SPEED_MASK 0xc0000000 -#define IMPR_SPEED_SHIFT 30 -#define IMPR_XSPEED_MASK 0x00000060 -#define IMPR_XSPEED_SHIFT 5 -#define IMPR_PLUGS_MASK 0x0000001f - -#define IPCR_ONLINE 0x80000000 -#define IPCR_BCAST_CONN 0x40000000 -#define IPCR_P2P_CONN_MASK 0x3f000000 -#define IPCR_P2P_CONN_SHIFT 24 -#define IPCR_CHANNEL_MASK 0x003f0000 -#define IPCR_CHANNEL_SHIFT 16 +/* MPR common fields */ +#define MPR_SPEED_MASK 0xc0000000 +#define MPR_SPEED_SHIFT 30 +#define MPR_XSPEED_MASK 0x00000060 +#define MPR_XSPEED_SHIFT 5 +#define MPR_PLUGS_MASK 0x0000001f + +/* PCR common fields */ +#define PCR_ONLINE 0x80000000 +#define PCR_BCAST_CONN 0x40000000 +#define PCR_P2P_CONN_MASK 0x3f000000 +#define PCR_P2P_CONN_SHIFT 24 +#define PCR_CHANNEL_MASK 0x003f0000 +#define PCR_CHANNEL_SHIFT 16 + +/* oPCR specific fields */ +#define OPCR_XSPEED_MASK 0x00C00000 +#define OPCR_XSPEED_SHIFT 22 +#define OPCR_SPEED_MASK 0x0000C000 +#define OPCR_SPEED_SHIFT 14 +#define OPCR_OVERHEAD_ID_MASK 0x00003C00 +#define OPCR_OVERHEAD_ID_SHIFT 10 enum bus_reset_handling { ABORT_ON_BUS_RESET, @@ -39,10 +49,27 @@ void cmp_error(struct cmp_connection *c, const char *fmt, ...) va_start(va, fmt); dev_err(&c->resources.unit->device, "%cPCR%u: %pV", - 'i', c->pcr_index, &(struct va_format){ fmt, &va }); + (c->direction == CMP_INPUT) ? 'i' : 'o', + c->pcr_index, &(struct va_format){ fmt, &va }); va_end(va); } +static u64 mpr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IMPR; + else + return CSR_REGISTER_BASE + CSR_OMPR; +} + +static u64 pcr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index); + else + return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index); +} + static int pcr_modify(struct cmp_connection *c, __be32 (*modify)(struct cmp_connection *c, __be32 old), int (*check)(struct cmp_connection *c, __be32 pcr), @@ -58,8 +85,7 @@ static int pcr_modify(struct cmp_connection *c, err = snd_fw_transaction( c->resources.unit, TCODE_LOCK_COMPARE_SWAP, - CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index), - buffer, 8, + pcr_address(c), buffer, 8, FW_FIXED_GENERATION | c->resources.generation); if (err < 0) { @@ -88,24 +114,25 @@ static int pcr_modify(struct cmp_connection *c, * cmp_connection_init - initializes a connection manager * @c: the connection manager to initialize * @unit: a unit of the target device - * @ipcr_index: the index of the iPCR on the target device + * @pcr_index: the index of the iPCR/oPCR on the target device */ int cmp_connection_init(struct cmp_connection *c, struct fw_unit *unit, - unsigned int ipcr_index) + enum cmp_direction direction, + unsigned int pcr_index) { - __be32 impr_be; - u32 impr; + __be32 mpr_be; + u32 mpr; int err; + c->direction = direction; err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, - CSR_REGISTER_BASE + CSR_IMPR, - &impr_be, 4, 0); + mpr_address(c), &mpr_be, 4, 0); if (err < 0) return err; - impr = be32_to_cpu(impr_be); + mpr = be32_to_cpu(mpr_be); - if (ipcr_index >= (impr & IMPR_PLUGS_MASK)) + if (pcr_index >= (mpr & MPR_PLUGS_MASK)) return -EINVAL; err = fw_iso_resources_init(&c->resources, unit); @@ -115,16 +142,36 @@ int cmp_connection_init(struct cmp_connection *c, c->connected = false; mutex_init(&c->mutex); c->last_pcr_value = cpu_to_be32(0x80000000); - c->pcr_index = ipcr_index; - c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT; + c->pcr_index = pcr_index; + c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT; if (c->max_speed == SCODE_BETA) - c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; + c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT; return 0; } EXPORT_SYMBOL(cmp_connection_init); /** + * cmp_connection_check_used - check connection is already esablished or not + * @c: the connection manager to be checked + */ +int cmp_connection_check_used(struct cmp_connection *c, bool *used) +{ + __be32 pcr; + int err; + + err = snd_fw_transaction( + c->resources.unit, TCODE_READ_QUADLET_REQUEST, + pcr_address(c), &pcr, 4, 0); + if (err >= 0) + *used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK)); + + return err; +} +EXPORT_SYMBOL(cmp_connection_check_used); + +/** * cmp_connection_destroy - free connection manager resources * @c: the connection manager */ @@ -139,23 +186,70 @@ EXPORT_SYMBOL(cmp_connection_destroy); static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) { - ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN | - IPCR_P2P_CONN_MASK | - IPCR_CHANNEL_MASK); - ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT); - ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); + ipcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + PCR_CHANNEL_MASK); + ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); return ipcr; } -static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) +static int get_overhead_id(struct cmp_connection *c) { - if (ipcr & cpu_to_be32(IPCR_BCAST_CONN | - IPCR_P2P_CONN_MASK)) { + int id; + + /* + * apply "oPCR overhead ID encoding" + * the encoding table can convert up to 512. + * here the value over 512 is converted as the same way as 512. + */ + for (id = 1; id < 16; id++) { + if (c->resources.bandwidth_overhead < (id << 5)) + break; + } + if (id == 16) + id = 0; + + return id; +} + +static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr) +{ + unsigned int spd, xspd; + + /* generate speed and extended speed field value */ + if (c->speed > SCODE_400) { + spd = SCODE_800; + xspd = c->speed - SCODE_800; + } else { + spd = c->speed; + xspd = 0; + } + + opcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + OPCR_XSPEED_MASK | + PCR_CHANNEL_MASK | + OPCR_SPEED_MASK | + OPCR_OVERHEAD_ID_MASK); + opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT); + opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); + opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT); + opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT); + + return opcr; +} + +static int pcr_set_check(struct cmp_connection *c, __be32 pcr) +{ + if (pcr & cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK)) { cmp_error(c, "plug is already in use\n"); return -EBUSY; } - if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) { + if (!(pcr & cpu_to_be32(PCR_ONLINE))) { cmp_error(c, "plug is not on-line\n"); return -ECONNREFUSED; } @@ -170,9 +264,9 @@ static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) * * This function establishes a point-to-point connection from the local * computer to the target by allocating isochronous resources (channel and - * bandwidth) and setting the target's input plug control register. When this - * function succeeds, the caller is responsible for starting transmitting - * packets. + * bandwidth) and setting the target's input/output plug control register. + * When this function succeeds, the caller is responsible for starting + * transmitting packets. */ int cmp_connection_establish(struct cmp_connection *c, unsigned int max_payload_bytes) @@ -193,8 +287,13 @@ retry_after_bus_reset: if (err < 0) goto err_mutex; - err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, - ABORT_ON_BUS_RESET); + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + if (err == -EAGAIN) { fw_iso_resources_free(&c->resources); goto retry_after_bus_reset; @@ -221,8 +320,8 @@ EXPORT_SYMBOL(cmp_connection_establish); * cmp_connection_update - update the connection after a bus reset * @c: the connection manager * - * This function must be called from the driver's .update handler to reestablish - * any connection that might have been active. + * This function must be called from the driver's .update handler to + * reestablish any connection that might have been active. * * Returns zero on success, or a negative error code. On an error, the * connection is broken and the caller must stop transmitting iso packets. @@ -242,8 +341,13 @@ int cmp_connection_update(struct cmp_connection *c) if (err < 0) goto err_unconnect; - err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, - SUCCEED_ON_BUS_RESET); + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + if (err < 0) goto err_resources; @@ -261,19 +365,18 @@ err_unconnect: } EXPORT_SYMBOL(cmp_connection_update); - -static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr) +static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr) { - return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK); + return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK); } /** * cmp_connection_break - break the connection to the target * @c: the connection manager * - * This function deactives the connection in the target's input plug control - * register, and frees the isochronous resources of the connection. Before - * calling this function, the caller should cease transmitting packets. + * This function deactives the connection in the target's input/output plug + * control register, and frees the isochronous resources of the connection. + * Before calling this function, the caller should cease transmitting packets. */ void cmp_connection_break(struct cmp_connection *c) { @@ -286,7 +389,7 @@ void cmp_connection_break(struct cmp_connection *c) return; } - err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); + err = pcr_modify(c, pcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); if (err < 0) cmp_error(c, "plug is still connected\n"); diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h index f47de08feb12..ebcb48484fca 100644 --- a/sound/firewire/cmp.h +++ b/sound/firewire/cmp.h @@ -7,12 +7,17 @@ struct fw_unit; +enum cmp_direction { + CMP_INPUT = 0, + CMP_OUTPUT, +}; + /** * struct cmp_connection - manages an isochronous connection to a device * @speed: the connection's actual speed * - * This structure manages (using CMP) an isochronous stream from the local - * computer to a device's input plug (iPCR). + * This structure manages (using CMP) an isochronous stream between the local + * computer and a device's input plug (iPCR) and output plug (oPCR). * * There is no corresponding oPCR created on the local computer, so it is not * possible to overlay connections on top of this one. @@ -26,11 +31,14 @@ struct cmp_connection { __be32 last_pcr_value; unsigned int pcr_index; unsigned int max_speed; + enum cmp_direction direction; }; int cmp_connection_init(struct cmp_connection *connection, struct fw_unit *unit, - unsigned int ipcr_index); + enum cmp_direction direction, + unsigned int pcr_index); +int cmp_connection_check_used(struct cmp_connection *connection, bool *used); void cmp_connection_destroy(struct cmp_connection *connection); int cmp_connection_establish(struct cmp_connection *connection, diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 0c3948630cf7..a9a30c0161f1 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -51,7 +51,7 @@ struct dice { wait_queue_head_t hwdep_wait; u32 notification_bits; struct fw_iso_resources resources; - struct amdtp_out_stream stream; + struct amdtp_stream stream; }; MODULE_DESCRIPTION("DICE driver"); @@ -420,22 +420,7 @@ static int dice_open(struct snd_pcm_substream *substream) if (err < 0) goto err_lock; - err = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); - if (err < 0) - goto err_lock; - err = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); - if (err < 0) - goto err_lock; - - err = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 5000, UINT_MAX); - if (err < 0) - goto err_lock; - - err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + err = amdtp_stream_add_pcm_hw_constraints(&dice->stream, runtime); if (err < 0) goto err_lock; @@ -460,17 +445,17 @@ static int dice_stream_start_packets(struct dice *dice) { int err; - if (amdtp_out_stream_running(&dice->stream)) + if (amdtp_stream_running(&dice->stream)) return 0; - err = amdtp_out_stream_start(&dice->stream, dice->resources.channel, - fw_parent_device(dice->unit)->max_speed); + err = amdtp_stream_start(&dice->stream, dice->resources.channel, + fw_parent_device(dice->unit)->max_speed); if (err < 0) return err; err = dice_enable_set(dice); if (err < 0) { - amdtp_out_stream_stop(&dice->stream); + amdtp_stream_stop(&dice->stream); return err; } @@ -484,7 +469,7 @@ static int dice_stream_start(struct dice *dice) if (!dice->resources.allocated) { err = fw_iso_resources_allocate(&dice->resources, - amdtp_out_stream_get_max_payload(&dice->stream), + amdtp_stream_get_max_payload(&dice->stream), fw_parent_device(dice->unit)->max_speed); if (err < 0) goto error; @@ -516,9 +501,9 @@ error: static void dice_stream_stop_packets(struct dice *dice) { - if (amdtp_out_stream_running(&dice->stream)) { + if (amdtp_stream_running(&dice->stream)) { dice_enable_clear(dice); - amdtp_out_stream_stop(&dice->stream); + amdtp_stream_stop(&dice->stream); } } @@ -563,7 +548,7 @@ static int dice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct dice *dice = substream->private_data; - unsigned int rate_index, mode; + unsigned int rate_index, mode, rate, channels, i; int err; mutex_lock(&dice->mutex); @@ -575,18 +560,39 @@ static int dice_hw_params(struct snd_pcm_substream *substream, if (err < 0) return err; - rate_index = rate_to_index(params_rate(hw_params)); + rate = params_rate(hw_params); + rate_index = rate_to_index(rate); err = dice_change_rate(dice, rate_index << CLOCK_RATE_SHIFT); if (err < 0) return err; + /* + * At rates above 96 kHz, pretend that the stream runs at half the + * actual sample rate with twice the number of channels; two samples + * of a channel are stored consecutively in the packet. Requires + * blocking mode and PCM buffer size should be aligned to SYT_INTERVAL. + */ + channels = params_channels(hw_params); + if (rate_index > 4) { + if (channels > AMDTP_MAX_CHANNELS_FOR_PCM / 2) { + err = -ENOSYS; + return err; + } + + for (i = 0; i < channels; i++) { + dice->stream.pcm_positions[i * 2] = i; + dice->stream.pcm_positions[i * 2 + 1] = i + channels; + } + + rate /= 2; + channels *= 2; + } + mode = rate_index_to_mode(rate_index); - amdtp_out_stream_set_parameters(&dice->stream, - params_rate(hw_params), - params_channels(hw_params), - dice->rx_midi_ports[mode]); - amdtp_out_stream_set_pcm_format(&dice->stream, - params_format(hw_params)); + amdtp_stream_set_parameters(&dice->stream, rate, channels, + dice->rx_midi_ports[mode]); + amdtp_stream_set_pcm_format(&dice->stream, + params_format(hw_params)); return 0; } @@ -609,7 +615,7 @@ static int dice_prepare(struct snd_pcm_substream *substream) mutex_lock(&dice->mutex); - if (amdtp_out_streaming_error(&dice->stream)) + if (amdtp_streaming_error(&dice->stream)) dice_stream_stop_packets(dice); err = dice_stream_start(dice); @@ -620,7 +626,7 @@ static int dice_prepare(struct snd_pcm_substream *substream) mutex_unlock(&dice->mutex); - amdtp_out_stream_pcm_prepare(&dice->stream); + amdtp_stream_pcm_prepare(&dice->stream); return 0; } @@ -640,7 +646,7 @@ static int dice_trigger(struct snd_pcm_substream *substream, int cmd) default: return -EINVAL; } - amdtp_out_stream_pcm_trigger(&dice->stream, pcm); + amdtp_stream_pcm_trigger(&dice->stream, pcm); return 0; } @@ -649,7 +655,7 @@ static snd_pcm_uframes_t dice_pointer(struct snd_pcm_substream *substream) { struct dice *dice = substream->private_data; - return amdtp_out_stream_pcm_pointer(&dice->stream); + return amdtp_stream_pcm_pointer(&dice->stream); } static int dice_create_pcm(struct dice *dice) @@ -1104,7 +1110,7 @@ static void dice_card_free(struct snd_card *card) { struct dice *dice = card->private_data; - amdtp_out_stream_destroy(&dice->stream); + amdtp_stream_destroy(&dice->stream); fw_core_remove_address_handler(&dice->notification_handler); mutex_destroy(&dice->mutex); } @@ -1360,8 +1366,8 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) goto err_owner; dice->resources.channels_mask = 0x00000000ffffffffuLL; - err = amdtp_out_stream_init(&dice->stream, unit, - CIP_BLOCKING | CIP_HI_DUALWIRE); + err = amdtp_stream_init(&dice->stream, unit, AMDTP_OUT_STREAM, + CIP_BLOCKING); if (err < 0) goto err_resources; @@ -1417,7 +1423,7 @@ static void dice_remove(struct fw_unit *unit) { struct dice *dice = dev_get_drvdata(&unit->device); - amdtp_out_stream_pcm_abort(&dice->stream); + amdtp_stream_pcm_abort(&dice->stream); snd_card_disconnect(dice->card); @@ -1443,7 +1449,7 @@ static void dice_bus_reset(struct fw_unit *unit) * to stop so that the application can restart them in an orderly * manner. */ - amdtp_out_stream_pcm_abort(&dice->stream); + amdtp_stream_pcm_abort(&dice->stream); mutex_lock(&dice->mutex); diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index 860c08073c59..0619597e3a3f 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -10,12 +10,14 @@ #include <linux/firewire-constants.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/slab.h> #include <linux/sched.h> #include <linux/spinlock.h> #include <linux/wait.h> #include <linux/delay.h> #include "fcp.h" #include "lib.h" +#include "amdtp.h" #define CTS_AVC 0x00 @@ -23,6 +25,158 @@ #define ERROR_DELAY_MS 5 #define FCP_TIMEOUT_MS 125 +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + bool flag; + int err; + + flag = false; + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) { + flag = true; + break; + } + } + if (!flag) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used)*/ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_set_sig_fmt); + +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* Unit */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0xff; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-4] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* check sfc field and pick up rate */ + sfc = 0x07 & buf[5]; + if (sfc >= CIP_SFC_COUNT) { + err = -EAGAIN; /* also in transition */ + goto end; + } + + *rate = amdtp_rate_table[sfc]; + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_sig_fmt); + +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]) +{ + u8 *buf; + int err; + + /* extended subunit in spec.4.2 is not supported */ + if ((subunit_type == 0x1E) || (subunit_id == 5)) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + /* UNIT or Subunit, Functionblock */ + buf[1] = ((subunit_type & 0x1f) << 3) | (subunit_id & 0x7); + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xff & subfunction; + + err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + info[0] = buf[4]; + info[1] = buf[5]; + info[2] = buf[6]; + info[3] = buf[7]; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_plug_info); + static DEFINE_SPINLOCK(transactions_lock); static LIST_HEAD(transactions); @@ -30,6 +184,7 @@ enum fcp_state { STATE_PENDING, STATE_BUS_RESET, STATE_COMPLETE, + STATE_DEFERRED, }; struct fcp_transaction { @@ -40,6 +195,7 @@ struct fcp_transaction { unsigned int response_match_bytes; enum fcp_state state; wait_queue_head_t wait; + bool deferrable; }; /** @@ -62,8 +218,6 @@ struct fcp_transaction { * * @command and @response can point to the same buffer. * - * Asynchronous operation (INTERIM, NOTIFY) is not supported at the moment. - * * Returns the actual size of the response frame, or a negative error code. */ int fcp_avc_transaction(struct fw_unit *unit, @@ -81,6 +235,9 @@ int fcp_avc_transaction(struct fw_unit *unit, t.state = STATE_PENDING; init_waitqueue_head(&t.wait); + if (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03) + t.deferrable = true; + spin_lock_irq(&transactions_lock); list_add_tail(&t.list, &transactions); spin_unlock_irq(&transactions_lock); @@ -93,11 +250,21 @@ int fcp_avc_transaction(struct fw_unit *unit, (void *)command, command_size, 0); if (ret < 0) break; - +deferred: wait_event_timeout(t.wait, t.state != STATE_PENDING, msecs_to_jiffies(FCP_TIMEOUT_MS)); - if (t.state == STATE_COMPLETE) { + if (t.state == STATE_DEFERRED) { + /* + * 'AV/C General Specification' define no time limit + * on command completion once an INTERIM response has + * been sent. but we promise to finish this function + * for a caller. Here we use FCP_TIMEOUT_MS for next + * interval. This is not in the specification. + */ + t.state = STATE_PENDING; + goto deferred; + } else if (t.state == STATE_COMPLETE) { ret = t.response_size; break; } else if (t.state == STATE_BUS_RESET) { @@ -132,7 +299,8 @@ void fcp_bus_reset(struct fw_unit *unit) spin_lock_irq(&transactions_lock); list_for_each_entry(t, &transactions, list) { if (t->unit == unit && - t->state == STATE_PENDING) { + (t->state == STATE_PENDING || + t->state == STATE_DEFERRED)) { t->state = STATE_BUS_RESET; wake_up(&t->wait); } @@ -186,10 +354,15 @@ static void fcp_response(struct fw_card *card, struct fw_request *request, if (t->state == STATE_PENDING && is_matching_response(t, data, length)) { - t->state = STATE_COMPLETE; - t->response_size = min((unsigned int)length, - t->response_size); - memcpy(t->response_buffer, data, t->response_size); + if (t->deferrable && *(const u8 *)data == 0x0f) { + t->state = STATE_DEFERRED; + } else { + t->state = STATE_COMPLETE; + t->response_size = min_t(unsigned int, length, + t->response_size); + memcpy(t->response_buffer, data, + t->response_size); + } wake_up(&t->wait); } } diff --git a/sound/firewire/fcp.h b/sound/firewire/fcp.h index 86595688bd91..63ae4f7ce3af 100644 --- a/sound/firewire/fcp.h +++ b/sound/firewire/fcp.h @@ -1,8 +1,29 @@ #ifndef SOUND_FIREWIRE_FCP_H_INCLUDED #define SOUND_FIREWIRE_FCP_H_INCLUDED +#define AVC_PLUG_INFO_BUF_BYTES 4 + struct fw_unit; +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +enum avc_general_plug_dir { + AVC_GENERAL_PLUG_DIR_IN = 0, + AVC_GENERAL_PLUG_DIR_OUT = 1, + AVC_GENERAL_PLUG_DIR_COUNT +}; +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]); + int fcp_avc_transaction(struct fw_unit *unit, const void *command, unsigned int command_size, void *response, unsigned int response_size, diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile new file mode 100644 index 000000000000..0c7440826db8 --- /dev/null +++ b/sound/firewire/fireworks/Makefile @@ -0,0 +1,4 @@ +snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ + fireworks_stream.o fireworks_proc.o fireworks_midi.o \ + fireworks_pcm.o fireworks_hwdep.o fireworks.o +obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c new file mode 100644 index 000000000000..3e2ed8e82cbc --- /dev/null +++ b/sound/firewire/fireworks/fireworks.c @@ -0,0 +1,352 @@ +/* + * fireworks.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * Fireworks is a board module which Echo Audio produced. This module consists + * of three chipsets: + * - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6 + * - DSP or/and FPGA for signal processing + * - Flash Memory to store firmwares + */ + +#include "fireworks.h" + +MODULE_DESCRIPTION("Echo Fireworks driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +unsigned int snd_efw_resp_buf_size = 1024; +bool snd_efw_resp_buf_debug = false; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable Fireworks sound card"); +module_param_named(resp_buf_size, snd_efw_resp_buf_size, uint, 0444); +MODULE_PARM_DESC(resp_buf_size, + "response buffer size (max 4096, default 1024)"); +module_param_named(resp_buf_debug, snd_efw_resp_buf_debug, bool, 0444); +MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +#define VENDOR_LOUD 0x000ff2 +#define MODEL_MACKIE_400F 0x00400f +#define MODEL_MACKIE_1200F 0x01200f + +#define VENDOR_ECHO 0x001486 +#define MODEL_ECHO_AUDIOFIRE_12 0x00af12 +#define MODEL_ECHO_AUDIOFIRE_12HD 0x0af12d +#define MODEL_ECHO_AUDIOFIRE_12_APPLE 0x0af12a +/* This is applied for AudioFire8 (until 2009 July) */ +#define MODEL_ECHO_AUDIOFIRE_8 0x000af8 +#define MODEL_ECHO_AUDIOFIRE_2 0x000af2 +#define MODEL_ECHO_AUDIOFIRE_4 0x000af4 +/* AudioFire9 is applied for AudioFire8(since 2009 July) and AudioFirePre8 */ +#define MODEL_ECHO_AUDIOFIRE_9 0x000af9 +/* unknown as product */ +#define MODEL_ECHO_FIREWORKS_8 0x0000f8 +#define MODEL_ECHO_FIREWORKS_HDMI 0x00afd1 + +#define VENDOR_GIBSON 0x00075b +/* for Robot Interface Pack of Dark Fire, Dusk Tiger, Les Paul Standard 2010 */ +#define MODEL_GIBSON_RIP 0x00afb2 +/* unknown as product */ +#define MODEL_GIBSON_GOLDTOP 0x00afb9 + +/* part of hardware capability flags */ +#define FLAG_RESP_ADDR_CHANGABLE 0 + +static int +get_hardware_info(struct snd_efw *efw) +{ + struct fw_device *fw_dev = fw_parent_device(efw->unit); + struct snd_efw_hwinfo *hwinfo; + char version[12] = {0}; + int err; + + hwinfo = kzalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return -ENOMEM; + + err = snd_efw_command_get_hwinfo(efw, hwinfo); + if (err < 0) + goto end; + + /* firmware version for communication chipset */ + snprintf(version, sizeof(version), "%u.%u", + (hwinfo->arm_version >> 24) & 0xff, + (hwinfo->arm_version >> 16) & 0xff); + efw->firmware_version = hwinfo->arm_version; + + strcpy(efw->card->driver, "Fireworks"); + strcpy(efw->card->shortname, hwinfo->model_name); + strcpy(efw->card->mixername, hwinfo->model_name); + snprintf(efw->card->longname, sizeof(efw->card->longname), + "%s %s v%s, GUID %08x%08x at %s, S%d", + hwinfo->vendor_name, hwinfo->model_name, version, + hwinfo->guid_hi, hwinfo->guid_lo, + dev_name(&efw->unit->device), 100 << fw_dev->max_speed); + + if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE)) + efw->resp_addr_changable = true; + + efw->supported_sampling_rate = 0; + if ((hwinfo->min_sample_rate <= 22050) + && (22050 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050; + if ((hwinfo->min_sample_rate <= 32000) + && (32000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000; + if ((hwinfo->min_sample_rate <= 44100) + && (44100 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100; + if ((hwinfo->min_sample_rate <= 48000) + && (48000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000; + if ((hwinfo->min_sample_rate <= 88200) + && (88200 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200; + if ((hwinfo->min_sample_rate <= 96000) + && (96000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000; + if ((hwinfo->min_sample_rate <= 176400) + && (176400 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400; + if ((hwinfo->min_sample_rate <= 192000) + && (192000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000; + + /* the number of MIDI ports, not of MIDI conformant data channels */ + if (hwinfo->midi_out_ports > SND_EFW_MAX_MIDI_OUT_PORTS || + hwinfo->midi_in_ports > SND_EFW_MAX_MIDI_IN_PORTS) { + err = -EIO; + goto end; + } + efw->midi_out_ports = hwinfo->midi_out_ports; + efw->midi_in_ports = hwinfo->midi_in_ports; + + if (hwinfo->amdtp_tx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels; + efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x; + efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x; + efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels; + efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x; + efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x; + + /* Hardware metering. */ + if (hwinfo->phys_in_grp_count > HWINFO_MAX_CAPS_GROUPS || + hwinfo->phys_out_grp_count > HWINFO_MAX_CAPS_GROUPS) { + err = -EIO; + goto end; + } + efw->phys_in = hwinfo->phys_in; + efw->phys_out = hwinfo->phys_out; + efw->phys_in_grp_count = hwinfo->phys_in_grp_count; + efw->phys_out_grp_count = hwinfo->phys_out_grp_count; + memcpy(&efw->phys_in_grps, hwinfo->phys_in_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count); + memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count); +end: + kfree(hwinfo); + return err; +} + +static void +efw_card_free(struct snd_card *card) +{ + struct snd_efw *efw = card->private_data; + + if (efw->card_index >= 0) { + mutex_lock(&devices_mutex); + clear_bit(efw->card_index, devices_used); + mutex_unlock(&devices_mutex); + } + + mutex_destroy(&efw->mutex); + kfree(efw->resp_buf); +} + +static int +efw_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_efw *efw; + int card_index, err; + + mutex_lock(&devices_mutex); + + /* check registered cards */ + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + err = -ENOENT; + goto end; + } + + err = snd_card_new(&unit->device, index[card_index], id[card_index], + THIS_MODULE, sizeof(struct snd_efw), &card); + if (err < 0) + goto end; + efw = card->private_data; + efw->card_index = card_index; + set_bit(card_index, devices_used); + card->private_free = efw_card_free; + + efw->card = card; + efw->unit = unit; + mutex_init(&efw->mutex); + spin_lock_init(&efw->lock); + init_waitqueue_head(&efw->hwdep_wait); + + /* prepare response buffer */ + snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size, + SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U); + efw->resp_buf = kzalloc(snd_efw_resp_buf_size, GFP_KERNEL); + if (efw->resp_buf == NULL) { + err = -ENOMEM; + goto error; + } + efw->pull_ptr = efw->push_ptr = efw->resp_buf; + snd_efw_transaction_add_instance(efw); + + err = get_hardware_info(efw); + if (err < 0) + goto error; + if (entry->model_id == MODEL_ECHO_AUDIOFIRE_9) + efw->is_af9 = true; + + snd_efw_proc_init(efw); + + if (efw->midi_out_ports || efw->midi_in_ports) { + err = snd_efw_create_midi_devices(efw); + if (err < 0) + goto error; + } + + err = snd_efw_create_pcm_devices(efw); + if (err < 0) + goto error; + + err = snd_efw_create_hwdep_device(efw); + if (err < 0) + goto error; + + err = snd_efw_stream_init_duplex(efw); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) { + snd_efw_stream_destroy_duplex(efw); + goto error; + } + + dev_set_drvdata(&unit->device, efw); +end: + mutex_unlock(&devices_mutex); + return err; +error: + snd_efw_transaction_remove_instance(efw); + mutex_unlock(&devices_mutex); + snd_card_free(card); + return err; +} + +static void efw_update(struct fw_unit *unit) +{ + struct snd_efw *efw = dev_get_drvdata(&unit->device); + + snd_efw_transaction_bus_reset(efw->unit); + snd_efw_stream_update_duplex(efw); +} + +static void efw_remove(struct fw_unit *unit) +{ + struct snd_efw *efw = dev_get_drvdata(&unit->device); + + snd_efw_stream_destroy_duplex(efw); + snd_efw_transaction_remove_instance(efw); + + snd_card_disconnect(efw->card); + snd_card_free_when_closed(efw->card); +} + +static const struct ieee1394_device_id efw_id_table[] = { + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_400F), + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_1200F), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12HD), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12_APPLE), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_2), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_4), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_9), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_HDMI), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_RIP), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_GOLDTOP), + {} +}; +MODULE_DEVICE_TABLE(ieee1394, efw_id_table); + +static struct fw_driver efw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireworks", + .bus = &fw_bus_type, + }, + .probe = efw_probe, + .update = efw_update, + .remove = efw_remove, + .id_table = efw_id_table, +}; + +static int __init snd_efw_init(void) +{ + int err; + + err = snd_efw_transaction_register(); + if (err < 0) + goto end; + + err = driver_register(&efw_driver.driver); + if (err < 0) + snd_efw_transaction_unregister(); + +end: + return err; +} + +static void __exit snd_efw_exit(void) +{ + snd_efw_transaction_unregister(); + driver_unregister(&efw_driver.driver); +} + +module_init(snd_efw_init); +module_exit(snd_efw_exit); diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h new file mode 100644 index 000000000000..4f0201a95222 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.h @@ -0,0 +1,232 @@ +/* + * fireworks.h - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#ifndef SOUND_FIREWORKS_H_INCLUDED +#define SOUND_FIREWORKS_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp.h" +#include "../cmp.h" +#include "../lib.h" + +#define SND_EFW_MAX_MIDI_OUT_PORTS 2 +#define SND_EFW_MAX_MIDI_IN_PORTS 2 + +#define SND_EFW_MULTIPLIER_MODES 3 +#define HWINFO_NAME_SIZE_BYTES 32 +#define HWINFO_MAX_CAPS_GROUPS 8 + +/* + * This should be greater than maximum bytes for EFW response content. + * Currently response against command for isochronous channel mapping is + * confirmed to be the maximum one. But for flexibility, use maximum data + * payload for asynchronous primary packets at S100 (Cable base rate) in + * IEEE Std 1394-1995. + */ +#define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U + +extern unsigned int snd_efw_resp_buf_size; +extern bool snd_efw_resp_buf_debug; + +struct snd_efw_phys_grp { + u8 type; /* see enum snd_efw_grp_type */ + u8 count; +} __packed; + +struct snd_efw { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; + + /* for transaction */ + u32 seqnum; + bool resp_addr_changable; + + /* for quirks */ + bool is_af9; + u32 firmware_version; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; + + unsigned int supported_sampling_rate; + unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES]; + unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES]; + + struct amdtp_stream *master; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + atomic_t capture_substreams; + atomic_t playback_substreams; + + /* hardware metering parameters */ + unsigned int phys_out; + unsigned int phys_in; + unsigned int phys_out_grp_count; + unsigned int phys_in_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* response queue */ + u8 *resp_buf; + u8 *pull_ptr; + u8 *push_ptr; + unsigned int resp_queues; +}; + +int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size); +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size); +int snd_efw_transaction_register(void); +void snd_efw_transaction_unregister(void); +void snd_efw_transaction_bus_reset(struct fw_unit *unit); +void snd_efw_transaction_add_instance(struct snd_efw *efw); +void snd_efw_transaction_remove_instance(struct snd_efw *efw); + +struct snd_efw_hwinfo { + u32 flags; + u32 guid_hi; + u32 guid_lo; + u32 type; + u32 version; + char vendor_name[HWINFO_NAME_SIZE_BYTES]; + char model_name[HWINFO_NAME_SIZE_BYTES]; + u32 supported_clocks; + u32 amdtp_rx_pcm_channels; + u32 amdtp_tx_pcm_channels; + u32 phys_out; + u32 phys_in; + u32 phys_out_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 phys_in_grp_count; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 midi_out_ports; + u32 midi_in_ports; + u32 max_sample_rate; + u32 min_sample_rate; + u32 dsp_version; + u32 arm_version; + u32 mixer_playback_channels; + u32 mixer_capture_channels; + u32 fpga_version; + u32 amdtp_rx_pcm_channels_2x; + u32 amdtp_tx_pcm_channels_2x; + u32 amdtp_rx_pcm_channels_4x; + u32 amdtp_tx_pcm_channels_4x; + u32 reserved[16]; +} __packed; +enum snd_efw_grp_type { + SND_EFW_CH_TYPE_ANALOG = 0, + SND_EFW_CH_TYPE_SPDIF = 1, + SND_EFW_CH_TYPE_ADAT = 2, + SND_EFW_CH_TYPE_SPDIF_OR_ADAT = 3, + SND_EFW_CH_TYPE_ANALOG_MIRRORING = 4, + SND_EFW_CH_TYPE_HEADPHONES = 5, + SND_EFW_CH_TYPE_I2S = 6, + SND_EFW_CH_TYPE_GUITAR = 7, + SND_EFW_CH_TYPE_PIEZO_GUITAR = 8, + SND_EFW_CH_TYPE_GUITAR_STRING = 9, + SND_EFW_CH_TYPE_DUMMY +}; +struct snd_efw_phys_meters { + u32 status; /* guitar state/midi signal/clock input detect */ + u32 reserved0; + u32 reserved1; + u32 reserved2; + u32 reserved3; + u32 out_meters; + u32 in_meters; + u32 reserved4; + u32 reserved5; + u32 values[0]; +} __packed; +enum snd_efw_clock_source { + SND_EFW_CLOCK_SOURCE_INTERNAL = 0, + SND_EFW_CLOCK_SOURCE_SYTMATCH = 1, + SND_EFW_CLOCK_SOURCE_WORDCLOCK = 2, + SND_EFW_CLOCK_SOURCE_SPDIF = 3, + SND_EFW_CLOCK_SOURCE_ADAT_1 = 4, + SND_EFW_CLOCK_SOURCE_ADAT_2 = 5, + SND_EFW_CLOCK_SOURCE_CONTINUOUS = 6 /* internal variable clock */ +}; +enum snd_efw_transport_mode { + SND_EFW_TRANSPORT_MODE_WINDOWS = 0, + SND_EFW_TRANSPORT_MODE_IEC61883 = 1, +}; +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low); +int snd_efw_command_set_tx_mode(struct snd_efw *efw, + enum snd_efw_transport_mode mode); +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo); +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len); +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source); +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate); +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate); + +int snd_efw_stream_init_duplex(struct snd_efw *efw); +int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate); +void snd_efw_stream_stop_duplex(struct snd_efw *efw); +void snd_efw_stream_update_duplex(struct snd_efw *efw); +void snd_efw_stream_destroy_duplex(struct snd_efw *efw); +void snd_efw_stream_lock_changed(struct snd_efw *efw); +int snd_efw_stream_lock_try(struct snd_efw *efw); +void snd_efw_stream_lock_release(struct snd_efw *efw); + +void snd_efw_proc_init(struct snd_efw *efw); + +int snd_efw_create_midi_devices(struct snd_efw *efw); + +int snd_efw_create_pcm_devices(struct snd_efw *efw); +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode); + +int snd_efw_create_hwdep_device(struct snd_efw *efw); + +#define SND_EFW_DEV_ENTRY(vendor, model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor,\ + .model_id = model \ +} + +#endif diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c new file mode 100644 index 000000000000..166f80584c2a --- /dev/null +++ b/sound/firewire/fireworks/fireworks_command.c @@ -0,0 +1,372 @@ +/* + * fireworks_command.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./fireworks.h" + +/* + * This driver uses transaction version 1 or later to use extended hardware + * information. Then too old devices are not available. + * + * Each commands are not required to have continuous sequence numbers. This + * number is just used to match command and response. + * + * This module support a part of commands. Please see FFADO if you want to see + * whole commands. But there are some commands which FFADO don't implement. + * + * Fireworks also supports AV/C general commands and AV/C Stream Format + * Information commands. But this module don't use them. + */ + +#define KERNEL_SEQNUM_MIN (SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 2) +#define KERNEL_SEQNUM_MAX ((u32)~0) + +/* for clock source and sampling rate */ +struct efc_clock { + u32 source; + u32 sampling_rate; + u32 index; +}; + +/* command categories */ +enum efc_category { + EFC_CAT_HWINFO = 0, + EFC_CAT_TRANSPORT = 2, + EFC_CAT_HWCTL = 3, +}; + +/* hardware info category commands */ +enum efc_cmd_hwinfo { + EFC_CMD_HWINFO_GET_CAPS = 0, + EFC_CMD_HWINFO_GET_POLLED = 1, + EFC_CMD_HWINFO_SET_RESP_ADDR = 2 +}; + +enum efc_cmd_transport { + EFC_CMD_TRANSPORT_SET_TX_MODE = 0 +}; + +/* hardware control category commands */ +enum efc_cmd_hwctl { + EFC_CMD_HWCTL_SET_CLOCK = 0, + EFC_CMD_HWCTL_GET_CLOCK = 1, + EFC_CMD_HWCTL_IDENTIFY = 5 +}; + +/* return values in response */ +enum efr_status { + EFR_STATUS_OK = 0, + EFR_STATUS_BAD = 1, + EFR_STATUS_BAD_COMMAND = 2, + EFR_STATUS_COMM_ERR = 3, + EFR_STATUS_BAD_QUAD_COUNT = 4, + EFR_STATUS_UNSUPPORTED = 5, + EFR_STATUS_1394_TIMEOUT = 6, + EFR_STATUS_DSP_TIMEOUT = 7, + EFR_STATUS_BAD_RATE = 8, + EFR_STATUS_BAD_CLOCK = 9, + EFR_STATUS_BAD_CHANNEL = 10, + EFR_STATUS_BAD_PAN = 11, + EFR_STATUS_FLASH_BUSY = 12, + EFR_STATUS_BAD_MIRROR = 13, + EFR_STATUS_BAD_LED = 14, + EFR_STATUS_BAD_PARAMETER = 15, + EFR_STATUS_INCOMPLETE = 0x80000000 +}; + +static const char *const efr_status_names[] = { + [EFR_STATUS_OK] = "OK", + [EFR_STATUS_BAD] = "bad", + [EFR_STATUS_BAD_COMMAND] = "bad command", + [EFR_STATUS_COMM_ERR] = "comm err", + [EFR_STATUS_BAD_QUAD_COUNT] = "bad quad count", + [EFR_STATUS_UNSUPPORTED] = "unsupported", + [EFR_STATUS_1394_TIMEOUT] = "1394 timeout", + [EFR_STATUS_DSP_TIMEOUT] = "DSP timeout", + [EFR_STATUS_BAD_RATE] = "bad rate", + [EFR_STATUS_BAD_CLOCK] = "bad clock", + [EFR_STATUS_BAD_CHANNEL] = "bad channel", + [EFR_STATUS_BAD_PAN] = "bad pan", + [EFR_STATUS_FLASH_BUSY] = "flash busy", + [EFR_STATUS_BAD_MIRROR] = "bad mirror", + [EFR_STATUS_BAD_LED] = "bad LED", + [EFR_STATUS_BAD_PARAMETER] = "bad parameter", + [EFR_STATUS_BAD_PARAMETER + 1] = "incomplete" +}; + +static int +efw_transaction(struct snd_efw *efw, unsigned int category, + unsigned int command, + const __be32 *params, unsigned int param_bytes, + const __be32 *resp, unsigned int resp_bytes) +{ + struct snd_efw_transaction *header; + __be32 *buf; + u32 seqnum; + unsigned int buf_bytes, cmd_bytes; + int err; + + /* calculate buffer size*/ + buf_bytes = sizeof(struct snd_efw_transaction) + + max(param_bytes, resp_bytes); + + /* keep buffer */ + buf = kzalloc(buf_bytes, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* to keep consistency of sequence number */ + spin_lock(&efw->lock); + if ((efw->seqnum < KERNEL_SEQNUM_MIN) || + (efw->seqnum >= KERNEL_SEQNUM_MAX - 2)) + efw->seqnum = KERNEL_SEQNUM_MIN; + else + efw->seqnum += 2; + seqnum = efw->seqnum; + spin_unlock(&efw->lock); + + /* fill transaction header fields */ + cmd_bytes = sizeof(struct snd_efw_transaction) + param_bytes; + header = (struct snd_efw_transaction *)buf; + header->length = cpu_to_be32(cmd_bytes / sizeof(__be32)); + header->version = cpu_to_be32(1); + header->seqnum = cpu_to_be32(seqnum); + header->category = cpu_to_be32(category); + header->command = cpu_to_be32(command); + header->status = 0; + + /* fill transaction command parameters */ + memcpy(header->params, params, param_bytes); + + err = snd_efw_transaction_run(efw->unit, buf, cmd_bytes, + buf, buf_bytes); + if (err < 0) + goto end; + + /* check transaction header fields */ + if ((be32_to_cpu(header->version) < 1) || + (be32_to_cpu(header->category) != category) || + (be32_to_cpu(header->command) != command) || + (be32_to_cpu(header->status) != EFR_STATUS_OK)) { + dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n", + be32_to_cpu(header->category), + be32_to_cpu(header->command), + efr_status_names[be32_to_cpu(header->status)]); + err = -EIO; + goto end; + } + + if (resp == NULL) + goto end; + + /* fill transaction response parameters */ + memset((void *)resp, 0, resp_bytes); + resp_bytes = min_t(unsigned int, resp_bytes, + be32_to_cpu(header->length) * sizeof(__be32) - + sizeof(struct snd_efw_transaction)); + memcpy((void *)resp, &buf[6], resp_bytes); +end: + kfree(buf); + return err; +} + +/* + * The address in host system for transaction response is changable when the + * device supports. struct hwinfo.flags includes its flag. The default is + * MEMORY_SPACE_EFW_RESPONSE. + */ +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low) +{ + __be32 addr[2]; + + addr[0] = cpu_to_be32(addr_high); + addr[1] = cpu_to_be32(addr_low); + + if (!efw->resp_addr_changable) + return -ENOSYS; + + return efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWINFO_SET_RESP_ADDR, + addr, sizeof(addr), NULL, 0); +} + +/* + * This is for timestamp processing. In Windows mode, all 32bit fields of second + * CIP header in AMDTP transmit packet is used for 'presentation timestamp'. In + * 'no data' packet the value of this field is 0x90ffffff. + */ +int snd_efw_command_set_tx_mode(struct snd_efw *efw, + enum snd_efw_transport_mode mode) +{ + __be32 param = cpu_to_be32(mode); + return efw_transaction(efw, EFC_CAT_TRANSPORT, + EFC_CMD_TRANSPORT_SET_TX_MODE, + ¶m, sizeof(param), NULL, 0); +} + +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_CAPS, + NULL, 0, (__be32 *)hwinfo, sizeof(*hwinfo)); + if (err < 0) + goto end; + + be32_to_cpus(&hwinfo->flags); + be32_to_cpus(&hwinfo->guid_hi); + be32_to_cpus(&hwinfo->guid_lo); + be32_to_cpus(&hwinfo->type); + be32_to_cpus(&hwinfo->version); + be32_to_cpus(&hwinfo->supported_clocks); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels); + be32_to_cpus(&hwinfo->phys_out); + be32_to_cpus(&hwinfo->phys_in); + be32_to_cpus(&hwinfo->phys_out_grp_count); + be32_to_cpus(&hwinfo->phys_in_grp_count); + be32_to_cpus(&hwinfo->midi_out_ports); + be32_to_cpus(&hwinfo->midi_in_ports); + be32_to_cpus(&hwinfo->max_sample_rate); + be32_to_cpus(&hwinfo->min_sample_rate); + be32_to_cpus(&hwinfo->dsp_version); + be32_to_cpus(&hwinfo->arm_version); + be32_to_cpus(&hwinfo->mixer_playback_channels); + be32_to_cpus(&hwinfo->mixer_capture_channels); + be32_to_cpus(&hwinfo->fpga_version); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_4x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_4x); + + /* ensure terminated */ + hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; + hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; +end: + return err; +} + +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len) +{ + __be32 *buf = (__be32 *)meters; + unsigned int i; + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_POLLED, + NULL, 0, (__be32 *)meters, len); + if (err >= 0) + for (i = 0; i < len / sizeof(u32); i++) + be32_to_cpus(&buf[i]); + + return err; +} + +static int +command_get_clock(struct snd_efw *efw, struct efc_clock *clock) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_GET_CLOCK, + NULL, 0, + (__be32 *)clock, sizeof(struct efc_clock)); + if (err >= 0) { + be32_to_cpus(&clock->source); + be32_to_cpus(&clock->sampling_rate); + be32_to_cpus(&clock->index); + } + + return err; +} + +/* give UINT_MAX if set nothing */ +static int +command_set_clock(struct snd_efw *efw, + unsigned int source, unsigned int rate) +{ + struct efc_clock clock = {0}; + int err; + + /* check arguments */ + if ((source == UINT_MAX) && (rate == UINT_MAX)) { + err = -EINVAL; + goto end; + } + + /* get current status */ + err = command_get_clock(efw, &clock); + if (err < 0) + goto end; + + /* no need */ + if ((clock.source == source) && (clock.sampling_rate == rate)) + goto end; + + /* set params */ + if ((source != UINT_MAX) && (clock.source != source)) + clock.source = source; + if ((rate != UINT_MAX) && (clock.sampling_rate != rate)) + clock.sampling_rate = rate; + clock.index = 0; + + cpu_to_be32s(&clock.source); + cpu_to_be32s(&clock.sampling_rate); + cpu_to_be32s(&clock.index); + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_SET_CLOCK, + (__be32 *)&clock, sizeof(struct efc_clock), + NULL, 0); + if (err < 0) + goto end; + + /* + * With firmware version 5.8, just after changing clock state, these + * parameters are not immediately retrieved by get command. In my + * trial, there needs to be 100msec to get changed parameters. + */ + msleep(150); +end: + return err; +} + +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *source = clock.source; + + return err; +} + +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *rate = clock.sampling_rate; + + return err; +} + +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate) +{ + return command_set_clock(efw, UINT_MAX, rate); +} + diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c new file mode 100644 index 000000000000..33df8655fe81 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_hwdep.c @@ -0,0 +1,298 @@ +/* + * fireworks_hwdep.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * 4.transmit command of EFW transaction + * 5.receive response of EFW transaction + * + */ + +#include "fireworks.h" + +static long +hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained, + loff_t *offset) +{ + unsigned int length, till_end, type; + struct snd_efw_transaction *t; + long count = 0; + + if (remained < sizeof(type) + sizeof(struct snd_efw_transaction)) + return -ENOSPC; + + /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */ + type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE; + if (copy_to_user(buf, &type, sizeof(type))) + return -EFAULT; + remained -= sizeof(type); + buf += sizeof(type); + + /* write into buffer as many responses as possible */ + while (efw->resp_queues > 0) { + t = (struct snd_efw_transaction *)(efw->pull_ptr); + length = be32_to_cpu(t->length) * sizeof(__be32); + + /* confirm enough space for this response */ + if (remained < length) + break; + + /* copy from ring buffer to user buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(efw->pull_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + if (copy_to_user(buf, efw->pull_ptr, till_end)) + return -EFAULT; + + efw->pull_ptr += till_end; + if (efw->pull_ptr >= efw->resp_buf + + snd_efw_resp_buf_size) + efw->pull_ptr -= snd_efw_resp_buf_size; + + length -= till_end; + buf += till_end; + count += till_end; + remained -= till_end; + } + + efw->resp_queues--; + } + + return count; +} + +static long +hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count, + loff_t *offset) +{ + union snd_firewire_event event; + + memset(&event, 0, sizeof(event)); + + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (efw->dev_lock_count > 0); + efw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + DEFINE_WAIT(wait); + + spin_lock_irq(&efw->lock); + + while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) { + prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&efw->lock); + schedule(); + finish_wait(&efw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&efw->lock); + } + + if (efw->dev_lock_changed) + count = hwdep_read_locked(efw, buf, count, offset); + else if (efw->resp_queues > 0) + count = hwdep_read_resp_buf(efw, buf, count, offset); + + spin_unlock_irq(&efw->lock); + + return count; +} + +static long +hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + u32 seqnum; + u8 *buf; + + if (count < sizeof(struct snd_efw_transaction) || + SND_EFW_RESPONSE_MAXIMUM_BYTES < count) + return -EINVAL; + + buf = memdup_user(data, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* check seqnum is not for kernel-land */ + seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) { + count = -EINVAL; + goto end; + } + + if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0) + count = -EIO; +end: + kfree(buf); + return count; +} + +static unsigned int +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_efw *efw = hwdep->private_data; + unsigned int events; + + poll_wait(file, &efw->hwdep_wait, wait); + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_changed || (efw->resp_queues > 0)) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&efw->lock); + + return events | POLLOUT; +} + +static int +hwdep_get_info(struct snd_efw *efw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(efw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int +hwdep_lock(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == 0) { + efw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == -1) { + efw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_efw *efw = hwdep->private_data; + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_count == -1) + efw->dev_lock_count = 0; + spin_unlock_irq(&efw->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_efw *efw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(efw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(efw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(efw); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .write = hwdep_write, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, +}; + +int snd_efw_create_hwdep_device(struct snd_efw *efw) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "Fireworks"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; + hwdep->ops = hwdep_ops; + hwdep->private_data = efw; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c new file mode 100644 index 000000000000..cf9c65260439 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -0,0 +1,168 @@ +/* + * fireworks_midi.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "fireworks.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end; + + atomic_inc(&efw->capture_substreams); + err = snd_efw_stream_start_duplex(efw, 0); + if (err < 0) + snd_efw_stream_lock_release(efw); + +end: + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end; + + atomic_inc(&efw->playback_substreams); + err = snd_efw_stream_start_duplex(efw, 0); + if (err < 0) + snd_efw_stream_lock_release(efw); +end: + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + atomic_dec(&efw->capture_substreams); + snd_efw_stream_stop_duplex(efw); + + snd_efw_stream_lock_release(efw); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + atomic_dec(&efw->playback_substreams); + snd_efw_stream_stop_duplex(efw); + + snd_efw_stream_lock_release(efw); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&efw->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&efw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&efw->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&efw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_efw *efw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", efw->card->shortname, subs->number + 1); + } +} + +int snd_efw_create_midi_devices(struct snd_efw *efw) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(efw->card, efw->card->driver, 0, + efw->midi_out_ports, efw->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", efw->card->shortname); + rmidi->private_data = efw; + + if (efw->midi_in_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(efw, str); + } + + if (efw->midi_out_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(efw, str); + } + + if ((efw->midi_out_ports > 0) && (efw->midi_in_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c new file mode 100644 index 000000000000..8a34753de210 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -0,0 +1,403 @@ +/* + * fireworks_pcm.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "./fireworks.h" + +/* + * NOTE: + * Fireworks changes its AMDTP channels for PCM data according to its sampling + * rate. There are three modes. Here _XX is either _rx or _tx. + * 0: 32.0- 48.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels applied + * 1: 88.2- 96.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_2x applied + * 2: 176.4-192.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_4x applied + * + * The number of PCM channels for analog input and output are always fixed but + * the number of PCM channels for digital input and output are differed. + * + * Additionally, according to "AudioFire Owner's Manual Version 2.2", in some + * model, the number of PCM channels for digital input has more restriction + * depending on which digital interface is selected. + * - S/PDIF coaxial and optical : use input 1-2 + * - ADAT optical at 32.0-48.0 kHz : use input 1-8 + * - ADAT optical at 88.2-96.0 kHz : use input 1-4 (S/MUX format) + * + * The data in AMDTP channels for blank PCM channels are zero. + */ +static const unsigned int freq_table[] = { + /* multiplier mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* multiplier mode 1 */ + [3] = 88200, + [4] = 96000, + /* multiplier mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +static inline unsigned int +get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + if (freq_table[i] == sampling_rate) { + *mode = get_multiplier_mode_with_index(i); + return 0; + } + } + + return -EINVAL; +} + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, freq_table[i]); + t.max = max(t.max, freq_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, freq_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels(struct snd_pcm_hardware *hw, unsigned int *pcm_channels) +{ + unsigned int i, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (pcm_channels[mode] == 0) + continue; + + hw->channels_min = min(hw->channels_min, pcm_channels[mode]); + hw->channels_max = max(hw->channels_max, pcm_channels[mode]); + } +} + +static void +limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int +pcm_init_hw_params(struct snd_efw *efw, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + unsigned int *pcm_channels; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + s = &efw->tx_stream; + pcm_channels = efw->pcm_capture_channels; + } else { + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + s = &efw->rx_stream; + pcm_channels = efw->pcm_playback_channels; + } + + /* limit rates */ + runtime->hw.rates = efw->supported_sampling_rate, + snd_pcm_limit_hw_rates(runtime); + + limit_channels(&runtime->hw, pcm_channels); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_stream_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + unsigned int sampling_rate; + enum snd_efw_clock_source clock_source; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end; + + err = pcm_init_hw_params(efw, substream); + if (err < 0) + goto err_locked; + + err = snd_efw_command_get_clock_source(efw, &clock_source); + if (err < 0) + goto err_locked; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) || + amdtp_stream_pcm_running(&efw->tx_stream) || + amdtp_stream_pcm_running(&efw->rx_stream)) { + err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_efw_stream_lock_release(efw); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + snd_efw_stream_lock_release(efw); + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&efw->capture_substreams); + amdtp_stream_set_pcm_format(&efw->tx_stream, params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&efw->playback_substreams); + amdtp_stream_set_pcm_format(&efw->rx_stream, params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&efw->capture_substreams); + + snd_efw_stream_stop_duplex(efw); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&efw->playback_substreams); + + snd_efw_stream_stop_duplex(efw); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_efw_stream_start_duplex(efw, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->tx_stream); + + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_efw_stream_start_duplex(efw, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->rx_stream); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&efw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&efw->rx_stream); +} + +static const struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_efw_create_pcm_devices(struct snd_efw *efw) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(efw->card, efw->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = efw; + snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c new file mode 100644 index 000000000000..f29d4aaf56a1 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_proc.c @@ -0,0 +1,232 @@ +/* + * fireworks_proc.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./fireworks.h" + +static inline const char* +get_phys_name(struct snd_efw_phys_grp *grp, bool input) +{ + const char *const ch_type[] = { + "Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT", "Mirroring", + "Headphones", "I2S", "Guitar", "Pirzo Guitar", "Guitar String", + }; + + if (grp->type < ARRAY_SIZE(ch_type)) + return ch_type[grp->type]; + else if (input) + return "Input"; + else + return "Output"; +} + +static void +proc_read_hwinfo(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned short i; + struct snd_efw_hwinfo *hwinfo; + + hwinfo = kmalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return; + + if (snd_efw_command_get_hwinfo(efw, hwinfo) < 0) + goto end; + + snd_iprintf(buffer, "guid_hi: 0x%X\n", hwinfo->guid_hi); + snd_iprintf(buffer, "guid_lo: 0x%X\n", hwinfo->guid_lo); + snd_iprintf(buffer, "type: 0x%X\n", hwinfo->type); + snd_iprintf(buffer, "version: 0x%X\n", hwinfo->version); + snd_iprintf(buffer, "vendor_name: %s\n", hwinfo->vendor_name); + snd_iprintf(buffer, "model_name: %s\n", hwinfo->model_name); + + snd_iprintf(buffer, "dsp_version: 0x%X\n", hwinfo->dsp_version); + snd_iprintf(buffer, "arm_version: 0x%X\n", hwinfo->arm_version); + snd_iprintf(buffer, "fpga_version: 0x%X\n", hwinfo->fpga_version); + + snd_iprintf(buffer, "flags: 0x%X\n", hwinfo->flags); + + snd_iprintf(buffer, "max_sample_rate: 0x%X\n", hwinfo->max_sample_rate); + snd_iprintf(buffer, "min_sample_rate: 0x%X\n", hwinfo->min_sample_rate); + snd_iprintf(buffer, "supported_clock: 0x%X\n", + hwinfo->supported_clocks); + + snd_iprintf(buffer, "phys out: 0x%X\n", hwinfo->phys_out); + snd_iprintf(buffer, "phys in: 0x%X\n", hwinfo->phys_in); + + snd_iprintf(buffer, "phys in grps: 0x%X\n", + hwinfo->phys_in_grp_count); + for (i = 0; i < hwinfo->phys_in_grp_count; i++) { + snd_iprintf(buffer, + "phys in grp[0x%d]: type 0x%d, count 0x%d\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "phys out grps: 0x%X\n", + hwinfo->phys_out_grp_count); + for (i = 0; i < hwinfo->phys_out_grp_count; i++) { + snd_iprintf(buffer, + "phys out grps[0x%d]: type 0x%d, count 0x%d\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "amdtp rx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels); + snd_iprintf(buffer, "amdtp tx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels); + snd_iprintf(buffer, "amdtp rx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp tx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp rx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_4x); + snd_iprintf(buffer, "amdtp tx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_4x); + + snd_iprintf(buffer, "midi out ports: 0x%X\n", hwinfo->midi_out_ports); + snd_iprintf(buffer, "midi in ports: 0x%X\n", hwinfo->midi_in_ports); + + snd_iprintf(buffer, "mixer playback channels: 0x%X\n", + hwinfo->mixer_playback_channels); + snd_iprintf(buffer, "mixer capture channels: 0x%X\n", + hwinfo->mixer_capture_channels); +end: + kfree(hwinfo); +} + +static void +proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + enum snd_efw_clock_source clock_source; + unsigned int sampling_rate; + + if (snd_efw_command_get_clock_source(efw, &clock_source) < 0) + return; + + if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0) + return; + + snd_iprintf(buffer, "Clock Source: %d\n", clock_source); + snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate); +} + +/* + * NOTE: + * dB = 20 * log10(linear / 0x01000000) + * -144.0 dB when linear is 0 + */ +static void +proc_read_phys_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + struct snd_efw_phys_meters *meters; + unsigned int g, c, m, max, size; + const char *name; + u32 *linear; + int err; + + size = sizeof(struct snd_efw_phys_meters) + + (efw->phys_in + efw->phys_out) * sizeof(u32); + meters = kzalloc(size, GFP_KERNEL); + if (meters == NULL) + return; + + err = snd_efw_command_get_phys_meters(efw, meters, size); + if (err < 0) + goto end; + + snd_iprintf(buffer, "Physical Meters:\n"); + + m = 0; + max = min(efw->phys_out, meters->out_meters); + linear = meters->values; + snd_iprintf(buffer, " %d Outputs:\n", max); + for (g = 0; g < efw->phys_out_grp_count; g++) { + name = get_phys_name(&efw->phys_out_grps[g], false); + for (c = 0; c < efw->phys_out_grps[g].count; c++) { + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } + } + + m = 0; + max = min(efw->phys_in, meters->in_meters); + linear = meters->values + meters->out_meters; + snd_iprintf(buffer, " %d Inputs:\n", max); + for (g = 0; g < efw->phys_in_grp_count; g++) { + name = get_phys_name(&efw->phys_in_grps[g], true); + for (c = 0; c < efw->phys_in_grps[g].count; c++) + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } +end: + kfree(meters); +} + +static void +proc_read_queues_state(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned int consumed; + + if (efw->pull_ptr > efw->push_ptr) + consumed = snd_efw_resp_buf_size - + (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr); + + snd_iprintf(buffer, "%d %d/%d\n", + efw->resp_queues, consumed, snd_efw_resp_buf_size); +} + +static void +add_node(struct snd_efw *efw, struct snd_info_entry *root, const char *name, + void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(efw->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, efw, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_efw_proc_init(struct snd_efw *efw) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(efw->card, "firewire", + efw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(efw, root, "clock", proc_read_clock); + add_node(efw, root, "firmware", proc_read_hwinfo); + add_node(efw, root, "meters", proc_read_phys_meters); + add_node(efw, root, "queues", proc_read_queues_state); +} diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c new file mode 100644 index 000000000000..b985fc5ebdc6 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -0,0 +1,372 @@ +/* + * fireworks_stream.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "./fireworks.h" + +#define CALLBACK_TIMEOUT 100 + +static int +init_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &efw->tx_stream) { + conn = &efw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &efw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, efw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); + } +end: + return err; +} + +static void +stop_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream); + + if (stream == &efw->tx_stream) + cmp_connection_break(&efw->out_conn); + else + cmp_connection_break(&efw->in_conn); +} + +static int +start_stream(struct snd_efw *efw, struct amdtp_stream *stream, + unsigned int sampling_rate) +{ + struct cmp_connection *conn; + unsigned int mode, pcm_channels, midi_ports; + int err; + + err = snd_efw_get_multiplier_mode(sampling_rate, &mode); + if (err < 0) + goto end; + if (stream == &efw->tx_stream) { + conn = &efw->out_conn; + pcm_channels = efw->pcm_capture_channels[mode]; + midi_ports = efw->midi_out_ports; + } else { + conn = &efw->in_conn; + pcm_channels = efw->pcm_playback_channels[mode]; + midi_ports = efw->midi_in_ports; + } + + amdtp_stream_set_parameters(stream, sampling_rate, + pcm_channels, midi_ports); + + /* establish connection via CMP */ + err = cmp_connection_establish(conn, + amdtp_stream_get_max_payload(stream)); + if (err < 0) + goto end; + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); + if (err < 0) { + stop_stream(efw, stream); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + stop_stream(efw, stream); + err = -ETIMEDOUT; + } +end: + return err; +} + +static void +destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + stop_stream(efw, stream); + + amdtp_stream_destroy(stream); + + if (stream == &efw->tx_stream) + cmp_connection_destroy(&efw->out_conn); + else + cmp_connection_destroy(&efw->in_conn); +} + +static int +get_sync_mode(struct snd_efw *efw, enum cip_flags *sync_mode) +{ + enum snd_efw_clock_source clock_source; + int err; + + err = snd_efw_command_get_clock_source(efw, &clock_source); + if (err < 0) + return err; + + if (clock_source == SND_EFW_CLOCK_SOURCE_SYTMATCH) + return -ENOSYS; + + *sync_mode = CIP_SYNC_TO_DEVICE; + return 0; +} + +static int +check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &efw->tx_stream) + conn = &efw->out_conn; + else + conn = &efw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&efw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +int snd_efw_stream_init_duplex(struct snd_efw *efw) +{ + int err; + + err = init_stream(efw, &efw->tx_stream); + if (err < 0) + goto end; + /* Fireworks transmits NODATA packets with TAG0. */ + efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; + /* Fireworks has its own meaning for dbc. */ + efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; + /* Fireworks reset dbc at bus reset. */ + efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK; + /* AudioFire9 always reports wrong dbs. */ + if (efw->is_af9) + efw->tx_stream.flags |= CIP_WRONG_DBS; + /* Firmware version 5.5 reports fixed interval for dbc. */ + if (efw->firmware_version == 0x5050000) + efw->tx_stream.tx_dbc_interval = 8; + + err = init_stream(efw, &efw->rx_stream); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + goto end; + } + /* + * Fireworks ignores MIDI messages in more than first 8 data + * blocks of an received AMDTP packet. + */ + efw->rx_stream.rx_blocks_for_midi = 8; + + /* set IEC61883 compliant mode (actually not fully compliant...) */ + err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + destroy_stream(efw, &efw->rx_stream); + } +end: + return err; +} + +int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate) +{ + struct amdtp_stream *master, *slave; + atomic_t *slave_substreams; + enum cip_flags sync_mode; + unsigned int curr_rate; + int err = 0; + + mutex_lock(&efw->mutex); + + /* Need no substreams */ + if ((atomic_read(&efw->playback_substreams) == 0) && + (atomic_read(&efw->capture_substreams) == 0)) + goto end; + + err = get_sync_mode(efw, &sync_mode); + if (err < 0) + goto end; + if (sync_mode == CIP_SYNC_TO_DEVICE) { + master = &efw->tx_stream; + slave = &efw->rx_stream; + slave_substreams = &efw->playback_substreams; + } else { + master = &efw->rx_stream; + slave = &efw->tx_stream; + slave_substreams = &efw->capture_substreams; + } + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(efw, master); + if (err < 0) + goto end; + + /* packet queueing error */ + if (amdtp_streaming_error(slave)) + stop_stream(efw, slave); + if (amdtp_streaming_error(master)) + stop_stream(efw, master); + + /* stop streams if rate is different */ + err = snd_efw_command_get_sampling_rate(efw, &curr_rate); + if (err < 0) + goto end; + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + stop_stream(efw, slave); + stop_stream(efw, master); + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + amdtp_stream_set_sync(sync_mode, master, slave); + efw->master = master; + + err = snd_efw_command_set_sampling_rate(efw, rate); + if (err < 0) + goto end; + + err = start_stream(efw, master, rate); + if (err < 0) { + dev_err(&efw->unit->device, + "fail to start AMDTP master stream:%d\n", err); + goto end; + } + } + + /* start slave if needed */ + if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) { + err = start_stream(efw, slave, rate); + if (err < 0) { + dev_err(&efw->unit->device, + "fail to start AMDTP slave stream:%d\n", err); + stop_stream(efw, master); + } + } +end: + mutex_unlock(&efw->mutex); + return err; +} + +void snd_efw_stream_stop_duplex(struct snd_efw *efw) +{ + struct amdtp_stream *master, *slave; + atomic_t *master_substreams, *slave_substreams; + + if (efw->master == &efw->rx_stream) { + slave = &efw->tx_stream; + master = &efw->rx_stream; + slave_substreams = &efw->capture_substreams; + master_substreams = &efw->playback_substreams; + } else { + slave = &efw->rx_stream; + master = &efw->tx_stream; + slave_substreams = &efw->playback_substreams; + master_substreams = &efw->capture_substreams; + } + + mutex_lock(&efw->mutex); + + if (atomic_read(slave_substreams) == 0) { + stop_stream(efw, slave); + + if (atomic_read(master_substreams) == 0) + stop_stream(efw, master); + } + + mutex_unlock(&efw->mutex); +} + +void snd_efw_stream_update_duplex(struct snd_efw *efw) +{ + if ((cmp_connection_update(&efw->out_conn) < 0) || + (cmp_connection_update(&efw->in_conn) < 0)) { + mutex_lock(&efw->mutex); + stop_stream(efw, &efw->rx_stream); + stop_stream(efw, &efw->tx_stream); + mutex_unlock(&efw->mutex); + } else { + amdtp_stream_update(&efw->rx_stream); + amdtp_stream_update(&efw->tx_stream); + } +} + +void snd_efw_stream_destroy_duplex(struct snd_efw *efw) +{ + mutex_lock(&efw->mutex); + + destroy_stream(efw, &efw->rx_stream); + destroy_stream(efw, &efw->tx_stream); + + mutex_unlock(&efw->mutex); +} + +void snd_efw_stream_lock_changed(struct snd_efw *efw) +{ + efw->dev_lock_changed = true; + wake_up(&efw->hwdep_wait); +} + +int snd_efw_stream_lock_try(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + /* user land lock this */ + if (efw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (efw->dev_lock_count++ == 0) + snd_efw_stream_lock_changed(efw); + err = 0; +end: + spin_unlock_irq(&efw->lock); + return err; +} + +void snd_efw_stream_lock_release(struct snd_efw *efw) +{ + spin_lock_irq(&efw->lock); + + if (WARN_ON(efw->dev_lock_count <= 0)) + goto end; + if (--efw->dev_lock_count == 0) + snd_efw_stream_lock_changed(efw); +end: + spin_unlock_irq(&efw->lock); +} diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c new file mode 100644 index 000000000000..255dabc6fc33 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_transaction.c @@ -0,0 +1,326 @@ +/* + * fireworks_transaction.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * Fireworks have its own transaction. The transaction can be delivered by AV/C + * Vendor Specific command frame or usual asynchronous transaction. At least, + * Windows driver and firmware version 5.5 or later don't use AV/C command. + * + * Transaction substance: + * At first, 6 data exist. Following to the data, parameters for each command + * exist. All of the parameters are 32 bit alighed to big endian. + * data[0]: Length of transaction substance + * data[1]: Transaction version + * data[2]: Sequence number. This is incremented by the device + * data[3]: Transaction category + * data[4]: Transaction command + * data[5]: Return value in response. + * data[6-]: Parameters + * + * Transaction address: + * command: 0xecc000000000 + * response: 0xecc080000000 (default) + * + * I note that the address for response can be changed by command. But this + * module uses the default address. + */ +#include "./fireworks.h" + +#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000ULL +#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000ULL + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define EFC_TIMEOUT_MS 125 + +static DEFINE_SPINLOCK(instances_lock); +static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +static DEFINE_SPINLOCK(transaction_queues_lock); +static LIST_HEAD(transaction_queues); + +enum transaction_queue_state { + STATE_PENDING, + STATE_BUS_RESET, + STATE_COMPLETE +}; + +struct transaction_queue { + struct list_head list; + struct fw_unit *unit; + void *buf; + unsigned int size; + u32 seqnum; + enum transaction_queue_state state; + wait_queue_head_t wait; +}; + +int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size) +{ + return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST, + MEMORY_SPACE_EFW_COMMAND, + (void *)cmd, size, 0); +} + +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size) +{ + struct transaction_queue t; + unsigned int tries; + int ret; + + t.unit = unit; + t.buf = resp; + t.size = resp_size; + t.seqnum = be32_to_cpu(((struct snd_efw_transaction *)cmd)->seqnum) + 1; + t.state = STATE_PENDING; + init_waitqueue_head(&t.wait); + + spin_lock_irq(&transaction_queues_lock); + list_add_tail(&t.list, &transaction_queues); + spin_unlock_irq(&transaction_queues_lock); + + tries = 0; + do { + ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size); + if (ret < 0) + break; + + wait_event_timeout(t.wait, t.state != STATE_PENDING, + msecs_to_jiffies(EFC_TIMEOUT_MS)); + + if (t.state == STATE_COMPLETE) { + ret = t.size; + break; + } else if (t.state == STATE_BUS_RESET) { + msleep(ERROR_DELAY_MS); + } else if (++tries >= ERROR_RETRIES) { + dev_err(&t.unit->device, "EFW transaction timed out\n"); + ret = -EIO; + break; + } + } while (1); + + spin_lock_irq(&transaction_queues_lock); + list_del(&t.list); + spin_unlock_irq(&transaction_queues_lock); + + return ret; +} + +static void +copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode) +{ + size_t capacity, till_end; + struct snd_efw_transaction *t; + + spin_lock_irq(&efw->lock); + + t = (struct snd_efw_transaction *)data; + length = min_t(size_t, t->length * sizeof(t->length), length); + + if (efw->push_ptr < efw->pull_ptr) + capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + capacity = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->pull_ptr); + + /* confirm enough space for this response */ + if (capacity < length) { + *rcode = RCODE_CONFLICT_ERROR; + goto end; + } + + /* copy to ring buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + memcpy(efw->push_ptr, data, till_end); + + efw->push_ptr += till_end; + if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size) + efw->push_ptr -= snd_efw_resp_buf_size; + + length -= till_end; + data += till_end; + } + + /* for hwdep */ + efw->resp_queues++; + wake_up(&efw->hwdep_wait); + + *rcode = RCODE_COMPLETE; +end: + spin_unlock_irq(&efw->lock); +} + +static void +handle_resp_for_user(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode) +{ + struct fw_device *device; + struct snd_efw *efw; + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + efw = instances[i]; + if (efw == NULL) + continue; + device = fw_parent_device(efw->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node id vs. generation */ + if (device->node_id != source) + continue; + + break; + } + if (i == SNDRV_CARDS) + goto end; + + copy_resp_to_buf(efw, data, length, rcode); +end: + spin_unlock_irq(&instances_lock); +} + +static void +handle_resp_for_kernel(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode, u32 seqnum) +{ + struct fw_device *device; + struct transaction_queue *t; + unsigned long flags; + + spin_lock_irqsave(&transaction_queues_lock, flags); + list_for_each_entry(t, &transaction_queues, list) { + device = fw_parent_device(t->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node_id vs. generation */ + if (device->node_id != source) + continue; + + if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) { + t->state = STATE_COMPLETE; + t->size = min_t(unsigned int, length, t->size); + memcpy(t->buf, data, t->size); + wake_up(&t->wait); + *rcode = RCODE_COMPLETE; + } + } + spin_unlock_irqrestore(&transaction_queues_lock, flags); +} + +static void +efw_response(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + int rcode, dummy; + u32 seqnum; + + rcode = RCODE_TYPE_ERROR; + if (length < sizeof(struct snd_efw_transaction)) { + rcode = RCODE_DATA_ERROR; + goto end; + } else if (offset != MEMORY_SPACE_EFW_RESPONSE) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + + seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) { + handle_resp_for_kernel(card, generation, source, + data, length, &rcode, seqnum); + if (snd_efw_resp_buf_debug) + handle_resp_for_user(card, generation, source, + data, length, &dummy); + } else { + handle_resp_for_user(card, generation, source, + data, length, &rcode); + } +end: + fw_send_response(card, request, rcode); +} + +void snd_efw_transaction_add_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != NULL) + continue; + instances[i] = efw; + break; + } + + spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_remove_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != efw) + continue; + instances[i] = NULL; + } + + spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_bus_reset(struct fw_unit *unit) +{ + struct transaction_queue *t; + + spin_lock_irq(&transaction_queues_lock); + list_for_each_entry(t, &transaction_queues, list) { + if ((t->unit == unit) && + (t->state == STATE_PENDING)) { + t->state = STATE_BUS_RESET; + wake_up(&t->wait); + } + } + spin_unlock_irq(&transaction_queues_lock); +} + +static struct fw_address_handler resp_register_handler = { + .length = SND_EFW_RESPONSE_MAXIMUM_BYTES, + .address_callback = efw_response +}; + +int snd_efw_transaction_register(void) +{ + static const struct fw_address_region resp_register_region = { + .start = MEMORY_SPACE_EFW_RESPONSE, + .end = MEMORY_SPACE_EFW_RESPONSE + + SND_EFW_RESPONSE_MAXIMUM_BYTES + }; + return fw_core_add_address_handler(&resp_register_handler, + &resp_register_region); +} + +void snd_efw_transaction_unregister(void) +{ + WARN_ON(!list_empty(&transaction_queues)); + fw_core_remove_address_handler(&resp_register_handler); +} diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 9f7ef219b109..768d40ddfebb 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -51,7 +51,7 @@ struct fwspk { const struct device_info *device_info; struct mutex mutex; struct cmp_connection connection; - struct amdtp_out_stream stream; + struct amdtp_stream stream; bool mute; s16 volume[6]; s16 volume_min; @@ -167,13 +167,7 @@ static int fwspk_open(struct snd_pcm_substream *substream) if (err < 0) return err; - err = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 5000, UINT_MAX); - if (err < 0) - return err; - - err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + err = amdtp_stream_add_pcm_hw_constraints(&fwspk->stream, runtime); if (err < 0) return err; @@ -187,48 +181,12 @@ static int fwspk_close(struct snd_pcm_substream *substream) static void fwspk_stop_stream(struct fwspk *fwspk) { - if (amdtp_out_stream_running(&fwspk->stream)) { - amdtp_out_stream_stop(&fwspk->stream); + if (amdtp_stream_running(&fwspk->stream)) { + amdtp_stream_stop(&fwspk->stream); cmp_connection_break(&fwspk->connection); } } -static int fwspk_set_rate(struct fwspk *fwspk, unsigned int sfc) -{ - u8 *buf; - int err; - - buf = kmalloc(8, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - buf[0] = 0x00; /* AV/C, CONTROL */ - buf[1] = 0xff; /* unit */ - buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ - buf[3] = 0x00; /* plug 0 */ - buf[4] = 0x90; /* format: audio */ - buf[5] = 0x00 | sfc; /* AM824, frequency */ - buf[6] = 0xff; /* SYT (not used) */ - buf[7] = 0xff; - - err = fcp_avc_transaction(fwspk->unit, buf, 8, buf, 8, - BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if (err < 0) - goto error; - if (err < 6 || buf[0] != 0x09 /* ACCEPTED */) { - dev_err(&fwspk->unit->device, "failed to set sample rate\n"); - err = -EIO; - goto error; - } - - err = 0; - -error: - kfree(buf); - - return err; -} - static int fwspk_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { @@ -244,17 +202,20 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream, if (err < 0) goto error; - amdtp_out_stream_set_parameters(&fwspk->stream, - params_rate(hw_params), - params_channels(hw_params), - 0); + amdtp_stream_set_parameters(&fwspk->stream, + params_rate(hw_params), + params_channels(hw_params), + 0); - amdtp_out_stream_set_pcm_format(&fwspk->stream, - params_format(hw_params)); + amdtp_stream_set_pcm_format(&fwspk->stream, + params_format(hw_params)); - err = fwspk_set_rate(fwspk, fwspk->stream.sfc); - if (err < 0) + err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) { + dev_err(&fwspk->unit->device, "failed to set sample rate\n"); goto err_buffer; + } return 0; @@ -282,25 +243,25 @@ static int fwspk_prepare(struct snd_pcm_substream *substream) mutex_lock(&fwspk->mutex); - if (amdtp_out_streaming_error(&fwspk->stream)) + if (amdtp_streaming_error(&fwspk->stream)) fwspk_stop_stream(fwspk); - if (!amdtp_out_stream_running(&fwspk->stream)) { + if (!amdtp_stream_running(&fwspk->stream)) { err = cmp_connection_establish(&fwspk->connection, - amdtp_out_stream_get_max_payload(&fwspk->stream)); + amdtp_stream_get_max_payload(&fwspk->stream)); if (err < 0) goto err_mutex; - err = amdtp_out_stream_start(&fwspk->stream, - fwspk->connection.resources.channel, - fwspk->connection.speed); + err = amdtp_stream_start(&fwspk->stream, + fwspk->connection.resources.channel, + fwspk->connection.speed); if (err < 0) goto err_connection; } mutex_unlock(&fwspk->mutex); - amdtp_out_stream_pcm_prepare(&fwspk->stream); + amdtp_stream_pcm_prepare(&fwspk->stream); return 0; @@ -327,7 +288,7 @@ static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) default: return -EINVAL; } - amdtp_out_stream_pcm_trigger(&fwspk->stream, pcm); + amdtp_stream_pcm_trigger(&fwspk->stream, pcm); return 0; } @@ -335,7 +296,7 @@ static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) { struct fwspk *fwspk = substream->private_data; - return amdtp_out_stream_pcm_pointer(&fwspk->stream); + return amdtp_stream_pcm_pointer(&fwspk->stream); } static int fwspk_create_pcm(struct fwspk *fwspk) @@ -653,7 +614,7 @@ static void fwspk_card_free(struct snd_card *card) { struct fwspk *fwspk = card->private_data; - amdtp_out_stream_destroy(&fwspk->stream); + amdtp_stream_destroy(&fwspk->stream); cmp_connection_destroy(&fwspk->connection); fw_unit_put(fwspk->unit); mutex_destroy(&fwspk->mutex); @@ -679,11 +640,12 @@ static int fwspk_probe(struct fw_unit *unit, fwspk->unit = fw_unit_get(unit); fwspk->device_info = (const struct device_info *)id->driver_data; - err = cmp_connection_init(&fwspk->connection, unit, 0); + err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0); if (err < 0) goto err_unit; - err = amdtp_out_stream_init(&fwspk->stream, unit, CIP_NONBLOCKING); + err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM, + CIP_NONBLOCKING); if (err < 0) goto err_connection; @@ -733,21 +695,21 @@ static void fwspk_bus_reset(struct fw_unit *unit) fcp_bus_reset(fwspk->unit); if (cmp_connection_update(&fwspk->connection) < 0) { - amdtp_out_stream_pcm_abort(&fwspk->stream); + amdtp_stream_pcm_abort(&fwspk->stream); mutex_lock(&fwspk->mutex); fwspk_stop_stream(fwspk); mutex_unlock(&fwspk->mutex); return; } - amdtp_out_stream_update(&fwspk->stream); + amdtp_stream_update(&fwspk->stream); } static void fwspk_remove(struct fw_unit *unit) { struct fwspk *fwspk = dev_get_drvdata(&unit->device); - amdtp_out_stream_pcm_abort(&fwspk->stream); + amdtp_stream_pcm_abort(&fwspk->stream); snd_card_disconnect(fwspk->card); mutex_lock(&fwspk->mutex); diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c index 5abbbe477d16..ad55e5cb8e94 100644 --- a/sound/isa/gus/interwave.c +++ b/sound/isa/gus/interwave.c @@ -442,17 +442,11 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus) for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) { for (i = 0; i < 8; ++i) iwave[i] = snd_gf1_peek(gus, bank_pos + i); -#ifdef CONFIG_SND_DEBUG_ROM - printk(KERN_DEBUG "ROM at 0x%06x = %8phC\n", bank_pos, iwave); -#endif if (strncmp(iwave, "INTRWAVE", 8)) continue; /* first check */ csum = 0; for (i = 0; i < sizeof(struct rom_hdr); i++) csum += snd_gf1_peek(gus, bank_pos + i); -#ifdef CONFIG_SND_DEBUG_ROM - printk(KERN_DEBUG "ROM checksum = 0x%x (computed)\n", csum); -#endif if (csum != 0) continue; /* not valid rom */ gus->gf1.rom_banks++; diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c index 6496822c1808..1ff78ec9f0ac 100644 --- a/sound/isa/sb/sb_mixer.c +++ b/sound/isa/sb/sb_mixer.c @@ -818,12 +818,14 @@ int snd_sbmixer_new(struct snd_sb *chip) return err; break; case SB_HW_DT019X: - if ((err = snd_sbmixer_init(chip, - snd_dt019x_controls, - ARRAY_SIZE(snd_dt019x_controls), - snd_dt019x_init_values, - ARRAY_SIZE(snd_dt019x_init_values), - "DT019X")) < 0) + err = snd_sbmixer_init(chip, + snd_dt019x_controls, + ARRAY_SIZE(snd_dt019x_controls), + snd_dt019x_init_values, + ARRAY_SIZE(snd_dt019x_init_values), + "DT019X"); + if (err < 0) + return err; break; default: strcpy(card->mixername, "???"); diff --git a/sound/mips/au1x00.c b/sound/mips/au1x00.c index d10ef7675268..fbcaa5434fd8 100644 --- a/sound/mips/au1x00.c +++ b/sound/mips/au1x00.c @@ -648,14 +648,14 @@ static int au1000_ac97_probe(struct platform_device *pdev) goto out; err = -EBUSY; - au1000->ac97_res_port = request_mem_region(r->start, - r->end - r->start + 1, pdev->name); + au1000->ac97_res_port = request_mem_region(r->start, resource_size(r), + pdev->name); if (!au1000->ac97_res_port) { snd_printk(KERN_ERR "ALSA AC97: can't grab AC97 port\n"); goto out; } - io = ioremap(r->start, r->end - r->start + 1); + io = ioremap(r->start, resource_size(r)); if (!io) goto out; diff --git a/sound/oss/mpu401.c b/sound/oss/mpu401.c index 25e4609f8339..3bbc3ec5be82 100644 --- a/sound/oss/mpu401.c +++ b/sound/oss/mpu401.c @@ -567,7 +567,6 @@ static int mpu401_out(int dev, unsigned char midi_byte) static int mpu401_command(int dev, mpu_command_rec * cmd) { int i, timeout, ok; - int ret = 0; unsigned long flags; struct mpu_config *devc; @@ -644,7 +643,6 @@ retry: } } } - ret = 0; cmd->data[0] = 0; if (cmd->nr_returns) @@ -666,7 +664,7 @@ retry: } } spin_unlock_irqrestore(&devc->lock,flags); - return ret; + return 0; } static int mpu_cmd(int dev, int cmd, int data) diff --git a/sound/oss/swarm_cs4297a.c b/sound/oss/swarm_cs4297a.c index f851fd0e199c..a33e8ce8085b 100644 --- a/sound/oss/swarm_cs4297a.c +++ b/sound/oss/swarm_cs4297a.c @@ -2625,15 +2625,12 @@ static int __init cs4297a_init(void) u32 pwr, id; mm_segment_t fs; int rval; -#ifndef CONFIG_BCM_CS4297A_CSWARM u64 cfg; int mdio_val; -#endif CS_DBGOUT(CS_INIT | CS_FUNCTION, 2, printk(KERN_INFO "cs4297a: cs4297a_init_module()+ \n")); -#ifndef CONFIG_BCM_CS4297A_CSWARM mdio_val = __raw_readq(KSEG1 + A_MAC_REGISTER(2, R_MAC_MDIO)) & (M_MAC_MDIO_DIR|M_MAC_MDIO_OUT); @@ -2659,7 +2656,6 @@ static int __init cs4297a_init(void) __raw_writeq(mdio_val | M_MAC_GENC, KSEG1+A_MAC_REGISTER(2, R_MAC_MDIO)); /* Give the codec some time to finish resetting (start the bit clock) */ udelay(100); -#endif if (!(s = kzalloc(sizeof(struct cs4297a_state), GFP_KERNEL))) { CS_DBGOUT(CS_ERROR, 1, printk(KERN_ERR diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index 8546711d12f9..70951fd9b354 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -443,7 +443,7 @@ static int snd_bt87x_pcm_open(struct snd_pcm_substream *substream) _error: clear_bit(0, &chip->opened); - smp_mb__after_clear_bit(); + smp_mb__after_atomic(); return err; } @@ -458,7 +458,7 @@ static int snd_bt87x_close(struct snd_pcm_substream *substream) chip->substream = NULL; clear_bit(0, &chip->opened); - smp_mb__after_clear_bit(); + smp_mb__after_atomic(); return 0; } diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c index db18ccabadd6..529f5f4f4c9c 100644 --- a/sound/pci/fm801.c +++ b/sound/pci/fm801.c @@ -23,6 +23,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/interrupt.h> +#include <linux/io.h> #include <linux/pci.h> #include <linux/slab.h> #include <linux/module.h> @@ -34,8 +35,6 @@ #include <sound/opl3.h> #include <sound/initval.h> -#include <asm/io.h> - #ifdef CONFIG_SND_FM801_TEA575X_BOOL #include <media/tea575x.h> #endif @@ -80,7 +79,10 @@ MODULE_PARM_DESC(radio_nr, "Radio device numbers"); * Direct registers */ -#define FM801_REG(chip, reg) (chip->port + FM801_##reg) +#define fm801_writew(chip,reg,value) outw((value), chip->port + FM801_##reg) +#define fm801_readw(chip,reg) inw(chip->port + FM801_##reg) + +#define fm801_writel(chip,reg,value) outl((value), chip->port + FM801_##reg) #define FM801_PCM_VOL 0x00 /* PCM Output Volume */ #define FM801_FM_VOL 0x02 /* FM Output Volume */ @@ -156,21 +158,27 @@ MODULE_PARM_DESC(radio_nr, "Radio device numbers"); #define FM801_GPIO_GS3 (1<<15) #define FM801_GPIO_GS(x) (1<<(12+(x))) -/* - +/** + * struct fm801 - describes FM801 chip + * @port: I/O port number + * @multichannel: multichannel support + * @secondary: secondary codec + * @secondary_addr: address of the secondary codec + * @tea575x_tuner: tuner access method & flags + * @ply_ctrl: playback control + * @cap_ctrl: capture control */ - struct fm801 { int irq; - unsigned long port; /* I/O port number */ - unsigned int multichannel: 1, /* multichannel support */ - secondary: 1; /* secondary codec */ - unsigned char secondary_addr; /* address of the secondary codec */ - unsigned int tea575x_tuner; /* tuner access method & flags */ + unsigned long port; + unsigned int multichannel: 1, + secondary: 1; + unsigned char secondary_addr; + unsigned int tea575x_tuner; - unsigned short ply_ctrl; /* playback control */ - unsigned short cap_ctrl; /* capture control */ + unsigned short ply_ctrl; + unsigned short cap_ctrl; unsigned long ply_buffer; unsigned int ply_buf; @@ -222,6 +230,30 @@ MODULE_DEVICE_TABLE(pci, snd_fm801_ids); * common I/O routines */ +static bool fm801_ac97_is_ready(struct fm801 *chip, unsigned int iterations) +{ + unsigned int idx; + + for (idx = 0; idx < iterations; idx++) { + if (!(fm801_readw(chip, AC97_CMD) & FM801_AC97_BUSY)) + return true; + udelay(10); + } + return false; +} + +static bool fm801_ac97_is_valid(struct fm801 *chip, unsigned int iterations) +{ + unsigned int idx; + + for (idx = 0; idx < iterations; idx++) { + if (fm801_readw(chip, AC97_CMD) & FM801_AC97_VALID) + return true; + udelay(10); + } + return false; +} + static int snd_fm801_update_bits(struct fm801 *chip, unsigned short reg, unsigned short mask, unsigned short value) { @@ -244,73 +276,54 @@ static void snd_fm801_codec_write(struct snd_ac97 *ac97, unsigned short val) { struct fm801 *chip = ac97->private_data; - int idx; /* * Wait until the codec interface is not ready.. */ - for (idx = 0; idx < 100; idx++) { - if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY)) - goto ok1; - udelay(10); + if (!fm801_ac97_is_ready(chip, 100)) { + dev_err(chip->card->dev, "AC'97 interface is busy (1)\n"); + return; } - dev_err(chip->card->dev, "AC'97 interface is busy (1)\n"); - return; - ok1: /* write data and address */ - outw(val, FM801_REG(chip, AC97_DATA)); - outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT), FM801_REG(chip, AC97_CMD)); + fm801_writew(chip, AC97_DATA, val); + fm801_writew(chip, AC97_CMD, reg | (ac97->addr << FM801_AC97_ADDR_SHIFT)); /* * Wait until the write command is not completed.. - */ - for (idx = 0; idx < 1000; idx++) { - if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY)) - return; - udelay(10); - } - dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", ac97->num); + */ + if (!fm801_ac97_is_ready(chip, 1000)) + dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", + ac97->num); } static unsigned short snd_fm801_codec_read(struct snd_ac97 *ac97, unsigned short reg) { struct fm801 *chip = ac97->private_data; - int idx; /* * Wait until the codec interface is not ready.. */ - for (idx = 0; idx < 100; idx++) { - if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY)) - goto ok1; - udelay(10); + if (!fm801_ac97_is_ready(chip, 100)) { + dev_err(chip->card->dev, "AC'97 interface is busy (1)\n"); + return 0; } - dev_err(chip->card->dev, "AC'97 interface is busy (1)\n"); - return 0; - ok1: /* read command */ - outw(reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ, - FM801_REG(chip, AC97_CMD)); - for (idx = 0; idx < 100; idx++) { - if (!(inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_BUSY)) - goto ok2; - udelay(10); + fm801_writew(chip, AC97_CMD, + reg | (ac97->addr << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ); + if (!fm801_ac97_is_ready(chip, 100)) { + dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", + ac97->num); + return 0; } - dev_err(chip->card->dev, "AC'97 interface #%d is busy (2)\n", ac97->num); - return 0; - ok2: - for (idx = 0; idx < 1000; idx++) { - if (inw(FM801_REG(chip, AC97_CMD)) & FM801_AC97_VALID) - goto ok3; - udelay(10); + if (!fm801_ac97_is_valid(chip, 1000)) { + dev_err(chip->card->dev, + "AC'97 interface #%d is not valid (2)\n", ac97->num); + return 0; } - dev_err(chip->card->dev, "AC'97 interface #%d is not valid (2)\n", ac97->num); - return 0; - ok3: - return inw(FM801_REG(chip, AC97_DATA)); + return fm801_readw(chip, AC97_DATA); } static unsigned int rates[] = { @@ -384,7 +397,7 @@ static int snd_fm801_playback_trigger(struct snd_pcm_substream *substream, snd_BUG(); return -EINVAL; } - outw(chip->ply_ctrl, FM801_REG(chip, PLY_CTRL)); + fm801_writew(chip, PLY_CTRL, chip->ply_ctrl); spin_unlock(&chip->reg_lock); return 0; } @@ -419,7 +432,7 @@ static int snd_fm801_capture_trigger(struct snd_pcm_substream *substream, snd_BUG(); return -EINVAL; } - outw(chip->cap_ctrl, FM801_REG(chip, CAP_CTRL)); + fm801_writew(chip, CAP_CTRL, chip->cap_ctrl); spin_unlock(&chip->reg_lock); return 0; } @@ -457,12 +470,13 @@ static int snd_fm801_playback_prepare(struct snd_pcm_substream *substream) } chip->ply_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT; chip->ply_buf = 0; - outw(chip->ply_ctrl, FM801_REG(chip, PLY_CTRL)); - outw(chip->ply_count - 1, FM801_REG(chip, PLY_COUNT)); + fm801_writew(chip, PLY_CTRL, chip->ply_ctrl); + fm801_writew(chip, PLY_COUNT, chip->ply_count - 1); chip->ply_buffer = runtime->dma_addr; chip->ply_pos = 0; - outl(chip->ply_buffer, FM801_REG(chip, PLY_BUF1)); - outl(chip->ply_buffer + (chip->ply_count % chip->ply_size), FM801_REG(chip, PLY_BUF2)); + fm801_writel(chip, PLY_BUF1, chip->ply_buffer); + fm801_writel(chip, PLY_BUF2, + chip->ply_buffer + (chip->ply_count % chip->ply_size)); spin_unlock_irq(&chip->reg_lock); return 0; } @@ -483,12 +497,13 @@ static int snd_fm801_capture_prepare(struct snd_pcm_substream *substream) chip->cap_ctrl |= FM801_STEREO; chip->cap_ctrl |= snd_fm801_rate_bits(runtime->rate) << FM801_RATE_SHIFT; chip->cap_buf = 0; - outw(chip->cap_ctrl, FM801_REG(chip, CAP_CTRL)); - outw(chip->cap_count - 1, FM801_REG(chip, CAP_COUNT)); + fm801_writew(chip, CAP_CTRL, chip->cap_ctrl); + fm801_writew(chip, CAP_COUNT, chip->cap_count - 1); chip->cap_buffer = runtime->dma_addr; chip->cap_pos = 0; - outl(chip->cap_buffer, FM801_REG(chip, CAP_BUF1)); - outl(chip->cap_buffer + (chip->cap_count % chip->cap_size), FM801_REG(chip, CAP_BUF2)); + fm801_writel(chip, CAP_BUF1, chip->cap_buffer); + fm801_writel(chip, CAP_BUF2, + chip->cap_buffer + (chip->cap_count % chip->cap_size)); spin_unlock_irq(&chip->reg_lock); return 0; } @@ -501,8 +516,8 @@ static snd_pcm_uframes_t snd_fm801_playback_pointer(struct snd_pcm_substream *su if (!(chip->ply_ctrl & FM801_START)) return 0; spin_lock(&chip->reg_lock); - ptr = chip->ply_pos + (chip->ply_count - 1) - inw(FM801_REG(chip, PLY_COUNT)); - if (inw(FM801_REG(chip, IRQ_STATUS)) & FM801_IRQ_PLAYBACK) { + ptr = chip->ply_pos + (chip->ply_count - 1) - fm801_readw(chip, PLY_COUNT); + if (fm801_readw(chip, IRQ_STATUS) & FM801_IRQ_PLAYBACK) { ptr += chip->ply_count; ptr %= chip->ply_size; } @@ -518,8 +533,8 @@ static snd_pcm_uframes_t snd_fm801_capture_pointer(struct snd_pcm_substream *sub if (!(chip->cap_ctrl & FM801_START)) return 0; spin_lock(&chip->reg_lock); - ptr = chip->cap_pos + (chip->cap_count - 1) - inw(FM801_REG(chip, CAP_COUNT)); - if (inw(FM801_REG(chip, IRQ_STATUS)) & FM801_IRQ_CAPTURE) { + ptr = chip->cap_pos + (chip->cap_count - 1) - fm801_readw(chip, CAP_COUNT); + if (fm801_readw(chip, IRQ_STATUS) & FM801_IRQ_CAPTURE) { ptr += chip->cap_count; ptr %= chip->cap_size; } @@ -533,12 +548,12 @@ static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id) unsigned short status; unsigned int tmp; - status = inw(FM801_REG(chip, IRQ_STATUS)); + status = fm801_readw(chip, IRQ_STATUS); status &= FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU|FM801_IRQ_VOLUME; if (! status) return IRQ_NONE; /* ack first */ - outw(status, FM801_REG(chip, IRQ_STATUS)); + fm801_writew(chip, IRQ_STATUS, status); if (chip->pcm && (status & FM801_IRQ_PLAYBACK) && chip->playback_substream) { spin_lock(&chip->reg_lock); chip->ply_buf++; @@ -546,10 +561,10 @@ static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id) chip->ply_pos %= chip->ply_size; tmp = chip->ply_pos + chip->ply_count; tmp %= chip->ply_size; - outl(chip->ply_buffer + tmp, - (chip->ply_buf & 1) ? - FM801_REG(chip, PLY_BUF1) : - FM801_REG(chip, PLY_BUF2)); + if (chip->ply_buf & 1) + fm801_writel(chip, PLY_BUF1, chip->ply_buffer + tmp); + else + fm801_writel(chip, PLY_BUF2, chip->ply_buffer + tmp); spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(chip->playback_substream); } @@ -560,10 +575,10 @@ static irqreturn_t snd_fm801_interrupt(int irq, void *dev_id) chip->cap_pos %= chip->cap_size; tmp = chip->cap_pos + chip->cap_count; tmp %= chip->cap_size; - outl(chip->cap_buffer + tmp, - (chip->cap_buf & 1) ? - FM801_REG(chip, CAP_BUF1) : - FM801_REG(chip, CAP_BUF2)); + if (chip->cap_buf & 1) + fm801_writel(chip, CAP_BUF1, chip->cap_buffer + tmp); + else + fm801_writel(chip, CAP_BUF2, chip->cap_buffer + tmp); spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(chip->capture_substream); } @@ -747,7 +762,7 @@ static struct snd_fm801_tea575x_gpio snd_fm801_tea575x_gpios[] = { static void snd_fm801_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) { struct fm801 *chip = tea->private_data; - unsigned short reg = inw(FM801_REG(chip, GPIO_CTRL)); + unsigned short reg = fm801_readw(chip, GPIO_CTRL); struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip); reg &= ~(FM801_GPIO_GP(gpio.data) | @@ -759,13 +774,13 @@ static void snd_fm801_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) /* WRITE_ENABLE is inverted */ reg |= (pins & TEA575X_WREN) ? 0 : FM801_GPIO_GP(gpio.wren); - outw(reg, FM801_REG(chip, GPIO_CTRL)); + fm801_writew(chip, GPIO_CTRL, reg); } static u8 snd_fm801_tea575x_get_pins(struct snd_tea575x *tea) { struct fm801 *chip = tea->private_data; - unsigned short reg = inw(FM801_REG(chip, GPIO_CTRL)); + unsigned short reg = fm801_readw(chip, GPIO_CTRL); struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip); u8 ret; @@ -780,7 +795,7 @@ static u8 snd_fm801_tea575x_get_pins(struct snd_tea575x *tea) static void snd_fm801_tea575x_set_direction(struct snd_tea575x *tea, bool output) { struct fm801 *chip = tea->private_data; - unsigned short reg = inw(FM801_REG(chip, GPIO_CTRL)); + unsigned short reg = fm801_readw(chip, GPIO_CTRL); struct snd_fm801_tea575x_gpio gpio = *get_tea575x_gpio(chip); /* use GPIO lines and set write enable bit */ @@ -811,7 +826,7 @@ static void snd_fm801_tea575x_set_direction(struct snd_tea575x *tea, bool output FM801_GPIO_GP(gpio.clk)); } - outw(reg, FM801_REG(chip, GPIO_CTRL)); + fm801_writew(chip, GPIO_CTRL, reg); } static struct snd_tea575x_ops snd_fm801_tea_ops = { @@ -962,7 +977,7 @@ static int snd_fm801_get_mux(struct snd_kcontrol *kcontrol, struct fm801 *chip = snd_kcontrol_chip(kcontrol); unsigned short val; - val = inw(FM801_REG(chip, REC_SRC)) & 7; + val = fm801_readw(chip, REC_SRC) & 7; if (val > 4) val = 4; ucontrol->value.enumerated.item[0] = val; @@ -1073,12 +1088,12 @@ static int wait_for_codec(struct fm801 *chip, unsigned int codec_id, { unsigned long timeout = jiffies + waits; - outw(FM801_AC97_READ | (codec_id << FM801_AC97_ADDR_SHIFT) | reg, - FM801_REG(chip, AC97_CMD)); + fm801_writew(chip, AC97_CMD, + reg | (codec_id << FM801_AC97_ADDR_SHIFT) | FM801_AC97_READ); udelay(5); do { - if ((inw(FM801_REG(chip, AC97_CMD)) & (FM801_AC97_VALID|FM801_AC97_BUSY)) - == FM801_AC97_VALID) + if ((fm801_readw(chip, AC97_CMD) & + (FM801_AC97_VALID | FM801_AC97_BUSY)) == FM801_AC97_VALID) return 0; schedule_timeout_uninterruptible(1); } while (time_after(timeout, jiffies)); @@ -1093,10 +1108,10 @@ static int snd_fm801_chip_init(struct fm801 *chip, int resume) goto __ac97_ok; /* codec cold reset + AC'97 warm reset */ - outw((1<<5) | (1<<6), FM801_REG(chip, CODEC_CTRL)); - inw(FM801_REG(chip, CODEC_CTRL)); /* flush posting data */ + fm801_writew(chip, CODEC_CTRL, (1 << 5) | (1 << 6)); + fm801_readw(chip, CODEC_CTRL); /* flush posting data */ udelay(100); - outw(0, FM801_REG(chip, CODEC_CTRL)); + fm801_writew(chip, CODEC_CTRL, 0); if (wait_for_codec(chip, 0, AC97_RESET, msecs_to_jiffies(750)) < 0) if (!resume) { @@ -1117,7 +1132,7 @@ static int snd_fm801_chip_init(struct fm801 *chip, int resume) for (i = 3; i > 0; i--) { if (!wait_for_codec(chip, i, AC97_VENDOR_ID1, msecs_to_jiffies(50))) { - cmdw = inw(FM801_REG(chip, AC97_DATA)); + cmdw = fm801_readw(chip, AC97_DATA); if (cmdw != 0xffff && cmdw != 0) { chip->secondary = 1; chip->secondary_addr = i; @@ -1135,23 +1150,24 @@ static int snd_fm801_chip_init(struct fm801 *chip, int resume) __ac97_ok: /* init volume */ - outw(0x0808, FM801_REG(chip, PCM_VOL)); - outw(0x9f1f, FM801_REG(chip, FM_VOL)); - outw(0x8808, FM801_REG(chip, I2S_VOL)); + fm801_writew(chip, PCM_VOL, 0x0808); + fm801_writew(chip, FM_VOL, 0x9f1f); + fm801_writew(chip, I2S_VOL, 0x8808); /* I2S control - I2S mode */ - outw(0x0003, FM801_REG(chip, I2S_MODE)); + fm801_writew(chip, I2S_MODE, 0x0003); /* interrupt setup */ - cmdw = inw(FM801_REG(chip, IRQ_MASK)); + cmdw = fm801_readw(chip, IRQ_MASK); if (chip->irq < 0) cmdw |= 0x00c3; /* mask everything, no PCM nor MPU */ else cmdw &= ~0x0083; /* unmask MPU, PLAYBACK & CAPTURE */ - outw(cmdw, FM801_REG(chip, IRQ_MASK)); + fm801_writew(chip, IRQ_MASK, cmdw); /* interrupt clear */ - outw(FM801_IRQ_PLAYBACK|FM801_IRQ_CAPTURE|FM801_IRQ_MPU, FM801_REG(chip, IRQ_STATUS)); + fm801_writew(chip, IRQ_STATUS, + FM801_IRQ_PLAYBACK | FM801_IRQ_CAPTURE | FM801_IRQ_MPU); return 0; } @@ -1165,9 +1181,9 @@ static int snd_fm801_free(struct fm801 *chip) goto __end_hw; /* interrupt setup - mask everything */ - cmdw = inw(FM801_REG(chip, IRQ_MASK)); + cmdw = fm801_readw(chip, IRQ_MASK); cmdw |= 0x00c3; - outw(cmdw, FM801_REG(chip, IRQ_MASK)); + fm801_writew(chip, IRQ_MASK, cmdw); __end_hw: #ifdef CONFIG_SND_FM801_TEA575X_BOOL @@ -1339,15 +1355,15 @@ static int snd_card_fm801_probe(struct pci_dev *pci, return err; } if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_FM801, - FM801_REG(chip, MPU401_DATA), + chip->port + FM801_MPU401_DATA, MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK, -1, &chip->rmidi)) < 0) { snd_card_free(card); return err; } - if ((err = snd_opl3_create(card, FM801_REG(chip, OPL3_BANK0), - FM801_REG(chip, OPL3_BANK1), + if ((err = snd_opl3_create(card, chip->port + FM801_OPL3_BANK0, + chip->port + FM801_OPL3_BANK1, OPL3_HW_OPL3_FM801, 1, &opl3)) < 0) { snd_card_free(card); return err; diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index ac17c3fc9388..ebf4c2fb99df 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -20,6 +20,21 @@ config SND_HDA_INTEL To compile this driver as a module, choose M here: the module will be called snd-hda-intel. +config SND_HDA_TEGRA + tristate "NVIDIA Tegra HD Audio" + depends on ARCH_TEGRA + select SND_HDA + help + Say Y here to support the HDA controller present in NVIDIA + Tegra SoCs + + This options enables support for the HD Audio controller + present in some NVIDIA Tegra SoCs, used to communicate audio + to the HDMI output. + + To compile this driver as a module, choose M here: the module + will be called snd-hda-tegra. + if SND_HDA config SND_HDA_DSP_LOADER diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index d0d0c19ddfc2..194f30935e77 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -1,5 +1,6 @@ snd-hda-intel-objs := hda_intel.o snd-hda-controller-objs := hda_controller.o +snd-hda-tegra-objs := hda_tegra.o # for haswell power well snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o @@ -47,3 +48,4 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o # otherwise the codec patches won't be hooked before the PCI probe # when built in kernel obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o +obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index 90d2fda6c8f9..dabe41975a9d 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -839,6 +839,43 @@ void snd_hda_apply_fixup(struct hda_codec *codec, int action) } EXPORT_SYMBOL_GPL(snd_hda_apply_fixup); +static bool pin_config_match(struct hda_codec *codec, + const struct hda_pintbl *pins) +{ + for (; pins->nid; pins++) { + u32 def_conf = snd_hda_codec_get_pincfg(codec, pins->nid); + if (pins->val != def_conf) + return false; + } + return true; +} + +void snd_hda_pick_pin_fixup(struct hda_codec *codec, + const struct snd_hda_pin_quirk *pin_quirk, + const struct hda_fixup *fixlist) +{ + const struct snd_hda_pin_quirk *pq; + + if (codec->fixup_forced) + return; + + for (pq = pin_quirk; pq->subvendor; pq++) { + if ((codec->subsystem_id & 0xffff0000) != (pq->subvendor << 16)) + continue; + if (codec->vendor_id != pq->codec) + continue; + if (pin_config_match(codec, pq->pins)) { + codec->fixup_id = pq->value; +#ifdef CONFIG_SND_DEBUG_VERBOSE + codec->fixup_name = pq->name; +#endif + codec->fixup_list = fixlist; + return; + } + } +} +EXPORT_SYMBOL_GPL(snd_hda_pick_pin_fixup); + void snd_hda_pick_fixup(struct hda_codec *codec, const struct hda_model_fixup *models, const struct snd_pci_quirk *quirk, @@ -852,15 +889,18 @@ void snd_hda_pick_fixup(struct hda_codec *codec, if (codec->modelname && !strcmp(codec->modelname, "nofixup")) { codec->fixup_list = NULL; codec->fixup_id = -1; + codec->fixup_forced = 1; return; } if (codec->modelname && models) { while (models->name) { if (!strcmp(codec->modelname, models->name)) { - id = models->id; - name = models->name; - break; + codec->fixup_id = models->id; + codec->fixup_name = models->name; + codec->fixup_list = fixlist; + codec->fixup_forced = 1; + return; } models++; } @@ -889,6 +929,7 @@ void snd_hda_pick_fixup(struct hda_codec *codec, } } + codec->fixup_forced = 0; codec->fixup_id = id; if (id >= 0) { codec->fixup_list = fixlist; diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index a4233136cb93..5825aa17d8e3 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -402,6 +402,7 @@ struct hda_codec { /* fix-up list */ int fixup_id; + unsigned int fixup_forced:1; /* fixup explicitly set by user */ const struct hda_fixup *fixup_list; const char *fixup_name; diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c index 480bbddbd801..6df04d91c93c 100644 --- a/sound/pci/hda/hda_controller.c +++ b/sound/pci/hda/hda_controller.c @@ -193,7 +193,8 @@ azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream) dsp_unlock(azx_dev); return azx_dev; } - if (!res) + if (!res || + (chip->driver_caps & AZX_DCAPS_REVERSE_ASSIGN)) res = azx_dev; } dsp_unlock(azx_dev); diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 16133881e967..589e47c5aeb3 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -3722,7 +3722,7 @@ static void parse_digital(struct hda_codec *codec) } else { spec->multiout.slave_dig_outs = spec->slave_dig_outs; if (nums >= ARRAY_SIZE(spec->slave_dig_outs) - 1) - break; + break; spec->slave_dig_outs[nums - 1] = dig_nid; } nums++; diff --git a/sound/pci/hda/hda_i915.c b/sound/pci/hda/hda_i915.c index 9d07e4edacdb..8b4940ba33d6 100644 --- a/sound/pci/hda/hda_i915.c +++ b/sound/pci/hda/hda_i915.c @@ -20,24 +20,71 @@ #include <linux/module.h> #include <sound/core.h> #include <drm/i915_powerwell.h> +#include "hda_priv.h" #include "hda_i915.h" -static void (*get_power)(void); -static void (*put_power)(void); +/* Intel HSW/BDW display HDA controller Extended Mode registers. + * EM4 (M value) and EM5 (N Value) are used to convert CDClk (Core Display + * Clock) to 24MHz BCLK: BCLK = CDCLK * M / N + * The values will be lost when the display power well is disabled. + */ +#define ICH6_REG_EM4 0x100c +#define ICH6_REG_EM5 0x1010 + +static int (*get_power)(void); +static int (*put_power)(void); +static int (*get_cdclk)(void); -void hda_display_power(bool enable) +int hda_display_power(bool enable) { if (!get_power || !put_power) - return; + return -ENODEV; pr_debug("HDA display power %s \n", enable ? "Enable" : "Disable"); if (enable) - get_power(); + return get_power(); else - put_power(); + return put_power(); +} + +void haswell_set_bclk(struct azx *chip) +{ + int cdclk_freq; + unsigned int bclk_m, bclk_n; + + if (!get_cdclk) + return; + + cdclk_freq = get_cdclk(); + switch (cdclk_freq) { + case 337500: + bclk_m = 16; + bclk_n = 225; + break; + + case 450000: + default: /* default CDCLK 450MHz */ + bclk_m = 4; + bclk_n = 75; + break; + + case 540000: + bclk_m = 4; + bclk_n = 90; + break; + + case 675000: + bclk_m = 8; + bclk_n = 225; + break; + } + + azx_writew(chip, EM4, bclk_m); + azx_writew(chip, EM5, bclk_n); } + int hda_i915_init(void) { int err = 0; @@ -55,6 +102,10 @@ int hda_i915_init(void) return -ENODEV; } + get_cdclk = symbol_request(i915_get_cdclk_freq); + if (!get_cdclk) /* may have abnormal BCLK and audio playback rate */ + pr_warn("hda-i915: get_cdclk symbol get fail\n"); + pr_debug("HDA driver get symbol successfully from i915 module\n"); return err; @@ -70,6 +121,10 @@ int hda_i915_exit(void) symbol_put(i915_release_power_well); put_power = NULL; } + if (get_cdclk) { + symbol_put(i915_get_cdclk_freq); + get_cdclk = NULL; + } return 0; } diff --git a/sound/pci/hda/hda_i915.h b/sound/pci/hda/hda_i915.h index 5a63da2c53e5..e6072c627583 100644 --- a/sound/pci/hda/hda_i915.h +++ b/sound/pci/hda/hda_i915.h @@ -17,11 +17,13 @@ #define __SOUND_HDA_I915_H #ifdef CONFIG_SND_HDA_I915 -void hda_display_power(bool enable); +int hda_display_power(bool enable); +void haswell_set_bclk(struct azx *chip); int hda_i915_init(void); int hda_i915_exit(void); #else -static inline void hda_display_power(bool enable) {} +static inline int hda_display_power(bool enable) { return 0; } +static inline void haswell_set_bclk(struct azx *chip) { return; } static inline int hda_i915_init(void) { return -ENODEV; diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index b540ad71eb0d..83cd19017cf3 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -62,9 +62,9 @@ #include <linux/vga_switcheroo.h> #include <linux/firmware.h> #include "hda_codec.h" -#include "hda_i915.h" #include "hda_controller.h" #include "hda_priv.h" +#include "hda_i915.h" static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; @@ -227,7 +227,7 @@ enum { /* quirks for Intel PCH */ #define AZX_DCAPS_INTEL_PCH_NOPM \ (AZX_DCAPS_SCH_SNOOP | AZX_DCAPS_BUFSIZE | \ - AZX_DCAPS_COUNT_LPIB_DELAY) + AZX_DCAPS_COUNT_LPIB_DELAY | AZX_DCAPS_REVERSE_ASSIGN) #define AZX_DCAPS_INTEL_PCH \ (AZX_DCAPS_INTEL_PCH_NOPM | AZX_DCAPS_PM_RUNTIME) @@ -237,6 +237,12 @@ enum { AZX_DCAPS_COUNT_LPIB_DELAY | AZX_DCAPS_PM_RUNTIME | \ AZX_DCAPS_I915_POWERWELL) +/* Broadwell HDMI can't use position buffer reliably, force to use LPIB */ +#define AZX_DCAPS_INTEL_BROADWELL \ + (AZX_DCAPS_SCH_SNOOP | AZX_DCAPS_ALIGN_BUFSIZE | \ + AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_PM_RUNTIME | \ + AZX_DCAPS_I915_POWERWELL) + /* quirks for ATI SB / AMD Hudson */ #define AZX_DCAPS_PRESET_ATI_SB \ (AZX_DCAPS_ATI_SNOOP | AZX_DCAPS_NO_TCSEL | \ @@ -282,6 +288,11 @@ static char *driver_short_names[] = { [AZX_DRIVER_GENERIC] = "HD-Audio Generic", }; +struct hda_intel { + struct azx chip; +}; + + #ifdef CONFIG_X86 static void __mark_pages_wc(struct azx *chip, struct snd_dma_buffer *dmab, bool on) { @@ -585,7 +596,7 @@ static int azx_suspend(struct device *dev) struct azx *chip = card->private_data; struct azx_pcm *p; - if (chip->disabled) + if (chip->disabled || chip->init_failed) return 0; snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); @@ -600,6 +611,7 @@ static int azx_suspend(struct device *dev) free_irq(chip->irq, chip); chip->irq = -1; } + if (chip->msi) pci_disable_msi(chip->pci); pci_disable_device(pci); @@ -616,11 +628,13 @@ static int azx_resume(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data; - if (chip->disabled) + if (chip->disabled || chip->init_failed) return 0; - if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { hda_display_power(true); + haswell_set_bclk(chip); + } pci_set_power_state(pci, PCI_D0); pci_restore_state(pci); if (pci_enable_device(pci) < 0) { @@ -651,7 +665,7 @@ static int azx_runtime_suspend(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data; - if (chip->disabled) + if (chip->disabled || chip->init_failed) return 0; if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME)) @@ -666,6 +680,7 @@ static int azx_runtime_suspend(struct device *dev) azx_clear_irq_pending(chip); if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) hda_display_power(false); + return 0; } @@ -677,14 +692,16 @@ static int azx_runtime_resume(struct device *dev) struct hda_codec *codec; int status; - if (chip->disabled) + if (chip->disabled || chip->init_failed) return 0; if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME)) return 0; - if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { hda_display_power(true); + haswell_set_bclk(chip); + } /* Read STATESTS before controller reset */ status = azx_readw(chip, STATESTS); @@ -712,7 +729,7 @@ static int azx_runtime_idle(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data; - if (chip->disabled) + if (chip->disabled || chip->init_failed) return 0; if (!power_save_controller || @@ -877,6 +894,8 @@ static int register_vga_switcheroo(struct azx *chip) static int azx_free(struct azx *chip) { struct pci_dev *pci = chip->pci; + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + int i; if ((chip->driver_caps & AZX_DCAPS_PM_RUNTIME) @@ -924,7 +943,7 @@ static int azx_free(struct azx *chip) hda_display_power(false); hda_i915_exit(); } - kfree(chip); + kfree(hda); return 0; } @@ -1168,6 +1187,7 @@ static int azx_create(struct snd_card *card, struct pci_dev *pci, static struct snd_device_ops ops = { .dev_free = azx_dev_free, }; + struct hda_intel *hda; struct azx *chip; int err; @@ -1177,13 +1197,14 @@ static int azx_create(struct snd_card *card, struct pci_dev *pci, if (err < 0) return err; - chip = kzalloc(sizeof(*chip), GFP_KERNEL); - if (!chip) { - dev_err(card->dev, "Cannot allocate chip\n"); + hda = kzalloc(sizeof(*hda), GFP_KERNEL); + if (!hda) { + dev_err(card->dev, "Cannot allocate hda\n"); pci_disable_device(pci); return -ENOMEM; } + chip = &hda->chip; spin_lock_init(&chip->reg_lock); mutex_init(&chip->open_mutex); chip->card = card; @@ -1369,6 +1390,10 @@ static int azx_first_init(struct azx *chip) /* initialize chip */ azx_init_pci(chip); + + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + haswell_set_bclk(chip); + azx_init_chip(chip, (probe_only[dev] & 2) == 0); /* codec detection */ @@ -1650,8 +1675,13 @@ static int azx_probe_continue(struct azx *chip) "Error request power-well from i915\n"); goto out_free; } + err = hda_display_power(true); + if (err < 0) { + dev_err(chip->card->dev, + "Cannot turn on display power on i915\n"); + goto out_free; + } #endif - hda_display_power(true); } err = azx_first_init(chip); @@ -1724,7 +1754,7 @@ static void azx_remove(struct pci_dev *pci) } /* PCI IDs */ -static DEFINE_PCI_DEVICE_TABLE(azx_ids) = { +static const struct pci_device_id azx_ids[] = { /* CPT */ { PCI_DEVICE(0x8086, 0x1c20), .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM }, @@ -1737,6 +1767,9 @@ static DEFINE_PCI_DEVICE_TABLE(azx_ids) = { /* Lynx Point */ { PCI_DEVICE(0x8086, 0x8c20), .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, + /* 9 Series */ + { PCI_DEVICE(0x8086, 0x8ca0), + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, /* Wellsburg */ { PCI_DEVICE(0x8086, 0x8d20), .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, @@ -1760,7 +1793,7 @@ static DEFINE_PCI_DEVICE_TABLE(azx_ids) = { .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL }, /* Broadwell */ { PCI_DEVICE(0x8086, 0x160c), - .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL }, + .driver_data = AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_BROADWELL }, /* 5 Series/3400 */ { PCI_DEVICE(0x8086, 0x3b56), .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_NOPM }, diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index e51d15529215..4e2d4863daa1 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -407,6 +407,37 @@ struct hda_fixup { } v; }; +struct snd_hda_pin_quirk { + unsigned int codec; /* Codec vendor/device ID */ + unsigned short subvendor; /* PCI subvendor ID */ + const struct hda_pintbl *pins; /* list of matching pins */ +#ifdef CONFIG_SND_DEBUG_VERBOSE + const char *name; +#endif + int value; /* quirk value */ +}; + +#ifdef CONFIG_SND_DEBUG_VERBOSE + +#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \ + { .codec = _codec,\ + .subvendor = _subvendor,\ + .name = _name,\ + .value = _value,\ + .pins = (const struct hda_pintbl[]) { _pins } \ + } +#else + +#define SND_HDA_PIN_QUIRK(_codec, _subvendor, _name, _value, _pins...) \ + { .codec = _codec,\ + .subvendor = _subvendor,\ + .value = _value,\ + .pins = (const struct hda_pintbl[]) { _pins } \ + } + +#endif + + /* fixup types */ enum { HDA_FIXUP_INVALID, @@ -434,6 +465,10 @@ void snd_hda_pick_fixup(struct hda_codec *codec, const struct hda_model_fixup *models, const struct snd_pci_quirk *quirk, const struct hda_fixup *fixlist); +void snd_hda_pick_pin_fixup(struct hda_codec *codec, + const struct snd_hda_pin_quirk *pin_quirk, + const struct hda_fixup *fixlist); + /* * unsolicited event handler diff --git a/sound/pci/hda/hda_priv.h b/sound/pci/hda/hda_priv.h index 4a7cb01fa912..e9d1a5762a55 100644 --- a/sound/pci/hda/hda_priv.h +++ b/sound/pci/hda/hda_priv.h @@ -186,6 +186,7 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_DCAPS_BUFSIZE (1 << 21) /* no buffer size alignment */ #define AZX_DCAPS_ALIGN_BUFSIZE (1 << 22) /* buffer size alignment */ #define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */ +#define AZX_DCAPS_REVERSE_ASSIGN (1 << 24) /* Assign devices in reverse order */ #define AZX_DCAPS_COUNT_LPIB_DELAY (1 << 25) /* Take LPIB as delay */ #define AZX_DCAPS_PM_RUNTIME (1 << 26) /* runtime PM support */ #define AZX_DCAPS_I915_POWERWELL (1 << 27) /* HSW i915 powerwell support */ diff --git a/sound/pci/hda/hda_tegra.c b/sound/pci/hda/hda_tegra.c new file mode 100644 index 000000000000..358414da6418 --- /dev/null +++ b/sound/pci/hda/hda_tegra.c @@ -0,0 +1,588 @@ +/* + * + * Implementation of primary ALSA driver code base for NVIDIA Tegra HDA. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/clk.h> +#include <linux/clocksource.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/time.h> + +#include <sound/core.h> +#include <sound/initval.h> + +#include "hda_codec.h" +#include "hda_controller.h" +#include "hda_priv.h" + +/* Defines for Nvidia Tegra HDA support */ +#define HDA_BAR0 0x8000 + +#define HDA_CFG_CMD 0x1004 +#define HDA_CFG_BAR0 0x1010 + +#define HDA_ENABLE_IO_SPACE (1 << 0) +#define HDA_ENABLE_MEM_SPACE (1 << 1) +#define HDA_ENABLE_BUS_MASTER (1 << 2) +#define HDA_ENABLE_SERR (1 << 8) +#define HDA_DISABLE_INTR (1 << 10) +#define HDA_BAR0_INIT_PROGRAM 0xFFFFFFFF +#define HDA_BAR0_FINAL_PROGRAM (1 << 14) + +/* IPFS */ +#define HDA_IPFS_CONFIG 0x180 +#define HDA_IPFS_EN_FPCI 0x1 + +#define HDA_IPFS_FPCI_BAR0 0x80 +#define HDA_FPCI_BAR0_START 0x40 + +#define HDA_IPFS_INTR_MASK 0x188 +#define HDA_IPFS_EN_INTR (1 << 16) + +/* max number of SDs */ +#define NUM_CAPTURE_SD 1 +#define NUM_PLAYBACK_SD 1 + +struct hda_tegra { + struct azx chip; + struct device *dev; + struct clk *hda_clk; + struct clk *hda2codec_2x_clk; + struct clk *hda2hdmi_clk; + void __iomem *regs; +}; + +#ifdef CONFIG_PM +static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT; +module_param(power_save, bint, 0644); +MODULE_PARM_DESC(power_save, + "Automatic power-saving timeout (in seconds, 0 = disable)."); +#else +static int power_save = 0; +#endif + +/* + * DMA page allocation ops. + */ +static int dma_alloc_pages(struct azx *chip, int type, size_t size, + struct snd_dma_buffer *buf) +{ + return snd_dma_alloc_pages(type, chip->card->dev, size, buf); +} + +static void dma_free_pages(struct azx *chip, struct snd_dma_buffer *buf) +{ + snd_dma_free_pages(buf); +} + +static int substream_alloc_pages(struct azx *chip, + struct snd_pcm_substream *substream, + size_t size) +{ + struct azx_dev *azx_dev = get_azx_dev(substream); + + azx_dev->bufsize = 0; + azx_dev->period_bytes = 0; + azx_dev->format_val = 0; + return snd_pcm_lib_malloc_pages(substream, size); +} + +static int substream_free_pages(struct azx *chip, + struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* + * Register access ops. Tegra HDA register access is DWORD only. + */ +static void hda_tegra_writel(u32 value, u32 *addr) +{ + writel(value, addr); +} + +static u32 hda_tegra_readl(u32 *addr) +{ + return readl(addr); +} + +static void hda_tegra_writew(u16 value, u16 *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void *dword_addr = (void *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + v &= ~(0xffff << shift); + v |= value << shift; + writel(v, dword_addr); +} + +static u16 hda_tegra_readw(u16 *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void *dword_addr = (void *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + return (v >> shift) & 0xffff; +} + +static void hda_tegra_writeb(u8 value, u8 *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void *dword_addr = (void *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + v &= ~(0xff << shift); + v |= value << shift; + writel(v, dword_addr); +} + +static u8 hda_tegra_readb(u8 *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void *dword_addr = (void *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + return (v >> shift) & 0xff; +} + +static const struct hda_controller_ops hda_tegra_ops = { + .reg_writel = hda_tegra_writel, + .reg_readl = hda_tegra_readl, + .reg_writew = hda_tegra_writew, + .reg_readw = hda_tegra_readw, + .reg_writeb = hda_tegra_writeb, + .reg_readb = hda_tegra_readb, + .dma_alloc_pages = dma_alloc_pages, + .dma_free_pages = dma_free_pages, + .substream_alloc_pages = substream_alloc_pages, + .substream_free_pages = substream_free_pages, +}; + +static void hda_tegra_init(struct hda_tegra *hda) +{ + u32 v; + + /* Enable PCI access */ + v = readl(hda->regs + HDA_IPFS_CONFIG); + v |= HDA_IPFS_EN_FPCI; + writel(v, hda->regs + HDA_IPFS_CONFIG); + + /* Enable MEM/IO space and bus master */ + v = readl(hda->regs + HDA_CFG_CMD); + v &= ~HDA_DISABLE_INTR; + v |= HDA_ENABLE_MEM_SPACE | HDA_ENABLE_IO_SPACE | + HDA_ENABLE_BUS_MASTER | HDA_ENABLE_SERR; + writel(v, hda->regs + HDA_CFG_CMD); + + writel(HDA_BAR0_INIT_PROGRAM, hda->regs + HDA_CFG_BAR0); + writel(HDA_BAR0_FINAL_PROGRAM, hda->regs + HDA_CFG_BAR0); + writel(HDA_FPCI_BAR0_START, hda->regs + HDA_IPFS_FPCI_BAR0); + + v = readl(hda->regs + HDA_IPFS_INTR_MASK); + v |= HDA_IPFS_EN_INTR; + writel(v, hda->regs + HDA_IPFS_INTR_MASK); +} + +static int hda_tegra_enable_clocks(struct hda_tegra *data) +{ + int rc; + + rc = clk_prepare_enable(data->hda_clk); + if (rc) + return rc; + rc = clk_prepare_enable(data->hda2codec_2x_clk); + if (rc) + goto disable_hda; + rc = clk_prepare_enable(data->hda2hdmi_clk); + if (rc) + goto disable_codec_2x; + + return 0; + +disable_codec_2x: + clk_disable_unprepare(data->hda2codec_2x_clk); +disable_hda: + clk_disable_unprepare(data->hda_clk); + return rc; +} + +#ifdef CONFIG_PM_SLEEP +static void hda_tegra_disable_clocks(struct hda_tegra *data) +{ + clk_disable_unprepare(data->hda2hdmi_clk); + clk_disable_unprepare(data->hda2codec_2x_clk); + clk_disable_unprepare(data->hda_clk); +} + +/* + * power management + */ +static int hda_tegra_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + struct azx_pcm *p; + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + list_for_each_entry(p, &chip->pcm_list, list) + snd_pcm_suspend_all(p->pcm); + if (chip->initialized) + snd_hda_suspend(chip->bus); + + azx_stop_chip(chip); + azx_enter_link_reset(chip); + hda_tegra_disable_clocks(hda); + + return 0; +} + +static int hda_tegra_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + int status; + + hda_tegra_enable_clocks(hda); + + /* Read STATESTS before controller reset */ + status = azx_readw(chip, STATESTS); + + hda_tegra_init(hda); + + azx_init_chip(chip, 1); + + snd_hda_resume(chip->bus); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops hda_tegra_pm = { + SET_SYSTEM_SLEEP_PM_OPS(hda_tegra_suspend, hda_tegra_resume) +}; + +/* + * reboot notifier for hang-up problem at power-down + */ +static int hda_tegra_halt(struct notifier_block *nb, unsigned long event, + void *buf) +{ + struct azx *chip = container_of(nb, struct azx, reboot_notifier); + snd_hda_bus_reboot_notify(chip->bus); + azx_stop_chip(chip); + return NOTIFY_OK; +} + +static void hda_tegra_notifier_register(struct azx *chip) +{ + chip->reboot_notifier.notifier_call = hda_tegra_halt; + register_reboot_notifier(&chip->reboot_notifier); +} + +static void hda_tegra_notifier_unregister(struct azx *chip) +{ + if (chip->reboot_notifier.notifier_call) + unregister_reboot_notifier(&chip->reboot_notifier); +} + +/* + * destructor + */ +static int hda_tegra_dev_free(struct snd_device *device) +{ + int i; + struct azx *chip = device->device_data; + + hda_tegra_notifier_unregister(chip); + + if (chip->initialized) { + for (i = 0; i < chip->num_streams; i++) + azx_stream_stop(chip, &chip->azx_dev[i]); + azx_stop_chip(chip); + } + + azx_free_stream_pages(chip); + + return 0; +} + +static int hda_tegra_init_chip(struct azx *chip, struct platform_device *pdev) +{ + struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip); + struct device *dev = hda->dev; + struct resource *res; + int err; + + hda->hda_clk = devm_clk_get(dev, "hda"); + if (IS_ERR(hda->hda_clk)) + return PTR_ERR(hda->hda_clk); + hda->hda2codec_2x_clk = devm_clk_get(dev, "hda2codec_2x"); + if (IS_ERR(hda->hda2codec_2x_clk)) + return PTR_ERR(hda->hda2codec_2x_clk); + hda->hda2hdmi_clk = devm_clk_get(dev, "hda2hdmi"); + if (IS_ERR(hda->hda2hdmi_clk)) + return PTR_ERR(hda->hda2hdmi_clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hda->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(chip->remap_addr)) + return PTR_ERR(chip->remap_addr); + + chip->remap_addr = hda->regs + HDA_BAR0; + chip->addr = res->start + HDA_BAR0; + + err = hda_tegra_enable_clocks(hda); + if (err) + return err; + + hda_tegra_init(hda); + + return 0; +} + +/* + * The codecs were powered up in snd_hda_codec_new(). + * Now all initialization done, so turn them down if possible + */ +static void power_down_all_codecs(struct azx *chip) +{ + struct hda_codec *codec; + list_for_each_entry(codec, &chip->bus->codec_list, list) + snd_hda_power_down(codec); +} + +static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) +{ + struct snd_card *card = chip->card; + int err; + unsigned short gcap; + int irq_id = platform_get_irq(pdev, 0); + + err = hda_tegra_init_chip(chip, pdev); + if (err) + return err; + + err = devm_request_irq(chip->card->dev, irq_id, azx_interrupt, + IRQF_SHARED, KBUILD_MODNAME, chip); + if (err) { + dev_err(chip->card->dev, + "unable to request IRQ %d, disabling device\n", + irq_id); + return err; + } + chip->irq = irq_id; + + synchronize_irq(chip->irq); + + gcap = azx_readw(chip, GCAP); + dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + chip->capture_streams = (gcap >> 8) & 0x0f; + chip->playback_streams = (gcap >> 12) & 0x0f; + if (!chip->playback_streams && !chip->capture_streams) { + /* gcap didn't give any info, switching to old method */ + chip->playback_streams = NUM_PLAYBACK_SD; + chip->capture_streams = NUM_CAPTURE_SD; + } + chip->capture_index_offset = 0; + chip->playback_index_offset = chip->capture_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + chip->azx_dev = devm_kcalloc(card->dev, chip->num_streams, + sizeof(*chip->azx_dev), GFP_KERNEL); + if (!chip->azx_dev) + return -ENOMEM; + + err = azx_alloc_stream_pages(chip); + if (err < 0) + return err; + + /* initialize streams */ + azx_init_stream(chip); + + /* initialize chip */ + azx_init_chip(chip, 1); + + /* codec detection */ + if (!chip->codec_mask) { + dev_err(card->dev, "no codecs found!\n"); + return -ENODEV; + } + + strcpy(card->driver, "tegra-hda"); + strcpy(card->shortname, "tegra-hda"); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx irq %i", + card->shortname, chip->addr, chip->irq); + + return 0; +} + +/* + * constructor + */ +static int hda_tegra_create(struct snd_card *card, + unsigned int driver_caps, + const struct hda_controller_ops *hda_ops, + struct hda_tegra *hda) +{ + static struct snd_device_ops ops = { + .dev_free = hda_tegra_dev_free, + }; + struct azx *chip; + int err; + + chip = &hda->chip; + + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->open_mutex); + chip->card = card; + chip->ops = hda_ops; + chip->irq = -1; + chip->driver_caps = driver_caps; + chip->driver_type = driver_caps & 0xff; + chip->dev_index = 0; + INIT_LIST_HEAD(&chip->pcm_list); + INIT_LIST_HEAD(&chip->list); + + chip->position_fix[0] = POS_FIX_AUTO; + chip->position_fix[1] = POS_FIX_AUTO; + chip->codec_probe_mask = -1; + + chip->single_cmd = false; + chip->snoop = true; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + dev_err(card->dev, "Error creating device\n"); + return err; + } + + return 0; +} + +static const struct of_device_id hda_tegra_match[] = { + { .compatible = "nvidia,tegra30-hda" }, + {}, +}; +MODULE_DEVICE_TABLE(of, hda_tegra_match); + +static int hda_tegra_probe(struct platform_device *pdev) +{ + struct snd_card *card; + struct azx *chip; + struct hda_tegra *hda; + int err; + const unsigned int driver_flags = AZX_DCAPS_RIRB_DELAY; + + hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL); + if (!hda) + return -ENOMEM; + hda->dev = &pdev->dev; + chip = &hda->chip; + + err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err < 0) { + dev_err(&pdev->dev, "Error creating card!\n"); + return err; + } + + err = hda_tegra_create(card, driver_flags, &hda_tegra_ops, hda); + if (err < 0) + goto out_free; + card->private_data = chip; + + dev_set_drvdata(&pdev->dev, card); + + err = hda_tegra_first_init(chip, pdev); + if (err < 0) + goto out_free; + + /* create codec instances */ + err = azx_codec_create(chip, NULL, 0, &power_save); + if (err < 0) + goto out_free; + + err = azx_codec_configure(chip); + if (err < 0) + goto out_free; + + /* create PCM streams */ + err = snd_hda_build_pcms(chip->bus); + if (err < 0) + goto out_free; + + /* create mixer controls */ + err = azx_mixer_create(chip); + if (err < 0) + goto out_free; + + err = snd_card_register(chip->card); + if (err < 0) + goto out_free; + + chip->running = 1; + power_down_all_codecs(chip); + hda_tegra_notifier_register(chip); + + return 0; + +out_free: + snd_card_free(card); + return err; +} + +static int hda_tegra_remove(struct platform_device *pdev) +{ + return snd_card_free(dev_get_drvdata(&pdev->dev)); +} + +static struct platform_driver tegra_platform_hda = { + .driver = { + .name = "tegra-hda", + .pm = &hda_tegra_pm, + .of_match_table = hda_tegra_match, + }, + .probe = hda_tegra_probe, + .remove = hda_tegra_remove, +}; +module_platform_driver(tegra_platform_hda); + +MODULE_DESCRIPTION("Tegra HDA bus driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index 40ba06eb44af..06275f8807a8 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -332,6 +332,7 @@ static const struct hda_fixup ad1986a_fixups[] = { static const struct snd_pci_quirk ad1986a_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x30af, "HP B2800", AD1986A_FIXUP_LAPTOP_IMIC), + SND_PCI_QUIRK(0x1043, 0x1447, "ASUS A8JN", AD1986A_FIXUP_EAPD), SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8100, "ASUS P5", AD1986A_FIXUP_3STACK), SND_PCI_QUIRK_MASK(0x1043, 0xff00, 0x8200, "ASUS M2", AD1986A_FIXUP_3STACK), SND_PCI_QUIRK(0x10de, 0xcb84, "ASUS A8N-VM", AD1986A_FIXUP_3STACK), diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 1edbb9c47c2d..ba4ca52072ff 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -1127,10 +1127,6 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, AMP_OUT_UNMUTE); eld = &per_pin->sink_eld; - if (!eld->monitor_present) { - hdmi_set_channel_count(codec, per_pin->cvt_nid, channels); - return; - } if (!non_pcm && per_pin->chmap_set) ca = hdmi_manual_channel_allocation(channels, per_pin->chmap); @@ -1598,10 +1594,18 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll) * Re-setup pin and infoframe. This is needed e.g. when * - sink is first plugged-in (infoframe is not set up if !monitor_present) * - transcoder can change during stream playback on Haswell + * and this can make HW reset converter selection on a pin. */ - if (eld->eld_valid && !old_eld_valid && per_pin->setup) + if (eld->eld_valid && !old_eld_valid && per_pin->setup) { + if (is_haswell_plus(codec) || is_valleyview(codec)) { + intel_verify_pin_cvt_connect(codec, per_pin); + intel_not_share_assigned_cvt(codec, pin_nid, + per_pin->mux_idx); + } + hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm); + } } if (eld_changed) @@ -2200,7 +2204,7 @@ static int generic_hdmi_resume(struct hda_codec *codec) struct hdmi_spec *spec = codec->spec; int pin_idx; - generic_hdmi_init(codec); + codec->patch_ops.init(codec); snd_hda_codec_resume_amp(codec); snd_hda_codec_resume_cache(codec); @@ -3324,6 +3328,7 @@ static const struct hda_codec_preset snd_hda_preset_hdmi[] = { { .id = 0x10de001a, .name = "GPU 1a HDMI/DP", .patch = patch_nvhdmi }, { .id = 0x10de001b, .name = "GPU 1b HDMI/DP", .patch = patch_nvhdmi }, { .id = 0x10de001c, .name = "GPU 1c HDMI/DP", .patch = patch_nvhdmi }, +{ .id = 0x10de0028, .name = "Tegra12x HDMI", .patch = patch_nvhdmi }, { .id = 0x10de0040, .name = "GPU 40 HDMI/DP", .patch = patch_nvhdmi }, { .id = 0x10de0041, .name = "GPU 41 HDMI/DP", .patch = patch_nvhdmi }, { .id = 0x10de0042, .name = "GPU 42 HDMI/DP", .patch = patch_nvhdmi }, @@ -3332,6 +3337,8 @@ static const struct hda_codec_preset snd_hda_preset_hdmi[] = { { .id = 0x10de0051, .name = "GPU 51 HDMI/DP", .patch = patch_nvhdmi }, { .id = 0x10de0060, .name = "GPU 60 HDMI/DP", .patch = patch_nvhdmi }, { .id = 0x10de0067, .name = "MCP67 HDMI", .patch = patch_nvhdmi_2ch }, +{ .id = 0x10de0070, .name = "GPU 70 HDMI/DP", .patch = patch_nvhdmi }, +{ .id = 0x10de0071, .name = "GPU 71 HDMI/DP", .patch = patch_nvhdmi }, { .id = 0x10de8001, .name = "MCP73 HDMI", .patch = patch_nvhdmi_2ch }, { .id = 0x11069f80, .name = "VX900 HDMI/DP", .patch = patch_via_hdmi }, { .id = 0x11069f81, .name = "VX900 HDMI/DP", .patch = patch_via_hdmi }, @@ -3379,6 +3386,7 @@ MODULE_ALIAS("snd-hda-codec-id:10de0019"); MODULE_ALIAS("snd-hda-codec-id:10de001a"); MODULE_ALIAS("snd-hda-codec-id:10de001b"); MODULE_ALIAS("snd-hda-codec-id:10de001c"); +MODULE_ALIAS("snd-hda-codec-id:10de0028"); MODULE_ALIAS("snd-hda-codec-id:10de0040"); MODULE_ALIAS("snd-hda-codec-id:10de0041"); MODULE_ALIAS("snd-hda-codec-id:10de0042"); @@ -3387,6 +3395,8 @@ MODULE_ALIAS("snd-hda-codec-id:10de0044"); MODULE_ALIAS("snd-hda-codec-id:10de0051"); MODULE_ALIAS("snd-hda-codec-id:10de0060"); MODULE_ALIAS("snd-hda-codec-id:10de0067"); +MODULE_ALIAS("snd-hda-codec-id:10de0070"); +MODULE_ALIAS("snd-hda-codec-id:10de0071"); MODULE_ALIAS("snd-hda-codec-id:10de8001"); MODULE_ALIAS("snd-hda-codec-id:11069f80"); MODULE_ALIAS("snd-hda-codec-id:11069f81"); diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 5f7c765391f1..b60824e90408 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -929,6 +929,7 @@ struct alc_codec_rename_pci_table { }; static struct alc_codec_rename_table rename_tbl[] = { + { 0x10ec0221, 0xf00f, 0x1003, "ALC231" }, { 0x10ec0269, 0xfff0, 0x3010, "ALC277" }, { 0x10ec0269, 0xf0f0, 0x2010, "ALC259" }, { 0x10ec0269, 0xf0f0, 0x3010, "ALC258" }, @@ -937,6 +938,7 @@ static struct alc_codec_rename_table rename_tbl[] = { { 0x10ec0269, 0xffff, 0x6023, "ALC281X" }, { 0x10ec0269, 0x00f0, 0x0020, "ALC269VC" }, { 0x10ec0269, 0x00f0, 0x0030, "ALC269VD" }, + { 0x10ec0662, 0xffff, 0x4020, "ALC656" }, { 0x10ec0887, 0x00f0, 0x0030, "ALC887-VD" }, { 0x10ec0888, 0x00f0, 0x0030, "ALC888-VD" }, { 0x10ec0888, 0xf0f0, 0x3020, "ALC886" }, @@ -951,9 +953,24 @@ static struct alc_codec_rename_pci_table rename_pci_tbl[] = { { 0x10ec0280, 0x1028, 0, "ALC3220" }, { 0x10ec0282, 0x1028, 0, "ALC3221" }, { 0x10ec0283, 0x1028, 0, "ALC3223" }, + { 0x10ec0288, 0x1028, 0, "ALC3263" }, { 0x10ec0292, 0x1028, 0, "ALC3226" }, + { 0x10ec0293, 0x1028, 0, "ALC3235" }, { 0x10ec0255, 0x1028, 0, "ALC3234" }, { 0x10ec0668, 0x1028, 0, "ALC3661" }, + { 0x10ec0275, 0x1028, 0, "ALC3260" }, + { 0x10ec0899, 0x1028, 0, "ALC3861" }, + { 0x10ec0670, 0x1025, 0, "ALC669X" }, + { 0x10ec0676, 0x1025, 0, "ALC679X" }, + { 0x10ec0282, 0x1043, 0, "ALC3229" }, + { 0x10ec0233, 0x1043, 0, "ALC3236" }, + { 0x10ec0280, 0x103c, 0, "ALC3228" }, + { 0x10ec0282, 0x103c, 0, "ALC3227" }, + { 0x10ec0286, 0x103c, 0, "ALC3242" }, + { 0x10ec0290, 0x103c, 0, "ALC3241" }, + { 0x10ec0668, 0x103c, 0, "ALC3662" }, + { 0x10ec0283, 0x17aa, 0, "ALC3239" }, + { 0x10ec0292, 0x17aa, 0, "ALC3232" }, { } /* terminator */ }; @@ -1410,6 +1427,7 @@ static const struct snd_pci_quirk alc880_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x10c3, "ASUS W5A", ALC880_FIXUP_ASUS_W5A), SND_PCI_QUIRK(0x1043, 0x1964, "ASUS Z71V", ALC880_FIXUP_Z71V), SND_PCI_QUIRK_VENDOR(0x1043, "ASUS", ALC880_FIXUP_GPIO1), + SND_PCI_QUIRK(0x147b, 0x1045, "ABit AA8XE", ALC880_FIXUP_6ST_AUTOMUTE), SND_PCI_QUIRK(0x1558, 0x5401, "Clevo GPIO2", ALC880_FIXUP_GPIO2), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", ALC880_FIXUP_EAPD_COEF), SND_PCI_QUIRK(0x1584, 0x9050, "Uniwill", ALC880_FIXUP_UNIWILL_DIG), @@ -1647,12 +1665,10 @@ static const struct hda_fixup alc260_fixups[] = { [ALC260_FIXUP_COEF] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { - { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x3040 }, + { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x1a, AC_VERB_SET_PROC_COEF, 0x3040 }, { } }, - .chained = true, - .chain_id = ALC260_FIXUP_HP_PIN_0F, }, [ALC260_FIXUP_GPIO1] = { .type = HDA_FIXUP_VERBS, @@ -1667,8 +1683,8 @@ static const struct hda_fixup alc260_fixups[] = { [ALC260_FIXUP_REPLACER] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { - { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, + { 0x1a, AC_VERB_SET_COEF_INDEX, 0x07 }, + { 0x1a, AC_VERB_SET_PROC_COEF, 0x3050 }, { } }, .chained = true, @@ -3522,6 +3538,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) /* Direct Drive HP Amp control */ alc_write_coefex_idx(codec, 0x57, 0x03, 0x8aa6); break; + case 0x10ec0233: case 0x10ec0283: alc_write_coef_idx(codec, 0x1b, 0x0c0b); alc_write_coef_idx(codec, 0x45, 0xc429); @@ -3538,6 +3555,25 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) alc_write_coef_idx(codec, 0x18, 0x7308); alc_write_coef_idx(codec, 0x6b, 0xc429); break; + case 0x10ec0293: + /* SET Line1 JD to 0 */ + val = alc_read_coef_idx(codec, 0x10); + alc_write_coef_idx(codec, 0x10, (val & ~(7<<8)) | 6<<8); + /* SET charge pump by verb */ + val = alc_read_coefex_idx(codec, 0x57, 0x05); + alc_write_coefex_idx(codec, 0x57, 0x05, (val & ~(1<<15|1<<13)) | 0x0); + /* SET EN_OSW to 1 */ + val = alc_read_coefex_idx(codec, 0x57, 0x03); + alc_write_coefex_idx(codec, 0x57, 0x03, (val & ~(1<<10)) | (1<<10) ); + /* Combo JD gating with LINE1-VREFO */ + val = alc_read_coef_idx(codec, 0x1a); + alc_write_coef_idx(codec, 0x1a, (val & ~(1<<3)) | (1<<3)); + /* Set to TRS type */ + alc_write_coef_idx(codec, 0x45, 0xc429); + /* Combo Jack auto detect */ + val = alc_read_coef_idx(codec, 0x4a); + alc_write_coef_idx(codec, 0x4a, (val & 0xfff0) | 0x000e); + break; case 0x10ec0668: alc_write_coef_idx(codec, 0x15, 0x0d40); alc_write_coef_idx(codec, 0xb7, 0x802b); @@ -3561,6 +3597,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, alc_write_coef_idx(codec, 0x06, 0x6100); snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); break; + case 0x10ec0233: case 0x10ec0283: alc_write_coef_idx(codec, 0x45, 0xc429); snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); @@ -3576,6 +3613,21 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, alc_write_coef_idx(codec, 0x19, 0xa208); alc_write_coef_idx(codec, 0x2e, 0xacf0); break; + case 0x10ec0293: + /* Set to TRS mode */ + alc_write_coef_idx(codec, 0x45, 0xc429); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); + /* SET charge pump by verb */ + val = alc_read_coefex_idx(codec, 0x57, 0x05); + alc_write_coefex_idx(codec, 0x57, 0x05, (val & ~(1<<15|1<<13)) | (1<<15|1<<13)); + /* SET EN_OSW to 0 */ + val = alc_read_coefex_idx(codec, 0x57, 0x03); + alc_write_coefex_idx(codec, 0x57, 0x03, (val & ~(1<<10)) | 0x0); + /* Combo JD gating without LINE1-VREFO */ + val = alc_read_coef_idx(codec, 0x1a); + alc_write_coef_idx(codec, 0x1a, (val & ~(1<<3)) | 0x0); + snd_hda_set_pin_ctl_cache(codec, mic_pin, PIN_VREF50); + break; case 0x10ec0668: alc_write_coef_idx(codec, 0x11, 0x0001); snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); @@ -3591,6 +3643,8 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, static void alc_headset_mode_default(struct hda_codec *codec) { + int val; + switch (codec->vendor_id) { case 0x10ec0255: alc_write_coef_idx(codec, 0x45, 0xc089); @@ -3598,6 +3652,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) alc_write_coefex_idx(codec, 0x57, 0x03, 0x8ea6); alc_write_coef_idx(codec, 0x49, 0x0049); break; + case 0x10ec0233: case 0x10ec0283: alc_write_coef_idx(codec, 0x06, 0x2100); alc_write_coef_idx(codec, 0x32, 0x4ea3); @@ -3608,6 +3663,16 @@ static void alc_headset_mode_default(struct hda_codec *codec) alc_write_coef_idx(codec, 0x6b, 0xc429); alc_write_coef_idx(codec, 0x18, 0x7308); break; + case 0x10ec0293: + /* Combo Jack auto detect */ + val = alc_read_coef_idx(codec, 0x4a); + alc_write_coef_idx(codec, 0x4a, (val & 0xfff0) | 0x000e); + /* Set to TRS type */ + alc_write_coef_idx(codec, 0x45, 0xC429); + /* Combo JD gating without LINE1-VREFO */ + val = alc_read_coef_idx(codec, 0x1a); + alc_write_coef_idx(codec, 0x1a, (val & ~(1<<3)) | 0x0); + break; case 0x10ec0668: alc_write_coef_idx(codec, 0x11, 0x0041); alc_write_coef_idx(codec, 0x15, 0x0d40); @@ -3620,6 +3685,8 @@ static void alc_headset_mode_default(struct hda_codec *codec) /* Iphone type */ static void alc_headset_mode_ctia(struct hda_codec *codec) { + int val; + switch (codec->vendor_id) { case 0x10ec0255: /* Set to CTIA type */ @@ -3627,6 +3694,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) alc_write_coef_idx(codec, 0x1b, 0x0c2b); alc_write_coefex_idx(codec, 0x57, 0x03, 0x8ea6); break; + case 0x10ec0233: case 0x10ec0283: alc_write_coef_idx(codec, 0x45, 0xd429); alc_write_coef_idx(codec, 0x1b, 0x0c2b); @@ -3637,6 +3705,13 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) alc_write_coef_idx(codec, 0x76, 0x0008); alc_write_coef_idx(codec, 0x18, 0x7388); break; + case 0x10ec0293: + /* Set to ctia type */ + alc_write_coef_idx(codec, 0x45, 0xd429); + /* SET Line1 JD to 1 */ + val = alc_read_coef_idx(codec, 0x10); + alc_write_coef_idx(codec, 0x10, (val & ~(7<<8)) | 7<<8); + break; case 0x10ec0668: alc_write_coef_idx(codec, 0x11, 0x0001); alc_write_coef_idx(codec, 0x15, 0x0d60); @@ -3649,6 +3724,8 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) /* Nokia type */ static void alc_headset_mode_omtp(struct hda_codec *codec) { + int val; + switch (codec->vendor_id) { case 0x10ec0255: /* Set to OMTP Type */ @@ -3656,6 +3733,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) alc_write_coef_idx(codec, 0x1b, 0x0c2b); alc_write_coefex_idx(codec, 0x57, 0x03, 0x8ea6); break; + case 0x10ec0233: case 0x10ec0283: alc_write_coef_idx(codec, 0x45, 0xe429); alc_write_coef_idx(codec, 0x1b, 0x0c2b); @@ -3666,6 +3744,13 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) alc_write_coef_idx(codec, 0x76, 0x0008); alc_write_coef_idx(codec, 0x18, 0x7388); break; + case 0x10ec0293: + /* Set to omtp type */ + alc_write_coef_idx(codec, 0x45, 0xe429); + /* SET Line1 JD to 1 */ + val = alc_read_coef_idx(codec, 0x10); + alc_write_coef_idx(codec, 0x10, (val & ~(7<<8)) | 7<<8); + break; case 0x10ec0668: alc_write_coef_idx(codec, 0x11, 0x0001); alc_write_coef_idx(codec, 0x15, 0x0d50); @@ -3691,6 +3776,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) val = alc_read_coef_idx(codec, 0x46); is_ctia = (val & 0x0070) == 0x0070; break; + case 0x10ec0233: case 0x10ec0283: alc_write_coef_idx(codec, 0x45, 0xd029); msleep(300); @@ -3703,6 +3789,16 @@ static void alc_determine_headset_type(struct hda_codec *codec) val = alc_read_coef_idx(codec, 0x6c); is_ctia = (val & 0x001c) == 0x001c; break; + case 0x10ec0293: + /* Combo Jack auto detect */ + val = alc_read_coef_idx(codec, 0x4a); + alc_write_coef_idx(codec, 0x4a, (val & 0xfff0) | 0x0008); + /* Set to ctia type */ + alc_write_coef_idx(codec, 0x45, 0xD429); + msleep(300); + val = alc_read_coef_idx(codec, 0x46); + is_ctia = (val & 0x0070) == 0x0070; + break; case 0x10ec0668: alc_write_coef_idx(codec, 0x11, 0x0001); alc_write_coef_idx(codec, 0xb7, 0x802b); @@ -3894,6 +3990,39 @@ static void alc_fixup_no_shutup(struct hda_codec *codec, } } +static void alc_fixup_disable_aamix(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + struct alc_spec *spec = codec->spec; + /* Disable AA-loopback as it causes white noise */ + spec->gen.mixer_nid = 0; + } +} + +static unsigned int alc_power_filter_xps13(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + struct alc_spec *spec = codec->spec; + + /* Avoid pop noises when headphones are plugged in */ + if (spec->gen.hp_jack_present) + if (nid == codec->afg || nid == 0x02) + return AC_PWRST_D0; + return power_state; +} + +static void alc_fixup_dell_xps13(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PROBE) { + struct alc_spec *spec = codec->spec; + spec->shutup = alc_no_shutup; + codec->power_filter = alc_power_filter_xps13; + } +} + static void alc_fixup_headset_mode_alc668(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4110,12 +4239,14 @@ enum { ALC269_FIXUP_ASUS_G73JW, ALC269_FIXUP_LENOVO_EAPD, ALC275_FIXUP_SONY_HWEQ, + ALC275_FIXUP_SONY_DISABLE_AAMIX, ALC271_FIXUP_DMIC, ALC269_FIXUP_PCM_44K, ALC269_FIXUP_STEREO_DMIC, ALC269_FIXUP_HEADSET_MIC, ALC269_FIXUP_QUANTA_MUTE, ALC269_FIXUP_LIFEBOOK, + ALC269_FIXUP_LIFEBOOK_EXTMIC, ALC269_FIXUP_AMIC, ALC269_FIXUP_DMIC, ALC269VB_FIXUP_AMIC, @@ -4159,6 +4290,8 @@ enum { ALC255_FIXUP_DELL2_MIC_NO_PRESENCE, ALC255_FIXUP_HEADSET_MODE, ALC255_FIXUP_HEADSET_MODE_NO_HP_MIC, + ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC292_FIXUP_TPT440_DOCK, }; static const struct hda_fixup alc269_fixups[] = { @@ -4213,6 +4346,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC275_FIXUP_SONY_VAIO_GPIO2 }, + [ALC275_FIXUP_SONY_DISABLE_AAMIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC269_FIXUP_SONY_VAIO + }, [ALC271_FIXUP_DMIC] = { .type = HDA_FIXUP_FUNC, .v.func = alc271_fixup_dmic, @@ -4245,6 +4384,13 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_QUANTA_MUTE }, + [ALC269_FIXUP_LIFEBOOK_EXTMIC] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1903c }, /* headset mic, with jack detect */ + { } + }, + }, [ALC269_FIXUP_AMIC] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -4552,6 +4698,26 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_headset_mode_alc255_no_hp_mic, }, + [ALC293_FIXUP_DELL1_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a1913d }, /* use as headphone mic, without its own jack detect */ + { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, + [ALC292_FIXUP_TPT440_DOCK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x16, 0x21211010 }, /* dock headphone */ + { 0x19, 0x21a11010 }, /* dock mic */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -4595,51 +4761,45 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x0606, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0608, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0609, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x060f, "Dell", ALC269_FIXUP_DELL3_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0610, "Dell", ALC269_FIXUP_DELL3_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0613, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0614, "Dell Inspiron 3135", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0615, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK), SND_PCI_QUIRK(0x1028, 0x0616, "Dell Vostro 5470", ALC290_FIXUP_SUBWOOFER_HSJACK), - SND_PCI_QUIRK(0x1028, 0x061f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0629, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x062c, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x062e, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0632, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0638, "Dell Inspiron 5439", ALC290_FIXUP_MONO_SPEAKERS_HSJACK), - SND_PCI_QUIRK(0x1028, 0x063e, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x063f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0640, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x064d, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0651, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0652, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0653, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0657, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0658, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x065f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0662, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0667, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x064a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x064b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0668, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0669, "Dell", ALC255_FIXUP_DELL2_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0674, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x067e, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x067f, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x0684, "Dell", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x15cc, "Dell X5 Precision", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x15cd, "Dell X5 Precision", ALC269_FIXUP_DELL2_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x164a, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x1973, "HP Pavilion", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x1983, "HP Pavilion", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), /* ALC282 */ + SND_PCI_QUIRK(0x103c, 0x220d, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x220e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x220f, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2211, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2212, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2213, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2214, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2266, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2267, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2268, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2269, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x226a, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x226b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x226c, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x226d, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x226e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x226f, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x227a, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x227b, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x229e, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), @@ -4679,6 +4839,10 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x22c8, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x22c3, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x22c4, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2334, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2335, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2336, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2337, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), @@ -4699,8 +4863,10 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x104d, 0x9073, "Sony VAIO", ALC275_FIXUP_SONY_VAIO_GPIO2), SND_PCI_QUIRK(0x104d, 0x907b, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ), SND_PCI_QUIRK(0x104d, 0x9084, "Sony VAIO", ALC275_FIXUP_SONY_HWEQ), + SND_PCI_QUIRK(0x104d, 0x9099, "Sony VAIO S13", ALC275_FIXUP_SONY_DISABLE_AAMIX), SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), SND_PCI_QUIRK(0x10cf, 0x1475, "Lifebook", ALC269_FIXUP_LIFEBOOK), + SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), 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), @@ -4712,7 +4878,9 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x21fb, "Thinkpad T430s", ALC269_FIXUP_LENOVO_DOCK), SND_PCI_QUIRK(0x17aa, 0x2203, "Thinkpad X230 Tablet", ALC269_FIXUP_LENOVO_DOCK), SND_PCI_QUIRK(0x17aa, 0x2208, "Thinkpad T431s", ALC269_FIXUP_LENOVO_DOCK), - SND_PCI_QUIRK(0x17aa, 0x220c, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x220c, "Thinkpad T440s", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x220e, "Thinkpad T440p", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2210, "Thinkpad T540p", ALC292_FIXUP_TPT440_DOCK), SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), @@ -4790,9 +4958,148 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC269_FIXUP_DELL2_MIC_NO_PRESENCE, .name = "dell-headset-dock"}, {.id = ALC283_FIXUP_CHROME_BOOK, .name = "alc283-dac-wcaps"}, {.id = ALC283_FIXUP_SENSE_COMBO_JACK, .name = "alc283-sense-combo"}, + {.id = ALC292_FIXUP_TPT440_DOCK, .name = "tpt440-dock"}, {} }; +static const struct snd_hda_pin_quirk alc269_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60140}, + {0x14, 0x90170110}, + {0x17, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170120}, + {0x17, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170120}, + {0x17, 0x90170140}, + {0x18, 0x40000000}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x41163b05}, + {0x1e, 0x411111f0}, + {0x21, 0x0321102f}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170130}, + {0x17, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}, + {0x21, 0x02211040}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170140}, + {0x17, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}, + {0x21, 0x02211050}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60170}, + {0x14, 0x90170120}, + {0x17, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0255, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60170}, + {0x14, 0x90170130}, + {0x17, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}, + {0x21, 0x02211040}), + SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x17, 0x40020008}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40e00001}, + {0x1e, 0x411111f0}, + {0x21, 0x0321101f}), + SND_HDA_PIN_QUIRK(0x10ec0283, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170120}, + {0x17, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0292, 0x1028, "Dell", ALC269_FIXUP_DELL3_MIC_NO_PRESENCE, + {0x12, 0x90a60140}, + {0x13, 0x411111f0}, + {0x14, 0x90170110}, + {0x15, 0x0221401f}, + {0x16, 0x411111f0}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}), + SND_HDA_PIN_QUIRK(0x10ec0293, 0x1028, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x40000000}, + {0x13, 0x90a60140}, + {0x14, 0x90170110}, + {0x15, 0x0221401f}, + {0x16, 0x21014020}, + {0x18, 0x411111f0}, + {0x19, 0x21a19030}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}), + SND_HDA_PIN_QUIRK(0x10ec0293, 0x1028, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x40000000}, + {0x13, 0x90a60140}, + {0x14, 0x90170110}, + {0x15, 0x0221401f}, + {0x16, 0x411111f0}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40700001}, + {0x1e, 0x411111f0}), + {} +}; static void alc269_fill_coef(struct hda_codec *codec) { @@ -4854,6 +5161,7 @@ static int patch_alc269(struct hda_codec *codec) snd_hda_pick_fixup(codec, alc269_fixup_models, alc269_fixup_tbl, alc269_fixups); + snd_hda_pick_pin_fixup(codec, alc269_pin_fixup_tbl, alc269_fixups); snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); alc_auto_parse_customize_define(codec); @@ -5310,6 +5618,8 @@ enum { ALC662_FIXUP_BASS_1A, ALC662_FIXUP_BASS_CHMAP, ALC668_FIXUP_AUTO_MUTE, + ALC668_FIXUP_DELL_DISABLE_AAMIX, + ALC668_FIXUP_DELL_XPS13, }; static const struct hda_fixup alc662_fixups[] = { @@ -5476,6 +5786,18 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_inv_dmic_0x12, }, + [ALC668_FIXUP_DELL_XPS13] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_dell_xps13, + .chained = true, + .chain_id = ALC668_FIXUP_DELL_DISABLE_AAMIX + }, + [ALC668_FIXUP_DELL_DISABLE_AAMIX] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_disable_aamix, + .chained = true, + .chain_id = ALC668_FIXUP_DELL_MIC_NO_PRESENCE + }, [ALC668_FIXUP_AUTO_MUTE] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_auto_mute_via_amp, @@ -5536,13 +5858,9 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x038b, "Acer Aspire 8943G", ALC662_FIXUP_ASPIRE), SND_PCI_QUIRK(0x1028, 0x05d8, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x05db, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x060a, "Dell XPS 13", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0623, "Dell", ALC668_FIXUP_AUTO_MUTE), - SND_PCI_QUIRK(0x1028, 0x0624, "Dell", ALC668_FIXUP_AUTO_MUTE), + SND_PCI_QUIRK(0x1028, 0x060a, "Dell XPS 13", ALC668_FIXUP_DELL_XPS13), SND_PCI_QUIRK(0x1028, 0x0625, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0626, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1028, 0x0628, "Dell", ALC668_FIXUP_AUTO_MUTE), - SND_PCI_QUIRK(0x1028, 0x064e, "Dell", ALC668_FIXUP_AUTO_MUTE), SND_PCI_QUIRK(0x1028, 0x0696, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x0698, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x1632, "HP RP5800", ALC662_FIXUP_HP_RP5800), @@ -5634,6 +5952,70 @@ static const struct hda_model_fixup alc662_fixup_models[] = { {} }; +static const struct snd_hda_pin_quirk alc662_pin_fixup_tbl[] = { + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x99a30130}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}, + {0x18, 0x40000008}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x41000001}, + {0x1e, 0x411111f0}, + {0x1f, 0x411111f0}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x99a30140}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}, + {0x18, 0x40000008}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x41000001}, + {0x1e, 0x411111f0}, + {0x1f, 0x411111f0}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x99a30150}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}, + {0x18, 0x40000008}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x41000001}, + {0x1e, 0x411111f0}, + {0x1f, 0x411111f0}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x411111f0}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x03011020}, + {0x18, 0x40000008}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x41000001}, + {0x1e, 0x411111f0}, + {0x1f, 0x411111f0}), + SND_HDA_PIN_QUIRK(0x10ec0668, 0x1028, "Dell XPS 15", ALC668_FIXUP_AUTO_MUTE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x15, 0x0321101f}, + {0x16, 0x40000000}, + {0x18, 0x411111f0}, + {0x19, 0x411111f0}, + {0x1a, 0x411111f0}, + {0x1b, 0x411111f0}, + {0x1d, 0x40d6832d}, + {0x1e, 0x411111f0}, + {0x1f, 0x411111f0}), + {} +}; + static void alc662_fill_coef(struct hda_codec *codec) { int val, coef; @@ -5683,6 +6065,7 @@ static int patch_alc662(struct hda_codec *codec) snd_hda_pick_fixup(codec, alc662_fixup_models, alc662_fixup_tbl, alc662_fixups); + snd_hda_pick_pin_fixup(codec, alc662_pin_fixup_tbl, alc662_fixups); snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); alc_auto_parse_customize_define(codec); @@ -5771,6 +6154,7 @@ static const struct hda_codec_preset snd_hda_preset_realtek[] = { { .id = 0x10ec0221, .name = "ALC221", .patch = patch_alc269 }, { .id = 0x10ec0231, .name = "ALC231", .patch = patch_alc269 }, { .id = 0x10ec0233, .name = "ALC233", .patch = patch_alc269 }, + { .id = 0x10ec0235, .name = "ALC233", .patch = patch_alc269 }, { .id = 0x10ec0255, .name = "ALC255", .patch = patch_alc269 }, { .id = 0x10ec0260, .name = "ALC260", .patch = patch_alc260 }, { .id = 0x10ec0262, .name = "ALC262", .patch = patch_alc262 }, @@ -5804,10 +6188,12 @@ static const struct hda_codec_preset snd_hda_preset_realtek[] = { .patch = patch_alc662 }, { .id = 0x10ec0663, .name = "ALC663", .patch = patch_alc662 }, { .id = 0x10ec0665, .name = "ALC665", .patch = patch_alc662 }, + { .id = 0x10ec0667, .name = "ALC667", .patch = patch_alc662 }, { .id = 0x10ec0668, .name = "ALC668", .patch = patch_alc662 }, { .id = 0x10ec0670, .name = "ALC670", .patch = patch_alc662 }, { .id = 0x10ec0671, .name = "ALC671", .patch = patch_alc662 }, { .id = 0x10ec0680, .name = "ALC680", .patch = patch_alc680 }, + { .id = 0x10ec0867, .name = "ALC891", .patch = patch_alc882 }, { .id = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 }, { .id = 0x10ec0882, .name = "ALC882", .patch = patch_alc882 }, { .id = 0x10ec0883, .name = "ALC883", .patch = patch_alc882 }, diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 75515b494034..3744ea4e843d 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -122,6 +122,12 @@ enum { }; enum { + STAC_92HD95_HP_LED, + STAC_92HD95_HP_BASS, + STAC_92HD95_MODELS +}; + +enum { STAC_925x_REF, STAC_M1, STAC_M1_2, @@ -795,7 +801,7 @@ static int find_mute_led_cfg(struct hda_codec *codec, int default_polarity) } while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) { - if (sscanf(dev->name, "HP_Mute_LED_%d_%x", + if (sscanf(dev->name, "HP_Mute_LED_%u_%x", &spec->gpio_led_polarity, &spec->gpio_led) == 2) { unsigned int max_gpio; @@ -808,7 +814,7 @@ static int find_mute_led_cfg(struct hda_codec *codec, int default_polarity) spec->vref_mute_led_nid = spec->gpio_led; return 1; } - if (sscanf(dev->name, "HP_Mute_LED_%d", + if (sscanf(dev->name, "HP_Mute_LED_%u", &spec->gpio_led_polarity) == 1) { set_hp_led_gpio(codec); return 1; @@ -4128,6 +4134,48 @@ static const struct snd_pci_quirk stac9205_fixup_tbl[] = { {} /* terminator */ }; +static void stac92hd95_fixup_hp_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct sigmatel_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + if (find_mute_led_cfg(codec, spec->default_polarity)) + codec_dbg(codec, "mute LED gpio %d polarity %d\n", + spec->gpio_led, + spec->gpio_led_polarity); +} + +static const struct hda_fixup stac92hd95_fixups[] = { + [STAC_92HD95_HP_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = stac92hd95_fixup_hp_led, + }, + [STAC_92HD95_HP_BASS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + {0x1a, 0x795, 0x00}, /* HPF to 100Hz */ + {} + }, + .chained = true, + .chain_id = STAC_92HD95_HP_LED, + }, +}; + +static const struct snd_pci_quirk stac92hd95_fixup_tbl[] = { + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x1911, "HP Spectre 13", STAC_92HD95_HP_BASS), + {} /* terminator */ +}; + +static const struct hda_model_fixup stac92hd95_models[] = { + { .id = STAC_92HD95_HP_LED, .name = "hp-led" }, + { .id = STAC_92HD95_HP_BASS, .name = "hp-bass" }, + {} +}; + + static int stac_parse_auto_config(struct hda_codec *codec) { struct sigmatel_spec *spec = codec->spec; @@ -4580,10 +4628,16 @@ static int patch_stac92hd95(struct hda_codec *codec) spec->gen.beep_nid = 0x19; /* digital beep */ spec->pwr_nids = stac92hd95_pwr_nids; spec->num_pwrs = ARRAY_SIZE(stac92hd95_pwr_nids); - spec->default_polarity = -1; /* no default cfg */ + spec->default_polarity = 0; codec->patch_ops = stac_patch_ops; + snd_hda_pick_fixup(codec, stac92hd95_models, stac92hd95_fixup_tbl, + stac92hd95_fixups); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + stac_setup_gpio(codec); + err = stac_parse_auto_config(codec); if (err < 0) { stac_free(codec); @@ -4592,6 +4646,8 @@ static int patch_stac92hd95(struct hda_codec *codec) codec->proc_widget_hook = stac92hd_proc_hook; + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + return 0; } diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index 68340d7df76d..c91860e0a28d 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -2779,7 +2779,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip) unsigned long port; unsigned long pos, pos1, t; int civ, timeout = 1000, attempt = 1; - struct timespec start_time, stop_time; + ktime_t start_time, stop_time; if (chip->ac97_bus->clock != 48000) return; /* specified in module option */ @@ -2813,7 +2813,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip) iputbyte(chip, port + ICH_REG_OFF_CR, ICH_IOCE); iputdword(chip, ICHREG(ALI_DMACR), 1 << ichdev->ali_slot); } - do_posix_clock_monotonic_gettime(&start_time); + start_time = ktime_get(); spin_unlock_irq(&chip->reg_lock); msleep(50); spin_lock_irq(&chip->reg_lock); @@ -2837,7 +2837,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip) pos += ichdev->position; } chip->in_measurement = 0; - do_posix_clock_monotonic_gettime(&stop_time); + stop_time = ktime_get(); /* stop */ if (chip->device_type == DEVICE_ALI) { iputdword(chip, ICHREG(ALI_DMACR), 1 << (ichdev->ali_slot + 16)); @@ -2865,9 +2865,7 @@ static void intel8x0_measure_ac97_clock(struct intel8x0 *chip) } pos /= 4; - t = stop_time.tv_sec - start_time.tv_sec; - t *= 1000000; - t += (stop_time.tv_nsec - start_time.tv_nsec) / 1000; + t = ktime_us_delta(stop_time, start_time); dev_info(chip->card->dev, "%s: measured %lu usecs (%lu samples)\n", __func__, t, pos); if (t == 0) { diff --git a/sound/pci/lola/lola_proc.c b/sound/pci/lola/lola_proc.c index 04df83defc09..c241dc06dd92 100644 --- a/sound/pci/lola/lola_proc.c +++ b/sound/pci/lola/lola_proc.c @@ -151,7 +151,7 @@ static void lola_proc_codec_rw_write(struct snd_info_entry *entry, char line[64]; unsigned int id, verb, data, extdata; while (!snd_info_get_line(buffer, line, sizeof(line))) { - if (sscanf(line, "%i %i %i %i", &id, &verb, &data, &extdata) != 4) + if (sscanf(line, "%u %u %u %u", &id, &verb, &data, &extdata) != 4) continue; lola_codec_read(chip, id, verb, data, extdata, &chip->debug_res, diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c index 2d8e95e9fbe5..e8f38e5df10a 100644 --- a/sound/pci/lx6464es/lx_core.c +++ b/sound/pci/lx6464es/lx_core.c @@ -24,6 +24,7 @@ /* #define RMH_DEBUG 1 */ +#include <linux/bitops.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/delay.h> @@ -429,11 +430,6 @@ int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data) return ret; } -#define CSES_TIMEOUT 100 /* microseconds */ -#define CSES_CE 0x0001 -#define CSES_BROADCAST 0x0002 -#define CSES_UPDATE_LDSV 0x0004 - #define PIPE_INFO_TO_CMD(capture, pipe) \ ((u32)((u32)(pipe) | ((capture) ? ID_IS_CAPTURE : 0L)) << ID_OFFSET) @@ -519,7 +515,6 @@ int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture, *r_needed += 1; } -#if 0 dev_dbg(chip->card->dev, "CMD_08_ASK_BUFFERS: needed %d, freed %d\n", *r_needed, *r_freed); @@ -530,7 +525,6 @@ int lx_buffer_ask(struct lx6464es *chip, u32 pipe, int is_capture, chip->rmh.stat[i], chip->rmh.stat[i] & MASK_DATA_SIZE); } -#endif } spin_unlock_irqrestore(&chip->msg_lock, flags); @@ -971,9 +965,9 @@ int lx_level_peaks(struct lx6464es *chip, int is_capture, int channels, /* interrupt handling */ #define PCX_IRQ_NONE 0 -#define IRQCS_ACTIVE_PCIDB 0x00002000L /* Bit nø 13 */ -#define IRQCS_ENABLE_PCIIRQ 0x00000100L /* Bit nø 08 */ -#define IRQCS_ENABLE_PCIDB 0x00000200L /* Bit nø 09 */ +#define IRQCS_ACTIVE_PCIDB BIT(13) +#define IRQCS_ENABLE_PCIIRQ BIT(8) +#define IRQCS_ENABLE_PCIDB BIT(9) static u32 lx_interrupt_test_ack(struct lx6464es *chip) { @@ -1030,25 +1024,21 @@ static int lx_interrupt_handle_async_events(struct lx6464es *chip, u32 irqsrc, int err; u32 stat[9]; /* answer from CMD_04_GET_EVENT */ - /* On peut optimiser pour ne pas lire les evenements vides - * les mots de réponse sont dans l'ordre suivant : - * Stat[0] mot de status général - * Stat[1] fin de buffer OUT pF - * Stat[2] fin de buffer OUT pf - * Stat[3] fin de buffer IN pF - * Stat[4] fin de buffer IN pf - * Stat[5] underrun poid fort - * Stat[6] underrun poid faible - * Stat[7] overrun poid fort - * Stat[8] overrun poid faible + /* We can optimize this to not read dumb events. + * Answer words are in the following order: + * Stat[0] general status + * Stat[1] end of buffer OUT pF + * Stat[2] end of buffer OUT pf + * Stat[3] end of buffer IN pF + * Stat[4] end of buffer IN pf + * Stat[5] MSB underrun + * Stat[6] LSB underrun + * Stat[7] MSB overrun + * Stat[8] LSB overrun * */ u64 orun_mask; u64 urun_mask; -#if 0 - int has_underrun = (irqsrc & MASK_SYS_STATUS_URUN) ? 1 : 0; - int has_overrun = (irqsrc & MASK_SYS_STATUS_ORUN) ? 1 : 0; -#endif int eb_pending_out = (irqsrc & MASK_SYS_STATUS_EOBO) ? 1 : 0; int eb_pending_in = (irqsrc & MASK_SYS_STATUS_EOBI) ? 1 : 0; @@ -1199,9 +1189,8 @@ irqreturn_t lx_interrupt(int irq, void *dev_id) if (irqsrc & MASK_SYS_STATUS_CMD_DONE) goto exit; -#if 0 if (irqsrc & MASK_SYS_STATUS_EOBI) - dev_dgg(chip->card->dev, "interrupt: EOBI\n"); + dev_dbg(chip->card->dev, "interrupt: EOBI\n"); if (irqsrc & MASK_SYS_STATUS_EOBO) dev_dbg(chip->card->dev, "interrupt: EOBO\n"); @@ -1211,7 +1200,6 @@ irqreturn_t lx_interrupt(int irq, void *dev_id) if (irqsrc & MASK_SYS_STATUS_ORUN) dev_dbg(chip->card->dev, "interrupt: ORUN\n"); -#endif if (async_pending) { u64 notified_in_pipe_mask = 0; @@ -1238,7 +1226,6 @@ irqreturn_t lx_interrupt(int irq, void *dev_id) } if (async_escmd) { -#if 0 /* backdoor for ethersound commands * * for now, we do not need this @@ -1246,7 +1233,6 @@ irqreturn_t lx_interrupt(int irq, void *dev_id) * */ dev_dbg(chip->card->dev, "interrupt requests escmd handling\n"); -#endif } exit: diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 0060b31cc3f3..0e9623368ab0 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -47,6 +47,7 @@ source "sound/soc/kirkwood/Kconfig" source "sound/soc/intel/Kconfig" source "sound/soc/mxs/Kconfig" source "sound/soc/pxa/Kconfig" +source "sound/soc/rockchip/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 5f1df02984f8..534714a1ca44 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ +obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index de433cfd044c..f403f399808a 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -347,6 +347,7 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, u32 tfmr, rfmr, tcmr, rcmr; int start_event; int ret; + int fslen, fslen_ext; /* * Currently, there is only one set of dma params for @@ -388,18 +389,6 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, } /* - * The SSC only supports up to 16-bit samples in I2S format, due - * to the size of the Frame Mode Register FSLEN field. - */ - if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S - && bits > 16) { - printk(KERN_WARNING - "atmel_ssc_dai: sample size %d " - "is too large for I2S\n", bits); - return -EINVAL; - } - - /* * Compute SSC register settings. */ switch (ssc_p->daifmt @@ -413,6 +402,17 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, * from the MCK divider, and the BCLK signal * is output on the SSC TK line. */ + + if (bits > 16 && !ssc->pdata->has_fslen_ext) { + dev_err(dai->dev, + "sample size %d is too large for SSC device\n", + bits); + return -EINVAL; + } + + fslen_ext = (bits - 1) / 16; + fslen = (bits - 1) % 16; + rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period) | SSC_BF(RCMR_STTDLY, START_DELAY) | SSC_BF(RCMR_START, SSC_START_FALLING_RF) @@ -420,9 +420,10 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, | SSC_BF(RCMR_CKO, SSC_CKO_NONE) | SSC_BF(RCMR_CKS, SSC_CKS_DIV); - rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + rfmr = SSC_BF(RFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE) - | SSC_BF(RFMR_FSLEN, (bits - 1)) + | SSC_BF(RFMR_FSLEN, fslen) | SSC_BF(RFMR_DATNB, (channels - 1)) | SSC_BIT(RFMR_MSBF) | SSC_BF(RFMR_LOOP, 0) @@ -435,10 +436,11 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS) | SSC_BF(TCMR_CKS, SSC_CKS_DIV); - tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + tfmr = SSC_BF(TFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | SSC_BF(TFMR_FSDEN, 0) | SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE) - | SSC_BF(TFMR_FSLEN, (bits - 1)) + | SSC_BF(TFMR_FSLEN, fslen) | SSC_BF(TFMR_DATNB, (channels - 1)) | SSC_BIT(TFMR_MSBF) | SSC_BF(TFMR_DATDEF, 0) diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c index b4e36901a40b..4052268ce462 100644 --- a/sound/soc/atmel/atmel_wm8904.c +++ b/sound/soc/atmel/atmel_wm8904.c @@ -18,10 +18,6 @@ #include "../codecs/wm8904.h" #include "atmel_ssc_dai.h" -#define MCLK_RATE 32768 - -static struct clk *mclk; - static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Mic", NULL), @@ -61,26 +57,6 @@ static struct snd_soc_ops atmel_asoc_wm8904_ops = { .hw_params = atmel_asoc_wm8904_hw_params, }; -static int atmel_set_bias_level(struct snd_soc_card *card, - struct snd_soc_dapm_context *dapm, - enum snd_soc_bias_level level) -{ - if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { - switch (level) { - case SND_SOC_BIAS_PREPARE: - clk_prepare_enable(mclk); - break; - case SND_SOC_BIAS_OFF: - clk_disable_unprepare(mclk); - break; - default: - break; - } - } - - return 0; -}; - static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { .name = "WM8904", .stream_name = "WM8904 PCM", @@ -94,7 +70,6 @@ static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { static struct snd_soc_card atmel_asoc_wm8904_card = { .name = "atmel_asoc_wm8904", .owner = THIS_MODULE, - .set_bias_level = atmel_set_bias_level, .dai_link = &atmel_asoc_wm8904_dailink, .num_links = 1, .dapm_widgets = atmel_asoc_wm8904_dapm_widgets, @@ -153,7 +128,6 @@ static int atmel_asoc_wm8904_probe(struct platform_device *pdev) { struct snd_soc_card *card = &atmel_asoc_wm8904_card; struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; - struct clk *clk_src; int id, ret; card->dev = &pdev->dev; @@ -170,30 +144,6 @@ static int atmel_asoc_wm8904_probe(struct platform_device *pdev) return ret; } - mclk = clk_get(NULL, "pck0"); - if (IS_ERR(mclk)) { - dev_err(&pdev->dev, "failed to get pck0\n"); - ret = PTR_ERR(mclk); - goto err_set_audio; - } - - clk_src = clk_get(NULL, "clk32k"); - if (IS_ERR(clk_src)) { - dev_err(&pdev->dev, "failed to get clk32k\n"); - ret = PTR_ERR(clk_src); - goto err_set_audio; - } - - ret = clk_set_parent(mclk, clk_src); - clk_put(clk_src); - if (ret != 0) { - dev_err(&pdev->dev, "failed to set MCLK parent\n"); - goto err_set_audio; - } - - dev_info(&pdev->dev, "setting pck0 to %dHz\n", MCLK_RATE); - clk_set_rate(mclk, MCLK_RATE); - ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card failed\n"); diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index 174bd546c08b..bb1149126c54 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -48,7 +48,6 @@ #include <asm/mach-types.h> #include <mach/hardware.h> -#include <mach/gpio.h> #include "../codecs/wm8731.h" #include "atmel-pcm.h" diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c index a3881c4381c9..bcf591373a7a 100644 --- a/sound/soc/blackfin/bf5xx-i2s-pcm.c +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c @@ -290,19 +290,19 @@ static int bf5xx_pcm_silence(struct snd_pcm_substream *substream, unsigned int sample_size = runtime->sample_bits / 8; void *buf = runtime->dma_area; struct bf5xx_i2s_pcm_data *dma_data; - unsigned int offset, size; + unsigned int offset, samples; dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); if (dma_data->tdm_mode) { offset = pos * 8 * sample_size; - size = count * 8 * sample_size; + samples = count * 8; } else { offset = frames_to_bytes(runtime, pos); - size = frames_to_bytes(runtime, count); + samples = count * runtime->channels; } - snd_pcm_format_set_silence(runtime->format, buf + offset, size); + snd_pcm_format_set_silence(runtime->format, buf + offset, samples); return 0; } diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index cbfa1e18f651..4c7542571484 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -47,6 +47,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS42L52 if I2C && INPUT select SND_SOC_CS42L56 if I2C && INPUT select SND_SOC_CS42L73 if I2C + select SND_SOC_CS4265 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI select SND_SOC_CS42XX8_I2C if I2C @@ -74,6 +75,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_PCM3008 select SND_SOC_PCM512x_I2C if I2C select SND_SOC_PCM512x_SPI if SPI_MASTER + select SND_SOC_RT286 if I2C select SND_SOC_RT5631 if I2C select SND_SOC_RT5640 if I2C select SND_SOC_RT5645 if I2C @@ -225,11 +227,11 @@ config SND_SOC_ADAU1373 config SND_SOC_ADAU1701 tristate "Analog Devices ADAU1701 CODEC" depends on I2C - select SND_SOC_SIGMADSP + select SND_SOC_SIGMADSP_I2C config SND_SOC_ADAU17X1 tristate - select SND_SOC_SIGMADSP + select SND_SOC_SIGMADSP_REGMAP config SND_SOC_ADAU1761 tristate @@ -338,6 +340,11 @@ config SND_SOC_CS42L73 tristate "Cirrus Logic CS42L73 CODEC" depends on I2C +config SND_SOC_CS4265 + tristate "Cirrus Logic CS4265 CODEC" + depends on I2C + select REGMAP_I2C + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate "Cirrus Logic CS4270 CODEC" @@ -449,6 +456,9 @@ config SND_SOC_RL6231 default m if SND_SOC_RT5645=m default m if SND_SOC_RT5651=m +config SND_SOC_RT286 + tristate + config SND_SOC_RT5631 tristate @@ -476,6 +486,14 @@ config SND_SOC_SIGMADSP tristate select CRC32 +config SND_SOC_SIGMADSP_I2C + tristate + select SND_SOC_SIGMADSP + +config SND_SOC_SIGMADSP_REGMAP + tristate + select SND_SOC_SIGMADSP + config SND_SOC_SIRF_AUDIO_CODEC tristate "SiRF SoC internal audio codec" select REGMAP_MMIO diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index be3377b8d73f..ade412e49bd0 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -37,6 +37,7 @@ snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o snd-soc-cs42l52-objs := cs42l52.o snd-soc-cs42l56-objs := cs42l56.o snd-soc-cs42l73-objs := cs42l73.o +snd-soc-cs4265-objs := cs4265.o snd-soc-cs4270-objs := cs4270.o snd-soc-cs4271-objs := cs4271.o snd-soc-cs42xx8-objs := cs42xx8.o @@ -68,6 +69,7 @@ snd-soc-pcm512x-objs := pcm512x.o snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o snd-soc-pcm512x-spi-objs := pcm512x-spi.o snd-soc-rl6231-objs := rl6231.o +snd-soc-rt286-objs := rt286.o snd-soc-rt5631-objs := rt5631.o snd-soc-rt5640-objs := rt5640.o snd-soc-rt5645-objs := rt5645.o @@ -77,6 +79,8 @@ snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o snd-soc-alc5632-objs := alc5632.o snd-soc-sigmadsp-objs := sigmadsp.o +snd-soc-sigmadsp-i2c-objs := sigmadsp-i2c.o +snd-soc-sigmadsp-regmap-objs := sigmadsp-regmap.o snd-soc-si476x-objs := si476x.o snd-soc-sirf-audio-codec-objs := sirf-audio-codec.o snd-soc-sn95031-objs := sn95031.o @@ -202,6 +206,7 @@ obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o obj-$(CONFIG_SND_SOC_CS42L56) += snd-soc-cs42l56.o obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o +obj-$(CONFIG_SND_SOC_CS4265) += snd-soc-cs4265.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o @@ -233,6 +238,7 @@ obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o obj-$(CONFIG_SND_SOC_RL6231) += snd-soc-rl6231.o +obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o obj-$(CONFIG_SND_SOC_RT5645) += snd-soc-rt5645.o @@ -240,6 +246,8 @@ obj-$(CONFIG_SND_SOC_RT5651) += snd-soc-rt5651.o obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o +obj-$(CONFIG_SND_SOC_SIGMADSP_I2C) += snd-soc-sigmadsp-i2c.o +obj-$(CONFIG_SND_SOC_SIGMADSP_REGMAP) += snd-soc-sigmadsp-regmap.o obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index 8d9ba4ba4bfe..e889e1b84192 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -89,8 +89,8 @@ static int ac97_soc_probe(struct snd_soc_codec *codec) int ret; /* add codec as bus device for standard ac97 */ - ret = snd_ac97_bus(codec->card->snd_card, 0, soc_ac97_ops, NULL, - &ac97_bus); + ret = snd_ac97_bus(codec->component.card->snd_card, 0, soc_ac97_ops, + NULL, &ac97_bus); if (ret < 0) return ret; diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index d71c59cf7bdd..370b742117ef 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -230,8 +230,10 @@ static int adau1701_reg_read(void *context, unsigned int reg, *value = 0; - for (i = 0; i < size; i++) - *value |= recv_buf[i] << (i * 8); + for (i = 0; i < size; i++) { + *value <<= 8; + *value |= recv_buf[i]; + } return 0; } diff --git a/sound/soc/codecs/adau1977.c b/sound/soc/codecs/adau1977.c index fd55da7cb9d4..70ab35744aba 100644 --- a/sound/soc/codecs/adau1977.c +++ b/sound/soc/codecs/adau1977.c @@ -968,7 +968,7 @@ int adau1977_probe(struct device *dev, struct regmap *regmap, if (adau1977->dvdd_reg) power_off_mask = ~0; else - power_off_mask = ~ADAU1977_BLOCK_POWER_SAI_LDO_EN; + power_off_mask = (unsigned int)~ADAU1977_BLOCK_POWER_SAI_LDO_EN; ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_BLOCK_POWER_SAI, power_off_mask, 0x00); diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 3ba4c0f11418..041712592e29 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -547,7 +547,7 @@ static const struct ak4642_drvdata ak4648_drvdata = { .extended_frequencies = 1, }; -static struct of_device_id ak4642_of_match[]; +static const struct of_device_id ak4642_of_match[]; static int ak4642_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -593,7 +593,7 @@ static int ak4642_i2c_remove(struct i2c_client *client) return 0; } -static struct of_device_id ak4642_of_match[] = { +static const struct of_device_id ak4642_of_match[] = { { .compatible = "asahi-kasei,ak4642", .data = &ak4642_drvdata}, { .compatible = "asahi-kasei,ak4643", .data = &ak4643_drvdata}, { .compatible = "asahi-kasei,ak4648", .data = &ak4648_drvdata}, diff --git a/sound/soc/codecs/ak5386.c b/sound/soc/codecs/ak5386.c index 72e953b2cb41..8107a1cac876 100644 --- a/sound/soc/codecs/ak5386.c +++ b/sound/soc/codecs/ak5386.c @@ -14,12 +14,18 @@ #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/of_device.h> +#include <linux/regulator/consumer.h> #include <sound/soc.h> #include <sound/pcm.h> #include <sound/initval.h> +static const char * const supply_names[] = { + "va", "vd" +}; + struct ak5386_priv { int reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; }; static const struct snd_soc_dapm_widget ak5386_dapm_widgets[] = { @@ -32,7 +38,42 @@ static const struct snd_soc_dapm_route ak5386_dapm_routes[] = { { "Capture", NULL, "AINR" }, }; +static int ak5386_soc_probe(struct snd_soc_codec *codec) +{ + struct ak5386_priv *priv = snd_soc_codec_get_drvdata(codec); + return regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +} + +static int ak5386_soc_remove(struct snd_soc_codec *codec) +{ + struct ak5386_priv *priv = snd_soc_codec_get_drvdata(codec); + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + return 0; +} + +#ifdef CONFIG_PM +static int ak5386_soc_suspend(struct snd_soc_codec *codec) +{ + struct ak5386_priv *priv = snd_soc_codec_get_drvdata(codec); + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + return 0; +} + +static int ak5386_soc_resume(struct snd_soc_codec *codec) +{ + struct ak5386_priv *priv = snd_soc_codec_get_drvdata(codec); + return regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +} +#else +#define ak5386_soc_suspend NULL +#define ak5386_soc_resume NULL +#endif /* CONFIG_PM */ + static struct snd_soc_codec_driver soc_codec_ak5386 = { + .probe = ak5386_soc_probe, + .remove = ak5386_soc_remove, + .suspend = ak5386_soc_suspend, + .resume = ak5386_soc_resume, .dapm_widgets = ak5386_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(ak5386_dapm_widgets), .dapm_routes = ak5386_dapm_routes, @@ -122,6 +163,7 @@ static int ak5386_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct ak5386_priv *priv; + int ret, i; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -130,6 +172,14 @@ static int ak5386_probe(struct platform_device *pdev) priv->reset_gpio = -EINVAL; dev_set_drvdata(dev, priv); + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + priv->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret < 0) + return ret; + if (of_match_device(of_match_ptr(ak5386_dt_ids), dev)) priv->reset_gpio = of_get_named_gpio(dev->of_node, "reset-gpio", 0); diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 29e198f57d4c..2f2e91ac690f 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -243,6 +243,31 @@ int arizona_init_spk(struct snd_soc_codec *codec) } EXPORT_SYMBOL_GPL(arizona_init_spk); +static const struct snd_soc_dapm_route arizona_mono_routes[] = { + { "OUT1R", NULL, "OUT1L" }, + { "OUT2R", NULL, "OUT2L" }, + { "OUT3R", NULL, "OUT3L" }, + { "OUT4R", NULL, "OUT4L" }, + { "OUT5R", NULL, "OUT5L" }, + { "OUT6R", NULL, "OUT6L" }, +}; + +int arizona_init_mono(struct snd_soc_codec *codec) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + int i; + + for (i = 0; i < ARIZONA_MAX_OUTPUT; ++i) { + if (arizona->pdata.out_mono[i]) + snd_soc_dapm_add_routes(&codec->dapm, + &arizona_mono_routes[i], 1); + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_mono); + int arizona_init_gpio(struct snd_soc_codec *codec) { struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); @@ -1127,6 +1152,31 @@ static int arizona_startup(struct snd_pcm_substream *substream, constraint); } +static void arizona_wm5102_set_dac_comp(struct snd_soc_codec *codec, + unsigned int rate) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + struct reg_default dac_comp[] = { + { 0x80, 0x3 }, + { ARIZONA_DAC_COMP_1, 0 }, + { ARIZONA_DAC_COMP_2, 0 }, + { 0x80, 0x0 }, + }; + + mutex_lock(&codec->mutex); + + dac_comp[1].def = arizona->dac_comp_coeff; + if (rate >= 176400) + dac_comp[2].def = arizona->dac_comp_enabled; + + mutex_unlock(&codec->mutex); + + regmap_multi_reg_write(arizona->regmap, + dac_comp, + ARRAY_SIZE(dac_comp)); +} + static int arizona_hw_params_rate(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -1153,6 +1203,15 @@ static int arizona_hw_params_rate(struct snd_pcm_substream *substream, switch (dai_priv->clk) { case ARIZONA_CLK_SYSCLK: + switch (priv->arizona->type) { + case WM5102: + arizona_wm5102_set_dac_comp(codec, + params_rate(params)); + break; + default: + break; + } + snd_soc_update_bits(codec, ARIZONA_SAMPLE_RATE_1, ARIZONA_SAMPLE_RATE_1_MASK, sr_val); if (base) @@ -1175,6 +1234,27 @@ static int arizona_hw_params_rate(struct snd_pcm_substream *substream, return 0; } +static bool arizona_aif_cfg_changed(struct snd_soc_codec *codec, + int base, int bclk, int lrclk, int frame) +{ + int val; + + val = snd_soc_read(codec, base + ARIZONA_AIF_BCLK_CTRL); + if (bclk != (val & ARIZONA_AIF1_BCLK_FREQ_MASK)) + return true; + + val = snd_soc_read(codec, base + ARIZONA_AIF_TX_BCLK_RATE); + if (lrclk != (val & ARIZONA_AIF1TX_BCPF_MASK)) + return true; + + val = snd_soc_read(codec, base + ARIZONA_AIF_FRAME_CTRL_1); + if (frame != (val & (ARIZONA_AIF1TX_WL_MASK | + ARIZONA_AIF1TX_SLOT_LEN_MASK))) + return true; + + return false; +} + static int arizona_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -1185,26 +1265,40 @@ static int arizona_hw_params(struct snd_pcm_substream *substream, int base = dai->driver->base; const int *rates; int i, ret, val; + int channels = params_channels(params); int chan_limit = arizona->pdata.max_channels_clocked[dai->id - 1]; + int tdm_width = arizona->tdm_width[dai->id - 1]; + int tdm_slots = arizona->tdm_slots[dai->id - 1]; int bclk, lrclk, wl, frame, bclk_target; + bool reconfig; + unsigned int aif_tx_state, aif_rx_state; if (params_rate(params) % 8000) rates = &arizona_44k1_bclk_rates[0]; else rates = &arizona_48k_bclk_rates[0]; - bclk_target = snd_soc_params_to_bclk(params); - if (chan_limit && chan_limit < params_channels(params)) { + if (tdm_slots) { + arizona_aif_dbg(dai, "Configuring for %d %d bit TDM slots\n", + tdm_slots, tdm_width); + bclk_target = tdm_slots * tdm_width * params_rate(params); + channels = tdm_slots; + } else { + bclk_target = snd_soc_params_to_bclk(params); + } + + if (chan_limit && chan_limit < channels) { arizona_aif_dbg(dai, "Limiting to %d channels\n", chan_limit); - bclk_target /= params_channels(params); + bclk_target /= channels; bclk_target *= chan_limit; } - /* Force stereo for I2S mode */ + /* Force multiple of 2 channels for I2S mode */ val = snd_soc_read(codec, base + ARIZONA_AIF_FORMAT); - if (params_channels(params) == 1 && (val & ARIZONA_AIF1_FMT_MASK)) { + if ((channels & 1) && (val & ARIZONA_AIF1_FMT_MASK)) { arizona_aif_dbg(dai, "Forcing stereo mode\n"); - bclk_target *= 2; + bclk_target /= channels; + bclk_target *= channels + 1; } for (i = 0; i < ARRAY_SIZE(arizona_44k1_bclk_rates); i++) { @@ -1228,28 +1322,56 @@ static int arizona_hw_params(struct snd_pcm_substream *substream, wl = snd_pcm_format_width(params_format(params)); frame = wl << ARIZONA_AIF1TX_WL_SHIFT | wl; + reconfig = arizona_aif_cfg_changed(codec, base, bclk, lrclk, frame); + + if (reconfig) { + /* Save AIF TX/RX state */ + aif_tx_state = snd_soc_read(codec, + base + ARIZONA_AIF_TX_ENABLES); + aif_rx_state = snd_soc_read(codec, + base + ARIZONA_AIF_RX_ENABLES); + /* Disable AIF TX/RX before reconfiguring it */ + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_TX_ENABLES, 0xff, 0x0); + regmap_update_bits(arizona->regmap, + base + ARIZONA_AIF_RX_ENABLES, 0xff, 0x0); + } + ret = arizona_hw_params_rate(substream, params, dai); if (ret != 0) - return ret; + goto restore_aif; - regmap_update_bits_async(arizona->regmap, - base + ARIZONA_AIF_BCLK_CTRL, - ARIZONA_AIF1_BCLK_FREQ_MASK, bclk); - regmap_update_bits_async(arizona->regmap, - base + ARIZONA_AIF_TX_BCLK_RATE, - ARIZONA_AIF1TX_BCPF_MASK, lrclk); - regmap_update_bits_async(arizona->regmap, - base + ARIZONA_AIF_RX_BCLK_RATE, - ARIZONA_AIF1RX_BCPF_MASK, lrclk); - regmap_update_bits_async(arizona->regmap, - base + ARIZONA_AIF_FRAME_CTRL_1, - ARIZONA_AIF1TX_WL_MASK | - ARIZONA_AIF1TX_SLOT_LEN_MASK, frame); - regmap_update_bits(arizona->regmap, base + ARIZONA_AIF_FRAME_CTRL_2, - ARIZONA_AIF1RX_WL_MASK | - ARIZONA_AIF1RX_SLOT_LEN_MASK, frame); + if (reconfig) { + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_BCLK_CTRL, + ARIZONA_AIF1_BCLK_FREQ_MASK, bclk); + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_TX_BCLK_RATE, + ARIZONA_AIF1TX_BCPF_MASK, lrclk); + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_RX_BCLK_RATE, + ARIZONA_AIF1RX_BCPF_MASK, lrclk); + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_FRAME_CTRL_1, + ARIZONA_AIF1TX_WL_MASK | + ARIZONA_AIF1TX_SLOT_LEN_MASK, frame); + regmap_update_bits(arizona->regmap, + base + ARIZONA_AIF_FRAME_CTRL_2, + ARIZONA_AIF1RX_WL_MASK | + ARIZONA_AIF1RX_SLOT_LEN_MASK, frame); + } - return 0; +restore_aif: + if (reconfig) { + /* Restore AIF TX/RX state */ + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_TX_ENABLES, + 0xff, aif_tx_state); + regmap_update_bits(arizona->regmap, + base + ARIZONA_AIF_RX_ENABLES, + 0xff, aif_rx_state); + } + return ret; } static const char *arizona_dai_clk_str(int clk_id) @@ -1324,9 +1446,63 @@ static int arizona_set_tristate(struct snd_soc_dai *dai, int tristate) ARIZONA_AIF1_TRI, reg); } +static void arizona_set_channels_to_mask(struct snd_soc_dai *dai, + unsigned int base, + int channels, unsigned int mask) +{ + struct snd_soc_codec *codec = dai->codec; + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + int slot, i; + + for (i = 0; i < channels; ++i) { + slot = ffs(mask) - 1; + if (slot < 0) + return; + + regmap_write(arizona->regmap, base + i, slot); + + mask &= ~(1 << slot); + } + + if (mask) + arizona_aif_warn(dai, "Too many channels in TDM mask\n"); +} + +static int arizona_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 arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + int base = dai->driver->base; + int rx_max_chan = dai->driver->playback.channels_max; + int tx_max_chan = dai->driver->capture.channels_max; + + /* Only support TDM for the physical AIFs */ + if (dai->id > ARIZONA_MAX_AIF) + return -ENOTSUPP; + + if (slots == 0) { + tx_mask = (1 << tx_max_chan) - 1; + rx_mask = (1 << rx_max_chan) - 1; + } + + arizona_set_channels_to_mask(dai, base + ARIZONA_AIF_FRAME_CTRL_3, + tx_max_chan, tx_mask); + arizona_set_channels_to_mask(dai, base + ARIZONA_AIF_FRAME_CTRL_11, + rx_max_chan, rx_mask); + + arizona->tdm_width[dai->id - 1] = slot_width; + arizona->tdm_slots[dai->id - 1] = slots; + + return 0; +} + const struct snd_soc_dai_ops arizona_dai_ops = { .startup = arizona_startup, .set_fmt = arizona_set_fmt, + .set_tdm_slot = arizona_set_tdm_slot, .hw_params = arizona_hw_params, .set_sysclk = arizona_dai_set_sysclk, .set_tristate = arizona_set_tristate, @@ -1400,6 +1576,12 @@ static int arizona_validate_fll(struct arizona_fll *fll, { unsigned int Fvco_min; + if (fll->fout && Fout != fll->fout) { + arizona_fll_err(fll, + "Can't change output on active FLL\n"); + return -EINVAL; + } + if (Fref / ARIZONA_FLL_MAX_REFDIV > ARIZONA_FLL_MAX_FREF) { arizona_fll_err(fll, "Can't scale %dMHz in to <=13.5MHz\n", @@ -1478,6 +1660,10 @@ static int arizona_calc_fratio(struct arizona_fll *fll, while (div <= ARIZONA_FLL_MAX_REFDIV) { for (ratio = init_ratio; ratio <= ARIZONA_FLL_MAX_FRATIO; ratio++) { + if ((ARIZONA_FLL_VCO_CORNER / 2) / + (fll->vco_mult * ratio) < Fref) + break; + if (target % (ratio * Fref)) { cfg->refdiv = refdiv; cfg->fratio = ratio - 1; @@ -1485,11 +1671,7 @@ static int arizona_calc_fratio(struct arizona_fll *fll, } } - for (ratio = init_ratio - 1; ratio >= 0; ratio--) { - if (ARIZONA_FLL_VCO_CORNER / (fll->vco_mult * ratio) < - Fref) - break; - + for (ratio = init_ratio - 1; ratio > 0; ratio--) { if (target % (ratio * Fref)) { cfg->refdiv = refdiv; cfg->fratio = ratio - 1; @@ -1616,7 +1798,7 @@ static void arizona_apply_fll(struct arizona *arizona, unsigned int base, ARIZONA_FLL1_CTRL_UPD | cfg->n); } -static bool arizona_is_enabled_fll(struct arizona_fll *fll) +static int arizona_is_enabled_fll(struct arizona_fll *fll) { struct arizona *arizona = fll->arizona; unsigned int reg; @@ -1632,13 +1814,26 @@ static bool arizona_is_enabled_fll(struct arizona_fll *fll) return reg & ARIZONA_FLL1_ENA; } -static void arizona_enable_fll(struct arizona_fll *fll) +static int arizona_enable_fll(struct arizona_fll *fll) { struct arizona *arizona = fll->arizona; int ret; bool use_sync = false; + int already_enabled = arizona_is_enabled_fll(fll); struct arizona_fll_cfg cfg; + if (already_enabled < 0) + return already_enabled; + + if (already_enabled) { + /* Facilitate smooth refclk across the transition */ + regmap_update_bits_async(fll->arizona->regmap, fll->base + 0x7, + ARIZONA_FLL1_GAIN_MASK, 0); + regmap_update_bits_async(fll->arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, + ARIZONA_FLL1_FREERUN); + } + /* * If we have both REFCLK and SYNCCLK then enable both, * otherwise apply the SYNCCLK settings to REFCLK. @@ -1666,7 +1861,7 @@ static void arizona_enable_fll(struct arizona_fll *fll) ARIZONA_FLL1_SYNC_ENA, 0); } else { arizona_fll_err(fll, "No clocks provided\n"); - return; + return -EINVAL; } /* @@ -1681,25 +1876,29 @@ static void arizona_enable_fll(struct arizona_fll *fll) ARIZONA_FLL1_SYNC_BW, ARIZONA_FLL1_SYNC_BW); - if (!arizona_is_enabled_fll(fll)) + if (!already_enabled) pm_runtime_get(arizona->dev); /* Clear any pending completions */ try_wait_for_completion(&fll->ok); regmap_update_bits_async(arizona->regmap, fll->base + 1, - ARIZONA_FLL1_FREERUN, 0); - regmap_update_bits_async(arizona->regmap, fll->base + 1, ARIZONA_FLL1_ENA, ARIZONA_FLL1_ENA); if (use_sync) regmap_update_bits_async(arizona->regmap, fll->base + 0x11, ARIZONA_FLL1_SYNC_ENA, ARIZONA_FLL1_SYNC_ENA); + if (already_enabled) + regmap_update_bits_async(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, 0); + ret = wait_for_completion_timeout(&fll->ok, msecs_to_jiffies(250)); if (ret == 0) arizona_fll_warn(fll, "Timed out waiting for lock\n"); + + return 0; } static void arizona_disable_fll(struct arizona_fll *fll) @@ -1713,6 +1912,8 @@ static void arizona_disable_fll(struct arizona_fll *fll) ARIZONA_FLL1_ENA, 0, &change); regmap_update_bits(arizona->regmap, fll->base + 0x11, ARIZONA_FLL1_SYNC_ENA, 0); + regmap_update_bits_async(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, 0); if (change) pm_runtime_put_autosuspend(arizona->dev); @@ -1721,7 +1922,7 @@ static void arizona_disable_fll(struct arizona_fll *fll) int arizona_set_fll_refclk(struct arizona_fll *fll, int source, unsigned int Fref, unsigned int Fout) { - int ret; + int ret = 0; if (fll->ref_src == source && fll->ref_freq == Fref) return 0; @@ -1736,17 +1937,17 @@ int arizona_set_fll_refclk(struct arizona_fll *fll, int source, fll->ref_freq = Fref; if (fll->fout && Fref > 0) { - arizona_enable_fll(fll); + ret = arizona_enable_fll(fll); } - return 0; + return ret; } EXPORT_SYMBOL_GPL(arizona_set_fll_refclk); int arizona_set_fll(struct arizona_fll *fll, int source, unsigned int Fref, unsigned int Fout) { - int ret; + int ret = 0; if (fll->sync_src == source && fll->sync_freq == Fref && fll->fout == Fout) @@ -1768,13 +1969,12 @@ int arizona_set_fll(struct arizona_fll *fll, int source, fll->sync_freq = Fref; fll->fout = Fout; - if (Fout) { - arizona_enable_fll(fll); - } else { + if (Fout) + ret = arizona_enable_fll(fll); + else arizona_disable_fll(fll); - } - return 0; + return ret; } EXPORT_SYMBOL_GPL(arizona_set_fll); diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index 05ae17f5bca3..942cfb197b6d 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -249,6 +249,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_dai(struct arizona_priv *priv, int dai); diff --git a/sound/soc/codecs/cs4265.c b/sound/soc/codecs/cs4265.c new file mode 100644 index 000000000000..a20b30ca52c0 --- /dev/null +++ b/sound/soc/codecs/cs4265.c @@ -0,0 +1,682 @@ +/* + * cs4265.c -- CS4265 ALSA SoC audio driver + * + * Copyright 2014 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/gpio/consumer.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/regmap.h> +#include <linux/slab.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 "cs4265.h" + +struct cs4265_private { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + u8 format; + u32 sysclk; +}; + +static const struct reg_default cs4265_reg_defaults[] = { + { CS4265_PWRCTL, 0x0F }, + { CS4265_DAC_CTL, 0x08 }, + { CS4265_ADC_CTL, 0x00 }, + { CS4265_MCLK_FREQ, 0x00 }, + { CS4265_SIG_SEL, 0x40 }, + { CS4265_CHB_PGA_CTL, 0x00 }, + { CS4265_CHA_PGA_CTL, 0x00 }, + { CS4265_ADC_CTL2, 0x19 }, + { CS4265_DAC_CHA_VOL, 0x00 }, + { CS4265_DAC_CHB_VOL, 0x00 }, + { CS4265_DAC_CTL2, 0xC0 }, + { CS4265_SPDIF_CTL1, 0x00 }, + { CS4265_SPDIF_CTL2, 0x00 }, + { CS4265_INT_MASK, 0x00 }, + { CS4265_STATUS_MODE_MSB, 0x00 }, + { CS4265_STATUS_MODE_LSB, 0x00 }, +}; + +static bool cs4265_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS4265_PWRCTL: + case CS4265_DAC_CTL: + case CS4265_ADC_CTL: + case CS4265_MCLK_FREQ: + case CS4265_SIG_SEL: + case CS4265_CHB_PGA_CTL: + case CS4265_CHA_PGA_CTL: + case CS4265_ADC_CTL2: + case CS4265_DAC_CHA_VOL: + case CS4265_DAC_CHB_VOL: + case CS4265_DAC_CTL2: + case CS4265_SPDIF_CTL1: + case CS4265_SPDIF_CTL2: + case CS4265_INT_MASK: + case CS4265_STATUS_MODE_MSB: + case CS4265_STATUS_MODE_LSB: + return true; + default: + return false; + } +} + +static bool cs4265_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS4265_INT_STATUS: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(pga_tlv, -1200, 50, 0); + +static DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 0); + +static const char * const digital_input_mux_text[] = { + "SDIN1", "SDIN2" +}; + +static SOC_ENUM_SINGLE_DECL(digital_input_mux_enum, CS4265_SIG_SEL, 7, + digital_input_mux_text); + +static const struct snd_kcontrol_new digital_input_mux = + SOC_DAPM_ENUM("Digital Input Mux", digital_input_mux_enum); + +static const char * const mic_linein_text[] = { + "MIC", "LINEIN" +}; + +static SOC_ENUM_SINGLE_DECL(mic_linein_enum, CS4265_ADC_CTL2, 0, + mic_linein_text); + +static const char * const cam_mode_text[] = { + "One Byte", "Two Byte" +}; + +static SOC_ENUM_SINGLE_DECL(cam_mode_enum, CS4265_SPDIF_CTL1, 5, + cam_mode_text); + +static const char * const cam_mono_stereo_text[] = { + "Stereo", "Mono" +}; + +static SOC_ENUM_SINGLE_DECL(spdif_mono_stereo_enum, CS4265_SPDIF_CTL2, 2, + cam_mono_stereo_text); + +static const char * const mono_select_text[] = { + "Channel A", "Channel B" +}; + +static SOC_ENUM_SINGLE_DECL(spdif_mono_select_enum, CS4265_SPDIF_CTL2, 0, + mono_select_text); + +static const struct snd_kcontrol_new mic_linein_mux = + SOC_DAPM_ENUM("ADC Input Capture Mux", mic_linein_enum); + +static const struct snd_kcontrol_new loopback_ctl = + SOC_DAPM_SINGLE("Switch", CS4265_SIG_SEL, 1, 1, 0); + +static const struct snd_kcontrol_new spdif_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 0, 0); + +static const struct snd_kcontrol_new dac_switch = + SOC_DAPM_SINGLE("Switch", CS4265_PWRCTL, 1, 1, 0); + +static const struct snd_kcontrol_new cs4265_snd_controls[] = { + + SOC_DOUBLE_R_SX_TLV("PGA Volume", CS4265_CHA_PGA_CTL, + CS4265_CHB_PGA_CTL, 0, 0x28, 0x30, pga_tlv), + SOC_DOUBLE_R_TLV("DAC Volume", CS4265_DAC_CHA_VOL, + CS4265_DAC_CHB_VOL, 0, 0xFF, 1, dac_tlv), + SOC_SINGLE("De-emp 44.1kHz Switch", CS4265_DAC_CTL, 1, + 1, 0), + SOC_SINGLE("DAC INV Switch", CS4265_DAC_CTL2, 5, + 1, 0), + SOC_SINGLE("DAC Zero Cross Switch", CS4265_DAC_CTL2, 6, + 1, 0), + SOC_SINGLE("DAC Soft Ramp Switch", CS4265_DAC_CTL2, 7, + 1, 0), + SOC_SINGLE("ADC HPF Switch", CS4265_ADC_CTL, 1, + 1, 0), + SOC_SINGLE("ADC Zero Cross Switch", CS4265_ADC_CTL2, 3, + 1, 1), + SOC_SINGLE("ADC Soft Ramp Switch", CS4265_ADC_CTL2, 7, + 1, 0), + SOC_SINGLE("E to F Buffer Disable Switch", CS4265_SPDIF_CTL1, + 6, 1, 0), + SOC_ENUM("C Data Access", cam_mode_enum), + SOC_SINGLE("Validity Bit Control Switch", CS4265_SPDIF_CTL2, + 3, 1, 0), + SOC_ENUM("SPDIF Mono/Stereo", spdif_mono_stereo_enum), + SOC_SINGLE("MMTLR Data Switch", 0, + 1, 1, 0), + SOC_ENUM("Mono Channel Select", spdif_mono_select_enum), + SND_SOC_BYTES("C Data Buffer", CS4265_C_DATA_BUFF, 24), +}; + +static const struct snd_soc_dapm_widget cs4265_dapm_widgets[] = { + + SND_SOC_DAPM_INPUT("LINEINL"), + SND_SOC_DAPM_INPUT("LINEINR"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + + SND_SOC_DAPM_AIF_OUT("DOUT", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SPDIFOUT", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("ADC Mux", SND_SOC_NOPM, 0, 0, &mic_linein_mux), + + SND_SOC_DAPM_ADC("ADC", NULL, CS4265_PWRCTL, 2, 1), + SND_SOC_DAPM_PGA("Pre-amp MIC", CS4265_PWRCTL, 3, + 1, NULL, 0), + + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, + 0, 0, &digital_input_mux), + + SND_SOC_DAPM_MIXER("SDIN1 Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("SDIN2 Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("SPDIF Transmitter", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("Loopback", SND_SOC_NOPM, 0, 0, + &loopback_ctl), + SND_SOC_DAPM_SWITCH("SPDIF", SND_SOC_NOPM, 0, 0, + &spdif_switch), + SND_SOC_DAPM_SWITCH("DAC", CS4265_PWRCTL, 1, 1, + &dac_switch), + + SND_SOC_DAPM_AIF_IN("DIN1", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DIN2", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TXIN", NULL, 0, + CS4265_SPDIF_CTL2, 5, 1), + + SND_SOC_DAPM_OUTPUT("LINEOUTL"), + SND_SOC_DAPM_OUTPUT("LINEOUTR"), + +}; + +static const struct snd_soc_dapm_route cs4265_audio_map[] = { + + {"DIN1", NULL, "DAI1 Playback"}, + {"DIN2", NULL, "DAI2 Playback"}, + {"SDIN1 Input Mixer", NULL, "DIN1"}, + {"SDIN2 Input Mixer", NULL, "DIN2"}, + {"Input Mux", "SDIN1", "SDIN1 Input Mixer"}, + {"Input Mux", "SDIN2", "SDIN2 Input Mixer"}, + {"DAC", "Switch", "Input Mux"}, + {"SPDIF", "Switch", "Input Mux"}, + {"LINEOUTL", NULL, "DAC"}, + {"LINEOUTR", NULL, "DAC"}, + {"SPDIFOUT", NULL, "SPDIF"}, + + {"ADC Mux", "LINEIN", "LINEINL"}, + {"ADC Mux", "LINEIN", "LINEINR"}, + {"ADC Mux", "MIC", "MICL"}, + {"ADC Mux", "MIC", "MICR"}, + {"ADC", NULL, "ADC Mux"}, + {"DOUT", NULL, "ADC"}, + {"DAI1 Capture", NULL, "DOUT"}, + {"DAI2 Capture", NULL, "DOUT"}, + + /* Loopback */ + {"Loopback", "Switch", "ADC"}, + {"DAC", NULL, "Loopback"}, +}; + +struct cs4265_clk_para { + u32 mclk; + u32 rate; + u8 fm_mode; /* values 1, 2, or 4 */ + u8 mclkdiv; +}; + +static const struct cs4265_clk_para clk_map_table[] = { + /*32k*/ + {8192000, 32000, 0, 0}, + {12288000, 32000, 0, 1}, + {16384000, 32000, 0, 2}, + {24576000, 32000, 0, 3}, + {32768000, 32000, 0, 4}, + + /*44.1k*/ + {11289600, 44100, 0, 0}, + {16934400, 44100, 0, 1}, + {22579200, 44100, 0, 2}, + {33868000, 44100, 0, 3}, + {45158400, 44100, 0, 4}, + + /*48k*/ + {12288000, 48000, 0, 0}, + {18432000, 48000, 0, 1}, + {24576000, 48000, 0, 2}, + {36864000, 48000, 0, 3}, + {49152000, 48000, 0, 4}, + + /*64k*/ + {8192000, 64000, 1, 0}, + {1228800, 64000, 1, 1}, + {1693440, 64000, 1, 2}, + {2457600, 64000, 1, 3}, + {3276800, 64000, 1, 4}, + + /* 88.2k */ + {11289600, 88200, 1, 0}, + {16934400, 88200, 1, 1}, + {22579200, 88200, 1, 2}, + {33868000, 88200, 1, 3}, + {45158400, 88200, 1, 4}, + + /* 96k */ + {12288000, 96000, 1, 0}, + {18432000, 96000, 1, 1}, + {24576000, 96000, 1, 2}, + {36864000, 96000, 1, 3}, + {49152000, 96000, 1, 4}, + + /* 128k */ + {8192000, 128000, 2, 0}, + {12288000, 128000, 2, 1}, + {16934400, 128000, 2, 2}, + {24576000, 128000, 2, 3}, + {32768000, 128000, 2, 4}, + + /* 176.4k */ + {11289600, 176400, 2, 0}, + {16934400, 176400, 2, 1}, + {22579200, 176400, 2, 2}, + {33868000, 176400, 2, 3}, + {49152000, 176400, 2, 4}, + + /* 192k */ + {12288000, 192000, 2, 0}, + {18432000, 192000, 2, 1}, + {24576000, 192000, 2, 2}, + {36864000, 192000, 2, 3}, + {49152000, 192000, 2, 4}, +}; + +static int cs4265_get_clk_index(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(clk_map_table); i++) { + if (clk_map_table[i].rate == rate && + clk_map_table[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int cs4265_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs4265_private *cs4265 = snd_soc_codec_get_drvdata(codec); + int i; + + if (clk_id != 0) { + dev_err(codec->dev, "Invalid clk_id %d\n", clk_id); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(clk_map_table); i++) { + if (clk_map_table[i].mclk == freq) { + cs4265->sysclk = freq; + return 0; + } + } + cs4265->sysclk = 0; + dev_err(codec->dev, "Invalid freq parameter %d\n", freq); + return -EINVAL; +} + +static int cs4265_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs4265_private *cs4265 = snd_soc_codec_get_drvdata(codec); + u8 iface = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_ADC_MASTER, + CS4265_ADC_MASTER); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_ADC_MASTER, + 0); + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= SND_SOC_DAIFMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface |= SND_SOC_DAIFMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= SND_SOC_DAIFMT_LEFT_J; + break; + default: + return -EINVAL; + } + + cs4265->format = iface; + return 0; +} + +static int cs4265_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + if (mute) { + snd_soc_update_bits(codec, CS4265_DAC_CTL, + CS4265_DAC_CTL_MUTE, + CS4265_DAC_CTL_MUTE); + snd_soc_update_bits(codec, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_MUTE, + CS4265_SPDIF_CTL2_MUTE); + } else { + snd_soc_update_bits(codec, CS4265_DAC_CTL, + CS4265_DAC_CTL_MUTE, + 0); + snd_soc_update_bits(codec, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_MUTE, + 0); + } + return 0; +} + +static int cs4265_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 cs4265_private *cs4265 = snd_soc_codec_get_drvdata(codec); + int index; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + ((cs4265->format & SND_SOC_DAIFMT_FORMAT_MASK) + == SND_SOC_DAIFMT_RIGHT_J)) + return -EINVAL; + + index = cs4265_get_clk_index(cs4265->sysclk, params_rate(params)); + if (index >= 0) { + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_ADC_FM, clk_map_table[index].fm_mode); + snd_soc_update_bits(codec, CS4265_MCLK_FREQ, + CS4265_MCLK_FREQ_MASK, + clk_map_table[index].mclkdiv); + + } else { + dev_err(codec->dev, "can't get correct mclk\n"); + return -EINVAL; + } + + switch (cs4265->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_soc_update_bits(codec, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, (1 << 4)); + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_ADC_DIF, (1 << 4)); + snd_soc_update_bits(codec, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_DIF, (1 << 6)); + break; + case SND_SOC_DAIFMT_RIGHT_J: + if (params_width(params) == 16) { + snd_soc_update_bits(codec, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, (1 << 5)); + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_SPDIF_CTL2_DIF, (1 << 7)); + } else { + snd_soc_update_bits(codec, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, (3 << 5)); + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_SPDIF_CTL2_DIF, (1 << 7)); + } + break; + case SND_SOC_DAIFMT_LEFT_J: + snd_soc_update_bits(codec, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, 0); + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_ADC_DIF, 0); + snd_soc_update_bits(codec, CS4265_ADC_CTL, + CS4265_SPDIF_CTL2_DIF, (1 << 6)); + + break; + default: + return -EINVAL; + } + return 0; +} + +static int cs4265_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + snd_soc_update_bits(codec, CS4265_PWRCTL, + CS4265_PWRCTL_PDN, 0); + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_update_bits(codec, CS4265_PWRCTL, + CS4265_PWRCTL_PDN, + CS4265_PWRCTL_PDN); + break; + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, CS4265_PWRCTL, + CS4265_PWRCTL_PDN, + CS4265_PWRCTL_PDN); + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define CS4265_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define CS4265_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE) + +static const struct snd_soc_dai_ops cs4265_ops = { + .hw_params = cs4265_pcm_hw_params, + .digital_mute = cs4265_digital_mute, + .set_fmt = cs4265_set_fmt, + .set_sysclk = cs4265_set_sysclk, +}; + +static struct snd_soc_dai_driver cs4265_dai[] = { + { + .name = "cs4265-dai1", + .playback = { + .stream_name = "DAI1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .capture = { + .stream_name = "DAI1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .ops = &cs4265_ops, + }, + { + .name = "cs4265-dai2", + .playback = { + .stream_name = "DAI2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .capture = { + .stream_name = "DAI2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .ops = &cs4265_ops, + }, +}; + +static const struct snd_soc_codec_driver soc_codec_cs4265 = { + .set_bias_level = cs4265_set_bias_level, + + .dapm_widgets = cs4265_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs4265_dapm_widgets), + .dapm_routes = cs4265_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs4265_audio_map), + + .controls = cs4265_snd_controls, + .num_controls = ARRAY_SIZE(cs4265_snd_controls), +}; + +static const struct regmap_config cs4265_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS4265_MAX_REGISTER, + .reg_defaults = cs4265_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs4265_reg_defaults), + .readable_reg = cs4265_readable_register, + .volatile_reg = cs4265_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs4265_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs4265_private *cs4265; + int ret = 0; + unsigned int devid = 0; + unsigned int reg; + + cs4265 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs4265_private), + GFP_KERNEL); + if (cs4265 == NULL) + return -ENOMEM; + cs4265->dev = &i2c_client->dev; + + cs4265->regmap = devm_regmap_init_i2c(i2c_client, &cs4265_regmap); + if (IS_ERR(cs4265->regmap)) { + ret = PTR_ERR(cs4265->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + cs4265->reset_gpio = devm_gpiod_get(&i2c_client->dev, + "reset-gpios"); + if (IS_ERR(cs4265->reset_gpio)) { + ret = PTR_ERR(cs4265->reset_gpio); + if (ret != -ENOENT && ret != -ENOSYS) + return ret; + + cs4265->reset_gpio = NULL; + } else { + ret = gpiod_direction_output(cs4265->reset_gpio, 0); + if (ret) + return ret; + mdelay(1); + gpiod_set_value_cansleep(cs4265->reset_gpio, 1); + + } + + i2c_set_clientdata(i2c_client, cs4265); + + ret = regmap_read(cs4265->regmap, CS4265_CHIP_ID, ®); + devid = reg & CS4265_CHIP_ID_MASK; + if (devid != CS4265_CHIP_ID_VAL) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS4265 Device ID (%X). Expected %X\n", + devid, CS4265_CHIP_ID); + return ret; + } + dev_info(&i2c_client->dev, + "CS4265 Version %x\n", + reg & CS4265_REV_ID_MASK); + + regmap_write(cs4265->regmap, CS4265_PWRCTL, 0x0F); + + ret = snd_soc_register_codec(&i2c_client->dev, + &soc_codec_cs4265, cs4265_dai, + ARRAY_SIZE(cs4265_dai)); + return ret; +} + +static int cs4265_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct of_device_id cs4265_of_match[] = { + { .compatible = "cirrus,cs4265", }, + { } +}; +MODULE_DEVICE_TABLE(of, cs4265_of_match); + +static const struct i2c_device_id cs4265_id[] = { + { "cs4265", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs4265_id); + +static struct i2c_driver cs4265_i2c_driver = { + .driver = { + .name = "cs4265", + .owner = THIS_MODULE, + .of_match_table = cs4265_of_match, + }, + .id_table = cs4265_id, + .probe = cs4265_i2c_probe, + .remove = cs4265_i2c_remove, +}; + +module_i2c_driver(cs4265_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS4265 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, <paul.handrigan@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4265.h b/sound/soc/codecs/cs4265.h new file mode 100644 index 000000000000..0a80a8dcec67 --- /dev/null +++ b/sound/soc/codecs/cs4265.h @@ -0,0 +1,64 @@ +/* + * cs4265.h -- CS4265 ALSA SoC audio driver + * + * Copyright 2014 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 __CS4265_H__ +#define __CS4265_H__ + +#define CS4265_CHIP_ID 0x1 +#define CS4265_CHIP_ID_VAL 0xD0 +#define CS4265_CHIP_ID_MASK 0xF0 +#define CS4265_REV_ID_MASK 0x0F + +#define CS4265_PWRCTL 0x02 +#define CS4265_PWRCTL_PDN 1 + +#define CS4265_DAC_CTL 0x3 +#define CS4265_DAC_CTL_MUTE (1 << 2) +#define CS4265_DAC_CTL_DIF (3 << 4) + +#define CS4265_ADC_CTL 0x4 +#define CS4265_ADC_MASTER 1 +#define CS4265_ADC_DIF (1 << 4) +#define CS4265_ADC_FM (3 << 6) + +#define CS4265_MCLK_FREQ 0x5 +#define CS4265_MCLK_FREQ_MASK (7 << 4) + +#define CS4265_SIG_SEL 0x6 +#define CS4265_SIG_SEL_LOOP (1 << 1) + +#define CS4265_CHB_PGA_CTL 0x7 +#define CS4265_CHA_PGA_CTL 0x8 + +#define CS4265_ADC_CTL2 0x9 + +#define CS4265_DAC_CHA_VOL 0xA +#define CS4265_DAC_CHB_VOL 0xB + +#define CS4265_DAC_CTL2 0xC + +#define CS4265_INT_STATUS 0xD +#define CS4265_INT_MASK 0xE +#define CS4265_STATUS_MODE_MSB 0xF +#define CS4265_STATUS_MODE_LSB 0x10 + +#define CS4265_SPDIF_CTL1 0x11 + +#define CS4265_SPDIF_CTL2 0x12 +#define CS4265_SPDIF_CTL2_MUTE (1 << 4) +#define CS4265_SPDIF_CTL2_DIF (3 << 6) + +#define CS4265_C_DATA_BUFF 0x13 +#define CS4265_MAX_REGISTER 0x2A + +#endif diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 9947a9583679..e6d4ff9fd992 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -664,10 +664,8 @@ static int cs4270_i2c_probe(struct i2c_client *i2c_client, cs4270 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs4270_private), GFP_KERNEL); - if (!cs4270) { - dev_err(&i2c_client->dev, "could not allocate codec\n"); + if (!cs4270) return -ENOMEM; - } /* get the power supply regulators */ for (i = 0; i < ARRAY_SIZE(supply_names); i++) diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c index 071fc77f2f06..969167d8b71e 100644 --- a/sound/soc/codecs/cs42l52.c +++ b/sound/soc/codecs/cs42l52.c @@ -399,15 +399,15 @@ static const struct snd_kcontrol_new cs42l52_snd_controls[] = { CS42L52_MASTERB_VOL, 0, 0x34, 0xE4, hl_tlv), SOC_DOUBLE_R_SX_TLV("Headphone Volume", CS42L52_HPA_VOL, - CS42L52_HPB_VOL, 0, 0x34, 0xCC, hpd_tlv), + CS42L52_HPB_VOL, 0, 0x34, 0xC0, hpd_tlv), SOC_ENUM("Headphone Analog Gain", hp_gain_enum), SOC_DOUBLE_R_SX_TLV("Speaker Volume", CS42L52_SPKA_VOL, - CS42L52_SPKB_VOL, 0, 0x1, 0xff, hl_tlv), + CS42L52_SPKB_VOL, 0, 0x40, 0xC0, hl_tlv), SOC_DOUBLE_R_SX_TLV("Bypass Volume", CS42L52_PASSTHRUA_VOL, - CS42L52_PASSTHRUB_VOL, 6, 0x18, 0x90, pga_tlv), + CS42L52_PASSTHRUB_VOL, 0, 0x88, 0x90, pga_tlv), SOC_DOUBLE("Bypass Mute", CS42L52_MISC_CTL, 4, 5, 1, 0), @@ -417,10 +417,10 @@ static const struct snd_kcontrol_new cs42l52_snd_controls[] = { SOC_ENUM("MIC Bias Level", mic_bias_level_enum), SOC_DOUBLE_R_SX_TLV("ADC Volume", CS42L52_ADCA_VOL, - CS42L52_ADCB_VOL, 7, 0x80, 0xA0, ipd_tlv), + CS42L52_ADCB_VOL, 0, 0xA0, 0x78, ipd_tlv), SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", CS42L52_ADCA_MIXER_VOL, CS42L52_ADCB_MIXER_VOL, - 6, 0x7f, 0x19, ipd_tlv), + 0, 0x19, 0x7F, ipd_tlv), SOC_DOUBLE("ADC Switch", CS42L52_ADC_MISC_CTL, 0, 1, 1, 0), @@ -428,11 +428,11 @@ static const struct snd_kcontrol_new cs42l52_snd_controls[] = { CS42L52_ADCB_MIXER_VOL, 7, 1, 1), SOC_DOUBLE_R_SX_TLV("PGA Volume", CS42L52_PGAA_CTL, - CS42L52_PGAB_CTL, 0, 0x28, 0x30, pga_tlv), + CS42L52_PGAB_CTL, 0, 0x28, 0x24, pga_tlv), SOC_DOUBLE_R_SX_TLV("PCM Mixer Volume", CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, - 0, 0x7f, 0x19, mix_tlv), + 0, 0x19, 0x7f, mix_tlv), SOC_DOUBLE_R("PCM Mixer Switch", CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, 7, 1, 1), diff --git a/sound/soc/codecs/cs42l56.c b/sound/soc/codecs/cs42l56.c index fdc4bd27b0df..c766a5a9ce80 100644 --- a/sound/soc/codecs/cs42l56.c +++ b/sound/soc/codecs/cs42l56.c @@ -318,24 +318,32 @@ static const struct soc_enum adca_swap_enum = ARRAY_SIZE(left_swap_text), left_swap_text, swap_values); +static const struct snd_kcontrol_new adca_swap_mux = + SOC_DAPM_ENUM("Route", adca_swap_enum); static const struct soc_enum pcma_swap_enum = SOC_VALUE_ENUM_SINGLE(CS42L56_CHAN_MIX_SWAP, 4, 3, ARRAY_SIZE(left_swap_text), left_swap_text, swap_values); +static const struct snd_kcontrol_new pcma_swap_mux = + SOC_DAPM_ENUM("Route", pcma_swap_enum); static const struct soc_enum adcb_swap_enum = SOC_VALUE_ENUM_SINGLE(CS42L56_CHAN_MIX_SWAP, 2, 3, ARRAY_SIZE(right_swap_text), right_swap_text, swap_values); +static const struct snd_kcontrol_new adcb_swap_mux = + SOC_DAPM_ENUM("Route", adcb_swap_enum); static const struct soc_enum pcmb_swap_enum = SOC_VALUE_ENUM_SINGLE(CS42L56_CHAN_MIX_SWAP, 6, 3, ARRAY_SIZE(right_swap_text), right_swap_text, swap_values); +static const struct snd_kcontrol_new pcmb_swap_mux = + SOC_DAPM_ENUM("Route", pcmb_swap_enum); static const struct snd_kcontrol_new hpa_switch = SOC_DAPM_SINGLE("Switch", CS42L56_PWRCTL_2, 6, 1, 1); @@ -421,15 +429,15 @@ static const struct soc_enum ng_delay_enum = static const struct snd_kcontrol_new cs42l56_snd_controls[] = { SOC_DOUBLE_R_SX_TLV("Master Volume", CS42L56_MASTER_A_VOLUME, - CS42L56_MASTER_B_VOLUME, 0, 0x34, 0xfd, adv_tlv), + CS42L56_MASTER_B_VOLUME, 0, 0x34, 0xE4, adv_tlv), SOC_DOUBLE("Master Mute Switch", CS42L56_DSP_MUTE_CTL, 0, 1, 1, 1), SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", CS42L56_ADCA_MIX_VOLUME, - CS42L56_ADCB_MIX_VOLUME, 0, 0x88, 0xa9, hl_tlv), + CS42L56_ADCB_MIX_VOLUME, 0, 0x88, 0x90, hl_tlv), SOC_DOUBLE("ADC Mixer Mute Switch", CS42L56_DSP_MUTE_CTL, 6, 7, 1, 1), SOC_DOUBLE_R_SX_TLV("PCM Mixer Volume", CS42L56_PCMA_MIX_VOLUME, - CS42L56_PCMB_MIX_VOLUME, 0, 0x88, 0xa9, hl_tlv), + CS42L56_PCMB_MIX_VOLUME, 0, 0x88, 0x90, hl_tlv), SOC_DOUBLE("PCM Mixer Mute Switch", CS42L56_DSP_MUTE_CTL, 4, 5, 1, 1), SOC_SINGLE_TLV("Analog Advisory Volume", @@ -438,16 +446,16 @@ static const struct snd_kcontrol_new cs42l56_snd_controls[] = { CS42L56_DIGINPUT_ADV_VOLUME, 0, 0x00, 1, adv_tlv), SOC_DOUBLE_R_SX_TLV("PGA Volume", CS42L56_PGAA_MUX_VOLUME, - CS42L56_PGAB_MUX_VOLUME, 0, 0x34, 0xfd, pga_tlv), + CS42L56_PGAB_MUX_VOLUME, 0, 0x34, 0x24, pga_tlv), SOC_DOUBLE_R_TLV("ADC Volume", CS42L56_ADCA_ATTENUATOR, CS42L56_ADCB_ATTENUATOR, 0, 0x00, 1, adc_tlv), SOC_DOUBLE("ADC Mute Switch", CS42L56_MISC_ADC_CTL, 2, 3, 1, 1), SOC_DOUBLE("ADC Boost Switch", CS42L56_GAIN_BIAS_CTL, 3, 2, 1, 1), SOC_DOUBLE_R_SX_TLV("Headphone Volume", CS42L56_HPA_VOLUME, - CS42L56_HPA_VOLUME, 0, 0x44, 0x55, hl_tlv), + CS42L56_HPB_VOLUME, 0, 0x84, 0x48, hl_tlv), SOC_DOUBLE_R_SX_TLV("LineOut Volume", CS42L56_LOA_VOLUME, - CS42L56_LOA_VOLUME, 0, 0x44, 0x55, hl_tlv), + CS42L56_LOB_VOLUME, 0, 0x84, 0x48, hl_tlv), SOC_SINGLE_TLV("Bass Shelving Volume", CS42L56_TONE_CTL, 0, 0x00, 1, tone_tlv), @@ -467,11 +475,6 @@ static const struct snd_kcontrol_new cs42l56_snd_controls[] = { SOC_SINGLE("ADCA Invert", CS42L56_MISC_ADC_CTL, 2, 1, 1), SOC_SINGLE("ADCB Invert", CS42L56_MISC_ADC_CTL, 3, 1, 1), - SOC_ENUM("PCMA Swap", pcma_swap_enum), - SOC_ENUM("PCMB Swap", pcmb_swap_enum), - SOC_ENUM("ADCA Swap", adca_swap_enum), - SOC_ENUM("ADCB Swap", adcb_swap_enum), - SOC_DOUBLE("HPF Switch", CS42L56_HPF_CTL, 5, 7, 1, 1), SOC_DOUBLE("HPF Freeze Switch", CS42L56_HPF_CTL, 4, 6, 1, 1), SOC_ENUM("HPFA Corner Freq", hpfa_freq_enum), @@ -570,6 +573,16 @@ static const struct snd_soc_dapm_widget cs42l56_dapm_widgets[] = { SND_SOC_DAPM_ADC("ADCA", NULL, CS42L56_PWRCTL_1, 1, 1), SND_SOC_DAPM_ADC("ADCB", NULL, CS42L56_PWRCTL_1, 2, 1), + SND_SOC_DAPM_MUX("ADCA Swap Mux", SND_SOC_NOPM, 0, 0, + &adca_swap_mux), + SND_SOC_DAPM_MUX("ADCB Swap Mux", SND_SOC_NOPM, 0, 0, + &adcb_swap_mux), + + SND_SOC_DAPM_MUX("PCMA Swap Mux", SND_SOC_NOPM, 0, 0, + &pcma_swap_mux), + SND_SOC_DAPM_MUX("PCMB Swap Mux", SND_SOC_NOPM, 0, 0, + &pcmb_swap_mux), + SND_SOC_DAPM_DAC("DACA", NULL, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DACB", NULL, SND_SOC_NOPM, 0, 0), @@ -607,8 +620,19 @@ static const struct snd_soc_dapm_route cs42l56_audio_map[] = { {"Digital Output Mux", NULL, "ADCA"}, {"Digital Output Mux", NULL, "ADCB"}, - {"ADCB", NULL, "ADCB Mux"}, - {"ADCA", NULL, "ADCA Mux"}, + {"ADCB", NULL, "ADCB Swap Mux"}, + {"ADCA", NULL, "ADCA Swap Mux"}, + + {"ADCA Swap Mux", NULL, "ADCA"}, + {"ADCB Swap Mux", NULL, "ADCB"}, + + {"DACA", "Left", "ADCA Swap Mux"}, + {"DACA", "LR 2", "ADCA Swap Mux"}, + {"DACA", "Right", "ADCA Swap Mux"}, + + {"DACB", "Left", "ADCB Swap Mux"}, + {"DACB", "LR 2", "ADCB Swap Mux"}, + {"DACB", "Right", "ADCB Swap Mux"}, {"ADCA Mux", NULL, "AIN3A"}, {"ADCA Mux", NULL, "AIN2A"}, @@ -633,30 +657,32 @@ static const struct snd_soc_dapm_route cs42l56_audio_map[] = { {"PGAB Input Mux", NULL, "AIN2B"}, {"PGAB Input Mux", NULL, "AIN3B"}, - {"LOB", NULL, "Lineout Right"}, - {"LOA", NULL, "Lineout Left"}, - - {"Lineout Right", "Switch", "LINEOUTB Input Mux"}, - {"Lineout Left", "Switch", "LINEOUTA Input Mux"}, + {"LOB", "Switch", "LINEOUTB Input Mux"}, + {"LOA", "Switch", "LINEOUTA Input Mux"}, {"LINEOUTA Input Mux", "PGAA", "PGAA"}, {"LINEOUTB Input Mux", "PGAB", "PGAB"}, {"LINEOUTA Input Mux", "DACA", "DACA"}, {"LINEOUTB Input Mux", "DACB", "DACB"}, - {"HPA", NULL, "Headphone Left"}, - {"HPB", NULL, "Headphone Right"}, - - {"Headphone Right", "Switch", "HPB Input Mux"}, - {"Headphone Left", "Switch", "HPA Input Mux"}, + {"HPA", "Switch", "HPB Input Mux"}, + {"HPB", "Switch", "HPA Input Mux"}, {"HPA Input Mux", "PGAA", "PGAA"}, {"HPB Input Mux", "PGAB", "PGAB"}, {"HPA Input Mux", "DACA", "DACA"}, {"HPB Input Mux", "DACB", "DACB"}, - {"DACB", NULL, "HiFi Playback"}, - {"DACA", NULL, "HiFi Playback"}, + {"DACA", NULL, "PCMA Swap Mux"}, + {"DACB", NULL, "PCMB Swap Mux"}, + + {"PCMB Swap Mux", "Left", "HiFi Playback"}, + {"PCMB Swap Mux", "LR 2", "HiFi Playback"}, + {"PCMB Swap Mux", "Right", "HiFi Playback"}, + + {"PCMA Swap Mux", "Left", "HiFi Playback"}, + {"PCMA Swap Mux", "LR 2", "HiFi Playback"}, + {"PCMA Swap Mux", "Right", "HiFi Playback"}, }; diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index ae3717992d56..0e7b9eb2ba61 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -401,7 +401,7 @@ static const struct snd_kcontrol_new cs42l73_snd_controls[] = { CS42L73_LOBAVOL, 0, 0x41, 0x4B, hpaloa_tlv), SOC_DOUBLE_R_SX_TLV("Input PGA Analog Volume", CS42L73_MICAPREPGAAVOL, - CS42L73_MICBPREPGABVOL, 5, 0x34, + CS42L73_MICBPREPGABVOL, 0, 0x34, 0x24, micpga_tlv), SOC_DOUBLE_R("MIC Preamp Switch", CS42L73_MICAPREPGAAVOL, @@ -1408,10 +1408,8 @@ static int cs42l73_i2c_probe(struct i2c_client *i2c_client, cs42l73 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs42l73_private), GFP_KERNEL); - if (!cs42l73) { - dev_err(&i2c_client->dev, "could not allocate codec\n"); + if (!cs42l73) return -ENOMEM; - } cs42l73->regmap = devm_regmap_init_i2c(i2c_client, &cs42l73_regmap); if (IS_ERR(cs42l73->regmap)) { diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index a25bc6061a30..02b1520ae0bc 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -219,6 +219,9 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, case SND_SOC_DAIFMT_RIGHT_J: val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ; break; + case SND_SOC_DAIFMT_DSP_A: + val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM; + break; default: dev_err(codec->dev, "unsupported dai format\n"); return -EINVAL; @@ -422,7 +425,7 @@ const struct cs42xx8_driver_data cs42888_data = { }; EXPORT_SYMBOL_GPL(cs42888_data); -const struct of_device_id cs42xx8_of_match[] = { +static const struct of_device_id cs42xx8_of_match[] = { { .compatible = "cirrus,cs42448", .data = &cs42448_data, }, { .compatible = "cirrus,cs42888", .data = &cs42888_data, }, { /* sentinel */ } diff --git a/sound/soc/codecs/cs42xx8.h b/sound/soc/codecs/cs42xx8.h index da0b94aee419..b2c10e537ef6 100644 --- a/sound/soc/codecs/cs42xx8.h +++ b/sound/soc/codecs/cs42xx8.h @@ -128,8 +128,8 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap); #define CS42XX8_INTF_DAC_DIF_RIGHTJ (2 << CS42XX8_INTF_DAC_DIF_SHIFT) #define CS42XX8_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_DAC_DIF_SHIFT) #define CS42XX8_INTF_DAC_DIF_ONELINE_20 (4 << CS42XX8_INTF_DAC_DIF_SHIFT) -#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (6 << CS42XX8_INTF_DAC_DIF_SHIFT) -#define CS42XX8_INTF_DAC_DIF_TDM (7 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (5 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_TDM (6 << CS42XX8_INTF_DAC_DIF_SHIFT) #define CS42XX8_INTF_ADC_DIF_SHIFT 0 #define CS42XX8_INTF_ADC_DIF_WIDTH 3 #define CS42XX8_INTF_ADC_DIF_MASK (((1 << CS42XX8_INTF_ADC_DIF_WIDTH) - 1) << CS42XX8_INTF_ADC_DIF_SHIFT) @@ -138,8 +138,8 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap); #define CS42XX8_INTF_ADC_DIF_RIGHTJ (2 << CS42XX8_INTF_ADC_DIF_SHIFT) #define CS42XX8_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_ADC_DIF_SHIFT) #define CS42XX8_INTF_ADC_DIF_ONELINE_20 (4 << CS42XX8_INTF_ADC_DIF_SHIFT) -#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (6 << CS42XX8_INTF_ADC_DIF_SHIFT) -#define CS42XX8_INTF_ADC_DIF_TDM (7 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (5 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_TDM (6 << CS42XX8_INTF_ADC_DIF_SHIFT) /* ADC Control & DAC De-Emphasis (Address 05h) */ #define CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT 7 diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index d5fd00a64748..8f95b0300f1a 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -253,7 +253,7 @@ static void v253_close(struct tty_struct *tty) /* Prevent the codec driver from further accessing the modem */ codec->hw_write = NULL; cx20442->control_data = NULL; - codec->card->pop_time = 0; + codec->component.card->pop_time = 0; } /* Line discipline .hangup() */ @@ -281,7 +281,7 @@ static void v253_receive(struct tty_struct *tty, /* Set up codec driver access to modem controls */ cx20442->control_data = tty; codec->hw_write = (hw_write_t)tty->ops->write; - codec->card->pop_time = 1; + codec->component.card->pop_time = 1; } } @@ -372,7 +372,7 @@ static int cx20442_codec_probe(struct snd_soc_codec *codec) snd_soc_codec_set_drvdata(codec, cx20442); codec->hw_write = NULL; - codec->card->pop_time = 0; + codec->component.card->pop_time = 0; return 0; } @@ -383,8 +383,8 @@ static int cx20442_codec_remove(struct snd_soc_codec *codec) struct cx20442_priv *cx20442 = snd_soc_codec_get_drvdata(codec); if (cx20442->control_data) { - struct tty_struct *tty = cx20442->control_data; - tty_hangup(tty); + struct tty_struct *tty = cx20442->control_data; + tty_hangup(tty); } if (!IS_ERR(cx20442->por)) { diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index f5fccc7a8e89..4a063fa88526 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -26,10 +26,6 @@ #include <sound/max98090.h> #include "max98090.h" -#define DEBUG -#define EXTMIC_METHOD -#define EXTMIC_METHOD_TEST - /* Allows for sparsely populated register maps */ static struct reg_default max98090_reg[] = { { 0x00, 0x00 }, /* 00 Software Reset */ @@ -820,7 +816,6 @@ static int max98090_micinput_event(struct snd_soc_dapm_widget *w, else val = (val & M98090_MIC_PA2EN_MASK) >> M98090_MIC_PA2EN_SHIFT; - if (val >= 1) { if (w->reg == M98090_REG_MIC1_INPUT_LEVEL) { max98090->pa1en = val - 1; /* Update for volatile */ @@ -1140,7 +1135,6 @@ static const struct snd_kcontrol_new max98090_mixhprsel_mux = SOC_DAPM_ENUM("MIXHPRSEL Mux", mixhprsel_mux_enum); static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { - SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), SND_SOC_DAPM_INPUT("DMICL"), @@ -1304,7 +1298,6 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { }; static const struct snd_soc_dapm_widget max98091_dapm_widgets[] = { - SND_SOC_DAPM_INPUT("DMIC3"), SND_SOC_DAPM_INPUT("DMIC4"), @@ -1315,7 +1308,6 @@ static const struct snd_soc_dapm_widget max98091_dapm_widgets[] = { }; static const struct snd_soc_dapm_route max98090_dapm_routes[] = { - {"MIC1 Input", NULL, "MIC1"}, {"MIC2 Input", NULL, "MIC2"}, @@ -1493,17 +1485,14 @@ static const struct snd_soc_dapm_route max98090_dapm_routes[] = { {"SPKR", NULL, "SPK Right Out"}, {"RCVL", NULL, "RCV Left Out"}, {"RCVR", NULL, "RCV Right Out"}, - }; static const struct snd_soc_dapm_route max98091_dapm_routes[] = { - /* DMIC inputs */ {"DMIC3", NULL, "DMIC3_ENA"}, {"DMIC4", NULL, "DMIC4_ENA"}, {"DMIC3", NULL, "AHPF"}, {"DMIC4", NULL, "AHPF"}, - }; static int max98090_add_widgets(struct snd_soc_codec *codec) @@ -1531,7 +1520,6 @@ static int max98090_add_widgets(struct snd_soc_codec *codec) snd_soc_dapm_add_routes(dapm, max98091_dapm_routes, ARRAY_SIZE(max98091_dapm_routes)); - } return 0; @@ -2212,22 +2200,11 @@ static struct snd_soc_dai_driver max98090_dai[] = { } }; -static void max98090_handle_pdata(struct snd_soc_codec *codec) -{ - struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); - struct max98090_pdata *pdata = max98090->pdata; - - if (!pdata) { - dev_err(codec->dev, "No platform data\n"); - return; - } - -} - static int max98090_probe(struct snd_soc_codec *codec) { struct max98090_priv *max98090 = snd_soc_codec_get_drvdata(codec); struct max98090_cdata *cdata; + enum max98090_type devtype; int ret = 0; dev_dbg(codec->dev, "max98090_probe\n"); @@ -2263,16 +2240,21 @@ static int max98090_probe(struct snd_soc_codec *codec) } if ((ret >= M98090_REVA) && (ret <= M98090_REVA + 0x0f)) { - max98090->devtype = MAX98090; + devtype = MAX98090; dev_info(codec->dev, "MAX98090 REVID=0x%02x\n", ret); } else if ((ret >= M98091_REVA) && (ret <= M98091_REVA + 0x0f)) { - max98090->devtype = MAX98091; + devtype = MAX98091; dev_info(codec->dev, "MAX98091 REVID=0x%02x\n", ret); } else { - max98090->devtype = MAX98090; + devtype = MAX98090; dev_err(codec->dev, "Unrecognized revision 0x%02x\n", ret); } + if (max98090->devtype != devtype) { + dev_warn(codec->dev, "Mismatch in DT specified CODEC type.\n"); + max98090->devtype = devtype; + } + max98090->jack_state = M98090_JACK_STATE_NO_HEADSET; INIT_DELAYED_WORK(&max98090->jack_work, max98090_jack_work); @@ -2284,7 +2266,7 @@ static int max98090_probe(struct snd_soc_codec *codec) /* Register for interrupts */ dev_dbg(codec->dev, "irq = %d\n", max98090->irq); - ret = request_threaded_irq(max98090->irq, NULL, + ret = devm_request_threaded_irq(codec->dev, max98090->irq, NULL, max98090_interrupt, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "max98090_interrupt", codec); if (ret < 0) { @@ -2317,8 +2299,6 @@ static int max98090_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, M98090_REG_MIC_BIAS_VOLTAGE, M98090_MBVSEL_MASK, M98090_MBVSEL_2V8); - max98090_handle_pdata(codec); - max98090_add_widgets(codec); err_access: @@ -2428,7 +2408,7 @@ static int max98090_runtime_suspend(struct device *dev) } #endif -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP static int max98090_resume(struct device *dev) { struct max98090_priv *max98090 = dev_get_drvdata(dev); @@ -2460,12 +2440,14 @@ static const struct dev_pm_ops max98090_pm = { static const struct i2c_device_id max98090_i2c_id[] = { { "max98090", MAX98090 }, + { "max98091", MAX98091 }, { } }; MODULE_DEVICE_TABLE(i2c, max98090_i2c_id); static const struct of_device_id max98090_of_match[] = { { .compatible = "maxim,max98090", }, + { .compatible = "maxim,max98091", }, { } }; MODULE_DEVICE_TABLE(of, max98090_of_match); diff --git a/sound/soc/codecs/mc13783.c b/sound/soc/codecs/mc13783.c index 9965277b595a..388f90a597fa 100644 --- a/sound/soc/codecs/mc13783.c +++ b/sound/soc/codecs/mc13783.c @@ -766,11 +766,11 @@ static int __init mc13783_codec_probe(struct platform_device *pdev) ret = of_property_read_u32(np, "adc-port", &priv->adc_ssi_port); if (ret) - return ret; + goto out; ret = of_property_read_u32(np, "dac-port", &priv->dac_ssi_port); if (ret) - return ret; + goto out; } dev_set_drvdata(&pdev->dev, priv); @@ -783,6 +783,8 @@ static int __init mc13783_codec_probe(struct platform_device *pdev) ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_mc13783, mc13783_dai_async, ARRAY_SIZE(mc13783_dai_async)); +out: + of_node_put(np); return ret; } diff --git a/sound/soc/codecs/pcm1792a.c b/sound/soc/codecs/pcm1792a.c index 3a80ba4452df..57b0c94a710b 100644 --- a/sound/soc/codecs/pcm1792a.c +++ b/sound/soc/codecs/pcm1792a.c @@ -36,6 +36,7 @@ #define PCM1792A_DAC_VOL_LEFT 0x10 #define PCM1792A_DAC_VOL_RIGHT 0x11 #define PCM1792A_FMT_CONTROL 0x12 +#define PCM1792A_MODE_CONTROL 0x13 #define PCM1792A_SOFT_MUTE PCM1792A_FMT_CONTROL #define PCM1792A_FMT_MASK 0x70 @@ -164,6 +165,8 @@ static const struct snd_kcontrol_new pcm1792a_controls[] = { SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM1792A_DAC_VOL_LEFT, PCM1792A_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0, pcm1792a_dac_tlv), + SOC_SINGLE("DAC Invert Output Switch", PCM1792A_MODE_CONTROL, 7, 1, 0), + SOC_SINGLE("DAC Rolloff Filter Switch", PCM1792A_MODE_CONTROL, 1, 1, 0), }; static const struct snd_soc_dapm_widget pcm1792a_dapm_widgets[] = { diff --git a/sound/soc/codecs/pcm1792a.h b/sound/soc/codecs/pcm1792a.h index 7a83d1fc102a..51d5470fee16 100644 --- a/sound/soc/codecs/pcm1792a.h +++ b/sound/soc/codecs/pcm1792a.h @@ -18,7 +18,8 @@ #define __PCM1792A_H__ #define PCM1792A_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_8000_48000 | \ - SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) #define PCM1792A_FORMATS (SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S16_LE) diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c new file mode 100644 index 000000000000..e4f6102efc1a --- /dev/null +++ b/sound/soc/codecs/rt286.c @@ -0,0 +1,1222 @@ +/* + * rt286.c -- RT286 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Bard Liao <bardliao@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/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/acpi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/jack.h> +#include <linux/workqueue.h> +#include <sound/rt286.h> +#include <sound/hda_verbs.h> + +#include "rt286.h" + +#define RT286_VENDOR_ID 0x10ec0286 + +struct rt286_priv { + struct regmap *regmap; + struct rt286_platform_data pdata; + struct i2c_client *i2c; + struct snd_soc_jack *jack; + struct delayed_work jack_detect_work; + int sys_clk; + struct reg_default *index_cache; +}; + +static struct reg_default rt286_index_def[] = { + { 0x01, 0xaaaa }, + { 0x02, 0x8aaa }, + { 0x03, 0x0002 }, + { 0x04, 0xaf01 }, + { 0x08, 0x000d }, + { 0x09, 0xd810 }, + { 0x0a, 0x0060 }, + { 0x0b, 0x0000 }, + { 0x0d, 0x2800 }, + { 0x0f, 0x0000 }, + { 0x19, 0x0a17 }, + { 0x20, 0x0020 }, + { 0x33, 0x0208 }, + { 0x49, 0x0004 }, + { 0x4f, 0x50e9 }, + { 0x50, 0x2c00 }, + { 0x63, 0x2902 }, + { 0x67, 0x1111 }, + { 0x68, 0x1016 }, + { 0x69, 0x273f }, +}; +#define INDEX_CACHE_SIZE ARRAY_SIZE(rt286_index_def) + +static const struct reg_default rt286_reg[] = { + { 0x00170500, 0x00000400 }, + { 0x00220000, 0x00000031 }, + { 0x00239000, 0x0000007f }, + { 0x0023a000, 0x0000007f }, + { 0x00270500, 0x00000400 }, + { 0x00370500, 0x00000400 }, + { 0x00870500, 0x00000400 }, + { 0x00920000, 0x00000031 }, + { 0x00935000, 0x000000c3 }, + { 0x00936000, 0x000000c3 }, + { 0x00970500, 0x00000400 }, + { 0x00b37000, 0x00000097 }, + { 0x00b37200, 0x00000097 }, + { 0x00b37300, 0x00000097 }, + { 0x00c37000, 0x00000000 }, + { 0x00c37100, 0x00000080 }, + { 0x01270500, 0x00000400 }, + { 0x01370500, 0x00000400 }, + { 0x01371f00, 0x411111f0 }, + { 0x01439000, 0x00000080 }, + { 0x0143a000, 0x00000080 }, + { 0x01470700, 0x00000000 }, + { 0x01470500, 0x00000400 }, + { 0x01470c00, 0x00000000 }, + { 0x01470100, 0x00000000 }, + { 0x01837000, 0x00000000 }, + { 0x01870500, 0x00000400 }, + { 0x02050000, 0x00000000 }, + { 0x02139000, 0x00000080 }, + { 0x0213a000, 0x00000080 }, + { 0x02170100, 0x00000000 }, + { 0x02170500, 0x00000400 }, + { 0x02170700, 0x00000000 }, + { 0x02270100, 0x00000000 }, + { 0x02370100, 0x00000000 }, + { 0x02040000, 0x00004002 }, + { 0x01870700, 0x00000020 }, + { 0x00830000, 0x000000c3 }, + { 0x00930000, 0x000000c3 }, + { 0x01270700, 0x00000000 }, +}; + +static bool rt286_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT286_GET_HP_SENSE: + case RT286_GET_MIC1_SENSE: + case RT286_PROC_COEF: + return true; + default: + return false; + } + + +} + +static bool rt286_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT286_GET_HP_SENSE: + case RT286_GET_MIC1_SENSE: + case RT286_SET_AUDIO_POWER: + case RT286_SET_HPO_POWER: + case RT286_SET_SPK_POWER: + case RT286_SET_DMIC1_POWER: + case RT286_SPK_MUX: + case RT286_HPO_MUX: + case RT286_ADC0_MUX: + case RT286_ADC1_MUX: + case RT286_SET_MIC1: + case RT286_SET_PIN_HPO: + case RT286_SET_PIN_SPK: + case RT286_SET_PIN_DMIC1: + case RT286_SPK_EAPD: + case RT286_SET_AMP_GAIN_HPO: + case RT286_SET_DMIC2_DEFAULT: + case RT286_DACL_GAIN: + case RT286_DACR_GAIN: + case RT286_ADCL_GAIN: + case RT286_ADCR_GAIN: + case RT286_MIC_GAIN: + case RT286_SPOL_GAIN: + case RT286_SPOR_GAIN: + case RT286_HPOL_GAIN: + case RT286_HPOR_GAIN: + case RT286_F_DAC_SWITCH: + case RT286_F_RECMIX_SWITCH: + case RT286_REC_MIC_SWITCH: + case RT286_REC_I2S_SWITCH: + case RT286_REC_LINE_SWITCH: + case RT286_REC_BEEP_SWITCH: + case RT286_DAC_FORMAT: + case RT286_ADC_FORMAT: + case RT286_COEF_INDEX: + case RT286_PROC_COEF: + case RT286_SET_AMP_GAIN_ADC_IN1: + case RT286_SET_AMP_GAIN_ADC_IN2: + case RT286_SET_POWER(RT286_DAC_OUT1): + case RT286_SET_POWER(RT286_DAC_OUT2): + case RT286_SET_POWER(RT286_ADC_IN1): + case RT286_SET_POWER(RT286_ADC_IN2): + case RT286_SET_POWER(RT286_DMIC2): + case RT286_SET_POWER(RT286_MIC1): + return true; + default: + return false; + } +} + +static int rt286_hw_write(void *context, unsigned int reg, unsigned int value) +{ + struct i2c_client *client = context; + struct rt286_priv *rt286 = i2c_get_clientdata(client); + u8 data[4]; + int ret, i; + + /*handle index registers*/ + if (reg <= 0xff) { + rt286_hw_write(client, RT286_COEF_INDEX, reg); + reg = RT286_PROC_COEF; + for (i = 0; i < INDEX_CACHE_SIZE; i++) { + if (reg == rt286->index_cache[i].reg) { + rt286->index_cache[i].def = value; + break; + } + + } + } + + data[0] = (reg >> 24) & 0xff; + data[1] = (reg >> 16) & 0xff; + /* + * 4 bit VID: reg should be 0 + * 12 bit VID: value should be 0 + * So we use an OR operator to handle it rather than use if condition. + */ + data[2] = ((reg >> 8) & 0xff) | ((value >> 8) & 0xff); + data[3] = value & 0xff; + + ret = i2c_master_send(client, data, 4); + + if (ret == 4) + return 0; + else + pr_err("ret=%d\n", ret); + if (ret < 0) + return ret; + else + return -EIO; +} + +static int rt286_hw_read(void *context, unsigned int reg, unsigned int *value) +{ + struct i2c_client *client = context; + struct i2c_msg xfer[2]; + int ret; + __be32 be_reg; + unsigned int index, vid, buf = 0x0; + + /*handle index registers*/ + if (reg <= 0xff) { + rt286_hw_write(client, RT286_COEF_INDEX, reg); + reg = RT286_PROC_COEF; + } + + reg = reg | 0x80000; + vid = (reg >> 8) & 0xfff; + + if (AC_VERB_GET_AMP_GAIN_MUTE == (vid & 0xf00)) { + index = (reg >> 8) & 0xf; + reg = (reg & ~0xf0f) | index; + } + be_reg = cpu_to_be32(reg); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 4; + xfer[0].buf = (u8 *)&be_reg; + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 4; + xfer[1].buf = (u8 *)&buf; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + + *value = be32_to_cpu(buf); + + return 0; +} + +static void rt286_index_sync(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + int i; + + for (i = 0; i < INDEX_CACHE_SIZE; i++) { + snd_soc_write(codec, rt286->index_cache[i].reg, + rt286->index_cache[i].def); + } +} + +static int rt286_support_power_controls[] = { + RT286_DAC_OUT1, + RT286_DAC_OUT2, + RT286_ADC_IN1, + RT286_ADC_IN2, + RT286_MIC1, + RT286_DMIC1, + RT286_DMIC2, + RT286_SPK_OUT, + RT286_HP_OUT, +}; +#define RT286_POWER_REG_LEN ARRAY_SIZE(rt286_support_power_controls) + +static int rt286_jack_detect(struct rt286_priv *rt286, bool *hp, bool *mic) +{ + unsigned int val, buf; + int i; + + *hp = false; + *mic = false; + + if (rt286->pdata.cbj_en) { + regmap_read(rt286->regmap, RT286_GET_HP_SENSE, &buf); + *hp = buf & 0x80000000; + if (*hp) { + /* power on HV,VERF */ + regmap_update_bits(rt286->regmap, + RT286_POWER_CTRL1, 0x1001, 0x0); + /* power LDO1 */ + regmap_update_bits(rt286->regmap, + RT286_POWER_CTRL2, 0x4, 0x4); + regmap_write(rt286->regmap, RT286_SET_MIC1, 0x24); + regmap_read(rt286->regmap, RT286_CBJ_CTRL2, &val); + + msleep(200); + i = 40; + while (((val & 0x0800) == 0) && (i > 0)) { + regmap_read(rt286->regmap, + RT286_CBJ_CTRL2, &val); + i--; + msleep(20); + } + + if (0x0400 == (val & 0x0700)) { + *mic = false; + + regmap_write(rt286->regmap, + RT286_SET_MIC1, 0x20); + /* power off HV,VERF */ + regmap_update_bits(rt286->regmap, + RT286_POWER_CTRL1, 0x1001, 0x1001); + regmap_update_bits(rt286->regmap, + RT286_A_BIAS_CTRL3, 0xc000, 0x0000); + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0x0030, 0x0000); + regmap_update_bits(rt286->regmap, + RT286_A_BIAS_CTRL2, 0xc000, 0x0000); + } else if ((0x0200 == (val & 0x0700)) || + (0x0100 == (val & 0x0700))) { + *mic = true; + regmap_update_bits(rt286->regmap, + RT286_A_BIAS_CTRL3, 0xc000, 0x8000); + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0x0030, 0x0020); + regmap_update_bits(rt286->regmap, + RT286_A_BIAS_CTRL2, 0xc000, 0x8000); + } else { + *mic = false; + } + + regmap_update_bits(rt286->regmap, + RT286_MISC_CTRL1, + 0x0060, 0x0000); + } else { + regmap_update_bits(rt286->regmap, + RT286_MISC_CTRL1, + 0x0060, 0x0020); + regmap_update_bits(rt286->regmap, + RT286_A_BIAS_CTRL3, + 0xc000, 0x8000); + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, + 0x0030, 0x0020); + regmap_update_bits(rt286->regmap, + RT286_A_BIAS_CTRL2, + 0xc000, 0x8000); + + *mic = false; + } + } else { + regmap_read(rt286->regmap, RT286_GET_HP_SENSE, &buf); + *hp = buf & 0x80000000; + regmap_read(rt286->regmap, RT286_GET_MIC1_SENSE, &buf); + *mic = buf & 0x80000000; + } + + return 0; +} + +static void rt286_jack_detect_work(struct work_struct *work) +{ + struct rt286_priv *rt286 = + container_of(work, struct rt286_priv, jack_detect_work.work); + int status = 0; + bool hp = false; + bool mic = false; + + rt286_jack_detect(rt286, &hp, &mic); + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt286->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); +} + +int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + rt286->jack = jack; + + /* Send an initial empty report */ + snd_soc_jack_report(rt286->jack, 0, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + return 0; +} +EXPORT_SYMBOL_GPL(rt286_mic_detect); + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6350, 50, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt286_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC0 Playback Volume", RT286_DACL_GAIN, + RT286_DACR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_DOUBLE_R_TLV("ADC0 Capture Volume", RT286_ADCL_GAIN, + RT286_ADCR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_SINGLE_TLV("AMIC Volume", RT286_MIC_GAIN, + 0, 0x3, 0, mic_vol_tlv), + SOC_DOUBLE_R("Speaker Playback Switch", RT286_SPOL_GAIN, + RT286_SPOR_GAIN, RT286_MUTE_SFT, 1, 1), +}; + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt286_front_mix[] = { + SOC_DAPM_SINGLE("DAC Switch", RT286_F_DAC_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIX Switch", RT286_F_RECMIX_SWITCH, + RT286_MUTE_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt286_rec_mix[] = { + SOC_DAPM_SINGLE("Mic1 Switch", RT286_REC_MIC_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("I2S Switch", RT286_REC_I2S_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Line1 Switch", RT286_REC_LINE_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Beep Switch", RT286_REC_BEEP_SWITCH, + RT286_MUTE_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new spo_enable_control = + SOC_DAPM_SINGLE("Switch", RT286_SET_PIN_SPK, + RT286_SET_PIN_SFT, 1, 0); + +static const struct snd_kcontrol_new hpol_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOL_GAIN, + RT286_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpor_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOR_GAIN, + RT286_MUTE_SFT, 1, 1); + +/* ADC0 source */ +static const char * const rt286_adc_src[] = { + "Mic", "RECMIX", "Dmic" +}; + +static const int rt286_adc_values[] = { + 0, 4, 5, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc0_enum, RT286_ADC0_MUX, RT286_ADC_SEL_SFT, + RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values); + +static const struct snd_kcontrol_new rt286_adc0_mux = + SOC_DAPM_ENUM("ADC 0 source", rt286_adc0_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc1_enum, RT286_ADC1_MUX, RT286_ADC_SEL_SFT, + RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values); + +static const struct snd_kcontrol_new rt286_adc1_mux = + SOC_DAPM_ENUM("ADC 1 source", rt286_adc1_enum); + +static const char * const rt286_dac_src[] = { + "Front", "Surround" +}; +/* HP-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt286_hpo_enum, RT286_HPO_MUX, + 0, rt286_dac_src); + +static const struct snd_kcontrol_new rt286_hpo_mux = +SOC_DAPM_ENUM("HPO source", rt286_hpo_enum); + +/* SPK-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt286_spo_enum, RT286_SPK_MUX, + 0, rt286_dac_src); + +static const struct snd_kcontrol_new rt286_spo_mux = +SOC_DAPM_ENUM("SPO source", rt286_spo_enum); + +static int rt286_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_write(codec, + RT286_SPK_EAPD, RT286_SET_EAPD_HIGH); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_write(codec, + RT286_SPK_EAPD, RT286_SET_EAPD_LOW); + break; + + default: + return 0; + } + + return 0; +} + +static int rt286_set_dmic1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_write(codec, RT286_SET_PIN_DMIC1, 0x20); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_write(codec, RT286_SET_PIN_DMIC1, 0); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int nid; + + nid = (w->reg >> 20) & 0xff; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), + 0x7080, 0x7000); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), + 0x7080, 0x7080); + break; + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt286_dapm_widgets[] = { + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC1 Pin"), + SND_SOC_DAPM_INPUT("DMIC2 Pin"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("Beep"), + + /* DMIC */ + SND_SOC_DAPM_PGA_E("DMIC1", RT286_SET_POWER(RT286_DMIC1), 0, 1, + NULL, 0, rt286_set_dmic1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA("DMIC2", RT286_SET_POWER(RT286_DMIC2), 0, 1, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC Receiver", SND_SOC_NOPM, + 0, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX", SND_SOC_NOPM, 0, 0, + rt286_rec_mix, ARRAY_SIZE(rt286_rec_mix)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX_E("ADC 0 Mux", RT286_SET_POWER(RT286_ADC_IN1), 0, 1, + &rt286_adc0_mux, rt286_adc_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MUX_E("ADC 1 Mux", RT286_SET_POWER(RT286_ADC_IN2), 0, 1, + &rt286_adc1_mux, rt286_adc_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("DAC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* Output Mux */ + SND_SOC_DAPM_MUX("SPK Mux", SND_SOC_NOPM, 0, 0, &rt286_spo_mux), + SND_SOC_DAPM_MUX("HPO Mux", SND_SOC_NOPM, 0, 0, &rt286_hpo_mux), + + SND_SOC_DAPM_SUPPLY("HP Power", RT286_SET_PIN_HPO, + RT286_SET_PIN_SFT, 0, NULL, 0), + + /* Output Mixer */ + SND_SOC_DAPM_MIXER("Front", RT286_SET_POWER(RT286_DAC_OUT1), 0, 1, + rt286_front_mix, ARRAY_SIZE(rt286_front_mix)), + SND_SOC_DAPM_PGA("Surround", RT286_SET_POWER(RT286_DAC_OUT2), 0, 1, + NULL, 0), + + /* Output Pga */ + SND_SOC_DAPM_SWITCH_E("SPO", SND_SOC_NOPM, 0, 0, + &spo_enable_control, rt286_spk_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SWITCH("HPO L", SND_SOC_NOPM, 0, 0, + &hpol_enable_control), + SND_SOC_DAPM_SWITCH("HPO R", SND_SOC_NOPM, 0, 0, + &hpor_enable_control), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + SND_SOC_DAPM_OUTPUT("HPO Pin"), + SND_SOC_DAPM_OUTPUT("SPDIF"), +}; + +static const struct snd_soc_dapm_route rt286_dapm_routes[] = { + {"DMIC1", NULL, "DMIC1 Pin"}, + {"DMIC2", NULL, "DMIC2 Pin"}, + {"DMIC1", NULL, "DMIC Receiver"}, + {"DMIC2", NULL, "DMIC Receiver"}, + + {"RECMIX", "Beep Switch", "Beep"}, + {"RECMIX", "Line1 Switch", "LINE1"}, + {"RECMIX", "Mic1 Switch", "MIC1"}, + + {"ADC 0 Mux", "Dmic", "DMIC1"}, + {"ADC 0 Mux", "RECMIX", "RECMIX"}, + {"ADC 0 Mux", "Mic", "MIC1"}, + {"ADC 1 Mux", "Dmic", "DMIC2"}, + {"ADC 1 Mux", "RECMIX", "RECMIX"}, + {"ADC 1 Mux", "Mic", "MIC1"}, + + {"ADC 0", NULL, "ADC 0 Mux"}, + {"ADC 1", NULL, "ADC 1 Mux"}, + + {"AIF1TX", NULL, "ADC 0"}, + {"AIF2TX", NULL, "ADC 1"}, + + {"DAC 0", NULL, "AIF1RX"}, + {"DAC 1", NULL, "AIF2RX"}, + + {"Front", "DAC Switch", "DAC 0"}, + {"Front", "RECMIX Switch", "RECMIX"}, + + {"Surround", NULL, "DAC 1"}, + + {"SPK Mux", "Front", "Front"}, + {"SPK Mux", "Surround", "Surround"}, + + {"HPO Mux", "Front", "Front"}, + {"HPO Mux", "Surround", "Surround"}, + + {"SPO", "Switch", "SPK Mux"}, + {"HPO L", "Switch", "HPO Mux"}, + {"HPO R", "Switch", "HPO Mux"}, + {"HPO L", NULL, "HP Power"}, + {"HPO R", NULL, "HP Power"}, + + {"SPOL", NULL, "SPO"}, + {"SPOR", NULL, "SPO"}, + {"HPO Pin", NULL, "HPO L"}, + {"HPO Pin", NULL, "HPO R"}, +}; + +static int rt286_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 rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + unsigned int val = 0; + int d_len_code; + + switch (params_rate(params)) { + /* bit 14 0:48K 1:44.1K */ + case 44100: + val |= 0x4000; + break; + case 48000: + break; + default: + dev_err(codec->dev, "Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + switch (rt286->sys_clk) { + case 12288000: + case 24576000: + if (params_rate(params) != 48000) { + dev_err(codec->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt286->sys_clk); + return -EINVAL; + } + break; + case 11289600: + case 22579200: + if (params_rate(params) != 44100) { + dev_err(codec->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt286->sys_clk); + return -EINVAL; + } + break; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(codec->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + d_len_code = 0; + switch (params_width(params)) { + /* bit 6:4 Bits per Sample */ + case 16: + d_len_code = 0; + val |= (0x1 << 4); + break; + case 32: + d_len_code = 2; + val |= (0x4 << 4); + break; + case 20: + d_len_code = 1; + val |= (0x2 << 4); + break; + case 24: + d_len_code = 2; + val |= (0x3 << 4); + break; + case 8: + d_len_code = 3; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x0018, d_len_code << 3); + dev_dbg(codec->dev, "format val = 0x%x\n", val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_update_bits(codec, RT286_DAC_FORMAT, 0x407f, val); + else + snd_soc_update_bits(codec, RT286_ADC_FORMAT, 0x407f, val); + + return 0; +} + +static int rt286_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x800, 0x800); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x800, 0x0); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x300, 0x0); + break; + case SND_SOC_DAIFMT_LEFT_J: + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x300, 0x1 << 8); + break; + case SND_SOC_DAIFMT_DSP_A: + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x300, 0x2 << 8); + break; + case SND_SOC_DAIFMT_DSP_B: + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x300, 0x3 << 8); + break; + default: + return -EINVAL; + } + /* bit 15 Stream Type 0:PCM 1:Non-PCM */ + snd_soc_update_bits(codec, RT286_DAC_FORMAT, 0x8000, 0); + snd_soc_update_bits(codec, RT286_ADC_FORMAT, 0x8000, 0); + + return 0; +} + +static int rt286_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "%s freq=%d\n", __func__, freq); + + if (RT286_SCLK_S_MCLK == clk_id) { + snd_soc_update_bits(codec, + RT286_I2S_CTRL2, 0x0100, 0x0); + snd_soc_update_bits(codec, + RT286_PLL_CTRL1, 0x20, 0x20); + } else { + snd_soc_update_bits(codec, + RT286_I2S_CTRL2, 0x0100, 0x0100); + snd_soc_update_bits(codec, + RT286_PLL_CTRL, 0x4, 0x4); + snd_soc_update_bits(codec, + RT286_PLL_CTRL1, 0x20, 0x0); + } + + switch (freq) { + case 19200000: + if (RT286_SCLK_S_MCLK == clk_id) { + dev_err(codec->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, + RT286_I2S_CTRL2, 0x40, 0x40); + break; + case 24000000: + if (RT286_SCLK_S_MCLK == clk_id) { + dev_err(codec->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, + RT286_I2S_CTRL2, 0x40, 0x0); + break; + case 12288000: + case 11289600: + snd_soc_update_bits(codec, + RT286_I2S_CTRL2, 0x8, 0x0); + snd_soc_update_bits(codec, + RT286_CLK_DIV, 0xfc1e, 0x0004); + break; + case 24576000: + case 22579200: + snd_soc_update_bits(codec, + RT286_I2S_CTRL2, 0x8, 0x8); + snd_soc_update_bits(codec, + RT286_CLK_DIV, 0xfc1e, 0x5406); + break; + default: + dev_err(codec->dev, "Unsupported system clock\n"); + return -EINVAL; + } + + rt286->sys_clk = freq; + + return 0; +} + +static int rt286_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_codec *codec = dai->codec; + + dev_dbg(codec->dev, "%s ratio=%d\n", __func__, ratio); + if (50 == ratio) + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x1000, 0x1000); + else + snd_soc_update_bits(codec, + RT286_I2S_CTRL1, 0x1000, 0x0); + + + return 0; +} + +static int rt286_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == codec->dapm.bias_level) { + snd_soc_write(codec, + RT286_SET_AUDIO_POWER, AC_PWRST_D0); + snd_soc_update_bits(codec, + RT286_DC_GAIN, 0x200, 0x200); + } + break; + + case SND_SOC_BIAS_ON: + mdelay(10); + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_write(codec, + RT286_SET_AUDIO_POWER, AC_PWRST_D3); + snd_soc_update_bits(codec, + RT286_DC_GAIN, 0x200, 0x0); + break; + + default: + break; + } + codec->dapm.bias_level = level; + + return 0; +} + +static irqreturn_t rt286_irq(int irq, void *data) +{ + struct rt286_priv *rt286 = data; + bool hp = false; + bool mic = false; + int status = 0; + + rt286_jack_detect(rt286, &hp, &mic); + + /* Clear IRQ */ + regmap_update_bits(rt286->regmap, RT286_IRQ_CTRL, 0x1, 0x1); + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt286->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + pm_wakeup_event(&rt286->i2c->dev, 300); + + return IRQ_HANDLED; +} + +static int rt286_probe(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + codec->dapm.bias_level = SND_SOC_BIAS_OFF; + + if (rt286->i2c->irq) { + regmap_update_bits(rt286->regmap, + RT286_IRQ_CTRL, 0x2, 0x2); + + INIT_DELAYED_WORK(&rt286->jack_detect_work, + rt286_jack_detect_work); + schedule_delayed_work(&rt286->jack_detect_work, + msecs_to_jiffies(1250)); + } + + return 0; +} + +static int rt286_remove(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + cancel_delayed_work_sync(&rt286->jack_detect_work); + + return 0; +} + +#ifdef CONFIG_PM +static int rt286_suspend(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + regcache_cache_only(rt286->regmap, true); + regcache_mark_dirty(rt286->regmap); + + return 0; +} + +static int rt286_resume(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + regcache_cache_only(rt286->regmap, false); + rt286_index_sync(codec); + regcache_sync(rt286->regmap); + + return 0; +} +#else +#define rt286_suspend NULL +#define rt286_resume NULL +#endif + +#define RT286_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT286_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt286_aif_dai_ops = { + .hw_params = rt286_hw_params, + .set_fmt = rt286_set_dai_fmt, + .set_sysclk = rt286_set_dai_sysclk, + .set_bclk_ratio = rt286_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt286_dai[] = { + { + .name = "rt286-aif1", + .id = RT286_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .ops = &rt286_aif_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "rt286-aif2", + .id = RT286_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .ops = &rt286_aif_dai_ops, + .symmetric_rates = 1, + }, + +}; + +static struct snd_soc_codec_driver soc_codec_dev_rt286 = { + .probe = rt286_probe, + .remove = rt286_remove, + .suspend = rt286_suspend, + .resume = rt286_resume, + .set_bias_level = rt286_set_bias_level, + .idle_bias_off = true, + .controls = rt286_snd_controls, + .num_controls = ARRAY_SIZE(rt286_snd_controls), + .dapm_widgets = rt286_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt286_dapm_widgets), + .dapm_routes = rt286_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt286_dapm_routes), +}; + +static const struct regmap_config rt286_regmap = { + .reg_bits = 32, + .val_bits = 32, + .max_register = 0x02370100, + .volatile_reg = rt286_volatile_register, + .readable_reg = rt286_readable_register, + .reg_write = rt286_hw_write, + .reg_read = rt286_hw_read, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt286_reg, + .num_reg_defaults = ARRAY_SIZE(rt286_reg), +}; + +static const struct i2c_device_id rt286_i2c_id[] = { + {"rt286", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt286_i2c_id); + +static const struct acpi_device_id rt286_acpi_match[] = { + { "INT343A", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt286_acpi_match); + +static int rt286_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt286_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt286_priv *rt286; + int i, ret; + + rt286 = devm_kzalloc(&i2c->dev, sizeof(*rt286), + GFP_KERNEL); + if (NULL == rt286) + return -ENOMEM; + + rt286->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt286_regmap); + if (IS_ERR(rt286->regmap)) { + ret = PTR_ERR(rt286->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt286->regmap, + RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &ret); + if (ret != RT286_VENDOR_ID) { + dev_err(&i2c->dev, + "Device with ID register %x is not rt286\n", ret); + return -ENODEV; + } + + rt286->index_cache = rt286_index_def; + rt286->i2c = i2c; + i2c_set_clientdata(i2c, rt286); + + if (pdata) + rt286->pdata = *pdata; + + regmap_write(rt286->regmap, RT286_SET_AUDIO_POWER, AC_PWRST_D3); + + for (i = 0; i < RT286_POWER_REG_LEN; i++) + regmap_write(rt286->regmap, + RT286_SET_POWER(rt286_support_power_controls[i]), + AC_PWRST_D1); + + if (!rt286->pdata.cbj_en) { + regmap_write(rt286->regmap, RT286_CBJ_CTRL2, 0x0000); + regmap_write(rt286->regmap, RT286_MIC1_DET_CTRL, 0x0816); + regmap_write(rt286->regmap, RT286_MISC_CTRL1, 0x0000); + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xf000, 0xb000); + } else { + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xf000, 0x5000); + } + + mdelay(10); + + if (!rt286->pdata.gpio2_en) + regmap_write(rt286->regmap, RT286_SET_DMIC2_DEFAULT, 0x4000); + else + regmap_write(rt286->regmap, RT286_SET_DMIC2_DEFAULT, 0); + + mdelay(10); + + /*Power down LDO2*/ + regmap_update_bits(rt286->regmap, RT286_POWER_CTRL2, 0x8, 0x0); + + /*Set depop parameter*/ + regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL2, 0x403a, 0x401a); + regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL3, 0xf777, 0x4737); + regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL4, 0x00ff, 0x003f); + + if (rt286->i2c->irq) { + ret = request_threaded_irq(rt286->i2c->irq, NULL, rt286_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "rt286", rt286); + if (ret != 0) { + dev_err(&i2c->dev, + "Failed to reguest IRQ: %d\n", ret); + return ret; + } + } + + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt286, + rt286_dai, ARRAY_SIZE(rt286_dai)); + + return ret; +} + +static int rt286_i2c_remove(struct i2c_client *i2c) +{ + struct rt286_priv *rt286 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, rt286); + snd_soc_unregister_codec(&i2c->dev); + + return 0; +} + + +static struct i2c_driver rt286_i2c_driver = { + .driver = { + .name = "rt286", + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(rt286_acpi_match), + }, + .probe = rt286_i2c_probe, + .remove = rt286_i2c_remove, + .id_table = rt286_i2c_id, +}; + +module_i2c_driver(rt286_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT286 driver"); +MODULE_AUTHOR("Bard Liao <bardliao@realtek.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt286.h b/sound/soc/codecs/rt286.h new file mode 100644 index 000000000000..b539b7320a79 --- /dev/null +++ b/sound/soc/codecs/rt286.h @@ -0,0 +1,198 @@ +/* + * rt286.h -- RT286 ALSA SoC audio driver + * + * Copyright 2011 Realtek Microelectronics + * Author: Johnny Hsu <johnnyhsu@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 __RT286_H__ +#define __RT286_H__ + +#define VERB_CMD(V, N, D) ((N << 20) | (V << 8) | D) + +#define RT286_AUDIO_FUNCTION_GROUP 0x01 +#define RT286_DAC_OUT1 0x02 +#define RT286_DAC_OUT2 0x03 +#define RT286_ADC_IN1 0x09 +#define RT286_ADC_IN2 0x08 +#define RT286_MIXER_IN 0x0b +#define RT286_MIXER_OUT1 0x0c +#define RT286_MIXER_OUT2 0x0d +#define RT286_DMIC1 0x12 +#define RT286_DMIC2 0x13 +#define RT286_SPK_OUT 0x14 +#define RT286_MIC1 0x18 +#define RT286_LINE1 0x1a +#define RT286_BEEP 0x1d +#define RT286_SPDIF 0x1e +#define RT286_VENDOR_REGISTERS 0x20 +#define RT286_HP_OUT 0x21 +#define RT286_MIXER_IN1 0x22 +#define RT286_MIXER_IN2 0x23 + +#define RT286_SET_PIN_SFT 6 +#define RT286_SET_PIN_ENABLE 0x40 +#define RT286_SET_PIN_DISABLE 0 +#define RT286_SET_EAPD_HIGH 0x2 +#define RT286_SET_EAPD_LOW 0 + +#define RT286_MUTE_SFT 7 + +/* Verb commands */ +#define RT286_GET_PARAM(NID, PARAM) VERB_CMD(AC_VERB_PARAMETERS, NID, PARAM) +#define RT286_SET_POWER(NID) VERB_CMD(AC_VERB_SET_POWER_STATE, NID, 0) +#define RT286_SET_AUDIO_POWER RT286_SET_POWER(RT286_AUDIO_FUNCTION_GROUP) +#define RT286_SET_HPO_POWER RT286_SET_POWER(RT286_HP_OUT) +#define RT286_SET_SPK_POWER RT286_SET_POWER(RT286_SPK_OUT) +#define RT286_SET_DMIC1_POWER RT286_SET_POWER(RT286_DMIC1) +#define RT286_SPK_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_SPK_OUT, 0) +#define RT286_HPO_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_HP_OUT, 0) +#define RT286_ADC0_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN1, 0) +#define RT286_ADC1_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN2, 0) +#define RT286_SET_MIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_MIC1, 0) +#define RT286_SET_PIN_HPO\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_HP_OUT, 0) +#define RT286_SET_PIN_SPK\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_SPK_OUT, 0) +#define RT286_SET_PIN_DMIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_DMIC1, 0) +#define RT286_SPK_EAPD\ + VERB_CMD(AC_VERB_SET_EAPD_BTLENABLE, RT286_SPK_OUT, 0) +#define RT286_SET_AMP_GAIN_HPO\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0) +#define RT286_SET_AMP_GAIN_ADC_IN1\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0) +#define RT286_SET_AMP_GAIN_ADC_IN2\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN2, 0) +#define RT286_GET_HP_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_HP_OUT, 0) +#define RT286_GET_MIC1_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_MIC1, 0) +#define RT286_SET_DMIC2_DEFAULT\ + VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT286_DMIC2, 0) +#define RT286_DACL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0xa000) +#define RT286_DACR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0x9000) +#define RT286_ADCL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x6000) +#define RT286_ADCR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x5000) +#define RT286_MIC_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIC1, 0x7000) +#define RT286_SPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0xa000) +#define RT286_SPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0x9000) +#define RT286_HPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0xa000) +#define RT286_HPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0x9000) +#define RT286_F_DAC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7000) +#define RT286_F_RECMIX_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7100) +#define RT286_REC_MIC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7000) +#define RT286_REC_I2S_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7100) +#define RT286_REC_LINE_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7200) +#define RT286_REC_BEEP_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7300) +#define RT286_DAC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_DAC_OUT1, 0) +#define RT286_ADC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_ADC_IN1, 0) +#define RT286_COEF_INDEX\ + VERB_CMD(AC_VERB_SET_COEF_INDEX, RT286_VENDOR_REGISTERS, 0) +#define RT286_PROC_COEF\ + VERB_CMD(AC_VERB_SET_PROC_COEF, RT286_VENDOR_REGISTERS, 0) + +/* Index registers */ +#define RT286_A_BIAS_CTRL1 0x01 +#define RT286_A_BIAS_CTRL2 0x02 +#define RT286_POWER_CTRL1 0x03 +#define RT286_A_BIAS_CTRL3 0x04 +#define RT286_POWER_CTRL2 0x08 +#define RT286_I2S_CTRL1 0x09 +#define RT286_I2S_CTRL2 0x0a +#define RT286_CLK_DIV 0x0b +#define RT286_DC_GAIN 0x0d +#define RT286_POWER_CTRL3 0x0f +#define RT286_MIC1_DET_CTRL 0x19 +#define RT286_MISC_CTRL1 0x20 +#define RT286_IRQ_CTRL 0x33 +#define RT286_PLL_CTRL1 0x49 +#define RT286_CBJ_CTRL1 0x4f +#define RT286_CBJ_CTRL2 0x50 +#define RT286_PLL_CTRL 0x63 +#define RT286_DEPOP_CTRL1 0x66 +#define RT286_DEPOP_CTRL2 0x67 +#define RT286_DEPOP_CTRL3 0x68 +#define RT286_DEPOP_CTRL4 0x69 + +/* SPDIF (0x06) */ +#define RT286_SPDIF_SEL_SFT 0 +#define RT286_SPDIF_SEL_PCM0 0 +#define RT286_SPDIF_SEL_PCM1 1 +#define RT286_SPDIF_SEL_SPOUT 2 +#define RT286_SPDIF_SEL_PP 3 + +/* RECMIX (0x0b) */ +#define RT286_M_REC_BEEP_SFT 0 +#define RT286_M_REC_LINE1_SFT 1 +#define RT286_M_REC_MIC1_SFT 2 +#define RT286_M_REC_I2S_SFT 3 + +/* Front (0x0c) */ +#define RT286_M_FRONT_DAC_SFT 0 +#define RT286_M_FRONT_REC_SFT 1 + +/* SPK-OUT (0x14) */ +#define RT286_M_SPK_MUX_SFT 14 +#define RT286_SPK_SEL_MASK 0x1 +#define RT286_SPK_SEL_SFT 0 +#define RT286_SPK_SEL_F 0 +#define RT286_SPK_SEL_S 1 + +/* HP-OUT (0x21) */ +#define RT286_M_HP_MUX_SFT 14 +#define RT286_HP_SEL_MASK 0x1 +#define RT286_HP_SEL_SFT 0 +#define RT286_HP_SEL_F 0 +#define RT286_HP_SEL_S 1 + +/* ADC (0x22) (0x23) */ +#define RT286_ADC_SEL_MASK 0x7 +#define RT286_ADC_SEL_SFT 0 +#define RT286_ADC_SEL_SURR 0 +#define RT286_ADC_SEL_FRONT 1 +#define RT286_ADC_SEL_DMIC 2 +#define RT286_ADC_SEL_BEEP 4 +#define RT286_ADC_SEL_LINE1 5 +#define RT286_ADC_SEL_I2S 6 +#define RT286_ADC_SEL_MIC1 7 + +#define RT286_SCLK_S_MCLK 0 +#define RT286_SCLK_S_PLL 1 + +enum { + RT286_AIF1, + RT286_AIF2, + RT286_AIFS, +}; + +int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack); + +#endif /* __RT286_H__ */ + diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index de80e89b5fd8..6bc6efdec550 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -2215,14 +2215,8 @@ static int rt5640_i2c_probe(struct i2c_client *i2c, rt5640->hp_mute = 1; - ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5640, - rt5640_dai, ARRAY_SIZE(rt5640_dai)); - if (ret < 0) - goto err; - - return 0; -err: - return ret; + return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5640, + rt5640_dai, ARRAY_SIZE(rt5640_dai)); } static int rt5640_i2c_remove(struct i2c_client *i2c) diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 02147be2b302..a7762d0a623e 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2345,14 +2345,8 @@ static int rt5645_i2c_probe(struct i2c_client *i2c, } - ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5645, - rt5645_dai, ARRAY_SIZE(rt5645_dai)); - if (ret < 0) - goto err; - - return 0; -err: - return ret; + return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5645, + rt5645_dai, ARRAY_SIZE(rt5645_dai)); } static int rt5645_i2c_remove(struct i2c_client *i2c) diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 3d39f0b5b4a8..d739f83380a8 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -843,10 +843,8 @@ static int ldo_regulator_register(struct snd_soc_codec *codec, ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL); - if (!ldo) { - dev_err(codec->dev, "failed to allocate ldo_regulator\n"); + if (!ldo) return -ENOMEM; - } ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL); if (!ldo->desc.name) { @@ -1277,7 +1275,7 @@ static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) return ret; } - ret = devm_regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies), + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies), sgtl5000->supplies); if (ret) goto err_ldo_remove; @@ -1285,13 +1283,16 @@ static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies), sgtl5000->supplies); if (ret) - goto err_ldo_remove; + goto err_regulator_free; /* wait for all power rails bring up */ udelay(10); return 0; +err_regulator_free: + regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->supplies); err_ldo_remove: if (!external_vddd) ldo_regulator_remove(codec); @@ -1361,6 +1362,8 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) 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; @@ -1374,6 +1377,8 @@ static int sgtl5000_remove(struct snd_soc_codec *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; diff --git a/sound/soc/codecs/sigmadsp-i2c.c b/sound/soc/codecs/sigmadsp-i2c.c new file mode 100644 index 000000000000..246081aae8ca --- /dev/null +++ b/sound/soc/codecs/sigmadsp-i2c.c @@ -0,0 +1,35 @@ +/* + * Load Analog Devices SigmaStudio firmware files + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/i2c.h> +#include <linux/export.h> +#include <linux/module.h> + +#include "sigmadsp.h" + +static int sigma_action_write_i2c(void *control_data, + const struct sigma_action *sa, size_t len) +{ + return i2c_master_send(control_data, (const unsigned char *)&sa->addr, + len); +} + +int process_sigma_firmware(struct i2c_client *client, const char *name) +{ + struct sigma_firmware ssfw; + + ssfw.control_data = client; + ssfw.write = sigma_action_write_i2c; + + return _process_sigma_firmware(&client->dev, &ssfw, name); +} +EXPORT_SYMBOL(process_sigma_firmware); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("SigmaDSP I2C firmware loader"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp-regmap.c b/sound/soc/codecs/sigmadsp-regmap.c new file mode 100644 index 000000000000..f78ed8d2cfb2 --- /dev/null +++ b/sound/soc/codecs/sigmadsp-regmap.c @@ -0,0 +1,36 @@ +/* + * Load Analog Devices SigmaStudio firmware files + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/regmap.h> +#include <linux/export.h> +#include <linux/module.h> + +#include "sigmadsp.h" + +static int sigma_action_write_regmap(void *control_data, + const struct sigma_action *sa, size_t len) +{ + return regmap_raw_write(control_data, be16_to_cpu(sa->addr), + sa->payload, len - 2); +} + +int process_sigma_firmware_regmap(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct sigma_firmware ssfw; + + ssfw.control_data = regmap; + ssfw.write = sigma_action_write_regmap; + + return _process_sigma_firmware(dev, &ssfw, name); +} +EXPORT_SYMBOL(process_sigma_firmware_regmap); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("SigmaDSP regmap firmware loader"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c index 4068f2491232..f2de7e049bc6 100644 --- a/sound/soc/codecs/sigmadsp.c +++ b/sound/soc/codecs/sigmadsp.c @@ -34,23 +34,6 @@ enum { SIGMA_ACTION_END, }; -struct sigma_action { - u8 instr; - u8 len_hi; - __le16 len; - __be16 addr; - unsigned char payload[]; -} __packed; - -struct sigma_firmware { - const struct firmware *fw; - size_t pos; - - void *control_data; - int (*write)(void *control_data, const struct sigma_action *sa, - size_t len); -}; - static inline u32 sigma_action_len(struct sigma_action *sa) { return (sa->len_hi << 16) | le16_to_cpu(sa->len); @@ -138,7 +121,7 @@ process_sigma_actions(struct sigma_firmware *ssfw) return 0; } -static int _process_sigma_firmware(struct device *dev, +int _process_sigma_firmware(struct device *dev, struct sigma_firmware *ssfw, const char *name) { int ret; @@ -197,50 +180,6 @@ static int _process_sigma_firmware(struct device *dev, return ret; } - -#if IS_ENABLED(CONFIG_I2C) - -static int sigma_action_write_i2c(void *control_data, - const struct sigma_action *sa, size_t len) -{ - return i2c_master_send(control_data, (const unsigned char *)&sa->addr, - len); -} - -int process_sigma_firmware(struct i2c_client *client, const char *name) -{ - struct sigma_firmware ssfw; - - ssfw.control_data = client; - ssfw.write = sigma_action_write_i2c; - - return _process_sigma_firmware(&client->dev, &ssfw, name); -} -EXPORT_SYMBOL(process_sigma_firmware); - -#endif - -#if IS_ENABLED(CONFIG_REGMAP) - -static int sigma_action_write_regmap(void *control_data, - const struct sigma_action *sa, size_t len) -{ - return regmap_raw_write(control_data, be16_to_cpu(sa->addr), - sa->payload, len - 2); -} - -int process_sigma_firmware_regmap(struct device *dev, struct regmap *regmap, - const char *name) -{ - struct sigma_firmware ssfw; - - ssfw.control_data = regmap; - ssfw.write = sigma_action_write_regmap; - - return _process_sigma_firmware(dev, &ssfw, name); -} -EXPORT_SYMBOL(process_sigma_firmware_regmap); - -#endif +EXPORT_SYMBOL_GPL(_process_sigma_firmware); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp.h b/sound/soc/codecs/sigmadsp.h index e439cbd7af7d..c47cd23e9827 100644 --- a/sound/soc/codecs/sigmadsp.h +++ b/sound/soc/codecs/sigmadsp.h @@ -12,6 +12,26 @@ #include <linux/device.h> #include <linux/regmap.h> +struct sigma_action { + u8 instr; + u8 len_hi; + __le16 len; + __be16 addr; + unsigned char payload[]; +} __packed; + +struct sigma_firmware { + const struct firmware *fw; + size_t pos; + + void *control_data; + int (*write)(void *control_data, const struct sigma_action *sa, + size_t len); +}; + +int _process_sigma_firmware(struct device *dev, + struct sigma_firmware *ssfw, const char *name); + struct i2c_client; extern int process_sigma_firmware(struct i2c_client *client, const char *name); diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c index a40c4b0196a3..fcdf11026aff 100644 --- a/sound/soc/codecs/sta529.c +++ b/sound/soc/codecs/sta529.c @@ -380,10 +380,8 @@ static int sta529_i2c_probe(struct i2c_client *i2c, return -EINVAL; sta529 = devm_kzalloc(&i2c->dev, sizeof(struct sta529), GFP_KERNEL); - if (sta529 == NULL) { - dev_err(&i2c->dev, "Can not allocate memory\n"); + if (!sta529) return -ENOMEM; - } sta529->regmap = devm_regmap_init_i2c(i2c, &sta529_regmap); if (IS_ERR(sta529->regmap)) { diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index 23419109ecac..1cdae8ccc61b 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1178,7 +1178,7 @@ static void aic31xx_pdata_from_of(struct aic31xx_priv *aic31xx) } #endif /* CONFIG_OF */ -static void aic31xx_device_init(struct aic31xx_priv *aic31xx) +static int aic31xx_device_init(struct aic31xx_priv *aic31xx) { int ret, i; @@ -1197,7 +1197,7 @@ static void aic31xx_device_init(struct aic31xx_priv *aic31xx) "aic31xx-reset-pin"); if (ret < 0) { dev_err(aic31xx->dev, "not able to acquire gpio\n"); - return; + return ret; } } @@ -1210,6 +1210,7 @@ static void aic31xx_device_init(struct aic31xx_priv *aic31xx) if (ret != 0) dev_err(aic31xx->dev, "Failed to request supplies: %d\n", ret); + return ret; } static int aic31xx_i2c_probe(struct i2c_client *i2c, @@ -1239,7 +1240,9 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c, aic31xx->pdata.codec_type = id->driver_data; - aic31xx_device_init(aic31xx); + ret = aic31xx_device_init(aic31xx); + if (ret) + return ret; return snd_soc_register_codec(&i2c->dev, &soc_codec_driver_aic31xx, aic31xx_dai_driver, diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index e12fafbb1e09..f14b551b729e 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -879,7 +879,7 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, case SNDRV_PCM_FORMAT_S20_3LE: data |= (0x01 << 4); break; - case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_3LE: data |= (0x02 << 4); break; case SNDRV_PCM_FORMAT_S32_LE: @@ -1477,10 +1477,8 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, u32 value; aic3x = devm_kzalloc(&i2c->dev, sizeof(struct aic3x_priv), GFP_KERNEL); - if (aic3x == NULL) { - dev_err(&i2c->dev, "failed to create private data\n"); + if (!aic3x) return -ENOMEM; - } aic3x->regmap = devm_regmap_init_i2c(i2c, &aic3x_regmap); if (IS_ERR(aic3x->regmap)) { @@ -1498,10 +1496,8 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, } else if (np) { ai3x_setup = devm_kzalloc(&i2c->dev, sizeof(*ai3x_setup), GFP_KERNEL); - if (ai3x_setup == NULL) { - dev_err(&i2c->dev, "failed to create private data\n"); + if (!ai3x_setup) return -ENOMEM; - } ret = of_get_named_gpio(np, "gpio-reset", 0); if (ret >= 0) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index df3a7506c023..ff006cc95520 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1404,7 +1404,7 @@ static int dac33_soc_probe(struct snd_soc_codec *codec) if (dac33->irq >= 0) { ret = request_irq(dac33->irq, dac33_interrupt_handler, IRQF_TRIGGER_RISING, - codec->name, codec); + codec->component.name, codec); if (ret < 0) { dev_err(codec->dev, "Could not request IRQ%d (%d)\n", dac33->irq, ret); diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 8fc5a647453b..6fac9e034c48 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -381,10 +381,8 @@ static int tpa6130a2_probe(struct i2c_client *client, dev = &client->dev; data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); - if (data == NULL) { - dev_err(dev, "Can not allocate memory\n"); + if (!data) return -ENOMEM; - } if (pdata) { data->power_gpio = pdata->power_gpio; diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 69e12a311ba2..cf404505e7d1 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -344,17 +344,16 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) { struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); - int status = -1; if (enable) { twl4030->apll_enabled++; if (twl4030->apll_enabled == 1) - status = twl4030_audio_enable_resource( + twl4030_audio_enable_resource( TWL4030_AUDIO_RES_APLL); } else { twl4030->apll_enabled--; if (!twl4030->apll_enabled) - status = twl4030_audio_disable_resource( + twl4030_audio_disable_resource( TWL4030_AUDIO_RES_APLL); } } @@ -2162,10 +2161,8 @@ static int twl4030_soc_probe(struct snd_soc_codec *codec) twl4030 = devm_kzalloc(codec->dev, sizeof(struct twl4030_priv), GFP_KERNEL); - if (twl4030 == NULL) { - dev_err(codec->dev, "Can not allocate memory\n"); + if (!twl4030) return -ENOMEM; - } snd_soc_codec_set_drvdata(codec, twl4030); /* Set the defaults, and power up the codec */ twl4030->sysclk = twl4030_audio_get_mclk() / 1000; diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index edf27acc1d77..12fc0aed7503 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -479,7 +479,7 @@ static struct snd_soc_dai_driver uda134x_dai = { static int uda134x_soc_probe(struct snd_soc_codec *codec) { struct uda134x_priv *uda134x; - struct uda134x_platform_data *pd = codec->card->dev->platform_data; + struct uda134x_platform_data *pd = codec->component.card->dev->platform_data; const struct snd_soc_dapm_widget *widgets; unsigned num_widgets; diff --git a/sound/soc/codecs/wl1273.c b/sound/soc/codecs/wl1273.c index 4ead0dc02b87..5d8ba779085b 100644 --- a/sound/soc/codecs/wl1273.c +++ b/sound/soc/codecs/wl1273.c @@ -461,10 +461,8 @@ static int wl1273_probe(struct snd_soc_codec *codec) } wl1273 = kzalloc(sizeof(struct wl1273_priv), GFP_KERNEL); - if (wl1273 == NULL) { - dev_err(codec->dev, "Cannot allocate memory.\n"); + if (!wl1273) return -ENOMEM; - } wl1273->mode = WL1273_MODE_BT; wl1273->core = *core; diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c index 71ce3159a62e..982467c1d56a 100644 --- a/sound/soc/codecs/wm0010.c +++ b/sound/soc/codecs/wm0010.c @@ -413,7 +413,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_codec *codec) xfer = kzalloc(sizeof(*xfer), GFP_KERNEL); if (!xfer) { - dev_err(codec->dev, "Failed to allocate xfer\n"); ret = -ENOMEM; goto abort; } @@ -423,8 +422,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_codec *codec) out = kzalloc(len, GFP_KERNEL | GFP_DMA); if (!out) { - dev_err(codec->dev, - "Failed to allocate RX buffer\n"); ret = -ENOMEM; goto abort1; } @@ -432,8 +429,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_codec *codec) img = kzalloc(len, GFP_KERNEL | GFP_DMA); if (!img) { - dev_err(codec->dev, - "Failed to allocate image buffer\n"); ret = -ENOMEM; goto abort1; } @@ -526,14 +521,12 @@ static int wm0010_stage2_load(struct snd_soc_codec *codec) /* Copy to local buffer first as vmalloc causes problems for dma */ img = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); if (!img) { - dev_err(codec->dev, "Failed to allocate image buffer\n"); ret = -ENOMEM; goto abort2; } out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); if (!out) { - dev_err(codec->dev, "Failed to allocate output buffer\n"); ret = -ENOMEM; goto abort1; } @@ -679,11 +672,8 @@ static int wm0010_boot(struct snd_soc_codec *codec) } img_swap = kzalloc(len, GFP_KERNEL | GFP_DMA); - if (!img_swap) { - dev_err(codec->dev, - "Failed to allocate image buffer\n"); + if (!img_swap) goto abort; - } /* We need to re-order for 0010 */ byte_swap_64((u64 *)&pll_rec, img_swap, len); diff --git a/sound/soc/codecs/wm1250-ev1.c b/sound/soc/codecs/wm1250-ev1.c index 6e6b93d4696e..8011f75fb6cb 100644 --- a/sound/soc/codecs/wm1250-ev1.c +++ b/sound/soc/codecs/wm1250-ev1.c @@ -164,7 +164,6 @@ static int wm1250_ev1_pdata(struct i2c_client *i2c) wm1250 = devm_kzalloc(&i2c->dev, sizeof(*wm1250), GFP_KERNEL); if (!wm1250) { - dev_err(&i2c->dev, "Unable to allocate private data\n"); ret = -ENOMEM; goto err; } diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index a4c352cc3464..34ef65c52a7d 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -826,10 +826,8 @@ static int wm2000_i2c_probe(struct i2c_client *i2c, wm2000 = devm_kzalloc(&i2c->dev, sizeof(struct wm2000_priv), GFP_KERNEL); - if (wm2000 == NULL) { - dev_err(&i2c->dev, "Unable to allocate private data\n"); + if (!wm2000) return -ENOMEM; - } mutex_init(&wm2000->lock); diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 91a9ea2a2056..7bb0d36d4c54 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -735,8 +735,7 @@ WM5100_MIXER_CONTROLS("LHPF4", WM5100_HPLP4MIX_INPUT_1_SOURCE), static void wm5100_seq_notifier(struct snd_soc_dapm_context *dapm, enum snd_soc_dapm_type event, int subseq) { - struct snd_soc_codec *codec = container_of(dapm, - struct snd_soc_codec, dapm); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); struct wm5100_priv *wm5100 = snd_soc_codec_get_drvdata(codec); u16 val, expect, i; diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index 289b64d89abd..f60234962527 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -612,6 +612,62 @@ static int wm5102_sysclk_ev(struct snd_soc_dapm_widget *w, return 0; } +static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct arizona *arizona = dev_get_drvdata(codec->dev->parent); + uint16_t data; + + mutex_lock(&codec->mutex); + data = cpu_to_be16(arizona->dac_comp_coeff); + memcpy(ucontrol->value.bytes.data, &data, sizeof(data)); + mutex_unlock(&codec->mutex); + + return 0; +} + +static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct arizona *arizona = dev_get_drvdata(codec->dev->parent); + + mutex_lock(&codec->mutex); + memcpy(&arizona->dac_comp_coeff, ucontrol->value.bytes.data, + sizeof(arizona->dac_comp_coeff)); + arizona->dac_comp_coeff = be16_to_cpu(arizona->dac_comp_coeff); + mutex_unlock(&codec->mutex); + + return 0; +} + +static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct arizona *arizona = dev_get_drvdata(codec->dev->parent); + + mutex_lock(&codec->mutex); + ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; + mutex_unlock(&codec->mutex); + + return 0; +} + +static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct arizona *arizona = dev_get_drvdata(codec->dev->parent); + + mutex_lock(&codec->mutex); + arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; + mutex_unlock(&codec->mutex); + + return 0; +} + static const char *wm5102_osr_text[] = { "Low power", "Normal", "High performance", }; @@ -843,6 +899,12 @@ SOC_SINGLE_TLV("Noise Gate Threshold Volume", ARIZONA_NOISE_GATE_CONTROL, ARIZONA_NGATE_THR_SHIFT, 7, 1, ng_tlv), SOC_ENUM("Noise Gate Hold", arizona_ng_hold), +SND_SOC_BYTES_EXT("Output Compensation Coefficient", 2, + wm5102_out_comp_coeff_get, wm5102_out_comp_coeff_put), + +SOC_SINGLE_EXT("Output Compensation Switch", 0, 0, 1, 0, + wm5102_out_comp_switch_get, wm5102_out_comp_switch_put), + WM5102_NG_SRC("HPOUT1L", ARIZONA_NOISE_GATE_SELECT_1L), WM5102_NG_SRC("HPOUT1R", ARIZONA_NOISE_GATE_SELECT_1R), WM5102_NG_SRC("HPOUT2L", ARIZONA_NOISE_GATE_SELECT_2L), @@ -1653,6 +1715,7 @@ static struct snd_soc_dai_driver wm5102_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm5102-aif2", @@ -1674,6 +1737,7 @@ static struct snd_soc_dai_driver wm5102_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm5102-aif3", @@ -1695,6 +1759,7 @@ static struct snd_soc_dai_driver wm5102_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm5102-slim1", diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index 2e5fcb559e90..2f2ec26d831c 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -1485,6 +1485,7 @@ static struct snd_soc_dai_driver wm5110_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm5110-aif2", @@ -1506,6 +1507,7 @@ static struct snd_soc_dai_driver wm5110_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm5110-aif3", @@ -1527,6 +1529,7 @@ static struct snd_soc_dai_driver wm5110_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm5110-slim1", @@ -1596,6 +1599,7 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec) arizona_init_spk(codec); arizona_init_gpio(codec); + arizona_init_mono(codec); ret = snd_soc_add_codec_controls(codec, wm_adsp2_fw_controls, 8); if (ret != 0) diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 392285edb595..d9e634c55e81 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1341,21 +1341,18 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, { struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct wm8350 *wm8350 = priv->wm8350; - int irq; int ena; switch (which) { case WM8350_JDL: priv->hpl.jack = jack; priv->hpl.report = report; - irq = WM8350_IRQ_CODEC_JCK_DET_L; ena = WM8350_JDL_ENA; break; case WM8350_JDR: priv->hpr.jack = jack; priv->hpr.report = report; - irq = WM8350_IRQ_CODEC_JCK_DET_R; ena = WM8350_JDR_ENA; break; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index b84940c359a1..ec3250daa93e 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -281,8 +281,7 @@ static int wm8903_dcs_event(struct snd_soc_dapm_widget *w, static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm, enum snd_soc_dapm_type event, int subseq) { - struct snd_soc_codec *codec = container_of(dapm, - struct snd_soc_codec, dapm); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); int dcs_mode = WM8903_DCS_MODE_WRITE_STOP; int i, val; diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index f7c549949c54..e2792980ecf2 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2013,12 +2013,8 @@ static void wm8904_handle_pdata(struct snd_soc_codec *codec) /* We need an array of texts for the enum API */ wm8904->drc_texts = kmalloc(sizeof(char *) * pdata->num_drc_cfgs, GFP_KERNEL); - if (!wm8904->drc_texts) { - dev_err(codec->dev, - "Failed to allocate %d DRC config texts\n", - pdata->num_drc_cfgs); + if (!wm8904->drc_texts) return; - } for (i = 0; i < pdata->num_drc_cfgs; i++) wm8904->drc_texts[i] = pdata->drc_cfgs[i].name; diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c index b2ebb104d879..0dada7f0105e 100644 --- a/sound/soc/codecs/wm8958-dsp2.c +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -934,12 +934,8 @@ void wm8958_dsp2_init(struct snd_soc_codec *codec) /* We need an array of texts for the enum API */ wm8994->mbc_texts = kmalloc(sizeof(char *) * pdata->num_mbc_cfgs, GFP_KERNEL); - if (!wm8994->mbc_texts) { - dev_err(wm8994->hubs.codec->dev, - "Failed to allocate %d MBC config texts\n", - pdata->num_mbc_cfgs); + if (!wm8994->mbc_texts) return; - } for (i = 0; i < pdata->num_mbc_cfgs; i++) wm8994->mbc_texts[i] = pdata->mbc_cfgs[i].name; @@ -963,12 +959,8 @@ void wm8958_dsp2_init(struct snd_soc_codec *codec) /* We need an array of texts for the enum API */ wm8994->vss_texts = kmalloc(sizeof(char *) * pdata->num_vss_cfgs, GFP_KERNEL); - if (!wm8994->vss_texts) { - dev_err(wm8994->hubs.codec->dev, - "Failed to allocate %d VSS config texts\n", - pdata->num_vss_cfgs); + if (!wm8994->vss_texts) return; - } for (i = 0; i < pdata->num_vss_cfgs; i++) wm8994->vss_texts[i] = pdata->vss_cfgs[i].name; @@ -993,12 +985,8 @@ void wm8958_dsp2_init(struct snd_soc_codec *codec) /* We need an array of texts for the enum API */ wm8994->vss_hpf_texts = kmalloc(sizeof(char *) * pdata->num_vss_hpf_cfgs, GFP_KERNEL); - if (!wm8994->vss_hpf_texts) { - dev_err(wm8994->hubs.codec->dev, - "Failed to allocate %d VSS HPF config texts\n", - pdata->num_vss_hpf_cfgs); + if (!wm8994->vss_hpf_texts) return; - } for (i = 0; i < pdata->num_vss_hpf_cfgs; i++) wm8994->vss_hpf_texts[i] = pdata->vss_hpf_cfgs[i].name; @@ -1024,12 +1012,8 @@ void wm8958_dsp2_init(struct snd_soc_codec *codec) /* We need an array of texts for the enum API */ wm8994->enh_eq_texts = kmalloc(sizeof(char *) * pdata->num_enh_eq_cfgs, GFP_KERNEL); - if (!wm8994->enh_eq_texts) { - dev_err(wm8994->hubs.codec->dev, - "Failed to allocate %d enhanced EQ config texts\n", - pdata->num_enh_eq_cfgs); + if (!wm8994->enh_eq_texts) return; - } for (i = 0; i < pdata->num_enh_eq_cfgs; i++) wm8994->enh_eq_texts[i] = pdata->enh_eq_cfgs[i].name; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index a145d0431b63..e96349b04ba6 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -472,7 +472,7 @@ static int wm8960_add_widgets(struct snd_soc_codec *codec) * list each time to find the desired power state do so now * and save the result. */ - list_for_each_entry(w, &codec->card->widgets, list) { + list_for_each_entry(w, &codec->component.card->widgets, list) { if (w->dapm != &codec->dapm) continue; if (strcmp(w->name, "LOUT1 PGA") == 0) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 247b39013fba..39d8df040116 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -3296,12 +3296,8 @@ static void wm8994_handle_pdata(struct wm8994_priv *wm8994) /* We need an array of texts for the enum API */ wm8994->drc_texts = devm_kzalloc(wm8994->hubs.codec->dev, sizeof(char *) * pdata->num_drc_cfgs, GFP_KERNEL); - if (!wm8994->drc_texts) { - dev_err(wm8994->hubs.codec->dev, - "Failed to allocate %d DRC config texts\n", - pdata->num_drc_cfgs); + if (!wm8994->drc_texts) return; - } for (i = 0; i < pdata->num_drc_cfgs; i++) wm8994->drc_texts[i] = pdata->drc_cfgs[i].name; @@ -3505,6 +3501,7 @@ static irqreturn_t wm8994_mic_irq(int irq, void *data) return IRQ_HANDLED; } +/* Should be called with accdet_lock held */ static void wm1811_micd_stop(struct snd_soc_codec *codec) { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); @@ -3512,14 +3509,10 @@ static void wm1811_micd_stop(struct snd_soc_codec *codec) if (!wm8994->jackdet) return; - mutex_lock(&wm8994->accdet_lock); - snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, WM8958_MICD_ENA, 0); wm1811_jackdet_set_mode(codec, WM1811_JACKDET_MODE_JACK); - mutex_unlock(&wm8994->accdet_lock); - if (wm8994->wm8994->pdata.jd_ext_cap) snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2"); @@ -3560,10 +3553,10 @@ static void wm8958_open_circuit_work(struct work_struct *work) open_circuit_work.work); struct device *dev = wm8994->wm8994->dev; - wm1811_micd_stop(wm8994->hubs.codec); - mutex_lock(&wm8994->accdet_lock); + wm1811_micd_stop(wm8994->hubs.codec); + dev_dbg(dev, "Reporting open circuit\n"); wm8994->jack_mic = false; diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index 69266332760e..f16ff4f56923 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -620,15 +620,12 @@ static int bg_event(struct snd_soc_dapm_widget *w, static int cp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - int ret = 0; - switch (event) { case SND_SOC_DAPM_POST_PMU: msleep(5); break; default: WARN(1, "Invalid event %d\n", event); - ret = -EINVAL; } return 0; @@ -690,8 +687,7 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, u16 mask) static void wm8996_seq_notifier(struct snd_soc_dapm_context *dapm, enum snd_soc_dapm_type event, int subseq) { - struct snd_soc_codec *codec = container_of(dapm, - struct snd_soc_codec, dapm); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); struct wm8996_priv *wm8996 = snd_soc_codec_get_drvdata(codec); u16 val, mask; diff --git a/sound/soc/codecs/wm8997.c b/sound/soc/codecs/wm8997.c index bb9b47b956aa..ab33fe596519 100644 --- a/sound/soc/codecs/wm8997.c +++ b/sound/soc/codecs/wm8997.c @@ -967,6 +967,7 @@ static struct snd_soc_dai_driver wm8997_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm8997-aif2", @@ -988,6 +989,7 @@ static struct snd_soc_dai_driver wm8997_dai[] = { }, .ops = &arizona_dai_ops, .symmetric_rates = 1, + .symmetric_samplebits = 1, }, { .name = "wm8997-slim1", diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c index 87934171f063..a13f0725611a 100644 --- a/sound/soc/codecs/wm9090.c +++ b/sound/soc/codecs/wm9090.c @@ -613,10 +613,8 @@ static int wm9090_i2c_probe(struct i2c_client *i2c, int ret; wm9090 = devm_kzalloc(&i2c->dev, sizeof(*wm9090), GFP_KERNEL); - if (wm9090 == NULL) { - dev_err(&i2c->dev, "Can not allocate memory\n"); + if (!wm9090) return -ENOMEM; - } wm9090->regmap = devm_regmap_init_i2c(i2c, &wm9090_regmap); if (IS_ERR(wm9090->regmap)) { diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 060027182dcb..f412a9911a75 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -1382,7 +1382,7 @@ int wm_adsp1_event(struct snd_soc_dapm_widget *w, int ret; int val; - dsp->card = codec->card; + dsp->card = codec->component.card; switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -1617,7 +1617,7 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec); struct wm_adsp *dsp = &dsps[w->shift]; - dsp->card = codec->card; + dsp->card = codec->component.card; switch (event) { case SND_SOC_DAPM_PRE_PMU: @@ -1758,3 +1758,5 @@ int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs) return 0; } EXPORT_SYMBOL_GPL(wm_adsp2_init); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 916817fe6632..374537d5e179 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -183,10 +183,8 @@ static void wm_hubs_dcs_cache_set(struct snd_soc_codec *codec, u16 dcs_cfg) return; cache = devm_kzalloc(codec->dev, sizeof(*cache), GFP_KERNEL); - if (!cache) { - dev_err(codec->dev, "Failed to allocate DCS cache entry\n"); + if (!cache) return; - } cache->left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME); cache->left &= WM8993_HPOUT1L_VOL_MASK; diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 50a098749b9e..d69510c53239 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -1,12 +1,29 @@ config SND_DAVINCI_SOC - tristate "SoC Audio for TI DAVINCI or AM33XX/AM43XX chips" - depends on ARCH_DAVINCI || SOC_AM33XX || SOC_AM43XX + tristate "SoC Audio for TI DAVINCI" + depends on ARCH_DAVINCI + +config SND_EDMA_SOC + tristate "SoC Audio for Texas Instruments chips using eDMA (AM33XX/43XX)" + depends on SOC_AM33XX || SOC_AM43XX + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M here if you want audio support for TI SoC which uses eDMA. + The following line of SoCs are supported by this platform driver: + - AM335x + - AM437x/AM438x config SND_DAVINCI_SOC_I2S tristate config SND_DAVINCI_SOC_MCASP - tristate + tristate "Multichannel Audio Serial Port (McASP) support" + depends on SND_DAVINCI_SOC || SND_OMAP_SOC || SND_EDMA_SOC + help + Say Y or M here if you want to have support for McASP IP found in + various Texas Instruments SoCs like: + - daVinci devices + - Sitara line of SoCs (AM335x, AM438x, etc) + - DRA7x devices config SND_DAVINCI_SOC_VCIF tristate @@ -18,7 +35,7 @@ config SND_DAVINCI_SOC_GENERIC_EVM config SND_AM33XX_SOC_EVM tristate "SoC Audio for the AM33XX chip based boards" - depends on SND_DAVINCI_SOC && SOC_AM33XX && I2C + depends on SND_EDMA_SOC && SOC_AM33XX && I2C select SND_DAVINCI_SOC_GENERIC_EVM help Say Y or M if you want to add support for SoC audio on AM33XX diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile index 744d4d9a0184..09bf2ba92d38 100644 --- a/sound/soc/davinci/Makefile +++ b/sound/soc/davinci/Makefile @@ -1,10 +1,12 @@ # DAVINCI Platform Support snd-soc-davinci-objs := davinci-pcm.o +snd-soc-edma-objs := edma-pcm.o snd-soc-davinci-i2s-objs := davinci-i2s.o snd-soc-davinci-mcasp-objs:= davinci-mcasp.o snd-soc-davinci-vcif-objs:= davinci-vcif.o obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o +obj-$(CONFIG_SND_EDMA_SOC) += snd-soc-edma.o obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o obj-$(CONFIG_SND_DAVINCI_SOC_VCIF) += snd-soc-davinci-vcif.o diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 9afb14629a17..c28508da34cf 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -27,6 +27,7 @@ #include <linux/of_platform.h> #include <linux/of_device.h> +#include <sound/asoundef.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -36,6 +37,7 @@ #include <sound/omap-pcm.h> #include "davinci-pcm.h" +#include "edma-pcm.h" #include "davinci-mcasp.h" #define MCASP_MAX_AFIFO_DEPTH 64 @@ -63,6 +65,7 @@ struct davinci_mcasp { u8 num_serializer; u8 *serial_dir; u8 version; + u8 bclk_div; u16 bclk_lrclk_ratio; int streams; @@ -417,6 +420,7 @@ static int davinci_mcasp_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div ACLKXDIV(div - 1), ACLKXDIV_MASK); mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRDIV(div - 1), ACLKRDIV_MASK); + mcasp->bclk_div = div; break; case 2: /* BCLK/LRCLK ratio */ @@ -637,8 +641,12 @@ static int mcasp_i2s_hw_param(struct davinci_mcasp *mcasp, int stream) } /* S/PDIF */ -static int mcasp_dit_hw_param(struct davinci_mcasp *mcasp) +static int mcasp_dit_hw_param(struct davinci_mcasp *mcasp, + unsigned int rate) { + u32 cs_value = 0; + u8 *cs_bytes = (u8*) &cs_value; + /* Set the TX format : 24 bit right rotation, 32 bit slot, Pad 0 and LSB first */ mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXROT(6) | TXSSZ(15)); @@ -660,6 +668,46 @@ static int mcasp_dit_hw_param(struct davinci_mcasp *mcasp) /* Enable the DIT */ mcasp_set_bits(mcasp, DAVINCI_MCASP_TXDITCTL_REG, DITEN); + /* Set S/PDIF channel status bits */ + cs_bytes[0] = IEC958_AES0_CON_NOT_COPYRIGHT; + cs_bytes[1] = IEC958_AES1_CON_PCM_CODER; + + switch (rate) { + case 22050: + cs_bytes[3] |= IEC958_AES3_CON_FS_22050; + break; + case 24000: + cs_bytes[3] |= IEC958_AES3_CON_FS_24000; + break; + case 32000: + cs_bytes[3] |= IEC958_AES3_CON_FS_32000; + break; + case 44100: + cs_bytes[3] |= IEC958_AES3_CON_FS_44100; + break; + case 48000: + cs_bytes[3] |= IEC958_AES3_CON_FS_48000; + break; + case 88200: + cs_bytes[3] |= IEC958_AES3_CON_FS_88200; + break; + case 96000: + cs_bytes[3] |= IEC958_AES3_CON_FS_96000; + break; + case 176400: + cs_bytes[3] |= IEC958_AES3_CON_FS_176400; + break; + case 192000: + cs_bytes[3] |= IEC958_AES3_CON_FS_192000; + break; + default: + printk(KERN_WARNING "unsupported sampling rate: %d\n", rate); + return -EINVAL; + } + + mcasp_set_reg(mcasp, DAVINCI_MCASP_DITCSRA_REG, cs_value); + mcasp_set_reg(mcasp, DAVINCI_MCASP_DITCSRB_REG, cs_value); + return 0; } @@ -675,15 +723,22 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, int period_size = params_period_size(params); int ret; - /* If mcasp is BCLK master we need to set BCLK divider */ - if (mcasp->bclk_master) { + /* + * If mcasp is BCLK master, and a BCLK divider was not provided by + * the machine driver, we need to calculate the ratio. + */ + if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { unsigned int bclk_freq = snd_soc_params_to_bclk(params); + unsigned int div = mcasp->sysclk_freq / bclk_freq; if (mcasp->sysclk_freq % bclk_freq != 0) { - dev_err(mcasp->dev, "Can't produce required BCLK\n"); - return -EINVAL; + if (((mcasp->sysclk_freq / div) - bclk_freq) > + (bclk_freq - (mcasp->sysclk_freq / (div+1)))) + div++; + dev_warn(mcasp->dev, + "Inaccurate BCLK: %u Hz / %u != %u Hz\n", + mcasp->sysclk_freq, div, bclk_freq); } - davinci_mcasp_set_clkdiv( - cpu_dai, 1, mcasp->sysclk_freq / bclk_freq); + davinci_mcasp_set_clkdiv(cpu_dai, 1, div); } ret = mcasp_common_hw_param(mcasp, substream->stream, @@ -692,7 +747,7 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, return ret; if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) - ret = mcasp_dit_hw_param(mcasp); + ret = mcasp_dit_hw_param(mcasp, params_rate(params)); else ret = mcasp_i2s_hw_param(mcasp, substream->stream); @@ -720,6 +775,10 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, case SNDRV_PCM_FORMAT_U24_LE: case SNDRV_PCM_FORMAT_S24_LE: + dma_params->data_type = 4; + word_length = 24; + break; + case SNDRV_PCM_FORMAT_U32_LE: case SNDRV_PCM_FORMAT_S32_LE: dma_params->data_type = 4; @@ -778,7 +837,7 @@ static int davinci_mcasp_dai_probe(struct snd_soc_dai *dai) { struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); - if (mcasp->version == MCASP_VERSION_4) { + if (mcasp->version >= MCASP_VERSION_3) { /* Using dmaengine PCM */ dai->playback_dma_data = &mcasp->dma_data[SNDRV_PCM_STREAM_PLAYBACK]; @@ -1223,14 +1282,28 @@ static int davinci_mcasp_probe(struct platform_device *pdev) goto err; switch (mcasp->version) { +#if IS_BUILTIN(CONFIG_SND_DAVINCI_SOC) || \ + (IS_MODULE(CONFIG_SND_DAVINCI_SOC_MCASP) && \ + IS_MODULE(CONFIG_SND_DAVINCI_SOC)) case MCASP_VERSION_1: case MCASP_VERSION_2: - case MCASP_VERSION_3: ret = davinci_soc_platform_register(&pdev->dev); break; +#endif +#if IS_BUILTIN(CONFIG_SND_EDMA_SOC) || \ + (IS_MODULE(CONFIG_SND_DAVINCI_SOC_MCASP) && \ + IS_MODULE(CONFIG_SND_EDMA_SOC)) + case MCASP_VERSION_3: + ret = edma_pcm_platform_register(&pdev->dev); + break; +#endif +#if IS_BUILTIN(CONFIG_SND_OMAP_SOC) || \ + (IS_MODULE(CONFIG_SND_DAVINCI_SOC_MCASP) && \ + IS_MODULE(CONFIG_SND_OMAP_SOC)) case MCASP_VERSION_4: ret = omap_pcm_platform_register(&pdev->dev); break; +#endif default: dev_err(&pdev->dev, "Invalid McASP version: %d\n", mcasp->version); diff --git a/sound/soc/davinci/edma-pcm.c b/sound/soc/davinci/edma-pcm.c index d38afb1c61ae..605e643133db 100644 --- a/sound/soc/davinci/edma-pcm.c +++ b/sound/soc/davinci/edma-pcm.c @@ -28,8 +28,8 @@ static const struct snd_pcm_hardware edma_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | SNDRV_PCM_INFO_INTERLEAVED, .buffer_bytes_max = 128 * 1024, .period_bytes_min = 32, diff --git a/sound/soc/davinci/edma-pcm.h b/sound/soc/davinci/edma-pcm.h index 894c378c0f74..b0957744851c 100644 --- a/sound/soc/davinci/edma-pcm.h +++ b/sound/soc/davinci/edma-pcm.h @@ -20,6 +20,13 @@ #ifndef __EDMA_PCM_H__ #define __EDMA_PCM_H__ +#if IS_ENABLED(CONFIG_SND_EDMA_SOC) int edma_pcm_platform_register(struct device *dev); +#else +static inline int edma_pcm_platform_register(struct device *dev) +{ + return 0; +} +#endif /* CONFIG_SND_EDMA_SOC */ #endif /* __EDMA_PCM_H__ */ diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 37933629cbed..f54a8fc99291 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -2,9 +2,20 @@ menu "SoC Audio for Freescale CPUs" comment "Common SoC Audio options for Freescale CPUs:" +config SND_SOC_FSL_ASRC + tristate "Asynchronous Sample Rate Converter (ASRC) module support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Asynchronous Sample Rate Converter (ASRC) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + config SND_SOC_FSL_SAI tristate "Synchronous Audio Interface (SAI) module support" select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y if you want to add Synchronous Audio Interface (SAI) @@ -15,7 +26,7 @@ config SND_SOC_FSL_SAI config SND_SOC_FSL_SSI tristate "Synchronous Serial Interface module support" select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n - select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && ARCH_MXC + select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) select REGMAP_MMIO help Say Y if you want to add Synchronous Serial Interface (SSI) @@ -27,7 +38,7 @@ config SND_SOC_FSL_SPDIF tristate "Sony/Philips Digital Interface module support" select REGMAP_MMIO select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n - select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && ARCH_MXC + select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) help Say Y if you want to add Sony/Philips Digital Interface (SPDIF) support for the Freescale CPUs. @@ -37,6 +48,7 @@ config SND_SOC_FSL_SPDIF config SND_SOC_FSL_ESAI tristate "Enhanced Serial Audio Interface (ESAI) module support" select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_FSL_UTILS help Say Y if you want to add Enhanced Synchronous Audio Interface diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index db254e358c18..9ff59267eac9 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -11,6 +11,7 @@ snd-soc-p1022-rdk-objs := p1022_rdk.o obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support +snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o snd-soc-fsl-sai-objs := fsl_sai.o snd-soc-fsl-ssi-y := fsl_ssi.o snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o @@ -18,6 +19,7 @@ snd-soc-fsl-spdif-objs := fsl_spdif.o snd-soc-fsl-esai-objs := fsl_esai.o snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o +obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c new file mode 100644 index 000000000000..822110420b71 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc.c @@ -0,0 +1,995 @@ +/* + * Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <nicoleotsuka@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/pm_runtime.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_asrc.h" + +#define IDEAL_RATIO_DECIMAL_DEPTH 26 + +#define pair_err(fmt, ...) \ + dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + +#define pair_dbg(fmt, ...) \ + dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + +/* Sample rates are aligned with that defined in pcm.h file */ +static const u8 process_option[][8][2] = { + /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 5512Hz */ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 8kHz */ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 11025Hz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 16kHz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 22050Hz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, /* 32kHz */ + {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 44.1kHz */ + {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 48kHz */ + {{1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, /* 64kHz */ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 88.2kHz */ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 96kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 176kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 192kHz */ +}; + +/* Corresponding to process_option */ +static int supported_input_rate[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, + 96000, 176400, 192000, +}; + +static int supported_asrc_rate[] = { + 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000, +}; + +/** + * The following tables map the relationship between asrc_inclk/asrc_outclk in + * fsl_asrc.h and the registers of ASRCSR + */ +static unsigned char input_clk_map_imx35[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +static unsigned char output_clk_map_imx35[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +/* i.MX53 uses the same map for input and output */ +static unsigned char input_clk_map_imx53[] = { +/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ + 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd, +}; + +static unsigned char output_clk_map_imx53[] = { +/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ + 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd, +}; + +static unsigned char *clk_map[2]; + +/** + * Request ASRC pair + * + * It assigns pair by the order of A->C->B because allocation of pair B, + * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A + * while pair A and pair C are comparatively independent. + */ +static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) +{ + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct device *dev = &asrc_priv->pdev->dev; + unsigned long lock_flags; + int i, ret = 0; + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc_priv->pair[i] != NULL) + continue; + + index = i; + + if (i != ASRC_PAIR_B) + break; + } + + if (index == ASRC_INVALID_PAIR) { + dev_err(dev, "all pairs are busy now\n"); + ret = -EBUSY; + } else if (asrc_priv->channel_avail < channels) { + dev_err(dev, "can't afford required channels: %d\n", channels); + ret = -EINVAL; + } else { + asrc_priv->channel_avail -= channels; + asrc_priv->pair[index] = pair; + pair->channels = channels; + pair->index = index; + } + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + + return ret; +} + +/** + * Release ASRC pair + * + * It clears the resource from asrc_priv and releases the occupied channels. + */ +static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + asrc_priv->channel_avail += pair->channels; + asrc_priv->pair[index] = NULL; + pair->error = 0; + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); +} + +/** + * Configure input and output thresholds + */ +static void fsl_asrc_set_watermarks(struct fsl_asrc_pair *pair, u32 in, u32 out) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), + ASRMCRi_EXTTHRSHi_MASK | + ASRMCRi_INFIFO_THRESHOLD_MASK | + ASRMCRi_OUTFIFO_THRESHOLD_MASK, + ASRMCRi_EXTTHRSHi | + ASRMCRi_INFIFO_THRESHOLD(in) | + ASRMCRi_OUTFIFO_THRESHOLD(out)); +} + +/** + * Calculate the total divisor between asrck clock rate and sample rate + * + * It follows the formula clk_rate = samplerate * (2 ^ prescaler) * divider + */ +static u32 fsl_asrc_cal_asrck_divisor(struct fsl_asrc_pair *pair, u32 div) +{ + u32 ps; + + /* Calculate the divisors: prescaler [2^0, 2^7], divder [1, 8] */ + for (ps = 0; div > 8; ps++) + div >>= 1; + + return ((div - 1) << ASRCDRi_AxCPi_WIDTH) | ps; +} + +/** + * Calculate and set the ratio for Ideal Ratio mode only + * + * The ratio is a 32-bit fixed point value with 26 fractional bits. + */ +static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair, + int inrate, int outrate) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + unsigned long ratio; + int i; + + if (!outrate) { + pair_err("output rate should not be zero\n"); + return -EINVAL; + } + + /* Calculate the intergal part of the ratio */ + ratio = (inrate / outrate) << IDEAL_RATIO_DECIMAL_DEPTH; + + /* ... and then the 26 depth decimal part */ + inrate %= outrate; + + for (i = 1; i <= IDEAL_RATIO_DECIMAL_DEPTH; i++) { + inrate <<= 1; + + if (inrate < outrate) + continue; + + ratio |= 1 << (IDEAL_RATIO_DECIMAL_DEPTH - i); + inrate -= outrate; + + if (!inrate) + break; + } + + regmap_write(asrc_priv->regmap, REG_ASRIDRL(index), ratio); + regmap_write(asrc_priv->regmap, REG_ASRIDRH(index), ratio >> 24); + + return 0; +} + +/** + * Configure the assigned ASRC pair + * + * It configures those ASRC registers according to a configuration instance + * of struct asrc_config which includes in/output sample rate, width, channel + * and clock settings. + */ +static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) +{ + struct asrc_config *config = pair->config; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + u32 inrate, outrate, indiv, outdiv; + u32 clk_index[2], div[2]; + int in, out, channels; + struct clk *clk; + bool ideal; + + if (!config) { + pair_err("invalid pair config\n"); + return -EINVAL; + } + + /* Validate channels */ + if (config->channel_num < 1 || config->channel_num > 10) { + pair_err("does not support %d channels\n", config->channel_num); + return -EINVAL; + } + + /* Validate output width */ + if (config->output_word_width == ASRC_WIDTH_8_BIT) { + pair_err("does not support 8bit width output\n"); + return -EINVAL; + } + + inrate = config->input_sample_rate; + outrate = config->output_sample_rate; + ideal = config->inclk == INCLK_NONE; + + /* Validate input and output sample rates */ + for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++) + if (inrate == supported_input_rate[in]) + break; + + if (in == ARRAY_SIZE(supported_input_rate)) { + pair_err("unsupported input sample rate: %dHz\n", inrate); + return -EINVAL; + } + + for (out = 0; out < ARRAY_SIZE(supported_asrc_rate); out++) + if (outrate == supported_asrc_rate[out]) + break; + + if (out == ARRAY_SIZE(supported_asrc_rate)) { + pair_err("unsupported output sample rate: %dHz\n", outrate); + return -EINVAL; + } + + /* Validate input and output clock sources */ + clk_index[IN] = clk_map[IN][config->inclk]; + clk_index[OUT] = clk_map[OUT][config->outclk]; + + /* We only have output clock for ideal ratio mode */ + clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]]; + + div[IN] = clk_get_rate(clk) / inrate; + if (div[IN] == 0) { + pair_err("failed to support input sample rate %dHz by asrck_%x\n", + inrate, clk_index[ideal ? OUT : IN]); + return -EINVAL; + } + + clk = asrc_priv->asrck_clk[clk_index[OUT]]; + + /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */ + if (ideal) + div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE; + else + div[OUT] = clk_get_rate(clk) / outrate; + + if (div[OUT] == 0) { + pair_err("failed to support output sample rate %dHz by asrck_%x\n", + outrate, clk_index[OUT]); + return -EINVAL; + } + + /* Set the channel number */ + channels = config->channel_num; + + if (asrc_priv->channel_bits < 4) + channels /= 2; + + /* Update channels for current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCNCR, + ASRCNCR_ANCi_MASK(index, asrc_priv->channel_bits), + ASRCNCR_ANCi(index, channels, asrc_priv->channel_bits)); + + /* Default setting: Automatic selection for processing mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index)); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_USRi_MASK(index), 0); + + /* Set the input and output clock sources */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCSR, + ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), + ASRCSR_AICS(index, clk_index[IN]) | + ASRCSR_AOCS(index, clk_index[OUT])); + + /* Calculate the input clock divisors */ + indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]); + outdiv = fsl_asrc_cal_asrck_divisor(pair, div[OUT]); + + /* Suppose indiv and outdiv includes prescaler, so add its MASK too */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCDR(index), + ASRCDRi_AOCPi_MASK(index) | ASRCDRi_AICPi_MASK(index) | + ASRCDRi_AOCDi_MASK(index) | ASRCDRi_AICDi_MASK(index), + ASRCDRi_AOCP(index, outdiv) | ASRCDRi_AICP(index, indiv)); + + /* Implement word_width configurations */ + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index), + ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK, + ASRMCR1i_OW16(config->output_word_width) | + ASRMCR1i_IWD(config->input_word_width)); + + /* Enable BUFFER STALL */ + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), + ASRMCRi_BUFSTALLi_MASK, ASRMCRi_BUFSTALLi); + + /* Set default thresholds for input and output FIFO */ + fsl_asrc_set_watermarks(pair, ASRC_INPUTFIFO_THRESHOLD, + ASRC_INPUTFIFO_THRESHOLD); + + /* Configure the followings only for Ideal Ratio mode */ + if (!ideal) + return 0; + + /* Clear ASTSx bit to use Ideal Ratio mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ATSi_MASK(index), 0); + + /* Enable Ideal Ratio mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index), + ASRCTR_IDR(index) | ASRCTR_USR(index)); + + /* Apply configurations for pre- and post-processing */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, + ASRCFG_PREMODi_MASK(index) | ASRCFG_POSTMODi_MASK(index), + ASRCFG_PREMOD(index, process_option[in][out][0]) | + ASRCFG_POSTMOD(index, process_option[in][out][1])); + + return fsl_asrc_set_ideal_ratio(pair, inrate, outrate); +} + +/** + * Start the assigned ASRC pair + * + * It enables the assigned pair and makes it stopped at the stall level. + */ +static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + int reg, retry = 10, i; + + /* Enable the current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), ASRCTR_ASRCE(index)); + + /* Wait for status of initialization */ + do { + udelay(5); + regmap_read(asrc_priv->regmap, REG_ASRCFG, ®); + reg &= ASRCFG_INIRQi_MASK(index); + } while (!reg && --retry); + + /* Make the input fifo to ASRC STALL level */ + regmap_read(asrc_priv->regmap, REG_ASRCNCR, ®); + for (i = 0; i < pair->channels * 4; i++) + regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0); + + /* Enable overload interrupt */ + regmap_write(asrc_priv->regmap, REG_ASRIER, ASRIER_AOLIE); +} + +/** + * Stop the assigned ASRC pair + */ +static void fsl_asrc_stop_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + /* Stop the current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); +} + +/** + * Get DMA channel according to the pair and direction. + */ +struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + char name[4]; + + sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a'); + + return dma_request_slave_channel(&asrc_priv->pdev->dev, name); +} +EXPORT_SYMBOL_GPL(fsl_asrc_get_dma_channel); + +static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); + int width = snd_pcm_format_width(params_format(params)); + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + struct asrc_config config; + int word_width, ret; + + ret = fsl_asrc_request_pair(channels, pair); + if (ret) { + dev_err(dai->dev, "fail to request asrc pair\n"); + return ret; + } + + pair->config = &config; + + if (width == 16) + width = ASRC_WIDTH_16_BIT; + else + width = ASRC_WIDTH_24_BIT; + + if (asrc_priv->asrc_width == 16) + word_width = ASRC_WIDTH_16_BIT; + else + word_width = ASRC_WIDTH_24_BIT; + + config.pair = pair->index; + config.channel_num = channels; + config.inclk = INCLK_NONE; + config.outclk = OUTCLK_ASRCK1_CLK; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + config.input_word_width = width; + config.output_word_width = word_width; + config.input_sample_rate = rate; + config.output_sample_rate = asrc_priv->asrc_rate; + } else { + config.input_word_width = word_width; + config.output_word_width = width; + config.input_sample_rate = asrc_priv->asrc_rate; + config.output_sample_rate = rate; + } + + ret = fsl_asrc_config_pair(pair); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } + + return 0; +} + +static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + if (pair) + fsl_asrc_release_pair(pair); + + return 0; +} + +static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fsl_asrc_start_pair(pair); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fsl_asrc_stop_pair(pair); + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops fsl_asrc_dai_ops = { + .hw_params = fsl_asrc_dai_hw_params, + .hw_free = fsl_asrc_dai_hw_free, + .trigger = fsl_asrc_dai_trigger, +}; + +static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &asrc_priv->dma_params_tx, + &asrc_priv->dma_params_rx); + + return 0; +} + +#define FSL_ASRC_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE) + +static struct snd_soc_dai_driver fsl_asrc_dai = { + .probe = fsl_asrc_dai_probe, + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 10, + .rates = FSL_ASRC_RATES, + .formats = FSL_ASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 10, + .rates = FSL_ASRC_RATES, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_asrc_component = { + .name = "fsl-asrc-dai", +}; + +static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRCTR: + case REG_ASRIER: + case REG_ASRCNCR: + case REG_ASRCFG: + case REG_ASRCSR: + case REG_ASRCDR1: + case REG_ASRCDR2: + case REG_ASRSTR: + case REG_ASRPM1: + case REG_ASRPM2: + case REG_ASRPM3: + case REG_ASRPM4: + case REG_ASRPM5: + case REG_ASRTFR1: + case REG_ASRCCR: + case REG_ASRDOA: + case REG_ASRDOB: + case REG_ASRDOC: + case REG_ASRIDRHA: + case REG_ASRIDRLA: + case REG_ASRIDRHB: + case REG_ASRIDRLB: + case REG_ASRIDRHC: + case REG_ASRIDRLC: + case REG_ASR76K: + case REG_ASR56K: + case REG_ASRMCRA: + case REG_ASRFSTA: + case REG_ASRMCRB: + case REG_ASRFSTB: + case REG_ASRMCRC: + case REG_ASRFSTC: + case REG_ASRMCR1A: + case REG_ASRMCR1B: + case REG_ASRMCR1C: + return true; + default: + return false; + } +} + +static bool fsl_asrc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRSTR: + case REG_ASRDIA: + case REG_ASRDIB: + case REG_ASRDIC: + case REG_ASRDOA: + case REG_ASRDOB: + case REG_ASRDOC: + case REG_ASRFSTA: + case REG_ASRFSTB: + case REG_ASRFSTC: + case REG_ASRCFG: + return true; + default: + return false; + } +} + +static bool fsl_asrc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRCTR: + case REG_ASRIER: + case REG_ASRCNCR: + case REG_ASRCFG: + case REG_ASRCSR: + case REG_ASRCDR1: + case REG_ASRCDR2: + case REG_ASRSTR: + case REG_ASRPM1: + case REG_ASRPM2: + case REG_ASRPM3: + case REG_ASRPM4: + case REG_ASRPM5: + case REG_ASRTFR1: + case REG_ASRCCR: + case REG_ASRDIA: + case REG_ASRDIB: + case REG_ASRDIC: + case REG_ASRIDRHA: + case REG_ASRIDRLA: + case REG_ASRIDRHB: + case REG_ASRIDRLB: + case REG_ASRIDRHC: + case REG_ASRIDRLC: + case REG_ASR76K: + case REG_ASR56K: + case REG_ASRMCRA: + case REG_ASRMCRB: + case REG_ASRMCRC: + case REG_ASRMCR1A: + case REG_ASRMCR1B: + case REG_ASRMCR1C: + return true; + default: + return false; + } +} + +static struct regmap_config fsl_asrc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_ASRMCR1C, + .readable_reg = fsl_asrc_readable_reg, + .volatile_reg = fsl_asrc_volatile_reg, + .writeable_reg = fsl_asrc_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +/** + * Initialize ASRC registers with a default configurations + */ +static int fsl_asrc_init(struct fsl_asrc *asrc_priv) +{ + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ + regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN); + + /* Disable interrupt by default */ + regmap_write(asrc_priv->regmap, REG_ASRIER, 0x0); + + /* Apply recommended settings for parameters from Reference Manual */ + regmap_write(asrc_priv->regmap, REG_ASRPM1, 0x7fffff); + regmap_write(asrc_priv->regmap, REG_ASRPM2, 0x255555); + regmap_write(asrc_priv->regmap, REG_ASRPM3, 0xff7280); + regmap_write(asrc_priv->regmap, REG_ASRPM4, 0xff7280); + regmap_write(asrc_priv->regmap, REG_ASRPM5, 0xff7280); + + /* Base address for task queue FIFO. Set to 0x7C */ + regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1, + ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc)); + + /* Set the processing clock for 76KHz to 133M */ + regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6); + + /* Set the processing clock for 56KHz to 133M */ + return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947); +} + +/** + * Interrupt handler for ASRC + */ +static irqreturn_t fsl_asrc_isr(int irq, void *dev_id) +{ + struct fsl_asrc *asrc_priv = (struct fsl_asrc *)dev_id; + struct device *dev = &asrc_priv->pdev->dev; + enum asrc_pair_index index; + u32 status; + + regmap_read(asrc_priv->regmap, REG_ASRSTR, &status); + + /* Clean overload error */ + regmap_write(asrc_priv->regmap, REG_ASRSTR, ASRSTR_AOLE); + + /* + * We here use dev_dbg() for all exceptions because ASRC itself does + * not care if FIFO overflowed or underrun while a warning in the + * interrupt would result a ridged conversion. + */ + for (index = ASRC_PAIR_A; index < ASRC_PAIR_MAX_NUM; index++) { + if (!asrc_priv->pair[index]) + continue; + + if (status & ASRSTR_ATQOL) { + asrc_priv->pair[index]->error |= ASRC_TASK_Q_OVERLOAD; + dev_dbg(dev, "ASRC Task Queue FIFO overload\n"); + } + + if (status & ASRSTR_AOOL(index)) { + asrc_priv->pair[index]->error |= ASRC_OUTPUT_TASK_OVERLOAD; + pair_dbg("Output Task Overload\n"); + } + + if (status & ASRSTR_AIOL(index)) { + asrc_priv->pair[index]->error |= ASRC_INPUT_TASK_OVERLOAD; + pair_dbg("Input Task Overload\n"); + } + + if (status & ASRSTR_AODO(index)) { + asrc_priv->pair[index]->error |= ASRC_OUTPUT_BUFFER_OVERFLOW; + pair_dbg("Output Data Buffer has overflowed\n"); + } + + if (status & ASRSTR_AIDU(index)) { + asrc_priv->pair[index]->error |= ASRC_INPUT_BUFFER_UNDERRUN; + pair_dbg("Input Data Buffer has underflowed\n"); + } + } + + return IRQ_HANDLED; +} + +static int fsl_asrc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_asrc *asrc_priv; + struct resource *res; + void __iomem *regs; + int irq, ret, i; + char tmp[16]; + + asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL); + if (!asrc_priv) + return -ENOMEM; + + asrc_priv->pdev = pdev; + strcpy(asrc_priv->name, np->name); + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + asrc_priv->paddr = res->start; + + /* Register regmap and let it prepare core clock */ + if (of_property_read_bool(np, "big-endian")) + fsl_asrc_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG; + + asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs, + &fsl_asrc_regmap_config); + if (IS_ERR(asrc_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap\n"); + return PTR_ERR(asrc_priv->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", np->full_name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0, + asrc_priv->name, asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret); + return ret; + } + + asrc_priv->mem_clk = devm_clk_get(&pdev->dev, "mem"); + if (IS_ERR(asrc_priv->mem_clk)) { + dev_err(&pdev->dev, "failed to get mem clock\n"); + return PTR_ERR(asrc_priv->mem_clk); + } + + asrc_priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(asrc_priv->ipg_clk)) { + dev_err(&pdev->dev, "failed to get ipg clock\n"); + return PTR_ERR(asrc_priv->ipg_clk); + } + + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) { + sprintf(tmp, "asrck_%x", i); + asrc_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(asrc_priv->asrck_clk[i])) { + dev_err(&pdev->dev, "failed to get %s clock\n", tmp); + return PTR_ERR(asrc_priv->asrck_clk[i]); + } + } + + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx35-asrc")) { + asrc_priv->channel_bits = 3; + clk_map[IN] = input_clk_map_imx35; + clk_map[OUT] = output_clk_map_imx35; + } else { + asrc_priv->channel_bits = 4; + clk_map[IN] = input_clk_map_imx53; + clk_map[OUT] = output_clk_map_imx53; + } + + ret = fsl_asrc_init(asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to init asrc %d\n", ret); + return -EINVAL; + } + + asrc_priv->channel_avail = 10; + + ret = of_property_read_u32(np, "fsl,asrc-rate", + &asrc_priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "fsl,asrc-width", + &asrc_priv->asrc_width); + if (ret) { + dev_err(&pdev->dev, "failed to get output width\n"); + return -EINVAL; + } + + if (asrc_priv->asrc_width != 16 && asrc_priv->asrc_width != 24) { + dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n"); + asrc_priv->asrc_width = 24; + } + + platform_set_drvdata(pdev, asrc_priv); + pm_runtime_enable(&pdev->dev); + spin_lock_init(&asrc_priv->lock); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, + &fsl_asrc_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + ret = devm_snd_soc_register_platform(&pdev->dev, &fsl_asrc_platform); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC platform\n"); + return ret; + } + + dev_info(&pdev->dev, "driver registered\n"); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int fsl_asrc_runtime_resume(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int i; + + clk_prepare_enable(asrc_priv->mem_clk); + clk_prepare_enable(asrc_priv->ipg_clk); + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) + clk_prepare_enable(asrc_priv->asrck_clk[i]); + + return 0; +} + +static int fsl_asrc_runtime_suspend(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int i; + + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) + clk_disable_unprepare(asrc_priv->asrck_clk[i]); + clk_disable_unprepare(asrc_priv->ipg_clk); + clk_disable_unprepare(asrc_priv->mem_clk); + + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM_SLEEP +static int fsl_asrc_suspend(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + + regcache_cache_only(asrc_priv->regmap, true); + regcache_mark_dirty(asrc_priv->regmap); + + return 0; +} + +static int fsl_asrc_resume(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + u32 asrctr; + + /* Stop all pairs provisionally */ + regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, 0); + + /* Restore all registers */ + regcache_cache_only(asrc_priv->regmap, false); + regcache_sync(asrc_priv->regmap); + + /* Restart enabled pairs */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, asrctr); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_asrc_pm = { + SET_RUNTIME_PM_OPS(fsl_asrc_runtime_suspend, fsl_asrc_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_asrc_suspend, fsl_asrc_resume) +}; + +static const struct of_device_id fsl_asrc_ids[] = { + { .compatible = "fsl,imx35-asrc", }, + { .compatible = "fsl,imx53-asrc", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_asrc_ids); + +static struct platform_driver fsl_asrc_driver = { + .probe = fsl_asrc_probe, + .driver = { + .name = "fsl-asrc", + .of_match_table = fsl_asrc_ids, + .pm = &fsl_asrc_pm, + }, +}; +module_platform_driver(fsl_asrc_driver); + +MODULE_DESCRIPTION("Freescale ASRC ASoC driver"); +MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>"); +MODULE_ALIAS("platform:fsl-asrc"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h new file mode 100644 index 000000000000..a3f211f53c23 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc.h @@ -0,0 +1,461 @@ +/* + * fsl_asrc.h - Freescale ASRC ALSA SoC header file + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <nicoleotsuka@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 _FSL_ASRC_H +#define _FSL_ASRC_H + +#define IN 0 +#define OUT 1 + +#define ASRC_DMA_BUFFER_NUM 2 +#define ASRC_INPUTFIFO_THRESHOLD 32 +#define ASRC_OUTPUTFIFO_THRESHOLD 32 +#define ASRC_FIFO_THRESHOLD_MIN 0 +#define ASRC_FIFO_THRESHOLD_MAX 63 +#define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) +#define ASRC_MAX_BUFFER_SIZE (1024 * 48) +#define ASRC_OUTPUT_LAST_SAMPLE 8 + +#define IDEAL_RATIO_RATE 1000000 + +#define REG_ASRCTR 0x00 +#define REG_ASRIER 0x04 +#define REG_ASRCNCR 0x0C +#define REG_ASRCFG 0x10 +#define REG_ASRCSR 0x14 + +#define REG_ASRCDR1 0x18 +#define REG_ASRCDR2 0x1C +#define REG_ASRCDR(i) ((i < 2) ? REG_ASRCDR1 : REG_ASRCDR2) + +#define REG_ASRSTR 0x20 +#define REG_ASRRA 0x24 +#define REG_ASRRB 0x28 +#define REG_ASRRC 0x2C +#define REG_ASRPM1 0x40 +#define REG_ASRPM2 0x44 +#define REG_ASRPM3 0x48 +#define REG_ASRPM4 0x4C +#define REG_ASRPM5 0x50 +#define REG_ASRTFR1 0x54 +#define REG_ASRCCR 0x5C + +#define REG_ASRDIA 0x60 +#define REG_ASRDOA 0x64 +#define REG_ASRDIB 0x68 +#define REG_ASRDOB 0x6C +#define REG_ASRDIC 0x70 +#define REG_ASRDOC 0x74 +#define REG_ASRDI(i) (REG_ASRDIA + (i << 3)) +#define REG_ASRDO(i) (REG_ASRDOA + (i << 3)) +#define REG_ASRDx(x, i) (x == IN ? REG_ASRDI(i) : REG_ASRDO(i)) + +#define REG_ASRIDRHA 0x80 +#define REG_ASRIDRLA 0x84 +#define REG_ASRIDRHB 0x88 +#define REG_ASRIDRLB 0x8C +#define REG_ASRIDRHC 0x90 +#define REG_ASRIDRLC 0x94 +#define REG_ASRIDRH(i) (REG_ASRIDRHA + (i << 3)) +#define REG_ASRIDRL(i) (REG_ASRIDRLA + (i << 3)) + +#define REG_ASR76K 0x98 +#define REG_ASR56K 0x9C + +#define REG_ASRMCRA 0xA0 +#define REG_ASRFSTA 0xA4 +#define REG_ASRMCRB 0xA8 +#define REG_ASRFSTB 0xAC +#define REG_ASRMCRC 0xB0 +#define REG_ASRFSTC 0xB4 +#define REG_ASRMCR(i) (REG_ASRMCRA + (i << 3)) +#define REG_ASRFST(i) (REG_ASRFSTA + (i << 3)) + +#define REG_ASRMCR1A 0xC0 +#define REG_ASRMCR1B 0xC4 +#define REG_ASRMCR1C 0xC8 +#define REG_ASRMCR1(i) (REG_ASRMCR1A + (i << 2)) + + +/* REG0 0x00 REG_ASRCTR */ +#define ASRCTR_ATSi_SHIFT(i) (20 + i) +#define ASRCTR_ATSi_MASK(i) (1 << ASRCTR_ATSi_SHIFT(i)) +#define ASRCTR_ATS(i) (1 << ASRCTR_ATSi_SHIFT(i)) +#define ASRCTR_USRi_SHIFT(i) (14 + (i << 1)) +#define ASRCTR_USRi_MASK(i) (1 << ASRCTR_USRi_SHIFT(i)) +#define ASRCTR_USR(i) (1 << ASRCTR_USRi_SHIFT(i)) +#define ASRCTR_IDRi_SHIFT(i) (13 + (i << 1)) +#define ASRCTR_IDRi_MASK(i) (1 << ASRCTR_IDRi_SHIFT(i)) +#define ASRCTR_IDR(i) (1 << ASRCTR_IDRi_SHIFT(i)) +#define ASRCTR_SRST_SHIFT 4 +#define ASRCTR_SRST_MASK (1 << ASRCTR_SRST_SHIFT) +#define ASRCTR_SRST (1 << ASRCTR_SRST_SHIFT) +#define ASRCTR_ASRCEi_SHIFT(i) (1 + i) +#define ASRCTR_ASRCEi_MASK(i) (1 << ASRCTR_ASRCEi_SHIFT(i)) +#define ASRCTR_ASRCE(i) (1 << ASRCTR_ASRCEi_SHIFT(i)) +#define ASRCTR_ASRCEi_ALL_MASK (0x7 << ASRCTR_ASRCEi_SHIFT(0)) +#define ASRCTR_ASRCEN_SHIFT 0 +#define ASRCTR_ASRCEN_MASK (1 << ASRCTR_ASRCEN_SHIFT) +#define ASRCTR_ASRCEN (1 << ASRCTR_ASRCEN_SHIFT) + +/* REG1 0x04 REG_ASRIER */ +#define ASRIER_AFPWE_SHIFT 7 +#define ASRIER_AFPWE_MASK (1 << ASRIER_AFPWE_SHIFT) +#define ASRIER_AFPWE (1 << ASRIER_AFPWE_SHIFT) +#define ASRIER_AOLIE_SHIFT 6 +#define ASRIER_AOLIE_MASK (1 << ASRIER_AOLIE_SHIFT) +#define ASRIER_AOLIE (1 << ASRIER_AOLIE_SHIFT) +#define ASRIER_ADOEi_SHIFT(i) (3 + i) +#define ASRIER_ADOEi_MASK(i) (1 << ASRIER_ADOEi_SHIFT(i)) +#define ASRIER_ADOE(i) (1 << ASRIER_ADOEi_SHIFT(i)) +#define ASRIER_ADIEi_SHIFT(i) (0 + i) +#define ASRIER_ADIEi_MASK(i) (1 << ASRIER_ADIEi_SHIFT(i)) +#define ASRIER_ADIE(i) (1 << ASRIER_ADIEi_SHIFT(i)) + +/* REG2 0x0C REG_ASRCNCR */ +#define ASRCNCR_ANCi_SHIFT(i, b) (b * i) +#define ASRCNCR_ANCi_MASK(i, b) (((1 << b) - 1) << ASRCNCR_ANCi_SHIFT(i, b)) +#define ASRCNCR_ANCi(i, v, b) ((v << ASRCNCR_ANCi_SHIFT(i, b)) & ASRCNCR_ANCi_MASK(i, b)) + +/* REG3 0x10 REG_ASRCFG */ +#define ASRCFG_INIRQi_SHIFT(i) (21 + i) +#define ASRCFG_INIRQi_MASK(i) (1 << ASRCFG_INIRQi_SHIFT(i)) +#define ASRCFG_INIRQi (1 << ASRCFG_INIRQi_SHIFT(i)) +#define ASRCFG_NDPRi_SHIFT(i) (18 + i) +#define ASRCFG_NDPRi_MASK(i) (1 << ASRCFG_NDPRi_SHIFT(i)) +#define ASRCFG_NDPRi (1 << ASRCFG_NDPRi_SHIFT(i)) +#define ASRCFG_POSTMODi_SHIFT(i) (8 + (i << 2)) +#define ASRCFG_POSTMODi_WIDTH 2 +#define ASRCFG_POSTMODi_MASK(i) (((1 << ASRCFG_POSTMODi_WIDTH) - 1) << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMOD(i, v) ((v) << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_UP(i) (0 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_DCON(i) (1 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_DOWN(i) (2 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_PREMODi_SHIFT(i) (6 + (i << 2)) +#define ASRCFG_PREMODi_WIDTH 2 +#define ASRCFG_PREMODi_MASK(i) (((1 << ASRCFG_PREMODi_WIDTH) - 1) << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMOD(i, v) ((v) << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_UP(i) (0 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_DCON(i) (1 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_DOWN(i) (2 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_BYPASS(i) (3 << ASRCFG_PREMODi_SHIFT(i)) + +/* REG4 0x14 REG_ASRCSR */ +#define ASRCSR_AxCSi_WIDTH 4 +#define ASRCSR_AxCSi_MASK ((1 << ASRCSR_AxCSi_WIDTH) - 1) +#define ASRCSR_AOCSi_SHIFT(i) (12 + (i << 2)) +#define ASRCSR_AOCSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AOCSi_SHIFT(i)) +#define ASRCSR_AOCS(i, v) ((v) << ASRCSR_AOCSi_SHIFT(i)) +#define ASRCSR_AICSi_SHIFT(i) (i << 2) +#define ASRCSR_AICSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AICSi_SHIFT(i)) +#define ASRCSR_AICS(i, v) ((v) << ASRCSR_AICSi_SHIFT(i)) + +/* REG5&6 0x18 & 0x1C REG_ASRCDR1 & ASRCDR2 */ +#define ASRCDRi_AxCPi_WIDTH 3 +#define ASRCDRi_AICPi_SHIFT(i) (0 + (i % 2) * 6) +#define ASRCDRi_AICPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICPi_SHIFT(i)) +#define ASRCDRi_AICP(i, v) ((v) << ASRCDRi_AICPi_SHIFT(i)) +#define ASRCDRi_AICDi_SHIFT(i) (3 + (i % 2) * 6) +#define ASRCDRi_AICDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICDi_SHIFT(i)) +#define ASRCDRi_AICD(i, v) ((v) << ASRCDRi_AICDi_SHIFT(i)) +#define ASRCDRi_AOCPi_SHIFT(i) ((i < 2) ? 12 + i * 6 : 6) +#define ASRCDRi_AOCPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCPi_SHIFT(i)) +#define ASRCDRi_AOCP(i, v) ((v) << ASRCDRi_AOCPi_SHIFT(i)) +#define ASRCDRi_AOCDi_SHIFT(i) ((i < 2) ? 15 + i * 6 : 9) +#define ASRCDRi_AOCDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCDi_SHIFT(i)) +#define ASRCDRi_AOCD(i, v) ((v) << ASRCDRi_AOCDi_SHIFT(i)) + +/* REG7 0x20 REG_ASRSTR */ +#define ASRSTR_DSLCNT_SHIFT 21 +#define ASRSTR_DSLCNT_MASK (1 << ASRSTR_DSLCNT_SHIFT) +#define ASRSTR_DSLCNT (1 << ASRSTR_DSLCNT_SHIFT) +#define ASRSTR_ATQOL_SHIFT 20 +#define ASRSTR_ATQOL_MASK (1 << ASRSTR_ATQOL_SHIFT) +#define ASRSTR_ATQOL (1 << ASRSTR_ATQOL_SHIFT) +#define ASRSTR_AOOLi_SHIFT(i) (17 + i) +#define ASRSTR_AOOLi_MASK(i) (1 << ASRSTR_AOOLi_SHIFT(i)) +#define ASRSTR_AOOL(i) (1 << ASRSTR_AOOLi_SHIFT(i)) +#define ASRSTR_AIOLi_SHIFT(i) (14 + i) +#define ASRSTR_AIOLi_MASK(i) (1 << ASRSTR_AIOLi_SHIFT(i)) +#define ASRSTR_AIOL(i) (1 << ASRSTR_AIOLi_SHIFT(i)) +#define ASRSTR_AODOi_SHIFT(i) (11 + i) +#define ASRSTR_AODOi_MASK(i) (1 << ASRSTR_AODOi_SHIFT(i)) +#define ASRSTR_AODO(i) (1 << ASRSTR_AODOi_SHIFT(i)) +#define ASRSTR_AIDUi_SHIFT(i) (8 + i) +#define ASRSTR_AIDUi_MASK(i) (1 << ASRSTR_AIDUi_SHIFT(i)) +#define ASRSTR_AIDU(i) (1 << ASRSTR_AIDUi_SHIFT(i)) +#define ASRSTR_FPWT_SHIFT 7 +#define ASRSTR_FPWT_MASK (1 << ASRSTR_FPWT_SHIFT) +#define ASRSTR_FPWT (1 << ASRSTR_FPWT_SHIFT) +#define ASRSTR_AOLE_SHIFT 6 +#define ASRSTR_AOLE_MASK (1 << ASRSTR_AOLE_SHIFT) +#define ASRSTR_AOLE (1 << ASRSTR_AOLE_SHIFT) +#define ASRSTR_AODEi_SHIFT(i) (3 + i) +#define ASRSTR_AODFi_MASK(i) (1 << ASRSTR_AODEi_SHIFT(i)) +#define ASRSTR_AODF(i) (1 << ASRSTR_AODEi_SHIFT(i)) +#define ASRSTR_AIDEi_SHIFT(i) (0 + i) +#define ASRSTR_AIDEi_MASK(i) (1 << ASRSTR_AIDEi_SHIFT(i)) +#define ASRSTR_AIDE(i) (1 << ASRSTR_AIDEi_SHIFT(i)) + +/* REG10 0x54 REG_ASRTFR1 */ +#define ASRTFR1_TF_BASE_WIDTH 7 +#define ASRTFR1_TF_BASE_SHIFT 6 +#define ASRTFR1_TF_BASE_MASK (((1 << ASRTFR1_TF_BASE_WIDTH) - 1) << ASRTFR1_TF_BASE_SHIFT) +#define ASRTFR1_TF_BASE(i) ((i) << ASRTFR1_TF_BASE_SHIFT) + +/* + * REG22 0xA0 REG_ASRMCRA + * REG24 0xA8 REG_ASRMCRB + * REG26 0xB0 REG_ASRMCRC + */ +#define ASRMCRi_ZEROBUFi_SHIFT 23 +#define ASRMCRi_ZEROBUFi_MASK (1 << ASRMCRi_ZEROBUFi_SHIFT) +#define ASRMCRi_ZEROBUFi (1 << ASRMCRi_ZEROBUFi_SHIFT) +#define ASRMCRi_EXTTHRSHi_SHIFT 22 +#define ASRMCRi_EXTTHRSHi_MASK (1 << ASRMCRi_EXTTHRSHi_SHIFT) +#define ASRMCRi_EXTTHRSHi (1 << ASRMCRi_EXTTHRSHi_SHIFT) +#define ASRMCRi_BUFSTALLi_SHIFT 21 +#define ASRMCRi_BUFSTALLi_MASK (1 << ASRMCRi_BUFSTALLi_SHIFT) +#define ASRMCRi_BUFSTALLi (1 << ASRMCRi_BUFSTALLi_SHIFT) +#define ASRMCRi_BYPASSPOLYi_SHIFT 20 +#define ASRMCRi_BYPASSPOLYi_MASK (1 << ASRMCRi_BYPASSPOLYi_SHIFT) +#define ASRMCRi_BYPASSPOLYi (1 << ASRMCRi_BYPASSPOLYi_SHIFT) +#define ASRMCRi_OUTFIFO_THRESHOLD_WIDTH 6 +#define ASRMCRi_OUTFIFO_THRESHOLD_SHIFT 12 +#define ASRMCRi_OUTFIFO_THRESHOLD_MASK (((1 << ASRMCRi_OUTFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) +#define ASRMCRi_OUTFIFO_THRESHOLD(v) (((v) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) & ASRMCRi_OUTFIFO_THRESHOLD_MASK) +#define ASRMCRi_RSYNIFi_SHIFT 11 +#define ASRMCRi_RSYNIFi_MASK (1 << ASRMCRi_RSYNIFi_SHIFT) +#define ASRMCRi_RSYNIFi (1 << ASRMCRi_RSYNIFi_SHIFT) +#define ASRMCRi_RSYNOFi_SHIFT 10 +#define ASRMCRi_RSYNOFi_MASK (1 << ASRMCRi_RSYNOFi_SHIFT) +#define ASRMCRi_RSYNOFi (1 << ASRMCRi_RSYNOFi_SHIFT) +#define ASRMCRi_INFIFO_THRESHOLD_WIDTH 6 +#define ASRMCRi_INFIFO_THRESHOLD_SHIFT 0 +#define ASRMCRi_INFIFO_THRESHOLD_MASK (((1 << ASRMCRi_INFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) +#define ASRMCRi_INFIFO_THRESHOLD(v) (((v) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) & ASRMCRi_INFIFO_THRESHOLD_MASK) + +/* + * REG23 0xA4 REG_ASRFSTA + * REG25 0xAC REG_ASRFSTB + * REG27 0xB4 REG_ASRFSTC + */ +#define ASRFSTi_OAFi_SHIFT 23 +#define ASRFSTi_OAFi_MASK (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_OAFi (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_OUTPUT_FIFO_WIDTH 7 +#define ASRFSTi_OUTPUT_FIFO_SHIFT 12 +#define ASRFSTi_OUTPUT_FIFO_MASK (((1 << ASRFSTi_OUTPUT_FIFO_WIDTH) - 1) << ASRFSTi_OUTPUT_FIFO_SHIFT) +#define ASRFSTi_IAEi_SHIFT 11 +#define ASRFSTi_IAEi_MASK (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_IAEi (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_INPUT_FIFO_WIDTH 7 +#define ASRFSTi_INPUT_FIFO_SHIFT 0 +#define ASRFSTi_INPUT_FIFO_MASK ((1 << ASRFSTi_INPUT_FIFO_WIDTH) - 1) + +/* REG28 0xC0 & 0xC4 & 0xC8 REG_ASRMCR1i */ +#define ASRMCR1i_IWD_WIDTH 3 +#define ASRMCR1i_IWD_SHIFT 9 +#define ASRMCR1i_IWD_MASK (((1 << ASRMCR1i_IWD_WIDTH) - 1) << ASRMCR1i_IWD_SHIFT) +#define ASRMCR1i_IWD(v) ((v) << ASRMCR1i_IWD_SHIFT) +#define ASRMCR1i_IMSB_SHIFT 8 +#define ASRMCR1i_IMSB_MASK (1 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_IMSB_MSB (1 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_IMSB_LSB (0 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_OMSB_SHIFT 2 +#define ASRMCR1i_OMSB_MASK (1 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OMSB_MSB (1 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OMSB_LSB (0 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OSGN_SHIFT 1 +#define ASRMCR1i_OSGN_MASK (1 << ASRMCR1i_OSGN_SHIFT) +#define ASRMCR1i_OSGN (1 << ASRMCR1i_OSGN_SHIFT) +#define ASRMCR1i_OW16_SHIFT 0 +#define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT) +#define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT) + + +enum asrc_pair_index { + ASRC_INVALID_PAIR = -1, + ASRC_PAIR_A = 0, + ASRC_PAIR_B = 1, + ASRC_PAIR_C = 2, +}; + +#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) + +enum asrc_inclk { + INCLK_NONE = 0x03, + INCLK_ESAI_RX = 0x00, + INCLK_SSI1_RX = 0x01, + INCLK_SSI2_RX = 0x02, + INCLK_SSI3_RX = 0x07, + INCLK_SPDIF_RX = 0x04, + INCLK_MLB_CLK = 0x05, + INCLK_PAD = 0x06, + INCLK_ESAI_TX = 0x08, + INCLK_SSI1_TX = 0x09, + INCLK_SSI2_TX = 0x0a, + INCLK_SSI3_TX = 0x0b, + INCLK_SPDIF_TX = 0x0c, + INCLK_ASRCK1_CLK = 0x0f, +}; + +enum asrc_outclk { + OUTCLK_NONE = 0x03, + OUTCLK_ESAI_TX = 0x00, + OUTCLK_SSI1_TX = 0x01, + OUTCLK_SSI2_TX = 0x02, + OUTCLK_SSI3_TX = 0x07, + OUTCLK_SPDIF_TX = 0x04, + OUTCLK_MLB_CLK = 0x05, + OUTCLK_PAD = 0x06, + OUTCLK_ESAI_RX = 0x08, + OUTCLK_SSI1_RX = 0x09, + OUTCLK_SSI2_RX = 0x0a, + OUTCLK_SSI3_RX = 0x0b, + OUTCLK_SPDIF_RX = 0x0c, + OUTCLK_ASRCK1_CLK = 0x0f, +}; + +#define ASRC_CLK_MAX_NUM 16 + +enum asrc_word_width { + ASRC_WIDTH_24_BIT = 0, + ASRC_WIDTH_16_BIT = 1, + ASRC_WIDTH_8_BIT = 2, +}; + +struct asrc_config { + enum asrc_pair_index pair; + unsigned int channel_num; + unsigned int buffer_num; + unsigned int dma_buffer_size; + unsigned int input_sample_rate; + unsigned int output_sample_rate; + enum asrc_word_width input_word_width; + enum asrc_word_width output_word_width; + enum asrc_inclk inclk; + enum asrc_outclk outclk; +}; + +struct asrc_req { + unsigned int chn_num; + enum asrc_pair_index index; +}; + +struct asrc_querybuf { + unsigned int buffer_index; + unsigned int input_length; + unsigned int output_length; + unsigned long input_offset; + unsigned long output_offset; +}; + +struct asrc_convert_buffer { + void *input_buffer_vaddr; + void *output_buffer_vaddr; + unsigned int input_buffer_length; + unsigned int output_buffer_length; +}; + +struct asrc_status_flags { + enum asrc_pair_index index; + unsigned int overload_error; +}; + +enum asrc_error_status { + ASRC_TASK_Q_OVERLOAD = 0x01, + ASRC_OUTPUT_TASK_OVERLOAD = 0x02, + ASRC_INPUT_TASK_OVERLOAD = 0x04, + ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, + ASRC_INPUT_BUFFER_UNDERRUN = 0x10, +}; + +struct dma_block { + dma_addr_t dma_paddr; + void *dma_vaddr; + unsigned int length; +}; + +/** + * fsl_asrc_pair: ASRC Pair private data + * + * @asrc_priv: pointer to its parent module + * @config: configuration profile + * @error: error record + * @index: pair index (ASRC_PAIR_A, ASRC_PAIR_B, ASRC_PAIR_C) + * @channels: occupied channel number + * @desc: input and output dma descriptors + * @dma_chan: inputer and output DMA channels + * @dma_data: private dma data + * @pos: hardware pointer position + * @private: pair private area + */ +struct fsl_asrc_pair { + struct fsl_asrc *asrc_priv; + struct asrc_config *config; + unsigned int error; + + enum asrc_pair_index index; + unsigned int channels; + + struct dma_async_tx_descriptor *desc[2]; + struct dma_chan *dma_chan[2]; + struct imx_dma_data dma_data; + unsigned int pos; + + void *private; +}; + +/** + * fsl_asrc_pair: ASRC private data + * + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @pdev: platform device pointer + * @regmap: regmap handler + * @paddr: physical address to the base address of registers + * @mem_clk: clock source to access register + * @ipg_clk: clock source to drive peripheral + * @asrck_clk: clock sources to driver ASRC internal logic + * @lock: spin lock for resource protection + * @pair: pair pointers + * @channel_bits: width of ASRCNCR register for each pair + * @channel_avail: non-occupied channel numbers + * @asrc_rate: default sample rate for ASoC Back-Ends + * @asrc_width: default sample width for ASoC Back-Ends + * @name: driver name + */ +struct fsl_asrc { + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct platform_device *pdev; + struct regmap *regmap; + unsigned long paddr; + struct clk *mem_clk; + struct clk *ipg_clk; + struct clk *asrck_clk[ASRC_CLK_MAX_NUM]; + spinlock_t lock; + + struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM]; + unsigned int channel_bits; + unsigned int channel_avail; + + int asrc_rate; + int asrc_width; + + char name[32]; +}; + +extern struct snd_soc_platform_driver fsl_asrc_platform; +struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir); +#endif /* _FSL_ASRC_H */ diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c new file mode 100644 index 000000000000..ffc000bc1f15 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -0,0 +1,391 @@ +/* + * Freescale ASRC ALSA SoC Platform (DMA) driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <nicoleotsuka@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/platform_data/dma-imx.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_asrc.h" + +#define FSL_ASRC_DMABUF_SIZE (256 * 1024) + +static struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65535, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool filter(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static void fsl_asrc_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + pair->pos += snd_pcm_lib_period_bytes(substream); + if (pair->pos >= snd_pcm_lib_buffer_bytes(substream)) + pair->pos = 0; + + snd_pcm_period_elapsed(substream); +} + +static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) +{ + u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct device *dev = rtd->platform->dev; + unsigned long flags = DMA_CTRL_ACK; + + /* Prepare and submit Front-End DMA channel */ + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + pair->pos = 0; + pair->desc[!dir] = dmaengine_prep_dma_cyclic( + pair->dma_chan[!dir], runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dir == OUT ? DMA_TO_DEVICE : DMA_FROM_DEVICE, flags); + if (!pair->desc[!dir]) { + dev_err(dev, "failed to prepare slave DMA for Front-End\n"); + return -ENOMEM; + } + + pair->desc[!dir]->callback = fsl_asrc_dma_complete; + pair->desc[!dir]->callback_param = substream; + + dmaengine_submit(pair->desc[!dir]); + + /* Prepare and submit Back-End DMA channel */ + pair->desc[dir] = dmaengine_prep_dma_cyclic( + pair->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0); + if (!pair->desc[dir]) { + dev_err(dev, "failed to prepare slave DMA for Back-End\n"); + return -ENOMEM; + } + + dmaengine_submit(pair->desc[dir]); + + return 0; +} + +static int fsl_asrc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_asrc_dma_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(pair->dma_chan[IN]); + dma_async_issue_pending(pair->dma_chan[OUT]); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(pair->dma_chan[OUT]); + dmaengine_terminate_all(pair->dma_chan[IN]); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL; + struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct dma_slave_config config_fe, config_be; + enum asrc_pair_index index = pair->index; + struct device *dev = rtd->platform->dev; + int stream = substream->stream; + struct imx_dma_data *tmp_data; + struct snd_soc_dpcm *dpcm; + struct dma_chan *tmp_chan; + struct device *dev_be; + u8 dir = tx ? OUT : IN; + dma_cap_mask_t mask; + int ret; + + /* Fetch the Back-End dma_data from DPCM */ + list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *substream_be; + struct snd_soc_dai *dai = be->cpu_dai; + + if (dpcm->fe != rtd) + continue; + + substream_be = snd_soc_dpcm_get_substream(be, stream); + dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be); + dev_be = dai->dev; + break; + } + + if (!dma_params_be) { + dev_err(dev, "failed to get the substream of Back-End\n"); + return -EINVAL; + } + + /* Override dma_data of the Front-End and config its dmaengine */ + dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_params_fe->addr = asrc_priv->paddr + REG_ASRDx(!dir, index); + dma_params_fe->maxburst = dma_params_be->maxburst; + + pair->dma_chan[!dir] = fsl_asrc_get_dma_channel(pair, !dir); + if (!pair->dma_chan[!dir]) { + dev_err(dev, "failed to request DMA channel\n"); + return -EINVAL; + } + + memset(&config_fe, 0, sizeof(config_fe)); + ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe); + if (ret) { + dev_err(dev, "failed to prepare DMA config for Front-End\n"); + return ret; + } + + ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe); + if (ret) { + dev_err(dev, "failed to config DMA channel for Front-End\n"); + return ret; + } + + /* Request and config DMA channel for Back-End */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_CYCLIC, mask); + + /* Get DMA request of Back-End */ + tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); + tmp_data = tmp_chan->private; + pair->dma_data.dma_request = tmp_data->dma_request; + dma_release_channel(tmp_chan); + + /* Get DMA request of Front-End */ + tmp_chan = fsl_asrc_get_dma_channel(pair, dir); + tmp_data = tmp_chan->private; + pair->dma_data.dma_request2 = tmp_data->dma_request; + pair->dma_data.peripheral_type = tmp_data->peripheral_type; + pair->dma_data.priority = tmp_data->priority; + dma_release_channel(tmp_chan); + + pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data); + if (!pair->dma_chan[dir]) { + dev_err(dev, "failed to request DMA channel for Back-End\n"); + return -EINVAL; + } + + if (asrc_priv->asrc_width == 16) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + + config_be.direction = DMA_DEV_TO_DEV; + config_be.src_addr_width = buswidth; + config_be.src_maxburst = dma_params_be->maxburst; + config_be.dst_addr_width = buswidth; + config_be.dst_maxburst = dma_params_be->maxburst; + + if (tx) { + config_be.src_addr = asrc_priv->paddr + REG_ASRDO(index); + config_be.dst_addr = dma_params_be->addr; + } else { + config_be.dst_addr = asrc_priv->paddr + REG_ASRDI(index); + config_be.src_addr = dma_params_be->addr; + } + + ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be); + if (ret) { + dev_err(dev, "failed to config DMA channel for Back-End\n"); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int fsl_asrc_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = NULL; + pair->dma_chan[OUT] = NULL; + + return 0; +} + +static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = rtd->platform->dev; + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + struct fsl_asrc_pair *pair; + + pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); + if (!pair) { + dev_err(dev, "failed to allocate pair\n"); + return -ENOMEM; + } + + pair->asrc_priv = asrc_priv; + + runtime->private_data = pair; + + snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + return 0; +} + +static int fsl_asrc_dma_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct fsl_asrc *asrc_priv; + + if (!pair) + return 0; + + asrc_priv = pair->asrc_priv; + + if (asrc_priv->pair[pair->index] == pair) + asrc_priv->pair[pair->index] = NULL; + + kfree(pair); + + return 0; +} + +static snd_pcm_uframes_t fsl_asrc_dma_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + return bytes_to_frames(substream->runtime, pair->pos); +} + +static struct snd_pcm_ops fsl_asrc_dma_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_asrc_dma_hw_params, + .hw_free = fsl_asrc_dma_hw_free, + .trigger = fsl_asrc_dma_trigger, + .open = fsl_asrc_dma_startup, + .close = fsl_asrc_dma_shutdown, + .pointer = fsl_asrc_dma_pcm_pointer, +}; + +static int fsl_asrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + int ret, i; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(card->dev, "failed to set DMA mask\n"); + return ret; + } + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + FSL_ASRC_DMABUF_SIZE, &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to allocate DMA buffer\n"); + goto err; + } + } + + return 0; + +err: + if (--i == 0 && pcm->streams[i].substream) + snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer); + + return ret; +} + +static void fsl_asrc_dma_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } +} + +struct snd_soc_platform_driver fsl_asrc_platform = { + .ops = &fsl_asrc_dma_pcm_ops, + .pcm_new = fsl_asrc_dma_pcm_new, + .pcm_free = fsl_asrc_dma_pcm_free, +}; +EXPORT_SYMBOL_GPL(fsl_asrc_platform); diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index 6bb0ea59284f..a609aafc994d 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -923,8 +923,8 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) dma->dai.pcm_free = fsl_dma_free_dma_buffers; /* Store the SSI-specific information that we need */ - dma->ssi_stx_phys = res.start + offsetof(struct ccsr_ssi, stx0); - dma->ssi_srx_phys = res.start + offsetof(struct ccsr_ssi, srx0); + dma->ssi_stx_phys = res.start + CCSR_SSI_STX0; + dma->ssi_srx_phys = res.start + CCSR_SSI_SRX0; iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); if (iprop) diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index d719caf26dc2..72d154e7dd03 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -624,12 +624,14 @@ static int fsl_esai_dai_probe(struct snd_soc_dai *dai) static struct snd_soc_dai_driver fsl_esai_dai = { .probe = fsl_esai_dai_probe, .playback = { + .stream_name = "CPU-Playback", .channels_min = 1, .channels_max = 12, .rates = FSL_ESAI_RATES, .formats = FSL_ESAI_FORMATS, }, .capture = { + .stream_name = "CPU-Capture", .channels_min = 1, .channels_max = 8, .rates = FSL_ESAI_RATES, diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index c5a0e8af8226..faa049797897 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -106,7 +106,7 @@ irq_rx: xcsr &= ~FSL_SAI_CSR_xF_MASK; if (flags) - regmap_write(sai->regmap, FSL_SAI_TCSR, flags | xcsr); + regmap_write(sai->regmap, FSL_SAI_RCSR, flags | xcsr); out: if (irq_none) @@ -327,7 +327,7 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - u32 tcsr, rcsr; + u32 xcsr, count = 100; /* * The transmitter bit clock and frame sync are to be @@ -338,9 +338,6 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC, FSL_SAI_CR2_SYNC); - regmap_read(sai->regmap, FSL_SAI_TCSR, &tcsr); - regmap_read(sai->regmap, FSL_SAI_RCSR, &rcsr); - /* * It is recommended that the transmitter is the last enabled * and the first disabled. @@ -349,17 +346,16 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (!(tcsr & FSL_SAI_CSR_FRDE || rcsr & FSL_SAI_CSR_FRDE)) { - regmap_update_bits(sai->regmap, FSL_SAI_RCSR, - FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); - regmap_update_bits(sai->regmap, FSL_SAI_TCSR, - FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); - } + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE); + + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS); - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), - FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -370,11 +366,24 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, FSL_SAI_CSR_xIE_MASK, 0); /* Check if the opposite FRDE is also disabled */ - if (!(tx ? rcsr & FSL_SAI_CSR_FRDE : tcsr & FSL_SAI_CSR_FRDE)) { + regmap_read(sai->regmap, FSL_SAI_xCSR(!tx), &xcsr); + if (!(xcsr & FSL_SAI_CSR_FRDE)) { + /* Disable both directions and reset their FIFOs */ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_TERE, 0); regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_TERE, 0); + + /* TERE will remain set till the end of current frame */ + do { + udelay(10); + regmap_read(sai->regmap, FSL_SAI_xCSR(tx), &xcsr); + } while (--count && xcsr & FSL_SAI_CSR_TERE); + + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); } break; default: @@ -446,12 +455,14 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) static struct snd_soc_dai_driver fsl_sai_dai = { .probe = fsl_sai_dai_probe, .playback = { + .stream_name = "CPU-Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_96000, .formats = FSL_SAI_FORMATS, }, .capture = { + .stream_name = "CPU-Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_96000, diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index b912d45a2a4c..70acfe4a9bd5 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -32,10 +32,13 @@ #define FSL_SPDIF_TXFIFO_WML 0x8 #define FSL_SPDIF_RXFIFO_WML 0x8 -#define INTR_FOR_PLAYBACK (INT_TXFIFO_RESYNC) -#define INTR_FOR_CAPTURE (INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL | INT_URX_OV|\ - INT_QRX_FUL | INT_QRX_OV | INT_UQ_SYNC | INT_UQ_ERR |\ - INT_RXFIFO_RESYNC | INT_LOSS_LOCK | INT_DPLL_LOCKED) +#define INTR_FOR_PLAYBACK (INT_TXFIFO_RESYNC) +#define INTR_FOR_CAPTURE (INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL |\ + INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |\ + INT_UQ_SYNC | INT_UQ_ERR | INT_RXFIFO_RESYNC |\ + INT_LOSS_LOCK | INT_DPLL_LOCKED) + +#define SIE_INTR_FOR(tx) (tx ? INTR_FOR_PLAYBACK : INTR_FOR_CAPTURE) /* Index list for the values that has if (DPLL Locked) condition */ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb }; @@ -96,7 +99,7 @@ struct fsl_spdif_priv { struct platform_device *pdev; struct regmap *regmap; bool dpll_locked; - u16 txrate[SPDIF_TXRATE_MAX]; + u32 txrate[SPDIF_TXRATE_MAX]; u8 txclk_df[SPDIF_TXRATE_MAX]; u8 sysclk_df[SPDIF_TXRATE_MAX]; u8 txclk_src[SPDIF_TXRATE_MAX]; @@ -137,10 +140,9 @@ static void spdif_irq_sym_error(struct fsl_spdif_priv *spdif_priv) dev_dbg(&pdev->dev, "isr: receiver found illegal symbol\n"); - if (!spdif_priv->dpll_locked) { - /* DPLL unlocked seems no audio stream */ + /* Clear illegal symbol if DPLL unlocked since no audio stream */ + if (!spdif_priv->dpll_locked) regmap_update_bits(regmap, REG_SPDIF_SIE, INT_SYM_ERR, 0); - } } /* U/Q Channel receive register full */ @@ -335,8 +337,8 @@ static void spdif_write_channel_status(struct fsl_spdif_priv *spdif_priv) u32 ch_status; ch_status = (bitrev8(ctrl->ch_status[0]) << 16) | - (bitrev8(ctrl->ch_status[1]) << 8) | - bitrev8(ctrl->ch_status[2]); + (bitrev8(ctrl->ch_status[1]) << 8) | + bitrev8(ctrl->ch_status[2]); regmap_write(regmap, REG_SPDIF_STCSCH, ch_status); dev_dbg(&pdev->dev, "STCSCH: 0x%06x\n", ch_status); @@ -390,6 +392,14 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, rate = SPDIF_TXRATE_48000; csfs = IEC958_AES3_CON_FS_48000; break; + case 96000: + rate = SPDIF_TXRATE_96000; + csfs = IEC958_AES3_CON_FS_96000; + break; + case 192000: + rate = SPDIF_TXRATE_192000; + csfs = IEC958_AES3_CON_FS_192000; + break; default: dev_err(&pdev->dev, "unsupported sample rate %d\n", sample_rate); return -EINVAL; @@ -433,13 +443,12 @@ clk_set_bypass: spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs); /* select clock source and divisor */ - stc = STC_TXCLK_ALL_EN | STC_TXCLK_SRC_SET(clk) | STC_TXCLK_DF(txclk_df); - mask = STC_TXCLK_ALL_EN_MASK | STC_TXCLK_SRC_MASK | STC_TXCLK_DF_MASK; + stc = STC_TXCLK_ALL_EN | STC_TXCLK_SRC_SET(clk) | + STC_TXCLK_DF(txclk_df) | STC_SYSCLK_DF(sysclk_df); + mask = STC_TXCLK_ALL_EN_MASK | STC_TXCLK_SRC_MASK | + STC_TXCLK_DF_MASK | STC_SYSCLK_DF_MASK; regmap_update_bits(regmap, REG_SPDIF_STC, mask, stc); - regmap_update_bits(regmap, REG_SPDIF_STC, - STC_SYSCLK_DF_MASK, STC_SYSCLK_DF(sysclk_df)); - dev_dbg(&pdev->dev, "set sample rate to %dHz for %dHz playback\n", spdif_priv->txrate[rate], sample_rate); @@ -553,7 +562,7 @@ static int fsl_spdif_hw_params(struct snd_pcm_substream *substream, return ret; } spdif_set_cstatus(ctrl, IEC958_AES3_CON_CLOCK, - IEC958_AES3_CON_CLOCK_1000PPM); + IEC958_AES3_CON_CLOCK_1000PPM); spdif_write_channel_status(spdif_priv); } else { /* Setup rx clock source */ @@ -569,9 +578,9 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); struct regmap *regmap = spdif_priv->regmap; - int is_playack = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); - u32 intr = is_playack ? INTR_FOR_PLAYBACK : INTR_FOR_CAPTURE; - u32 dmaen = is_playack ? SCR_DMA_TX_EN : SCR_DMA_RX_EN;; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 intr = SIE_INTR_FOR(tx); + u32 dmaen = SCR_DMA_xX_EN(tx); switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -662,9 +671,8 @@ static int fsl_spdif_capture_get(struct snd_kcontrol *kcontrol, u32 cstatus, val; regmap_read(regmap, REG_SPDIF_SIS, &val); - if (!(val & INT_CNEW)) { + if (!(val & INT_CNEW)) return -EAGAIN; - } regmap_read(regmap, REG_SPDIF_SRCSH, &cstatus); ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF; @@ -693,15 +701,14 @@ static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; unsigned long flags; - int ret = 0; + int ret = -EAGAIN; spin_lock_irqsave(&ctrl->ctl_lock, flags); if (ctrl->ready_buf) { int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; memcpy(&ucontrol->value.iec958.subcode[0], &ctrl->subcode[idx], SPDIF_UBITS_SIZE); - } else { - ret = -EAGAIN; + ret = 0; } spin_unlock_irqrestore(&ctrl->ctl_lock, flags); @@ -726,15 +733,14 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; unsigned long flags; - int ret = 0; + int ret = -EAGAIN; spin_lock_irqsave(&ctrl->ctl_lock, flags); if (ctrl->ready_buf) { int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; memcpy(&ucontrol->value.bytes.data[0], &ctrl->qsub[idx], SPDIF_QSUB_SIZE); - } else { - ret = -EAGAIN; + ret = 0; } spin_unlock_irqrestore(&ctrl->ctl_lock, flags); @@ -762,7 +768,7 @@ static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, struct regmap *regmap = spdif_priv->regmap; u32 val; - val = regmap_read(regmap, REG_SPDIF_SIS, &val); + regmap_read(regmap, REG_SPDIF_SIS, &val); ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0; regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD); @@ -799,10 +805,10 @@ static int spdif_get_rxclk_rate(struct fsl_spdif_priv *spdif_priv, regmap_read(regmap, REG_SPDIF_SRPC, &phaseconf); clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0xf; - if (srpc_dpll_locked[clksrc] && (phaseconf & SRPC_DPLL_LOCKED)) { - /* Get bus clock from system */ + + /* Get bus clock from system */ + if (srpc_dpll_locked[clksrc] && (phaseconf & SRPC_DPLL_LOCKED)) busclk_freq = clk_get_rate(spdif_priv->sysclk); - } /* FreqMeas_CLK = (BUS_CLK * FreqMeas) / 2 ^ 10 / GAINSEL / 128 */ tmpval64 = (u64) busclk_freq * freqmeas; @@ -826,12 +832,12 @@ static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol, { struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); - int rate = spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL); + int rate = 0; if (spdif_priv->dpll_locked) - ucontrol->value.integer.value[0] = rate; - else - ucontrol->value.integer.value[0] = 0; + rate = spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL); + + ucontrol->value.integer.value[0] = rate; return 0; } @@ -969,12 +975,14 @@ static int fsl_spdif_dai_probe(struct snd_soc_dai *dai) static struct snd_soc_dai_driver fsl_spdif_dai = { .probe = &fsl_spdif_dai_probe, .playback = { + .stream_name = "CPU-Playback", .channels_min = 2, .channels_max = 2, .rates = FSL_SPDIF_RATES_PLAYBACK, .formats = FSL_SPDIF_FORMATS_PLAYBACK, }, .capture = { + .stream_name = "CPU-Capture", .channels_min = 2, .channels_max = 2, .rates = FSL_SPDIF_RATES_CAPTURE, @@ -1046,7 +1054,7 @@ static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, struct clk *clk, u64 savesub, enum spdif_txrate index, bool round) { - const u32 rate[] = { 32000, 44100, 48000 }; + const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; bool is_sysclk = clk == spdif_priv->sysclk; u64 rate_ideal, rate_actual, sub; u32 sysclk_dfmin, sysclk_dfmax; @@ -1076,7 +1084,7 @@ static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, goto out; } else if (arate / rate[index] == 1) { /* A little bigger than expect */ - sub = (arate - rate[index]) * 100000; + sub = (u64)(arate - rate[index]) * 100000; do_div(sub, rate[index]); if (sub >= savesub) continue; @@ -1086,7 +1094,7 @@ static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, spdif_priv->txrate[index] = arate; } else if (rate[index] / arate == 1) { /* A little smaller than expect */ - sub = (rate[index] - arate) * 100000; + sub = (u64)(rate[index] - arate) * 100000; do_div(sub, rate[index]); if (sub >= savesub) continue; @@ -1105,7 +1113,7 @@ out: static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, enum spdif_txrate index) { - const u32 rate[] = { 32000, 44100, 48000 }; + const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; struct platform_device *pdev = spdif_priv->pdev; struct device *dev = &pdev->dev; u64 savesub = 100000, ret; @@ -1238,12 +1246,12 @@ static int fsl_spdif_probe(struct platform_device *pdev) spin_lock_init(&ctrl->ctl_lock); /* Init tx channel status default value */ - ctrl->ch_status[0] = - IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS_5015; + ctrl->ch_status[0] = IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS_5015; ctrl->ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID; ctrl->ch_status[2] = 0x00; - ctrl->ch_status[3] = - IEC958_AES3_CON_FS_44100 | IEC958_AES3_CON_CLOCK_1000PPM; + ctrl->ch_status[3] = IEC958_AES3_CON_FS_44100 | + IEC958_AES3_CON_CLOCK_1000PPM; spdif_priv->dpll_locked = false; diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h index 16fde4b927d3..00bd3514c610 100644 --- a/sound/soc/fsl/fsl_spdif.h +++ b/sound/soc/fsl/fsl_spdif.h @@ -93,6 +93,8 @@ #define SCR_USRC_SEL_RECV (0x1 << SCR_USRC_SEL_OFFSET) #define SCR_USRC_SEL_CHIP (0x3 << SCR_USRC_SEL_OFFSET) +#define SCR_DMA_xX_EN(tx) (tx ? SCR_DMA_TX_EN : SCR_DMA_RX_EN) + /* SPDIF CDText control */ #define SRCD_CD_USER_OFFSET 1 #define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET) @@ -164,8 +166,10 @@ enum spdif_txrate { SPDIF_TXRATE_32000 = 0, SPDIF_TXRATE_44100, SPDIF_TXRATE_48000, + SPDIF_TXRATE_96000, + SPDIF_TXRATE_192000, }; -#define SPDIF_TXRATE_MAX (SPDIF_TXRATE_48000 + 1) +#define SPDIF_TXRATE_MAX (SPDIF_TXRATE_192000 + 1) #define SPDIF_CSTATUS_BYTE 6 @@ -175,7 +179,9 @@ enum spdif_txrate { #define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | \ - SNDRV_PCM_RATE_48000) + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) #define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \ SNDRV_PCM_RATE_32000 | \ diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 9bfef55d77d1..87eb5776a39b 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -590,8 +590,8 @@ static int fsl_ssi_set_bclk(struct snd_pcm_substream *substream, else clkrate = clk_round_rate(ssi_private->baudclk, tmprate); - do_div(clkrate, factor); - afreq = (u32)clkrate / (i + 1); + clkrate /= factor; + afreq = clkrate / (i + 1); if (freq == afreq) sub = 0; @@ -1032,12 +1032,14 @@ static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { static struct snd_soc_dai_driver fsl_ssi_dai_template = { .probe = fsl_ssi_dai_probe, .playback = { + .stream_name = "CPU-Playback", .channels_min = 1, .channels_max = 2, .rates = FSLSSI_I2S_RATES, .formats = FSLSSI_I2S_FORMATS, }, .capture = { + .stream_name = "CPU-Capture", .channels_min = 1, .channels_max = 2, .rates = FSLSSI_I2S_RATES, diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c index 267717aa96c1..46f9beb6b273 100644 --- a/sound/soc/fsl/imx-audmux.c +++ b/sound/soc/fsl/imx-audmux.c @@ -67,7 +67,7 @@ static ssize_t audmux_read_file(struct file *file, char __user *user_buf, { ssize_t ret; char *buf; - int port = (int)file->private_data; + uintptr_t port = (uintptr_t)file->private_data; u32 pdcr, ptcr; if (audmux_clk) { @@ -147,7 +147,7 @@ static const struct file_operations audmux_debugfs_fops = { static void audmux_debugfs_init(void) { - int i; + uintptr_t i; char buf[20]; audmux_debugfs_root = debugfs_create_dir("audmux", NULL); @@ -157,10 +157,10 @@ static void audmux_debugfs_init(void) } for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) { - snprintf(buf, sizeof(buf), "ssi%d", i); + snprintf(buf, sizeof(buf), "ssi%lu", i); if (!debugfs_create_file(buf, 0444, audmux_debugfs_root, (void *)i, &audmux_debugfs_fops)) - pr_warning("Failed to create AUDMUX port %d debugfs file\n", + pr_warning("Failed to create AUDMUX port %lu debugfs file\n", i); } } diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c index 0849b7b83f0a..0db94f492e97 100644 --- a/sound/soc/fsl/imx-pcm-dma.c +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -59,7 +59,6 @@ int imx_pcm_dma_init(struct platform_device *pdev) { return devm_snd_dmaengine_pcm_register(&pdev->dev, &imx_dmaengine_pcm_config, - SND_DMAENGINE_PCM_FLAG_NO_RESIDUE | SND_DMAENGINE_PCM_FLAG_COMPAT); } EXPORT_SYMBOL_GPL(imx_pcm_dma_init); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 03a7fdcdf114..159e517fa09a 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -116,6 +116,7 @@ asoc_simple_card_sub_parse_of(struct device_node *np, { struct device_node *node; struct clk *clk; + u32 val; int ret; /* @@ -151,10 +152,8 @@ asoc_simple_card_sub_parse_of(struct device_node *np, } dai->sysclk = clk_get_rate(clk); - } else if (of_property_read_bool(np, "system-clock-frequency")) { - of_property_read_u32(np, - "system-clock-frequency", - &dai->sysclk); + } else if (!of_property_read_u32(np, "system-clock-frequency", &val)) { + dai->sysclk = val; } else { clk = of_clk_get(node, 0); if (!IS_ERR(clk)) @@ -303,6 +302,7 @@ static int asoc_simple_card_parse_of(struct device_node *node, { struct snd_soc_dai_link *dai_link = priv->snd_card.dai_link; struct simple_dai_props *dai_props = priv->dai_props; + u32 val; int ret; /* parsing the card name from DT */ @@ -325,8 +325,9 @@ static int asoc_simple_card_parse_of(struct device_node *node, } /* Factor to mclk, used in hw_params() */ - of_property_read_u32(node, "simple-audio-card,mclk-fs", - &priv->mclk_fs); + ret = of_property_read_u32(node, "simple-audio-card,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 : ""); diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index c30fedb3e149..f5b4a9c79cdf 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -58,3 +58,15 @@ config SND_SOC_INTEL_BYT_MAX98090_MACH help This adds audio driver for Intel Baytrail platform based boards with the MAX98090 audio codec. + +config SND_SOC_INTEL_BROADWELL_MACH + tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint" + depends on SND_SOC_INTEL_SST && X86_INTEL_LPSS && DW_DMAC + select SND_SOC_INTEL_HASWELL + select SND_COMPRESS_OFFLOAD + 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 + If unsure select "N". diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index 4bfca79a42ba..7acbfc43a0c6 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -24,7 +24,9 @@ obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o 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 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_BROADWELL_MACH) += snd-soc-sst-broadwell.o diff --git a/sound/soc/intel/broadwell.c b/sound/soc/intel/broadwell.c new file mode 100644 index 000000000000..0e550f14028f --- /dev/null +++ b/sound/soc/intel/broadwell.c @@ -0,0 +1,251 @@ +/* + * Intel Broadwell Wildcatpoint SST Audio + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * 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/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "sst-dsp.h" +#include "sst-haswell-ipc.h" + +#include "../codecs/rt286.h" + +static const struct snd_soc_dapm_widget broadwell_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("DMIC1", NULL), + SND_SOC_DAPM_MIC("DMIC2", NULL), + SND_SOC_DAPM_LINE("Line Jack", NULL), +}; + +static const struct snd_soc_dapm_route broadwell_rt286_map[] = { + + /* speaker */ + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, + + /* HP jack connectors - unknown if we have jack deteck */ + {"Headphones", NULL, "HPO Pin"}, + + /* other jacks */ + {"MIC1", NULL, "Mic Jack"}, + {"LINE1", NULL, "Line Jack"}, + + /* digital mics */ + {"DMIC1 Pin", NULL, "DMIC1"}, + {"DMIC2 Pin", NULL, "DMIC2"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static int broadwell_ssp0_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); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + snd_mask_set(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - + SNDRV_PCM_HW_PARAM_FIRST_MASK], + SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int broadwell_rt286_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, RT286_SCLK_S_PLL, 24000000, + SND_SOC_CLOCK_IN); + + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + return ret; +} + +static struct snd_soc_ops broadwell_rt286_ops = { + .hw_params = broadwell_rt286_hw_params, +}; + +static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct sst_pdata *pdata = dev_get_platdata(rtd->platform->dev); + struct sst_hsw *broadwell = pdata->dsp; + int ret; + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set device config\n"); + return ret; + } + + /* always connected - check HP for jack detect */ + snd_soc_dapm_enable_pin(dapm, "Headphones"); + snd_soc_dapm_enable_pin(dapm, "Speaker"); + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + snd_soc_dapm_enable_pin(dapm, "Line Jack"); + snd_soc_dapm_enable_pin(dapm, "DMIC1"); + snd_soc_dapm_enable_pin(dapm, "DMIC2"); + + return 0; +} + +/* broadwell digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link broadwell_rt286_dais[] = { + /* Front End DAI links */ + { + .name = "System PCM", + .stream_name = "System Playback", + .cpu_dai_name = "System Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = broadwell_rtd_init, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Offload0", + .stream_name = "Offload0 Playback", + .cpu_dai_name = "Offload0 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Offload1", + .stream_name = "Offload1 Playback", + .cpu_dai_name = "Offload1 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Loopback PCM", + .stream_name = "Loopback", + .cpu_dai_name = "Loopback Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 0, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + }, + { + .name = "Capture PCM", + .stream_name = "Capture", + .cpu_dai_name = "Capture Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .be_id = 0, + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_name = "i2c-INT343A:00", + .codec_dai_name = "rt286-aif1", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broadwell_ssp0_fixup, + .ops = &broadwell_rt286_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +/* broadwell audio machine driver for WPT + RT286S */ +static struct snd_soc_card broadwell_rt286 = { + .name = "broadwell-rt286", + .owner = THIS_MODULE, + .dai_link = broadwell_rt286_dais, + .num_links = ARRAY_SIZE(broadwell_rt286_dais), + .dapm_widgets = broadwell_widgets, + .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets), + .dapm_routes = broadwell_rt286_map, + .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map), + .fully_routed = true, +}; + +static int broadwell_audio_probe(struct platform_device *pdev) +{ + broadwell_rt286.dev = &pdev->dev; + + return snd_soc_register_card(&broadwell_rt286); +} + +static int broadwell_audio_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&broadwell_rt286); + return 0; +} + +static struct platform_driver broadwell_audio = { + .probe = broadwell_audio_probe, + .remove = broadwell_audio_remove, + .driver = { + .name = "broadwell-audio", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(broadwell_audio) + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Intel SST Audio for WPT/Broadwell"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:broadwell-audio"); diff --git a/sound/soc/intel/byt-max98090.c b/sound/soc/intel/byt-max98090.c index 5fc98c64a3f4..b8b8af571ef1 100644 --- a/sound/soc/intel/byt-max98090.c +++ b/sound/soc/intel/byt-max98090.c @@ -39,8 +39,7 @@ static const struct snd_soc_dapm_widget byt_max98090_widgets[] = { static const struct snd_soc_dapm_route byt_max98090_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"}, @@ -64,14 +63,6 @@ static struct snd_soc_jack_pin hs_jack_pins[] = { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE, }, - { - .pin = "Ext Spk", - .mask = SND_JACK_LINEOUT, - }, - { - .pin = "Int Mic", - .mask = SND_JACK_LINEIN, - }, }; static struct snd_soc_jack_gpio hs_jack_gpios[] = { @@ -84,7 +75,8 @@ static struct snd_soc_jack_gpio hs_jack_gpios[] = { { .name = "mic-gpio", .idx = 1, - .report = SND_JACK_MICROPHONE | SND_JACK_LINEIN, + .invert = 1, + .report = SND_JACK_MICROPHONE, .debounce_time = 200, }, }; @@ -108,7 +100,8 @@ static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime) } /* Enable jack detection */ - ret = snd_soc_jack_new(codec, "Headphone", SND_JACK_HEADPHONE, jack); + ret = snd_soc_jack_new(codec, "Headset", + SND_JACK_LINEOUT | SND_JACK_HEADSET, jack); if (ret) return ret; @@ -117,13 +110,9 @@ static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime) if (ret) return ret; - ret = snd_soc_jack_add_gpiods(card->dev->parent, jack, - ARRAY_SIZE(hs_jack_gpios), - hs_jack_gpios); - if (ret) - return ret; - - return max98090_mic_detect(codec, jack); + return snd_soc_jack_add_gpiods(card->dev->parent, jack, + ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); } static struct snd_soc_dai_link byt_max98090_dais[] = { diff --git a/sound/soc/intel/byt-rt5640.c b/sound/soc/intel/byt-rt5640.c index 53d160d39972..234a58de3c53 100644 --- a/sound/soc/intel/byt-rt5640.c +++ b/sound/soc/intel/byt-rt5640.c @@ -34,6 +34,7 @@ static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { }; static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { + {"Headset Mic", NULL, "MICBIAS1"}, {"IN2P", NULL, "Headset Mic"}, {"IN2N", NULL, "Headset Mic"}, {"DMIC1", NULL, "Internal Mic"}, diff --git a/sound/soc/intel/sst-atom-controls.h b/sound/soc/intel/sst-atom-controls.h new file mode 100644 index 000000000000..14063ab8c7c5 --- /dev/null +++ b/sound/soc/intel/sst-atom-controls.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013-14 Intel Corp + * Author: Ramesh Babu <ramesh.babu.koul@intel.com> + * Omair M Abdullah <omair.m.abdullah@intel.com> + * Samreen Nilofer <samreen.nilofer@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef __SST_CONTROLS_V2_H__ +#define __SST_CONTROLS_V2_H__ + +enum { + MERR_DPCM_AUDIO = 0, + MERR_DPCM_COMPR, +}; + + +#endif diff --git a/sound/soc/intel/sst-baytrail-ipc.c b/sound/soc/intel/sst-baytrail-ipc.c index d207b22ea330..67673a2c0f41 100644 --- a/sound/soc/intel/sst-baytrail-ipc.c +++ b/sound/soc/intel/sst-baytrail-ipc.c @@ -122,6 +122,26 @@ struct sst_byt_tstamp { u32 channel_peak[8]; } __packed; +struct sst_byt_fw_version { + u8 build; + u8 minor; + u8 major; + u8 type; +} __packed; + +struct sst_byt_fw_build_info { + u8 date[16]; + u8 time[16]; +} __packed; + +struct sst_byt_fw_init { + struct sst_byt_fw_version fw_version; + struct sst_byt_fw_build_info build_info; + u16 result; + u8 module_id; + u8 debug_info; +} __packed; + /* driver internal IPC message structure */ struct ipc_message { struct list_head list; @@ -868,6 +888,7 @@ int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) { struct sst_byt *byt; struct sst_fw *byt_sst_fw; + struct sst_byt_fw_init init; int err; dev_dbg(dev, "initialising Byt DSP IPC\n"); @@ -929,6 +950,15 @@ int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) goto boot_err; } + /* show firmware information */ + sst_dsp_inbox_read(byt->dsp, &init, sizeof(init)); + dev_info(byt->dev, "FW version: %02x.%02x.%02x.%02x\n", + init.fw_version.major, init.fw_version.minor, + init.fw_version.build, init.fw_version.type); + dev_info(byt->dev, "Build type: %x\n", init.fw_version.type); + dev_info(byt->dev, "Build date: %s %s\n", + init.build_info.date, init.build_info.time); + pdata->dsp = byt; byt->fw = byt_sst_fw; diff --git a/sound/soc/intel/sst-baytrail-pcm.c b/sound/soc/intel/sst-baytrail-pcm.c index 8eab97368ea7..599401c0c655 100644 --- a/sound/soc/intel/sst-baytrail-pcm.c +++ b/sound/soc/intel/sst-baytrail-pcm.c @@ -32,7 +32,7 @@ static const struct snd_pcm_hardware sst_byt_pcm_hardware = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FORMAT_S24_LE, + SNDRV_PCM_FMTBIT_S24_LE, .period_bytes_min = 384, .period_bytes_max = 48000, .periods_min = 2, diff --git a/sound/soc/intel/sst-dsp.c b/sound/soc/intel/sst-dsp.c index 0b715b20a2d7..cd23060a0d86 100644 --- a/sound/soc/intel/sst-dsp.c +++ b/sound/soc/intel/sst-dsp.c @@ -224,19 +224,23 @@ EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64); void sst_dsp_dump(struct sst_dsp *sst) { - sst->ops->dump(sst); + if (sst->ops->dump) + sst->ops->dump(sst); } EXPORT_SYMBOL_GPL(sst_dsp_dump); void sst_dsp_reset(struct sst_dsp *sst) { - sst->ops->reset(sst); + if (sst->ops->reset) + sst->ops->reset(sst); } EXPORT_SYMBOL_GPL(sst_dsp_reset); int sst_dsp_boot(struct sst_dsp *sst) { - sst->ops->boot(sst); + if (sst->ops->boot) + sst->ops->boot(sst); + return 0; } EXPORT_SYMBOL_GPL(sst_dsp_boot); diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h index e44423be66c4..3165dfa97408 100644 --- a/sound/soc/intel/sst-dsp.h +++ b/sound/soc/intel/sst-dsp.h @@ -52,7 +52,11 @@ #define SST_CLKCTL 0x78 #define SST_CSR2 0x80 #define SST_LTRC 0xE0 -#define SST_HDMC 0xE8 +#define SST_HMDC 0xE8 + +#define SST_SHIM_BEGIN SST_CSR +#define SST_SHIM_END SST_HDMC + #define SST_DBGO 0xF0 #define SST_SHIM_SIZE 0x100 @@ -73,6 +77,8 @@ #define SST_CSR_S0IOCS (0x1 << 21) #define SST_CSR_S1IOCS (0x1 << 23) #define SST_CSR_LPCS (0x1 << 31) +#define SST_CSR_24MHZ_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1 | SST_CSR_LPCS) +#define SST_CSR_24MHZ_NO_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1) #define SST_BYT_CSR_RST (0x1 << 0) #define SST_BYT_CSR_VECTOR_SEL (0x1 << 1) #define SST_BYT_CSR_STALL (0x1 << 2) @@ -92,6 +98,14 @@ #define SST_IMRX_DONE (0x1 << 0) #define SST_BYT_IMRX_REQUEST (0x1 << 1) +/* IMRD / IMD */ +#define SST_IMRD_DONE (0x1 << 0) +#define SST_IMRD_BUSY (0x1 << 1) +#define SST_IMRD_SSP0 (0x1 << 16) +#define SST_IMRD_DMAC0 (0x1 << 21) +#define SST_IMRD_DMAC1 (0x1 << 22) +#define SST_IMRD_DMAC (SST_IMRD_DMAC0 | SST_IMRD_DMAC1) + /* IPCX / IPCC */ #define SST_IPCX_DONE (0x1 << 30) #define SST_IPCX_BUSY (0x1 << 31) @@ -118,9 +132,21 @@ /* LTRC */ #define SST_LTRC_VAL(x) (x << 0) -/* HDMC */ -#define SST_HDMC_HDDA0(x) (x << 0) -#define SST_HDMC_HDDA1(x) (x << 7) +/* HMDC */ +#define SST_HMDC_HDDA0(x) (x << 0) +#define SST_HMDC_HDDA1(x) (x << 7) +#define SST_HMDC_HDDA_E0_CH0 1 +#define SST_HMDC_HDDA_E0_CH1 2 +#define SST_HMDC_HDDA_E0_CH2 4 +#define SST_HMDC_HDDA_E0_CH3 8 +#define SST_HMDC_HDDA_E1_CH0 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH0) +#define SST_HMDC_HDDA_E1_CH1 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH1) +#define SST_HMDC_HDDA_E1_CH2 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH2) +#define SST_HMDC_HDDA_E1_CH3 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH3) +#define SST_HMDC_HDDA_E0_ALLCH (SST_HMDC_HDDA_E0_CH0 | SST_HMDC_HDDA_E0_CH1 | \ + SST_HMDC_HDDA_E0_CH2 | SST_HMDC_HDDA_E0_CH3) +#define SST_HMDC_HDDA_E1_ALLCH (SST_HMDC_HDDA_E1_CH0 | SST_HMDC_HDDA_E1_CH1 | \ + SST_HMDC_HDDA_E1_CH2 | SST_HMDC_HDDA_E1_CH3) /* SST Vendor Defined Registers and bits */ @@ -130,11 +156,16 @@ #define SST_VDRTCTL3 0xaC /* VDRTCTL0 */ +#define SST_VDRTCL0_APLLSE_MASK 1 #define SST_VDRTCL0_DSRAMPGE_SHIFT 16 #define SST_VDRTCL0_DSRAMPGE_MASK (0xffff << SST_VDRTCL0_DSRAMPGE_SHIFT) #define SST_VDRTCL0_ISRAMPGE_SHIFT 6 #define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) +/* PMCS */ +#define SST_PMCS 0x84 +#define SST_PMCS_PS_MASK 0x3 + struct sst_dsp; /* diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c index 535f517629fd..4b6c163c10ff 100644 --- a/sound/soc/intel/sst-haswell-dsp.c +++ b/sound/soc/intel/sst-haswell-dsp.c @@ -28,9 +28,6 @@ #include <linux/firmware.h> #include <linux/pm_runtime.h> -#include <linux/acpi.h> -#include <acpi/acpi_bus.h> - #include "sst-dsp.h" #include "sst-dsp-priv.h" #include "sst-haswell-ipc.h" @@ -272,9 +269,9 @@ static void hsw_boot(struct sst_dsp *sst) SST_CSR2_SDFD_SSP1); /* enable DMA engine 0,1 all channels to access host memory */ - sst_dsp_shim_update_bits_unlocked(sst, SST_HDMC, - SST_HDMC_HDDA1(0xff) | SST_HDMC_HDDA0(0xff), - SST_HDMC_HDDA1(0xff) | SST_HDMC_HDDA0(0xff)); + sst_dsp_shim_update_bits_unlocked(sst, SST_HMDC, + SST_HMDC_HDDA1(0xff) | SST_HMDC_HDDA0(0xff), + SST_HMDC_HDDA1(0xff) | SST_HMDC_HDDA0(0xff)); /* disable all clock gating */ writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL2); @@ -313,9 +310,7 @@ static const struct sst_adsp_memregion lp_region[] = { /* wild cat point ADSP mem regions */ static const struct sst_adsp_memregion wpt_region[] = { - {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ - {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ - {0x80000, 0xA0000, 4, SST_MEM_DRAM}, /* D-SRAM2 - 4 * 32kB */ + {0x00000, 0xA0000, 20, SST_MEM_DRAM}, /* D-SRAM0,D-SRAM1,D-SRAM2 - 20 * 32kB */ {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */ }; @@ -339,26 +334,56 @@ static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) return 0; } +struct sst_sram_shift { + u32 dev_id; /* SST Device IDs */ + u32 iram_shift; + u32 dram_shift; +}; + +static const struct sst_sram_shift sram_shift[] = { + {SST_DEV_ID_LYNX_POINT, 6, 16}, /* lp */ + {SST_DEV_ID_WILDCAT_POINT, 2, 12}, /* wpt */ +}; static u32 hsw_block_get_bit(struct sst_mem_block *block) { - u32 bit = 0, shift = 0; + u32 bit = 0, shift = 0, index; + struct sst_dsp *sst = block->dsp; - switch (block->type) { - case SST_MEM_DRAM: - shift = 16; - break; - case SST_MEM_IRAM: - shift = 6; - break; - default: - return 0; + for (index = 0; index < ARRAY_SIZE(sram_shift); index++) { + if (sram_shift[index].dev_id == sst->id) + break; } + if (index < ARRAY_SIZE(sram_shift)) { + switch (block->type) { + case SST_MEM_DRAM: + shift = sram_shift[index].dram_shift; + break; + case SST_MEM_IRAM: + shift = sram_shift[index].iram_shift; + break; + default: + shift = 0; + } + } else + shift = 0; + bit = 1 << (block->index + shift); return bit; } +/*dummy read a SRAM block.*/ +static void sst_mem_block_dummy_read(struct sst_mem_block *block) +{ + u32 size; + u8 tmp_buf[4]; + struct sst_dsp *sst = block->dsp; + + size = block->size > 4 ? 4 : block->size; + memcpy_fromio(tmp_buf, sst->addr.lpe + block->offset, size); +} + /* enable 32kB memory block - locks held by caller */ static int hsw_block_enable(struct sst_mem_block *block) { @@ -378,6 +403,8 @@ static int hsw_block_enable(struct sst_mem_block *block) /* wait 18 DSP clock ticks */ udelay(10); + /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/ + sst_mem_block_dummy_read(block); return 0; } @@ -488,8 +515,9 @@ static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) } } - /* set default power gating mask */ - writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL0); + /* set default power gating control, enable power gating control for all blocks. that is, + can't be accessed, please enable each block before accessing. */ + writel(0xffffffff, sst->addr.pci_cfg + SST_VDRTCTL0); return 0; } diff --git a/sound/soc/intel/sst-haswell-ipc.c b/sound/soc/intel/sst-haswell-ipc.c index 434236343ddf..b6291516dbbf 100644 --- a/sound/soc/intel/sst-haswell-ipc.c +++ b/sound/soc/intel/sst-haswell-ipc.c @@ -183,7 +183,7 @@ struct sst_hsw_ipc_fw_ready { u32 inbox_size; u32 outbox_size; u32 fw_info_size; - u8 fw_info[1]; + u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; } __attribute__((packed)); struct ipc_message { @@ -457,9 +457,10 @@ static void ipc_tx_msgs(struct kthread_work *work) return; } - /* if the DSP is busy we will TX messages after IRQ */ + /* if the DSP is busy, we will TX messages after IRQ. + * also postpone if we are in the middle of procesing completion irq*/ ipcx = sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX); - if (ipcx & SST_IPCX_BUSY) { + if (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)) { spin_unlock_irqrestore(&hsw->dsp->spinlock, flags); return; } @@ -502,6 +503,7 @@ static int tx_wait_done(struct sst_hsw *hsw, struct ipc_message *msg, ipc_shim_dbg(hsw, "message timeout"); trace_ipc_error("error message timeout for", msg->header); + list_del(&msg->list); ret = -ETIMEDOUT; } else { @@ -569,6 +571,9 @@ static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) { struct sst_hsw_ipc_fw_ready fw_ready; u32 offset; + u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; + char *tmp[5], *pinfo; + int i = 0; offset = (header & 0x1FFFFFFF) << 3; @@ -589,6 +594,19 @@ static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) fw_ready.inbox_offset, fw_ready.inbox_size); dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n", fw_ready.outbox_offset, fw_ready.outbox_size); + if (fw_ready.fw_info_size < sizeof(fw_ready.fw_info)) { + fw_ready.fw_info[fw_ready.fw_info_size] = 0; + dev_dbg(hsw->dev, " Firmware info: %s \n", fw_ready.fw_info); + + /* log the FW version info got from the mailbox here. */ + memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size); + pinfo = &fw_info[0]; + for (i = 0; i < sizeof(tmp) / sizeof(char *); i++) + tmp[i] = strsep(&pinfo, " "); + dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - " + "version: %s.%s, build %s, source commit id: %s\n", + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]); + } } static void hsw_notification_work(struct work_struct *work) @@ -671,7 +689,9 @@ static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) switch (stream_msg) { case IPC_STR_STAGE_MESSAGE: case IPC_STR_NOTIFICATION: + break; case IPC_STR_RESET: + trace_ipc_notification("stream reset", stream->reply.stream_hw_id); break; case IPC_STR_PAUSE: stream->running = false; @@ -762,7 +782,8 @@ static int hsw_process_reply(struct sst_hsw *hsw, u32 header) } /* update any stream states */ - hsw_stream_update(hsw, msg); + if (msg_get_global_type(header) == IPC_GLB_STREAM_MESSAGE) + hsw_stream_update(hsw, msg); /* wake up and return the error if we have waiters on this message ? */ list_del(&msg->list); @@ -1628,7 +1649,7 @@ int sst_hsw_dx_set_state(struct sst_hsw *hsw, enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx) { u32 header, state_; - int ret; + int ret, item; header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); state_ = state; @@ -1642,6 +1663,13 @@ int sst_hsw_dx_set_state(struct sst_hsw *hsw, return ret; } + for (item = 0; item < dx->entries_no; item++) { + dev_dbg(hsw->dev, + "Item[%d] offset[%x] - size[%x] - source[%x]\n", + item, dx->mem_info[item].offset, + dx->mem_info[item].size, + dx->mem_info[item].source); + } dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", dx->entries_no, state); @@ -1775,8 +1803,6 @@ int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) /* get the FW version */ sst_hsw_fw_get_version(hsw, &version); - dev_info(hsw->dev, "FW loaded: type %d - version: %d.%d build %d\n", - version.type, version.major, version.minor, version.build); /* get the globalmixer */ ret = sst_hsw_mixer_get_info(hsw); diff --git a/sound/soc/intel/sst-haswell-pcm.c b/sound/soc/intel/sst-haswell-pcm.c index 058efb17c568..61bf6da4bb02 100644 --- a/sound/soc/intel/sst-haswell-pcm.c +++ b/sound/soc/intel/sst-haswell-pcm.c @@ -80,7 +80,7 @@ static const struct snd_pcm_hardware hsw_pcm_hardware = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE | + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .period_bytes_min = PAGE_SIZE, .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE, @@ -400,7 +400,15 @@ static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16); break; case SNDRV_PCM_FORMAT_S24_LE: - bits = SST_HSW_DEPTH_24BIT; + bits = SST_HSW_DEPTH_32BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 24); + break; + case SNDRV_PCM_FORMAT_S8: + bits = SST_HSW_DEPTH_8BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 8); + break; + case SNDRV_PCM_FORMAT_S32_LE: + bits = SST_HSW_DEPTH_32BIT; sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32); break; default: @@ -685,8 +693,9 @@ static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) } #define HSW_FORMATS \ - (SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE |\ - SNDRV_PCM_FMTBIT_S32_LE) + (SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S8) static struct snd_soc_dai_driver hsw_dais[] = { { @@ -696,7 +705,7 @@ static struct snd_soc_dai_driver hsw_dais[] = { .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, }, }, { @@ -727,8 +736,8 @@ static struct snd_soc_dai_driver hsw_dais[] = { .stream_name = "Loopback Capture", .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = HSW_FORMATS, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, }, }, { @@ -737,8 +746,8 @@ static struct snd_soc_dai_driver hsw_dais[] = { .stream_name = "Analog Capture", .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = HSW_FORMATS, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, }, }, }; diff --git a/sound/soc/intel/sst-mfld-dsp.h b/sound/soc/intel/sst-mfld-dsp.h index 8d482d76475a..4257263157cd 100644 --- a/sound/soc/intel/sst-mfld-dsp.h +++ b/sound/soc/intel/sst-mfld-dsp.h @@ -3,7 +3,7 @@ /* * sst_mfld_dsp.h - Intel SST Driver for audio engine * - * Copyright (C) 2008-12 Intel Corporation + * Copyright (C) 2008-14 Intel Corporation * Authors: Vinod Koul <vinod.koul@linux.intel.com> * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * @@ -19,6 +19,142 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#define SST_MAX_BIN_BYTES 1024 + +#define MAX_DBG_RW_BYTES 80 +#define MAX_NUM_SCATTER_BUFFERS 8 +#define MAX_LOOP_BACK_DWORDS 8 +/* IPC base address and mailbox, timestamp offsets */ +#define SST_MAILBOX_SIZE 0x0400 +#define SST_MAILBOX_SEND 0x0000 +#define SST_TIME_STAMP 0x1800 +#define SST_TIME_STAMP_MRFLD 0x800 +#define SST_RESERVED_OFFSET 0x1A00 +#define SST_SCU_LPE_MAILBOX 0x1000 +#define SST_LPE_SCU_MAILBOX 0x1400 +#define SST_SCU_LPE_LOG_BUF (SST_SCU_LPE_MAILBOX+16) +#define PROCESS_MSG 0x80 + +/* Message ID's for IPC messages */ +/* Bits B7: SST or IA/SC ; B6-B4: Msg Category; B3-B0: Msg Type */ + +/* I2L Firmware/Codec Download msgs */ +#define IPC_IA_PREP_LIB_DNLD 0x01 +#define IPC_IA_LIB_DNLD_CMPLT 0x02 +#define IPC_IA_GET_FW_VERSION 0x04 +#define IPC_IA_GET_FW_BUILD_INF 0x05 +#define IPC_IA_GET_FW_INFO 0x06 +#define IPC_IA_GET_FW_CTXT 0x07 +#define IPC_IA_SET_FW_CTXT 0x08 +#define IPC_IA_PREPARE_SHUTDOWN 0x31 +/* I2L Codec Config/control msgs */ +#define IPC_PREP_D3 0x10 +#define IPC_IA_SET_CODEC_PARAMS 0x10 +#define IPC_IA_GET_CODEC_PARAMS 0x11 +#define IPC_IA_SET_PPP_PARAMS 0x12 +#define IPC_IA_GET_PPP_PARAMS 0x13 +#define IPC_SST_PERIOD_ELAPSED_MRFLD 0xA +#define IPC_IA_ALG_PARAMS 0x1A +#define IPC_IA_TUNING_PARAMS 0x1B +#define IPC_IA_SET_RUNTIME_PARAMS 0x1C +#define IPC_IA_SET_PARAMS 0x1 +#define IPC_IA_GET_PARAMS 0x2 + +#define IPC_EFFECTS_CREATE 0xE +#define IPC_EFFECTS_DESTROY 0xF + +/* I2L Stream config/control msgs */ +#define IPC_IA_ALLOC_STREAM_MRFLD 0x2 +#define IPC_IA_ALLOC_STREAM 0x20 /* Allocate a stream ID */ +#define IPC_IA_FREE_STREAM_MRFLD 0x03 +#define IPC_IA_FREE_STREAM 0x21 /* Free the stream ID */ +#define IPC_IA_SET_STREAM_PARAMS 0x22 +#define IPC_IA_SET_STREAM_PARAMS_MRFLD 0x12 +#define IPC_IA_GET_STREAM_PARAMS 0x23 +#define IPC_IA_PAUSE_STREAM 0x24 +#define IPC_IA_PAUSE_STREAM_MRFLD 0x4 +#define IPC_IA_RESUME_STREAM 0x25 +#define IPC_IA_RESUME_STREAM_MRFLD 0x5 +#define IPC_IA_DROP_STREAM 0x26 +#define IPC_IA_DROP_STREAM_MRFLD 0x07 +#define IPC_IA_DRAIN_STREAM 0x27 /* Short msg with str_id */ +#define IPC_IA_DRAIN_STREAM_MRFLD 0x8 +#define IPC_IA_CONTROL_ROUTING 0x29 +#define IPC_IA_VTSV_UPDATE_MODULES 0x20 +#define IPC_IA_VTSV_DETECTED 0x21 + +#define IPC_IA_START_STREAM_MRFLD 0X06 +#define IPC_IA_START_STREAM 0x30 /* Short msg with str_id */ + +#define IPC_IA_SET_GAIN_MRFLD 0x21 +/* Debug msgs */ +#define IPC_IA_DBG_MEM_READ 0x40 +#define IPC_IA_DBG_MEM_WRITE 0x41 +#define IPC_IA_DBG_LOOP_BACK 0x42 +#define IPC_IA_DBG_LOG_ENABLE 0x45 +#define IPC_IA_DBG_SET_PROBE_PARAMS 0x47 + +/* L2I Firmware/Codec Download msgs */ +#define IPC_IA_FW_INIT_CMPLT 0x81 +#define IPC_IA_FW_INIT_CMPLT_MRFLD 0x01 +#define IPC_IA_FW_ASYNC_ERR_MRFLD 0x11 + +/* L2I Codec Config/control msgs */ +#define IPC_SST_FRAGMENT_ELPASED 0x90 /* Request IA more data */ + +#define IPC_SST_BUF_UNDER_RUN 0x92 /* PB Under run and stopped */ +#define IPC_SST_BUF_OVER_RUN 0x93 /* CAP Under run and stopped */ +#define IPC_SST_DRAIN_END 0x94 /* PB Drain complete and stopped */ +#define IPC_SST_CHNGE_SSP_PARAMS 0x95 /* PB SSP parameters changed */ +#define IPC_SST_STREAM_PROCESS_FATAL_ERR 0x96/* error in processing a stream */ +#define IPC_SST_PERIOD_ELAPSED 0x97 /* period elapsed */ + +#define IPC_SST_ERROR_EVENT 0x99 /* Buffer over run occurred */ +/* L2S messages */ +#define IPC_SC_DDR_LINK_UP 0xC0 +#define IPC_SC_DDR_LINK_DOWN 0xC1 +#define IPC_SC_SET_LPECLK_REQ 0xC2 +#define IPC_SC_SSP_BIT_BANG 0xC3 + +/* L2I Error reporting msgs */ +#define IPC_IA_MEM_ALLOC_FAIL 0xE0 +#define IPC_IA_PROC_ERR 0xE1 /* error in processing a + stream can be used by playback and + capture modules */ + +/* L2I Debug msgs */ +#define IPC_IA_PRINT_STRING 0xF0 + +/* Buffer under-run */ +#define IPC_IA_BUF_UNDER_RUN_MRFLD 0x0B + +/* Mrfld specific defines: + * For asynchronous messages(INIT_CMPLT, PERIOD_ELAPSED, ASYNC_ERROR) + * received from FW, the format is: + * - IPC High: pvt_id is set to zero. Always short message. + * - msg_id is in lower 16-bits of IPC low payload. + * - pipe_id is in higher 16-bits of IPC low payload for period_elapsed. + * - error id is in higher 16-bits of IPC low payload for async errors. + */ +#define SST_ASYNC_DRV_ID 0 + +/* Command Response or Acknowledge message to any IPC message will have + * same message ID and stream ID information which is sent. + * There is no specific Ack message ID. The data field is used as response + * meaning. + */ +enum ackData { + IPC_ACK_SUCCESS = 0, + IPC_ACK_FAILURE, +}; + +enum ipc_ia_msg_id { + IPC_CMD = 1, /*!< Task Control message ID */ + IPC_SET_PARAMS = 2,/*!< Task Set param message ID */ + IPC_GET_PARAMS = 3, /*!< Task Get param message ID */ + IPC_INVALID = 0xFF, /*!<Task Get param message ID */ +}; + enum sst_codec_types { /* AUDIO/MUSIC CODEC Type Definitions */ SST_CODEC_TYPE_UNKNOWN = 0, @@ -35,14 +171,157 @@ enum stream_type { SST_STREAM_TYPE_MUSIC = 1, }; +enum sst_error_codes { + /* Error code,response to msgId: Description */ + /* Common error codes */ + SST_SUCCESS = 0, /* Success */ + SST_ERR_INVALID_STREAM_ID = 1, + SST_ERR_INVALID_MSG_ID = 2, + SST_ERR_INVALID_STREAM_OP = 3, + SST_ERR_INVALID_PARAMS = 4, + SST_ERR_INVALID_CODEC = 5, + SST_ERR_INVALID_MEDIA_TYPE = 6, + SST_ERR_STREAM_ERR = 7, + + SST_ERR_STREAM_IN_USE = 15, +}; + +struct ipc_dsp_hdr { + u16 mod_index_id:8; /*!< DSP Command ID specific to tasks */ + u16 pipe_id:8; /*!< instance of the module in the pipeline */ + u16 mod_id; /*!< Pipe_id */ + u16 cmd_id; /*!< Module ID = lpe_algo_types_t */ + u16 length; /*!< Length of the payload only */ +} __packed; + +union ipc_header_high { + struct { + u32 msg_id:8; /* Message ID - Max 256 Message Types */ + u32 task_id:4; /* Task ID associated with this comand */ + u32 drv_id:4; /* Identifier for the driver to track*/ + u32 rsvd1:8; /* Reserved */ + u32 result:4; /* Reserved */ + u32 res_rqd:1; /* Response rqd */ + u32 large:1; /* Large Message if large = 1 */ + u32 done:1; /* bit 30 - Done bit */ + u32 busy:1; /* bit 31 - busy bit*/ + } part; + u32 full; +} __packed; +/* IPC header */ +union ipc_header_mrfld { + struct { + u32 header_low_payload; + union ipc_header_high header_high; + } p; + u64 full; +} __packed; +/* CAUTION NOTE: All IPC message body must be multiple of 32 bits.*/ + +/* IPC Header */ +union ipc_header { + struct { + u32 msg_id:8; /* Message ID - Max 256 Message Types */ + u32 str_id:5; + u32 large:1; /* Large Message if large = 1 */ + u32 reserved:2; /* Reserved for future use */ + u32 data:14; /* Ack/Info for msg, size of msg in Mailbox */ + u32 done:1; /* bit 30 */ + u32 busy:1; /* bit 31 */ + } part; + u32 full; +} __packed; + +/* Firmware build info */ +struct sst_fw_build_info { + unsigned char date[16]; /* Firmware build date */ + unsigned char time[16]; /* Firmware build time */ +} __packed; + +/* Firmware Version info */ +struct snd_sst_fw_version { + u8 build; /* build number*/ + u8 minor; /* minor number*/ + u8 major; /* major number*/ + u8 type; /* build type */ +}; + +struct ipc_header_fw_init { + struct snd_sst_fw_version fw_version;/* Firmware version details */ + struct sst_fw_build_info build_info; + u16 result; /* Fw init result */ + u8 module_id; /* Module ID in case of error */ + u8 debug_info; /* Debug info from Module ID in case of fail */ +} __packed; + +struct snd_sst_tstamp { + u64 ring_buffer_counter; /* PB/CP: Bytes copied from/to DDR. */ + u64 hardware_counter; /* PB/CP: Bytes DMAed to/from SSP. */ + u64 frames_decoded; + u64 bytes_decoded; + u64 bytes_copied; + u32 sampling_frequency; + u32 channel_peak[8]; +} __packed; + +/* Stream type params struture for Alloc stream */ +struct snd_sst_str_type { + u8 codec_type; /* Codec type */ + u8 str_type; /* 1 = voice 2 = music */ + u8 operation; /* Playback or Capture */ + u8 protected_str; /* 0=Non DRM, 1=DRM */ + u8 time_slots; + u8 reserved; /* Reserved */ + u16 result; /* Result used for acknowledgment */ +} __packed; + +/* Library info structure */ +struct module_info { + u32 lib_version; + u32 lib_type;/*TBD- KLOCKWORK u8 lib_type;*/ + u32 media_type; + u8 lib_name[12]; + u32 lib_caps; + unsigned char b_date[16]; /* Lib build date */ + unsigned char b_time[16]; /* Lib build time */ +} __packed; + +/* Library slot info */ +struct lib_slot_info { + u8 slot_num; /* 1 or 2 */ + u8 reserved1; + u16 reserved2; + u32 iram_size; /* slot size in IRAM */ + u32 dram_size; /* slot size in DRAM */ + u32 iram_offset; /* starting offset of slot in IRAM */ + u32 dram_offset; /* starting offset of slot in DRAM */ +} __packed; + +struct snd_ppp_mixer_params { + __u32 type; /*Type of the parameter */ + __u32 size; + __u32 input_stream_bitmap; /*Input stream Bit Map*/ +} __packed; + +struct snd_sst_lib_download { + struct module_info lib_info; /* library info type, capabilities etc */ + struct lib_slot_info slot_info; /* slot info to be downloaded */ + u32 mod_entry_pt; +}; + +struct snd_sst_lib_download_info { + struct snd_sst_lib_download dload_lib; + u16 result; /* Result used for acknowledgment */ + u8 pvt_id; /* Private ID */ + u8 reserved; /* for alignment */ +}; struct snd_pcm_params { u8 num_chan; /* 1=Mono, 2=Stereo */ u8 pcm_wd_sz; /* 16/24 - bit*/ - u32 reserved; /* Bitrate in bits per second */ - u32 sfreq; /* Sampling rate in Hz */ - u8 use_offload_path; + u8 use_offload_path; /* 0-PCM using period elpased & ALSA interfaces + 1-PCM stream via compressed interface */ u8 reserved2; - u16 reserved3; + u32 sfreq; /* Sampling rate in Hz */ u8 channel_map[8]; } __packed; @@ -76,6 +355,7 @@ struct snd_aac_params { struct snd_wma_params { u8 num_chan; /* 1=Mono, 2=Stereo */ u8 pcm_wd_sz; /* 16/24 - bit*/ + u16 reserved1; u32 brate; /* Use the hard coded value. */ u32 sfreq; /* Sampling freq eg. 8000, 441000, 48000 */ u32 channel_mask; /* Channel Mask */ @@ -101,26 +381,153 @@ struct sst_address_info { }; struct snd_sst_alloc_params_ext { - struct sst_address_info ring_buf_info[8]; - u8 sg_count; - u8 reserved; - u16 reserved2; - u32 frag_size; /*Number of samples after which period elapsed + __u16 sg_count; + __u16 reserved; + __u32 frag_size; /*Number of samples after which period elapsed message is sent valid only if path = 0*/ -} __packed; + struct sst_address_info ring_buf_info[8]; +}; struct snd_sst_stream_params { union snd_sst_codec_params uc; } __packed; struct snd_sst_params { + u32 result; u32 stream_id; u8 codec; u8 ops; u8 stream_type; u8 device_type; + u8 task; struct snd_sst_stream_params sparams; struct snd_sst_alloc_params_ext aparams; }; +struct snd_sst_alloc_mrfld { + u16 codec_type; + u8 operation; + u8 sg_count; + struct sst_address_info ring_buf_info[8]; + u32 frag_size; + u32 ts; + struct snd_sst_stream_params codec_params; +} __packed; + +/* Alloc stream params structure */ +struct snd_sst_alloc_params { + struct snd_sst_str_type str_type; + struct snd_sst_stream_params stream_params; + struct snd_sst_alloc_params_ext alloc_params; +} __packed; + +/* Alloc stream response message */ +struct snd_sst_alloc_response { + struct snd_sst_str_type str_type; /* Stream type for allocation */ + struct snd_sst_lib_download lib_dnld; /* Valid only for codec dnld */ +}; + +/* Drop response */ +struct snd_sst_drop_response { + u32 result; + u32 bytes; +}; + +struct snd_sst_async_msg { + u32 msg_id; /* Async msg id */ + u32 payload[0]; +}; + +struct snd_sst_async_err_msg { + u32 fw_resp; /* Firmware Result */ + u32 lib_resp; /*Library result */ +} __packed; + +struct snd_sst_vol { + u32 stream_id; + s32 volume; + u32 ramp_duration; + u32 ramp_type; /* Ramp type, default=0 */ +}; + +/* Gain library parameters for mrfld + * based on DSP command spec v0.82 + */ +struct snd_sst_gain_v2 { + u16 gain_cell_num; /* num of gain cells to modify*/ + u8 cell_nbr_idx; /* instance index*/ + u8 cell_path_idx; /* pipe-id */ + u16 module_id; /*module id */ + u16 left_cell_gain; /* left gain value in dB*/ + u16 right_cell_gain; /* right gain value in dB*/ + u16 gain_time_const; /* gain time constant*/ +} __packed; + +struct snd_sst_mute { + u32 stream_id; + u32 mute; +}; + +struct snd_sst_runtime_params { + u8 type; + u8 str_id; + u8 size; + u8 rsvd; + void *addr; +} __packed; + +enum stream_param_type { + SST_SET_TIME_SLOT = 0, + SST_SET_CHANNEL_INFO = 1, + OTHERS = 2, /*reserved for future params*/ +}; + +/* CSV Voice call routing structure */ +struct snd_sst_control_routing { + u8 control; /* 0=start, 1=Stop */ + u8 reserved[3]; /* Reserved- for 32 bit alignment */ +}; + +struct ipc_post { + struct list_head node; + union ipc_header header; /* driver specific */ + bool is_large; + bool is_process_reply; + union ipc_header_mrfld mrfld_header; + char *mailbox_data; +}; + +struct snd_sst_ctxt_params { + u32 address; /* Physical Address in DDR where the context is stored */ + u32 size; /* size of the context */ +}; + +struct snd_sst_lpe_log_params { + u8 dbg_type; + u8 module_id; + u8 log_level; + u8 reserved; +} __packed; + +enum snd_sst_bytes_type { + SND_SST_BYTES_SET = 0x1, + SND_SST_BYTES_GET = 0x2, +}; + +struct snd_sst_bytes_v2 { + u8 type; + u8 ipc_msg; + u8 block; + u8 task_id; + u8 pipe_id; + u8 rsvd; + u16 len; + char bytes[0]; +}; + +#define MAX_VTSV_FILES 2 +struct snd_sst_vtsv_info { + struct sst_address_info vfiles[MAX_VTSV_FILES]; +} __packed; + #endif /* __SST_MFLD_DSP_H__ */ diff --git a/sound/soc/intel/sst-mfld-platform-compress.c b/sound/soc/intel/sst-mfld-platform-compress.c index 02abd19fce1d..29c059ca19e8 100644 --- a/sound/soc/intel/sst-mfld-platform-compress.c +++ b/sound/soc/intel/sst-mfld-platform-compress.c @@ -100,14 +100,19 @@ static int sst_platform_compr_set_params(struct snd_compr_stream *cstream, int retval; struct snd_sst_params str_params; struct sst_compress_cb cb; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct sst_data *ctx = snd_soc_platform_get_drvdata(platform); stream = cstream->runtime->private_data; /* construct fw structure for this*/ memset(&str_params, 0, sizeof(str_params)); - str_params.ops = STREAM_OPS_PLAYBACK; - str_params.stream_type = SST_STREAM_TYPE_MUSIC; - str_params.device_type = SND_SST_DEVICE_COMPRESS; + /* fill the device type and stream id to pass to SST driver */ + retval = sst_fill_stream_params(cstream, ctx, &str_params, true); + pr_debug("compr_set_params: fill stream params ret_val = 0x%x\n", retval); + if (retval < 0) + return retval; switch (params->codec.id) { case SND_AUDIOCODEC_MP3: { diff --git a/sound/soc/intel/sst-mfld-platform-pcm.c b/sound/soc/intel/sst-mfld-platform-pcm.c index 7c790f51d259..706212a6a68c 100644 --- a/sound/soc/intel/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/sst-mfld-platform-pcm.c @@ -1,7 +1,7 @@ /* * sst_mfld_platform.c - Intel MID Platform driver * - * Copyright (C) 2010-2013 Intel Corp + * Copyright (C) 2010-2014 Intel Corp * Author: Vinod Koul <vinod.koul@intel.com> * Author: Harsha Priya <priya.harsha@intel.com> * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -27,7 +27,9 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/compress_driver.h> +#include <asm/platform_sst_audio.h> #include "sst-mfld-platform.h" +#include "sst-atom-controls.h" struct sst_device *sst; static DEFINE_MUTEX(sst_lock); @@ -92,6 +94,13 @@ static struct snd_pcm_hardware sst_platform_pcm_hw = { .fifo_size = SST_FIFO_SIZE, }; +static struct sst_dev_stream_map dpcm_strm_map[] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, /* Reserved, not in use */ + {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA1_IN, SST_TASK_ID_MEDIA, 0}, + {MERR_DPCM_COMPR, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA0_IN, SST_TASK_ID_MEDIA, 0}, + {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0}, +}; + /* MFLD - MSIC */ static struct snd_soc_dai_driver sst_platform_dai[] = { { @@ -143,58 +152,142 @@ static inline int sst_get_stream_status(struct sst_runtime_stream *stream) return state; } +static void sst_fill_alloc_params(struct snd_pcm_substream *substream, + struct snd_sst_alloc_params_ext *alloc_param) +{ + unsigned int channels; + snd_pcm_uframes_t period_size; + ssize_t periodbytes; + ssize_t buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + u32 buffer_addr = virt_to_phys(substream->dma_buffer.area); + + channels = substream->runtime->channels; + period_size = substream->runtime->period_size; + periodbytes = samples_to_bytes(substream->runtime, period_size); + alloc_param->ring_buf_info[0].addr = buffer_addr; + alloc_param->ring_buf_info[0].size = buffer_bytes; + alloc_param->sg_count = 1; + alloc_param->reserved = 0; + alloc_param->frag_size = periodbytes * channels; + +} static void sst_fill_pcm_params(struct snd_pcm_substream *substream, - struct sst_pcm_params *param) + struct snd_sst_stream_params *param) { + param->uc.pcm_params.num_chan = (u8) substream->runtime->channels; + param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits; + param->uc.pcm_params.sfreq = substream->runtime->rate; + + /* PCM stream via ALSA interface */ + param->uc.pcm_params.use_offload_path = 0; + param->uc.pcm_params.reserved2 = 0; + memset(param->uc.pcm_params.channel_map, 0, sizeof(u8)); - param->num_chan = (u8) substream->runtime->channels; - param->pcm_wd_sz = substream->runtime->sample_bits; - param->reserved = 0; - param->sfreq = substream->runtime->rate; - param->ring_buffer_size = snd_pcm_lib_buffer_bytes(substream); - param->period_count = substream->runtime->period_size; - param->ring_buffer_addr = virt_to_phys(substream->dma_buffer.area); - pr_debug("period_cnt = %d\n", param->period_count); - pr_debug("sfreq= %d, wd_sz = %d\n", param->sfreq, param->pcm_wd_sz); } -static int sst_platform_alloc_stream(struct snd_pcm_substream *substream) +static int sst_get_stream_mapping(int dev, int sdev, int dir, + struct sst_dev_stream_map *map, int size) +{ + int i; + + if (map == NULL) + return -EINVAL; + + + /* index 0 is not used in stream map */ + for (i = 1; i < size; i++) { + if ((map[i].dev_num == dev) && (map[i].direction == dir)) + return i; + } + return 0; +} + +int sst_fill_stream_params(void *substream, + const struct sst_data *ctx, struct snd_sst_params *str_params, bool is_compress) +{ + int map_size; + int index; + struct sst_dev_stream_map *map; + struct snd_pcm_substream *pstream = NULL; + struct snd_compr_stream *cstream = NULL; + + map = ctx->pdata->pdev_strm_map; + map_size = ctx->pdata->strm_map_size; + + if (is_compress == true) + cstream = (struct snd_compr_stream *)substream; + else + pstream = (struct snd_pcm_substream *)substream; + + str_params->stream_type = SST_STREAM_TYPE_MUSIC; + + /* For pcm streams */ + if (pstream) { + index = sst_get_stream_mapping(pstream->pcm->device, + pstream->number, pstream->stream, + map, map_size); + if (index <= 0) + return -EINVAL; + + str_params->stream_id = index; + str_params->device_type = map[index].device_id; + str_params->task = map[index].task_id; + + str_params->ops = (u8)pstream->stream; + } + + if (cstream) { + index = sst_get_stream_mapping(cstream->device->device, + 0, cstream->direction, + map, map_size); + if (index <= 0) + return -EINVAL; + str_params->stream_id = index; + str_params->device_type = map[index].device_id; + str_params->task = map[index].task_id; + + str_params->ops = (u8)cstream->direction; + } + return 0; +} + +static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + struct snd_soc_platform *platform) { struct sst_runtime_stream *stream = substream->runtime->private_data; - struct sst_pcm_params param = {0}; - struct sst_stream_params str_params = {0}; - int ret_val; + struct snd_sst_stream_params param = {{{0,},},}; + struct snd_sst_params str_params = {0}; + struct snd_sst_alloc_params_ext alloc_params = {0}; + int ret_val = 0; + struct sst_data *ctx = snd_soc_platform_get_drvdata(platform); /* set codec params and inform SST driver the same */ sst_fill_pcm_params(substream, ¶m); + sst_fill_alloc_params(substream, &alloc_params); substream->runtime->dma_area = substream->dma_buffer.area; str_params.sparams = param; - str_params.codec = param.codec; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - str_params.ops = STREAM_OPS_PLAYBACK; - str_params.device_type = substream->pcm->device + 1; - pr_debug("Playbck stream,Device %d\n", - substream->pcm->device); - } else { - str_params.ops = STREAM_OPS_CAPTURE; - str_params.device_type = SND_SST_DEVICE_CAPTURE; - pr_debug("Capture stream,Device %d\n", - substream->pcm->device); - } - ret_val = stream->ops->open(&str_params); - pr_debug("SST_SND_PLAY/CAPTURE ret_val = %x\n", ret_val); + str_params.aparams = alloc_params; + str_params.codec = SST_CODEC_TYPE_PCM; + + /* fill the device type and stream id to pass to SST driver */ + ret_val = sst_fill_stream_params(substream, ctx, &str_params, false); if (ret_val < 0) return ret_val; - stream->stream_info.str_id = ret_val; - pr_debug("str id : %d\n", stream->stream_info.str_id); + stream->stream_info.str_id = str_params.stream_id; + + ret_val = stream->ops->open(&str_params); + if (ret_val <= 0) + return ret_val; + + return ret_val; } -static void sst_period_elapsed(void *mad_substream) +static void sst_period_elapsed(void *arg) { - struct snd_pcm_substream *substream = mad_substream; + struct snd_pcm_substream *substream = arg; struct sst_runtime_stream *stream; int status; @@ -218,7 +311,7 @@ static int sst_platform_init_stream(struct snd_pcm_substream *substream) pr_debug("setting buffer ptr param\n"); sst_set_stream_status(stream, SST_PLATFORM_INIT); stream->stream_info.period_elapsed = sst_period_elapsed; - stream->stream_info.mad_substream = substream; + stream->stream_info.arg = substream; stream->stream_info.buffer_ptr = 0; stream->stream_info.sfreq = substream->runtime->rate; ret_val = stream->ops->device_control( @@ -230,19 +323,12 @@ static int sst_platform_init_stream(struct snd_pcm_substream *substream) } /* end -- helper functions */ -static int sst_platform_open(struct snd_pcm_substream *substream) +static int sst_media_open(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + int ret_val = 0; struct snd_pcm_runtime *runtime = substream->runtime; struct sst_runtime_stream *stream; - int ret_val; - - pr_debug("sst_platform_open called\n"); - - snd_soc_set_runtime_hwparams(substream, &sst_platform_pcm_hw); - ret_val = snd_pcm_hw_constraint_integer(runtime, - SNDRV_PCM_HW_PARAM_PERIODS); - if (ret_val < 0) - return ret_val; stream = kzalloc(sizeof(*stream), GFP_KERNEL); if (!stream) @@ -251,50 +337,69 @@ static int sst_platform_open(struct snd_pcm_substream *substream) /* get the sst ops */ mutex_lock(&sst_lock); - if (!sst) { + if (!sst || + !try_module_get(sst->dev->driver->owner)) { pr_err("no device available to run\n"); - mutex_unlock(&sst_lock); - kfree(stream); - return -ENODEV; - } - if (!try_module_get(sst->dev->driver->owner)) { - mutex_unlock(&sst_lock); - kfree(stream); - return -ENODEV; + ret_val = -ENODEV; + goto out_ops; } stream->ops = sst->ops; mutex_unlock(&sst_lock); stream->stream_info.str_id = 0; - sst_set_stream_status(stream, SST_PLATFORM_INIT); - stream->stream_info.mad_substream = substream; + + stream->stream_info.arg = substream; /* allocate memory for SST API set */ runtime->private_data = stream; - return 0; + /* Make sure, that the period size is always even */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +out_ops: + kfree(stream); + mutex_unlock(&sst_lock); + return ret_val; } -static int sst_platform_close(struct snd_pcm_substream *substream) +static void sst_media_close(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct sst_runtime_stream *stream; int ret_val = 0, str_id; - pr_debug("sst_platform_close called\n"); stream = substream->runtime->private_data; str_id = stream->stream_info.str_id; if (str_id) ret_val = stream->ops->close(str_id); module_put(sst->dev->driver->owner); kfree(stream); - return ret_val; } -static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream) +static inline unsigned int get_current_pipe_id(struct snd_soc_platform *platform, + struct snd_pcm_substream *substream) +{ + struct sst_data *sst = snd_soc_platform_get_drvdata(platform); + struct sst_dev_stream_map *map = sst->pdata->pdev_strm_map; + struct sst_runtime_stream *stream = + substream->runtime->private_data; + u32 str_id = stream->stream_info.str_id; + unsigned int pipe_id; + pipe_id = map[str_id].device_id; + + pr_debug("%s: got pipe_id = %#x for str_id = %d\n", + __func__, pipe_id, str_id); + return pipe_id; +} + +static int sst_media_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct sst_runtime_stream *stream; int ret_val = 0, str_id; - pr_debug("sst_platform_pcm_prepare called\n"); stream = substream->runtime->private_data; str_id = stream->stream_info.str_id; if (stream->stream_info.str_id) { @@ -303,8 +408,8 @@ static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream) return ret_val; } - ret_val = sst_platform_alloc_stream(substream); - if (ret_val < 0) + ret_val = sst_platform_alloc_stream(substream, dai->platform); + if (ret_val <= 0) return ret_val; snprintf(substream->pcm->id, sizeof(substream->pcm->id), "%d", stream->stream_info.str_id); @@ -316,6 +421,41 @@ static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream) return ret_val; } +static int sst_media_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); + return 0; +} + +static int sst_media_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_lib_free_pages(substream); +} + +static struct snd_soc_dai_ops sst_media_dai_ops = { + .startup = sst_media_open, + .shutdown = sst_media_close, + .prepare = sst_media_prepare, + .hw_params = sst_media_hw_params, + .hw_free = sst_media_hw_free, +}; + +static int sst_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (substream->pcm->internal) + return 0; + + runtime = substream->runtime; + runtime->hw = sst_platform_pcm_hw; + return 0; +} + static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { @@ -331,7 +471,7 @@ static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, pr_debug("sst: Trigger Start\n"); str_cmd = SST_SND_START; status = SST_PLATFORM_RUNNING; - stream->stream_info.mad_substream = substream; + stream->stream_info.arg = substream; break; case SNDRV_PCM_TRIGGER_STOP: pr_debug("sst: in stop\n"); @@ -377,32 +517,15 @@ static snd_pcm_uframes_t sst_platform_pcm_pointer pr_err("sst: error code = %d\n", ret_val); return ret_val; } - return stream->stream_info.buffer_ptr; -} - -static int sst_platform_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); - memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); - - return 0; -} - -static int sst_platform_pcm_hw_free(struct snd_pcm_substream *substream) -{ - return snd_pcm_lib_free_pages(substream); + substream->runtime->delay = str_info->pcm_delay; + return str_info->buffer_ptr; } static struct snd_pcm_ops sst_platform_ops = { .open = sst_platform_open, - .close = sst_platform_close, .ioctl = snd_pcm_lib_ioctl, - .prepare = sst_platform_pcm_prepare, .trigger = sst_platform_pcm_trigger, .pointer = sst_platform_pcm_pointer, - .hw_params = sst_platform_pcm_hw_params, - .hw_free = sst_platform_pcm_hw_free, }; static void sst_pcm_free(struct snd_pcm *pcm) @@ -413,15 +536,15 @@ static void sst_pcm_free(struct snd_pcm *pcm) static int sst_pcm_new(struct snd_soc_pcm_runtime *rtd) { + struct snd_soc_dai *dai = rtd->cpu_dai; struct snd_pcm *pcm = rtd->pcm; int retval = 0; - pr_debug("sst_pcm_new called\n"); - if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || - pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + if (dai->driver->playback.channels_min || + dai->driver->capture.channels_min) { retval = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), + snd_dma_continuous_data(GFP_DMA), SST_MIN_BUFFER, SST_MAX_BUFFER); if (retval) { pr_err("dma buffer allocationf fail\n"); @@ -445,10 +568,28 @@ static const struct snd_soc_component_driver sst_component = { static int sst_platform_probe(struct platform_device *pdev) { + struct sst_data *drv; int ret; + struct sst_platform_data *pdata; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (drv == NULL) { + pr_err("kzalloc failed\n"); + return -ENOMEM; + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) { + pr_err("kzalloc failed for pdata\n"); + return -ENOMEM; + } + + pdata->pdev_strm_map = dpcm_strm_map; + pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map); + drv->pdata = pdata; + mutex_init(&drv->lock); + dev_set_drvdata(&pdev->dev, drv); - pr_debug("sst_platform_probe called\n"); - sst = NULL; ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv); if (ret) { pr_err("registering soc platform failed\n"); diff --git a/sound/soc/intel/sst-mfld-platform.h b/sound/soc/intel/sst-mfld-platform.h index 6c5e7dc49e3c..6c6a42c08e24 100644 --- a/sound/soc/intel/sst-mfld-platform.h +++ b/sound/soc/intel/sst-mfld-platform.h @@ -39,9 +39,10 @@ extern struct sst_device *sst; struct pcm_stream_info { int str_id; - void *mad_substream; - void (*period_elapsed) (void *mad_substream); + void *arg; + void (*period_elapsed) (void *arg); unsigned long long buffer_ptr; + unsigned long long pcm_delay; int sfreq; }; @@ -62,7 +63,9 @@ enum sst_controls { SST_SND_BUFFER_POINTER = 0x05, SST_SND_STREAM_INIT = 0x06, SST_SND_START = 0x07, - SST_MAX_CONTROLS = 0x07, + SST_SET_BYTE_STREAM = 0x100A, + SST_GET_BYTE_STREAM = 0x100B, + SST_MAX_CONTROLS = SST_GET_BYTE_STREAM, }; enum sst_stream_ops { @@ -124,8 +127,9 @@ struct compress_sst_ops { }; struct sst_ops { - int (*open) (struct sst_stream_params *str_param); + int (*open) (struct snd_sst_params *str_param); int (*device_control) (int cmd, void *arg); + int (*set_generic_params)(enum sst_controls cmd, void *arg); int (*close) (unsigned int str_id); }; @@ -143,10 +147,27 @@ struct sst_device { char *name; struct device *dev; struct sst_ops *ops; + struct platform_device *pdev; struct compress_sst_ops *compr_ops; }; +struct sst_data; void sst_set_stream_status(struct sst_runtime_stream *stream, int state); +int sst_fill_stream_params(void *substream, const struct sst_data *ctx, + struct snd_sst_params *str_params, bool is_compress); + +struct sst_algo_int_control_v2 { + struct soc_mixer_control mc; + u16 module_id; /* module identifieer */ + u16 pipe_id; /* location info: pipe_id + instance_id */ + u16 instance_id; + unsigned int value; /* Value received is stored here */ +}; +struct sst_data { + struct platform_device *pdev; + struct sst_platform_data *pdata; + struct mutex lock; +}; int sst_register_dsp(struct sst_device *sst); int sst_unregister_dsp(struct sst_device *sst); #endif diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig index 06f4e8aa93ae..132bb83f8e99 100644 --- a/sound/soc/kirkwood/Kconfig +++ b/sound/soc/kirkwood/Kconfig @@ -1,6 +1,6 @@ config SND_KIRKWOOD_SOC tristate "SoC Audio for the Marvell Kirkwood and Dove chips" - depends on ARCH_KIRKWOOD || ARCH_DOVE || ARCH_MVEBU || MACH_KIRKWOOD || COMPILE_TEST + depends on ARCH_DOVE || ARCH_MVEBU || COMPILE_TEST help Say Y or M if you want to add support for codecs attached to the Kirkwood I2S interface. You will also need to select the @@ -15,20 +15,3 @@ config SND_KIRKWOOD_SOC_ARMADA370_DB Say Y if you want to add support for SoC audio on the Armada 370 Development Board. -config SND_KIRKWOOD_SOC_OPENRD - tristate "SoC Audio support for Kirkwood Openrd Client" - depends on SND_KIRKWOOD_SOC && (MACH_OPENRD_CLIENT || MACH_OPENRD_ULTIMATE || COMPILE_TEST) - depends on I2C - select SND_SOC_CS42L51 - help - Say Y if you want to add support for SoC audio on - Openrd Client. - -config SND_KIRKWOOD_SOC_T5325 - tristate "SoC Audio support for HP t5325" - depends on SND_KIRKWOOD_SOC && (MACH_T5325 || COMPILE_TEST) && I2C - select SND_SOC_ALC5623 - help - Say Y if you want to add support for SoC audio on - the HP t5325 thin client. - diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile index 7c1d8fe09e6b..c36b03d8006c 100644 --- a/sound/soc/kirkwood/Makefile +++ b/sound/soc/kirkwood/Makefile @@ -2,10 +2,6 @@ snd-soc-kirkwood-objs := kirkwood-dma.o kirkwood-i2s.o obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o -snd-soc-openrd-objs := kirkwood-openrd.o -snd-soc-t5325-objs := kirkwood-t5325.o snd-soc-armada-370-db-objs := armada-370-db.o obj-$(CONFIG_SND_KIRKWOOD_SOC_ARMADA370_DB) += snd-soc-armada-370-db.o -obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o -obj-$(CONFIG_SND_KIRKWOOD_SOC_T5325) += snd-soc-t5325.o diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c index aac22fccdcdc..4cf2245950d7 100644 --- a/sound/soc/kirkwood/kirkwood-dma.c +++ b/sound/soc/kirkwood/kirkwood-dma.c @@ -28,11 +28,12 @@ static struct kirkwood_dma_data *kirkwood_priv(struct snd_pcm_substream *subs) } static struct snd_pcm_hardware kirkwood_dma_snd_hw = { - .info = (SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_PAUSE), + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, .buffer_bytes_max = KIRKWOOD_SND_MAX_BUFFER_BYTES, .period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES, .period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES, diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c index 9f842222e798..0704cd6d2314 100644 --- a/sound/soc/kirkwood/kirkwood-i2s.c +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -212,7 +212,8 @@ static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, KIRKWOOD_PLAYCTL_SIZE_MASK); priv->ctl_play |= ctl_play; } else { - priv->ctl_rec &= ~KIRKWOOD_RECCTL_SIZE_MASK; + priv->ctl_rec &= ~(KIRKWOOD_RECCTL_ENABLE_MASK | + KIRKWOOD_RECCTL_SIZE_MASK); priv->ctl_rec |= ctl_rec; } @@ -221,14 +222,24 @@ static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, return 0; } +static unsigned kirkwood_i2s_play_mute(unsigned ctl) +{ + if (!(ctl & KIRKWOOD_PLAYCTL_I2S_EN)) + ctl |= KIRKWOOD_PLAYCTL_I2S_MUTE; + if (!(ctl & KIRKWOOD_PLAYCTL_SPDIF_EN)) + ctl |= KIRKWOOD_PLAYCTL_SPDIF_MUTE; + return ctl; +} + static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { + struct snd_pcm_runtime *runtime = substream->runtime; struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); uint32_t ctl, value; ctl = readl(priv->io + KIRKWOOD_PLAYCTL); - if (ctl & KIRKWOOD_PLAYCTL_PAUSE) { + if ((ctl & KIRKWOOD_PLAYCTL_ENABLE_MASK) == 0) { unsigned timeout = 5000; /* * The Armada510 spec says that if we enter pause mode, the @@ -256,14 +267,16 @@ static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, ctl &= ~KIRKWOOD_PLAYCTL_SPDIF_EN; /* i2s */ else ctl &= ~KIRKWOOD_PLAYCTL_I2S_EN; /* spdif */ - + ctl = kirkwood_i2s_play_mute(ctl); value = ctl & ~KIRKWOOD_PLAYCTL_ENABLE_MASK; writel(value, priv->io + KIRKWOOD_PLAYCTL); /* enable interrupts */ - value = readl(priv->io + KIRKWOOD_INT_MASK); - value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; - writel(value, priv->io + KIRKWOOD_INT_MASK); + if (!runtime->no_period_wakeup) { + value = readl(priv->io + KIRKWOOD_INT_MASK); + value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + } /* enable playback */ writel(ctl, priv->io + KIRKWOOD_PLAYCTL); @@ -295,6 +308,7 @@ static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ctl &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE | KIRKWOOD_PLAYCTL_SPDIF_MUTE); + ctl = kirkwood_i2s_play_mute(ctl); writel(ctl, priv->io + KIRKWOOD_PLAYCTL); break; @@ -322,8 +336,7 @@ static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, else ctl &= ~KIRKWOOD_RECCTL_I2S_EN; /* spdif */ - value = ctl & ~(KIRKWOOD_RECCTL_I2S_EN | - KIRKWOOD_RECCTL_SPDIF_EN); + value = ctl & ~KIRKWOOD_RECCTL_ENABLE_MASK; writel(value, priv->io + KIRKWOOD_RECCTL); /* enable interrupts */ @@ -347,7 +360,7 @@ static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, /* disable all records */ value = readl(priv->io + KIRKWOOD_RECCTL); - value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); + value &= ~KIRKWOOD_RECCTL_ENABLE_MASK; writel(value, priv->io + KIRKWOOD_RECCTL); break; @@ -411,7 +424,7 @@ static int kirkwood_i2s_init(struct kirkwood_dma_data *priv) writel(value, priv->io + KIRKWOOD_PLAYCTL); value = readl(priv->io + KIRKWOOD_RECCTL); - value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); + value &= ~KIRKWOOD_RECCTL_ENABLE_MASK; writel(value, priv->io + KIRKWOOD_RECCTL); return 0; diff --git a/sound/soc/kirkwood/kirkwood-openrd.c b/sound/soc/kirkwood/kirkwood-openrd.c deleted file mode 100644 index 65f2a5b9ec3b..000000000000 --- a/sound/soc/kirkwood/kirkwood-openrd.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * kirkwood-openrd.c - * - * (c) 2010 Arnaud Patard <apatard@mandriva.com> - * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <sound/soc.h> -#include <linux/platform_data/asoc-kirkwood.h> -#include "../codecs/cs42l51.h" - -static int openrd_client_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - unsigned int freq; - - switch (params_rate(params)) { - default: - case 44100: - freq = 11289600; - break; - case 48000: - freq = 12288000; - break; - case 96000: - freq = 24576000; - break; - } - - return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN); - -} - -static struct snd_soc_ops openrd_client_ops = { - .hw_params = openrd_client_hw_params, -}; - - -static struct snd_soc_dai_link openrd_client_dai[] = { -{ - .name = "CS42L51", - .stream_name = "CS42L51 HiFi", - .cpu_dai_name = "i2s", - .platform_name = "mvebu-audio", - .codec_dai_name = "cs42l51-hifi", - .codec_name = "cs42l51-codec.0-004a", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS, - .ops = &openrd_client_ops, -}, -}; - - -static struct snd_soc_card openrd_client = { - .name = "OpenRD Client", - .owner = THIS_MODULE, - .dai_link = openrd_client_dai, - .num_links = ARRAY_SIZE(openrd_client_dai), -}; - -static int openrd_probe(struct platform_device *pdev) -{ - struct snd_soc_card *card = &openrd_client; - int ret; - - card->dev = &pdev->dev; - - ret = snd_soc_register_card(card); - if (ret) - dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", - ret); - return ret; -} - -static int openrd_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - - snd_soc_unregister_card(card); - return 0; -} - -static struct platform_driver openrd_driver = { - .driver = { - .name = "openrd-client-audio", - .owner = THIS_MODULE, - }, - .probe = openrd_probe, - .remove = openrd_remove, -}; - -module_platform_driver(openrd_driver); - -/* Module information */ -MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); -MODULE_DESCRIPTION("ALSA SoC OpenRD Client"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:openrd-client-audio"); diff --git a/sound/soc/kirkwood/kirkwood-t5325.c b/sound/soc/kirkwood/kirkwood-t5325.c deleted file mode 100644 index 844b8415a011..000000000000 --- a/sound/soc/kirkwood/kirkwood-t5325.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * kirkwood-t5325.c - * - * (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <sound/soc.h> -#include <linux/platform_data/asoc-kirkwood.h> -#include "../codecs/alc5623.h" - -static int t5325_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - unsigned int freq; - - freq = params_rate(params) * 256; - - return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN); - -} - -static struct snd_soc_ops t5325_ops = { - .hw_params = t5325_hw_params, -}; - -static const struct snd_soc_dapm_widget t5325_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_SPK("Speaker", NULL), - SND_SOC_DAPM_MIC("Mic Jack", NULL), -}; - -static const struct snd_soc_dapm_route t5325_route[] = { - { "Headphone Jack", NULL, "HPL" }, - { "Headphone Jack", NULL, "HPR" }, - - {"Speaker", NULL, "SPKOUT"}, - {"Speaker", NULL, "SPKOUTN"}, - - { "MIC1", NULL, "Mic Jack" }, - { "MIC2", NULL, "Mic Jack" }, -}; - -static struct snd_soc_dai_link t5325_dai[] = { -{ - .name = "ALC5621", - .stream_name = "ALC5621 HiFi", - .cpu_dai_name = "i2s", - .platform_name = "mvebu-audio", - .codec_dai_name = "alc5621-hifi", - .codec_name = "alc562x-codec.0-001a", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS, - .ops = &t5325_ops, -}, -}; - -static struct snd_soc_card t5325 = { - .name = "t5325", - .owner = THIS_MODULE, - .dai_link = t5325_dai, - .num_links = ARRAY_SIZE(t5325_dai), - - .dapm_widgets = t5325_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(t5325_dapm_widgets), - .dapm_routes = t5325_route, - .num_dapm_routes = ARRAY_SIZE(t5325_route), -}; - -static int t5325_probe(struct platform_device *pdev) -{ - struct snd_soc_card *card = &t5325; - int ret; - - card->dev = &pdev->dev; - - ret = snd_soc_register_card(card); - if (ret) - dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", - ret); - return ret; -} - -static int t5325_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - - snd_soc_unregister_card(card); - return 0; -} - -static struct platform_driver t5325_driver = { - .driver = { - .name = "t5325-audio", - .owner = THIS_MODULE, - }, - .probe = t5325_probe, - .remove = t5325_remove, -}; - -module_platform_driver(t5325_driver); - -MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); -MODULE_DESCRIPTION("ALSA SoC t5325 audio client"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:t5325-audio"); diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h index bf23afbba1d7..90e32a781424 100644 --- a/sound/soc/kirkwood/kirkwood.h +++ b/sound/soc/kirkwood/kirkwood.h @@ -38,6 +38,9 @@ #define KIRKWOOD_RECCTL_SIZE_24 (1<<0) #define KIRKWOOD_RECCTL_SIZE_32 (0<<0) +#define KIRKWOOD_RECCTL_ENABLE_MASK (KIRKWOOD_RECCTL_SPDIF_EN | \ + KIRKWOOD_RECCTL_I2S_EN) + #define KIRKWOOD_REC_BUF_ADDR 0x1004 #define KIRKWOOD_REC_BUF_SIZE 0x1008 #define KIRKWOOD_REC_BYTE_COUNT 0x100C @@ -121,9 +124,9 @@ /* Theses values come from the marvell alsa driver */ /* need to find where they come from */ -#define KIRKWOOD_SND_MIN_PERIODS 8 +#define KIRKWOOD_SND_MIN_PERIODS 2 #define KIRKWOOD_SND_MAX_PERIODS 16 -#define KIRKWOOD_SND_MIN_PERIOD_BYTES 0x800 +#define KIRKWOOD_SND_MIN_PERIOD_BYTES 256 #define KIRKWOOD_SND_MAX_PERIOD_BYTES 0x8000 #define KIRKWOOD_SND_MAX_BUFFER_BYTES (KIRKWOOD_SND_MAX_PERIOD_BYTES \ * KIRKWOOD_SND_MAX_PERIODS) diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c index 1f41951d8b7f..8c9cc64a9dfb 100644 --- a/sound/soc/omap/ams-delta.c +++ b/sound/soc/omap/ams-delta.c @@ -301,7 +301,7 @@ static int cx81801_open(struct tty_struct *tty) static void cx81801_close(struct tty_struct *tty) { struct snd_soc_codec *codec = tty->disc_data; - struct snd_soc_dapm_context *dapm = &codec->card->dapm; + struct snd_soc_dapm_context *dapm = &codec->component.card->dapm; del_timer_sync(&cx81801_timer); @@ -527,7 +527,7 @@ static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static int ams_delta_card_remove(struct snd_soc_pcm_runtime *rtd) +static int ams_delta_card_remove(struct snd_soc_card *card) { snd_soc_jack_free_gpios(&ams_delta_hook_switch, ARRAY_SIZE(ams_delta_hook_switch_gpios), diff --git a/sound/soc/omap/omap-dmic.c b/sound/soc/omap/omap-dmic.c index 6925d7141215..0f34e28a3d55 100644 --- a/sound/soc/omap/omap-dmic.c +++ b/sound/soc/omap/omap-dmic.c @@ -466,7 +466,7 @@ static int asoc_dmic_probe(struct platform_device *pdev) mutex_init(&dmic->mutex); - dmic->fclk = clk_get(dmic->dev, "fck"); + dmic->fclk = devm_clk_get(dmic->dev, "fck"); if (IS_ERR(dmic->fclk)) { dev_err(dmic->dev, "cant get fck\n"); return -ENODEV; @@ -475,8 +475,7 @@ static int asoc_dmic_probe(struct platform_device *pdev) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); if (!res) { dev_err(dmic->dev, "invalid dma memory resource\n"); - ret = -ENODEV; - goto err_put_clk; + return -ENODEV; } dmic->dma_data.addr = res->start + OMAP_DMIC_DATA_REG; @@ -484,34 +483,19 @@ static int asoc_dmic_probe(struct platform_device *pdev) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); dmic->io_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(dmic->io_base)) { - ret = PTR_ERR(dmic->io_base); - goto err_put_clk; - } + if (IS_ERR(dmic->io_base)) + return PTR_ERR(dmic->io_base); - ret = snd_soc_register_component(&pdev->dev, &omap_dmic_component, - &omap_dmic_dai, 1); + ret = devm_snd_soc_register_component(&pdev->dev, + &omap_dmic_component, + &omap_dmic_dai, 1); if (ret) - goto err_put_clk; + return ret; ret = omap_pcm_platform_register(&pdev->dev); if (ret) - goto err_put_clk; - - return 0; - -err_put_clk: - clk_put(dmic->fclk); - return ret; -} - -static int asoc_dmic_remove(struct platform_device *pdev) -{ - struct omap_dmic *dmic = platform_get_drvdata(pdev); - - snd_soc_unregister_component(&pdev->dev); - clk_put(dmic->fclk); + return ret; return 0; } @@ -529,7 +513,6 @@ static struct platform_driver asoc_dmic_driver = { .of_match_table = omap_dmic_of_match, }, .probe = asoc_dmic_probe, - .remove = asoc_dmic_remove, }; module_platform_driver(asoc_dmic_driver); diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index efe2cd699b77..bd3ef2a88be0 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -805,8 +805,9 @@ static int asoc_mcbsp_probe(struct platform_device *pdev) if (ret) return ret; - ret = snd_soc_register_component(&pdev->dev, &omap_mcbsp_component, - &omap_mcbsp_dai, 1); + ret = devm_snd_soc_register_component(&pdev->dev, + &omap_mcbsp_component, + &omap_mcbsp_dai, 1); if (ret) return ret; @@ -817,8 +818,6 @@ static int asoc_mcbsp_remove(struct platform_device *pdev) { struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); - snd_soc_unregister_component(&pdev->dev); - if (mcbsp->pdata->ops && mcbsp->pdata->ops->free) mcbsp->pdata->ops->free(mcbsp->id); diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 8d809f8509c8..f4b05bc23e4b 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -31,6 +31,7 @@ #include <sound/pcm_params.h> #include <sound/dmaengine_pcm.h> #include <sound/soc.h> +#include <sound/omap-pcm.h> #ifdef CONFIG_ARCH_OMAP1 #define pcm_omap1510() cpu_is_omap1510() diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c index b4e282871658..f8a6adc2d81c 100644 --- a/sound/soc/omap/omap-twl4030.c +++ b/sound/soc/omap/omap-twl4030.c @@ -231,9 +231,8 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) return ret; } -static int omap_twl4030_card_remove(struct snd_soc_pcm_runtime *rtd) +static int omap_twl4030_card_remove(struct snd_soc_card *card) { - struct snd_soc_card *card = rtd->card; struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); if (priv->jack_detect > 0) diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index 47a10290535b..943922c79f78 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -334,7 +334,7 @@ static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) return err; } -static int rx51_card_remove(struct snd_soc_pcm_runtime *rtd) +static int rx51_card_remove(struct snd_soc_card *card) { snd_soc_jack_free_gpios(&rx51_av_jack, ARRAY_SIZE(rx51_av_jack_gpios), rx51_av_jack_gpios); diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 6acb225ec6fd..2434b6d61675 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -11,6 +11,7 @@ config SND_PXA2XX_SOC config SND_MMP_SOC bool "Soc Audio for Marvell MMP chips" depends on ARCH_MMP + select MMP_SRAM select SND_SOC_GENERIC_DMAENGINE_PCM select SND_ARM help @@ -40,7 +41,7 @@ config SND_MMP_SOC_SSPA config SND_PXA2XX_SOC_CORGI tristate "SoC Audio support for Sharp Zaurus SL-C7x0" - depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx + depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx && I2C select SND_PXA2XX_SOC_I2S select SND_SOC_WM8731 help @@ -49,7 +50,7 @@ config SND_PXA2XX_SOC_CORGI config SND_PXA2XX_SOC_SPITZ tristate "SoC Audio support for Sharp Zaurus SL-Cxx00" - depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00 + depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00 && I2C select SND_PXA2XX_SOC_I2S select SND_SOC_WM8750 help @@ -58,7 +59,7 @@ config SND_PXA2XX_SOC_SPITZ config SND_PXA2XX_SOC_Z2 tristate "SoC Audio support for Zipit Z2" - depends on SND_PXA2XX_SOC && MACH_ZIPIT2 + depends on SND_PXA2XX_SOC && MACH_ZIPIT2 && I2C select SND_PXA2XX_SOC_I2S select SND_SOC_WM8750 help @@ -66,7 +67,7 @@ config SND_PXA2XX_SOC_Z2 config SND_PXA2XX_SOC_POODLE tristate "SoC Audio support for Poodle" - depends on SND_PXA2XX_SOC && MACH_POODLE + depends on SND_PXA2XX_SOC && MACH_POODLE && I2C select SND_PXA2XX_SOC_I2S select SND_SOC_WM8731 help @@ -181,7 +182,7 @@ config SND_PXA2XX_SOC_HX4700 config SND_PXA2XX_SOC_MAGICIAN tristate "SoC Audio support for HTC Magician" - depends on SND_PXA2XX_SOC && MACH_MAGICIAN + depends on SND_PXA2XX_SOC && MACH_MAGICIAN && I2C select SND_PXA2XX_SOC_I2S select SND_PXA_SOC_SSP select SND_SOC_UDA1380 diff --git a/sound/soc/pxa/hx4700.c b/sound/soc/pxa/hx4700.c index 6b81acaffddd..05559a725bec 100644 --- a/sound/soc/pxa/hx4700.c +++ b/sound/soc/pxa/hx4700.c @@ -152,7 +152,7 @@ static int hx4700_ak4641_init(struct snd_soc_pcm_runtime *rtd) return err; } -static int hx4700_card_remove(struct snd_soc_pcm_runtime *rtd) +static int hx4700_card_remove(struct snd_soc_card *card) { snd_soc_jack_free_gpios(&hs_jack, 1, &hs_jack_gpio); diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 199a8b377553..0109f6c2334e 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -723,7 +723,8 @@ static int pxa_ssp_probe(struct snd_soc_dai *dai) ssp_handle = of_parse_phandle(dev->of_node, "port", 0); if (!ssp_handle) { dev_err(dev, "unable to get 'port' phandle\n"); - return -ENODEV; + ret = -ENODEV; + goto err_priv; } priv->ssp = pxa_ssp_request_of(ssp_handle, "SoC audio"); diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig new file mode 100644 index 000000000000..c196a466eef6 --- /dev/null +++ b/sound/soc/rockchip/Kconfig @@ -0,0 +1,12 @@ +config SND_SOC_ROCKCHIP + tristate "ASoC support for Rockchip" + depends on COMPILE_TEST || ARCH_ROCKCHIP + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_ROCKCHIP_I2S + help + Say Y or M if you want to add support for codecs attached to + the Rockchip SoCs' Audio interfaces. You will also need to + select the audio interfaces to support below. + +config SND_ROCKCHIP_I2S + tristate diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile new file mode 100644 index 000000000000..1006418e1394 --- /dev/null +++ b/sound/soc/rockchip/Makefile @@ -0,0 +1,4 @@ +# ROCKCHIP Platform Support +snd-soc-i2s-objs := rockchip_i2s.o + +obj-$(CONFIG_SND_ROCKCHIP_I2S) += snd-soc-i2s.o diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c new file mode 100644 index 000000000000..8d8e4b59049f --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -0,0 +1,529 @@ +/* sound/soc/rockchip/rockchip_i2s.c + * + * ALSA SoC Audio Layer - Rockchip I2S Controller driver + * + * Copyright (c) 2014 Rockchip Electronics Co. Ltd. + * Author: Jianqun <jay.xu@rock-chips.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/delay.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/pcm_params.h> +#include <sound/dmaengine_pcm.h> + +#include "rockchip_i2s.h" + +#define DRV_NAME "rockchip-i2s" + +struct rk_i2s_dev { + struct device *dev; + + struct clk *hclk; + struct clk *mclk; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + struct regmap *regmap; + +/* + * Used to indicate the tx/rx status. + * I2S controller hopes to start the tx and rx together, + * also to stop them when they are both try to stop. +*/ + bool tx_start; + bool rx_start; +}; + +static int i2s_runtime_suspend(struct device *dev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(i2s->mclk); + + return 0; +} + +static int i2s_runtime_resume(struct device *dev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->mclk); + if (ret) { + dev_err(i2s->dev, "clock enable failed %d\n", ret); + return ret; + } + + return 0; +} + +static inline struct rk_i2s_dev *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +static void rockchip_snd_txctrl(struct rk_i2s_dev *i2s, int on) +{ + unsigned int val = 0; + int retry = 10; + + if (on) { + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_TDE_ENABLE, I2S_DMACR_TDE_ENABLE); + + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | I2S_XFER_RXS_START, + I2S_XFER_TXS_START | I2S_XFER_RXS_START); + + i2s->tx_start = true; + } else { + i2s->tx_start = false; + + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_TDE_ENABLE, I2S_DMACR_TDE_DISABLE); + + if (!i2s->rx_start) { + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_STOP | + I2S_XFER_RXS_STOP); + + regmap_update_bits(i2s->regmap, I2S_CLR, + I2S_CLR_TXC | I2S_CLR_RXC, + I2S_CLR_TXC | I2S_CLR_RXC); + + regmap_read(i2s->regmap, I2S_CLR, &val); + + /* Should wait for clear operation to finish */ + while (val) { + regmap_read(i2s->regmap, I2S_CLR, &val); + retry--; + if (!retry) + dev_warn(i2s->dev, "fail to clear\n"); + } + } + } +} + +static void rockchip_snd_rxctrl(struct rk_i2s_dev *i2s, int on) +{ + unsigned int val = 0; + int retry = 10; + + if (on) { + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_RDE_ENABLE, I2S_DMACR_RDE_ENABLE); + + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | I2S_XFER_RXS_START, + I2S_XFER_TXS_START | I2S_XFER_RXS_START); + + i2s->rx_start = true; + } else { + i2s->rx_start = false; + + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_RDE_ENABLE, I2S_DMACR_RDE_DISABLE); + + if (!i2s->tx_start) { + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_STOP | + I2S_XFER_RXS_STOP); + + regmap_update_bits(i2s->regmap, I2S_CLR, + I2S_CLR_TXC | I2S_CLR_RXC, + I2S_CLR_TXC | I2S_CLR_RXC); + + regmap_read(i2s->regmap, I2S_CLR, &val); + + /* Should wait for clear operation to finish */ + while (val) { + regmap_read(i2s->regmap, I2S_CLR, &val); + retry--; + if (!retry) + dev_warn(i2s->dev, "fail to clear\n"); + } + } + } +} + +static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct rk_i2s_dev *i2s = to_info(cpu_dai); + unsigned int mask = 0, val = 0; + + mask = I2S_CKR_MSS_SLAVE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val = I2S_CKR_MSS_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + val = I2S_CKR_MSS_MASTER; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, I2S_CKR, mask, val); + + mask = I2S_TXCR_IBM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_TXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_TXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_TXCR_IBM_NORMAL; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, I2S_TXCR, mask, val); + + mask = I2S_RXCR_IBM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_RXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_RXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_RXCR_IBM_NORMAL; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, I2S_RXCR, mask, val); + + return 0; +} + +static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = to_info(dai); + unsigned int val = 0; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + val |= I2S_TXCR_VDW(8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + val |= I2S_TXCR_VDW(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= I2S_TXCR_VDW(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + val |= I2S_TXCR_VDW(24); + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, I2S_TXCR, I2S_TXCR_VDW_MASK, val); + regmap_update_bits(i2s->regmap, I2S_RXCR, I2S_RXCR_VDW_MASK, val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dai->playback_dma_data = &i2s->playback_dma_data; + regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_TDL_MASK, + I2S_DMACR_TDL(1) | I2S_DMACR_TDE_ENABLE); + } else { + dai->capture_dma_data = &i2s->capture_dma_data; + regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_RDL_MASK, + I2S_DMACR_RDL(1) | I2S_DMACR_RDE_ENABLE); + } + + return 0; +} + +static int rockchip_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = to_info(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 1); + else + rockchip_snd_txctrl(i2s, 1); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 0); + else + rockchip_snd_txctrl(i2s, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rockchip_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct rk_i2s_dev *i2s = to_info(cpu_dai); + int ret; + + ret = clk_set_rate(i2s->mclk, freq); + if (ret) + dev_err(i2s->dev, "Fail to set mclk %d\n", ret); + + return ret; +} + +static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = { + .hw_params = rockchip_i2s_hw_params, + .set_sysclk = rockchip_i2s_set_sysclk, + .set_fmt = rockchip_i2s_set_fmt, + .trigger = rockchip_i2s_trigger, +}; + +static struct snd_soc_dai_driver rockchip_i2s_dai = { + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &rockchip_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver rockchip_i2s_component = { + .name = DRV_NAME, +}; + +static bool rockchip_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_RXDR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_FIFOLR: + case I2S_INTSR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_FIFOLR: + return true; + default: + return false; + } +} + +static const struct regmap_config rockchip_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = I2S_RXDR, + .writeable_reg = rockchip_i2s_wr_reg, + .readable_reg = rockchip_i2s_rd_reg, + .volatile_reg = rockchip_i2s_volatile_reg, + .precious_reg = rockchip_i2s_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int rockchip_i2s_probe(struct platform_device *pdev) +{ + struct rk_i2s_dev *i2s; + struct resource *res; + void __iomem *regs; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) { + dev_err(&pdev->dev, "Can't allocate rk_i2s_dev\n"); + return -ENOMEM; + } + + /* try to prepare related clocks */ + i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk"); + if (IS_ERR(i2s->hclk)) { + dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); + return PTR_ERR(i2s->hclk); + } + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); + if (IS_ERR(i2s->mclk)) { + dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); + return PTR_ERR(i2s->mclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &rockchip_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, + "Failed to initialise managed register map\n"); + return PTR_ERR(i2s->regmap); + } + + i2s->playback_dma_data.addr = res->start + I2S_TXDR; + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma_data.maxburst = 16; + + i2s->capture_dma_data.addr = res->start + I2S_RXDR; + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma_data.maxburst = 16; + + i2s->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, i2s); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_component, + &rockchip_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_pcm_register; + } + + return 0; + +err_pcm_register: + snd_dmaengine_pcm_unregister(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int rockchip_i2s_remove(struct platform_device *pdev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_runtime_suspend(&pdev->dev); + + clk_disable_unprepare(i2s->mclk); + clk_disable_unprepare(i2s->hclk); + snd_dmaengine_pcm_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id rockchip_i2s_match[] = { + { .compatible = "rockchip,rk3066-i2s", }, + {}, +}; + +static const struct dev_pm_ops rockchip_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, + NULL) +}; + +static struct platform_driver rockchip_i2s_driver = { + .probe = rockchip_i2s_probe, + .remove = rockchip_i2s_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rockchip_i2s_match), + .pm = &rockchip_i2s_pm_ops, + }, +}; +module_platform_driver(rockchip_i2s_driver); + +MODULE_DESCRIPTION("ROCKCHIP IIS ASoC Interface"); +MODULE_AUTHOR("jianqun <jay.xu@rock-chips.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, rockchip_i2s_match); diff --git a/sound/soc/rockchip/rockchip_i2s.h b/sound/soc/rockchip/rockchip_i2s.h new file mode 100644 index 000000000000..89a5d8bc6ee7 --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s.h @@ -0,0 +1,223 @@ +/* + * sound/soc/rockchip/rockchip_i2s.h + * + * ALSA SoC Audio Layer - Rockchip I2S Controller driver + * + * Copyright (c) 2014 Rockchip Electronics Co. Ltd. + * Author: Jianqun xu <jay.xu@rock-chips.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 _ROCKCHIP_IIS_H +#define _ROCKCHIP_IIS_H + +/* + * TXCR + * transmit operation control register +*/ +#define I2S_TXCR_RCNT_SHIFT 17 +#define I2S_TXCR_RCNT_MASK (0x3f << I2S_TXCR_RCNT_SHIFT) +#define I2S_TXCR_CSR_SHIFT 15 +#define I2S_TXCR_CSR(x) (x << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_CSR_MASK (3 << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_HWT BIT(14) +#define I2S_TXCR_SJM_SHIFT 12 +#define I2S_TXCR_SJM_R (0 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_SJM_L (1 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_FBM_SHIFT 11 +#define I2S_TXCR_FBM_MSB (0 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_FBM_LSB (1 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_IBM_SHIFT 9 +#define I2S_TXCR_IBM_NORMAL (0 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_LSJM (1 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_RSJM (2 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_MASK (3 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_PBM_SHIFT 7 +#define I2S_TXCR_PBM_MODE(x) (x << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_PBM_MASK (3 << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_TFS_SHIFT 5 +#define I2S_TXCR_TFS_I2S (0 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_PCM (1 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_VDW_SHIFT 0 +#define I2S_TXCR_VDW(x) ((x - 1) << I2S_TXCR_VDW_SHIFT) +#define I2S_TXCR_VDW_MASK (0x1f << I2S_TXCR_VDW_SHIFT) + +/* + * RXCR + * receive operation control register +*/ +#define I2S_RXCR_HWT BIT(14) +#define I2S_RXCR_SJM_SHIFT 12 +#define I2S_RXCR_SJM_R (0 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_SJM_L (1 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_FBM_SHIFT 11 +#define I2S_RXCR_FBM_MSB (0 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_FBM_LSB (1 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_IBM_SHIFT 9 +#define I2S_RXCR_IBM_NORMAL (0 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_LSJM (1 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_RSJM (2 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_MASK (3 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_PBM_SHIFT 7 +#define I2S_RXCR_PBM_MODE(x) (x << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_PBM_MASK (3 << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_TFS_SHIFT 5 +#define I2S_RXCR_TFS_I2S (0 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_PCM (1 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_VDW_SHIFT 0 +#define I2S_RXCR_VDW(x) ((x - 1) << I2S_RXCR_VDW_SHIFT) +#define I2S_RXCR_VDW_MASK (0x1f << I2S_RXCR_VDW_SHIFT) + +/* + * CKR + * clock generation register +*/ +#define I2S_CKR_MSS_SHIFT 27 +#define I2S_CKR_MSS_MASTER (0 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_SLAVE (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_MASK (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_CKP_SHIFT 26 +#define I2S_CKR_CKP_NEG (0 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_CKP_POS (1 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_RLP_SHIFT 25 +#define I2S_CKR_RLP_NORMAL (0 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_RLP_OPPSITE (1 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_TLP_SHIFT 24 +#define I2S_CKR_TLP_NORMAL (0 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_TLP_OPPSITE (1 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_MDIV_SHIFT 16 +#define I2S_CKR_MDIV(x) ((x - 1) << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_MDIV_MASK (0xff << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_RSD_SHIFT 8 +#define I2S_CKR_RSD(x) ((x - 1) << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_RSD_MASK (0xff << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_TSD_SHIFT 0 +#define I2S_CKR_TSD(x) ((x - 1) << I2S_CKR_TSD_SHIFT) +#define I2S_CKR_TSD_MASK (0xff << I2S_CKR_TSD_SHIFT) + +/* + * FIFOLR + * FIFO level register +*/ +#define I2S_FIFOLR_RFL_SHIFT 24 +#define I2S_FIFOLR_RFL_MASK (0x3f << I2S_FIFOLR_RFL_SHIFT) +#define I2S_FIFOLR_TFL3_SHIFT 18 +#define I2S_FIFOLR_TFL3_MASK (0x3f << I2S_FIFOLR_TFL3_SHIFT) +#define I2S_FIFOLR_TFL2_SHIFT 12 +#define I2S_FIFOLR_TFL2_MASK (0x3f << I2S_FIFOLR_TFL2_SHIFT) +#define I2S_FIFOLR_TFL1_SHIFT 6 +#define I2S_FIFOLR_TFL1_MASK (0x3f << I2S_FIFOLR_TFL1_SHIFT) +#define I2S_FIFOLR_TFL0_SHIFT 0 +#define I2S_FIFOLR_TFL0_MASK (0x3f << I2S_FIFOLR_TFL0_SHIFT) + +/* + * DMACR + * DMA control register +*/ +#define I2S_DMACR_RDE_SHIFT 24 +#define I2S_DMACR_RDE_DISABLE (0 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDE_ENABLE (1 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDL_SHIFT 16 +#define I2S_DMACR_RDL(x) ((x - 1) << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_RDL_MASK (0x1f << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_TDE_SHIFT 8 +#define I2S_DMACR_TDE_DISABLE (0 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDE_ENABLE (1 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDL_SHIFT 0 +#define I2S_DMACR_TDL(x) ((x - 1) << I2S_DMACR_TDL_SHIFT) +#define I2S_DMACR_TDL_MASK (0x1f << I2S_DMACR_TDL_SHIFT) + +/* + * INTCR + * interrupt control register +*/ +#define I2S_INTCR_RFT_SHIFT 20 +#define I2S_INTCR_RFT(x) ((x - 1) << I2S_INTCR_RFT_SHIFT) +#define I2S_INTCR_RXOIC BIT(18) +#define I2S_INTCR_RXOIE_SHIFT 17 +#define I2S_INTCR_RXOIE_DISABLE (0 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXOIE_ENABLE (1 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXFIE_SHIFT 16 +#define I2S_INTCR_RXFIE_DISABLE (0 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_RXFIE_ENABLE (1 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_TFT_SHIFT 4 +#define I2S_INTCR_TFT(x) ((x - 1) << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TFT_MASK (0x1f << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TXUIC BIT(2) +#define I2S_INTCR_TXUIE_SHIFT 1 +#define I2S_INTCR_TXUIE_DISABLE (0 << I2S_INTCR_TXUIE_SHIFT) +#define I2S_INTCR_TXUIE_ENABLE (1 << I2S_INTCR_TXUIE_SHIFT) + +/* + * INTSR + * interrupt status register +*/ +#define I2S_INTSR_TXEIE_SHIFT 0 +#define I2S_INTSR_TXEIE_DISABLE (0 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_TXEIE_ENABLE (1 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_RXOI_SHIFT 17 +#define I2S_INTSR_RXOI_INA (0 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXOI_ACT (1 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXFI_SHIFT 16 +#define I2S_INTSR_RXFI_INA (0 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_RXFI_ACT (1 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_TXUI_SHIFT 1 +#define I2S_INTSR_TXUI_INA (0 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXUI_ACT (1 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXEI_SHIFT 0 +#define I2S_INTSR_TXEI_INA (0 << I2S_INTSR_TXEI_SHIFT) +#define I2S_INTSR_TXEI_ACT (1 << I2S_INTSR_TXEI_SHIFT) + +/* + * XFER + * Transfer start register +*/ +#define I2S_XFER_RXS_SHIFT 1 +#define I2S_XFER_RXS_STOP (0 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_RXS_START (1 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_TXS_SHIFT 0 +#define I2S_XFER_TXS_STOP (0 << I2S_XFER_TXS_SHIFT) +#define I2S_XFER_TXS_START (1 << I2S_XFER_TXS_SHIFT) + +/* + * CLR + * clear SCLK domain logic register +*/ +#define I2S_CLR_RXC BIT(1) +#define I2S_CLR_TXC BIT(0) + +/* + * TXDR + * Transimt FIFO data register, write only. +*/ +#define I2S_TXDR_MASK (0xff) + +/* + * RXDR + * Receive FIFO data register, write only. +*/ +#define I2S_RXDR_MASK (0xff) + +/* Clock divider id */ +enum { + ROCKCHIP_DIV_MCLK = 0, + ROCKCHIP_DIV_BCLK, +}; + +/* I2S REGS */ +#define I2S_TXCR (0x0000) +#define I2S_RXCR (0x0004) +#define I2S_CKR (0x0008) +#define I2S_FIFOLR (0x000c) +#define I2S_DMACR (0x0010) +#define I2S_INTCR (0x0014) +#define I2S_INTSR (0x0018) +#define I2S_XFER (0x001c) +#define I2S_CLR (0x0020) +#define I2S_TXDR (0x0024) +#define I2S_RXDR (0x0028) + +#endif /* _ROCKCHIP_IIS_H */ diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c index 7eba7979b9af..1c8d01166e5b 100644 --- a/sound/soc/s6000/s6000-i2s.c +++ b/sound/soc/s6000/s6000-i2s.c @@ -570,7 +570,7 @@ err_release_none: return ret; } -static void s6000_i2s_remove(struct platform_device *pdev) +static int s6000_i2s_remove(struct platform_device *pdev) { struct s6000_i2s_dev *dev = dev_get_drvdata(&pdev->dev); struct resource *region; @@ -597,6 +597,8 @@ static void s6000_i2s_remove(struct platform_device *pdev) iounmap(mmio); region = platform_get_resource(pdev, IORESOURCE_IO, 0); release_mem_region(region->start, resource_size(region)); + + return 0; } static struct platform_driver s6000_i2s_driver = { diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c index 720357f11a7f..f2d7980d7ddc 100644 --- a/sound/soc/samsung/h1940_uda1380.c +++ b/sound/soc/samsung/h1940_uda1380.c @@ -189,7 +189,7 @@ static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static int h1940_uda1380_card_remove(struct snd_soc_pcm_runtime *rtd) +static int h1940_uda1380_card_remove(struct snd_soc_card *card) { snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), hp_jack_gpios); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 2ac76fa3e742..d2533dbc8399 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -68,6 +68,8 @@ struct i2s_dai { #define DAI_OPENED (1 << 0) /* Dai is opened */ #define DAI_MANAGER (1 << 1) /* Dai is the manager */ unsigned mode; + /* CDCLK pin direction: 0 - input, 1 - output */ + unsigned int cdclk_out:1; /* Driver for this DAI */ struct snd_soc_dai_driver i2s_dai_drv; /* DMA parameters */ @@ -737,6 +739,9 @@ static int i2s_startup(struct snd_pcm_substream *substream, spin_unlock_irqrestore(&lock, flags); + if (!is_opened(other) && i2s->cdclk_out) + i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_OUT); return 0; } @@ -752,9 +757,13 @@ static void i2s_shutdown(struct snd_pcm_substream *substream, i2s->mode &= ~DAI_OPENED; i2s->mode &= ~DAI_MANAGER; - if (is_opened(other)) + if (is_opened(other)) { other->mode |= DAI_MANAGER; - + } else { + u32 mod = readl(i2s->addr + I2SMOD); + i2s->cdclk_out = !(mod & MOD_CDCLKCON); + other->cdclk_out = i2s->cdclk_out; + } /* Reset any constraint on RFS and BFS */ i2s->rfs = 0; i2s->bfs = 0; @@ -920,11 +929,9 @@ static int i2s_suspend(struct snd_soc_dai *dai) { struct i2s_dai *i2s = to_info(dai); - if (dai->active) { - i2s->suspend_i2smod = readl(i2s->addr + I2SMOD); - i2s->suspend_i2scon = readl(i2s->addr + I2SCON); - i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR); - } + i2s->suspend_i2smod = readl(i2s->addr + I2SMOD); + i2s->suspend_i2scon = readl(i2s->addr + I2SCON); + i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR); return 0; } @@ -933,11 +940,9 @@ static int i2s_resume(struct snd_soc_dai *dai) { struct i2s_dai *i2s = to_info(dai); - if (dai->active) { - writel(i2s->suspend_i2scon, i2s->addr + I2SCON); - writel(i2s->suspend_i2smod, i2s->addr + I2SMOD); - writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR); - } + writel(i2s->suspend_i2scon, i2s->addr + I2SCON); + writel(i2s->suspend_i2smod, i2s->addr + I2SMOD); + writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR); return 0; } diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c index 192aa9fc102f..37688ebbb2b4 100644 --- a/sound/soc/samsung/rx1950_uda1380.c +++ b/sound/soc/samsung/rx1950_uda1380.c @@ -31,7 +31,7 @@ #include "s3c24xx-i2s.h" static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd); -static int rx1950_uda1380_card_remove(struct snd_soc_pcm_runtime *rtd); +static int rx1950_uda1380_card_remove(struct snd_soc_card *card); static int rx1950_startup(struct snd_pcm_substream *substream); static int rx1950_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); @@ -236,7 +236,7 @@ static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd) return 0; } -static int rx1950_uda1380_card_remove(struct snd_soc_pcm_runtime *rtd) +static int rx1950_uda1380_card_remove(struct snd_soc_card *card) { snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), hp_jack_gpios); diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c index 271a904277a1..9b0ffacab790 100644 --- a/sound/soc/samsung/smartq_wm8987.c +++ b/sound/soc/samsung/smartq_wm8987.c @@ -182,7 +182,7 @@ static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd) return err; } -static int smartq_wm8987_card_remove(struct snd_soc_pcm_runtime *rtd) +static int smartq_wm8987_card_remove(struct snd_soc_card *card) { snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios), smartq_jack_gpios); diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index b43fdf0d08af..80245b6eebd6 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -37,7 +37,7 @@ config SND_SOC_SH4_SIU config SND_SOC_RCAR tristate "R-Car series SRU/SCU/SSIU/SSI support" select SND_SIMPLE_CARD - select REGMAP + select REGMAP_MMIO help This option enables R-Car SUR/SCU/SSIU/SSI sound support diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 710a079a7377..a57eb96e57eb 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -232,11 +232,7 @@ struct fsi_stream { * these are for DMAEngine */ struct dma_chan *chan; - struct work_struct work; - dma_addr_t dma; int dma_id; - int loop_cnt; - int additional_pos; }; struct fsi_clk { @@ -1042,6 +1038,26 @@ static int fsi_clk_set_rate_cpg(struct device *dev, return ret; } +static void fsi_pointer_update(struct fsi_stream *io, int size) +{ + io->buff_sample_pos += size; + + if (io->buff_sample_pos >= + io->period_samples * (io->period_pos + 1)) { + struct snd_pcm_substream *substream = io->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + io->period_pos++; + + if (io->period_pos >= runtime->periods) { + io->buff_sample_pos = 0; + io->period_pos = 0; + } + + snd_pcm_period_elapsed(substream); + } +} + /* * pio data transfer handler */ @@ -1108,31 +1124,11 @@ static int fsi_pio_transfer(struct fsi_priv *fsi, struct fsi_stream *io, void (*run32)(struct fsi_priv *fsi, u8 *buf, int samples), int samples) { - struct snd_pcm_runtime *runtime; - struct snd_pcm_substream *substream; u8 *buf; - int over_period; if (!fsi_stream_is_working(fsi, io)) return -EINVAL; - over_period = 0; - substream = io->substream; - runtime = substream->runtime; - - /* FSI FIFO has limit. - * So, this driver can not send periods data at a time - */ - if (io->buff_sample_pos >= - io->period_samples * (io->period_pos + 1)) { - - over_period = 1; - io->period_pos = (io->period_pos + 1) % runtime->periods; - - if (0 == io->period_pos) - io->buff_sample_pos = 0; - } - buf = fsi_pio_get_area(fsi, io); switch (io->sample_width) { @@ -1146,11 +1142,7 @@ static int fsi_pio_transfer(struct fsi_priv *fsi, struct fsi_stream *io, return -EINVAL; } - /* update buff_sample_pos */ - io->buff_sample_pos += samples; - - if (over_period) - snd_pcm_period_elapsed(substream); + fsi_pointer_update(io, samples); return 0; } @@ -1279,11 +1271,6 @@ static irqreturn_t fsi_interrupt(int irq, void *data) */ static int fsi_dma_init(struct fsi_priv *fsi, struct fsi_stream *io) { - struct snd_pcm_runtime *runtime = io->substream->runtime; - struct snd_soc_dai *dai = fsi_get_dai(io->substream); - enum dma_data_direction dir = fsi_stream_is_play(fsi, io) ? - DMA_TO_DEVICE : DMA_FROM_DEVICE; - /* * 24bit data : 24bit bus / package in back * 16bit data : 16bit bus / stream mode @@ -1291,107 +1278,48 @@ static int fsi_dma_init(struct fsi_priv *fsi, struct fsi_stream *io) io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | BUSOP_SET(16, PACKAGE_16BITBUS_STREAM); - io->loop_cnt = 2; /* push 1st, 2nd period first, then 3rd, 4th... */ - io->additional_pos = 0; - io->dma = dma_map_single(dai->dev, runtime->dma_area, - snd_pcm_lib_buffer_bytes(io->substream), dir); return 0; } -static int fsi_dma_quit(struct fsi_priv *fsi, struct fsi_stream *io) -{ - struct snd_soc_dai *dai = fsi_get_dai(io->substream); - enum dma_data_direction dir = fsi_stream_is_play(fsi, io) ? - DMA_TO_DEVICE : DMA_FROM_DEVICE; - - dma_unmap_single(dai->dev, io->dma, - snd_pcm_lib_buffer_bytes(io->substream), dir); - return 0; -} - -static dma_addr_t fsi_dma_get_area(struct fsi_stream *io, int additional) -{ - struct snd_pcm_runtime *runtime = io->substream->runtime; - int period = io->period_pos + additional; - - if (period >= runtime->periods) - period = 0; - - return io->dma + samples_to_bytes(runtime, period * io->period_samples); -} - static void fsi_dma_complete(void *data) { struct fsi_stream *io = (struct fsi_stream *)data; struct fsi_priv *fsi = fsi_stream_to_priv(io); - struct snd_pcm_runtime *runtime = io->substream->runtime; - struct snd_soc_dai *dai = fsi_get_dai(io->substream); - enum dma_data_direction dir = fsi_stream_is_play(fsi, io) ? - DMA_TO_DEVICE : DMA_FROM_DEVICE; - dma_sync_single_for_cpu(dai->dev, fsi_dma_get_area(io, 0), - samples_to_bytes(runtime, io->period_samples), dir); - - io->buff_sample_pos += io->period_samples; - io->period_pos++; - - if (io->period_pos >= runtime->periods) { - io->period_pos = 0; - io->buff_sample_pos = 0; - } + fsi_pointer_update(io, io->period_samples); fsi_count_fifo_err(fsi); - fsi_stream_transfer(io); - - snd_pcm_period_elapsed(io->substream); } -static void fsi_dma_do_work(struct work_struct *work) +static int fsi_dma_transfer(struct fsi_priv *fsi, struct fsi_stream *io) { - struct fsi_stream *io = container_of(work, struct fsi_stream, work); - struct fsi_priv *fsi = fsi_stream_to_priv(io); - struct snd_soc_dai *dai; + struct snd_soc_dai *dai = fsi_get_dai(io->substream); + struct snd_pcm_substream *substream = io->substream; struct dma_async_tx_descriptor *desc; - struct snd_pcm_runtime *runtime; - enum dma_data_direction dir; int is_play = fsi_stream_is_play(fsi, io); - int len, i; - dma_addr_t buf; - - if (!fsi_stream_is_working(fsi, io)) - return; - - dai = fsi_get_dai(io->substream); - runtime = io->substream->runtime; - dir = is_play ? DMA_TO_DEVICE : DMA_FROM_DEVICE; - len = samples_to_bytes(runtime, io->period_samples); - - for (i = 0; i < io->loop_cnt; i++) { - buf = fsi_dma_get_area(io, io->additional_pos); - - dma_sync_single_for_device(dai->dev, buf, len, dir); - - desc = dmaengine_prep_slave_single(io->chan, buf, len, dir, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!desc) { - dev_err(dai->dev, "dmaengine_prep_slave_sg() fail\n"); - return; - } - - desc->callback = fsi_dma_complete; - desc->callback_param = io; - - if (dmaengine_submit(desc) < 0) { - dev_err(dai->dev, "tx_submit() fail\n"); - return; - } + enum dma_data_direction dir = is_play ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + int ret = -EIO; + + desc = dmaengine_prep_dma_cyclic(io->chan, + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(dai->dev, "dmaengine_prep_dma_cyclic() fail\n"); + goto fsi_dma_transfer_err; + } - dma_async_issue_pending(io->chan); + desc->callback = fsi_dma_complete; + desc->callback_param = io; - io->additional_pos = 1; + if (dmaengine_submit(desc) < 0) { + dev_err(dai->dev, "tx_submit() fail\n"); + goto fsi_dma_transfer_err; } - io->loop_cnt = 1; + dma_async_issue_pending(io->chan); /* * FIXME @@ -1408,13 +1336,11 @@ static void fsi_dma_do_work(struct work_struct *work) fsi_reg_write(fsi, DIFF_ST, 0); } } -} -static int fsi_dma_transfer(struct fsi_priv *fsi, struct fsi_stream *io) -{ - schedule_work(&io->work); + ret = 0; - return 0; +fsi_dma_transfer_err: + return ret; } static int fsi_dma_push_start_stop(struct fsi_priv *fsi, struct fsi_stream *io, @@ -1475,15 +1401,11 @@ static int fsi_dma_probe(struct fsi_priv *fsi, struct fsi_stream *io, struct dev return fsi_stream_probe(fsi, dev); } - INIT_WORK(&io->work, fsi_dma_do_work); - return 0; } static int fsi_dma_remove(struct fsi_priv *fsi, struct fsi_stream *io) { - cancel_work_sync(&io->work); - fsi_stream_stop(fsi, io); if (io->chan) @@ -1495,7 +1417,6 @@ static int fsi_dma_remove(struct fsi_priv *fsi, struct fsi_stream *io) static struct fsi_stream_handler fsi_dma_push_handler = { .init = fsi_dma_init, - .quit = fsi_dma_quit, .probe = fsi_dma_probe, .transfer = fsi_dma_transfer, .remove = fsi_dma_remove, @@ -1657,9 +1578,9 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, if (!ret) ret = fsi_hw_startup(fsi, io, dai->dev); if (!ret) - ret = fsi_stream_transfer(io); + ret = fsi_stream_start(fsi, io); if (!ret) - fsi_stream_start(fsi, io); + ret = fsi_stream_transfer(io); break; case SNDRV_PCM_TRIGGER_STOP: if (!ret) @@ -1850,16 +1771,10 @@ static void fsi_pcm_free(struct snd_pcm *pcm) static int fsi_pcm_new(struct snd_soc_pcm_runtime *rtd) { - struct snd_pcm *pcm = rtd->pcm; - - /* - * dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel - * in MMAP mode (i.e. aplay -M) - */ return snd_pcm_lib_preallocate_pages_for_all( - pcm, - SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), + rtd->pcm, + SNDRV_DMA_TYPE_DEV, + rtd->card->snd_card->dev, PREALLOC_BUFFER, PREALLOC_BUFFER_MAX); } diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c index 91880156e1ae..19f78963e8b9 100644 --- a/sound/soc/sh/rcar/core.c +++ b/sound/soc/sh/rcar/core.c @@ -138,6 +138,17 @@ char *rsnd_mod_name(struct rsnd_mod *mod) return mod->ops->name; } +char *rsnd_mod_dma_name(struct rsnd_mod *mod) +{ + if (!mod || !mod->ops) + return "unknown"; + + if (!mod->ops->dma_name) + return mod->ops->name; + + return mod->ops->dma_name(mod); +} + void rsnd_mod_init(struct rsnd_priv *priv, struct rsnd_mod *mod, struct rsnd_mod_ops *ops, @@ -153,26 +164,8 @@ void rsnd_mod_init(struct rsnd_priv *priv, /* * rsnd_dma functions */ -static void __rsnd_dma_start(struct rsnd_dma *dma); -static void rsnd_dma_continue(struct rsnd_dma *dma) -{ - /* push next A or B plane */ - dma->submit_loop = 1; - schedule_work(&dma->work); -} - -void rsnd_dma_start(struct rsnd_dma *dma) -{ - /* push both A and B plane*/ - dma->offset = 0; - dma->submit_loop = 2; - __rsnd_dma_start(dma); -} - void rsnd_dma_stop(struct rsnd_dma *dma) { - dma->submit_loop = 0; - cancel_work_sync(&dma->work); dmaengine_terminate_all(dma->chan); } @@ -180,11 +173,7 @@ static void rsnd_dma_complete(void *data) { struct rsnd_dma *dma = (struct rsnd_dma *)data; struct rsnd_mod *mod = rsnd_dma_to_mod(dma); - struct rsnd_priv *priv = rsnd_mod_to_priv(rsnd_dma_to_mod(dma)); struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - unsigned long flags; - - rsnd_lock(priv, flags); /* * Renesas sound Gen1 needs 1 DMAC, @@ -197,57 +186,41 @@ static void rsnd_dma_complete(void *data) * rsnd_dai_pointer_update() will be called twice, * ant it will breaks io->byte_pos */ - if (dma->submit_loop) - rsnd_dma_continue(dma); - - rsnd_unlock(priv, flags); rsnd_dai_pointer_update(io, io->byte_per_period); } -static void __rsnd_dma_start(struct rsnd_dma *dma) +void rsnd_dma_start(struct rsnd_dma *dma) { struct rsnd_mod *mod = rsnd_dma_to_mod(dma); struct rsnd_priv *priv = rsnd_mod_to_priv(mod); struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct snd_pcm_substream *substream = io->substream; struct device *dev = rsnd_priv_to_dev(priv); struct dma_async_tx_descriptor *desc; - dma_addr_t buf; - size_t len = io->byte_per_period; - int i; - for (i = 0; i < dma->submit_loop; i++) { - - buf = runtime->dma_addr + - rsnd_dai_pointer_offset(io, dma->offset + len); - dma->offset = len; - - desc = dmaengine_prep_slave_single( - dma->chan, buf, len, dma->dir, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!desc) { - dev_err(dev, "dmaengine_prep_slave_sg() fail\n"); - return; - } + desc = dmaengine_prep_dma_cyclic(dma->chan, + (dma->addr) ? dma->addr : + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dma->dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - desc->callback = rsnd_dma_complete; - desc->callback_param = dma; + if (!desc) { + dev_err(dev, "dmaengine_prep_slave_sg() fail\n"); + return; + } - if (dmaengine_submit(desc) < 0) { - dev_err(dev, "dmaengine_submit() fail\n"); - return; - } + desc->callback = rsnd_dma_complete; + desc->callback_param = dma; - dma_async_issue_pending(dma->chan); + if (dmaengine_submit(desc) < 0) { + dev_err(dev, "dmaengine_submit() fail\n"); + return; } -} - -static void rsnd_dma_do_work(struct work_struct *work) -{ - struct rsnd_dma *dma = container_of(work, struct rsnd_dma, work); - __rsnd_dma_start(dma); + dma_async_issue_pending(dma->chan); } int rsnd_dma_available(struct rsnd_dma *dma) @@ -261,14 +234,27 @@ static int _rsnd_dma_of_name(char *dma_name, struct rsnd_mod *mod) { if (mod) return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d", - rsnd_mod_name(mod), rsnd_mod_id(mod)); + rsnd_mod_dma_name(mod), rsnd_mod_id(mod)); else return snprintf(dma_name, DMA_NAME_SIZE / 2, "mem"); } -static void rsnd_dma_of_name(struct rsnd_dma *dma, - int is_play, char *dma_name) +static void rsnd_dma_of_name(struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to, + char *dma_name) +{ + int index = 0; + + index = _rsnd_dma_of_name(dma_name + index, mod_from); + *(dma_name + index++) = '_'; + index = _rsnd_dma_of_name(dma_name + index, mod_to); +} + +static void rsnd_dma_of_path(struct rsnd_dma *dma, + int is_play, + struct rsnd_mod **mod_from, + struct rsnd_mod **mod_to) { struct rsnd_mod *this = rsnd_dma_to_mod(dma); struct rsnd_dai_stream *io = rsnd_mod_to_io(this); @@ -276,7 +262,6 @@ static void rsnd_dma_of_name(struct rsnd_dma *dma, struct rsnd_mod *src = rsnd_io_to_mod_src(io); struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); struct rsnd_mod *mod[MOD_MAX]; - struct rsnd_mod *src_mod, *dst_mod; int i, index; @@ -297,31 +282,34 @@ static void rsnd_dma_of_name(struct rsnd_dma *dma, for (i = 1; i < MOD_MAX; i++) { if (!src) { mod[i] = ssi; - break; } else if (!dvc) { mod[i] = src; src = NULL; } else { - mod[i] = dvc; + if ((!is_play) && (this == src)) + this = dvc; + + mod[i] = (is_play) ? src : dvc; + i++; + mod[i] = (is_play) ? dvc : src; + src = NULL; dvc = NULL; } if (mod[i] == this) index = i; + + if (mod[i] == ssi) + break; } if (is_play) { - src_mod = mod[index - 1]; - dst_mod = mod[index]; + *mod_from = mod[index - 1]; + *mod_to = mod[index]; } else { - src_mod = mod[index]; - dst_mod = mod[index + 1]; + *mod_from = mod[index]; + *mod_to = mod[index - 1]; } - - index = 0; - index = _rsnd_dma_of_name(dma_name + index, src_mod); - *(dma_name + index++) = '_'; - index = _rsnd_dma_of_name(dma_name + index, dst_mod); } int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, @@ -329,6 +317,8 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, { struct device *dev = rsnd_priv_to_dev(priv); struct dma_slave_config cfg; + struct rsnd_mod *mod_from; + struct rsnd_mod *mod_to; char dma_name[DMA_NAME_SIZE]; dma_cap_mask_t mask; int ret; @@ -341,13 +331,18 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); - if (dev->of_node) - rsnd_dma_of_name(dma, is_play, dma_name); - else - snprintf(dma_name, DMA_NAME_SIZE, - is_play ? "tx" : "rx"); + rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to); + rsnd_dma_of_name(mod_from, mod_to, dma_name); - dev_dbg(dev, "dma name : %s\n", dma_name); + cfg.slave_id = id; + cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + cfg.src_addr = rsnd_gen_dma_addr(priv, mod_from, is_play, 1); + cfg.dst_addr = rsnd_gen_dma_addr(priv, mod_to, is_play, 0); + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + dev_dbg(dev, "dma : %s %pad -> %pad\n", + dma_name, &cfg.src_addr, &cfg.dst_addr); dma->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter, (void *)id, dev, @@ -357,14 +352,12 @@ int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, return -EIO; } - rsnd_gen_dma_addr(priv, dma, &cfg, is_play, id); - ret = dmaengine_slave_config(dma->chan, &cfg); if (ret < 0) goto rsnd_dma_init_err; - dma->dir = is_play ? DMA_TO_DEVICE : DMA_FROM_DEVICE; - INIT_WORK(&dma->work, rsnd_dma_do_work); + dma->addr = is_play ? cfg.src_addr : cfg.dst_addr; + dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; return 0; @@ -631,40 +624,41 @@ static int rsnd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - /* set clock inversion */ - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_IF: - rdai->bit_clk_inv = 0; - rdai->frm_clk_inv = 1; - break; - case SND_SOC_DAIFMT_IB_NF: - rdai->bit_clk_inv = 1; - rdai->frm_clk_inv = 0; - break; - case SND_SOC_DAIFMT_IB_IF: - rdai->bit_clk_inv = 1; - rdai->frm_clk_inv = 1; - break; - case SND_SOC_DAIFMT_NB_NF: - default: - rdai->bit_clk_inv = 0; - rdai->frm_clk_inv = 0; - break; - } - /* set format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: rdai->sys_delay = 0; rdai->data_alignment = 0; + rdai->frm_clk_inv = 0; break; case SND_SOC_DAIFMT_LEFT_J: rdai->sys_delay = 1; rdai->data_alignment = 0; + rdai->frm_clk_inv = 1; break; case SND_SOC_DAIFMT_RIGHT_J: rdai->sys_delay = 1; rdai->data_alignment = 1; + rdai->frm_clk_inv = 1; + break; + } + + /* set clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + rdai->bit_clk_inv = rdai->bit_clk_inv; + rdai->frm_clk_inv = !rdai->frm_clk_inv; + break; + case SND_SOC_DAIFMT_IB_NF: + rdai->bit_clk_inv = !rdai->bit_clk_inv; + rdai->frm_clk_inv = rdai->frm_clk_inv; + break; + case SND_SOC_DAIFMT_IB_IF: + rdai->bit_clk_inv = !rdai->bit_clk_inv; + rdai->frm_clk_inv = !rdai->frm_clk_inv; + break; + case SND_SOC_DAIFMT_NB_NF: + default: break; } @@ -734,12 +728,13 @@ static void rsnd_of_parse_dai(struct platform_device *pdev, struct device_node *dai_node, *dai_np; struct device_node *ssi_node, *ssi_np; struct device_node *src_node, *src_np; + struct device_node *dvc_node, *dvc_np; struct device_node *playback, *capture; struct rsnd_dai_platform_info *dai_info; struct rcar_snd_info *info = rsnd_priv_to_info(priv); struct device *dev = &pdev->dev; int nr, i; - int dai_i, ssi_i, src_i; + int dai_i, ssi_i, src_i, dvc_i; if (!of_data) return; @@ -765,6 +760,7 @@ static void rsnd_of_parse_dai(struct platform_device *pdev, ssi_node = of_get_child_by_name(dev->of_node, "rcar_sound,ssi"); src_node = of_get_child_by_name(dev->of_node, "rcar_sound,src"); + dvc_node = of_get_child_by_name(dev->of_node, "rcar_sound,dvc"); #define mod_parse(name) \ if (name##_node) { \ @@ -800,6 +796,7 @@ if (name##_node) { \ mod_parse(ssi); mod_parse(src); + mod_parse(dvc); if (playback) of_node_put(playback); @@ -948,19 +945,17 @@ static struct snd_pcm_ops rsnd_pcm_ops = { static int rsnd_pcm_new(struct snd_soc_pcm_runtime *rtd) { - struct rsnd_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); - struct rsnd_dai *rdai; - int i, ret; + struct snd_soc_dai *dai = rtd->cpu_dai; + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + int ret; - for_each_rsnd_dai(rdai, priv, i) { - ret = rsnd_dai_call(pcm_new, &rdai->playback, rdai, rtd); - if (ret) - return ret; + ret = rsnd_dai_call(pcm_new, &rdai->playback, rdai, rtd); + if (ret) + return ret; - ret = rsnd_dai_call(pcm_new, &rdai->capture, rdai, rtd); - if (ret) - return ret; - } + ret = rsnd_dai_call(pcm_new, &rdai->capture, rdai, rtd); + if (ret) + return ret; return snd_pcm_lib_preallocate_pages_for_all( rtd->pcm, @@ -1047,11 +1042,11 @@ static int rsnd_probe(struct platform_device *pdev) for_each_rsnd_dai(rdai, priv, i) { ret = rsnd_dai_call(probe, &rdai->playback, rdai); if (ret) - return ret; + goto exit_snd_probe; ret = rsnd_dai_call(probe, &rdai->capture, rdai); if (ret) - return ret; + goto exit_snd_probe; } /* @@ -1079,6 +1074,11 @@ static int rsnd_probe(struct platform_device *pdev) exit_snd_soc: snd_soc_unregister_platform(dev); +exit_snd_probe: + for_each_rsnd_dai(rdai, priv, i) { + rsnd_dai_call(remove, &rdai->playback, rdai); + rsnd_dai_call(remove, &rdai->capture, rdai); + } return ret; } @@ -1087,21 +1087,16 @@ static int rsnd_remove(struct platform_device *pdev) { struct rsnd_priv *priv = dev_get_drvdata(&pdev->dev); struct rsnd_dai *rdai; - int ret, i; + int ret = 0, i; pm_runtime_disable(&pdev->dev); for_each_rsnd_dai(rdai, priv, i) { - ret = rsnd_dai_call(remove, &rdai->playback, rdai); - if (ret) - return ret; - - ret = rsnd_dai_call(remove, &rdai->capture, rdai); - if (ret) - return ret; + ret |= rsnd_dai_call(remove, &rdai->playback, rdai); + ret |= rsnd_dai_call(remove, &rdai->capture, rdai); } - return 0; + return ret; } static struct platform_driver rsnd_driver = { diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c index ed0007006899..3f443930c2b1 100644 --- a/sound/soc/sh/rcar/dvc.c +++ b/sound/soc/sh/rcar/dvc.c @@ -20,7 +20,8 @@ struct rsnd_dvc { struct rsnd_dvc_platform_info *info; /* rcar_snd.h */ struct rsnd_mod mod; struct clk *clk; - long volume[RSND_DVC_VOLUME_NUM]; + u8 volume[RSND_DVC_VOLUME_NUM]; + u8 mute[RSND_DVC_VOLUME_NUM]; }; #define rsnd_mod_to_dvc(_mod) \ @@ -37,13 +38,18 @@ static void rsnd_dvc_volume_update(struct rsnd_mod *mod) struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); u32 max = (0x00800000 - 1); u32 vol[RSND_DVC_VOLUME_NUM]; + u32 mute = 0; int i; - for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) + for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) { vol[i] = max / RSND_DVC_VOLUME_MAX * dvc->volume[i]; + mute |= (!!dvc->mute[i]) << i; + } rsnd_mod_write(mod, DVC_VOL0R, vol[0]); rsnd_mod_write(mod, DVC_VOL1R, vol[1]); + + rsnd_mod_write(mod, DVC_ZCMCR, mute); } static int rsnd_dvc_probe_gen2(struct rsnd_mod *mod, @@ -96,8 +102,8 @@ static int rsnd_dvc_init(struct rsnd_mod *dvc_mod, rsnd_mod_write(dvc_mod, DVC_ADINR, rsnd_get_adinr(dvc_mod)); - /* enable Volume */ - rsnd_mod_write(dvc_mod, DVC_DVUCR, 0x100); + /* enable Volume / Mute */ + rsnd_mod_write(dvc_mod, DVC_DVUCR, 0x101); /* ch0/ch1 Volume */ rsnd_dvc_volume_update(dvc_mod); @@ -140,10 +146,20 @@ static int rsnd_dvc_stop(struct rsnd_mod *mod, static int rsnd_dvc_volume_info(struct snd_kcontrol *kctrl, struct snd_ctl_elem_info *uinfo) { - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); + struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + u8 *val = (u8 *)kctrl->private_value; + uinfo->count = RSND_DVC_VOLUME_NUM; uinfo->value.integer.min = 0; - uinfo->value.integer.max = RSND_DVC_VOLUME_MAX; + + if (val == dvc->volume) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.max = RSND_DVC_VOLUME_MAX; + } else { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->value.integer.max = 1; + } return 0; } @@ -151,12 +167,11 @@ static int rsnd_dvc_volume_info(struct snd_kcontrol *kctrl, static int rsnd_dvc_volume_get(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *ucontrol) { - struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); - struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + u8 *val = (u8 *)kctrl->private_value; int i; for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) - ucontrol->value.integer.value[i] = dvc->volume[i]; + ucontrol->value.integer.value[i] = val[i]; return 0; } @@ -165,51 +180,38 @@ static int rsnd_dvc_volume_put(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *ucontrol) { struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); - struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + u8 *val = (u8 *)kctrl->private_value; int i, change = 0; for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) { - if (ucontrol->value.integer.value[i] < 0 || - ucontrol->value.integer.value[i] > RSND_DVC_VOLUME_MAX) - return -EINVAL; - - change |= (ucontrol->value.integer.value[i] != dvc->volume[i]); + change |= (ucontrol->value.integer.value[i] != val[i]); + val[i] = ucontrol->value.integer.value[i]; } - if (change) { - for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) - dvc->volume[i] = ucontrol->value.integer.value[i]; - + if (change) rsnd_dvc_volume_update(mod); - } return change; } -static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, - struct rsnd_dai *rdai, - struct snd_soc_pcm_runtime *rtd) +static int __rsnd_dvc_pcm_new(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + u8 *private) { - struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); - struct rsnd_priv *priv = rsnd_mod_to_priv(mod); - struct device *dev = rsnd_priv_to_dev(priv); struct snd_card *card = rtd->card->snd_card; struct snd_kcontrol *kctrl; - static struct snd_kcontrol_new knew = { + struct snd_kcontrol_new knew = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Playback Volume", + .name = name, .info = rsnd_dvc_volume_info, .get = rsnd_dvc_volume_get, .put = rsnd_dvc_volume_put, + .private_value = (unsigned long)private, }; int ret; - if (!rsnd_dai_is_play(rdai, io)) { - dev_err(dev, "DVC%d is connected to Capture DAI\n", - rsnd_mod_id(mod)); - return -EINVAL; - } - kctrl = snd_ctl_new1(&knew, mod); if (!kctrl) return -ENOMEM; @@ -221,6 +223,33 @@ static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, return 0; } +static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, + struct rsnd_dai *rdai, + struct snd_soc_pcm_runtime *rtd) +{ + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + int ret; + + /* Volume */ + ret = __rsnd_dvc_pcm_new(mod, rdai, rtd, + rsnd_dai_is_play(rdai, io) ? + "DVC Out Playback Volume" : "DVC In Capture Volume", + dvc->volume); + if (ret < 0) + return ret; + + /* Mute */ + ret = __rsnd_dvc_pcm_new(mod, rdai, rtd, + rsnd_dai_is_play(rdai, io) ? + "DVC Out Mute Switch" : "DVC In Mute Switch", + dvc->mute); + if (ret < 0) + return ret; + + return 0; +} + static struct rsnd_mod_ops rsnd_dvc_ops = { .name = DVC_NAME, .probe = rsnd_dvc_probe_gen2, @@ -239,6 +268,42 @@ struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id) return &((struct rsnd_dvc *)(priv->dvc) + id)->mod; } +static void rsnd_of_parse_dvc(struct platform_device *pdev, + const struct rsnd_of_data *of_data, + struct rsnd_priv *priv) +{ + struct device_node *node; + struct rsnd_dvc_platform_info *dvc_info; + struct rcar_snd_info *info = rsnd_priv_to_info(priv); + struct device *dev = &pdev->dev; + int nr; + + if (!of_data) + return; + + node = of_get_child_by_name(dev->of_node, "rcar_sound,dvc"); + if (!node) + return; + + nr = of_get_child_count(node); + if (!nr) + goto rsnd_of_parse_dvc_end; + + dvc_info = devm_kzalloc(dev, + sizeof(struct rsnd_dvc_platform_info) * nr, + GFP_KERNEL); + if (!dvc_info) { + dev_err(dev, "dvc info allocation error\n"); + goto rsnd_of_parse_dvc_end; + } + + info->dvc_info = dvc_info; + info->dvc_info_nr = nr; + +rsnd_of_parse_dvc_end: + of_node_put(node); +} + int rsnd_dvc_probe(struct platform_device *pdev, const struct rsnd_of_data *of_data, struct rsnd_priv *priv) @@ -250,6 +315,8 @@ int rsnd_dvc_probe(struct platform_device *pdev, char name[RSND_DVC_NAME_SIZE]; int i, nr; + rsnd_of_parse_dvc(pdev, of_data, priv); + nr = info->dvc_info_nr; if (!nr) return 0; diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index 1dd2b7d38c2c..3fdf3be7b99a 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -15,63 +15,35 @@ struct rsnd_gen { struct rsnd_gen_ops *ops; - struct regmap *regmap; + struct regmap *regmap[RSND_BASE_MAX]; struct regmap_field *regs[RSND_REG_MAX]; }; #define rsnd_priv_to_gen(p) ((struct rsnd_gen *)(p)->gen) -#define RSND_REG_SET(gen, id, reg_id, offset, _id_offset, _id_size) \ - [id] = { \ - .reg = (unsigned int)gen->base[reg_id] + offset, \ - .lsb = 0, \ - .msb = 31, \ - .id_size = _id_size, \ - .id_offset = _id_offset, \ - } - -/* - * basic function - */ -static int rsnd_regmap_write32(void *context, const void *_data, size_t count) -{ - struct rsnd_priv *priv = context; - struct device *dev = rsnd_priv_to_dev(priv); - u32 *data = (u32 *)_data; - u32 val = data[1]; - void __iomem *reg = (void *)data[0]; - - iowrite32(val, reg); - - dev_dbg(dev, "w %p : %08x\n", reg, val); - - return 0; -} - -static int rsnd_regmap_read32(void *context, - const void *_data, size_t reg_size, - void *_val, size_t val_size) -{ - struct rsnd_priv *priv = context; - struct device *dev = rsnd_priv_to_dev(priv); - u32 *data = (u32 *)_data; - u32 *val = (u32 *)_val; - void __iomem *reg = (void *)data[0]; - - *val = ioread32(reg); - - dev_dbg(dev, "r %p : %08x\n", reg, *val); +struct rsnd_regmap_field_conf { + int idx; + unsigned int reg_offset; + unsigned int id_offset; +}; - return 0; +#define RSND_REG_SET(id, offset, _id_offset) \ +{ \ + .idx = id, \ + .reg_offset = offset, \ + .id_offset = _id_offset, \ } +/* single address mapping */ +#define RSND_GEN_S_REG(id, offset) \ + RSND_REG_SET(RSND_REG_##id, offset, 0) -static struct regmap_bus rsnd_regmap_bus = { - .write = rsnd_regmap_write32, - .read = rsnd_regmap_read32, - .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, - .val_format_endian_default = REGMAP_ENDIAN_NATIVE, -}; +/* multi address mapping */ +#define RSND_GEN_M_REG(id, offset, _id_offset) \ + RSND_REG_SET(RSND_REG_##id, offset, _id_offset) +/* + * basic function + */ static int rsnd_is_accessible_reg(struct rsnd_priv *priv, struct rsnd_gen *gen, enum rsnd_reg reg) { @@ -88,6 +60,7 @@ static int rsnd_is_accessible_reg(struct rsnd_priv *priv, u32 rsnd_read(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg) { + struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_gen *gen = rsnd_priv_to_gen(priv); u32 val; @@ -96,6 +69,8 @@ u32 rsnd_read(struct rsnd_priv *priv, regmap_fields_read(gen->regs[reg], rsnd_mod_id(mod), &val); + dev_dbg(dev, "r %s - 0x%04d : %08x\n", rsnd_mod_name(mod), reg, val); + return val; } @@ -103,17 +78,21 @@ void rsnd_write(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg, u32 data) { + struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_gen *gen = rsnd_priv_to_gen(priv); if (!rsnd_is_accessible_reg(priv, gen, reg)) return; regmap_fields_write(gen->regs[reg], rsnd_mod_id(mod), data); + + dev_dbg(dev, "w %s - 0x%04d : %08x\n", rsnd_mod_name(mod), reg, data); } void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg, u32 mask, u32 data) { + struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_gen *gen = rsnd_priv_to_gen(priv); if (!rsnd_is_accessible_reg(priv, gen, reg)) @@ -121,35 +100,63 @@ void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, regmap_fields_update_bits(gen->regs[reg], rsnd_mod_id(mod), mask, data); + + dev_dbg(dev, "b %s - 0x%04d : %08x/%08x\n", + rsnd_mod_name(mod), reg, data, mask); } -static int rsnd_gen_regmap_init(struct rsnd_priv *priv, - struct rsnd_gen *gen, - struct reg_field *regf) +#define rsnd_gen_regmap_init(priv, id_size, reg_id, conf) \ + _rsnd_gen_regmap_init(priv, id_size, reg_id, conf, ARRAY_SIZE(conf)) +static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, + int id_size, + int reg_id, + struct rsnd_regmap_field_conf *conf, + int conf_size) { - int i; + struct platform_device *pdev = rsnd_priv_to_pdev(priv); + struct rsnd_gen *gen = rsnd_priv_to_gen(priv); struct device *dev = rsnd_priv_to_dev(priv); + struct resource *res; struct regmap_config regc; + struct regmap_field *regs; + struct regmap *regmap; + struct reg_field regf; + void __iomem *base; + int i; memset(®c, 0, sizeof(regc)); regc.reg_bits = 32; regc.val_bits = 32; + regc.reg_stride = 4; - gen->regmap = devm_regmap_init(dev, &rsnd_regmap_bus, priv, ®c); - if (IS_ERR(gen->regmap)) { - dev_err(dev, "regmap error %ld\n", PTR_ERR(gen->regmap)); - return PTR_ERR(gen->regmap); - } + res = platform_get_resource(pdev, IORESOURCE_MEM, reg_id); + if (!res) + return -ENODEV; - for (i = 0; i < RSND_REG_MAX; i++) { - gen->regs[i] = NULL; - if (!regf[i].reg) - continue; + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); - gen->regs[i] = devm_regmap_field_alloc(dev, gen->regmap, regf[i]); - if (IS_ERR(gen->regs[i])) - return PTR_ERR(gen->regs[i]); + regmap = devm_regmap_init_mmio(dev, base, ®c); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + gen->base[reg_id] = base; + gen->regmap[reg_id] = regmap; + + for (i = 0; i < conf_size; i++) { + + regf.reg = conf[i].reg_offset; + regf.id_offset = conf[i].id_offset; + regf.lsb = 0; + regf.msb = 31; + regf.id_size = id_size; + + regs = devm_regmap_field_alloc(dev, regmap, regf); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + gen->regs[conf[i].idx] = regs; } return 0; @@ -165,15 +172,19 @@ static int rsnd_gen_regmap_init(struct rsnd_priv *priv, * * ex) R-Car H2 case * mod / DMAC in / DMAC out / DMAC PP in / DMAC pp out - * SSI : 0xec541000 / 0xec241008 / 0xec24100c / 0xec400000 / 0xec400000 + * SSI : 0xec541000 / 0xec241008 / 0xec24100c + * SSIU: 0xec541000 / 0xec100000 / 0xec100000 / 0xec400000 / 0xec400000 * SCU : 0xec500000 / 0xec000000 / 0xec004000 / 0xec300000 / 0xec304000 - * CMD : 0xec500000 / 0xec008000 0xec308000 + * CMD : 0xec500000 / / 0xec008000 0xec308000 */ #define RDMA_SSI_I_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0x8) #define RDMA_SSI_O_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0xc) -#define RDMA_SSI_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) -#define RDMA_SSI_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) +#define RDMA_SSIU_I_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) +#define RDMA_SSIU_O_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) + +#define RDMA_SSIU_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) +#define RDMA_SSIU_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) #define RDMA_SRC_I_N(addr, i) (addr ##_reg - 0x00500000 + (0x400 * i)) #define RDMA_SRC_O_N(addr, i) (addr ##_reg - 0x004fc000 + (0x400 * i)) @@ -184,14 +195,13 @@ static int rsnd_gen_regmap_init(struct rsnd_priv *priv, #define RDMA_CMD_O_N(addr, i) (addr ##_reg - 0x004f8000 + (0x400 * i)) #define RDMA_CMD_O_P(addr, i) (addr ##_reg - 0x001f8000 + (0x400 * i)) -void rsnd_gen_dma_addr(struct rsnd_priv *priv, - struct rsnd_dma *dma, - struct dma_slave_config *cfg, - int is_play, int slave_id) +static dma_addr_t +rsnd_gen2_dma_addr(struct rsnd_priv *priv, + struct rsnd_mod *mod, + int is_play, int is_from) { struct platform_device *pdev = rsnd_priv_to_pdev(priv); struct device *dev = rsnd_priv_to_dev(priv); - struct rsnd_mod *mod = rsnd_dma_to_mod(dma); struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); dma_addr_t ssi_reg = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN2_SSI)->start; @@ -202,170 +212,152 @@ void rsnd_gen_dma_addr(struct rsnd_priv *priv, int use_dvc = !!rsnd_io_to_mod_dvc(io); int id = rsnd_mod_id(mod); struct dma_addr { - dma_addr_t src_addr; - dma_addr_t dst_addr; - } dma_addrs[2][2][3] = { - { /* SRC */ - /* Capture */ - {{ 0, 0 }, - { RDMA_SRC_O_N(src, id), 0 }, - { RDMA_CMD_O_N(src, id), 0 }}, - /* Playback */ - {{ 0, 0, }, - { 0, RDMA_SRC_I_N(src, id) }, - { 0, RDMA_SRC_I_N(src, id) }} - }, { /* SSI */ - /* Capture */ - {{ RDMA_SSI_O_N(ssi, id), 0 }, - { RDMA_SSI_O_P(ssi, id), RDMA_SRC_I_P(src, id) }, - { RDMA_SSI_O_P(ssi, id), RDMA_SRC_I_P(src, id) }}, - /* Playback */ - {{ 0, RDMA_SSI_I_N(ssi, id) }, - { RDMA_SRC_O_P(src, id), RDMA_SSI_I_P(ssi, id) }, - { RDMA_CMD_O_P(src, id), RDMA_SSI_I_P(ssi, id) }} - } + dma_addr_t out_addr; + dma_addr_t in_addr; + } dma_addrs[3][2][3] = { + /* SRC */ + {{{ 0, 0 }, + /* Capture */ + { RDMA_SRC_O_N(src, id), RDMA_SRC_I_P(src, id) }, + { RDMA_CMD_O_N(src, id), RDMA_SRC_I_P(src, id) } }, + /* Playback */ + {{ 0, 0, }, + { RDMA_SRC_O_P(src, id), RDMA_SRC_I_N(src, id) }, + { RDMA_CMD_O_P(src, id), RDMA_SRC_I_N(src, id) } } + }, + /* SSI */ + /* Capture */ + {{{ RDMA_SSI_O_N(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 } }, + /* Playback */ + {{ 0, RDMA_SSI_I_N(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) } } + }, + /* SSIU */ + /* Capture */ + {{{ RDMA_SSIU_O_N(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 } }, + /* Playback */ + {{ 0, RDMA_SSIU_I_N(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) } } }, }; - cfg->slave_id = slave_id; - cfg->src_addr = 0; - cfg->dst_addr = 0; - cfg->direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + /* it shouldn't happen */ + if (use_dvc & !use_src) + dev_err(dev, "DVC is selected without SRC\n"); + + /* use SSIU or SSI ? */ + if (is_ssi && (0 == strcmp(rsnd_mod_dma_name(mod), "ssiu"))) + is_ssi++; + + return (is_from) ? + dma_addrs[is_ssi][is_play][use_src + use_dvc].out_addr : + dma_addrs[is_ssi][is_play][use_src + use_dvc].in_addr; +} +dma_addr_t rsnd_gen_dma_addr(struct rsnd_priv *priv, + struct rsnd_mod *mod, + int is_play, int is_from) +{ /* * gen1 uses default DMA addr */ if (rsnd_is_gen1(priv)) - return; - - /* it shouldn't happen */ - if (use_dvc & !use_src) { - dev_err(dev, "DVC is selected without SRC\n"); - return; - } + return 0; - cfg->src_addr = dma_addrs[is_ssi][is_play][use_src + use_dvc].src_addr; - cfg->dst_addr = dma_addrs[is_ssi][is_play][use_src + use_dvc].dst_addr; + if (!mod) + return 0; - dev_dbg(dev, "dma%d addr - src : %x / dst : %x\n", - id, cfg->src_addr, cfg->dst_addr); + return rsnd_gen2_dma_addr(priv, mod, is_play, is_from); } /* * Gen2 */ - -/* single address mapping */ -#define RSND_GEN2_S_REG(gen, reg, id, offset) \ - RSND_REG_SET(gen, RSND_REG_##id, RSND_GEN2_##reg, offset, 0, 10) - -/* multi address mapping */ -#define RSND_GEN2_M_REG(gen, reg, id, offset, _id_offset) \ - RSND_REG_SET(gen, RSND_REG_##id, RSND_GEN2_##reg, offset, _id_offset, 10) - -static int rsnd_gen2_regmap_init(struct rsnd_priv *priv, struct rsnd_gen *gen) -{ - struct reg_field regf[RSND_REG_MAX] = { - RSND_GEN2_S_REG(gen, SSIU, SSI_MODE0, 0x800), - RSND_GEN2_S_REG(gen, SSIU, SSI_MODE1, 0x804), - /* FIXME: it needs SSI_MODE2/3 in the future */ - RSND_GEN2_M_REG(gen, SSIU, SSI_BUSIF_MODE, 0x0, 0x80), - RSND_GEN2_M_REG(gen, SSIU, SSI_BUSIF_ADINR,0x4, 0x80), - RSND_GEN2_M_REG(gen, SSIU, SSI_CTRL, 0x10, 0x80), - RSND_GEN2_M_REG(gen, SSIU, INT_ENABLE, 0x18, 0x80), - - RSND_GEN2_M_REG(gen, SCU, SRC_BUSIF_MODE, 0x0, 0x20), - RSND_GEN2_M_REG(gen, SCU, SRC_ROUTE_MODE0,0xc, 0x20), - RSND_GEN2_M_REG(gen, SCU, SRC_CTRL, 0x10, 0x20), - RSND_GEN2_M_REG(gen, SCU, CMD_ROUTE_SLCT, 0x18c, 0x20), - RSND_GEN2_M_REG(gen, SCU, CMD_CTRL, 0x190, 0x20), - RSND_GEN2_M_REG(gen, SCU, SRC_SWRSR, 0x200, 0x40), - RSND_GEN2_M_REG(gen, SCU, SRC_SRCIR, 0x204, 0x40), - RSND_GEN2_M_REG(gen, SCU, SRC_ADINR, 0x214, 0x40), - RSND_GEN2_M_REG(gen, SCU, SRC_IFSCR, 0x21c, 0x40), - RSND_GEN2_M_REG(gen, SCU, SRC_IFSVR, 0x220, 0x40), - RSND_GEN2_M_REG(gen, SCU, SRC_SRCCR, 0x224, 0x40), - RSND_GEN2_M_REG(gen, SCU, SRC_BSDSR, 0x22c, 0x40), - RSND_GEN2_M_REG(gen, SCU, SRC_BSISR, 0x238, 0x40), - RSND_GEN2_M_REG(gen, SCU, DVC_SWRSR, 0xe00, 0x100), - RSND_GEN2_M_REG(gen, SCU, DVC_DVUIR, 0xe04, 0x100), - RSND_GEN2_M_REG(gen, SCU, DVC_ADINR, 0xe08, 0x100), - RSND_GEN2_M_REG(gen, SCU, DVC_DVUCR, 0xe10, 0x100), - RSND_GEN2_M_REG(gen, SCU, DVC_ZCMCR, 0xe14, 0x100), - RSND_GEN2_M_REG(gen, SCU, DVC_VOL0R, 0xe28, 0x100), - RSND_GEN2_M_REG(gen, SCU, DVC_VOL1R, 0xe2c, 0x100), - RSND_GEN2_M_REG(gen, SCU, DVC_DVUER, 0xe48, 0x100), - - RSND_GEN2_S_REG(gen, ADG, BRRA, 0x00), - RSND_GEN2_S_REG(gen, ADG, BRRB, 0x04), - RSND_GEN2_S_REG(gen, ADG, SSICKR, 0x08), - RSND_GEN2_S_REG(gen, ADG, AUDIO_CLK_SEL0, 0x0c), - RSND_GEN2_S_REG(gen, ADG, AUDIO_CLK_SEL1, 0x10), - RSND_GEN2_S_REG(gen, ADG, AUDIO_CLK_SEL2, 0x14), - RSND_GEN2_S_REG(gen, ADG, DIV_EN, 0x30), - RSND_GEN2_S_REG(gen, ADG, SRCIN_TIMSEL0, 0x34), - RSND_GEN2_S_REG(gen, ADG, SRCIN_TIMSEL1, 0x38), - RSND_GEN2_S_REG(gen, ADG, SRCIN_TIMSEL2, 0x3c), - RSND_GEN2_S_REG(gen, ADG, SRCIN_TIMSEL3, 0x40), - RSND_GEN2_S_REG(gen, ADG, SRCIN_TIMSEL4, 0x44), - RSND_GEN2_S_REG(gen, ADG, SRCOUT_TIMSEL0, 0x48), - RSND_GEN2_S_REG(gen, ADG, SRCOUT_TIMSEL1, 0x4c), - RSND_GEN2_S_REG(gen, ADG, SRCOUT_TIMSEL2, 0x50), - RSND_GEN2_S_REG(gen, ADG, SRCOUT_TIMSEL3, 0x54), - RSND_GEN2_S_REG(gen, ADG, SRCOUT_TIMSEL4, 0x58), - RSND_GEN2_S_REG(gen, ADG, CMDOUT_TIMSEL, 0x5c), - - RSND_GEN2_M_REG(gen, SSI, SSICR, 0x00, 0x40), - RSND_GEN2_M_REG(gen, SSI, SSISR, 0x04, 0x40), - RSND_GEN2_M_REG(gen, SSI, SSITDR, 0x08, 0x40), - RSND_GEN2_M_REG(gen, SSI, SSIRDR, 0x0c, 0x40), - RSND_GEN2_M_REG(gen, SSI, SSIWSR, 0x20, 0x40), - }; - - return rsnd_gen_regmap_init(priv, gen, regf); -} - static int rsnd_gen2_probe(struct platform_device *pdev, struct rsnd_priv *priv) { struct device *dev = rsnd_priv_to_dev(priv); - struct rsnd_gen *gen = rsnd_priv_to_gen(priv); - struct resource *scu_res; - struct resource *adg_res; - struct resource *ssiu_res; - struct resource *ssi_res; - int ret; - - /* - * map address - */ - scu_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN2_SCU); - adg_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN2_ADG); - ssiu_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN2_SSIU); - ssi_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN2_SSI); - - gen->base[RSND_GEN2_SCU] = devm_ioremap_resource(dev, scu_res); - gen->base[RSND_GEN2_ADG] = devm_ioremap_resource(dev, adg_res); - gen->base[RSND_GEN2_SSIU] = devm_ioremap_resource(dev, ssiu_res); - gen->base[RSND_GEN2_SSI] = devm_ioremap_resource(dev, ssi_res); - if (IS_ERR(gen->base[RSND_GEN2_SCU]) || - IS_ERR(gen->base[RSND_GEN2_ADG]) || - IS_ERR(gen->base[RSND_GEN2_SSIU]) || - IS_ERR(gen->base[RSND_GEN2_SSI])) - return -ENODEV; - - ret = rsnd_gen2_regmap_init(priv, gen); - if (ret < 0) - return ret; - - dev_dbg(dev, "Gen2 device probed\n"); - dev_dbg(dev, "SCU : %pap => %p\n", &scu_res->start, - gen->base[RSND_GEN2_SCU]); - dev_dbg(dev, "ADG : %pap => %p\n", &adg_res->start, - gen->base[RSND_GEN2_ADG]); - dev_dbg(dev, "SSIU : %pap => %p\n", &ssiu_res->start, - gen->base[RSND_GEN2_SSIU]); - dev_dbg(dev, "SSI : %pap => %p\n", &ssi_res->start, - gen->base[RSND_GEN2_SSI]); + struct rsnd_regmap_field_conf conf_ssiu[] = { + RSND_GEN_S_REG(SSI_MODE0, 0x800), + RSND_GEN_S_REG(SSI_MODE1, 0x804), + /* FIXME: it needs SSI_MODE2/3 in the future */ + RSND_GEN_M_REG(SSI_BUSIF_MODE, 0x0, 0x80), + RSND_GEN_M_REG(SSI_BUSIF_ADINR, 0x4, 0x80), + RSND_GEN_M_REG(BUSIF_DALIGN, 0x8, 0x80), + RSND_GEN_M_REG(SSI_CTRL, 0x10, 0x80), + RSND_GEN_M_REG(INT_ENABLE, 0x18, 0x80), + }; + struct rsnd_regmap_field_conf conf_scu[] = { + RSND_GEN_M_REG(SRC_BUSIF_MODE, 0x0, 0x20), + RSND_GEN_M_REG(SRC_ROUTE_MODE0, 0xc, 0x20), + RSND_GEN_M_REG(SRC_CTRL, 0x10, 0x20), + RSND_GEN_M_REG(CMD_ROUTE_SLCT, 0x18c, 0x20), + RSND_GEN_M_REG(CMD_CTRL, 0x190, 0x20), + RSND_GEN_M_REG(SRC_SWRSR, 0x200, 0x40), + RSND_GEN_M_REG(SRC_SRCIR, 0x204, 0x40), + RSND_GEN_M_REG(SRC_ADINR, 0x214, 0x40), + RSND_GEN_M_REG(SRC_IFSCR, 0x21c, 0x40), + RSND_GEN_M_REG(SRC_IFSVR, 0x220, 0x40), + RSND_GEN_M_REG(SRC_SRCCR, 0x224, 0x40), + RSND_GEN_M_REG(SRC_BSDSR, 0x22c, 0x40), + RSND_GEN_M_REG(SRC_BSISR, 0x238, 0x40), + RSND_GEN_M_REG(DVC_SWRSR, 0xe00, 0x100), + RSND_GEN_M_REG(DVC_DVUIR, 0xe04, 0x100), + RSND_GEN_M_REG(DVC_ADINR, 0xe08, 0x100), + RSND_GEN_M_REG(DVC_DVUCR, 0xe10, 0x100), + RSND_GEN_M_REG(DVC_ZCMCR, 0xe14, 0x100), + RSND_GEN_M_REG(DVC_VOL0R, 0xe28, 0x100), + RSND_GEN_M_REG(DVC_VOL1R, 0xe2c, 0x100), + RSND_GEN_M_REG(DVC_DVUER, 0xe48, 0x100), + }; + 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), + RSND_GEN_S_REG(AUDIO_CLK_SEL2, 0x14), + RSND_GEN_S_REG(DIV_EN, 0x30), + RSND_GEN_S_REG(SRCIN_TIMSEL0, 0x34), + RSND_GEN_S_REG(SRCIN_TIMSEL1, 0x38), + RSND_GEN_S_REG(SRCIN_TIMSEL2, 0x3c), + RSND_GEN_S_REG(SRCIN_TIMSEL3, 0x40), + RSND_GEN_S_REG(SRCIN_TIMSEL4, 0x44), + RSND_GEN_S_REG(SRCOUT_TIMSEL0, 0x48), + RSND_GEN_S_REG(SRCOUT_TIMSEL1, 0x4c), + RSND_GEN_S_REG(SRCOUT_TIMSEL2, 0x50), + RSND_GEN_S_REG(SRCOUT_TIMSEL3, 0x54), + RSND_GEN_S_REG(SRCOUT_TIMSEL4, 0x58), + RSND_GEN_S_REG(CMDOUT_TIMSEL, 0x5c), + }; + 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), + RSND_GEN_M_REG(SSIRDR, 0x0c, 0x40), + RSND_GEN_M_REG(SSIWSR, 0x20, 0x40), + }; + int ret_ssiu; + int ret_scu; + int ret_adg; + int ret_ssi; + + ret_ssiu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSIU, conf_ssiu); + ret_scu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SCU, conf_scu); + ret_adg = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_ADG, conf_adg); + ret_ssi = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSI, conf_ssi); + if (ret_ssiu < 0 || + ret_scu < 0 || + ret_adg < 0 || + ret_ssi < 0) + return ret_ssiu | ret_scu | ret_adg | ret_ssi; + + dev_dbg(dev, "Gen2 is probed\n"); return 0; } @@ -374,92 +366,60 @@ static int rsnd_gen2_probe(struct platform_device *pdev, * Gen1 */ -/* single address mapping */ -#define RSND_GEN1_S_REG(gen, reg, id, offset) \ - RSND_REG_SET(gen, RSND_REG_##id, RSND_GEN1_##reg, offset, 0, 9) - -/* multi address mapping */ -#define RSND_GEN1_M_REG(gen, reg, id, offset, _id_offset) \ - RSND_REG_SET(gen, RSND_REG_##id, RSND_GEN1_##reg, offset, _id_offset, 9) - -static int rsnd_gen1_regmap_init(struct rsnd_priv *priv, struct rsnd_gen *gen) -{ - struct reg_field regf[RSND_REG_MAX] = { - RSND_GEN1_S_REG(gen, SRU, SRC_ROUTE_SEL, 0x00), - RSND_GEN1_S_REG(gen, SRU, SRC_TMG_SEL0, 0x08), - RSND_GEN1_S_REG(gen, SRU, SRC_TMG_SEL1, 0x0c), - RSND_GEN1_S_REG(gen, SRU, SRC_TMG_SEL2, 0x10), - RSND_GEN1_S_REG(gen, SRU, SRC_ROUTE_CTRL, 0xc0), - RSND_GEN1_S_REG(gen, SRU, SSI_MODE0, 0xD0), - RSND_GEN1_S_REG(gen, SRU, SSI_MODE1, 0xD4), - RSND_GEN1_M_REG(gen, SRU, SRC_BUSIF_MODE, 0x20, 0x4), - RSND_GEN1_M_REG(gen, SRU, SRC_ROUTE_MODE0,0x50, 0x8), - RSND_GEN1_M_REG(gen, SRU, SRC_SWRSR, 0x200, 0x40), - RSND_GEN1_M_REG(gen, SRU, SRC_SRCIR, 0x204, 0x40), - RSND_GEN1_M_REG(gen, SRU, SRC_ADINR, 0x214, 0x40), - RSND_GEN1_M_REG(gen, SRU, SRC_IFSCR, 0x21c, 0x40), - RSND_GEN1_M_REG(gen, SRU, SRC_IFSVR, 0x220, 0x40), - RSND_GEN1_M_REG(gen, SRU, SRC_SRCCR, 0x224, 0x40), - RSND_GEN1_M_REG(gen, SRU, SRC_MNFSR, 0x228, 0x40), - - RSND_GEN1_S_REG(gen, ADG, BRRA, 0x00), - RSND_GEN1_S_REG(gen, ADG, BRRB, 0x04), - RSND_GEN1_S_REG(gen, ADG, SSICKR, 0x08), - RSND_GEN1_S_REG(gen, ADG, AUDIO_CLK_SEL0, 0x0c), - RSND_GEN1_S_REG(gen, ADG, AUDIO_CLK_SEL1, 0x10), - RSND_GEN1_S_REG(gen, ADG, AUDIO_CLK_SEL3, 0x18), - RSND_GEN1_S_REG(gen, ADG, AUDIO_CLK_SEL4, 0x1c), - RSND_GEN1_S_REG(gen, ADG, AUDIO_CLK_SEL5, 0x20), - - RSND_GEN1_M_REG(gen, SSI, SSICR, 0x00, 0x40), - RSND_GEN1_M_REG(gen, SSI, SSISR, 0x04, 0x40), - RSND_GEN1_M_REG(gen, SSI, SSITDR, 0x08, 0x40), - RSND_GEN1_M_REG(gen, SSI, SSIRDR, 0x0c, 0x40), - RSND_GEN1_M_REG(gen, SSI, SSIWSR, 0x20, 0x40), - }; - - return rsnd_gen_regmap_init(priv, gen, regf); -} - static int rsnd_gen1_probe(struct platform_device *pdev, struct rsnd_priv *priv) { struct device *dev = rsnd_priv_to_dev(priv); - struct rsnd_gen *gen = rsnd_priv_to_gen(priv); - struct resource *sru_res; - struct resource *adg_res; - struct resource *ssi_res; - int ret; - - /* - * map address - */ - sru_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_SRU); - adg_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_ADG); - ssi_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_SSI); - - gen->base[RSND_GEN1_SRU] = devm_ioremap_resource(dev, sru_res); - gen->base[RSND_GEN1_ADG] = devm_ioremap_resource(dev, adg_res); - gen->base[RSND_GEN1_SSI] = devm_ioremap_resource(dev, ssi_res); - if (IS_ERR(gen->base[RSND_GEN1_SRU]) || - IS_ERR(gen->base[RSND_GEN1_ADG]) || - IS_ERR(gen->base[RSND_GEN1_SSI])) - return -ENODEV; + struct rsnd_regmap_field_conf conf_sru[] = { + RSND_GEN_S_REG(SRC_ROUTE_SEL, 0x00), + RSND_GEN_S_REG(SRC_TMG_SEL0, 0x08), + RSND_GEN_S_REG(SRC_TMG_SEL1, 0x0c), + RSND_GEN_S_REG(SRC_TMG_SEL2, 0x10), + RSND_GEN_S_REG(SRC_ROUTE_CTRL, 0xc0), + RSND_GEN_S_REG(SSI_MODE0, 0xD0), + RSND_GEN_S_REG(SSI_MODE1, 0xD4), + RSND_GEN_M_REG(SRC_BUSIF_MODE, 0x20, 0x4), + RSND_GEN_M_REG(SRC_ROUTE_MODE0, 0x50, 0x8), + RSND_GEN_M_REG(SRC_SWRSR, 0x200, 0x40), + RSND_GEN_M_REG(SRC_SRCIR, 0x204, 0x40), + RSND_GEN_M_REG(SRC_ADINR, 0x214, 0x40), + RSND_GEN_M_REG(SRC_IFSCR, 0x21c, 0x40), + RSND_GEN_M_REG(SRC_IFSVR, 0x220, 0x40), + RSND_GEN_M_REG(SRC_SRCCR, 0x224, 0x40), + RSND_GEN_M_REG(SRC_MNFSR, 0x228, 0x40), + }; + 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), + RSND_GEN_S_REG(AUDIO_CLK_SEL3, 0x18), + RSND_GEN_S_REG(AUDIO_CLK_SEL4, 0x1c), + RSND_GEN_S_REG(AUDIO_CLK_SEL5, 0x20), + }; + 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), + RSND_GEN_M_REG(SSIRDR, 0x0c, 0x40), + RSND_GEN_M_REG(SSIWSR, 0x20, 0x40), + }; + int ret_sru; + int ret_adg; + int ret_ssi; - ret = rsnd_gen1_regmap_init(priv, gen); - if (ret < 0) - return ret; + ret_sru = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SRU, conf_sru); + ret_adg = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_ADG, conf_adg); + ret_ssi = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SSI, conf_ssi); + if (ret_sru < 0 || + ret_adg < 0 || + ret_ssi < 0) + return ret_sru | ret_adg | ret_ssi; - dev_dbg(dev, "Gen1 device probed\n"); - dev_dbg(dev, "SRU : %pap => %p\n", &sru_res->start, - gen->base[RSND_GEN1_SRU]); - dev_dbg(dev, "ADG : %pap => %p\n", &adg_res->start, - gen->base[RSND_GEN1_ADG]); - dev_dbg(dev, "SSI : %pap => %p\n", &ssi_res->start, - gen->base[RSND_GEN1_SSI]); + dev_dbg(dev, "Gen1 is probed\n"); return 0; - } /* diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h index 39d98af5ee05..d119adf97c9c 100644 --- a/sound/soc/sh/rcar/rsnd.h +++ b/sound/soc/sh/rcar/rsnd.h @@ -90,6 +90,7 @@ enum rsnd_reg { RSND_REG_SHARE19, RSND_REG_SHARE20, RSND_REG_SHARE21, + RSND_REG_SHARE22, RSND_REG_MAX, }; @@ -127,6 +128,7 @@ enum rsnd_reg { #define RSND_REG_AUDIO_CLK_SEL2 RSND_REG_SHARE19 #define RSND_REG_CMD_CTRL RSND_REG_SHARE20 #define RSND_REG_CMDOUT_TIMSEL RSND_REG_SHARE21 +#define RSND_REG_BUSIF_DALIGN RSND_REG_SHARE22 struct rsnd_of_data; struct rsnd_priv; @@ -156,12 +158,9 @@ u32 rsnd_get_adinr(struct rsnd_mod *mod); */ struct rsnd_dma { struct sh_dmae_slave slave; - struct work_struct work; struct dma_chan *chan; - enum dma_data_direction dir; - - int submit_loop; - int offset; /* it cares A/B plane */ + enum dma_transfer_direction dir; + dma_addr_t addr; }; void rsnd_dma_start(struct rsnd_dma *dma); @@ -185,6 +184,7 @@ enum rsnd_mod_type { struct rsnd_mod_ops { char *name; + char* (*dma_name)(struct rsnd_mod *mod); int (*probe)(struct rsnd_mod *mod, struct rsnd_dai *rdai); int (*remove)(struct rsnd_mod *mod, @@ -224,6 +224,7 @@ void rsnd_mod_init(struct rsnd_priv *priv, enum rsnd_mod_type type, int id); char *rsnd_mod_name(struct rsnd_mod *mod); +char *rsnd_mod_dma_name(struct rsnd_mod *mod); /* * R-Car sound DAI @@ -281,10 +282,9 @@ int rsnd_gen_probe(struct platform_device *pdev, void __iomem *rsnd_gen_reg_get(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg); -void rsnd_gen_dma_addr(struct rsnd_priv *priv, - struct rsnd_dma *dma, - struct dma_slave_config *cfg, - int is_play, int slave_id); +dma_addr_t rsnd_gen_dma_addr(struct rsnd_priv *priv, + struct rsnd_mod *mod, + int is_play, int is_from); #define rsnd_is_gen1(s) (((s)->info->flags & RSND_GEN_MASK) == RSND_GEN1) #define rsnd_is_gen2(s) (((s)->info->flags & RSND_GEN_MASK) == RSND_GEN2) @@ -391,8 +391,12 @@ struct rsnd_mod *rsnd_src_mod_get(struct rsnd_priv *priv, int id); unsigned int rsnd_src_get_ssi_rate(struct rsnd_priv *priv, struct rsnd_dai_stream *io, struct snd_pcm_runtime *runtime); -int rsnd_src_ssi_mode_init(struct rsnd_mod *ssi_mod, - struct rsnd_dai *rdai); +int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, + struct rsnd_dai *rdai, + int use_busif); +int rsnd_src_ssiu_stop(struct rsnd_mod *ssi_mod, + struct rsnd_dai *rdai, + int use_busif); int rsnd_src_enable_ssi_irq(struct rsnd_mod *ssi_mod, struct rsnd_dai *rdai); diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c index 200eda019bc7..9183e0145503 100644 --- a/sound/soc/sh/rcar/src.c +++ b/sound/soc/sh/rcar/src.c @@ -106,18 +106,19 @@ struct rsnd_src { /* * Gen1/Gen2 common functions */ -int rsnd_src_ssi_mode_init(struct rsnd_mod *ssi_mod, - struct rsnd_dai *rdai) +int rsnd_src_ssiu_start(struct rsnd_mod *ssi_mod, + struct rsnd_dai *rdai, + int use_busif) { struct rsnd_dai_stream *io = rsnd_mod_to_io(ssi_mod); - struct rsnd_mod *src_mod = rsnd_io_to_mod_src(io); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); int ssi_id = rsnd_mod_id(ssi_mod); /* * SSI_MODE0 */ rsnd_mod_bset(ssi_mod, SSI_MODE0, (1 << ssi_id), - src_mod ? 0 : (1 << ssi_id)); + !use_busif << ssi_id); /* * SSI_MODE1 @@ -143,6 +144,46 @@ int rsnd_src_ssi_mode_init(struct rsnd_mod *ssi_mod, 0x2 << shift : 0x1 << shift); } + /* + * DMA settings for SSIU + */ + if (use_busif) { + u32 val = 0x76543210; + u32 mask = ~0; + + rsnd_mod_write(ssi_mod, SSI_BUSIF_ADINR, + rsnd_get_adinr(ssi_mod)); + rsnd_mod_write(ssi_mod, SSI_BUSIF_MODE, 1); + rsnd_mod_write(ssi_mod, SSI_CTRL, 0x1); + + mask <<= runtime->channels * 4; + val = val & mask; + + switch (runtime->sample_bits) { + case 16: + val |= 0x67452301 & ~mask; + break; + case 32: + val |= 0x76543210 & ~mask; + break; + } + rsnd_mod_write(ssi_mod, BUSIF_DALIGN, val); + + } + + return 0; +} + +int rsnd_src_ssiu_stop(struct rsnd_mod *ssi_mod, + struct rsnd_dai *rdai, + int use_busif) +{ + /* + * DMA settings for SSIU + */ + if (use_busif) + rsnd_mod_write(ssi_mod, SSI_CTRL, 0); + return 0; } @@ -461,18 +502,45 @@ static struct rsnd_mod_ops rsnd_src_gen1_ops = { static int rsnd_src_set_convert_rate_gen2(struct rsnd_mod *mod, struct rsnd_dai *rdai) { + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_src *src = rsnd_mod_to_src(mod); + uint ratio; int ret; + /* 6 - 1/6 are very enough ratio for SRC_BSDSR */ + if (!rsnd_src_convert_rate(src)) + ratio = 0; + else if (rsnd_src_convert_rate(src) > runtime->rate) + ratio = 100 * rsnd_src_convert_rate(src) / runtime->rate; + else + ratio = 100 * runtime->rate / rsnd_src_convert_rate(src); + + if (ratio > 600) { + dev_err(dev, "FSO/FSI ratio error\n"); + return -EINVAL; + } + ret = rsnd_src_set_convert_rate(mod, rdai); if (ret < 0) return ret; - rsnd_mod_write(mod, SSI_BUSIF_ADINR, rsnd_get_adinr(mod)); - rsnd_mod_write(mod, SSI_BUSIF_MODE, 1); - rsnd_mod_write(mod, SRC_SRCCR, 0x00011110); - rsnd_mod_write(mod, SRC_BSDSR, 0x01800000); + switch (rsnd_mod_id(mod)) { + case 5: + case 6: + case 7: + case 8: + rsnd_mod_write(mod, SRC_BSDSR, 0x02400000); + break; + default: + rsnd_mod_write(mod, SRC_BSDSR, 0x01800000); + break; + } + rsnd_mod_write(mod, SRC_BSISR, 0x00100060); return 0; @@ -554,7 +622,6 @@ static int rsnd_src_start_gen2(struct rsnd_mod *mod, rsnd_dma_start(rsnd_mod_to_dma(&src->mod)); - rsnd_mod_write(mod, SSI_CTRL, 0x1); rsnd_mod_write(mod, SRC_CTRL, val); return rsnd_src_start(mod, rdai); @@ -565,7 +632,6 @@ static int rsnd_src_stop_gen2(struct rsnd_mod *mod, { struct rsnd_src *src = rsnd_mod_to_src(mod); - rsnd_mod_write(mod, SSI_CTRL, 0); rsnd_mod_write(mod, SRC_CTRL, 0); rsnd_dma_stop(rsnd_mod_to_dma(&src->mod)); diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 2df723df5d19..34e84009162b 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -90,6 +90,20 @@ struct rsnd_ssi { #define rsnd_ssi_mode_flags(p) ((p)->info->flags) #define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id) +static int rsnd_ssi_use_busif(struct rsnd_mod *mod) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); + int use_busif = 0; + + if (!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_NO_BUSIF)) + use_busif = 1; + if (rsnd_io_to_mod_src(io)) + use_busif = 1; + + return use_busif; +} + static void rsnd_ssi_status_check(struct rsnd_mod *mod, u32 bit) { @@ -289,8 +303,6 @@ static int rsnd_ssi_init(struct rsnd_mod *mod, ssi->cr_own = cr; ssi->err = -1; /* ignore 1st error */ - rsnd_src_ssi_mode_init(mod, rdai); - return 0; } @@ -389,6 +401,8 @@ static int rsnd_ssi_pio_start(struct rsnd_mod *mod, /* enable PIO IRQ */ ssi->cr_etc = UIEN | OIEN | DIEN; + rsnd_src_ssiu_start(mod, rdai, 0); + rsnd_src_enable_ssi_irq(mod, rdai); rsnd_ssi_hw_start(ssi, rdai, io); @@ -405,6 +419,8 @@ static int rsnd_ssi_pio_stop(struct rsnd_mod *mod, rsnd_ssi_hw_stop(ssi, rdai); + rsnd_src_ssiu_stop(mod, rdai, 0); + return 0; } @@ -457,6 +473,8 @@ static int rsnd_ssi_dma_start(struct rsnd_mod *mod, /* enable DMA transfer */ ssi->cr_etc = DMEN; + rsnd_src_ssiu_start(mod, rdai, rsnd_ssi_use_busif(mod)); + rsnd_dma_start(dma); rsnd_ssi_hw_start(ssi, ssi->rdai, io); @@ -482,11 +500,19 @@ static int rsnd_ssi_dma_stop(struct rsnd_mod *mod, rsnd_dma_stop(dma); + rsnd_src_ssiu_stop(mod, rdai, 1); + return 0; } +static char *rsnd_ssi_dma_name(struct rsnd_mod *mod) +{ + return rsnd_ssi_use_busif(mod) ? "ssiu" : SSI_NAME; +} + static struct rsnd_mod_ops rsnd_ssi_dma_ops = { .name = SSI_NAME, + .dma_name = rsnd_ssi_dma_name, .probe = rsnd_ssi_dma_probe, .remove = rsnd_ssi_dma_remove, .init = rsnd_ssi_init, @@ -595,6 +621,9 @@ static void rsnd_of_parse_ssi(struct platform_device *pdev, */ ssi_info->dma_id = of_get_property(np, "pio-transfer", NULL) ? 0 : 1; + + if (of_get_property(np, "no-busif", NULL)) + ssi_info->flags |= RSND_SSI_NO_BUSIF; } rsnd_of_parse_ssi_end: diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 00e70b6c7da2..a9f82b5aba9d 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -78,7 +78,7 @@ int snd_soc_cache_init(struct snd_soc_codec *codec) mutex_init(&codec->cache_rw_mutex); dev_dbg(codec->dev, "ASoC: Initializing cache for %s codec\n", - codec->name); + codec->component.name); if (codec_drv->reg_cache_default) codec->reg_cache = kmemdup(codec_drv->reg_cache_default, @@ -98,8 +98,7 @@ int snd_soc_cache_init(struct snd_soc_codec *codec) int snd_soc_cache_exit(struct snd_soc_codec *codec) { dev_dbg(codec->dev, "ASoC: Destroying cache for %s codec\n", - codec->name); - + codec->component.name); kfree(codec->reg_cache); codec->reg_cache = NULL; return 0; @@ -192,7 +191,7 @@ int snd_soc_cache_sync(struct snd_soc_codec *codec) return 0; dev_dbg(codec->dev, "ASoC: Syncing cache for %s codec\n", - codec->name); + codec->component.name); trace_snd_soc_cache_sync(codec, name, "start"); ret = snd_soc_flat_cache_sync(codec); if (!ret) diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 10f7f1da2aca..27c06acce205 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -37,7 +37,8 @@ static int soc_compr_open(struct snd_compr_stream *cstream) if (platform->driver->compr_ops && platform->driver->compr_ops->open) { ret = platform->driver->compr_ops->open(cstream); if (ret < 0) { - pr_err("compress asoc: can't open platform %s\n", platform->name); + pr_err("compress asoc: can't open platform %s\n", + platform->component.name); goto out; } } @@ -84,7 +85,8 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) if (platform->driver->compr_ops && platform->driver->compr_ops->open) { ret = platform->driver->compr_ops->open(cstream); if (ret < 0) { - pr_err("compress asoc: can't open platform %s\n", platform->name); + pr_err("compress asoc: can't open platform %s\n", + platform->component.name); goto out; } } @@ -627,6 +629,11 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) char new_name[64]; int ret = 0, direction = 0; + if (rtd->num_codecs > 1) { + dev_err(rtd->card->dev, "Multicodec not supported for compressed stream\n"); + return -EINVAL; + } + /* check client and interface hw capabilities */ snprintf(new_name, sizeof(new_name), "%s %s-%d", rtd->dai_link->stream_name, codec_dai->name, num); @@ -680,7 +687,7 @@ int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) ret = snd_compress_new(rtd->card->snd_card, num, direction, compr); if (ret < 0) { pr_err("compress asoc: can't create compress for codec %s\n", - codec->name); + codec->component.name); goto compr_err; } diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index b87d7d882e6d..2d6c8b86b7d3 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -270,12 +270,33 @@ static const struct file_operations codec_reg_fops = { .llseek = default_llseek, }; +static struct dentry *soc_debugfs_create_dir(struct dentry *parent, + const char *fmt, ...) +{ + struct dentry *de; + va_list ap; + char *s; + + va_start(ap, fmt); + s = kvasprintf(GFP_KERNEL, fmt, ap); + va_end(ap); + + if (!s) + return NULL; + + de = debugfs_create_dir(s, parent); + kfree(s); + + return de; +} + static void soc_init_codec_debugfs(struct snd_soc_codec *codec) { - struct dentry *debugfs_card_root = codec->card->debugfs_card_root; + struct dentry *debugfs_card_root = codec->component.card->debugfs_card_root; - codec->debugfs_codec_root = debugfs_create_dir(codec->name, - debugfs_card_root); + codec->debugfs_codec_root = soc_debugfs_create_dir(debugfs_card_root, + "codec:%s", + codec->component.name); if (!codec->debugfs_codec_root) { dev_warn(codec->dev, "ASoC: Failed to create codec debugfs directory\n"); @@ -304,17 +325,18 @@ static void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec) static void soc_init_platform_debugfs(struct snd_soc_platform *platform) { - struct dentry *debugfs_card_root = platform->card->debugfs_card_root; + struct dentry *debugfs_card_root = platform->component.card->debugfs_card_root; - platform->debugfs_platform_root = debugfs_create_dir(platform->name, - debugfs_card_root); + platform->debugfs_platform_root = soc_debugfs_create_dir(debugfs_card_root, + "platform:%s", + platform->component.name); if (!platform->debugfs_platform_root) { dev_warn(platform->dev, "ASoC: Failed to create platform debugfs directory\n"); return; } - snd_soc_dapm_debugfs_init(&platform->dapm, + snd_soc_dapm_debugfs_init(&platform->component.dapm, platform->debugfs_platform_root); } @@ -335,7 +357,7 @@ static ssize_t codec_list_read_file(struct file *file, char __user *user_buf, list_for_each_entry(codec, &codec_list, list) { len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n", - codec->name); + codec->component.name); if (len >= 0) ret += len; if (ret > PAGE_SIZE) { @@ -406,7 +428,7 @@ static ssize_t platform_list_read_file(struct file *file, list_for_each_entry(platform, &platform_list, list) { len = snprintf(buf + ret, PAGE_SIZE - ret, "%s\n", - platform->name); + platform->component.name); if (len >= 0) ret += len; if (ret > PAGE_SIZE) { @@ -524,11 +546,12 @@ static int soc_ac97_dev_register(struct snd_soc_codec *codec) int err; codec->ac97->dev.bus = &ac97_bus_type; - codec->ac97->dev.parent = codec->card->dev; + codec->ac97->dev.parent = codec->component.card->dev; codec->ac97->dev.release = soc_ac97_device_release; dev_set_name(&codec->ac97->dev, "%d-%d:%s", - codec->card->snd_card->number, 0, codec->name); + codec->component.card->snd_card->number, 0, + codec->component.name); err = device_register(&codec->ac97->dev); if (err < 0) { dev_err(codec->dev, "ASoC: Can't register ac97 bus\n"); @@ -554,7 +577,7 @@ int snd_soc_suspend(struct device *dev) { struct snd_soc_card *card = dev_get_drvdata(dev); struct snd_soc_codec *codec; - int i; + int i, j; /* If the initialization of this soc device failed, there is no codec * associated with it. Just bail out in this case. @@ -574,14 +597,17 @@ int snd_soc_suspend(struct device *dev) /* mute any active DACs */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *dai = card->rtd[i].codec_dai; - struct snd_soc_dai_driver *drv = dai->driver; if (card->rtd[i].dai_link->ignore_suspend) continue; - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 1); + for (j = 0; j < card->rtd[i].num_codecs; j++) { + struct snd_soc_dai *dai = card->rtd[i].codec_dais[j]; + struct snd_soc_dai_driver *drv = dai->driver; + + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 1); + } } /* suspend all pcms */ @@ -612,8 +638,12 @@ int snd_soc_suspend(struct device *dev) /* close any waiting streams and save state */ for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dai **codec_dais = card->rtd[i].codec_dais; flush_delayed_work(&card->rtd[i].delayed_work); - card->rtd[i].codec->dapm.suspend_bias_level = card->rtd[i].codec->dapm.bias_level; + for (j = 0; j < card->rtd[i].num_codecs; j++) { + codec_dais[j]->codec->dapm.suspend_bias_level = + codec_dais[j]->codec->dapm.bias_level; + } } for (i = 0; i < card->num_rtd; i++) { @@ -697,7 +727,7 @@ static void soc_resume_deferred(struct work_struct *work) struct snd_soc_card *card = container_of(work, struct snd_soc_card, deferred_resume_work); struct snd_soc_codec *codec; - int i; + int i, j; /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time, * so userspace apps are blocked from touching us @@ -758,14 +788,17 @@ static void soc_resume_deferred(struct work_struct *work) /* unmute any active DACs */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *dai = card->rtd[i].codec_dai; - struct snd_soc_dai_driver *drv = dai->driver; if (card->rtd[i].dai_link->ignore_suspend) continue; - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 0); + for (j = 0; j < card->rtd[i].num_codecs; j++) { + struct snd_soc_dai *dai = card->rtd[i].codec_dais[j]; + struct snd_soc_dai_driver *drv = dai->driver; + + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 0); + } } for (i = 0; i < card->num_rtd; i++) { @@ -810,12 +843,19 @@ int snd_soc_resume(struct device *dev) /* activate pins from sleep state */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + struct snd_soc_dai **codec_dais = rtd->codec_dais; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + if (cpu_dai->active) pinctrl_pm_select_default_state(cpu_dai->dev); - if (codec_dai->active) - pinctrl_pm_select_default_state(codec_dai->dev); + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = codec_dais[j]; + if (codec_dai->active) + pinctrl_pm_select_default_state(codec_dai->dev); + } } /* AC97 devices might have other drivers hanging off them so @@ -847,8 +887,9 @@ EXPORT_SYMBOL_GPL(snd_soc_resume); static const struct snd_soc_dai_ops null_dai_ops = { }; -static struct snd_soc_codec *soc_find_codec(const struct device_node *codec_of_node, - const char *codec_name) +static struct snd_soc_codec *soc_find_codec( + const struct device_node *codec_of_node, + const char *codec_name) { struct snd_soc_codec *codec; @@ -857,7 +898,7 @@ static struct snd_soc_codec *soc_find_codec(const struct device_node *codec_of_n if (codec->dev->of_node != codec_of_node) continue; } else { - if (strcmp(codec->name, codec_name)) + if (strcmp(codec->component.name, codec_name)) continue; } @@ -886,9 +927,12 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_component *component; + struct snd_soc_dai_link_component *codecs = dai_link->codecs; + struct snd_soc_dai **codec_dais = rtd->codec_dais; struct snd_soc_platform *platform; struct snd_soc_dai *cpu_dai; const char *platform_name; + int i; dev_dbg(card->dev, "ASoC: binding %s at idx %d\n", dai_link->name, num); @@ -915,24 +959,30 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) return -EPROBE_DEFER; } - /* Find CODEC from registered list */ - rtd->codec = soc_find_codec(dai_link->codec_of_node, - dai_link->codec_name); - if (!rtd->codec) { - dev_err(card->dev, "ASoC: CODEC %s not registered\n", - dai_link->codec_name); - return -EPROBE_DEFER; - } + rtd->num_codecs = dai_link->num_codecs; - /* Find CODEC DAI from registered list */ - rtd->codec_dai = soc_find_codec_dai(rtd->codec, - dai_link->codec_dai_name); - if (!rtd->codec_dai) { - dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n", - dai_link->codec_dai_name); - return -EPROBE_DEFER; + /* Find CODEC from registered CODECs */ + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_codec *codec; + codec = soc_find_codec(codecs[i].of_node, codecs[i].name); + if (!codec) { + dev_err(card->dev, "ASoC: CODEC %s not registered\n", + codecs[i].name); + return -EPROBE_DEFER; + } + + codec_dais[i] = soc_find_codec_dai(codec, codecs[i].dai_name); + if (!codec_dais[i]) { + dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n", + codecs[i].dai_name); + return -EPROBE_DEFER; + } } + /* Single codec links expect codec and codec_dai in runtime data */ + rtd->codec_dai = codec_dais[0]; + rtd->codec = rtd->codec_dai->codec; + /* if there's no platform we match on the empty platform */ platform_name = dai_link->platform_name; if (!platform_name && !dai_link->platform_of_node) @@ -945,7 +995,7 @@ static int soc_bind_dai_link(struct snd_soc_card *card, int num) dai_link->platform_of_node) continue; } else { - if (strcmp(platform->name, platform_name)) + if (strcmp(platform->component.name, platform_name)) continue; } @@ -974,11 +1024,10 @@ static int soc_remove_platform(struct snd_soc_platform *platform) } /* Make sure all DAPM widgets are freed */ - snd_soc_dapm_free(&platform->dapm); + snd_soc_dapm_free(&platform->component.dapm); soc_cleanup_platform_debugfs(platform); platform->probed = 0; - list_del(&platform->card_list); module_put(platform->dev->driver->owner); return 0; @@ -1023,8 +1072,8 @@ static void soc_remove_codec_dai(struct snd_soc_dai *codec_dai, int order) static void soc_remove_link_dais(struct snd_soc_card *card, int num, int order) { struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; - struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai; - int err; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i, err; /* unregister the rtd device */ if (rtd->dev_registered) { @@ -1035,7 +1084,8 @@ static void soc_remove_link_dais(struct snd_soc_card *card, int num, int order) } /* remove the CODEC DAI */ - soc_remove_codec_dai(codec_dai, order); + for (i = 0; i < rtd->num_codecs; i++) + soc_remove_codec_dai(rtd->codec_dais[i], order); /* remove the cpu_dai */ if (cpu_dai && cpu_dai->probed && @@ -1048,11 +1098,8 @@ static void soc_remove_link_dais(struct snd_soc_card *card, int num, int order) cpu_dai->name, err); } cpu_dai->probed = 0; - - if (!cpu_dai->codec) { - snd_soc_dapm_free(&cpu_dai->dapm); + if (!cpu_dai->codec) module_put(cpu_dai->dev->driver->owner); - } } } @@ -1061,9 +1108,9 @@ static void soc_remove_link_components(struct snd_soc_card *card, int num, { struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_codec *codec; + int i; /* remove the platform */ if (platform && platform->probed && @@ -1072,8 +1119,8 @@ static void soc_remove_link_components(struct snd_soc_card *card, int num, } /* remove the CODEC-side CODEC */ - if (codec_dai) { - codec = codec_dai->codec; + for (i = 0; i < rtd->num_codecs; i++) { + codec = rtd->codec_dais[i]->codec; if (codec && codec->probed && codec->driver->remove_order == order) soc_remove_codec(codec); @@ -1108,7 +1155,7 @@ static void soc_remove_dai_links(struct snd_soc_card *card) } static void soc_set_name_prefix(struct snd_soc_card *card, - struct snd_soc_codec *codec) + struct snd_soc_component *component) { int i; @@ -1117,11 +1164,11 @@ static void soc_set_name_prefix(struct snd_soc_card *card, for (i = 0; i < card->num_configs; i++) { struct snd_soc_codec_conf *map = &card->codec_conf[i]; - if (map->of_node && codec->dev->of_node != map->of_node) + if (map->of_node && component->dev->of_node != map->of_node) continue; - if (map->dev_name && strcmp(codec->name, map->dev_name)) + if (map->dev_name && strcmp(component->name, map->dev_name)) continue; - codec->name_prefix = map->name_prefix; + component->name_prefix = map->name_prefix; break; } } @@ -1133,9 +1180,9 @@ static int soc_probe_codec(struct snd_soc_card *card, const struct snd_soc_codec_driver *driver = codec->driver; struct snd_soc_dai *dai; - codec->card = card; + codec->component.card = card; codec->dapm.card = card; - soc_set_name_prefix(card, codec); + soc_set_name_prefix(card, &codec->component); if (!try_module_get(codec->dev->driver->owner)) return -ENODEV; @@ -1177,7 +1224,7 @@ static int soc_probe_codec(struct snd_soc_card *card, WARN(codec->dapm.idle_bias_off && codec->dapm.bias_level != SND_SOC_BIAS_OFF, "codec %s can not start from non-off bias with idle_bias_off==1\n", - codec->name); + codec->component.name); } if (driver->controls) @@ -1209,8 +1256,8 @@ static int soc_probe_platform(struct snd_soc_card *card, struct snd_soc_component *component; struct snd_soc_dai *dai; - platform->card = card; - platform->dapm.card = card; + platform->component.card = card; + platform->component.dapm.card = card; if (!try_module_get(platform->dev->driver->owner)) return -ENODEV; @@ -1218,7 +1265,7 @@ static int soc_probe_platform(struct snd_soc_card *card, soc_init_platform_debugfs(platform); if (driver->dapm_widgets) - snd_soc_dapm_new_controls(&platform->dapm, + snd_soc_dapm_new_controls(&platform->component.dapm, driver->dapm_widgets, driver->num_dapm_widgets); /* Create DAPM widgets for each DAI stream */ @@ -1226,10 +1273,11 @@ static int soc_probe_platform(struct snd_soc_card *card, if (component->dev != platform->dev) continue; list_for_each_entry(dai, &component->dai_list, list) - snd_soc_dapm_new_dai_widgets(&platform->dapm, dai); + snd_soc_dapm_new_dai_widgets(&platform->component.dapm, + dai); } - platform->dapm.idle_bias_off = 1; + platform->component.dapm.idle_bias_off = 1; if (driver->probe) { ret = driver->probe(platform); @@ -1244,13 +1292,12 @@ static int soc_probe_platform(struct snd_soc_card *card, snd_soc_add_platform_controls(platform, driver->controls, driver->num_controls); if (driver->dapm_routes) - snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes, - driver->num_dapm_routes); + snd_soc_dapm_add_routes(&platform->component.dapm, + driver->dapm_routes, driver->num_dapm_routes); /* mark platform as probed and add to card platform list */ platform->probed = 1; - list_add(&platform->card_list, &card->platform_dev_list); - list_add(&platform->dapm.list, &card->dapm_list); + list_add(&platform->component.dapm.list, &card->dapm_list); return 0; @@ -1266,83 +1313,17 @@ static void rtd_release(struct device *dev) kfree(dev); } -static int soc_aux_dev_init(struct snd_soc_card *card, - struct snd_soc_codec *codec, - int num) -{ - struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; - struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num]; - int ret; - - rtd->card = card; - - /* do machine specific initialization */ - if (aux_dev->init) { - ret = aux_dev->init(&codec->dapm); - if (ret < 0) - return ret; - } - - rtd->codec = codec; - - return 0; -} - -static int soc_dai_link_init(struct snd_soc_card *card, - struct snd_soc_codec *codec, - int num) -{ - struct snd_soc_dai_link *dai_link = &card->dai_link[num]; - struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; - int ret; - - rtd->card = card; - - /* do machine specific initialization */ - if (dai_link->init) { - ret = dai_link->init(rtd); - if (ret < 0) - return ret; - } - - rtd->codec = codec; - - return 0; -} - -static int soc_post_component_init(struct snd_soc_card *card, - struct snd_soc_codec *codec, - int num, int dailess) +static int soc_post_component_init(struct snd_soc_pcm_runtime *rtd, + const char *name) { - struct snd_soc_dai_link *dai_link = NULL; - struct snd_soc_aux_dev *aux_dev = NULL; - struct snd_soc_pcm_runtime *rtd; - const char *name; int ret = 0; - if (!dailess) { - dai_link = &card->dai_link[num]; - rtd = &card->rtd[num]; - name = dai_link->name; - ret = soc_dai_link_init(card, codec, num); - } else { - aux_dev = &card->aux_dev[num]; - rtd = &card->rtd_aux[num]; - name = aux_dev->name; - ret = soc_aux_dev_init(card, codec, num); - } - - if (ret < 0) { - dev_err(card->dev, "ASoC: failed to init %s: %d\n", name, ret); - return ret; - } - /* register the rtd device */ rtd->dev = kzalloc(sizeof(struct device), GFP_KERNEL); if (!rtd->dev) return -ENOMEM; device_initialize(rtd->dev); - rtd->dev->parent = card->dev; + rtd->dev->parent = rtd->card->dev; rtd->dev->release = rtd_release; rtd->dev->init_name = name; dev_set_drvdata(rtd->dev, rtd); @@ -1355,7 +1336,7 @@ static int soc_post_component_init(struct snd_soc_card *card, if (ret < 0) { /* calling put_device() here to free the rtd->dev */ put_device(rtd->dev); - dev_err(card->dev, + dev_err(rtd->card->dev, "ASoC: failed to register runtime device: %d\n", ret); return ret; } @@ -1364,26 +1345,15 @@ static int soc_post_component_init(struct snd_soc_card *card, /* add DAPM sysfs entries for this codec */ ret = snd_soc_dapm_sys_add(rtd->dev); if (ret < 0) - dev_err(codec->dev, + dev_err(rtd->dev, "ASoC: failed to add codec dapm sysfs entries: %d\n", ret); /* add codec sysfs entries */ ret = device_create_file(rtd->dev, &dev_attr_codec_reg); if (ret < 0) - dev_err(codec->dev, + dev_err(rtd->dev, "ASoC: failed to add codec sysfs files: %d\n", ret); -#ifdef CONFIG_DEBUG_FS - /* add DPCM sysfs entries */ - if (!dailess && !dai_link->dynamic) - goto out; - - ret = soc_dpcm_debugfs_add(rtd); - if (ret < 0) - dev_err(rtd->dev, "ASoC: failed to add dpcm sysfs entries: %d\n", ret); - -out: -#endif return 0; } @@ -1392,9 +1362,8 @@ static int soc_probe_link_components(struct snd_soc_card *card, int num, { struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_platform *platform = rtd->platform; - int ret; + int i, ret; /* probe the CPU-side component, if it is a CODEC */ if (cpu_dai->codec && @@ -1405,12 +1374,14 @@ static int soc_probe_link_components(struct snd_soc_card *card, int num, return ret; } - /* probe the CODEC-side component */ - if (!codec_dai->codec->probed && - codec_dai->codec->driver->probe_order == order) { - ret = soc_probe_codec(card, codec_dai->codec); - if (ret < 0) - return ret; + /* probe the CODEC-side components */ + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->codec->probed && + rtd->codec_dais[i]->codec->driver->probe_order == order) { + ret = soc_probe_codec(card, rtd->codec_dais[i]->codec); + if (ret < 0) + return ret; + } } /* probe the platform */ @@ -1450,12 +1421,16 @@ static int soc_probe_codec_dai(struct snd_soc_card *card, static int soc_link_dai_widgets(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link, - struct snd_soc_dai *cpu_dai, - struct snd_soc_dai *codec_dai) + struct snd_soc_pcm_runtime *rtd) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dapm_widget *play_w, *capture_w; int ret; + if (rtd->num_codecs > 1) + dev_warn(card->dev, "ASoC: Multiple codecs not supported yet\n"); + /* link the DAI widgets */ play_w = codec_dai->playback_widget; capture_w = cpu_dai->capture_widget; @@ -1488,19 +1463,18 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) { struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; - struct snd_soc_codec *codec = rtd->codec; struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - int ret; + int i, ret; dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n", card->name, num, order); /* config components */ cpu_dai->platform = platform; - codec_dai->card = card; cpu_dai->card = card; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->card = card; /* set default power off timeout */ rtd->pmdown_time = pmdown_time; @@ -1509,11 +1483,8 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) if (!cpu_dai->probed && cpu_dai->driver->probe_order == order) { if (!cpu_dai->codec) { - cpu_dai->dapm.card = card; if (!try_module_get(cpu_dai->dev->driver->owner)) return -ENODEV; - - list_add(&cpu_dai->dapm.list, &card->dapm_list); } if (cpu_dai->driver->probe) { @@ -1530,18 +1501,43 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) } /* probe the CODEC DAI */ - ret = soc_probe_codec_dai(card, codec_dai, order); - if (ret) - return ret; + for (i = 0; i < rtd->num_codecs; i++) { + ret = soc_probe_codec_dai(card, rtd->codec_dais[i], order); + if (ret) + return ret; + } /* complete DAI probe during last probe */ if (order != SND_SOC_COMP_ORDER_LAST) return 0; - ret = soc_post_component_init(card, codec, num, 0); + /* do machine specific initialization */ + if (dai_link->init) { + ret = dai_link->init(rtd); + if (ret < 0) { + dev_err(card->dev, "ASoC: failed to init %s: %d\n", + dai_link->name, ret); + return ret; + } + } + + ret = soc_post_component_init(rtd, dai_link->name); if (ret) return ret; +#ifdef CONFIG_DEBUG_FS + /* add DPCM sysfs entries */ + if (dai_link->dynamic) { + ret = soc_dpcm_debugfs_add(rtd); + if (ret < 0) { + dev_err(rtd->dev, + "ASoC: failed to add dpcm sysfs entries: %d\n", + ret); + return ret; + } + } +#endif + ret = device_create_file(rtd->dev, &dev_attr_pmdown_time); if (ret < 0) dev_warn(rtd->dev, "ASoC: failed to add pmdown_time sysfs: %d\n", @@ -1570,16 +1566,18 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order) codec2codec_close_delayed_work); /* link the DAI widgets */ - ret = soc_link_dai_widgets(card, dai_link, - cpu_dai, codec_dai); + ret = soc_link_dai_widgets(card, dai_link, rtd); if (ret) return ret; } } /* add platform data for AC97 devices */ - if (rtd->codec_dai->driver->ac97_control) - snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata); + for (i = 0; i < rtd->num_codecs; i++) { + if (rtd->codec_dais[i]->driver->ac97_control) + snd_ac97_dev_add_pdata(rtd->codec_dais[i]->codec->ac97, + rtd->cpu_dai->ac97_pdata); + } return 0; } @@ -1617,11 +1615,6 @@ static int soc_register_ac97_codec(struct snd_soc_codec *codec, return 0; } -static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) -{ - return soc_register_ac97_codec(rtd->codec, rtd->codec_dai); -} - static void soc_unregister_ac97_codec(struct snd_soc_codec *codec) { if (codec->ac97_registered) { @@ -1630,74 +1623,77 @@ static void soc_unregister_ac97_codec(struct snd_soc_codec *codec) } } -static void soc_unregister_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) +static int soc_register_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) { - soc_unregister_ac97_codec(rtd->codec); -} -#endif + int i, ret; -static struct snd_soc_codec *soc_find_matching_codec(struct snd_soc_card *card, - int num) -{ - struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; - struct snd_soc_codec *codec; + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; - /* find CODEC from registered CODECs */ - list_for_each_entry(codec, &codec_list, list) { - if (aux_dev->codec_of_node && - (codec->dev->of_node != aux_dev->codec_of_node)) - continue; - if (aux_dev->codec_name && strcmp(codec->name, aux_dev->codec_name)) - continue; - return codec; + ret = soc_register_ac97_codec(codec_dai->codec, codec_dai); + if (ret) { + while (--i >= 0) + soc_unregister_ac97_codec(codec_dai->codec); + return ret; + } } - return NULL; + return 0; } -static int soc_check_aux_dev(struct snd_soc_card *card, int num) +static void soc_unregister_ac97_dai_link(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; - const char *codecname = aux_dev->codec_name; - struct snd_soc_codec *codec = soc_find_matching_codec(card, num); - - if (codec) - return 0; - if (aux_dev->codec_of_node) - codecname = of_node_full_name(aux_dev->codec_of_node); + int i; - dev_err(card->dev, "ASoC: %s not registered\n", codecname); - return -EPROBE_DEFER; + for (i = 0; i < rtd->num_codecs; i++) + soc_unregister_ac97_codec(rtd->codec_dais[i]->codec); } +#endif -static int soc_probe_aux_dev(struct snd_soc_card *card, int num) +static int soc_bind_aux_dev(struct snd_soc_card *card, int num) { + struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num]; struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; const char *codecname = aux_dev->codec_name; - int ret = -ENODEV; - struct snd_soc_codec *codec = soc_find_matching_codec(card, num); - if (!codec) { + rtd->codec = soc_find_codec(aux_dev->codec_of_node, codecname); + if (!rtd->codec) { if (aux_dev->codec_of_node) codecname = of_node_full_name(aux_dev->codec_of_node); - /* codec not found */ - dev_err(card->dev, "ASoC: codec %s not found", codecname); + dev_err(card->dev, "ASoC: %s not registered\n", codecname); return -EPROBE_DEFER; } - if (codec->probed) { - dev_err(codec->dev, "ASoC: codec already probed"); + return 0; +} + +static int soc_probe_aux_dev(struct snd_soc_card *card, int num) +{ + struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num]; + struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; + int ret; + + if (rtd->codec->probed) { + dev_err(rtd->codec->dev, "ASoC: codec already probed\n"); return -EBUSY; } - ret = soc_probe_codec(card, codec); + ret = soc_probe_codec(card, rtd->codec); if (ret < 0) return ret; - ret = soc_post_component_init(card, codec, num, 1); + /* do machine specific initialization */ + if (aux_dev->init) { + ret = aux_dev->init(&rtd->codec->dapm); + if (ret < 0) { + dev_err(card->dev, "ASoC: failed to init %s: %d\n", + aux_dev->name, ret); + return ret; + } + } - return ret; + return soc_post_component_init(rtd, aux_dev->name); } static void soc_remove_aux_dev(struct snd_soc_card *card, int num) @@ -1749,9 +1745,9 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) goto base_error; } - /* check aux_devs too */ + /* bind aux_devs too */ for (i = 0; i < card->num_aux_devs; i++) { - ret = soc_check_aux_dev(card, i); + ret = soc_bind_aux_dev(card, i); if (ret != 0) goto base_error; } @@ -1849,16 +1845,23 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) card->num_dapm_routes); for (i = 0; i < card->num_links; i++) { + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; dai_link = &card->dai_link[i]; dai_fmt = dai_link->dai_fmt; if (dai_fmt) { - ret = snd_soc_dai_set_fmt(card->rtd[i].codec_dai, - dai_fmt); - if (ret != 0 && ret != -ENOTSUPP) - dev_warn(card->rtd[i].codec_dai->dev, - "ASoC: Failed to set DAI format: %d\n", - ret); + struct snd_soc_dai **codec_dais = rtd->codec_dais; + int j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = codec_dais[j]; + + ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); + if (ret != 0 && ret != -ENOTSUPP) + dev_warn(codec_dai->dev, + "ASoC: Failed to set DAI format: %d\n", + ret); + } } /* If this is a regular CPU link there will be a platform */ @@ -1927,8 +1930,7 @@ static int snd_soc_instantiate_card(struct snd_soc_card *card) } if (card->fully_routed) - list_for_each_entry(codec, &card->codec_dev_list, card_list) - snd_soc_dapm_auto_nc_codec_pins(codec); + snd_soc_dapm_auto_nc_pins(card); snd_soc_dapm_new_widgets(card); @@ -2058,10 +2060,15 @@ int snd_soc_poweroff(struct device *dev) /* deactivate pins to sleep state */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; - pinctrl_pm_select_sleep_state(codec_dai->dev); + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + pinctrl_pm_select_sleep_state(cpu_dai->dev); + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + pinctrl_pm_select_sleep_state(codec_dai->dev); + } } return 0; @@ -2387,6 +2394,25 @@ struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, EXPORT_SYMBOL_GPL(snd_soc_card_get_kcontrol); /** + * snd_soc_add_component_controls - Add an array of controls to a component. + * + * @component: Component to add controls to + * @controls: Array of controls to add + * @num_controls: Number of elements in the array + * + * Return: 0 for success, else error. + */ +int snd_soc_add_component_controls(struct snd_soc_component *component, + const struct snd_kcontrol_new *controls, unsigned int num_controls) +{ + struct snd_card *card = component->card->snd_card; + + return snd_soc_add_controls(card, component->dev, controls, + num_controls, component->name_prefix, component); +} +EXPORT_SYMBOL_GPL(snd_soc_add_component_controls); + +/** * snd_soc_add_codec_controls - add an array of controls to a codec. * Convenience function to add a list of controls. Many codecs were * duplicating this code. @@ -2398,12 +2424,10 @@ EXPORT_SYMBOL_GPL(snd_soc_card_get_kcontrol); * Return 0 for success, else error. */ int snd_soc_add_codec_controls(struct snd_soc_codec *codec, - const struct snd_kcontrol_new *controls, int num_controls) + const struct snd_kcontrol_new *controls, unsigned int num_controls) { - struct snd_card *card = codec->card->snd_card; - - return snd_soc_add_controls(card, codec->dev, controls, num_controls, - codec->name_prefix, &codec->component); + return snd_soc_add_component_controls(&codec->component, controls, + num_controls); } EXPORT_SYMBOL_GPL(snd_soc_add_codec_controls); @@ -2418,12 +2442,10 @@ EXPORT_SYMBOL_GPL(snd_soc_add_codec_controls); * Return 0 for success, else error. */ int snd_soc_add_platform_controls(struct snd_soc_platform *platform, - const struct snd_kcontrol_new *controls, int num_controls) + const struct snd_kcontrol_new *controls, unsigned int num_controls) { - struct snd_card *card = platform->card->snd_card; - - return snd_soc_add_controls(card, platform->dev, controls, num_controls, - NULL, &platform->component); + return snd_soc_add_component_controls(&platform->component, controls, + num_controls); } EXPORT_SYMBOL_GPL(snd_soc_add_platform_controls); @@ -3095,7 +3117,7 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range); int snd_soc_limit_volume(struct snd_soc_codec *codec, const char *name, int max) { - struct snd_card *card = codec->card->snd_card; + struct snd_card *card = codec->component.card->snd_card; struct snd_kcontrol *kctl; struct soc_mixer_control *mc; int found = 0; @@ -3641,6 +3663,9 @@ int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, else snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); + dai->tx_mask = tx_mask; + dai->rx_mask = rx_mask; + if (dai->driver && dai->driver->ops->set_tdm_slot) return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, slots, slot_width); @@ -3713,6 +3738,33 @@ int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, } EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); +static int snd_soc_init_multicodec(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) +{ + /* Legacy codec/codec_dai link is a single entry in multicodec */ + if (dai_link->codec_name || dai_link->codec_of_node || + dai_link->codec_dai_name) { + dai_link->num_codecs = 1; + + dai_link->codecs = devm_kzalloc(card->dev, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!dai_link->codecs) + return -ENOMEM; + + dai_link->codecs[0].name = dai_link->codec_name; + dai_link->codecs[0].of_node = dai_link->codec_of_node; + dai_link->codecs[0].dai_name = dai_link->codec_dai_name; + } + + if (!dai_link->codecs) { + dev_err(card->dev, "ASoC: DAI link has no CODECs\n"); + return -EINVAL; + } + + return 0; +} + /** * snd_soc_register_card - Register a card with the ASoC core * @@ -3721,7 +3773,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); */ int snd_soc_register_card(struct snd_soc_card *card) { - int i, ret; + int i, j, ret; if (!card->name || !card->dev) return -EINVAL; @@ -3729,22 +3781,29 @@ int snd_soc_register_card(struct snd_soc_card *card) for (i = 0; i < card->num_links; i++) { struct snd_soc_dai_link *link = &card->dai_link[i]; - /* - * Codec must be specified by 1 of name or OF node, - * not both or neither. - */ - if (!!link->codec_name == !!link->codec_of_node) { - dev_err(card->dev, - "ASoC: Neither/both codec name/of_node are set for %s\n", - link->name); - return -EINVAL; + ret = snd_soc_init_multicodec(card, link); + if (ret) { + dev_err(card->dev, "ASoC: failed to init multicodec\n"); + return ret; } - /* Codec DAI name must be specified */ - if (!link->codec_dai_name) { - dev_err(card->dev, - "ASoC: codec_dai_name not set for %s\n", - link->name); - return -EINVAL; + + for (j = 0; j < link->num_codecs; j++) { + /* + * Codec must be specified by 1 of name or OF node, + * not both or neither. + */ + if (!!link->codecs[j].name == + !!link->codecs[j].of_node) { + dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n", + link->name); + return -EINVAL; + } + /* Codec DAI name must be specified */ + if (!link->codecs[j].dai_name) { + dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n", + link->name); + return -EINVAL; + } } /* @@ -3797,8 +3856,19 @@ int snd_soc_register_card(struct snd_soc_card *card) card->num_rtd = 0; card->rtd_aux = &card->rtd[card->num_links]; - for (i = 0; i < card->num_links; i++) + for (i = 0; i < card->num_links; i++) { + card->rtd[i].card = card; card->rtd[i].dai_link = &card->dai_link[i]; + card->rtd[i].codec_dais = devm_kzalloc(card->dev, + sizeof(struct snd_soc_dai *) * + (card->rtd[i].dai_link->num_codecs), + GFP_KERNEL); + if (card->rtd[i].codec_dais == NULL) + return -ENOMEM; + } + + for (i = 0; i < card->num_aux_devs; i++) + card->rtd_aux[i].card = card; INIT_LIST_HEAD(&card->dapm_dirty); card->instantiated = 0; @@ -3811,10 +3881,16 @@ int snd_soc_register_card(struct snd_soc_card *card) /* deactivate pins to sleep state */ for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + if (!codec_dai->active) + pinctrl_pm_select_sleep_state(codec_dai->dev); + } + if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev); } @@ -3921,16 +3997,14 @@ static void snd_soc_unregister_dais(struct snd_soc_component *component) * snd_soc_register_dais - Register a DAI with the ASoC core * * @component: The component the DAIs are registered for - * @codec: The CODEC that the DAIs are registered for, NULL if the component is - * not a CODEC. * @dai_drv: DAI driver to use for the DAIs * @count: Number of DAIs * @legacy_dai_naming: Use the legacy naming scheme and let the DAI inherit the * parent's name. */ static int snd_soc_register_dais(struct snd_soc_component *component, - struct snd_soc_codec *codec, struct snd_soc_dai_driver *dai_drv, - size_t count, bool legacy_dai_naming) + struct snd_soc_dai_driver *dai_drv, size_t count, + bool legacy_dai_naming) { struct device *dev = component->dev; struct snd_soc_dai *dai; @@ -3939,6 +4013,9 @@ static int snd_soc_register_dais(struct snd_soc_component *component, dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count); + component->dai_drv = dai_drv; + component->num_dai = count; + for (i = 0; i < count; i++) { dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); @@ -3971,16 +4048,11 @@ static int snd_soc_register_dais(struct snd_soc_component *component, } dai->component = component; - dai->codec = codec; dai->dev = dev; dai->driver = &dai_drv[i]; - dai->dapm.dev = dev; if (!dai->driver->ops) dai->driver->ops = &null_dai_ops; - if (!dai->codec) - dai->dapm.idle_bias_off = 1; - list_add(&dai->list, &component->dai_list); dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); @@ -3994,60 +4066,82 @@ err: return ret; } -/** - * snd_soc_register_component - Register a component with the ASoC core - * - */ -static int -__snd_soc_register_component(struct device *dev, - struct snd_soc_component *cmpnt, - const struct snd_soc_component_driver *cmpnt_drv, - struct snd_soc_codec *codec, - struct snd_soc_dai_driver *dai_drv, - int num_dai, bool allow_single_dai) +static void snd_soc_component_seq_notifier(struct snd_soc_dapm_context *dapm, + enum snd_soc_dapm_type type, int subseq) { - int ret; + struct snd_soc_component *component = dapm->component; - dev_dbg(dev, "component register %s\n", dev_name(dev)); + component->driver->seq_notifier(component, type, subseq); +} - if (!cmpnt) { - dev_err(dev, "ASoC: Failed to connecting component\n"); - return -ENOMEM; - } +static int snd_soc_component_stream_event(struct snd_soc_dapm_context *dapm, + int event) +{ + struct snd_soc_component *component = dapm->component; - mutex_init(&cmpnt->io_mutex); + return component->driver->stream_event(component, event); +} + +static int snd_soc_component_initialize(struct snd_soc_component *component, + const struct snd_soc_component_driver *driver, struct device *dev) +{ + struct snd_soc_dapm_context *dapm; - cmpnt->name = fmt_single_name(dev, &cmpnt->id); - if (!cmpnt->name) { - dev_err(dev, "ASoC: Failed to simplifying name\n"); + component->name = fmt_single_name(dev, &component->id); + if (!component->name) { + dev_err(dev, "ASoC: Failed to allocate name\n"); return -ENOMEM; } - cmpnt->dev = dev; - cmpnt->driver = cmpnt_drv; - cmpnt->dai_drv = dai_drv; - cmpnt->num_dai = num_dai; - INIT_LIST_HEAD(&cmpnt->dai_list); + component->dev = dev; + component->driver = driver; - ret = snd_soc_register_dais(cmpnt, codec, dai_drv, num_dai, - allow_single_dai); - if (ret < 0) { - dev_err(dev, "ASoC: Failed to regster DAIs: %d\n", ret); - goto error_component_name; - } + if (!component->dapm_ptr) + component->dapm_ptr = &component->dapm; + + dapm = component->dapm_ptr; + dapm->dev = dev; + dapm->component = component; + dapm->bias_level = SND_SOC_BIAS_OFF; + if (driver->seq_notifier) + dapm->seq_notifier = snd_soc_component_seq_notifier; + if (driver->stream_event) + dapm->stream_event = snd_soc_component_stream_event; + + INIT_LIST_HEAD(&component->dai_list); + mutex_init(&component->io_mutex); + + return 0; +} +static void snd_soc_component_add_unlocked(struct snd_soc_component *component) +{ + list_add(&component->list, &component_list); +} + +static void snd_soc_component_add(struct snd_soc_component *component) +{ mutex_lock(&client_mutex); - list_add(&cmpnt->list, &component_list); + snd_soc_component_add_unlocked(component); mutex_unlock(&client_mutex); +} - dev_dbg(cmpnt->dev, "ASoC: Registered component '%s'\n", cmpnt->name); - - return ret; +static void snd_soc_component_cleanup(struct snd_soc_component *component) +{ + snd_soc_unregister_dais(component); + kfree(component->name); +} -error_component_name: - kfree(cmpnt->name); +static void snd_soc_component_del_unlocked(struct snd_soc_component *component) +{ + list_del(&component->list); +} - return ret; +static void snd_soc_component_del(struct snd_soc_component *component) +{ + mutex_lock(&client_mutex); + snd_soc_component_del_unlocked(component); + mutex_unlock(&client_mutex); } int snd_soc_register_component(struct device *dev, @@ -4056,32 +4150,38 @@ int snd_soc_register_component(struct device *dev, int num_dai) { struct snd_soc_component *cmpnt; + int ret; - cmpnt = devm_kzalloc(dev, sizeof(*cmpnt), GFP_KERNEL); + cmpnt = kzalloc(sizeof(*cmpnt), GFP_KERNEL); if (!cmpnt) { dev_err(dev, "ASoC: Failed to allocate memory\n"); return -ENOMEM; } + ret = snd_soc_component_initialize(cmpnt, cmpnt_drv, dev); + if (ret) + goto err_free; + cmpnt->ignore_pmdown_time = true; cmpnt->registered_as_component = true; - return __snd_soc_register_component(dev, cmpnt, cmpnt_drv, NULL, - dai_drv, num_dai, true); -} -EXPORT_SYMBOL_GPL(snd_soc_register_component); + ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true); + if (ret < 0) { + dev_err(dev, "ASoC: Failed to regster DAIs: %d\n", ret); + goto err_cleanup; + } -static void __snd_soc_unregister_component(struct snd_soc_component *cmpnt) -{ - snd_soc_unregister_dais(cmpnt); + snd_soc_component_add(cmpnt); - mutex_lock(&client_mutex); - list_del(&cmpnt->list); - mutex_unlock(&client_mutex); + return 0; - dev_dbg(cmpnt->dev, "ASoC: Unregistered component '%s'\n", cmpnt->name); - kfree(cmpnt->name); +err_cleanup: + snd_soc_component_cleanup(cmpnt); +err_free: + kfree(cmpnt); + return ret; } +EXPORT_SYMBOL_GPL(snd_soc_register_component); /** * snd_soc_unregister_component - Unregister a component from the ASoC core @@ -4098,7 +4198,9 @@ void snd_soc_unregister_component(struct device *dev) return; found: - __snd_soc_unregister_component(cmpnt); + snd_soc_component_del(cmpnt); + snd_soc_component_cleanup(cmpnt); + kfree(cmpnt); } EXPORT_SYMBOL_GPL(snd_soc_unregister_component); @@ -4131,37 +4233,25 @@ int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform, { int ret; - /* create platform component name */ - platform->name = fmt_single_name(dev, &platform->id); - if (platform->name == NULL) - return -ENOMEM; + ret = snd_soc_component_initialize(&platform->component, + &platform_drv->component_driver, dev); + if (ret) + return ret; platform->dev = dev; platform->driver = platform_drv; - platform->dapm.dev = dev; - platform->dapm.platform = platform; - platform->dapm.component = &platform->component; - platform->dapm.stream_event = platform_drv->stream_event; if (platform_drv->write) platform->component.write = snd_soc_platform_drv_write; if (platform_drv->read) platform->component.read = snd_soc_platform_drv_read; - /* register component */ - ret = __snd_soc_register_component(dev, &platform->component, - &platform_drv->component_driver, - NULL, NULL, 0, false); - if (ret < 0) { - dev_err(platform->component.dev, - "ASoC: Failed to register component: %d\n", ret); - return ret; - } - mutex_lock(&client_mutex); + snd_soc_component_add_unlocked(&platform->component); list_add(&platform->list, &platform_list); mutex_unlock(&client_mutex); - dev_dbg(dev, "ASoC: Registered platform '%s'\n", platform->name); + dev_dbg(dev, "ASoC: Registered platform '%s'\n", + platform->component.name); return 0; } @@ -4198,15 +4288,16 @@ EXPORT_SYMBOL_GPL(snd_soc_register_platform); */ void snd_soc_remove_platform(struct snd_soc_platform *platform) { - __snd_soc_unregister_component(&platform->component); mutex_lock(&client_mutex); list_del(&platform->list); + snd_soc_component_del_unlocked(&platform->component); mutex_unlock(&client_mutex); + snd_soc_component_cleanup(&platform->component); + dev_dbg(platform->dev, "ASoC: Unregistered platform '%s'\n", - platform->name); - kfree(platform->name); + platform->component.name); } EXPORT_SYMBOL_GPL(snd_soc_remove_platform); @@ -4292,6 +4383,14 @@ static int snd_soc_codec_drv_read(struct snd_soc_component *component, return 0; } +static int snd_soc_codec_set_bias_level(struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); + + return codec->driver->set_bias_level(codec, level); +} + /** * snd_soc_register_codec - Register a codec with the ASoC core * @@ -4303,6 +4402,7 @@ int snd_soc_register_codec(struct device *dev, int num_dai) { struct snd_soc_codec *codec; + struct snd_soc_dai *dai; struct regmap *regmap; int ret, i; @@ -4312,24 +4412,23 @@ int snd_soc_register_codec(struct device *dev, if (codec == NULL) return -ENOMEM; - /* create CODEC component name */ - codec->name = fmt_single_name(dev, &codec->id); - if (codec->name == NULL) { - ret = -ENOMEM; - goto fail_codec; - } + codec->component.dapm_ptr = &codec->dapm; + + ret = snd_soc_component_initialize(&codec->component, + &codec_drv->component_driver, dev); + if (ret) + goto err_free; if (codec_drv->write) codec->component.write = snd_soc_codec_drv_write; if (codec_drv->read) codec->component.read = snd_soc_codec_drv_read; codec->component.ignore_pmdown_time = codec_drv->ignore_pmdown_time; - codec->dapm.bias_level = SND_SOC_BIAS_OFF; - codec->dapm.dev = dev; codec->dapm.codec = codec; - codec->dapm.component = &codec->component; - codec->dapm.seq_notifier = codec_drv->seq_notifier; - codec->dapm.stream_event = codec_drv->stream_event; + if (codec_drv->seq_notifier) + codec->dapm.seq_notifier = codec_drv->seq_notifier; + if (codec_drv->set_bias_level) + codec->dapm.set_bias_level = snd_soc_codec_set_bias_level; codec->dev = dev; codec->driver = codec_drv; codec->component.val_bytes = codec_drv->reg_word_size; @@ -4348,7 +4447,7 @@ int snd_soc_register_codec(struct device *dev, dev_err(codec->dev, "Failed to set cache I/O:%d\n", ret); - return ret; + goto err_cleanup; } } } @@ -4358,29 +4457,27 @@ int snd_soc_register_codec(struct device *dev, fixup_codec_formats(&dai_drv[i].capture); } - mutex_lock(&client_mutex); - list_add(&codec->list, &codec_list); - mutex_unlock(&client_mutex); - - /* register component */ - ret = __snd_soc_register_component(dev, &codec->component, - &codec_drv->component_driver, - codec, dai_drv, num_dai, false); + ret = snd_soc_register_dais(&codec->component, dai_drv, num_dai, false); if (ret < 0) { - dev_err(codec->dev, "ASoC: Failed to regster component: %d\n", ret); - goto fail_codec_name; + dev_err(dev, "ASoC: Failed to regster DAIs: %d\n", ret); + goto err_cleanup; } - dev_dbg(codec->dev, "ASoC: Registered codec '%s'\n", codec->name); - return 0; + list_for_each_entry(dai, &codec->component.dai_list, list) + dai->codec = codec; -fail_codec_name: mutex_lock(&client_mutex); - list_del(&codec->list); + snd_soc_component_add_unlocked(&codec->component); + list_add(&codec->list, &codec_list); mutex_unlock(&client_mutex); - kfree(codec->name); -fail_codec: + dev_dbg(codec->dev, "ASoC: Registered codec '%s'\n", + codec->component.name); + return 0; + +err_cleanup: + snd_soc_component_cleanup(&codec->component); +err_free: kfree(codec); return ret; } @@ -4402,16 +4499,17 @@ void snd_soc_unregister_codec(struct device *dev) return; found: - __snd_soc_unregister_component(&codec->component); mutex_lock(&client_mutex); list_del(&codec->list); + snd_soc_component_del_unlocked(&codec->component); mutex_unlock(&client_mutex); - dev_dbg(codec->dev, "ASoC: Unregistered codec '%s'\n", codec->name); + dev_dbg(codec->dev, "ASoC: Unregistered codec '%s'\n", + codec->component.name); + snd_soc_component_cleanup(&codec->component); snd_soc_cache_exit(codec); - kfree(codec->name); kfree(codec); } EXPORT_SYMBOL_GPL(snd_soc_unregister_codec); @@ -4420,9 +4518,16 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_codec); int snd_soc_of_parse_card_name(struct snd_soc_card *card, const char *propname) { - struct device_node *np = card->dev->of_node; + struct device_node *np; int ret; + if (!card->dev) { + pr_err("card->dev is not set before calling %s\n", __func__); + return -EINVAL; + } + + np = card->dev->of_node; + ret = of_property_read_string_index(np, propname, 0, &card->name); /* * EINVAL means the property does not exist. This is fine providing diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a74b9bf23d9f..8348352dc2c6 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -350,12 +350,27 @@ static bool dapm_kcontrol_set_value(const struct snd_kcontrol *kcontrol, } /** + * snd_soc_dapm_kcontrol_dapm() - Returns the dapm context associated to a + * kcontrol + * @kcontrol: The kcontrol + * + * Note: This function must only be used on kcontrols that are known to have + * been registered for a CODEC. Otherwise the behaviour is undefined. + */ +struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm( + struct snd_kcontrol *kcontrol) +{ + return dapm_kcontrol_get_wlist(kcontrol)->widgets[0]->dapm; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_dapm); + +/** * snd_soc_dapm_kcontrol_codec() - Returns the codec associated to a kcontrol * @kcontrol: The kcontrol */ struct snd_soc_codec *snd_soc_dapm_kcontrol_codec(struct snd_kcontrol *kcontrol) { - return dapm_kcontrol_get_wlist(kcontrol)->widgets[0]->codec; + return snd_soc_dapm_to_codec(snd_soc_dapm_kcontrol_dapm(kcontrol)); } EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_codec); @@ -375,23 +390,38 @@ static void dapm_reset(struct snd_soc_card *card) } } -static int soc_widget_read(struct snd_soc_dapm_widget *w, int reg, +static const char *soc_dapm_prefix(struct snd_soc_dapm_context *dapm) +{ + if (!dapm->component) + return NULL; + return dapm->component->name_prefix; +} + +static int soc_dapm_read(struct snd_soc_dapm_context *dapm, int reg, unsigned int *value) { - if (!w->dapm->component) + if (!dapm->component) return -EIO; - return snd_soc_component_read(w->dapm->component, reg, value); + return snd_soc_component_read(dapm->component, reg, value); } -static int soc_widget_update_bits(struct snd_soc_dapm_widget *w, +static int soc_dapm_update_bits(struct snd_soc_dapm_context *dapm, int reg, unsigned int mask, unsigned int value) { - if (!w->dapm->component) + if (!dapm->component) return -EIO; - return snd_soc_component_update_bits_async(w->dapm->component, reg, + return snd_soc_component_update_bits_async(dapm->component, reg, mask, value); } +static int soc_dapm_test_bits(struct snd_soc_dapm_context *dapm, + int reg, unsigned int mask, unsigned int value) +{ + if (!dapm->component) + return -EIO; + return snd_soc_component_test_bits(dapm->component, reg, mask, value); +} + static void soc_dapm_async_complete(struct snd_soc_dapm_context *dapm) { if (dapm->component) @@ -420,15 +450,10 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm, if (ret != 0) goto out; - if (dapm->codec) { - if (dapm->codec->driver->set_bias_level) - ret = dapm->codec->driver->set_bias_level(dapm->codec, - level); - else - dapm->bias_level = level; - } else if (!card || dapm != &card->dapm) { + if (dapm->set_bias_level) + ret = dapm->set_bias_level(dapm, level); + else if (!card || dapm != &card->dapm) dapm->bias_level = level; - } if (ret != 0) goto out; @@ -452,7 +477,7 @@ static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, int i; if (e->reg != SND_SOC_NOPM) { - soc_widget_read(dest, e->reg, &val); + soc_dapm_read(dapm, e->reg, &val); val = (val >> e->shift_l) & e->mask; item = snd_soc_enum_val_to_item(e, val); } else { @@ -496,7 +521,7 @@ static void dapm_set_mixer_path_status(struct snd_soc_dapm_widget *w, unsigned int val; if (reg != SND_SOC_NOPM) { - soc_widget_read(w, reg, &val); + soc_dapm_read(w->dapm, reg, &val); val = (val >> shift) & mask; if (invert) val = max - val; @@ -570,11 +595,7 @@ static int dapm_create_or_share_mixmux_kcontrol(struct snd_soc_dapm_widget *w, const char *name; int ret; - if (dapm->codec) - prefix = dapm->codec->name_prefix; - else - prefix = NULL; - + prefix = soc_dapm_prefix(dapm); if (prefix) prefix_len = strlen(prefix) + 1; else @@ -1308,16 +1329,18 @@ static void dapm_seq_check_event(struct snd_soc_card *card, static void dapm_seq_run_coalesced(struct snd_soc_card *card, struct list_head *pending) { + struct snd_soc_dapm_context *dapm; struct snd_soc_dapm_widget *w; int reg; unsigned int value = 0; unsigned int mask = 0; - reg = list_first_entry(pending, struct snd_soc_dapm_widget, - power_list)->reg; + w = list_first_entry(pending, struct snd_soc_dapm_widget, power_list); + reg = w->reg; + dapm = w->dapm; list_for_each_entry(w, pending, power_list) { - WARN_ON(reg != w->reg); + WARN_ON(reg != w->reg || dapm != w->dapm); w->power = w->new_power; mask |= w->mask << w->shift; @@ -1326,7 +1349,7 @@ static void dapm_seq_run_coalesced(struct snd_soc_card *card, else value |= w->off_val << w->shift; - pop_dbg(w->dapm->dev, card->pop_time, + pop_dbg(dapm->dev, card->pop_time, "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n", w->name, reg, value, mask); @@ -1339,14 +1362,12 @@ static void dapm_seq_run_coalesced(struct snd_soc_card *card, /* Any widget will do, they should all be updating the * same register. */ - w = list_first_entry(pending, struct snd_soc_dapm_widget, - power_list); - pop_dbg(w->dapm->dev, card->pop_time, + pop_dbg(dapm->dev, card->pop_time, "pop test : Applying 0x%x/0x%x to %x in %dms\n", value, mask, reg, card->pop_time); pop_wait(card->pop_time); - soc_widget_update_bits(w, reg, mask, value); + soc_dapm_update_bits(dapm, reg, mask, value); } list_for_each_entry(w, pending, power_list) { @@ -1492,7 +1513,8 @@ static void dapm_widget_update(struct snd_soc_card *card) if (!w) return; - ret = soc_widget_update_bits(w, update->reg, update->mask, update->val); + ret = soc_dapm_update_bits(w->dapm, update->reg, update->mask, + update->val); if (ret < 0) dev_err(w->dapm->dev, "ASoC: %s DAPM update failed: %d\n", w->name, ret); @@ -2062,17 +2084,13 @@ int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_context *dapm, } EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power); -/* show dapm widget status in sys fs */ -static ssize_t dapm_widget_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t dapm_widget_show_codec(struct snd_soc_codec *codec, char *buf) { - struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); - struct snd_soc_codec *codec =rtd->codec; struct snd_soc_dapm_widget *w; int count = 0; char *state = "not set"; - list_for_each_entry(w, &codec->card->widgets, list) { + list_for_each_entry(w, &codec->component.card->widgets, list) { if (w->dapm != &codec->dapm) continue; @@ -2120,6 +2138,21 @@ static ssize_t dapm_widget_show(struct device *dev, return count; } +/* show dapm widget status in sys fs */ +static ssize_t dapm_widget_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); + int i, count = 0; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_codec *codec = rtd->codec_dais[i]->codec; + count += dapm_widget_show_codec(codec, buf + count); + } + + return count; +} + static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL); int snd_soc_dapm_sys_add(struct device *dev) @@ -2371,14 +2404,16 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, const char *source; char prefixed_sink[80]; char prefixed_source[80]; + const char *prefix; int ret; - if (dapm->codec && dapm->codec->name_prefix) { + prefix = soc_dapm_prefix(dapm); + if (prefix) { snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s", - dapm->codec->name_prefix, route->sink); + prefix, route->sink); sink = prefixed_sink; snprintf(prefixed_source, sizeof(prefixed_source), "%s %s", - dapm->codec->name_prefix, route->source); + prefix, route->source); source = prefixed_source; } else { sink = route->sink; @@ -2439,6 +2474,7 @@ static int snd_soc_dapm_del_route(struct snd_soc_dapm_context *dapm, const char *source; char prefixed_sink[80]; char prefixed_source[80]; + const char *prefix; if (route->control) { dev_err(dapm->dev, @@ -2446,12 +2482,13 @@ static int snd_soc_dapm_del_route(struct snd_soc_dapm_context *dapm, return -EINVAL; } - if (dapm->codec && dapm->codec->name_prefix) { + prefix = soc_dapm_prefix(dapm); + if (prefix) { snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s", - dapm->codec->name_prefix, route->sink); + prefix, route->sink); sink = prefixed_sink; snprintf(prefixed_source, sizeof(prefixed_source), "%s %s", - dapm->codec->name_prefix, route->source); + prefix, route->source); source = prefixed_source; } else { sink = route->sink; @@ -2670,7 +2707,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) /* Read the initial power state from the device */ if (w->reg >= 0) { - soc_widget_read(w, w->reg, &val); + soc_dapm_read(w->dapm, w->reg, &val); val = val >> w->shift; val &= w->mask; if (val == w->on_val) @@ -2701,8 +2738,8 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets); int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); - struct snd_soc_card *card = codec->card; + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; int reg = mc->reg; @@ -2711,17 +2748,20 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; unsigned int val; + int ret = 0; if (snd_soc_volsw_is_stereo(mc)) - dev_warn(codec->dapm.dev, + dev_warn(dapm->dev, "ASoC: Control '%s' is stereo, which is not supported\n", kcontrol->id.name); mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); - if (dapm_kcontrol_is_powered(kcontrol) && reg != SND_SOC_NOPM) - val = (snd_soc_read(codec, reg) >> shift) & mask; - else + if (dapm_kcontrol_is_powered(kcontrol) && reg != SND_SOC_NOPM) { + ret = soc_dapm_read(dapm, reg, &val); + val = (val >> shift) & mask; + } else { val = dapm_kcontrol_get_value(kcontrol); + } mutex_unlock(&card->dapm_mutex); if (invert) @@ -2729,7 +2769,7 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, else ucontrol->value.integer.value[0] = val; - return 0; + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw); @@ -2745,8 +2785,8 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw); int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); - struct snd_soc_card *card = codec->card; + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; int reg = mc->reg; @@ -2755,12 +2795,12 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; unsigned int val; - int connect, change; + int connect, change, reg_change = 0; struct snd_soc_dapm_update update; int ret = 0; if (snd_soc_volsw_is_stereo(mc)) - dev_warn(codec->dapm.dev, + dev_warn(dapm->dev, "ASoC: Control '%s' is stereo, which is not supported\n", kcontrol->id.name); @@ -2773,20 +2813,23 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); change = dapm_kcontrol_set_value(kcontrol, val); - if (change) { - if (reg != SND_SOC_NOPM) { - mask = mask << shift; - val = val << shift; - - if (snd_soc_test_bits(codec, reg, mask, val)) { - update.kcontrol = kcontrol; - update.reg = reg; - update.mask = mask; - update.val = val; - card->update = &update; - } + if (reg != SND_SOC_NOPM) { + mask = mask << shift; + val = val << shift; + + reg_change = soc_dapm_test_bits(dapm, reg, mask, val); + } + + if (change || reg_change) { + if (reg_change) { + update.kcontrol = kcontrol; + update.reg = reg; + update.mask = mask; + update.val = val; + card->update = &update; } + change |= reg_change; ret = soc_dapm_mixer_update_power(card, kcontrol, connect); @@ -2814,12 +2857,13 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw); int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int reg_val, val; + int ret = 0; if (e->reg != SND_SOC_NOPM) - reg_val = snd_soc_read(codec, e->reg); + ret = soc_dapm_read(dapm, e->reg, ®_val); else reg_val = dapm_kcontrol_get_value(kcontrol); @@ -2831,7 +2875,7 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, ucontrol->value.enumerated.item[1] = val; } - return 0; + return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double); @@ -2847,8 +2891,8 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double); int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); - struct snd_soc_card *card = codec->card; + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; unsigned int val, change; @@ -2871,7 +2915,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); if (e->reg != SND_SOC_NOPM) - change = snd_soc_test_bits(codec, e->reg, mask, val); + change = soc_dapm_test_bits(dapm, e->reg, mask, val); else change = dapm_kcontrol_set_value(kcontrol, val); @@ -2968,6 +3012,7 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, const struct snd_soc_dapm_widget *widget) { struct snd_soc_dapm_widget *w; + const char *prefix; int ret; if ((w = dapm_cnew_widget(widget)) == NULL) @@ -3008,9 +3053,9 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, break; } - if (dapm->codec && dapm->codec->name_prefix) - w->name = kasprintf(GFP_KERNEL, "%s %s", - dapm->codec->name_prefix, widget->name); + prefix = soc_dapm_prefix(dapm); + if (prefix) + w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name); else w->name = kasprintf(GFP_KERNEL, "%s", widget->name); @@ -3063,7 +3108,6 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, w->dapm = dapm; w->codec = dapm->codec; - w->platform = dapm->platform; INIT_LIST_HEAD(&w->sources); INIT_LIST_HEAD(&w->sinks); INIT_LIST_HEAD(&w->list); @@ -3170,27 +3214,15 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - if (source->driver->ops && source->driver->ops->hw_params) { - substream.stream = SNDRV_PCM_STREAM_CAPTURE; - ret = source->driver->ops->hw_params(&substream, - params, source); - if (ret != 0) { - dev_err(source->dev, - "ASoC: hw_params() failed: %d\n", ret); - goto out; - } - } + substream.stream = SNDRV_PCM_STREAM_CAPTURE; + ret = soc_dai_hw_params(&substream, params, source); + if (ret < 0) + goto out; - if (sink->driver->ops && sink->driver->ops->hw_params) { - substream.stream = SNDRV_PCM_STREAM_PLAYBACK; - ret = sink->driver->ops->hw_params(&substream, params, - sink); - if (ret != 0) { - dev_err(sink->dev, - "ASoC: hw_params() failed: %d\n", ret); - goto out; - } - } + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + ret = soc_dai_hw_params(&substream, params, sink); + if (ret < 0) + goto out; break; case SND_SOC_DAPM_POST_PMU: @@ -3362,25 +3394,15 @@ int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card) return 0; } -void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) +static void dapm_connect_dai_link_widgets(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_pcm_runtime *rtd = card->rtd; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dapm_widget *sink, *source; - struct snd_soc_dai *cpu_dai, *codec_dai; int i; - /* for each BE DAI link... */ - for (i = 0; i < card->num_rtd; i++) { - rtd = &card->rtd[i]; - cpu_dai = rtd->cpu_dai; - codec_dai = rtd->codec_dai; - - /* - * dynamic FE links have no fixed DAI mapping. - * CODEC<->CODEC links have no direct connection. - */ - if (rtd->dai_link->dynamic || rtd->dai_link->params) - continue; + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; /* there is no point in connecting BE DAI links with dummies */ if (snd_soc_dai_is_dummy(codec_dai) || @@ -3392,8 +3414,8 @@ void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) source = cpu_dai->playback_widget; sink = codec_dai->playback_widget; dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n", - cpu_dai->codec->name, source->name, - codec_dai->platform->name, sink->name); + cpu_dai->component->name, source->name, + codec_dai->component->name, sink->name); snd_soc_dapm_add_path(&card->dapm, source, sink, NULL, NULL); @@ -3404,8 +3426,8 @@ void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) source = codec_dai->capture_widget; sink = cpu_dai->capture_widget; dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n", - codec_dai->codec->name, source->name, - cpu_dai->platform->name, sink->name); + codec_dai->component->name, source->name, + cpu_dai->component->name, sink->name); snd_soc_dapm_add_path(&card->dapm, source, sink, NULL, NULL); @@ -3442,11 +3464,34 @@ static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream, } } +void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = card->rtd; + int i; + + /* for each BE DAI link... */ + for (i = 0; i < card->num_rtd; i++) { + rtd = &card->rtd[i]; + + /* + * dynamic FE links have no fixed DAI mapping. + * CODEC<->CODEC links have no direct connection. + */ + if (rtd->dai_link->dynamic || rtd->dai_link->params) + continue; + + dapm_connect_dai_link_widgets(card, rtd); + } +} + static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, int event) { + int i; + soc_dapm_dai_stream_event(rtd->cpu_dai, stream, event); - soc_dapm_dai_stream_event(rtd->codec_dai, stream, event); + for (i = 0; i < rtd->num_codecs; i++) + soc_dapm_dai_stream_event(rtd->codec_dais[i], stream, event); dapm_power_widgets(rtd->card, event); } @@ -3755,36 +3800,31 @@ static bool snd_soc_dapm_widget_in_card_paths(struct snd_soc_card *card, } /** - * snd_soc_dapm_auto_nc_codec_pins - call snd_soc_dapm_nc_pin for unused pins - * @codec: The codec whose pins should be processed + * snd_soc_dapm_auto_nc_pins - call snd_soc_dapm_nc_pin for unused pins + * @card: The card whose pins should be processed * - * Automatically call snd_soc_dapm_nc_pin() for any external pins in the codec - * which are unused. Pins are used if they are connected externally to the - * codec, whether that be to some other device, or a loop-back connection to - * the codec itself. + * Automatically call snd_soc_dapm_nc_pin() for any external pins in the card + * which are unused. Pins are used if they are connected externally to a + * component, whether that be to some other device, or a loop-back connection to + * the component itself. */ -void snd_soc_dapm_auto_nc_codec_pins(struct snd_soc_codec *codec) +void snd_soc_dapm_auto_nc_pins(struct snd_soc_card *card) { - struct snd_soc_card *card = codec->card; - struct snd_soc_dapm_context *dapm = &codec->dapm; struct snd_soc_dapm_widget *w; - dev_dbg(codec->dev, "ASoC: Auto NC: DAPMs: card:%p codec:%p\n", - &card->dapm, &codec->dapm); + dev_dbg(card->dev, "ASoC: Auto NC: DAPMs: card:%p\n", &card->dapm); list_for_each_entry(w, &card->widgets, list) { - if (w->dapm != dapm) - continue; switch (w->id) { case snd_soc_dapm_input: case snd_soc_dapm_output: case snd_soc_dapm_micbias: - dev_dbg(codec->dev, "ASoC: Auto NC: Checking widget %s\n", + dev_dbg(card->dev, "ASoC: Auto NC: Checking widget %s\n", w->name); if (!snd_soc_dapm_widget_in_card_paths(card, w)) { - dev_dbg(codec->dev, + dev_dbg(card->dev, "... Not in map; disabling\n"); - snd_soc_dapm_nc_pin(dapm, w->name); + snd_soc_dapm_nc_pin(w->dapm, w->name); } break; default: diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 5bace124ef43..6307f85e871b 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -119,7 +119,10 @@ static int dmaengine_pcm_set_runtime_hwparams(struct snd_pcm_substream *substrea struct snd_dmaengine_dai_dma_data *dma_data; struct dma_slave_caps dma_caps; struct snd_pcm_hardware hw; - int ret; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int i, ret; if (pcm->config && pcm->config->pcm_hardware) return snd_soc_set_runtime_hwparams(substream, @@ -146,6 +149,38 @@ static int dmaengine_pcm_set_runtime_hwparams(struct snd_pcm_substream *substrea hw.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) hw.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dstn_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * Prepare formats mask for valid/allowed sample types. If the dma does + * not have support for the given physical word size, it needs to be + * masked out so user space can not use the format which produces + * corrupted audio. + * In case the dma driver does not implement the slave_caps the default + * assumption is that it supports 1, 2 and 4 bytes widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* Enable only samples with DMA supported physical widths */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + hw.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } } return snd_soc_set_runtime_hwparams(substream, &hw); diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index d0d98810af91..ab47fea997a3 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -43,7 +43,7 @@ int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type, INIT_LIST_HEAD(&jack->jack_zones); BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); - return snd_jack_new(codec->card->snd_card, id, type, &jack->jack); + return snd_jack_new(codec->component.card->snd_card, id, type, &jack->jack); } EXPORT_SYMBOL_GPL(snd_soc_jack_new); @@ -260,7 +260,7 @@ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) static irqreturn_t gpio_handler(int irq, void *data) { struct snd_soc_jack_gpio *gpio = data; - struct device *dev = gpio->jack->codec->card->dev; + struct device *dev = gpio->jack->codec->component.card->dev; trace_snd_soc_jack_irq(gpio->name); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 54d18f22a33e..731fdb5b5f9b 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -7,7 +7,7 @@ * Copyright (C) 2010 Texas Instruments Inc. * * Authors: Liam Girdwood <lrg@ti.com> - * Mark Brown <broonie@opensource.wolfsonmicro.com> + * Mark Brown <broonie@opensource.wolfsonmicro.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -47,22 +47,26 @@ void snd_soc_runtime_activate(struct snd_soc_pcm_runtime *rtd, int stream) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + int i; lockdep_assert_held(&rtd->pcm_mutex); if (stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active++; - codec_dai->playback_active++; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->playback_active++; } else { cpu_dai->capture_active++; - codec_dai->capture_active++; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->capture_active++; } cpu_dai->active++; - codec_dai->active++; cpu_dai->component->active++; - codec_dai->component->active++; + for (i = 0; i < rtd->num_codecs; i++) { + rtd->codec_dais[i]->active++; + rtd->codec_dais[i]->component->active++; + } } /** @@ -78,22 +82,26 @@ void snd_soc_runtime_activate(struct snd_soc_pcm_runtime *rtd, int stream) void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + int i; lockdep_assert_held(&rtd->pcm_mutex); if (stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active--; - codec_dai->playback_active--; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->playback_active--; } else { cpu_dai->capture_active--; - codec_dai->capture_active--; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->capture_active--; } cpu_dai->active--; - codec_dai->active--; cpu_dai->component->active--; - codec_dai->component->active--; + for (i = 0; i < rtd->num_codecs; i++) { + rtd->codec_dais[i]->component->active--; + rtd->codec_dais[i]->active--; + } } /** @@ -107,11 +115,16 @@ void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) */ bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) { + int i; + bool ignore = true; + if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) return true; - return rtd->cpu_dai->component->ignore_pmdown_time && - rtd->codec_dai->component->ignore_pmdown_time; + for (i = 0; i < rtd->num_codecs; i++) + ignore &= rtd->codec_dais[i]->component->ignore_pmdown_time; + + return rtd->cpu_dai->component->ignore_pmdown_time && ignore; } /** @@ -222,8 +235,7 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, { 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 rate, channels, sample_bits, symmetry; + unsigned int rate, channels, sample_bits, symmetry, i; rate = params_rate(params); channels = params_channels(params); @@ -231,8 +243,11 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, /* reject unmatched parameters when applying symmetry */ symmetry = cpu_dai->driver->symmetric_rates || - codec_dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry |= rtd->codec_dais[i]->driver->symmetric_rates; + if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) { dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n", cpu_dai->rate, rate); @@ -240,8 +255,11 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, } symmetry = cpu_dai->driver->symmetric_channels || - codec_dai->driver->symmetric_channels || rtd->dai_link->symmetric_channels; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry |= rtd->codec_dais[i]->driver->symmetric_channels; + if (symmetry && cpu_dai->channels && cpu_dai->channels != channels) { dev_err(rtd->dev, "ASoC: unmatched channel symmetry: %d - %d\n", cpu_dai->channels, channels); @@ -249,8 +267,11 @@ static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, } symmetry = cpu_dai->driver->symmetric_samplebits || - codec_dai->driver->symmetric_samplebits || rtd->dai_link->symmetric_samplebits; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry |= rtd->codec_dais[i]->driver->symmetric_samplebits; + if (symmetry && cpu_dai->sample_bits && cpu_dai->sample_bits != sample_bits) { dev_err(rtd->dev, "ASoC: unmatched sample bits symmetry: %d - %d\n", cpu_dai->sample_bits, sample_bits); @@ -264,15 +285,20 @@ static bool soc_pcm_has_symmetry(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_driver *cpu_driver = rtd->cpu_dai->driver; - struct snd_soc_dai_driver *codec_driver = rtd->codec_dai->driver; struct snd_soc_dai_link *link = rtd->dai_link; + unsigned int symmetry, i; - return cpu_driver->symmetric_rates || codec_driver->symmetric_rates || - link->symmetric_rates || cpu_driver->symmetric_channels || - codec_driver->symmetric_channels || link->symmetric_channels || - cpu_driver->symmetric_samplebits || - codec_driver->symmetric_samplebits || - link->symmetric_samplebits; + symmetry = cpu_driver->symmetric_rates || link->symmetric_rates || + cpu_driver->symmetric_channels || link->symmetric_channels || + cpu_driver->symmetric_samplebits || link->symmetric_samplebits; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry = symmetry || + rtd->codec_dais[i]->driver->symmetric_rates || + rtd->codec_dais[i]->driver->symmetric_channels || + rtd->codec_dais[i]->driver->symmetric_samplebits; + + return symmetry; } /* @@ -284,15 +310,10 @@ static int sample_sizes[] = { 24, 32, }; -static void soc_pcm_apply_msb(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) { - int ret, i, bits; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - bits = dai->driver->playback.sig_bits; - else - bits = dai->driver->capture.sig_bits; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret, i; if (!bits) return; @@ -304,38 +325,105 @@ static void soc_pcm_apply_msb(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, sample_sizes[i], bits); if (ret != 0) - dev_warn(dai->dev, + dev_warn(rtd->dev, "ASoC: Failed to set MSB %d/%d: %d\n", bits, sample_sizes[i], ret); } } -static void soc_pcm_init_runtime_hw(struct snd_pcm_runtime *runtime, - struct snd_soc_pcm_stream *codec_stream, - struct snd_soc_pcm_stream *cpu_stream) +static void soc_pcm_apply_msb(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i; + unsigned int bits = 0, cpu_bits; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->playback.sig_bits == 0) { + bits = 0; + break; + } + bits = max(codec_dai->driver->playback.sig_bits, bits); + } + cpu_bits = cpu_dai->driver->playback.sig_bits; + } else { + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->playback.sig_bits == 0) { + bits = 0; + break; + } + bits = max(codec_dai->driver->capture.sig_bits, bits); + } + cpu_bits = cpu_dai->driver->capture.sig_bits; + } + + soc_pcm_set_msb(substream, bits); + soc_pcm_set_msb(substream, cpu_bits); +} + +static void soc_pcm_init_runtime_hw(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_hardware *hw = &runtime->hw; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_driver *cpu_dai_drv = rtd->cpu_dai->driver; + struct snd_soc_dai_driver *codec_dai_drv; + struct snd_soc_pcm_stream *codec_stream; + struct snd_soc_pcm_stream *cpu_stream; + unsigned int chan_min = 0, chan_max = UINT_MAX; + unsigned int rate_min = 0, rate_max = UINT_MAX; + unsigned int rates = UINT_MAX; + u64 formats = ULLONG_MAX; + int i; - hw->channels_min = max(codec_stream->channels_min, - cpu_stream->channels_min); - hw->channels_max = min(codec_stream->channels_max, - cpu_stream->channels_max); - if (hw->formats) - hw->formats &= codec_stream->formats & cpu_stream->formats; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_stream = &cpu_dai_drv->playback; else - hw->formats = codec_stream->formats & cpu_stream->formats; - hw->rates = snd_pcm_rate_mask_intersect(codec_stream->rates, - cpu_stream->rates); + cpu_stream = &cpu_dai_drv->capture; - hw->rate_min = 0; - hw->rate_max = UINT_MAX; + /* first calculate min/max only for CODECs in the DAI link */ + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai_drv = rtd->codec_dais[i]->driver; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &codec_dai_drv->playback; + else + codec_stream = &codec_dai_drv->capture; + chan_min = max(chan_min, codec_stream->channels_min); + chan_max = min(chan_max, codec_stream->channels_max); + rate_min = max(rate_min, codec_stream->rate_min); + rate_max = min_not_zero(rate_max, codec_stream->rate_max); + formats &= codec_stream->formats; + rates = snd_pcm_rate_mask_intersect(codec_stream->rates, rates); + } + + /* + * chan min/max cannot be enforced if there are multiple CODEC DAIs + * connected to a single CPU DAI, use CPU DAI's directly and let + * channel allocation be fixed up later + */ + if (rtd->num_codecs > 1) { + chan_min = cpu_stream->channels_min; + chan_max = cpu_stream->channels_max; + } + + hw->channels_min = max(chan_min, cpu_stream->channels_min); + hw->channels_max = min(chan_max, cpu_stream->channels_max); + if (hw->formats) + hw->formats &= formats & cpu_stream->formats; + else + hw->formats = formats & cpu_stream->formats; + hw->rates = snd_pcm_rate_mask_intersect(rates, cpu_stream->rates); snd_pcm_limit_hw_rates(runtime); hw->rate_min = max(hw->rate_min, cpu_stream->rate_min); - hw->rate_min = max(hw->rate_min, codec_stream->rate_min); + hw->rate_min = max(hw->rate_min, rate_min); hw->rate_max = min_not_zero(hw->rate_max, cpu_stream->rate_max); - hw->rate_max = min_not_zero(hw->rate_max, codec_stream->rate_max); + hw->rate_max = min_not_zero(hw->rate_max, rate_max); } /* @@ -349,15 +437,16 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; - struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver; - int ret = 0; + struct snd_soc_dai *codec_dai; + const char *codec_dai_name = "multicodec"; + int i, ret = 0; pinctrl_pm_select_default_state(cpu_dai->dev); - pinctrl_pm_select_default_state(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev); pm_runtime_get_sync(cpu_dai->dev); - pm_runtime_get_sync(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pm_runtime_get_sync(rtd->codec_dais[i]->dev); pm_runtime_get_sync(platform->dev); mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -376,18 +465,28 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) ret = platform->driver->ops->open(substream); if (ret < 0) { dev_err(platform->dev, "ASoC: can't open platform" - " %s: %d\n", platform->name, ret); + " %s: %d\n", platform->component.name, ret); goto platform_err; } } - if (codec_dai->driver->ops && codec_dai->driver->ops->startup) { - ret = codec_dai->driver->ops->startup(substream, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: can't open codec" - " %s: %d\n", codec_dai->name, ret); - goto codec_dai_err; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->startup) { + ret = codec_dai->driver->ops->startup(substream, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "ASoC: can't open codec %s: %d\n", + codec_dai->name, ret); + goto codec_dai_err; + } } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_dai->tx_mask = 0; + else + codec_dai->rx_mask = 0; } if (rtd->dai_link->ops && rtd->dai_link->ops->startup) { @@ -404,13 +503,10 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) goto dynamic; /* Check that the codec and cpu DAIs are compatible */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - soc_pcm_init_runtime_hw(runtime, &codec_dai_drv->playback, - &cpu_dai_drv->playback); - } else { - soc_pcm_init_runtime_hw(runtime, &codec_dai_drv->capture, - &cpu_dai_drv->capture); - } + soc_pcm_init_runtime_hw(substream); + + if (rtd->num_codecs == 1) + codec_dai_name = rtd->codec_dai->name; if (soc_pcm_has_symmetry(substream)) runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; @@ -418,23 +514,22 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) ret = -EINVAL; if (!runtime->hw.rates) { printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.formats) { printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.channels_min || !runtime->hw.channels_max || runtime->hw.channels_min > runtime->hw.channels_max) { printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); goto config_err; } - soc_pcm_apply_msb(substream, codec_dai); - soc_pcm_apply_msb(substream, cpu_dai); + soc_pcm_apply_msb(substream); /* Symmetry only applies if we've already got an active stream. */ if (cpu_dai->active) { @@ -443,14 +538,17 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) goto config_err; } - if (codec_dai->active) { - ret = soc_pcm_apply_symmetry(substream, codec_dai); - if (ret != 0) - goto config_err; + for (i = 0; i < rtd->num_codecs; i++) { + if (rtd->codec_dais[i]->active) { + ret = soc_pcm_apply_symmetry(substream, + rtd->codec_dais[i]); + if (ret != 0) + goto config_err; + } } pr_debug("ASoC: %s <-> %s info:\n", - codec_dai->name, cpu_dai->name); + codec_dai_name, cpu_dai->name); pr_debug("ASoC: rate mask 0x%x\n", runtime->hw.rates); pr_debug("ASoC: min ch %d max ch %d\n", runtime->hw.channels_min, runtime->hw.channels_max); @@ -469,10 +567,15 @@ config_err: rtd->dai_link->ops->shutdown(substream); machine_err: - if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); + i = rtd->num_codecs; codec_dai_err: + while (--i >= 0) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->shutdown) + codec_dai->driver->ops->shutdown(substream, codec_dai); + } + if (platform->driver->ops && platform->driver->ops->close) platform->driver->ops->close(substream); @@ -483,10 +586,13 @@ out: mutex_unlock(&rtd->pcm_mutex); pm_runtime_put(platform->dev); - pm_runtime_put(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pm_runtime_put(rtd->codec_dais[i]->dev); pm_runtime_put(cpu_dai->dev); - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->active) + pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); + } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev); @@ -502,7 +608,7 @@ static void close_delayed_work(struct work_struct *work) { struct snd_soc_pcm_runtime *rtd = container_of(work, struct snd_soc_pcm_runtime, delayed_work.work); - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dais[0]; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -531,7 +637,8 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; + int i; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -541,14 +648,20 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) if (!cpu_dai->active) cpu_dai->rate = 0; - if (!codec_dai->active) - codec_dai->rate = 0; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (!codec_dai->active) + codec_dai->rate = 0; + } if (cpu_dai->driver->ops->shutdown) cpu_dai->driver->ops->shutdown(substream, cpu_dai); - if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->shutdown) + codec_dai->driver->ops->shutdown(substream, codec_dai); + } if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) rtd->dai_link->ops->shutdown(substream); @@ -578,10 +691,13 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) mutex_unlock(&rtd->pcm_mutex); pm_runtime_put(platform->dev); - pm_runtime_put(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pm_runtime_put(rtd->codec_dais[i]->dev); pm_runtime_put(cpu_dai->dev); - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->active) + pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); + } if (!cpu_dai->active) pinctrl_pm_select_sleep_state(cpu_dai->dev); @@ -598,8 +714,8 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret = 0; + struct snd_soc_dai *codec_dai; + int i, ret = 0; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -621,12 +737,16 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) } } - if (codec_dai->driver->ops && codec_dai->driver->ops->prepare) { - ret = codec_dai->driver->ops->prepare(substream, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: DAI prepare error: %d\n", - ret); - goto out; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->prepare) { + ret = codec_dai->driver->ops->prepare(substream, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "ASoC: DAI prepare error: %d\n", ret); + goto out; + } } } @@ -649,13 +769,44 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START); - snd_soc_dai_digital_mute(codec_dai, 0, substream->stream); + for (i = 0; i < rtd->num_codecs; i++) + snd_soc_dai_digital_mute(rtd->codec_dais[i], 0, + substream->stream); out: mutex_unlock(&rtd->pcm_mutex); return ret; } +static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, + unsigned int mask) +{ + struct snd_interval *interval; + int channels = hweight_long(mask); + + interval = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + interval->min = channels; + interval->max = channels; +} + +int soc_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret; + + if (dai->driver->ops && dai->driver->ops->hw_params) { + ret = dai->driver->ops->hw_params(substream, params, dai); + if (ret < 0) { + dev_err(dai->dev, "ASoC: can't set %s hw params: %d\n", + dai->name, ret); + return ret; + } + } + + return 0; +} + /* * Called by ALSA when the hardware params are set by application. This * function can also be called multiple times and can allocate buffers @@ -667,8 +818,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret = 0; + int i, ret = 0; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -685,29 +835,40 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, } } - if (codec_dai->driver->ops && codec_dai->driver->ops->hw_params) { - ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: can't set %s hw params:" - " %d\n", codec_dai->name, ret); + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + struct snd_pcm_hw_params codec_params; + + /* copy params for each codec */ + codec_params = *params; + + /* fixup params based on TDM slot masks */ + if (codec_dai->tx_mask) + soc_pcm_codec_params_fixup(&codec_params, + codec_dai->tx_mask); + if (codec_dai->rx_mask) + soc_pcm_codec_params_fixup(&codec_params, + codec_dai->rx_mask); + + ret = soc_dai_hw_params(substream, &codec_params, codec_dai); + if(ret < 0) goto codec_err; - } - } - if (cpu_dai->driver->ops && cpu_dai->driver->ops->hw_params) { - ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai); - if (ret < 0) { - dev_err(cpu_dai->dev, "ASoC: %s hw params failed: %d\n", - cpu_dai->name, ret); - goto interface_err; - } + codec_dai->rate = params_rate(&codec_params); + codec_dai->channels = params_channels(&codec_params); + codec_dai->sample_bits = snd_pcm_format_physical_width( + params_format(&codec_params)); } + ret = soc_dai_hw_params(substream, params, cpu_dai); + if (ret < 0) + goto interface_err; + if (platform->driver->ops && platform->driver->ops->hw_params) { ret = platform->driver->ops->hw_params(substream, params); if (ret < 0) { dev_err(platform->dev, "ASoC: %s hw params failed: %d\n", - platform->name, ret); + platform->component.name, ret); goto platform_err; } } @@ -718,11 +879,6 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, cpu_dai->sample_bits = snd_pcm_format_physical_width(params_format(params)); - codec_dai->rate = params_rate(params); - codec_dai->channels = params_channels(params); - codec_dai->sample_bits = - snd_pcm_format_physical_width(params_format(params)); - out: mutex_unlock(&rtd->pcm_mutex); return ret; @@ -732,10 +888,16 @@ platform_err: cpu_dai->driver->ops->hw_free(substream, cpu_dai); interface_err: - if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); + i = rtd->num_codecs; codec_err: + while (--i >= 0) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) + codec_dai->driver->ops->hw_free(substream, codec_dai); + codec_dai->rate = 0; + } + if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) rtd->dai_link->ops->hw_free(substream); @@ -751,8 +913,9 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; bool playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -763,16 +926,22 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) cpu_dai->sample_bits = 0; } - if (codec_dai->active == 1) { - codec_dai->rate = 0; - codec_dai->channels = 0; - codec_dai->sample_bits = 0; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->active == 1) { + codec_dai->rate = 0; + codec_dai->channels = 0; + codec_dai->sample_bits = 0; + } } /* apply codec digital mute */ - if ((playback && codec_dai->playback_active == 1) || - (!playback && codec_dai->capture_active == 1)) - snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); + for (i = 0; i < rtd->num_codecs; i++) { + if ((playback && rtd->codec_dais[i]->playback_active == 1) || + (!playback && rtd->codec_dais[i]->capture_active == 1)) + snd_soc_dai_digital_mute(rtd->codec_dais[i], 1, + substream->stream); + } /* free any machine hw params */ if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) @@ -783,8 +952,11 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) platform->driver->ops->hw_free(substream); /* now free hw params for the DAIs */ - if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->hw_free) + codec_dai->driver->ops->hw_free(substream, codec_dai); + } if (cpu_dai->driver->ops && cpu_dai->driver->ops->hw_free) cpu_dai->driver->ops->hw_free(substream, cpu_dai); @@ -798,13 +970,17 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - if (codec_dai->driver->ops && codec_dai->driver->ops->trigger) { - ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai); - if (ret < 0) - return ret; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->trigger) { + ret = codec_dai->driver->ops->trigger(substream, + cmd, codec_dai); + if (ret < 0) + return ret; + } } if (platform->driver->ops && platform->driver->ops->trigger) { @@ -834,14 +1010,18 @@ static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; - - if (codec_dai->driver->ops && - codec_dai->driver->ops->bespoke_trigger) { - ret = codec_dai->driver->ops->bespoke_trigger(substream, cmd, codec_dai); - if (ret < 0) - return ret; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && + codec_dai->driver->ops->bespoke_trigger) { + ret = codec_dai->driver->ops->bespoke_trigger(substream, + cmd, codec_dai); + if (ret < 0) + return ret; + } } if (platform->driver->bespoke_trigger) { @@ -867,10 +1047,12 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_uframes_t offset = 0; snd_pcm_sframes_t delay = 0; + snd_pcm_sframes_t codec_delay = 0; + int i; if (platform->driver->ops && platform->driver->ops->pointer) offset = platform->driver->ops->pointer(substream); @@ -878,11 +1060,21 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) if (cpu_dai->driver->ops && cpu_dai->driver->ops->delay) delay += cpu_dai->driver->ops->delay(substream, cpu_dai); - if (codec_dai->driver->ops && codec_dai->driver->ops->delay) - delay += codec_dai->driver->ops->delay(substream, codec_dai); + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops && codec_dai->driver->ops->delay) + codec_delay = max(codec_delay, + codec_dai->driver->ops->delay(substream, + codec_dai)); + } + delay += codec_delay; + /* + * None of the existing platform drivers implement delay(), so + * for now the codec_dai of first multicodec entry is used + */ if (platform->driver->delay) - delay += platform->driver->delay(substream, codec_dai); + delay += platform->driver->delay(substream, rtd->codec_dais[0]); runtime->delay = delay; @@ -985,7 +1177,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, struct snd_soc_dapm_widget *widget, int stream) { struct snd_soc_pcm_runtime *be; - int i; + int i, j; if (stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < card->num_links; i++) { @@ -994,9 +1186,14 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, if (!be->dai_link->no_pcm) continue; - if (be->cpu_dai->playback_widget == widget || - be->codec_dai->playback_widget == widget) + if (be->cpu_dai->playback_widget == widget) return be; + + for (j = 0; j < be->num_codecs; j++) { + struct snd_soc_dai *dai = be->codec_dais[j]; + if (dai->playback_widget == widget) + return be; + } } } else { @@ -1006,9 +1203,14 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, if (!be->dai_link->no_pcm) continue; - if (be->cpu_dai->capture_widget == widget || - be->codec_dai->capture_widget == widget) + if (be->cpu_dai->capture_widget == widget) return be; + + for (j = 0; j < be->num_codecs; j++) { + struct snd_soc_dai *dai = be->codec_dais[j]; + if (dai->capture_widget == widget) + return be; + } } } @@ -1071,6 +1273,7 @@ static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, /* Destroy any old FE <--> BE connections */ list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + unsigned int i; /* is there a valid CPU DAI widget for this BE */ widget = dai_get_widget(dpcm->be->cpu_dai, stream); @@ -1080,11 +1283,14 @@ static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, continue; /* is there a valid CODEC DAI widget for this BE */ - widget = dai_get_widget(dpcm->be->codec_dai, stream); + for (i = 0; i < dpcm->be->num_codecs; i++) { + struct snd_soc_dai *dai = dpcm->be->codec_dais[i]; + widget = dai_get_widget(dai, stream); - /* prune the BE if it's no longer in our active list */ - if (widget && widget_in_list(list, widget)) - continue; + /* prune the BE if it's no longer in our active list */ + if (widget && widget_in_list(list, widget)) + continue; + } dev_dbg(fe->dev, "ASoC: pruning %s BE %s for %s\n", stream ? "capture" : "playback", @@ -2069,6 +2275,7 @@ int soc_dpcm_runtime_update(struct snd_soc_card *card) dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); } + dpcm_path_put(&list); capture: /* skip if FE doesn't have capture capability */ if (!fe->cpu_dai->driver->capture.channels_min) @@ -2113,16 +2320,22 @@ int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) list_for_each_entry(dpcm, clients, list_be) { struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_soc_dai *dai = be->codec_dai; - struct snd_soc_dai_driver *drv = dai->driver; + int i; if (be->dai_link->ignore_suspend) continue; - dev_dbg(be->dev, "ASoC: BE digital mute %s\n", be->dai_link->name); + for (i = 0; i < be->num_codecs; i++) { + struct snd_soc_dai *dai = be->codec_dais[i]; + struct snd_soc_dai_driver *drv = dai->driver; + + dev_dbg(be->dev, "ASoC: BE digital mute %s\n", + be->dai_link->name); - if (drv->ops && drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, mute); + if (drv->ops && drv->ops->digital_mute && + dai->playback_active) + drv->ops->digital_mute(dai, mute); + } } return 0; @@ -2187,22 +2400,27 @@ static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0; + int i; if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { playback = rtd->dai_link->dpcm_playback; capture = rtd->dai_link->dpcm_capture; } else { - if (codec_dai->driver->playback.channels_min && - cpu_dai->driver->playback.channels_min) - playback = 1; - if (codec_dai->driver->capture.channels_min && - cpu_dai->driver->capture.channels_min) - capture = 1; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->playback.channels_min) + playback = 1; + if (codec_dai->driver->capture.channels_min) + capture = 1; + } + + capture = capture && cpu_dai->driver->capture.channels_min; + playback = playback && cpu_dai->driver->playback.channels_min; } if (rtd->dai_link->playback_only) { @@ -2228,7 +2446,9 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->dai_link->stream_name); else snprintf(new_name, sizeof(new_name), "%s %s-%d", - rtd->dai_link->stream_name, codec_dai->name, num); + rtd->dai_link->stream_name, + (rtd->num_codecs > 1) ? + "multicodec" : rtd->codec_dai->name, num); ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, capture, &pcm); @@ -2301,8 +2521,9 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) pcm->private_free = platform->driver->pcm_free; out: - dev_info(rtd->card->dev, "%s <-> %s mapping ok\n", codec_dai->name, - cpu_dai->name); + dev_info(rtd->card->dev, "%s <-> %s mapping ok\n", + (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name, + cpu_dai->name); return ret; } diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c index 02734bd4f09b..a83aff09dce2 100644 --- a/sound/soc/tegra/tegra_alc5632.c +++ b/sound/soc/tegra/tegra_alc5632.c @@ -41,8 +41,7 @@ static int tegra_alc5632_asoc_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_card *card = codec->card; + struct snd_soc_card *card = rtd->card; struct tegra_alc5632 *alc5632 = snd_soc_card_get_drvdata(card); int srate, mclk; int err; @@ -105,7 +104,7 @@ static int tegra_alc5632_asoc_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_codec *codec = codec_dai->codec; struct snd_soc_dapm_context *dapm = &codec->dapm; - struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(codec->card); + struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(rtd->card); snd_soc_jack_new(codec, "Headset Jack", SND_JACK_HEADSET, &tegra_alc5632_hs_jack); diff --git a/sound/soc/tegra/tegra_max98090.c b/sound/soc/tegra/tegra_max98090.c index ce73e1f62c4b..b86cd9936ef1 100644 --- a/sound/soc/tegra/tegra_max98090.c +++ b/sound/soc/tegra/tegra_max98090.c @@ -49,8 +49,7 @@ static int tegra_max98090_asoc_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_card *card = codec->card; + struct snd_soc_card *card = rtd->card; struct tegra_max98090 *machine = snd_soc_card_get_drvdata(card); int srate, mclk; int err; @@ -127,7 +126,7 @@ static int tegra_max98090_asoc_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_codec *codec = codec_dai->codec; - struct tegra_max98090 *machine = snd_soc_card_get_drvdata(codec->card); + struct tegra_max98090 *machine = snd_soc_card_get_drvdata(rtd->card); if (gpio_is_valid(machine->gpio_hp_det)) { snd_soc_jack_new(codec, "Headphones", SND_JACK_HEADPHONE, diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c index 4feb16a99e02..a6898831fb9f 100644 --- a/sound/soc/tegra/tegra_rt5640.c +++ b/sound/soc/tegra/tegra_rt5640.c @@ -51,8 +51,7 @@ static int tegra_rt5640_asoc_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_card *card = codec->card; + struct snd_soc_card *card = rtd->card; struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); int srate, mclk; int err; @@ -110,7 +109,7 @@ static int tegra_rt5640_asoc_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_codec *codec = codec_dai->codec; - struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(codec->card); + struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(rtd->card); snd_soc_jack_new(codec, "Headphones", SND_JACK_HEADPHONE, &tegra_rt5640_hp_jack); diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c index 8e774d1a243c..769e28f6642e 100644 --- a/sound/soc/tegra/tegra_wm8753.c +++ b/sound/soc/tegra/tegra_wm8753.c @@ -55,8 +55,7 @@ static int tegra_wm8753_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_card *card = codec->card; + struct snd_soc_card *card = rtd->card; struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card); int srate, mclk; int err; diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c index 0939661df60b..86e05e938585 100644 --- a/sound/soc/tegra/tegra_wm8903.c +++ b/sound/soc/tegra/tegra_wm8903.c @@ -60,8 +60,7 @@ static int tegra_wm8903_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_card *card = codec->card; + struct snd_soc_card *card = rtd->card; struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); int srate, mclk; int err; @@ -173,7 +172,7 @@ static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_codec *codec = codec_dai->codec; struct snd_soc_dapm_context *dapm = &codec->dapm; - struct snd_soc_card *card = codec->card; + struct snd_soc_card *card = rtd->card; struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); if (gpio_is_valid(machine->gpio_hp_det)) { diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c index 734bfcd21148..589d2d9b553a 100644 --- a/sound/soc/tegra/trimslice.c +++ b/sound/soc/tegra/trimslice.c @@ -50,8 +50,7 @@ static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = codec_dai->codec; - struct snd_soc_card *card = codec->card; + struct snd_soc_card *card = rtd->card; struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); int srate, mclk; int err; diff --git a/sound/synth/emux/soundfont.c b/sound/synth/emux/soundfont.c index 1137b85c36e6..78683b2064f7 100644 --- a/sound/synth/emux/soundfont.c +++ b/sound/synth/emux/soundfont.c @@ -1021,6 +1021,7 @@ load_guspatch(struct snd_sf_list *sflist, const char __user *data, data, count); if (rc < 0) { sf_sample_delete(sflist, sf, smp); + kfree(zone); return rc; } /* memory offset is updated after */ diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index e05a86b7c0da..d393153c474f 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -147,5 +147,18 @@ config SND_USB_HIFACE To compile this driver as a module, choose M here: the module will be called snd-usb-hiface. +config SND_BCD2000 + tristate "Behringer BCD2000 MIDI driver" + select SND_RAWMIDI + help + Say Y here to include MIDI support for the Behringer BCD2000 DJ + controller. + + Audio support is still work-in-progress at + https://github.com/anyc/snd-usb-bcd2000 + + To compile this driver as a module, choose M here: the module + will be called snd-bcd2000. + endif # SND_USB diff --git a/sound/usb/Makefile b/sound/usb/Makefile index abe668f660d1..2b92f0dcbc4c 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile new file mode 100644 index 000000000000..f09ccc0af6ff --- /dev/null +++ b/sound/usb/bcd2000/Makefile @@ -0,0 +1,3 @@ +snd-bcd2000-y := bcd2000.o + +obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o
\ No newline at end of file diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c new file mode 100644 index 000000000000..820d6ca8c458 --- /dev/null +++ b/sound/usb/bcd2000/bcd2000.c @@ -0,0 +1,461 @@ +/* + * Behringer BCD2000 driver + * + * Copyright (C) 2014 Mario Kicherer (dev@kicherer.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/bitmap.h> +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> + +#define PREFIX "snd-bcd2000: " +#define BUFSIZE 64 + +static struct usb_device_id id_table[] = { + { USB_DEVICE(0x1397, 0x00bd) }, + { }, +}; + +static unsigned char device_cmd_prefix[] = {0x03, 0x00}; + +static unsigned char bcd2000_init_sequence[] = { + 0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81, + 0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7, + 0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7, + 0x18, 0xfa, 0x11, 0xff +}; + +struct bcd2000 { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *intf; + int card_index; + + int midi_out_active; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_receive_substream; + struct snd_rawmidi_substream *midi_out_substream; + + unsigned char midi_in_buf[BUFSIZE]; + unsigned char midi_out_buf[BUFSIZE]; + + struct urb *midi_out_urb; + struct urb *midi_in_urb; + + struct usb_anchor anchor; +}; + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; + +static DEFINE_MUTEX(devices_mutex); +DECLARE_BITMAP(devices_used, SNDRV_CARDS); +static struct usb_driver bcd2000_driver; + +#ifdef CONFIG_SND_DEBUG +static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) +{ + print_hex_dump(KERN_DEBUG, prefix, + DUMP_PREFIX_NONE, 16, 1, + buf, len, false); +} +#else +static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {} +#endif + +static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/* (de)register midi substream from client */ +static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct bcd2000 *bcd2k = substream->rmidi->private_data; + bcd2k->midi_receive_substream = up ? substream : NULL; +} + +static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k, + const unsigned char *buf, unsigned int buf_len) +{ + unsigned int payload_length, tocopy; + struct snd_rawmidi_substream *midi_receive_substream; + + midi_receive_substream = ACCESS_ONCE(bcd2k->midi_receive_substream); + if (!midi_receive_substream) + return; + + bcd2000_dump_buffer(PREFIX "received from device: ", buf, buf_len); + + if (buf_len < 2) + return; + + payload_length = buf[0]; + + /* ignore packets without payload */ + if (payload_length == 0) + return; + + tocopy = min(payload_length, buf_len-1); + + bcd2000_dump_buffer(PREFIX "sending to userspace: ", + &buf[1], tocopy); + + snd_rawmidi_receive(midi_receive_substream, + &buf[1], tocopy); +} + +static void bcd2000_midi_send(struct bcd2000 *bcd2k) +{ + int len, ret; + struct snd_rawmidi_substream *midi_out_substream; + + BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE); + + midi_out_substream = ACCESS_ONCE(bcd2k->midi_out_substream); + if (!midi_out_substream) + return; + + /* copy command prefix bytes */ + memcpy(bcd2k->midi_out_buf, device_cmd_prefix, + sizeof(device_cmd_prefix)); + + /* + * get MIDI packet and leave space for command prefix + * and payload length + */ + len = snd_rawmidi_transmit(midi_out_substream, + bcd2k->midi_out_buf + 3, BUFSIZE - 3); + + if (len < 0) + dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n", + __func__, len); + + if (len <= 0) + return; + + /* set payload length */ + bcd2k->midi_out_buf[2] = len; + bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE; + + bcd2000_dump_buffer(PREFIX "sending to device: ", + bcd2k->midi_out_buf, len+3); + + /* send packet to the BCD2000 */ + ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n", + __func__, midi_out_substream, ret, len); + else + bcd2k->midi_out_active = 1; +} + +static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct bcd2000 *bcd2k = substream->rmidi->private_data; + + if (bcd2k->midi_out_active) { + usb_kill_urb(bcd2k->midi_out_urb); + bcd2k->midi_out_active = 0; + } + + return 0; +} + +/* (de)register midi substream from client */ +static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct bcd2000 *bcd2k = substream->rmidi->private_data; + + if (up) { + bcd2k->midi_out_substream = substream; + /* check if there is data userspace wants to send */ + if (!bcd2k->midi_out_active) + bcd2000_midi_send(bcd2k); + } else { + bcd2k->midi_out_substream = NULL; + } +} + +static void bcd2000_output_complete(struct urb *urb) +{ + struct bcd2000 *bcd2k = urb->context; + + bcd2k->midi_out_active = 0; + + if (urb->status) + dev_warn(&urb->dev->dev, + PREFIX "output urb->status: %d\n", urb->status); + + if (urb->status == -ESHUTDOWN) + return; + + /* check if there is more data userspace wants to send */ + bcd2000_midi_send(bcd2k); +} + +static void bcd2000_input_complete(struct urb *urb) +{ + int ret; + struct bcd2000 *bcd2k = urb->context; + + if (urb->status) + dev_warn(&urb->dev->dev, + PREFIX "input urb->status: %i\n", urb->status); + + if (!bcd2k || urb->status == -ESHUTDOWN) + return; + + if (urb->actual_length > 0) + bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer, + urb->actual_length); + + /* return URB to device */ + ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s: usb_submit_urb() failed, ret=%d\n", + __func__, ret); +} + +static struct snd_rawmidi_ops bcd2000_midi_output = { + .open = bcd2000_midi_output_open, + .close = bcd2000_midi_output_close, + .trigger = bcd2000_midi_output_trigger, +}; + +static struct snd_rawmidi_ops bcd2000_midi_input = { + .open = bcd2000_midi_input_open, + .close = bcd2000_midi_input_close, + .trigger = bcd2000_midi_input_trigger, +}; + +static void bcd2000_init_device(struct bcd2000 *bcd2k) +{ + int ret; + + init_usb_anchor(&bcd2k->anchor); + usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor); + usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor); + + /* copy init sequence into buffer */ + memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52); + bcd2k->midi_out_urb->transfer_buffer_length = 52; + + /* submit sequence */ + ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s: usb_submit_urb() out failed, ret=%d: ", + __func__, ret); + else + bcd2k->midi_out_active = 1; + + /* pass URB to device to enable button and controller events */ + ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s: usb_submit_urb() in failed, ret=%d: ", + __func__, ret); + + /* ensure initialization is finished */ + usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000); +} + +static int bcd2000_init_midi(struct bcd2000 *bcd2k) +{ + int ret; + struct snd_rawmidi *rmidi; + + ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0, + 1, /* output */ + 1, /* input */ + &rmidi); + + if (ret < 0) + return ret; + + strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name)); + + rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = bcd2k; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &bcd2000_midi_output); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &bcd2000_midi_input); + + bcd2k->rmidi = rmidi; + + bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL); + bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) { + dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n"); + return -ENOMEM; + } + + usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev, + usb_rcvintpipe(bcd2k->dev, 0x81), + bcd2k->midi_in_buf, BUFSIZE, + bcd2000_input_complete, bcd2k, 1); + + usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev, + usb_sndintpipe(bcd2k->dev, 0x1), + bcd2k->midi_out_buf, BUFSIZE, + bcd2000_output_complete, bcd2k, 1); + + bcd2000_init_device(bcd2k); + + return 0; +} + +static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k, + struct usb_interface *interface) +{ + /* usb_kill_urb not necessary, urb is aborted automatically */ + + usb_free_urb(bcd2k->midi_out_urb); + usb_free_urb(bcd2k->midi_in_urb); + + if (bcd2k->intf) { + usb_set_intfdata(bcd2k->intf, NULL); + bcd2k->intf = NULL; + } +} + +static int bcd2000_probe(struct usb_interface *interface, + const struct usb_device_id *usb_id) +{ + struct snd_card *card; + struct bcd2000 *bcd2k; + unsigned int card_index; + char usb_path[32]; + int err; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) + if (!test_bit(card_index, devices_used)) + break; + + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return -ENOENT; + } + + err = snd_card_new(&interface->dev, index[card_index], id[card_index], + THIS_MODULE, sizeof(*bcd2k), &card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return err; + } + + bcd2k = card->private_data; + bcd2k->dev = interface_to_usbdev(interface); + bcd2k->card = card; + bcd2k->card_index = card_index; + bcd2k->intf = interface; + + snd_card_set_dev(card, &interface->dev); + + strncpy(card->driver, "snd-bcd2000", sizeof(card->driver)); + strncpy(card->shortname, "BCD2000", sizeof(card->shortname)); + usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path)); + snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname), + "Behringer BCD2000 at %s", + usb_path); + + err = bcd2000_init_midi(bcd2k); + if (err < 0) + goto probe_error; + + err = snd_card_register(card); + if (err < 0) + goto probe_error; + + usb_set_intfdata(interface, bcd2k); + set_bit(card_index, devices_used); + + mutex_unlock(&devices_mutex); + return 0; + +probe_error: + dev_info(&bcd2k->dev->dev, PREFIX "error during probing"); + bcd2000_free_usb_related_resources(bcd2k, interface); + snd_card_free(card); + mutex_unlock(&devices_mutex); + return err; +} + +static void bcd2000_disconnect(struct usb_interface *interface) +{ + struct bcd2000 *bcd2k = usb_get_intfdata(interface); + + if (!bcd2k) + return; + + mutex_lock(&devices_mutex); + + /* make sure that userspace cannot create new requests */ + snd_card_disconnect(bcd2k->card); + + bcd2000_free_usb_related_resources(bcd2k, interface); + + clear_bit(bcd2k->card_index, devices_used); + + snd_card_free_when_closed(bcd2k->card); + + mutex_unlock(&devices_mutex); +} + +static struct usb_driver bcd2000_driver = { + .name = "snd-bcd2000", + .probe = bcd2000_probe, + .disconnect = bcd2000_disconnect, + .id_table = id_table, +}; + +module_usb_driver(bcd2000_driver); + +MODULE_DEVICE_TABLE(usb, id_table); +MODULE_AUTHOR("Mario Kicherer, dev@kicherer.org"); +MODULE_DESCRIPTION("Behringer BCD2000 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/card.c b/sound/usb/card.c index c3b5b7dca1c3..a09e5f3519e3 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -307,6 +307,11 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) static int snd_usb_audio_free(struct snd_usb_audio *chip) { + struct list_head *p, *n; + + list_for_each_safe(p, n, &chip->ep_list) + snd_usb_endpoint_free(p); + mutex_destroy(&chip->mutex); kfree(chip); return 0; @@ -585,7 +590,7 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, struct snd_usb_audio *chip) { struct snd_card *card; - struct list_head *p, *n; + struct list_head *p; if (chip == (void *)-1L) return; @@ -598,14 +603,16 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, mutex_lock(®ister_mutex); chip->num_interfaces--; if (chip->num_interfaces <= 0) { + struct snd_usb_endpoint *ep; + snd_card_disconnect(card); /* release the pcm resources */ list_for_each(p, &chip->pcm_list) { snd_usb_stream_disconnect(p); } /* release the endpoint resources */ - list_for_each_safe(p, n, &chip->ep_list) { - snd_usb_endpoint_free(p); + list_for_each_entry(ep, &chip->ep_list, list) { + snd_usb_endpoint_release(ep); } /* release the midi resources */ list_for_each(p, &chip->midi_list) { diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 289f582c9130..114e3e7ff511 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -987,19 +987,30 @@ void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep) } /** + * snd_usb_endpoint_release: Tear down an snd_usb_endpoint + * + * @ep: the endpoint to release + * + * This function does not care for the endpoint's use count but will tear + * down all the streaming URBs immediately. + */ +void snd_usb_endpoint_release(struct snd_usb_endpoint *ep) +{ + release_urbs(ep, 1); +} + +/** * snd_usb_endpoint_free: Free the resources of an snd_usb_endpoint * * @ep: the list header of the endpoint to free * - * This function does not care for the endpoint's use count but will tear - * down all the streaming URBs immediately and free all resources. + * This free all resources of the given ep. */ void snd_usb_endpoint_free(struct list_head *head) { struct snd_usb_endpoint *ep; ep = list_entry(head, struct snd_usb_endpoint, list); - release_urbs(ep, 1); kfree(ep); } diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h index 1c7e8ee48abc..e61ee5c356a3 100644 --- a/sound/usb/endpoint.h +++ b/sound/usb/endpoint.h @@ -23,6 +23,7 @@ void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep); void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep); int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep); void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_release(struct snd_usb_endpoint *ep); void snd_usb_endpoint_free(struct list_head *head); int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep); diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index d40a2850e270..0b728d886f0d 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -162,7 +162,7 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid, { const struct usbmix_selector_map *p; - if (! state->selector_map) + if (!state->selector_map) return 0; for (p = state->selector_map; p->id; p++) { if (p->id == unitid && index < p->count) @@ -174,7 +174,8 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid, /* * find an audio control unit with the given unit id */ -static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit) +static void *find_audio_control_unit(struct mixer_build *state, + unsigned char unit) { /* we just parse the header */ struct uac_feature_unit_descriptor *hdr = NULL; @@ -194,7 +195,8 @@ static void *find_audio_control_unit(struct mixer_build *state, unsigned char un /* * copy a string with the given id */ -static int snd_usb_copy_string_desc(struct mixer_build *state, int index, char *buf, int maxlen) +static int snd_usb_copy_string_desc(struct mixer_build *state, + int index, char *buf, int maxlen) { int len = usb_string(state->chip->dev, index, buf, maxlen - 1); buf[len] = 0; @@ -253,7 +255,7 @@ static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val) static int get_relative_value(struct usb_mixer_elem_info *cval, int val) { - if (! cval->res) + if (!cval->res) cval->res = 1; if (val < cval->min) return 0; @@ -267,7 +269,7 @@ static int get_abs_value(struct usb_mixer_elem_info *cval, int val) { if (val < 0) return cval->min; - if (! cval->res) + if (!cval->res) cval->res = 1; val *= cval->res; val += cval->min; @@ -281,7 +283,8 @@ static int get_abs_value(struct usb_mixer_elem_info *cval, int val) * retrieve a mixer value */ -static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) +static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, + int validx, int *value_ret) { struct snd_usb_audio *chip = cval->mixer->chip; unsigned char buf[2]; @@ -292,6 +295,7 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int v err = snd_usb_autoresume(cval->mixer->chip); if (err < 0) return -EIO; + down_read(&chip->shutdown_rwsem); while (timeout-- > 0) { if (chip->shutdown) @@ -316,10 +320,11 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, int v return err; } -static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) +static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, + int validx, int *value_ret) { struct snd_usb_audio *chip = cval->mixer->chip; - unsigned char buf[2 + 3*sizeof(__u16)]; /* enough space for one range */ + unsigned char buf[2 + 3 * sizeof(__u16)]; /* enough space for one range */ unsigned char *val; int idx = 0, ret, size; __u8 bRequest; @@ -339,9 +344,9 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v goto error; down_read(&chip->shutdown_rwsem); - if (chip->shutdown) + if (chip->shutdown) { ret = -ENODEV; - else { + } else { idx = snd_usb_ctrl_intf(chip) | (cval->id << 8); ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, @@ -382,7 +387,8 @@ error: return 0; } -static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int validx, int *value_ret) +static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, + int validx, int *value_ret) { validx += cval->idx_off; @@ -391,7 +397,8 @@ static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, int vali get_ctl_value_v2(cval, request, validx, value_ret); } -static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int *value) +static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, + int validx, int *value) { return get_ctl_value(cval, UAC_GET_CUR, validx, value); } @@ -400,7 +407,9 @@ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int * static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, int channel, int *value) { - return get_ctl_value(cval, UAC_GET_CUR, (cval->control << 8) | channel, value); + return get_ctl_value(cval, UAC_GET_CUR, + (cval->control << 8) | channel, + value); } static int get_cur_mix_value(struct usb_mixer_elem_info *cval, @@ -417,7 +426,7 @@ static int get_cur_mix_value(struct usb_mixer_elem_info *cval, if (!cval->mixer->ignore_ctl_error) usb_audio_dbg(cval->mixer->chip, "cannot get current value for control %d ch %d: err = %d\n", - cval->control, channel, err); + cval->control, channel, err); return err; } cval->cached |= 1 << channel; @@ -425,7 +434,6 @@ static int get_cur_mix_value(struct usb_mixer_elem_info *cval, return 0; } - /* * set a mixer value */ @@ -474,7 +482,7 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, } } usb_audio_dbg(chip, "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n", - request, validx, idx, cval->val_type, buf[0], buf[1]); + request, validx, idx, cval->val_type, buf[0], buf[1]); err = -EINVAL; out: @@ -483,7 +491,8 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, return err; } -static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, int validx, int value) +static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, + int validx, int value) { return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); } @@ -503,8 +512,9 @@ static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, return 0; } - err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel, - value); + err = snd_usb_mixer_set_ctl_value(cval, + UAC_SET_CUR, (cval->control << 8) | channel, + value); if (err < 0) return err; cval->cached |= 1 << channel; @@ -541,13 +551,13 @@ static int parse_audio_unit(struct mixer_build *state, int unitid); * check if the input/output channel routing is enabled on the given bitmap. * used for mixer unit parser */ -static int check_matrix_bitmap(unsigned char *bmap, int ich, int och, int num_outs) +static int check_matrix_bitmap(unsigned char *bmap, + int ich, int och, int num_outs) { int idx = ich * num_outs + och; return bmap[idx >> 3] & (0x80 >> (idx & 7)); } - /* * add an alsa control element * search and increment the index until an empty slot is found. @@ -564,7 +574,8 @@ int snd_usb_mixer_add_control(struct usb_mixer_interface *mixer, while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) kctl->id.index++; if ((err = snd_ctl_add(mixer->chip->card, kctl)) < 0) { - usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", err); + usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", + err); return err; } cval->elem_id = &kctl->id; @@ -573,7 +584,6 @@ int snd_usb_mixer_add_control(struct usb_mixer_interface *mixer, return 0; } - /* * get a terminal name string */ @@ -627,7 +637,8 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm struct iterm_name_combo *names; if (iterm->name) - return snd_usb_copy_string_desc(state, iterm->name, name, maxlen); + return snd_usb_copy_string_desc(state, iterm->name, + name, maxlen); /* virtual type - not a real terminal */ if (iterm->type >> 16) { @@ -635,13 +646,17 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm return 0; switch (iterm->type >> 16) { case UAC_SELECTOR_UNIT: - strcpy(name, "Selector"); return 8; + strcpy(name, "Selector"); + return 8; case UAC1_PROCESSING_UNIT: - strcpy(name, "Process Unit"); return 12; + strcpy(name, "Process Unit"); + return 12; case UAC1_EXTENSION_UNIT: - strcpy(name, "Ext Unit"); return 8; + strcpy(name, "Ext Unit"); + return 8; case UAC_MIXER_UNIT: - strcpy(name, "Mixer"); return 5; + strcpy(name, "Mixer"); + return 5; default: return sprintf(name, "Unit %d", iterm->id); } @@ -649,29 +664,35 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm switch (iterm->type & 0xff00) { case 0x0100: - strcpy(name, "PCM"); return 3; + strcpy(name, "PCM"); + return 3; case 0x0200: - strcpy(name, "Mic"); return 3; + strcpy(name, "Mic"); + return 3; case 0x0400: - strcpy(name, "Headset"); return 7; + strcpy(name, "Headset"); + return 7; case 0x0500: - strcpy(name, "Phone"); return 5; + strcpy(name, "Phone"); + return 5; } - for (names = iterm_names; names->type; names++) + for (names = iterm_names; names->type; names++) { if (names->type == iterm->type) { strcpy(name, names->name); return strlen(names->name); } + } + return 0; } - /* * parse the source unit recursively until it reaches to a terminal * or a branched unit. */ -static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term) +static int check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term) { int err; void *p1; @@ -766,7 +787,6 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_ return -ENODEV; } - /* * Feature Unit */ @@ -794,7 +814,6 @@ static struct usb_feature_control_info audio_feature_info[] = { { "Phase Inverter Control", USB_MIXER_BOOLEAN }, }; - /* private_free callback */ static void usb_mixer_elem_free(struct snd_kcontrol *kctl) { @@ -802,7 +821,6 @@ static void usb_mixer_elem_free(struct snd_kcontrol *kctl) kctl->private_data = NULL; } - /* * interface to ALSA control for feature/mixer units */ @@ -906,7 +924,6 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, cval->res = 384; } break; - } } @@ -939,21 +956,26 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { usb_audio_err(cval->mixer->chip, "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->id, snd_usb_ctrl_intf(cval->mixer->chip), cval->control, cval->id); + cval->id, snd_usb_ctrl_intf(cval->mixer->chip), + cval->control, cval->id); return -EINVAL; } - if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) { + if (get_ctl_value(cval, UAC_GET_RES, + (cval->control << 8) | minchn, + &cval->res) < 0) { cval->res = 1; } else { int last_valid_res = cval->res; while (cval->res > 1) { if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, - (cval->control << 8) | minchn, cval->res / 2) < 0) + (cval->control << 8) | minchn, + cval->res / 2) < 0) break; cval->res /= 2; } - if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, &cval->res) < 0) + if (get_ctl_value(cval, UAC_GET_RES, + (cval->control << 8) | minchn, &cval->res) < 0) cval->res = last_valid_res; } if (cval->res == 0) @@ -1017,7 +1039,8 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) /* get a feature/mixer unit info */ -static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { struct usb_mixer_elem_info *cval = kcontrol->private_data; @@ -1051,7 +1074,8 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_ } /* get the current value from feature/mixer unit */ -static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = kcontrol->private_data; int c, cnt, val, err; @@ -1082,7 +1106,8 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct snd_ctl_e } /* put the current value to feature/mixer unit */ -static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = kcontrol->private_data; int c, cnt, val, oval, err; @@ -1136,22 +1161,25 @@ static struct snd_kcontrol_new usb_feature_unit_ctl_ro = { .put = NULL, }; -/* This symbol is exported in order to allow the mixer quirks to - * hook up to the standard feature unit control mechanism */ +/* + * This symbol is exported in order to allow the mixer quirks to + * hook up to the standard feature unit control mechanism + */ struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl; /* * build a feature control */ - static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) { return strlcat(kctl->id.name, str, sizeof(kctl->id.name)); } -/* A lot of headsets/headphones have a "Speaker" mixer. Make sure we - rename it to "Headphone". We determine if something is a headphone - similar to how udev determines form factor. */ +/* + * A lot of headsets/headphones have a "Speaker" mixer. Make sure we + * rename it to "Headphone". We determine if something is a headphone + * similar to how udev determines form factor. + */ static void check_no_speaker_on_headset(struct snd_kcontrol *kctl, struct snd_card *card) { @@ -1201,10 +1229,8 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, return; cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + if (!cval) return; - } cval->mixer = state->mixer; cval->id = unitid; cval->control = control; @@ -1222,15 +1248,17 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, cval->ch_readonly = readonly_mask; } - /* if all channels in the mask are marked read-only, make the control + /* + * If all channels in the mask are marked read-only, make the control * read-only. set_cur_mix_value() will check the mask again and won't - * issue write commands to read-only channels. */ + * issue write commands to read-only channels. + */ if (cval->channels == readonly_mask) kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval); else kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); - if (! kctl) { + if (!kctl) { usb_audio_err(state->chip, "cannot malloc kcontrol\n"); kfree(cval); return; @@ -1239,48 +1267,53 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; - if (! len && nameid) + if (!len && nameid) len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); switch (control) { case UAC_FU_MUTE: case UAC_FU_VOLUME: - /* determine the control name. the rule is: + /* + * determine the control name. the rule is: * - if a name id is given in descriptor, use it. * - if the connected input can be determined, then use the name * of terminal type. * - if the connected output can be determined, use it. * - otherwise, anonymous name. */ - if (! len) { - len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1); - if (! len) - len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1); - if (! len) - len = snprintf(kctl->id.name, sizeof(kctl->id.name), + if (!len) { + len = get_term_name(state, iterm, kctl->id.name, + sizeof(kctl->id.name), 1); + if (!len) + len = get_term_name(state, &state->oterm, + kctl->id.name, + sizeof(kctl->id.name), 1); + if (!len) + len = snprintf(kctl->id.name, + sizeof(kctl->id.name), "Feature %d", unitid); } if (!mapped_name) check_no_speaker_on_headset(kctl, state->mixer->chip->card); - /* determine the stream direction: + /* + * determine the stream direction: * if the connected output is USB stream, then it's likely a * capture stream. otherwise it should be playback (hopefully :) */ - if (! mapped_name && ! (state->oterm.type >> 16)) { - if ((state->oterm.type & 0xff00) == 0x0100) { + if (!mapped_name && !(state->oterm.type >> 16)) { + if ((state->oterm.type & 0xff00) == 0x0100) len = append_ctl_name(kctl, " Capture"); - } else { + else len = append_ctl_name(kctl, " Playback"); - } } append_ctl_name(kctl, control == UAC_FU_MUTE ? " Switch" : " Volume"); break; default: - if (! len) + if (!len) strlcpy(kctl->id.name, audio_feature_info[control-1].name, sizeof(kctl->id.name)); break; @@ -1300,33 +1333,35 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } range = (cval->max - cval->min) / cval->res; - /* Are there devices with volume range more than 255? I use a bit more + /* + * Are there devices with volume range more than 255? I use a bit more * to be sure. 384 is a resolution magic number found on Logitech * devices. It will definitively catch all buggy Logitech devices. */ if (range > 384) { - usb_audio_warn(state->chip, "Warning! Unlikely big " - "volume range (=%u), cval->res is probably wrong.", - range); + usb_audio_warn(state->chip, + "Warning! Unlikely big volume range (=%u), " + "cval->res is probably wrong.", + range); usb_audio_warn(state->chip, "[%d] FU [%s] ch = %d, " - "val = %d/%d/%d", cval->id, - kctl->id.name, cval->channels, - cval->min, cval->max, cval->res); + "val = %d/%d/%d", cval->id, + kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); } usb_audio_dbg(state->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); + cval->id, kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); snd_usb_mixer_add_control(state->mixer, kctl); } - - /* * parse a feature unit * * most of controls are defined here. */ -static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void *_ftr) +static int parse_audio_feature_unit(struct mixer_build *state, int unitid, + void *_ftr) { int channels, i, j; struct usb_audio_term iterm; @@ -1400,15 +1435,25 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void for (i = 0; i < 10; i++) { unsigned int ch_bits = 0; for (j = 0; j < channels; j++) { - unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize); + unsigned int mask; + + mask = snd_usb_combine_bytes(bmaControls + + csize * (j+1), csize); if (mask & (1 << i)) ch_bits |= (1 << j); } /* audio class v1 controls are never read-only */ - if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ - build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, 0); + + /* + * The first channel must be set + * (for ease of programming). + */ + if (ch_bits & 1) + build_feature_ctl(state, _ftr, ch_bits, i, + &iterm, unitid, 0); if (master_bits & (1 << i)) - build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, 0); + build_feature_ctl(state, _ftr, 0, i, &iterm, + unitid, 0); } } else { /* UAC_VERSION_2 */ for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) { @@ -1416,7 +1461,10 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void unsigned int ch_read_only = 0; for (j = 0; j < channels; j++) { - unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize); + unsigned int mask; + + mask = snd_usb_combine_bytes(bmaControls + + csize * (j+1), csize); if (uac2_control_is_readable(mask, i)) { ch_bits |= (1 << j); if (!uac2_control_is_writeable(mask, i)) @@ -1424,12 +1472,22 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void } } - /* NOTE: build_feature_ctl() will mark the control read-only if all channels - * are marked read-only in the descriptors. Otherwise, the control will be - * reported as writeable, but the driver will not actually issue a write - * command for read-only channels */ - if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ - build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, ch_read_only); + /* + * NOTE: build_feature_ctl() will mark the control + * read-only if all channels are marked read-only in + * the descriptors. Otherwise, the control will be + * reported as writeable, but the driver will not + * actually issue a write command for read-only + * channels. + */ + + /* + * The first channel must be set + * (for ease of programming). + */ + if (ch_bits & 1) + build_feature_ctl(state, _ftr, ch_bits, i, + &iterm, unitid, ch_read_only); if (uac2_control_is_readable(master_bits, i)) build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, !uac2_control_is_writeable(master_bits, i)); @@ -1439,7 +1497,6 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void return 0; } - /* * Mixer Unit */ @@ -1450,7 +1507,6 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void * the callbacks are identical with feature unit. * input channel number (zero based) is given in control field instead. */ - static void build_mixer_unit_ctl(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc, int in_pin, int in_ch, int unitid, @@ -1467,7 +1523,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, return; cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) + if (!cval) return; cval->mixer = state->mixer; @@ -1475,7 +1531,9 @@ static void build_mixer_unit_ctl(struct mixer_build *state, cval->control = in_ch + 1; /* based on 1 */ cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) { - if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol), in_ch, i, num_outs)) { + __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); + + if (check_matrix_bitmap(c, in_ch, i, num_outs)) { cval->cmask |= (1 << i); cval->channels++; } @@ -1485,7 +1543,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, get_min_max(cval, 0); kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); - if (! kctl) { + if (!kctl) { usb_audio_err(state->chip, "cannot malloc kcontrol\n"); kfree(cval); return; @@ -1493,9 +1551,10 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kctl->private_free = usb_mixer_elem_free; len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); - if (! len) - len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0); - if (! len) + if (!len) + len = get_term_name(state, iterm, kctl->id.name, + sizeof(kctl->id.name), 0); + if (!len) len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); append_ctl_name(kctl, " Volume"); @@ -1504,24 +1563,28 @@ static void build_mixer_unit_ctl(struct mixer_build *state, snd_usb_mixer_add_control(state->mixer, kctl); } - /* * parse a mixer unit */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *raw_desc) +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, + void *raw_desc) { struct uac_mixer_unit_descriptor *desc = raw_desc; struct usb_audio_term iterm; int input_pins, num_ins, num_outs; int pin, ich, err; - if (desc->bLength < 11 || ! (input_pins = desc->bNrInPins) || ! (num_outs = uac_mixer_unit_bNrChannels(desc))) { - usb_audio_err(state->chip, "invalid MIXER UNIT descriptor %d\n", unitid); + if (desc->bLength < 11 || !(input_pins = desc->bNrInPins) || + !(num_outs = uac_mixer_unit_bNrChannels(desc))) { + usb_audio_err(state->chip, + "invalid MIXER UNIT descriptor %d\n", + unitid); return -EINVAL; } /* no bmControls field (e.g. Maya44) -> ignore */ if (desc->bLength <= 10 + input_pins) { - usb_audio_dbg(state->chip, "MU %d has no bmControls field\n", unitid); + usb_audio_dbg(state->chip, "MU %d has no bmControls field\n", + unitid); return 0; } @@ -1535,12 +1598,14 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *r if (err < 0) return err; num_ins += iterm.channels; - for (; ich < num_ins; ++ich) { + for (; ich < num_ins; ich++) { int och, ich_has_controls = 0; - for (och = 0; och < num_outs; ++och) { - if (check_matrix_bitmap(uac_mixer_unit_bmControls(desc, state->mixer->protocol), - ich, och, num_outs)) { + for (och = 0; och < num_outs; och++) { + __u8 *c = uac_mixer_unit_bmControls(desc, + state->mixer->protocol); + + if (check_matrix_bitmap(c, ich, och, num_outs)) { ich_has_controls = 1; break; } @@ -1553,13 +1618,13 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, void *r return 0; } - /* * Processing Unit / Extension Unit */ /* get callback for processing/extension unit */ -static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = kcontrol->private_data; int err, val; @@ -1577,7 +1642,8 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_ } /* put callback for processing/extension unit */ -static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = kcontrol->private_data; int val, oval, err; @@ -1606,7 +1672,6 @@ static struct snd_kcontrol_new mixer_procunit_ctl = { .put = mixer_ctl_procunit_put, }; - /* * predefined data for processing units */ @@ -1697,10 +1762,13 @@ static struct procunit_info extunits[] = { { USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info }, { 0 } }; + /* * build a processing/extension unit */ -static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw_desc, struct procunit_info *list, char *name) +static int build_audio_procunit(struct mixer_build *state, int unitid, + void *raw_desc, struct procunit_info *list, + char *name) { struct uac_processing_unit_descriptor *desc = raw_desc; int num_ins = desc->bNrInPins; @@ -1733,22 +1801,20 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw for (info = list; info && info->type; info++) if (info->type == type) break; - if (! info || ! info->type) + if (!info || !info->type) info = &default_info; for (valinfo = info->values; valinfo->control; valinfo++) { __u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol); - if (! (controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) + if (!(controls[valinfo->control / 8] & (1 << ((valinfo->control % 8) - 1)))) continue; map = find_map(state, unitid, valinfo->control); if (check_ignored_ctl(map)) continue; cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + if (!cval) return -ENOMEM; - } cval->mixer = state->mixer; cval->id = unitid; cval->control = valinfo->control; @@ -1765,7 +1831,8 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw cval->initialized = 1; } else { if (type == USB_XU_CLOCK_RATE) { - /* E-Mu USB 0404/0202/TrackerPre/0204 + /* + * E-Mu USB 0404/0202/TrackerPre/0204 * samplerate control quirk */ cval->min = 0; @@ -1777,60 +1844,69 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw } kctl = snd_ctl_new1(&mixer_procunit_ctl, cval); - if (! kctl) { - usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + if (!kctl) { kfree(cval); return -ENOMEM; } kctl->private_free = usb_mixer_elem_free; - if (check_mapped_name(map, kctl->id.name, - sizeof(kctl->id.name))) + if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { /* nothing */ ; - else if (info->name) + } else if (info->name) { strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); - else { + } else { nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol); len = 0; if (nameid) - len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); - if (! len) + len = snd_usb_copy_string_desc(state, nameid, + kctl->id.name, + sizeof(kctl->id.name)); + if (!len) strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); } append_ctl_name(kctl, " "); append_ctl_name(kctl, valinfo->suffix); usb_audio_dbg(state->chip, - "[%d] PU [%s] ch = %d, val = %d/%d\n", - cval->id, kctl->id.name, cval->channels, cval->min, cval->max); - if ((err = snd_usb_mixer_add_control(state->mixer, kctl)) < 0) + "[%d] PU [%s] ch = %d, val = %d/%d\n", + cval->id, kctl->id.name, cval->channels, + cval->min, cval->max); + + err = snd_usb_mixer_add_control(state->mixer, kctl); + if (err < 0) return err; } return 0; } - -static int parse_audio_processing_unit(struct mixer_build *state, int unitid, void *raw_desc) +static int parse_audio_processing_unit(struct mixer_build *state, int unitid, + void *raw_desc) { - return build_audio_procunit(state, unitid, raw_desc, procunits, "Processing Unit"); + return build_audio_procunit(state, unitid, raw_desc, + procunits, "Processing Unit"); } -static int parse_audio_extension_unit(struct mixer_build *state, int unitid, void *raw_desc) +static int parse_audio_extension_unit(struct mixer_build *state, int unitid, + void *raw_desc) { - /* Note that we parse extension units with processing unit descriptors. - * That's ok as the layout is the same */ - return build_audio_procunit(state, unitid, raw_desc, extunits, "Extension Unit"); + /* + * Note that we parse extension units with processing unit descriptors. + * That's ok as the layout is the same. + */ + return build_audio_procunit(state, unitid, raw_desc, + extunits, "Extension Unit"); } - /* * Selector Unit */ -/* info callback for selector unit +/* + * info callback for selector unit * use an enumerator type for routing */ -static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { struct usb_mixer_elem_info *cval = kcontrol->private_data; const char **itemlist = (const char **)kcontrol->private_value; @@ -1841,7 +1917,8 @@ static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, struct snd_ctl } /* get callback for selector unit */ -static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = kcontrol->private_data; int val, err; @@ -1860,7 +1937,8 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_ } /* put callback for selector unit */ -static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = kcontrol->private_data; int val, oval, err; @@ -1889,8 +1967,8 @@ static struct snd_kcontrol_new mixer_selectunit_ctl = { .put = mixer_ctl_selector_put, }; - -/* private free callback. +/* + * private free callback. * free both private_data and private_value */ static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) @@ -1915,7 +1993,8 @@ static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) /* * parse a selector unit */ -static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void *raw_desc) +static int parse_audio_selector_unit(struct mixer_build *state, int unitid, + void *raw_desc) { struct uac_selector_unit_descriptor *desc = raw_desc; unsigned int i, nameid, len; @@ -1944,10 +2023,8 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void return 0; cval = kzalloc(sizeof(*cval), GFP_KERNEL); - if (! cval) { - usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + if (!cval) return -ENOMEM; - } cval->mixer = state->mixer; cval->id = unitid; cval->val_type = USB_MIXER_U8; @@ -1963,8 +2040,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void cval->control = 0; namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL); - if (! namelist) { - usb_audio_err(state->chip, "cannot malloc\n"); + if (!namelist) { kfree(cval); return -ENOMEM; } @@ -1973,8 +2049,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void struct usb_audio_term iterm; len = 0; namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); - if (! namelist[i]) { - usb_audio_err(state->chip, "cannot malloc\n"); + if (!namelist[i]) { while (i--) kfree(namelist[i]); kfree(namelist); @@ -1986,7 +2061,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0) len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0); if (! len) - sprintf(namelist[i], "Input %d", i); + sprintf(namelist[i], "Input %u", i); } kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval); @@ -2004,11 +2079,12 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void if (len) ; else if (nameid) - snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name)); + snd_usb_copy_string_desc(state, nameid, kctl->id.name, + sizeof(kctl->id.name)); else { len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 0); - if (! len) + if (!len) strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name)); if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR) @@ -2027,7 +2103,6 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void return 0; } - /* * parse an audio unit recursively */ @@ -2125,14 +2200,16 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) } p = NULL; - while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, mixer->hostif->extralen, + while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, + mixer->hostif->extralen, p, UAC_OUTPUT_TERMINAL)) != NULL) { if (mixer->protocol == UAC_VERSION_1) { struct uac1_output_terminal_descriptor *desc = p; if (desc->bLength < sizeof(*desc)) continue; /* invalid descriptor? */ - set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */ + /* mark terminal ID as visited */ + set_bit(desc->bTerminalID, state.unitbitmap); state.oterm.id = desc->bTerminalID; state.oterm.type = le16_to_cpu(desc->wTerminalType); state.oterm.name = desc->iTerminal; @@ -2144,7 +2221,8 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) if (desc->bLength < sizeof(*desc)) continue; /* invalid descriptor? */ - set_bit(desc->bTerminalID, state.unitbitmap); /* mark terminal ID as visited */ + /* mark terminal ID as visited */ + set_bit(desc->bTerminalID, state.unitbitmap); state.oterm.id = desc->bTerminalID; state.oterm.type = le16_to_cpu(desc->wTerminalType); state.oterm.name = desc->iTerminal; @@ -2152,7 +2230,10 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) if (err < 0 && err != -EINVAL) return err; - /* for UAC2, use the same approach to also add the clock selectors */ + /* + * For UAC2, use the same approach to also add the + * clock selectors + */ err = parse_audio_unit(&state, desc->bCSourceID); if (err < 0 && err != -EINVAL) return err; @@ -2306,7 +2387,9 @@ static void snd_usb_mixer_interrupt(struct urb *urb) } requeue: - if (ustatus != -ENOENT && ustatus != -ECONNRESET && ustatus != -ESHUTDOWN) { + if (ustatus != -ENOENT && + ustatus != -ECONNRESET && + ustatus != -ESHUTDOWN) { urb->dev = mixer->chip->dev; usb_submit_urb(urb, GFP_ATOMIC); } |