From 6eb1d61bddb9ddf1bc82aff5ed2093daf88dd3b2 Mon Sep 17 00:00:00 2001
From: Iliyan Malchev <malchev@google.com>
Date: Tue, 17 Aug 2010 11:34:39 -0700
Subject: [PATCH] [ARM] tegra: tegra_i2s_audio: configure in/out buffer sizes
 from user space

-- Add ioctls for configuring buffer, threshold, and DMA-transaction sizes from
   user space.
-- Buffer sizes are provided in orders of magnitude.
-- Allocate max-sized buffers during probe, and allow the user to resize them
   only within the original allocation, to avoid the risk from kmalloc failing
   due to kernel-heap fragmentation, and also to avoid race conditions on DMA
   shut-down.
-- In tegra_audio_write(), moved the call to start_playback_if_necessary()
   immediately after writing to the fifo.  Otherwise, when the fifo size is
   smaller than what the user is trying to write, the user will block before
   playback is started.
-- Silenced printk spew on spinning on i2s registers after transactions are
   completed.
-- Cleaned up a 80-col style violation in downsample()

Signed-off-by: Iliyan Malchev <malchev@google.com>
---
 arch/arm/mach-tegra/tegra_i2s_audio.c | 243 ++++++++++++++++++++------
 include/linux/tegra_audio.h           |  16 ++
 2 files changed, 208 insertions(+), 51 deletions(-)

diff --git a/arch/arm/mach-tegra/tegra_i2s_audio.c b/arch/arm/mach-tegra/tegra_i2s_audio.c
index 30419eae2ba4..57582c12bb14 100644
--- a/arch/arm/mach-tegra/tegra_i2s_audio.c
+++ b/arch/arm/mach-tegra/tegra_i2s_audio.c
@@ -54,6 +54,7 @@ struct audio_stream {
 	int opened;
 	struct mutex lock;
 
+	struct tegra_audio_buf_config buf_config;
 	bool active; /* is DMA or PIO in progress? */
 	void *buffer;
 	dma_addr_t buf_phys;
@@ -105,6 +106,21 @@ struct audio_driver_state {
 	struct audio_stream in;
 };
 
+static inline int buf_size(struct audio_stream *s)
+{
+	return 1 << s->buf_config.size;
+}
+
+static inline int chunk_size(struct audio_stream *s)
+{
+	return 1 << s->buf_config.chunk;
+}
+
+static inline int threshold_size(struct audio_stream *s)
+{
+	return 1 << s->buf_config.threshold;
+}
+
 static inline struct audio_driver_state *ads_from_misc_out(struct file *file)
 {
 	struct miscdevice *m = file->private_data;
@@ -415,16 +431,12 @@ static inline u32 i2s_get_fifo_full_empty_count(unsigned long base, int fifo)
 	return val & I2S_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK;
 }
 
-/* FIXME: add an ioctl to report the buffer sizes */
+#define PCM_IN_BUFFER_PADDING		(1<<6) /* bytes */
+#define PCM_BUFFER_MAX_SIZE_ORDER	(PAGE_SHIFT + 2)
+#define PCM_DMA_CHUNK_MIN_SIZE_ORDER	3
 
-#define PCM_OUT_BUFFER_SIZE	(PAGE_SIZE*4)
-#define PCM_OUT_DMA_CHUNK	(PAGE_SIZE)
-#define PCM_OUT_THRESHOLD	(PAGE_SIZE*2)
-
-#define PCM_IN_BUFFER_SIZE	(PAGE_SIZE*4)
-#define PCM_IN_BUFFER_PADDING	256
-#define PCM_IN_DMA_CHUNK	(PAGE_SIZE)
-#define PCM_IN_THRESHOLD	(PAGE_SIZE*2)
+static int init_stream_buffer(struct audio_stream *,
+		struct tegra_audio_buf_config *cfg, unsigned);
 
 static int setup_dma(struct audio_driver_state *);
 static void tear_down_dma(struct audio_driver_state *);
@@ -514,6 +526,8 @@ static bool stop_recording_if_necessary_nosync(struct audio_stream *ais)
 	struct audio_driver_state *ads = ads_from_in(ais);
 
 	if (ads->recording_cancelled || kfifo_is_full(&ais->fifo)) {
+		if (kfifo_is_full(&ais->fifo))
+			pr_warn("%s: fifo is full, stop\n", __func__);
 		sound_ops->stop_recording(ais);
 		ais->active = false;
 		return true;
@@ -558,7 +572,7 @@ static int setup_dma(struct audio_driver_state *ads)
 
 	/* setup audio playback */
 	ads->out.buf_phys = dma_map_single(&ads->pdev->dev, ads->out.buffer,
-				PCM_OUT_BUFFER_SIZE, DMA_TO_DEVICE);
+				1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_TO_DEVICE);
 	BUG_ON(!ads->out.buf_phys);
 	setup_dma_tx_request(&ads->out.dma_req, &ads->out);
 	ads->out.dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT);
