From 5f3557a2e88b324e026d44f8fb7eb3ea37bba16b Mon Sep 17 00:00:00 2001
From: Giedrius Trainavicius <giedrius@blokas.io>
Date: Tue, 25 Oct 2016 01:47:20 +0300
Subject: [PATCH 138/454] Updates for Pisound module code:

	* Merged 'Fix a warning in DEBUG builds' (1c8b82b).
	* Updating some strings and copyright information.
	* Fix for handling high load of MIDI input and output.
	* Use dual rate oversampling ratio for 96kHz instead of single
	  rate one.

Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>
---
 .../arm/boot/dts/overlays/pisound-overlay.dts |   4 +-
 sound/soc/bcm/pisound.c                       | 209 ++++++++++++------
 2 files changed, 146 insertions(+), 67 deletions(-)

--- a/arch/arm/boot/dts/overlays/pisound-overlay.dts
+++ b/arch/arm/boot/dts/overlays/pisound-overlay.dts
@@ -1,6 +1,6 @@
 /*
- * pisound Linux kernel module.
- * Copyright (C) 2016  Vilniaus Blokas UAB, http://blokas.io/pisound
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2017  Vilniaus Blokas UAB, https://blokas.io/pisound
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
--- a/sound/soc/bcm/pisound.c
+++ b/sound/soc/bcm/pisound.c
@@ -1,6 +1,6 @@
 /*
- * pisound Linux kernel module.
- * Copyright (C) 2016  Vilniaus Blokas UAB, http://blokas.io/pisound
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2017  Vilniaus Blokas UAB, https://blokas.io/pisound
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -28,6 +28,7 @@
 #include <linux/spi/spi.h>
 #include <linux/interrupt.h>
 #include <linux/kfifo.h>
+#include <linux/jiffies.h>
 
 #include <sound/core.h>
 #include <sound/pcm.h>
@@ -41,7 +42,8 @@
 static int pisnd_spi_init(struct device *dev);
 static void pisnd_spi_uninit(void);
 
-static void pisnd_spi_send(uint8_t val);
+static void pisnd_spi_flush(void);
+static void pisnd_spi_start(void);
 static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length);
 
 typedef void (*pisnd_spi_recv_cb)(void *data);
@@ -56,7 +58,7 @@ static void pisnd_midi_uninit(void);
 
 #define PISOUND_LOG_PREFIX "pisound: "
 
-#ifdef DEBUG
+#ifdef PISOUND_DEBUG
 #	define printd(...) pr_alert(PISOUND_LOG_PREFIX __VA_ARGS__)
 #else
 #	define printd(...) do {} while (0)
@@ -65,13 +67,18 @@ static void pisnd_midi_uninit(void);
 #define printe(...) pr_err(PISOUND_LOG_PREFIX __VA_ARGS__)
 #define printi(...) pr_info(PISOUND_LOG_PREFIX __VA_ARGS__)
 
+static struct snd_rawmidi *g_rmidi;
+static struct snd_rawmidi_substream *g_midi_output_substream;
+
 static int pisnd_output_open(struct snd_rawmidi_substream *substream)
 {
+	g_midi_output_substream = substream;
 	return 0;
 }
 
 static int pisnd_output_close(struct snd_rawmidi_substream *substream)
 {
+	g_midi_output_substream = NULL;
 	return 0;
 }
 
@@ -80,26 +87,20 @@ static void pisnd_output_trigger(
 	int up
 	)
 {
-	uint8_t data;
+	if (substream != g_midi_output_substream) {
+		printe("MIDI output trigger called for an unexpected stream!");
+		return;
+	}
 
 	if (!up)
 		return;
 
-	while (snd_rawmidi_transmit_peek(substream, &data, 1)) {
-		pisnd_spi_send(data);
-		snd_rawmidi_transmit_ack(substream, 1);
-	}
+	pisnd_spi_start();
 }
 
 static void pisnd_output_drain(struct snd_rawmidi_substream *substream)
 {
-	uint8_t data;
-
-	while (snd_rawmidi_transmit_peek(substream, &data, 1)) {
-		pisnd_spi_send(data);
-
-		snd_rawmidi_transmit_ack(substream, 1);
-	}
+	pisnd_spi_flush();
 }
 
 static int pisnd_input_open(struct snd_rawmidi_substream *substream)
@@ -120,7 +121,7 @@ static void pisnd_midi_recv_callback(voi
 	while ((n = pisnd_spi_recv(data, sizeof(data)))) {
 		int res = snd_rawmidi_receive(substream, data, n);
 		(void)res;
-		printd("midi recv 0x%02x, res = %d\n", data, res);
+		printd("midi recv %u bytes, res = %d\n", n, res);
 	}
 }
 
@@ -134,8 +135,6 @@ static void pisnd_input_trigger(struct s
 	}
 }
 
-static struct snd_rawmidi *g_rmidi;
-
 static struct snd_rawmidi_ops pisnd_output_ops = {
 	.open = pisnd_output_open,
 	.close = pisnd_output_close,
@@ -168,7 +167,11 @@ static struct snd_rawmidi_global_ops pis
 
 static int pisnd_midi_init(struct snd_card *card)
 {
-	int err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi);
+	int err;
+
+	g_midi_output_substream = NULL;
+
+	err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi);
 
 	if (err < 0) {
 		printe("snd_rawmidi_new failed: %d\n", err);
@@ -209,7 +212,7 @@ static void pisnd_midi_uninit(void)
 static void *g_recvData;
 static pisnd_spi_recv_cb g_recvCallback;
 
-#define FIFO_SIZE 512
+#define FIFO_SIZE 4096
 
 static char g_serial_num[11];
 static char g_id[25];
@@ -231,6 +234,7 @@ static struct work_struct pisnd_work_pro
 
 static void pisnd_work_handler(struct work_struct *work);
 
+static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len);
 static uint16_t spi_transfer16(uint16_t val);
 
 static int pisnd_init_workqueues(void)
@@ -285,9 +289,6 @@ static unsigned long spilockflags;
 
 static uint16_t spi_transfer16(uint16_t val)
 {
-	int err;
-	struct spi_transfer transfer;
-	struct spi_message msg;
 	uint8_t txbuf[2];
 	uint8_t rxbuf[2];
 
@@ -296,19 +297,38 @@ static uint16_t spi_transfer16(uint16_t
 		return 0;
 	}
 
+	txbuf[0] = val >> 8;
+	txbuf[1] = val & 0xff;
+
+	spi_transfer(txbuf, rxbuf, sizeof(txbuf));
+
+	printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]);
+
+	return (rxbuf[0] << 8) | rxbuf[1];
+}
+
+static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len)
+{
+	int err;
+	struct spi_transfer transfer;
+	struct spi_message msg;
+
+	memset(rxbuf, 0, sizeof(txbuf));
+
+	if (!pisnd_spi_device) {
+		printe("pisnd_spi_device null, returning\n");
+		return;
+	}
+
 	spi_message_init(&msg);
 
 	memset(&transfer, 0, sizeof(transfer));
-	memset(&rxbuf, 0, sizeof(rxbuf));
 
-	txbuf[0] = val >> 8;
-	txbuf[1] = val & 0xff;
-
-	transfer.tx_buf = &txbuf;
-	transfer.rx_buf = &rxbuf;
-	transfer.len = sizeof(txbuf);
-	transfer.speed_hz = 125000;
-	transfer.delay_usecs = 100;
+	transfer.tx_buf = txbuf;
+	transfer.rx_buf = rxbuf;
+	transfer.len = len;
+	transfer.speed_hz = 100000;
+	transfer.delay_usecs = 10;
 	spi_message_add_tail(&transfer, &msg);
 
 	spin_lock_irqsave(&spilock, spilockflags);
@@ -317,13 +337,10 @@ static uint16_t spi_transfer16(uint16_t
 
 	if (err < 0) {
 		printe("spi_sync error %d\n", err);
-		return 0;
+		return;
 	}
 
-	printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]);
 	printd("hasMore %d\n", pisnd_spi_has_more());
-
-	return (rxbuf[0] << 8) | rxbuf[1];
 }
 
 static int spi_read_bytes(char *dst, size_t length, uint8_t *bytesRead)
@@ -335,7 +352,7 @@ static int spi_read_bytes(char *dst, siz
 	memset(dst, 0, length);
 	*bytesRead = 0;
 
-	 rx = spi_transfer16(0);
+	rx = spi_transfer16(0);
 	if (!(rx >> 8))
 		return -EINVAL;
 
@@ -388,35 +405,90 @@ static struct spi_device *pisnd_spi_find
 
 static void pisnd_work_handler(struct work_struct *work)
 {
-	uint16_t rx;
-	uint16_t tx;
+	enum { TRANSFER_SIZE = 4 };
+	enum { PISOUND_OUTPUT_BUFFER_SIZE = 128 };
+	enum { MIDI_BYTES_PER_SECOND = 3125 };
+	int out_buffer_used = 0;
+	unsigned long now;
 	uint8_t val;
+	uint8_t txbuf[TRANSFER_SIZE];
+	uint8_t rxbuf[TRANSFER_SIZE];
+	uint8_t midibuf[TRANSFER_SIZE];
+	int i, n;
+	bool had_data;
+
+	unsigned long last_transfer_at = jiffies;
 
 	if (work == &pisnd_work_process) {
 		if (pisnd_spi_device == NULL)
 			return;
 
 		do {
-			val = 0;
-			tx = 0;
+			if (g_midi_output_substream &&
+				kfifo_avail(&spi_fifo_out) >= sizeof(midibuf)) {
 
-			if (g_ledFlashDurationChanged) {
-				tx = 0xf000 | g_ledFlashDuration;
-				g_ledFlashDuration = 0;
-				g_ledFlashDurationChanged = false;
-			} else if (kfifo_get(&spi_fifo_out, &val)) {
-				tx = 0x0f00 | val;
+				n = snd_rawmidi_transmit_peek(
+					g_midi_output_substream,
+					midibuf, sizeof(midibuf)
+				);
+
+				if (n > 0) {
+					for (i = 0; i < n; ++i)
+						kfifo_put(
+							&spi_fifo_out,
+							midibuf[i]
+							);
+					snd_rawmidi_transmit_ack(
+						g_midi_output_substream,
+						i
+						);
+				}
 			}
 
-			rx = spi_transfer16(tx);
+			had_data = false;
+			memset(txbuf, 0, sizeof(txbuf));
+			for (i = 0; i < sizeof(txbuf) &&
+				out_buffer_used < PISOUND_OUTPUT_BUFFER_SIZE;
+				i += 2) {
+
+				val = 0;
+
+				if (g_ledFlashDurationChanged) {
+					txbuf[i+0] = 0xf0;
+					txbuf[i+1] = g_ledFlashDuration;
+					g_ledFlashDuration = 0;
+					g_ledFlashDurationChanged = false;
+				} else if (kfifo_get(&spi_fifo_out, &val)) {
+					txbuf[i+0] = 0x0f;
+					txbuf[i+1] = val;
+					++out_buffer_used;
+				}
+			}
 
-			if (rx & 0xff00) {
-				kfifo_put(&spi_fifo_in, rx & 0xff);
-				if (kfifo_len(&spi_fifo_in) > 16
-					&& g_recvCallback)
-					g_recvCallback(g_recvData);
+			spi_transfer(txbuf, rxbuf, sizeof(txbuf));
+			/* Estimate the Pisound's MIDI output buffer usage, so
+			 * that we don't overflow it. Space in the buffer should
+			 * be becoming available at the UART MIDI byte transfer
+			 * rate.
+			 */
+			now = jiffies;
+			out_buffer_used -=
+				(MIDI_BYTES_PER_SECOND / HZ) /
+				(now - last_transfer_at);
+			if (out_buffer_used < 0)
+				out_buffer_used = 0;
+			last_transfer_at = now;
+
+			for (i = 0; i < sizeof(rxbuf); i += 2) {
+				if (rxbuf[i]) {
+					kfifo_put(&spi_fifo_in, rxbuf[i+1]);
+					if (kfifo_len(&spi_fifo_in) > 16 &&
+						g_recvCallback)
+						g_recvCallback(g_recvData);
+					had_data = true;
+				}
 			}
