diff options
Diffstat (limited to 'sound/firewire/dice')
-rw-r--r-- | sound/firewire/dice/dice-transaction.c | 123 | ||||
-rw-r--r-- | sound/firewire/dice/dice.c | 227 | ||||
-rw-r--r-- | sound/firewire/dice/dice.h | 3 |
3 files changed, 202 insertions, 151 deletions
diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c index aee746187665..a4ff4e0bc0af 100644 --- a/sound/firewire/dice/dice-transaction.c +++ b/sound/firewire/dice/dice-transaction.c @@ -9,7 +9,7 @@ #include "dice.h" -#define NOTIFICATION_TIMEOUT_MS 100 +#define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC) static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type, u64 offset) @@ -65,16 +65,15 @@ static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info) static int set_clock_info(struct snd_dice *dice, unsigned int rate, unsigned int source) { - unsigned int retries = 3; unsigned int i; __be32 info; u32 mask; u32 clock; int err; -retry: + err = get_clock_info(dice, &info); if (err < 0) - goto end; + return err; clock = be32_to_cpu(info); if (source != UINT_MAX) { @@ -87,10 +86,8 @@ retry: if (snd_dice_rates[i] == rate) break; } - if (i == ARRAY_SIZE(snd_dice_rates)) { - err = -EINVAL; - goto end; - } + if (i == ARRAY_SIZE(snd_dice_rates)) + return -EINVAL; mask = CLOCK_RATE_MASK; clock &= ~mask; @@ -104,25 +101,13 @@ retry: err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT, &info, 4); if (err < 0) - goto end; + return err; - /* Timeout means it's invalid request, probably bus reset occurred. */ if (wait_for_completion_timeout(&dice->clock_accepted, - msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) { - if (retries-- == 0) { - err = -ETIMEDOUT; - goto end; - } + msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) + return -ETIMEDOUT; - err = snd_dice_transaction_reinit(dice); - if (err < 0) - goto end; - - msleep(500); /* arbitrary */ - goto retry; - } -end: - return err; + return 0; } int snd_dice_transaction_get_clock_source(struct snd_dice *dice, @@ -331,39 +316,60 @@ int snd_dice_transaction_reinit(struct snd_dice *dice) return register_notification_address(dice, false); } -int snd_dice_transaction_init(struct snd_dice *dice) +static int get_subaddrs(struct snd_dice *dice) { - struct fw_address_handler *handler = &dice->notification_handler; + static const int min_values[10] = { + 10, 0x64 / 4, + 10, 0x18 / 4, + 10, 0x18 / 4, + 0, 0, + 0, 0, + }; __be32 *pointers; + __be32 version; + u32 data; + unsigned int i; int err; - /* Use the same way which dice_interface_check() does. */ - pointers = kmalloc(sizeof(__be32) * 10, GFP_KERNEL); + pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32), + GFP_KERNEL); if (pointers == NULL) return -ENOMEM; - /* Get offsets for sub-addresses */ + /* + * Check that the sub address spaces exist and are located inside the + * private address space. The minimum values are chosen so that all + * minimally required registers are included. + */ err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, - DICE_PRIVATE_SPACE, - pointers, sizeof(__be32) * 10, 0); + DICE_PRIVATE_SPACE, pointers, + sizeof(__be32) * ARRAY_SIZE(min_values), 0); if (err < 0) goto end; - /* Allocation callback in address space over host controller */ - handler->length = 4; - handler->address_callback = dice_notification; - handler->callback_data = dice; - err = fw_core_add_address_handler(handler, &fw_high_memory_region); - if (err < 0) { - handler->callback_data = NULL; - goto end; + for (i = 0; i < ARRAY_SIZE(min_values); ++i) { + data = be32_to_cpu(pointers[i]); + if (data < min_values[i] || data >= 0x40000) { + err = -ENODEV; + goto end; + } } - /* Register the address space */ - err = register_notification_address(dice, true); - if (err < 0) { - fw_core_remove_address_handler(handler); - handler->callback_data = NULL; + /* + * Check that the implemented DICE driver specification major version + * number matches. + */ + err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, + DICE_PRIVATE_SPACE + + be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, + &version, sizeof(version), 0); + if (err < 0) + goto end; + + if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) { + dev_err(&dice->unit->device, + "unknown DICE version: 0x%08x\n", be32_to_cpu(version)); + err = -ENODEV; goto end; } @@ -380,3 +386,32 @@ end: kfree(pointers); return err; } + +int snd_dice_transaction_init(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + int err; + + err = get_subaddrs(dice); + if (err < 0) + return err; + + /* Allocation callback in address space over host controller */ + handler->length = 4; + handler->address_callback = dice_notification; + handler->callback_data = dice; + err = fw_core_add_address_handler(handler, &fw_high_memory_region); + if (err < 0) { + handler->callback_data = NULL; + return err; + } + + /* Register the address space */ + err = register_notification_address(dice, true); + if (err < 0) { + fw_core_remove_address_handler(handler); + handler->callback_data = NULL; + } + + return err; +} diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 0cda05c72f50..b91b3739c810 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -18,27 +18,14 @@ MODULE_LICENSE("GPL v2"); #define WEISS_CATEGORY_ID 0x00 #define LOUD_CATEGORY_ID 0x10 -static int dice_interface_check(struct fw_unit *unit) +#define PROBE_DELAY_MS (2 * MSEC_PER_SEC) + +static int check_dice_category(struct fw_unit *unit) { - static const int min_values[10] = { - 10, 0x64 / 4, - 10, 0x18 / 4, - 10, 0x18 / 4, - 0, 0, - 0, 0, - }; struct fw_device *device = fw_parent_device(unit); struct fw_csr_iterator it; - int key, val, vendor = -1, model = -1, err; - unsigned int category, i; - __be32 *pointers; - u32 value; - __be32 version; - - pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32), - GFP_KERNEL); - if (pointers == NULL) - return -ENOMEM; + int key, val, vendor = -1, model = -1; + unsigned int category; /* * Check that GUID and unit directory are constructed according to DICE @@ -64,51 +51,10 @@ static int dice_interface_check(struct fw_unit *unit) else category = DICE_CATEGORY_ID; if (device->config_rom[3] != ((vendor << 8) | category) || - device->config_rom[4] >> 22 != model) { - err = -ENODEV; - goto end; - } - - /* - * Check that the sub address spaces exist and are located inside the - * private address space. The minimum values are chosen so that all - * minimally required registers are included. - */ - err = snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, - DICE_PRIVATE_SPACE, pointers, - sizeof(__be32) * ARRAY_SIZE(min_values), 0); - if (err < 0) { - err = -ENODEV; - goto end; - } - for (i = 0; i < ARRAY_SIZE(min_values); ++i) { - value = be32_to_cpu(pointers[i]); - if (value < min_values[i] || value >= 0x40000) { - err = -ENODEV; - goto end; - } - } + device->config_rom[4] >> 22 != model) + return -ENODEV; - /* - * Check that the implemented DICE driver specification major version - * number matches. - */ - err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, - DICE_PRIVATE_SPACE + - be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, - &version, 4, 0); - if (err < 0) { - err = -ENODEV; - goto end; - } - if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) { - dev_err(&unit->device, - "unknown DICE version: 0x%08x\n", be32_to_cpu(version)); - err = -ENODEV; - goto end; - } -end: - return err; + return 0; } static int highest_supported_mode_rate(struct snd_dice *dice, @@ -231,6 +177,16 @@ static void dice_card_strings(struct snd_dice *dice) strcpy(card->mixername, "DICE"); } +static void dice_free(struct snd_dice *dice) +{ + snd_dice_stream_destroy_duplex(dice); + snd_dice_transaction_destroy(dice); + fw_unit_put(dice->unit); + + mutex_destroy(&dice->mutex); + kfree(dice); +} + /* * This module releases the FireWire unit data after all ALSA character devices * are released by applications. This is for releasing stream data or finishing @@ -239,39 +195,21 @@ static void dice_card_strings(struct snd_dice *dice) */ static void dice_card_free(struct snd_card *card) { - struct snd_dice *dice = card->private_data; - - snd_dice_stream_destroy_duplex(dice); - snd_dice_transaction_destroy(dice); - fw_unit_put(dice->unit); - - mutex_destroy(&dice->mutex); + dice_free(card->private_data); } -static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) +static void do_registration(struct work_struct *work) { - struct snd_card *card; - struct snd_dice *dice; + struct snd_dice *dice = container_of(work, struct snd_dice, dwork.work); int err; - err = dice_interface_check(unit); - if (err < 0) - goto end; + if (dice->registered) + return; - err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, - sizeof(*dice), &card); + err = snd_card_new(&dice->unit->device, -1, NULL, THIS_MODULE, 0, + &dice->card); if (err < 0) - goto end; - - dice = card->private_data; - dice->card = card; - dice->unit = fw_unit_get(unit); - card->private_free = dice_card_free; - - spin_lock_init(&dice->lock); - mutex_init(&dice->mutex); - init_completion(&dice->clock_accepted); - init_waitqueue_head(&dice->hwdep_wait); + return; err = snd_dice_transaction_init(dice); if (err < 0) @@ -283,56 +221,131 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) dice_card_strings(dice); + snd_dice_create_proc(dice); + err = snd_dice_create_pcm(dice); if (err < 0) goto error; - err = snd_dice_create_hwdep(dice); + err = snd_dice_create_midi(dice); if (err < 0) goto error; - snd_dice_create_proc(dice); - - err = snd_dice_create_midi(dice); + err = snd_dice_create_hwdep(dice); if (err < 0) goto error; - err = snd_dice_stream_init_duplex(dice); + err = snd_card_register(dice->card); if (err < 0) goto error; - err = snd_card_register(card); + /* + * After registered, dice instance can be released corresponding to + * releasing the sound card instance. + */ + dice->card->private_free = dice_card_free; + dice->card->private_data = dice; + dice->registered = true; + + return; +error: + snd_dice_transaction_destroy(dice); + snd_card_free(dice->card); + dev_info(&dice->unit->device, + "Sound card registration failed: %d\n", err); +} + +static void schedule_registration(struct snd_dice *dice) +{ + struct fw_card *fw_card = fw_parent_device(dice->unit)->card; + u64 now, delay; + + now = get_jiffies_64(); + delay = fw_card->reset_jiffies + msecs_to_jiffies(PROBE_DELAY_MS); + + if (time_after64(delay, now)) + delay -= now; + else + delay = 0; + + mod_delayed_work(system_wq, &dice->dwork, delay); +} + +static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) +{ + struct snd_dice *dice; + int err; + + err = check_dice_category(unit); + if (err < 0) + return -ENODEV; + + /* Allocate this independent of sound card instance. */ + dice = kzalloc(sizeof(struct snd_dice), GFP_KERNEL); + if (dice == NULL) + return -ENOMEM; + + dice->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, dice); + + spin_lock_init(&dice->lock); + mutex_init(&dice->mutex); + init_completion(&dice->clock_accepted); + init_waitqueue_head(&dice->hwdep_wait); + + err = snd_dice_stream_init_duplex(dice); if (err < 0) { - snd_dice_stream_destroy_duplex(dice); - goto error; + dice_free(dice); + return err; } - dev_set_drvdata(&unit->device, dice); -end: - return err; -error: - snd_card_free(card); - return err; + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&dice->dwork, do_registration); + schedule_registration(dice); + + return 0; } static void dice_remove(struct fw_unit *unit) { struct snd_dice *dice = dev_get_drvdata(&unit->device); - /* No need to wait for releasing card object in this context. */ - snd_card_free_when_closed(dice->card); + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&dice->dwork); + + if (dice->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(dice->card); + } else { + /* Don't forget this case. */ + dice_free(dice); + } } static void dice_bus_reset(struct fw_unit *unit) { struct snd_dice *dice = dev_get_drvdata(&unit->device); + /* Postpone a workqueue for deferred registration. */ + if (!dice->registered) + schedule_registration(dice); + /* The handler address register becomes initialized. */ snd_dice_transaction_reinit(dice); - mutex_lock(&dice->mutex); - snd_dice_stream_update_duplex(dice); - mutex_unlock(&dice->mutex); + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (dice->registered) { + mutex_lock(&dice->mutex); + snd_dice_stream_update_duplex(dice); + mutex_unlock(&dice->mutex); + } } #define DICE_INTERFACE 0x000001 diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 101550ac1a24..3d5ebebe61ea 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -45,6 +45,9 @@ struct snd_dice { spinlock_t lock; struct mutex mutex; + bool registered; + struct delayed_work dwork; + /* Offsets for sub-addresses */ unsigned int global_offset; unsigned int rx_offset; |