@@ -571,7 +585,8 @@ static int setup_dma(struct audio_driver_state *ads)
 
 	/* setup audio recording */
 	ads->in.buf_phys = dma_map_single(&ads->pdev->dev, ads->in.buffer,
-				PCM_IN_BUFFER_SIZE, DMA_FROM_DEVICE);
+				1 << PCM_BUFFER_MAX_SIZE_ORDER,
+				DMA_FROM_DEVICE);
 	BUG_ON(!ads->in.buf_phys);
 	setup_dma_rx_request(&ads->in.dma_req, &ads->in);
 	ads->in.dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT);
@@ -586,13 +601,13 @@ static int setup_dma(struct audio_driver_state *ads)
 
 fail_rx:
 	dma_unmap_single(&ads->pdev->dev, ads->in.buf_phys,
-			PCM_IN_BUFFER_SIZE, DMA_FROM_DEVICE);
+			1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_FROM_DEVICE);
 	tegra_dma_free_channel(ads->in.dma_chan);
 	ads->in.dma_chan = 0;
 
 fail_tx:
 	dma_unmap_single(&ads->pdev->dev, ads->out.buf_phys,
-			PCM_OUT_BUFFER_SIZE, DMA_TO_DEVICE);
+			1 << PCM_BUFFER_MAX_SIZE_ORDER, DMA_TO_DEVICE);
 	tegra_dma_free_channel(ads->out.dma_chan);
 	ads->out.dma_chan = 0;
 
@@ -606,14 +621,14 @@ static void tear_down_dma(struct audio_driver_state *ads)
 	tegra_dma_free_channel(ads->out.dma_chan);
 	ads->out.dma_chan = NULL;
 	dma_unmap_single(&ads->pdev->dev, ads->out.buf_phys,
-				PCM_OUT_BUFFER_SIZE,
+				buf_size(&ads->out),
 				DMA_TO_DEVICE);
 	ads->out.buf_phys = 0;
 
 	tegra_dma_free_channel(ads->in.dma_chan);
 	ads->in.dma_chan = NULL;
 	dma_unmap_single(&ads->pdev->dev, ads->in.buf_phys,
-				PCM_IN_BUFFER_SIZE,
+				buf_size(&ads->in),
 				DMA_FROM_DEVICE);
 	ads->in.buf_phys = 0;
 }
@@ -628,7 +643,7 @@ static void dma_tx_complete_callback(struct tegra_dma_req *req)
 
 	kfifo_skip(&aos->fifo, count);
 
