/*
 *  Loopback soundcard
 *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 */

#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/pcm.h>
#include <sound/initval.h>

MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("A loopback soundcard");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}");

#define MAX_PCM_SUBSTREAMS	8

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};

module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for loopback soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable this loopback soundcard.");
module_param_array(pcm_substreams, int, NULL, 0444);
MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");

typedef struct snd_card_loopback_cable {
	struct snd_pcm_substream *playback;
	struct snd_pcm_substream *capture;
	struct snd_dma_buffer *dma_buffer;
	struct snd_pcm_hardware hw;
	int playback_valid;
	int capture_valid;
	int playback_running;
	int capture_running;
	int playback_busy;
	int capture_busy;
} snd_card_loopback_cable_t;

typedef struct snd_card_loopback {
	struct snd_card *card;
	struct snd_card_loopback_cable cables[MAX_PCM_SUBSTREAMS][2];
} snd_card_loopback_t;

typedef struct snd_card_loopback_pcm {
	snd_card_loopback_t *loopback;
	spinlock_t lock;
	struct timer_list timer;
	unsigned int pcm_buffer_size;
	unsigned int pcm_period_size;
	unsigned int pcm_bps;		/* bytes per second */
	unsigned int pcm_hz;		/* HZ */
	unsigned int pcm_irq_pos;	/* IRQ position */
	unsigned int pcm_buf_pos;	/* position in buffer */
	struct snd_pcm_substream *substream;
	struct snd_card_loopback_cable *cable;
} snd_card_loopback_pcm_t;

static struct snd_card *snd_loopback_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;

static void snd_card_loopback_timer_start(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;

	dpcm->timer.expires = 1 + jiffies;
	add_timer(&dpcm->timer);
}

static void snd_card_loopback_timer_stop(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;

	del_timer(&dpcm->timer);
}

static int snd_card_loopback_playback_trigger(struct snd_pcm_substream *substream,
					   int cmd)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	if (cmd == SNDRV_PCM_TRIGGER_START) {
		dpcm->cable->playback_running = 1;
		snd_card_loopback_timer_start(substream);
	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
		dpcm->cable->playback_running = 0;
		snd_card_loopback_timer_stop(substream);
		snd_pcm_format_set_silence(runtime->format, runtime->dma_area,
				bytes_to_samples(runtime, runtime->dma_bytes));

	} else {
		return -EINVAL;
	}
	return 0;
}

static int snd_card_loopback_capture_trigger(struct snd_pcm_substream *substream,
					  int cmd)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	if (cmd == SNDRV_PCM_TRIGGER_START) {
		dpcm->cable->capture_running = 1;
		snd_card_loopback_timer_start(substream);
	} else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
		dpcm->cable->capture_running = 0;
		snd_card_loopback_timer_stop(substream);
	} else {
		return -EINVAL;
	}
	return 0;
}

static int snd_card_loopback_prepare(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	snd_card_loopback_cable_t *cable = dpcm->cable;
	unsigned int bps;

	bps = runtime->rate * runtime->channels;
	bps *= snd_pcm_format_width(runtime->format);
	bps /= 8;
	if (bps <= 0)
		return -EINVAL;
	dpcm->pcm_bps = bps;
	dpcm->pcm_hz = HZ;
	dpcm->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size);
	dpcm->pcm_period_size = frames_to_bytes(runtime, runtime->period_size);
	dpcm->pcm_irq_pos = 0;
	dpcm->pcm_buf_pos = 0;

	cable->hw.formats = (1ULL << runtime->format);
	cable->hw.rate_min = runtime->rate;
	cable->hw.rate_max = runtime->rate;
	cable->hw.channels_min = runtime->channels;
	cable->hw.channels_max = runtime->channels;
	cable->hw.buffer_bytes_max = frames_to_bytes(runtime, runtime->buffer_size);
	cable->hw.period_bytes_min = frames_to_bytes(runtime, runtime->period_size);
	cable->hw.period_bytes_max = frames_to_bytes(runtime, runtime->period_size);
	cable->hw.periods_min = runtime->periods;
	cable->hw.periods_max = runtime->periods;

	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
		snd_pcm_format_set_silence(runtime->format, runtime->dma_area,
				bytes_to_samples(runtime, runtime->dma_bytes));
		cable->playback_valid = 1;
	} else {
		if (!cable->playback_running) {
			snd_pcm_format_set_silence(runtime->format, runtime->dma_area,
					bytes_to_samples(runtime, runtime->dma_bytes));
		}
		cable->capture_valid = 1;
	}

	return 0;
}

