// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2013 Intel Corporation. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 *
 * 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.
 *
 *
 */
#include <linux/i2c.h>
#include <linux/firmware.h>
#include <linux/device.h>
#include <linux/export.h>
#include "../include/linux/libmsrlisthelper.h"
#include <linux/module.h>
#include <linux/slab.h>

/* Tagged binary data container structure definitions. */
struct tbd_header {
	u32 tag;          /*!< Tag identifier, also checks endianness */
	u32 size;         /*!< Container size including this header */
	u32 version;      /*!< Version, format 0xYYMMDDVV */
	u32 revision;     /*!< Revision, format 0xYYMMDDVV */
	u32 config_bits;  /*!< Configuration flag bits set */
	u32 checksum;     /*!< Global checksum, header included */
} __packed;

struct tbd_record_header {
	u32 size;        /*!< Size of record including header */
	u8 format_id;    /*!< tbd_format_t enumeration values used */
	u8 packing_key;  /*!< Packing method; 0 = no packing */
	u16 class_id;    /*!< tbd_class_t enumeration values used */
} __packed;

struct tbd_data_record_header {
	u16 next_offset;
	u16 flags;
	u16 data_offset;
	u16 data_size;
} __packed;

#define TBD_CLASS_DRV_ID 2

static int set_msr_configuration(struct i2c_client *client, uint8_t *bufptr,
				 unsigned int size)
{
	/* The configuration data contains any number of sequences where
	 * the first byte (that is, uint8_t) that marks the number of bytes
	 * in the sequence to follow, is indeed followed by the indicated
	 * number of bytes of actual data to be written to sensor.
	 * By convention, the first two bytes of actual data should be
	 * understood as an address in the sensor address space (hibyte
	 * followed by lobyte) where the remaining data in the sequence
	 * will be written. */

	u8 *ptr = bufptr;

	while (ptr < bufptr + size) {
		struct i2c_msg msg = {
			.addr = client->addr,
			.flags = 0,
		};
		int ret;

		/* How many bytes */
		msg.len = *ptr++;
		/* Where the bytes are located */
		msg.buf = ptr;
		ptr += msg.len;

		if (ptr > bufptr + size)
			/* Accessing data beyond bounds is not tolerated */
			return -EINVAL;

		ret = i2c_transfer(client->adapter, &msg, 1);
		if (ret < 0) {
			dev_err(&client->dev, "i2c write error: %d", ret);
			return ret;
		}
	}
	return 0;
}

static int parse_and_apply(struct i2c_client *client, uint8_t *buffer,
			   unsigned int size)
{
	u8 *endptr8 = buffer + size;
	struct tbd_data_record_header *header =
	    (struct tbd_data_record_header *)buffer;

	/* There may be any number of datasets present */
	unsigned int dataset = 0;

	do {
		/* In below, four variables are read from buffer */
		if ((uint8_t *)header + sizeof(*header) > endptr8)
			return -EINVAL;

		/* All data should be located within given buffer */
		if ((uint8_t *)header + header->data_offset +
		    header->data_size > endptr8)
			return -EINVAL;

		/* We have a new valid dataset */
		dataset++;
		/* See whether there is MSR data */
		/* If yes, update the reg info */
		if (header->data_size && (header->flags & 1)) {
			int ret;

			dev_info(&client->dev,
				 "New MSR data for sensor driver (dataset %02d) size:%d\n",
				 dataset, header->data_size);
			ret = set_msr_configuration(client,
						    buffer + header->data_offset,
						    header->data_size);
			if (ret)
				return ret;
		}
		header = (struct tbd_data_record_header *)(buffer +
			 header->next_offset);
	} while (header->next_offset);

	return 0;
}

int apply_msr_data(struct i2c_client *client, const struct firmware *fw)
{
	struct tbd_header *header;
	struct tbd_record_header *record;

	if (!fw) {
		dev_warn(&client->dev, "Drv data is not loaded.\n");
		return -EINVAL;
	}

	if (sizeof(*header) > fw->size)
		return -EINVAL;

	header = (struct tbd_header *)fw->data;
	/* Check that we have drvb block. */
	if (memcmp(&header->tag, "DRVB", 4))
		return -EINVAL;

	/* Check the size */
	if (header->size != fw->size)
		return -EINVAL;

	if (sizeof(*header) + sizeof(*record) > fw->size)
		return -EINVAL;

	record = (struct tbd_record_header *)(header + 1);
	/* Check that class id mathes tbd's drv id. */
	if (record->class_id != TBD_CLASS_DRV_ID)
		return -EINVAL;

	/* Size 0 shall not be treated as an error */
	if (!record->size)
		return 0;

	return parse_and_apply(client, (uint8_t *)(record + 1), record->size);
}
EXPORT_SYMBOL_GPL(apply_msr_data);

int load_msr_list(struct i2c_client *client, char *name,
		  const struct firmware **fw)
{
	int ret = request_firmware(fw, name, &client->dev);

	if (ret) {
		dev_err(&client->dev,
			"Error %d while requesting firmware %s\n",
			ret, name);
		return ret;
	}
	dev_info(&client->dev, "Received %lu bytes drv data\n",
		 (unsigned long)(*fw)->size);

	return 0;
}
EXPORT_SYMBOL_GPL(load_msr_list);

void release_msr_list(struct i2c_client *client, const struct firmware *fw)
{
	release_firmware(fw);
}
EXPORT_SYMBOL_GPL(release_msr_list);

static int init_msrlisthelper(void)
{
	return 0;
}

static void exit_msrlisthelper(void)
{
}

module_init(init_msrlisthelper);
module_exit(exit_msrlisthelper);

MODULE_AUTHOR("Jukka Kaartinen <jukka.o.kaartinen@intel.com>");
MODULE_LICENSE("GPL");
