/*
   forward-alsa-fd922.c - ALSA driver for SoftLab-NSK FD922 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 "fd922_reg.h"

#define FD922_IO_IRQ_NUM(io) ((io)->index * 3)
#define FD922_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD922_NUMBER_IN_BUFFERS (4)
#define FD922_NUMBER_OUT_BUFFERS (2)

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

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

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

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

	buf = forward_irq_extract_data(*irq, FD922_IO_IRQ_NUM(io) + 1, 2);
	buf = FD922_BUFFER_NEXT(buf, -1, FD922_NUMBER_IN_BUFFERS);

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

	return true;
}

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

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

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

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

	return true;
}

static bool fd922_irq_info(const struct forward_alsa_io *io, const forward_irq_t *irq, int *offset,
			   size_t *size, bool *field)
{
	if (io->index < 2)
		return fd922_irq_info_in(io, irq, offset, size, field);
	else
		return fd922_irq_info_out(io, irq, offset, size, field);
}

static void fd922_buffer_info(const struct forward_alsa_io *io, u32 *address, size_t *size)
{
	*address = io->index * 0x20000000UL + 0x10000000UL;
	*size = (io->index < 2) ? 0x00400000 : 0x00100000;
}

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

static void fd922_update_format(struct forward_alsa_io *io)
{
	struct forward_dev *dev = io->alsa->dev;
	if (io->index < 2) {
		FD922_VideoInCS ics = FD922_VideoInCS_R(dev->csr, io->index);
		if (ics.mode == 4) {
			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 {
		int idx = io->index - 2;
		FD922_VideoOutCS ocs = FD922_VideoOutCS_R(dev->csr, idx);

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

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

static int fd922_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 fd922_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 fd922_alsa_ops = {
	.irq_mask = fd922_irq_mask,
	.irq_info = fd922_irq_info,
	.buffer_info = fd922_buffer_info,
	.fill_hw = fd922_fill_hw,
	.update_format = fd922_update_format,
	.start = fd922_start,
	.stop = fd922_stop,
};
