// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) STMicroelectronics SA 2013
 * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
 */

#include "delta.h"
#include "delta-mjpeg.h"

#define MJPEG_SOF_0  0xc0
#define MJPEG_SOF_1  0xc1
#define MJPEG_SOI    0xd8
#define MJPEG_MARKER 0xff

static char *header_str(struct mjpeg_header *header,
			char *str,
			unsigned int len)
{
	char *cur = str;
	unsigned int left = len;

	if (!header)
		return "";

	snprintf(cur, left, "[MJPEG header]\n"
			"|- length     = %d\n"
			"|- precision  = %d\n"
			"|- width      = %d\n"
			"|- height     = %d\n"
			"|- components = %d\n",
			header->length,
			header->sample_precision,
			header->frame_width,
			header->frame_height,
			header->nb_of_components);

	return str;
}

static int delta_mjpeg_read_sof(struct delta_ctx *pctx,
				unsigned char *data, unsigned int size,
				struct mjpeg_header *header)
{
	struct delta_dev *delta = pctx->dev;
	unsigned int offset = 0;

	if (size < 64)
		goto err_no_more;

	memset(header, 0, sizeof(*header));
	header->length           = be16_to_cpu(*(__be16 *)(data + offset));
	offset += sizeof(u16);
	header->sample_precision = *(u8 *)(data + offset);
	offset += sizeof(u8);
	header->frame_height     = be16_to_cpu(*(__be16 *)(data + offset));
	offset += sizeof(u16);
	header->frame_width      = be16_to_cpu(*(__be16 *)(data + offset));
	offset += sizeof(u16);
	header->nb_of_components = *(u8 *)(data + offset);
	offset += sizeof(u8);

	if (header->nb_of_components >= MJPEG_MAX_COMPONENTS) {
		dev_err(delta->dev,
			"%s   unsupported number of components (%d > %d)\n",
			pctx->name, header->nb_of_components,
			MJPEG_MAX_COMPONENTS);
		return -EINVAL;
	}

	if ((offset + header->nb_of_components *
	     sizeof(header->components[0])) > size)
		goto err_no_more;

	return 0;

err_no_more:
	dev_err(delta->dev,
		"%s   sof: reached end of %d size input stream\n",
		pctx->name, size);
	return -ENODATA;
}

int delta_mjpeg_read_header(struct delta_ctx *pctx,
			    unsigned char *data, unsigned int size,
			    struct mjpeg_header *header,
			    unsigned int *data_offset)
{
	struct delta_dev *delta = pctx->dev;
	unsigned char str[200];

	unsigned int ret = 0;
	unsigned int offset = 0;
	unsigned int soi = 0;

	if (size < 2)
		goto err_no_more;

	offset = 0;
	while (1) {
		if (data[offset] == MJPEG_MARKER)
			switch (data[offset + 1]) {
			case MJPEG_SOI:
				soi = 1;
				*data_offset = offset;
				break;

			case MJPEG_SOF_0:
			case MJPEG_SOF_1:
				if (!soi) {
					dev_err(delta->dev,
						"%s   wrong sequence, got SOF while SOI not seen\n",
						pctx->name);
					return -EINVAL;
				}

				ret = delta_mjpeg_read_sof(pctx,
							   &data[offset + 2],
							   size - (offset + 2),
							   header);
				if (ret)
					goto err;

				goto done;

			default:
				break;
			}

		offset++;
		if ((offset + 2) >= size)
			goto err_no_more;
	}

done:
	dev_dbg(delta->dev,
		"%s   found header @ offset %d:\n%s", pctx->name,
		*data_offset,
		header_str(header, str, sizeof(str)));
	return 0;

err_no_more:
	dev_err(delta->dev,
		"%s   no header found within %d bytes input stream\n",
		pctx->name, size);
	return -ENODATA;

err:
	return ret;
}