-		} while (rx != 0
+		} while (had_data
 			|| !kfifo_is_empty(&spi_fifo_out)
 			|| pisnd_spi_has_more()
 			|| g_ledFlashDurationChanged
@@ -492,7 +564,7 @@ static int spi_read_info(void)
 	if (!(tmp >> 8))
 		return -EINVAL;
 
-	 count = tmp & 0xff;
+	count = tmp & 0xff;
 
 	for (i = 0; i < count; ++i) {
 		memset(buffer, 0, sizeof(buffer));
@@ -628,10 +700,17 @@ static void pisnd_spi_flash_leds(uint8_t
 	pisnd_schedule_process(TASK_PROCESS);
 }
 
-static void pisnd_spi_send(uint8_t val)
+static void pisnd_spi_flush(void)
+{
+	while (!kfifo_is_empty(&spi_fifo_out)) {
+		pisnd_spi_start();
+		flush_workqueue(pisnd_workqueue);
+	}
+}
+
+static void pisnd_spi_start(void)
 {
-	kfifo_put(&spi_fifo_out, val);
-	printd("schedule from spi_send\n");
+	printd("schedule from spi_start\n");
 	pisnd_schedule_process(TASK_PROCESS);
 }
 
@@ -765,7 +844,7 @@ static int pisnd_hw_params(
 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
 	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
 
-	/* pisound runs on fixed 32 clock counts per channel,
+	/* Pisound runs on fixed 32 clock counts per channel,
 	 * as generated by the master ADC.
 	 */
 	snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
@@ -786,8 +865,8 @@ static int pisnd_hw_params(
 		break;
 	case 96000:
 		gpiod_set_value(osr0, true);
-		gpiod_set_value(osr1, true);
-		gpiod_set_value(osr2, false);
+		gpiod_set_value(osr1, false);
+		gpiod_set_value(osr2, true);
 		break;
 	case 192000:
 		gpiod_set_value(osr0, true);
@@ -1030,7 +1109,7 @@ static int pisnd_probe(struct platform_d
 		return ret;
 	}
 
-	printi("Detected pisound card:\n");
+	printi("Detected Pisound card:\n");
 	printi("\tSerial:  %s\n", pisnd_spi_get_serial());
 	printi("\tVersion: %s\n", pisnd_spi_get_version());
 	printi("\tId:      %s\n", pisnd_spi_get_id());
@@ -1119,5 +1198,5 @@ static struct platform_driver pisnd_driv
 module_platform_driver(pisnd_driver);
 
 MODULE_AUTHOR("Giedrius Trainavicius <giedrius@blokas.io>");
-MODULE_DESCRIPTION("ASoC Driver for pisound, http://blokas.io/pisound");
+MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound");
 MODULE_LICENSE("GPL v2");