-	if (kfifo_avail(&aos->fifo) > PCM_OUT_THRESHOLD &&
+	if (kfifo_avail(&aos->fifo) >= threshold_size(aos) &&
 			!completion_done(&aos->fifo_completion)) {
 		pr_debug("%s: complete (%d avail)\n", __func__,
 				kfifo_avail(&aos->fifo));
@@ -659,7 +674,7 @@ static void dma_rx_complete_callback(struct tegra_dma_req *req)
 	BUG_ON(kfifo_avail(&ais->fifo) < count);
 	__kfifo_add_in(&ais->fifo, count);
 
-	if (kfifo_avail(&ais->fifo) < PCM_IN_THRESHOLD &&
+	if (kfifo_avail(&ais->fifo) <= threshold_size(ais) &&
 			!completion_done(&ais->fifo_completion))
 		complete(&ais->fifo_completion);
 
@@ -742,8 +757,8 @@ static int resume_dma_playback(struct audio_stream *aos)
 			aos->buf_phys + out, req->size, DMA_TO_DEVICE);
 
 	/* Don't send all the data yet. */
-	if (req->size > PCM_OUT_DMA_CHUNK)
-		req->size = PCM_OUT_DMA_CHUNK;
+	if (req->size > chunk_size(aos))
+		req->size = chunk_size(aos);
 	pr_debug("%s resume playback (%d in fifo, writing %d, in %d out %d)\n",
 			__func__, kfifo_len(&aos->fifo), req->size, in, out);
 
@@ -765,12 +780,17 @@ static int start_dma_playback(struct audio_stream *aos)
 /* Called with aos->dma_req_lock taken. */
 static void stop_dma_playback(struct audio_stream *aos)
 {
+	int spin = 0;
 	struct audio_driver_state *ads = ads_from_out(aos);
 	pr_debug("%s\n", __func__);
 	tegra_dma_dequeue_req(ads->out.dma_chan, &ads->out.dma_req);
 	i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 0);
-	while (i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_TX_BUSY)
-		pr_info("%s: spin\n", __func__);
+	while ((i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_TX_BUSY) &&
+			spin < 100)
+		if (spin++ > 50)
+			pr_info("%s: spin %d\n", __func__, spin);
+	if (spin == 100)
+		pr_warn("%s: spinny\n", __func__);
 }
 
 /* This function may be called from either interrupt or process context. */
@@ -797,8 +817,8 @@ static int resume_dma_recording(struct audio_stream *ais)
 		req->size = out - in;
 
 	/* Don't send all the data yet. */
-	if (req->size > PCM_OUT_DMA_CHUNK)
-		req->size = PCM_OUT_DMA_CHUNK;
+	if (req->size > chunk_size(ais))
+		req->size = chunk_size(ais);
 
 	req->size = round_down(req->size, 4);
 
@@ -835,12 +855,17 @@ static int start_dma_recording(struct audio_stream *ais)
 /* Called with ais->dma_req_lock taken. */
 static void stop_dma_recording(struct audio_stream *ais)
 {
+	int spin = 0;
 	struct audio_driver_state *ads = ads_from_in(ais);
 	pr_info("%s\n", __func__);
 	i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 0);
 	i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX);
-	while (i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_RX_BUSY)
-		pr_debug("%s: spin\n", __func__);
+	while ((i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_RX_BUSY) &&
+			spin < 100)
+		if (spin++ > 50)
+			pr_info("%s: spin %d\n", __func__, spin);
+	if (spin == 100)
+		pr_warn("%s: spinny\n", __func__);
 	ais->dma_has_it = false;
 }
 
@@ -969,7 +994,7 @@ static irqreturn_t i2s_interrupt(int irq, void *data)
 
 		pr_debug("%s tx fifo is ready\n", __func__);
 
