ALSA: core: Expose sound card online/offline state

Expose sound card online/offline state to procfs so userspace application
can poll and read sound card's state.

Change-Id: Icafb2a8b970fb53ebc3eb7c44d075f1c01985072
Signed-off-by: Joonwoo Park <joonwoop@codeaurora.org>
diff --git a/sound/core/init.c b/sound/core/init.c
index f300bd3..6559a40 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -55,6 +55,8 @@
 module_param_array(slots, charp, NULL, 0444);
 MODULE_PARM_DESC(slots, "Module names assigned to the slots.");
 
+#define SND_CARD_STATE_MAX_LEN 16
+
 /* return non-zero if the given index is reserved for the given
  * module via slots option
  */
@@ -104,10 +106,40 @@
 	snd_iprintf(buffer, "%s\n", entry->card->id);
 }
 
+static int snd_card_state_read(struct snd_info_entry *entry,
+			       void *file_private_data, struct file *file,
+			       char __user *buf, size_t count, loff_t pos)
+{
+	int len;
+	char buffer[SND_CARD_STATE_MAX_LEN];
+
+	/* make sure offline is updated prior to wake up */
+	rmb();
+	len = snprintf(buffer, sizeof(buffer), "%s\n",
+		       entry->card->offline ? "OFFLINE" : "ONLINE");
+	return simple_read_from_buffer(buf, count, &pos, buffer, len);
+}
+
+static unsigned int snd_card_state_poll(struct snd_info_entry *entry,
+					void *private_data, struct file *file,
+					poll_table *wait)
+{
+	poll_wait(file, &entry->card->offline_poll_wait, wait);
+	if (xchg(&entry->card->offline_change, 0))
+		return POLLIN | POLLPRI | POLLRDNORM;
+	else
+		return 0;
+}
+
+static struct snd_info_entry_ops snd_card_state_proc_ops = {
+	.read = snd_card_state_read,
+	.poll = snd_card_state_poll,
+};
+
 static inline int init_info_for_card(struct snd_card *card)
 {
 	int err;
-	struct snd_info_entry *entry;
+	struct snd_info_entry *entry, *entry_state;
 
 	if ((err = snd_info_card_register(card)) < 0) {
 		snd_printd("unable to create card info\n");
@@ -123,6 +155,24 @@
 		entry = NULL;
 	}
 	card->proc_id = entry;
+
+	entry_state = snd_info_create_card_entry(card, "state",
+						 card->proc_root);
+	if (entry_state == NULL) {
+		snd_printd("unable to create card entry state\n");
+		card->proc_id = NULL;
+		return err;
+	}
+	entry_state->size = SND_CARD_STATE_MAX_LEN;
+	entry_state->content = SNDRV_INFO_CONTENT_DATA;
+	entry_state->c.ops = &snd_card_state_proc_ops;
+	err = snd_info_register(entry_state);
+	if (err < 0) {
+		snd_printd("unable to register card entry state\n");
+		card->proc_id = NULL;
+		return err;
+	}
+
 	return 0;
 }
 #else /* !CONFIG_PROC_FS */
@@ -216,6 +266,7 @@
 	mutex_init(&card->power_lock);
 	init_waitqueue_head(&card->power_sleep);
 #endif
+	init_waitqueue_head(&card->offline_poll_wait);
 	/* the control interface cannot be accessed from the user space until */
 	/* snd_cards_bitmask and snd_cards are set with snd_card_register */
 	err = snd_ctl_create(card);
@@ -909,6 +960,35 @@
 
 EXPORT_SYMBOL(snd_card_file_remove);
 
+/**
+ * snd_card_change_online_state - mark card's online/offline state
+ * @card: Card to mark
+ * @online: whether online of offline
+ *
+ * Mutes the DAI DAC.
+ */
+void snd_card_change_online_state(struct snd_card *card, int online)
+{
+	snd_printd("snd card %s state change %d -> %d\n",
+		   card->shortname, !card->offline, online);
+	card->offline = !online;
+	/* make sure offline is updated prior to wake up */
+	wmb();
+	xchg(&card->offline_change, 1);
+	wake_up_interruptible(&card->offline_poll_wait);
+}
+EXPORT_SYMBOL(snd_card_change_online_state);
+
+/**
+ * snd_card_is_online_state - return true if card is online state
+ * @card: Card to query
+ */
+bool snd_card_is_online_state(struct snd_card *card)
+{
+	return !card->offline;
+}
+EXPORT_SYMBOL(snd_card_is_online_state);
+
 #ifdef CONFIG_PM
 /**
  *  snd_power_wait - wait until the power-state is changed.