static void snd_card_loopback_timer_function(unsigned long data)
{
	snd_card_loopback_pcm_t *dpcm = (snd_card_loopback_pcm_t *)data;
	
	dpcm->timer.expires = 1 + jiffies;
	add_timer(&dpcm->timer);
	spin_lock_irq(&dpcm->lock);

	dpcm->pcm_irq_pos += dpcm->pcm_bps;
	dpcm->pcm_buf_pos += dpcm->pcm_bps;
	dpcm->pcm_buf_pos %= dpcm->pcm_buffer_size * dpcm->pcm_hz;
	if (dpcm->pcm_irq_pos >= dpcm->pcm_period_size * dpcm->pcm_hz) {
		dpcm->pcm_irq_pos %= dpcm->pcm_period_size * dpcm->pcm_hz;
		spin_unlock_irq(&dpcm->lock);	
		snd_pcm_period_elapsed(dpcm->substream);
	} else {
		spin_unlock_irq(&dpcm->lock);
	}
}

static snd_pcm_uframes_t snd_card_loopback_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	return bytes_to_frames(runtime, dpcm->pcm_buf_pos / dpcm->pcm_hz);
}

static struct snd_pcm_hardware snd_card_loopback_info =
{
	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
				 SNDRV_PCM_INFO_MMAP_VALID),
	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |
				 SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT_LE),
	.rates =		(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000),
	.rate_min =		8000,
	.rate_max =		192000,
	.channels_min =		1,
	.channels_max =		32,
	.buffer_bytes_max =	64 * 1024,
	.period_bytes_min =	64,
	.period_bytes_max =	64 * 1024,
	.periods_min =		1,
	.periods_max =		1024,
	.fifo_size =		0,
};

static void snd_card_loopback_runtime_free(struct snd_pcm_runtime *runtime)
{
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	kfree(dpcm);
}

static int snd_card_loopback_hw_params(struct snd_pcm_substream *substream,
				    struct snd_pcm_hw_params *hw_params)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	struct snd_dma_buffer *dmab = NULL;
	if (NULL == dpcm->cable->dma_buffer) {
		dmab = kzalloc(sizeof(*dmab), GFP_KERNEL);
		if (NULL == dmab)
			return -ENOMEM;
		
		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_CONTINUOUS,
					snd_dma_continuous_data(GFP_KERNEL),
					params_buffer_bytes(hw_params),
					dmab) < 0) {
			kfree(dmab);
			return -ENOMEM;
		}
		dpcm->cable->dma_buffer = dmab;
	}
	snd_pcm_set_runtime_buffer(substream, dpcm->cable->dma_buffer);
	return 0;
}

static int snd_card_loopback_hw_free(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	snd_card_loopback_cable_t *cable = dpcm->cable;

	snd_pcm_set_runtime_buffer(substream, NULL);

	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
		if (cable->capture_busy)
			return 0;
	} else {
		if (cable->playback_busy)
			return 0;
	}

	if (NULL == cable->dma_buffer)
		return 0;

	snd_dma_free_pages(cable->dma_buffer);
	kfree(cable->dma_buffer);
	cable->dma_buffer = NULL;
	return 0;
}

static int snd_card_loopback_open(struct snd_pcm_substream *substream)
{
	int half;
	snd_card_loopback_pcm_t *dpcm;
	struct snd_card_loopback *loopback = substream->private_data;
	struct snd_pcm_runtime *runtime = substream->runtime;

	if (0 == substream->pcm->device) {
		if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream)
			half = 1;
		else
			half = 0;
	} else {
		if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream)
			half = 0;
		else
			half = 1;
	}
	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
		if (loopback->cables[substream->number][half].playback_busy)
			return -EBUSY;
	} else {
		if (loopback->cables[substream->number][half].capture_busy)
			return -EBUSY;
	}
	dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;
	init_timer(&dpcm->timer);
	spin_lock_init(&dpcm->lock);
	dpcm->substream = substream;
	dpcm->loopback = loopback;
	dpcm->timer.data = (unsigned long)dpcm;
	dpcm->timer.function = snd_card_loopback_timer_function;
	dpcm->cable = &loopback->cables[substream->number][half];
	runtime->private_data = dpcm;
	runtime->private_free = snd_card_loopback_runtime_free;
	runtime->hw = snd_card_loopback_info;

	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
		dpcm->cable->playback = substream;
		dpcm->cable->playback_valid = 0;
		dpcm->cable->playback_running = 0;
		dpcm->cable->playback_busy = 1;
		if (dpcm->cable->capture_valid)
			runtime->hw = dpcm->cable->hw;
		else
			dpcm->cable->hw = snd_card_loopback_info;
	} else {
		dpcm->cable->capture = substream;
		dpcm->cable->capture_valid = 0;
		dpcm->cable->capture_running = 0;
		dpcm->cable->capture_busy = 1;
		if (dpcm->cable->playback_valid)
			runtime->hw = dpcm->cable->hw;
		else
			dpcm->cable->hw = snd_card_loopback_info;
	}
	return 0;
}

