audio: Add a delay for DSP firmware loading

This is turned off by default.

Change-Id: Idf764933c67e6666cb2cc0043fa7a7e86935488d
diff --git a/audio/audio_hw.c b/audio/audio_hw.c
index cfe3013..3d80f7b 100644
--- a/audio/audio_hw.c
+++ b/audio/audio_hw.c
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2013 The Android Open Source Project
  * Copyright (C) 2017 Christopher N. Hesse <raymanfx@gmail.com>
+ * Copyright (C) 2017 Andreas Schneider <asn@cryptomilk.org>
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -243,6 +244,35 @@
     STRING_TO_ENUM(AUDIO_CHANNEL_OUT_7POINT1),
 };
 
+struct timespec time_spec_diff(struct timespec time1, struct timespec time0) {
+    struct timespec ret;
+    int xsec = 0;
+    int sign = 1;
+
+    if (time0.tv_nsec > time1.tv_nsec) {
+        xsec = (int) ((time0.tv_nsec - time1.tv_nsec) / (1E9 + 1));
+        time0.tv_nsec -= (long int) (1E9 * xsec);
+        time0.tv_sec += xsec;
+    }
+
+    if ((time1.tv_nsec - time0.tv_nsec) > 1E9) {
+        xsec = (int) ((time1.tv_nsec - time0.tv_nsec) / 1E9);
+        time0.tv_nsec += (long int) (1E9 * xsec);
+        time0.tv_sec -= xsec;
+    }
+
+    ret.tv_sec = time1.tv_sec - time0.tv_sec;
+    ret.tv_nsec = time1.tv_nsec - time0.tv_nsec;
+
+    if (time1.tv_sec < time0.tv_sec) {
+        sign = -1;
+    }
+
+    ret.tv_sec = ret.tv_sec * sign;
+
+    return ret;
+}
+
 static bool is_supported_format(audio_format_t format)
 {
     if (format == AUDIO_FORMAT_MP3 ||
@@ -389,6 +419,11 @@
             mixer_card->card = card;
             mixer_card->mixer = mixer;
             mixer_card->audio_route = audio_route;
+
+            /* Do not sleep on first enable_snd_device() */
+            mixer_card->dsp_poweroff_time.tv_sec = 1;
+            mixer_card->dsp_poweroff_time.tv_nsec = 0;
+
             list_add_tail(&adev->mixer_list, &mixer_card->adev_list_node);
         }
     }
@@ -702,6 +737,10 @@
     struct mixer_card *mixer_card;
     struct listnode *node;
     const char *snd_device_name = get_snd_device_name(snd_device);
+#ifdef DSP_POWEROFF_DELAY
+    struct timespec activation_time;
+    struct timespec elapsed_time;
+#endif /* DSP_POWEROFF_DELAY */
 
     if (snd_device_name == NULL)
         return -EINVAL;
@@ -724,9 +763,25 @@
 
     list_for_each(node, &uc_info->mixer_list) {
         mixer_card = node_to_item(node, struct mixer_card, uc_list_node[uc_info->id]);
+
+#ifdef DSP_POWEROFF_DELAY
+        clock_gettime(CLOCK_MONOTONIC, &activation_time);
+
+        elapsed_time = time_spec_diff(mixer_card->dsp_poweroff_time,
+                                      activation_time);
+        if (elapsed_time.tv_sec == 0) {
+            long elapsed_usec = elapsed_time.tv_nsec / 1000;
+
+            if (elapsed_usec < DSP_POWEROFF_DELAY) {
+                usleep(DSP_POWEROFF_DELAY - elapsed_usec);
+            }
+        }
+        update_mixer = true;
+#endif /* DSP_POWEROFF_DELAY */
         audio_route_apply_path(mixer_card->audio_route, snd_device_name);
-        if (update_mixer)
+        if (update_mixer) {
             audio_route_update_mixer(mixer_card->audio_route);
+        }
     }
 
     return 0;
@@ -761,9 +816,16 @@
               snd_device, snd_device_name);
         list_for_each(node, &uc_info->mixer_list) {
             mixer_card = node_to_item(node, struct mixer_card, uc_list_node[uc_info->id]);
+#ifdef DSP_POWEROFF_DELAY
+            update_mixer = true;
+#endif /* DSP_POWEROFF_DELAY */
             audio_route_reset_path(mixer_card->audio_route, snd_device_name);
-            if (update_mixer)
+            if (update_mixer) {
                 audio_route_update_mixer(mixer_card->audio_route);
+            }
+#ifdef DSP_POWEROFF_DELAY
+            clock_gettime(CLOCK_MONOTONIC, &(mixer_card->dsp_poweroff_time));
+#endif /* DSP_POWEROFF_DELAY */
         }
     }
     return 0;
diff --git a/audio/audio_hw.h b/audio/audio_hw.h
index fddeffc..da5cd86 100644
--- a/audio/audio_hw.h
+++ b/audio/audio_hw.h
@@ -355,6 +355,7 @@
     int                 card;
     struct mixer*       mixer;
     struct audio_route* audio_route;
+    struct timespec     dsp_poweroff_time;
 };
 
 struct audio_usecase {
diff --git a/audio/include/samsung_audio.h b/audio/include/samsung_audio.h
index 8b8aeac..1bd094a 100644
--- a/audio/include/samsung_audio.h
+++ b/audio/include/samsung_audio.h
@@ -57,4 +57,15 @@
  */
 #define SUPPORTS_IRQ_AFFINITY 0
 
+/*
+ * The Wolfson/Cirruslogic chips need to shutdown the DAPM route completely
+ * to be able to load a new firmware. Some of these chips need a delay after
+ * shutodown to full poweroff the DSPs.
+ *
+ * A good value to start with is 10ms:
+ *
+ * #define DSP_POWEROFF_DELAY 10 * 1000
+ */
+/* #define DSP_POWEROFF_DELAY 0 */
+
 #endif // SAMSUNG_AUDIO_H