/*
   forward-alsa-fd722.c - ALSA driver for SoftLab-NSK FD722 video board

   Copyright (C) 2017 - 2023 Konstantin Oblaukhov <oblaukhov@sl.iae.nsk.su>

   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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "forward-alsa.h"
#include "forward-alsa-io.h"

#include "fd722_reg.h"

#define FD722_BUFFER_NUMBER_IN(in, irq) (((irq) >> ((in)*4 + 17)) & 0x7)
#define FD722_BUFFER_NUMBER_OUT(out, irq) (((irq) >> ((out)*2 + 5)) & 0x1)
#define FD722_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD722_NUMBER_IN_BUFFERS (8)
#define FD722_NUMBER_OUT_BUFFERS (2)

static u32 fd722_irq_mask(const struct forward_alsa_io *io)
{
	if (io->index < 2)
		return (1 << (io->index * 4 + 16));
	else
		return (1 << ((io->index - 2) * 2 + 4));
}

static bool fd722_irq_info_in(const struct forward_alsa_io *io, u32 irq, int *offset, size_t *size)
{
	int buf;
	FD722_VideoInANCCounter anc;
	FD722_VideoInCS cs;

	if (!(irq & (1 << (io->index * 4 + 16))))
		return false;

	anc = FD722_VideoInANCCounter_R(io->alsa->dev->csr, io->index);
	cs = FD722_VideoInCS_R(io->alsa->dev->csr, io->index);

	buf = FD722_BUFFER_NUMBER_IN(io->index, irq);
	buf = FD722_BUFFER_NEXT(buf, cs.progressive ? -2 : -1, FD722_NUMBER_IN_BUFFERS);

	*offset = buf * 0x00100000;
	*size = anc.counter;

	return true;
}

inline bool fd722_irq_info_out(const struct forward_alsa_io *io, u32 irq, int *offset, size_t *size)
{
	FD722_VideoOutAudio audio;
	int idx = io->index - 2;

	if (!(irq & (1 << (idx * 2 + 4))))
		return false;

	audio = FD722_VideoOutAudio_R(io->alsa->dev->csr, idx);

	*offset = audio.address;
	*size = 0x00100000;

	return true;
}

inline bool fd722_irq_info(const struct forward_alsa_io *io, u32 irq, int *offset, size_t *size)
{
	if (io->index < 2)
		return fd722_irq_info_in(io, irq, offset, size);
	else
		return fd722_irq_info_out(io, irq, offset, size);
}

static void fd722_buffer_info(const struct forward_alsa_io *io, u32 *address, size_t *size)
{
	if (io->index < 2) {
		*address = io->index * 0x00800000UL + 0x04000000UL;
		*size = 0x00800000;
	} else {
		*address = (io->index - 2) * 0x00100000UL + 0x07000000UL;
		*size = 0x00100000;
	}
}

static void fd722_fill_hw(struct forward_alsa_io *io, struct snd_pcm_hardware *hw)
{
}

static int fd722_start(struct forward_alsa_io *io)
{
	struct forward_dev *dev = io->alsa->dev;

	if (io->index < 2) {
		FD722_VideoInCS ics = FD722_VideoInCS_R(dev->csr, io->index);
		if (ics.mode == 1)
			io->type = FORWARD_ALSA_SMPTE_272;
		else
			io->type = FORWARD_ALSA_SMPTE_299;
	} else {
		int idx = io->index - 2;
		FD722_VideoOutCS ocs = FD722_VideoOutCS_R(dev->csr, idx);

		ocs.audioCount = ((io->stream->runtime->channels - 1) / 4);
		FD722_VideoOutCS_W(dev->csr, idx, ocs);

		io->type = FORWARD_ALSA_RAW;
	}
	dev->cfg.toggle_streaming(dev, io->index, true);
	return 0;
}

static void fd722_stop(struct forward_alsa_io *io)
{
	struct forward_dev *dev = io->alsa->dev;
	dev->cfg.toggle_streaming(dev, io->index, false);
}

struct forward_alsa_dev_ops fd722_alsa_ops = {
	.irq_mask = fd722_irq_mask,
	.irq_info = fd722_irq_info,
	.buffer_info = fd722_buffer_info,
	.fill_hw = fd722_fill_hw,
	.start = fd722_start,
	.stop = fd722_stop,
};
