/*
   forward-alsa-fd722.c - ALSA driver for SoftLab-NSK FD722 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 "fd722_reg.h"

#define FD722_IS_M2(dev) (dev->pci_dev->device == 0x0023)

#define FD722_INPUT_REG_INDEX(io) (io->index % 2)
#define FD722_OUTPUT_REG_INDEX(io) (FD722_IS_M2(io->alsa->dev) ? (io->index - 1) : (io->index - 2))

#define FD722_IO_IRQ_NUM_NORMAL(io) \
	((io->index < 2) ? ((io->index) * 4 + 16) : ((io->index - 2) * 2 + 4))
#define FD722_IO_IRQ_NUM_M2(io) \
	((io->index == 0) ? 16 : ((io->index == 2) ? 6 : ((io->state == FORWARD_IO_RX) ? 20 : 4)))
#define FD722_IO_IRQ_NUM(io) \
	(FD722_IS_M2(io->alsa->dev) ? FD722_IO_IRQ_NUM_M2(io) : FD722_IO_IRQ_NUM_NORMAL(io))

#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 forward_irq_t fd722_irq_mask(const struct forward_alsa_io *io)
{
	forward_irq_t result = { 0 };
	forward_irq_flags_set_one(result, FD722_IO_IRQ_NUM(io));
	return result;
}

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

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

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

	buf = forward_irq_extract_data(*irq, FD722_IO_IRQ_NUM(io) + 1, 3);
	buf = FD722_BUFFER_NEXT(buf, cs.progressive ? -2 : -1, FD722_NUMBER_IN_BUFFERS);

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

	return true;
}

inline bool fd722_irq_info_out(const struct forward_alsa_io *io, const forward_irq_t *irq,
			       int *offset, size_t *size, bool *field)
{
	FD722_VideoOutAudio audio;
	int idx = FD722_OUTPUT_REG_INDEX(io);

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

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

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

	return true;
}

inline bool fd722_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 fd722_irq_info_in(io, irq, offset, size, field);
	else
		return fd722_irq_info_out(io, irq, offset, size, field);
}

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

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

static void fd722_update_format(struct forward_alsa_io *io)
{
	struct forward_dev *dev = io->alsa->dev;
	if (io->state == FORWARD_IO_RX) {
		FD722_VideoInCS ics = FD722_VideoInCS_R(dev->csr, FD722_INPUT_REG_INDEX(io));
		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 {
		int idx = FD722_OUTPUT_REG_INDEX(io);
		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;
		io->hw_format = SNDRV_PCM_FORMAT_S32;
		io->hw_channels = (ocs.audioCount + 1) * 4;
	}
}

static int fd722_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 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,
	.update_format = fd722_update_format,
	.start = fd722_start,
	.stop = fd722_stop,
};