-		if (kfifo_avail(&out->fifo) > PCM_OUT_THRESHOLD &&
+		if (kfifo_avail(&out->fifo) > threshold_size(out) &&
 				!completion_done(&out->fifo_completion)) {
 			pr_debug("%s: tx complete (%d avail)\n", __func__,
 					kfifo_avail(&out->fifo));
@@ -1033,7 +1058,7 @@ check_rx:
 
 		ads->pio_stats.rx_fifo_read += full * sizeof(u16);
 
-		if (kfifo_avail(&in->fifo) < PCM_IN_THRESHOLD &&
+		if (kfifo_avail(&in->fifo) < threshold_size(in) &&
 				!completion_done(&in->fifo_completion)) {
 			pr_debug("%s: rx complete (%d avail)\n", __func__,
 					kfifo_avail(&in->fifo));
@@ -1080,6 +1105,8 @@ again:
 		goto done;
 	}
 
+	start_playback_if_necessary(&ads->out);
+
 	total += nw;
 	if (total < size) {
 		pr_debug("%s: sleep (user %d total %d nw %d)\n", __func__,
@@ -1099,12 +1126,51 @@ again:
 	rc = total;
 	*off += total;
 
-	start_playback_if_necessary(&ads->out);
 done:
 	mutex_unlock(&ads->out.lock);
 	return rc;
 }
 
+static long tegra_audio_out_ioctl(struct file *file,
+			unsigned int cmd, unsigned long arg)
+{
+	int rc = 0;
+	struct audio_driver_state *ads = ads_from_misc_out(file);
+	struct audio_stream *aos = &ads->out;
+
+	mutex_lock(&aos->lock);
+
+	switch (cmd) {
+	case TEGRA_AUDIO_OUT_SET_BUF_CONFIG: {
+		struct tegra_audio_buf_config cfg;
+		if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg))) {
+			rc = -EFAULT;
+			break;
+		}
+		if (aos->active) {
+			pr_err("%s: playback in progress\n", __func__);
+			rc = -EBUSY;
+			break;
+		}
+		rc = init_stream_buffer(aos, &cfg, 0);
+		if (rc < 0)
+			break;
+		aos->buf_config = cfg;
+	}
+		break;
+	case TEGRA_AUDIO_OUT_GET_BUF_CONFIG:
+		if (copy_to_user((void __user *)arg, &aos->buf_config,
+				sizeof(aos->buf_config)))
+			rc = -EFAULT;
+		break;
+	default:
+		rc = -EINVAL;
+	}
+
+	mutex_unlock(&aos->lock);
+	return rc;
+}
+
 static long tegra_audio_in_ioctl(struct file *file,
 			unsigned int cmd, unsigned long arg)
 {
@@ -1181,6 +1247,28 @@ static long tegra_audio_in_ioctl(struct file *file,
 				sizeof(ads->in_config)))
 			rc = -EFAULT;
 		break;
+	case TEGRA_AUDIO_IN_SET_BUF_CONFIG: {
+		struct tegra_audio_buf_config cfg;
+		if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg))) {
+			rc = -EFAULT;
+			break;
+		}
+		if (ais->active) {
+			pr_err("%s: recording in progress\n", __func__);
+			rc = -EBUSY;
+			break;
+		}
+		rc = init_stream_buffer(ais, &cfg, PCM_IN_BUFFER_PADDING);
+		if (rc < 0)
+			break;
+		ais->buf_config = cfg;
+	}
+		break;
+	case TEGRA_AUDIO_IN_GET_BUF_CONFIG:
+		if (copy_to_user((void __user *)arg, &ais->buf_config,
+				sizeof(ais->buf_config)))
+			rc = -EFAULT;
+		break;
 	default:
 		rc = -EINVAL;
 	}
@@ -1227,8 +1315,8 @@ static int downsample(const s16 *in, int in_len,
 
 	*consumed = i;
 
-	pr_debug("%s: in_len %d out_len %d consumed %d generated %d\n", __func__,
-		in_len, out_len, *consumed, oi);
+	pr_debug("%s: in_len %d out_len %d consumed %d generated %d\n",
+		__func__, in_len, out_len, *consumed, oi);
 	return oi;
 }
 
@@ -1255,6 +1343,7 @@ static ssize_t __downsample_to_user(struct audio_driver_state *ads,
 	}
 
 	*num_consumed *= sizeof(s16);
