/*
   forward-alsa-fd788.c - ALSA driver for SoftLab-NSK FD788 video board

   Copyright (C) 2017 - 2024 SoftLab-NSK <forward@softlab.tv>

   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 "fd788_reg.h"

#define FD788_IO_IRQ_NUM(io) ((io)->index * 3)
#define FD788_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD788_NUMBER_IN_BUFFERS (4)
#define FD788_NUMBER_OUT_BUFFERS (2)

static forward_irq_t fd788_irq_mask(const struct forward_alsa_io *io)
{
	forward_irq_t result = { 0 };
	forward_irq_flags_set_one(result, FD788_IO_IRQ_NUM(io));
	return result;
}

inline bool fd788_irq_info_in(const struct forward_alsa_io *io, const forward_irq_t *irq,
			      int *offset, size_t *size, bool *field)
{
	int buf;
	FD788_VideoInANCCounter anc;

	if (!forward_irq_has_irq(*irq, FD788_IO_IRQ_NUM(io)))
		return false;

	anc = FD788_VideoInANCCounter_R(io->alsa->dev->csr, io->index);

	buf = forward_irq_extract_data(*irq, FD788_IO_IRQ_NUM(io) + 1, 2);
	buf = FD788_BUFFER_NEXT(buf, -1, FD788_NUMBER_IN_BUFFERS);

	*offset = buf * 0x00100000;
	*size = anc.counter;
	*field = io->interlaced ? (buf & 0x1) : false;

	return true;
}

inline bool fd788_irq_info_out(const struct forward_alsa_io *io, const forward_irq_t *irq,
			       int *offset, size_t *size, bool *field)
{
	FD788_VideoOutAudio audio;

	if (!forward_irq_has_irq(*irq, FD788_IO_IRQ_NUM(io)))
		return false;

	audio = FD788_VideoOutAudio_R(io->alsa->dev->csr, io->index);

	*offset = audio.address;
	*size = 0x00100000;
	*field = io->interlaced ? !forward_irq_extract_data(*irq, FD788_IO_IRQ_NUM(io) + 1, 1) :
				  false;

	return true;
}

static bool fd788_irq_info(const struct forward_alsa_io *io, const forward_irq_t *irq, int *offset,
			   size_t *size, bool *field)
{
	if (io->state == FORWARD_IO_RX)
		return fd788_irq_info_in(io, irq, offset, size, field);
	else
		return fd788_irq_info_out(io, irq, offset, size, field);
}

static void fd788_buffer_info(const struct forward_alsa_io *io, u32 *address, size_t *size)
{
	FD788_VideoInCS cs = FD788_VideoInCS_R(io->alsa->dev->csr, io->index);

	if (cs.dqEnable)
		if (cs.dqQuad)
			*address = io->index * 0x08000000UL + 0x10000000UL;
		else
			*address = io->index * 0x08000000UL + 0x08000000UL;
	else
		*address = io->index * 0x08000000UL + 0x04000000UL;

	*size = (io->state == FORWARD_IO_RX) ? 0x00400000 : 0x00100000;
}

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

static void fd788_update_format(struct forward_alsa_io *io)
{
	struct forward_dev *dev = io->alsa->dev;
	if (io->state == FORWARD_IO_RX) {
		FD788_VideoInCS ics = FD788_VideoInCS_R(dev->csr, io->index);
		if (ics.mode == 1) {
			io->type = FORWARD_ALSA_SMPTE_272;
			io->hw_format = SNDRV_PCM_FORMAT_S20;
		} else {
			io->type = FORWARD_ALSA_SMPTE_299;
			io->hw_format = SNDRV_PCM_FORMAT_S24;
		}
		io->hw_channels = 0;
	} else {
		FD788_VideoOutCS ocs = FD788_VideoOutCS_R(dev->csr, io->index);

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

		io->type = FORWARD_ALSA_RAW;
		io->hw_format = SNDRV_PCM_FORMAT_S32;
		io->hw_channels = (ocs.audioCount + 1) * 4;
	}
}

static int fd788_start(struct forward_alsa_io *io)
{
	struct forward_dev *dev = io->alsa->dev;
	dev->cfg.toggle_streaming(dev, io->index, true);
	return 0;
}

static void fd788_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 fd788_alsa_ops = {
	.irq_mask = fd788_irq_mask,
	.irq_info = fd788_irq_info,
	.buffer_info = fd788_buffer_info,
	.fill_hw = fd788_fill_hw,
	.update_format = fd788_update_format,
	.start = fd788_start,
	.stop = fd788_stop,
};
