/*
   forward-alsa-fd940.c - ALSA driver for SoftLab-NSK FD940 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 "fd940_reg.h"

#define FD940_IO_IRQ_NUM(io) ((io)->index * 3)
#define FD940_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD940_NUMBER_IN_BUFFERS (4)

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

static bool fd940_irq_info(const struct forward_alsa_io *io, const forward_irq_t *irq, int *offset,
			   size_t *size, bool *field)
{
	int buf;
	FD940_VideoInANCCounter anc;

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

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

	buf = forward_irq_extract_data(*irq, FD940_IO_IRQ_NUM(io) + 1, 2);
	buf = FD940_BUFFER_NEXT(buf, -1, FD940_NUMBER_IN_BUFFERS);

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

	return true;
}

static void fd940_buffer_info(const struct forward_alsa_io *io, u32 *address, size_t *size)
{
	*address = io->index * 0x10000000UL + 0x08000000UL;
	*size = 0x00800000;
}

static void fd940_fill_hw(struct forward_alsa_io *io, struct snd_pcm_hardware *hw)
{
	FD940_VideoInInfoFrame inf = FD940_VideoInInfoFrame_R(io->alsa->dev->csr, io->index);

	hw->formats = SNDRV_PCM_FMTBIT_S32_LE;
	hw->rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
		    SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
		    SNDRV_PCM_RATE_192000;
	hw->rate_min = 32000;
	hw->rate_max = 96000;
	hw->channels_min = 0;
	hw->channels_max = 8;
	hw->buffer_bytes_max = 2 * 1024 * 1024;
	hw->period_bytes_min = 4 * 1024;
	hw->period_bytes_max = 128 * 1024;
	hw->periods_min = 4;
	hw->periods_max = 128;

	if (!inf.aiValid)
		return;

	switch (inf.aiSF) {
	case 1:
		hw->rates = SNDRV_PCM_RATE_32000;
		hw->rate_min = 32000;
		hw->rate_max = 32000;
		break;
	case 2:
		hw->rates = SNDRV_PCM_RATE_44100;
		hw->rate_min = 44100;
		hw->rate_max = 44100;
		break;
	case 3:
		hw->rates = SNDRV_PCM_RATE_48000;
		hw->rate_min = 48000;
		hw->rate_max = 48000;
		break;
	case 4:
		hw->rates = SNDRV_PCM_RATE_88200;
		hw->rate_min = 88200;
		hw->rate_max = 88200;
		break;
	case 5:
		hw->rates = SNDRV_PCM_RATE_96000;
		hw->rate_min = 96000;
		hw->rate_max = 96000;
		break;
	case 6:
		hw->rates = SNDRV_PCM_RATE_176400;
		hw->rate_min = 176400;
		hw->rate_max = 176400;
		break;
	case 7:
		hw->rates = SNDRV_PCM_RATE_192000;
		hw->rate_min = 192000;
		hw->rate_max = 192000;
		break;
	default:
		break;
	}
	if (inf.aiCC > 0) {
		hw->channels_min = inf.aiCC + 1;
		hw->channels_max = inf.aiCC + 1;
	}
}

static void fd940_update_format(struct forward_alsa_io *io)
{
	io->type = FORWARD_ALSA_HDMI;
	io->hw_format = SNDRV_PCM_FORMAT_S24;
	io->hw_channels = 0;
}

static int fd940_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 fd940_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 fd940_alsa_ops = {
	.irq_mask = fd940_irq_mask,
	.irq_info = fd940_irq_info,
	.buffer_info = fd940_buffer_info,
	.fill_hw = fd940_fill_hw,
	.update_format = fd940_update_format,
	.start = fd940_start,
	.stop = fd940_stop,
};