+	BUG_ON(*num_consumed > src_size);
 
 	kfifo_skip(&ads->in.fifo, *num_consumed);
 
@@ -1327,7 +1416,7 @@ again:
 		if (in < PCM_IN_BUFFER_PADDING)
 			return 0;
 
-		memcpy(ads->in.buffer + PCM_IN_BUFFER_SIZE,
+		memcpy(ads->in.buffer + buf_size(&ads->in),
 			ads->in.buffer,
 			PCM_IN_BUFFER_PADDING);
 		bytes_till_end += PCM_IN_BUFFER_PADDING;
@@ -1356,7 +1445,7 @@ static ssize_t tegra_audio_read(struct file *file, char __user *buf,
 	}
 
 	pr_debug("%s: read %d bytes, %d available\n", __func__,
-			size, kfifo_avail(&ads->in.fifo));
+			size, kfifo_len(&ads->in.fifo));
 
 	if (!ads->recording_cancelled)
 		start_recording_if_necessary(&ads->in);
@@ -1382,7 +1471,7 @@ again:
 	pr_debug("%s: copied %d bytes to user, total %d/%d\n",
 			__func__, nr, total, size);
 
-	if (total < size && ads->in.active) {
+	if (total < size /* && ads->in.active */) {
 		if (!ads->recording_cancelled)
 			start_recording_if_necessary(&ads->in);
 		mutex_unlock(&ads->in.lock);
@@ -1592,6 +1681,7 @@ static const struct file_operations tegra_audio_out_fops = {
 	.open = tegra_audio_out_open,
 	.release = tegra_audio_out_release,
 	.write = tegra_audio_write,
+	.unlocked_ioctl = tegra_audio_out_ioctl,
 };
 
 static const struct file_operations tegra_audio_in_fops = {
@@ -1602,8 +1692,61 @@ static const struct file_operations tegra_audio_in_fops = {
 	.release = tegra_audio_in_release,
 };
 
+static int init_stream_buffer(struct audio_stream *s,
+				struct tegra_audio_buf_config *cfg,
+				unsigned padding)
+{
+	pr_info("%s (size %d threshold %d chunk %d)\n", __func__,
+		cfg->size, cfg->threshold, cfg->chunk);
+
+	if (cfg->chunk < PCM_DMA_CHUNK_MIN_SIZE_ORDER) {
+		pr_err("%s: chunk %d too small (%d min)\n", __func__,
+			cfg->chunk, PCM_DMA_CHUNK_MIN_SIZE_ORDER);
+		return -EINVAL;
+	}
+
+	if (cfg->chunk > cfg->size) {
+		pr_err("%s: chunk %d > size %d\n", __func__,
+				cfg->chunk, cfg->size);
+		return -EINVAL;
+	}
+
+	if (cfg->threshold > cfg->size) {
+		pr_err("%s: threshold %d > size %d\n", __func__,
+				cfg->threshold, cfg->size);
+		return -EINVAL;
+	}
+
+	if ((1 << cfg->size) < padding) {
+		pr_err("%s: size %d < buffer padding %d (bytes)\n", __func__,
+			cfg->size, padding);
+		return -EINVAL;
+	}
+
+	if (cfg->size > PCM_BUFFER_MAX_SIZE_ORDER) {
+		pr_err("%s: size %d exceeds max %d\n", __func__,
+			cfg->size, PCM_BUFFER_MAX_SIZE_ORDER);
+		return -EINVAL;
+	}
+
+	if (!s->buffer) {
+		pr_debug("%s: allocating buffer (size %d, padding %d)\n",
+				__func__, 1 << cfg->size, padding);
+		s->buffer = kmalloc((1 << cfg->size) + padding,
+				GFP_KERNEL | GFP_DMA);
+	}
+	if (!s->buffer) {
+		pr_err("%s: could not allocate output buffer\n", __func__);
+		return -ENOMEM;
+	}
+
+	kfifo_init(&s->fifo, s->buffer, 1 << cfg->size);
+	return 0;
+}
+
 static int tegra_audio_probe(struct platform_device *pdev)
 {
+	int rc;
 	struct resource *res;
 	struct clk *i2s_clk, *audio_sync_clk, *dap_mclk;
 	struct audio_driver_state *state;
@@ -1715,24 +1858,22 @@ static int tegra_audio_probe(struct platform_device *pdev)
 	state->in.dma_chan = NULL;
 	state->in.dma_has_it = false;
 
-	state->out.buffer = kmalloc(PCM_OUT_BUFFER_SIZE, GFP_KERNEL | GFP_DMA);
-	if (!state->out.buffer) {
-		pr_err("%s: could not allocate output buffer\n", __func__);
-		return -ENOMEM;
-	}
-
-	state->in.buffer = kmalloc(PCM_IN_BUFFER_SIZE + PCM_IN_BUFFER_PADDING,
-					GFP_KERNEL | GFP_DMA);
-	if (!state->in.buffer) {
-		pr_err("%s: could not allocate input buffer\n", __func__);
-		return -ENOMEM;
-	}
-
-	kfifo_init(&state->out.fifo, state->out.buffer,
-			PCM_OUT_BUFFER_SIZE);
-
-	kfifo_init(&state->in.fifo, state->in.buffer,
-			PCM_IN_BUFFER_SIZE);
+	state->out.buffer = 0;
+	state->out.buf_config.size = PCM_BUFFER_MAX_SIZE_ORDER;
+	state->out.buf_config.threshold = PAGE_SHIFT + 1;
+	state->out.buf_config.chunk = PAGE_SHIFT;
+	rc = init_stream_buffer(&state->out, &state->out.buf_config, 0);
+	if (rc < 0)
+		return rc;
+
+	state->in.buffer = 0;
+	state->in.buf_config.size = PCM_BUFFER_MAX_SIZE_ORDER;
+	state->in.buf_config.threshold = PAGE_SHIFT + 1;
+	state->in.buf_config.chunk = PAGE_SHIFT;
+	rc = init_stream_buffer(&state->in, &state->in.buf_config,
+			PCM_IN_BUFFER_PADDING);
+	if (rc < 0)
+		return rc;
 
 	if (request_irq(state->irq, i2s_interrupt,
 			IRQF_DISABLED, state->pdev->name, state) < 0) {
diff --git a/include/linux/tegra_audio.h b/include/linux/tegra_audio.h
index 9dc11cc8b871..9461a5f2eda9 100644
--- a/include/linux/tegra_audio.h
+++ b/include/linux/tegra_audio.h
@@ -36,4 +36,20 @@ struct tegra_audio_in_config {
 #define TEGRA_AUDIO_IN_GET_CONFIG	_IOR(TEGRA_AUDIO_MAGIC, 3, \
 			struct tegra_audio_in_config *)
 
+struct tegra_audio_buf_config {
+	unsigned size; /* order */
+	unsigned threshold; /* order */
+	unsigned chunk; /* order */
+};
+
+#define TEGRA_AUDIO_IN_SET_BUF_CONFIG	_IOW(TEGRA_AUDIO_MAGIC, 4, \
+			const struct tegra_audio_buf_config *)
+#define TEGRA_AUDIO_IN_GET_BUF_CONFIG	_IOR(TEGRA_AUDIO_MAGIC, 5, \
+			struct tegra_audio_buf_config *)
+
+#define TEGRA_AUDIO_OUT_SET_BUF_CONFIG	_IOW(TEGRA_AUDIO_MAGIC, 6, \
+			const struct tegra_audio_buf_config *)
+#define TEGRA_AUDIO_OUT_GET_BUF_CONFIG	_IOR(TEGRA_AUDIO_MAGIC, 7, \
+			struct tegra_audio_buf_config *)
+
 #endif/*_CPCAP_AUDIO_H*/
-- 
2.34.1