// SPDX-License-Identifier: GPL-2.0-only
//
// motu-command-dsp-message-parser.c - a part of driver for MOTU FireWire series
//
// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp>

// Below models allow software to configure their DSP function by command transferred in
// asynchronous transaction:
//  * 828 mk3 (FireWire only and Hybrid)
//  * 896 mk3 (FireWire only and Hybrid)
//  * Ultralite mk3 (FireWire only and Hybrid)
//  * Traveler mk3
//  * Track 16
//
// Isochronous packets from the above models includes messages to report state of hardware meter.

#include "motu.h"

enum msg_parser_state {
	INITIALIZED,
	FRAGMENT_DETECTED,
	AVAILABLE,
};

struct msg_parser {
	spinlock_t lock;
	enum msg_parser_state state;
	unsigned int interval;
	unsigned int message_count;
	unsigned int fragment_pos;
	unsigned int value_index;
	u64 value;
	struct snd_firewire_motu_command_dsp_meter meter;
};

int snd_motu_command_dsp_message_parser_new(struct snd_motu *motu)
{
	struct msg_parser *parser;

	parser = devm_kzalloc(&motu->card->card_dev, sizeof(*parser), GFP_KERNEL);
	if (!parser)
		return -ENOMEM;
	spin_lock_init(&parser->lock);
	motu->message_parser = parser;

	return 0;
}

int snd_motu_command_dsp_message_parser_init(struct snd_motu *motu, enum cip_sfc sfc)
{
	struct msg_parser *parser = motu->message_parser;

	parser->state = INITIALIZED;

	// All of data blocks don't have messages with meaningful information.
	switch (sfc) {
	case CIP_SFC_176400:
	case CIP_SFC_192000:
		parser->interval = 4;
		break;
	case CIP_SFC_88200:
	case CIP_SFC_96000:
		parser->interval = 2;
		break;
	case CIP_SFC_32000:
	case CIP_SFC_44100:
	case CIP_SFC_48000:
	default:
		parser->interval = 1;
		break;
	}

	return 0;
}

#define FRAGMENT_POS			6
#define MIDI_BYTE_POS			7
#define MIDI_FLAG_POS			8
// One value of hardware meter consists of 4 messages.
#define FRAGMENTS_PER_VALUE		4
#define VALUES_AT_IMAGE_END		0xffffffffffffffff

void snd_motu_command_dsp_message_parser_parse(struct snd_motu *motu, const struct pkt_desc *descs,
					unsigned int desc_count, unsigned int data_block_quadlets)
{
	struct msg_parser *parser = motu->message_parser;
	unsigned int interval = parser->interval;
	unsigned long flags;
	int i;

	spin_lock_irqsave(&parser->lock, flags);

	for (i = 0; i < desc_count; ++i) {
		const struct pkt_desc *desc = descs + i;
		__be32 *buffer = desc->ctx_payload;
		unsigned int data_blocks = desc->data_blocks;
		int j;

		for (j = 0; j < data_blocks; ++j) {
			u8 *b = (u8 *)buffer;
			buffer += data_block_quadlets;

			switch (parser->state) {
			case INITIALIZED:
			{
				u8 fragment = b[FRAGMENT_POS];

				if (fragment > 0) {
					parser->value = fragment;
					parser->message_count = 1;
					parser->state = FRAGMENT_DETECTED;
				}
				break;
			}
			case FRAGMENT_DETECTED:
			{
				if (parser->message_count % interval == 0) {
					u8 fragment = b[FRAGMENT_POS];

					parser->value >>= 8;
					parser->value |= (u64)fragment << 56;

					if (parser->value == VALUES_AT_IMAGE_END) {
						parser->state = AVAILABLE;
						parser->fragment_pos = 0;
						parser->value_index = 0;
						parser->message_count = 0;
					}
				}
				++parser->message_count;
				break;
			}
			case AVAILABLE:
			default:
			{
				if (parser->message_count % interval == 0) {
					u8 fragment = b[FRAGMENT_POS];

					parser->value >>= 8;
					parser->value |= (u64)fragment << 56;
					++parser->fragment_pos;

					if (parser->fragment_pos == 4) {
						// Skip the last two quadlets since they could be
						// invalid value (0xffffffff) as floating point
						// number.
						if (parser->value_index <
						    SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT - 2) {
							u32 val = (u32)(parser->value >> 32);
							parser->meter.data[parser->value_index] = val;
						}
						++parser->value_index;
						parser->fragment_pos = 0;
					}

					if (parser->value == VALUES_AT_IMAGE_END) {
						parser->value_index = 0;
						parser->fragment_pos = 0;
						parser->message_count = 0;
					}
				}
				++parser->message_count;
				break;
			}
			}
		}
	}

	spin_unlock_irqrestore(&parser->lock, flags);
}

void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu,
					struct snd_firewire_motu_command_dsp_meter *meter)
{
	struct msg_parser *parser = motu->message_parser;
	unsigned long flags;

	spin_lock_irqsave(&parser->lock, flags);
	memcpy(meter, &parser->meter, sizeof(*meter));
	spin_unlock_irqrestore(&parser->lock, flags);
}