static int snd_card_loopback_close(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	snd_card_loopback_pcm_t *dpcm = runtime->private_data;
	if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
		dpcm->cable->playback_valid = 0;
		dpcm->cable->playback_running = 0;
		dpcm->cable->playback_busy = 0;
		dpcm->cable->playback = NULL;
	} else {
		dpcm->cable->capture_valid = 0;
		dpcm->cable->capture_running = 0;
		dpcm->cable->capture_busy = 0;
		dpcm->cable->capture = NULL;
	}
	return 0;
}

static struct snd_pcm_ops snd_card_loopback_playback_ops = {
	.open =			snd_card_loopback_open,
	.close =		snd_card_loopback_close,
	.ioctl =		snd_pcm_lib_ioctl,
	.hw_params =		snd_card_loopback_hw_params,
	.hw_free =		snd_card_loopback_hw_free,
	.prepare =		snd_card_loopback_prepare,
	.trigger =		snd_card_loopback_playback_trigger,
	.pointer =		snd_card_loopback_pointer,
};

static struct snd_pcm_ops snd_card_loopback_capture_ops = {
	.open =			snd_card_loopback_open,
	.close =		snd_card_loopback_close,
	.ioctl =		snd_pcm_lib_ioctl,
	.hw_params =		snd_card_loopback_hw_params,
	.hw_free =		snd_card_loopback_hw_free,
	.prepare =		snd_card_loopback_prepare,
	.trigger =		snd_card_loopback_capture_trigger,
	.pointer =		snd_card_loopback_pointer,
};

static int __init snd_card_loopback_pcm(snd_card_loopback_t *loopback, int device, int substreams)
{
	struct snd_pcm *pcm;
	int err;

	if ((err = snd_pcm_new(loopback->card, "Loopback PCM", device, substreams, substreams, &pcm)) < 0)
		return err;
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_loopback_playback_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_loopback_capture_ops);

	pcm->private_data = loopback;
	pcm->info_flags = 0;
	strcpy(pcm->name, "Loopback PCM");
	return 0;
}

static int __init snd_card_loopback_new_mixer(snd_card_loopback_t *loopback)
{
	struct snd_card *card = loopback->card;

	snd_assert(loopback != NULL, return -EINVAL);
	strcpy(card->mixername, "Loopback Mixer");
	return 0;
}

static int __init snd_card_loopback_probe(int dev)
{
	struct snd_card *card;
	struct snd_card_loopback *loopback;
	int subdev, half, err;

	if (!enable[dev])
		return -ENODEV;
	card = snd_card_new(index[dev], id[dev], THIS_MODULE,
			    sizeof(struct snd_card_loopback));
	if (card == NULL)
		return -ENOMEM;
	loopback = (struct snd_card_loopback *)card->private_data;

	if (pcm_substreams[dev] < 1)
		pcm_substreams[dev] = 1;
	if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
		pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;

	for (subdev = 0; subdev < pcm_substreams[dev]; subdev++) {
		for (half = 0; half < 2; half++) {
			loopback->cables[subdev][half].playback = NULL;
			loopback->cables[subdev][half].capture = NULL;
			loopback->cables[subdev][half].dma_buffer = NULL;
			loopback->cables[subdev][half].playback_valid = 0;
			loopback->cables[subdev][half].capture_valid = 0;
			loopback->cables[subdev][half].playback_running = 0;
			loopback->cables[subdev][half].capture_running = 0;
			loopback->cables[subdev][half].playback_busy = 0;
			loopback->cables[subdev][half].capture_busy = 0;
		}
	}
	
	loopback->card = card;
	if ((err = snd_card_loopback_pcm(loopback, 0, pcm_substreams[dev])) < 0)
		goto __nodev;
	if ((err = snd_card_loopback_pcm(loopback, 1, pcm_substreams[dev])) < 0)
		goto __nodev;
	if ((err = snd_card_loopback_new_mixer(loopback)) < 0)
		goto __nodev;
	strcpy(card->driver, "Loopback");
	strcpy(card->shortname, "Loopback");
	sprintf(card->longname, "Loopback %i", dev + 1);
	if ((err = snd_card_register(card)) == 0) {
		snd_loopback_cards[dev] = card;
		return 0;
	}
      __nodev:
	snd_card_free(card);
	return err;
}

static int __init alsa_card_loopback_init(void)
{
	int dev, cards;

	for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) {
		if (snd_card_loopback_probe(dev) < 0) {
#ifdef MODULE
			printk(KERN_ERR "Loopback soundcard #%i not found or device busy\n", dev + 1);
#endif
			break;
		}
		cards++;
	}
	if (!cards) {
#ifdef MODULE
		printk(KERN_ERR "Loopback soundcard not found or device busy\n");
#endif
		return -ENODEV;
	}
	return 0;
}

static void __exit alsa_card_loopback_exit(void)
{
	int idx;

	for (idx = 0; idx < SNDRV_CARDS; idx++)
		snd_card_free(snd_loopback_cards[idx]);
}

module_init(alsa_card_loopback_init)
module_exit(alsa_card_loopback_exit)
