// SPDX-License-Identifier: GPL-2.0
/*
 * cus_ini_parser.c -- AD82088/AD82088D amp ini parser
 *
 * Copyright (c) 2020 MediaTek Inc.
 */

#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/io.h>

#include "cus_ini_parser.h"

#define FORMAT_MAX	2			/* 9 + \0 */
#define COUNT_MAX	5			/* 9999 + '\0' */
#define IDX_MAX		13			/* index_9999 = + '\0' */
#define SOUND_MODE_SIZE_MAX	13		/* sound_mode_9 + '\0'*/
#define VOL_CONFIG_SIZE_MAX	14		/* volume_100 = + '\0'*/
#define VOL_THRESHOLD_LEN	300		/* volume_threshold = "0,1,2,....,99,100" + '\0'*/
#define VOLUME_SOUND_MODE_SIZE_MAX	20	/* sound_mode_9_vol100 + '\0'*/

#define AD82088_TYPE	1
#define AD82088D_TYPE	2

#define SINGLE_REGISTER		1
#define SINGLE_RAM		2
#define MULTIPLE_RAM		3

/* address(1 byte) + value(1 byte) */
#define SINGLE_REGISTER_BYTE		2
/* ram bank index(1 byte) + address (1 byte) + 3 params(3 * 1 byte)*/
#define AD82088_SINGLE_RAM_BYTE		5
/* ram bank index(1 byte) + address (1 byte) + 5 * 3 params(5 * 3 * 1 byte)*/
#define AD82088_MULTIPLE_RAM_BYTE	17
/* ram bank index(1 byte) + address (1 byte) + 4 params(4 * 1 byte)*/
#define AD82088D_SINGLE_RAM_BYTE	6
/* ram bank index(1 byte) + address (1 byte) + 5 * 4 params(5 * 4 * 1 byte)*/
#define AD82088D_MULTIPLE_RAM_BYTE	22

char common_str[] = "common";
char volume_str[] = "volume_config";
char volume_threshold[] = "volume_thresholds";

enum {
	E_BYTE = 0,
	E_SINGLE_RAM,
	E_MULTIPLE_RAM,
	E_MAX,
};

static const char *cus_amp_type[E_MAX] = {
	(char *)"f0",
	(char *)"f1",
	(char *)"f2",
};

static u8 cus_ini_get_format(const struct firmware *fw)
{
	char *buf;
	char fmt_str[] = "IniFormatType = ";
	char temp_str[FORMAT_MAX] = {0};
	char *loc = NULL;
	int pos;
	u8 type = 0;
	int ret;

	buf = (char *)fw->data;

	loc = strstr(buf, fmt_str);
	if (loc) {
		pos = loc - buf;

		pos += strlen(fmt_str);

		strncpy(temp_str, (buf + pos), sizeof(u8));
		temp_str[1] = '\0';

		ret = kstrtou8(temp_str, 10, &type);
		if (ret != 0) {
			pr_err("[CUS INI PARSER]read format type fail\n");
			return -1;
		}
	}

	return type;
}

static int cus_ini_get_count(const struct firmware *fw, char *string)
{
	char *buf;
	char cnt_str[] = "count = ";
	char cnt_str_end[] = "\n";
	char temp_str[COUNT_MAX] = {0};
	char *loc = NULL, *loc_end = NULL;
	int pos, end_pos = 0, diff = 0, ret;
	u16 count = 0;

	buf = (char *)fw->data;

	loc = strstr(buf, string);
	if (loc)
		pos = loc - buf;
	else
		return -1;

	loc = strstr(buf + pos, cnt_str);

	if (loc) {
		pos = loc - buf;
		pos += strlen(cnt_str);
		loc_end = strstr(buf + pos, cnt_str_end);
		if (loc_end) {
			end_pos = loc_end - buf;
			diff = end_pos - pos;
			if (diff <= 0) {
				pr_err("[CUS INI PARSER]get count fail\n");
				return -1;
			}
		} else {
			pr_err("[CUS INI PARSER]get cnt_str_end %s fail\n", cnt_str_end);
			return -1;
		}

		strncpy(temp_str, (buf + pos), sizeof(int));
		temp_str[diff] = '\0';

		ret = kstrtou16(temp_str, 10, &count);
		if (ret != 0) {
			pr_err("[CUS INI PARSER]read count value fail\n");
			return -1;
		}
	} else {
		pr_err("[CUS INI PARSER]parse count fail\n");
		return -1;
	}

	return count;
}

static int cus_convert_cmd(char *string, u8 format_type, u8 *cmd, u8 *size)
{
	int i;
	s8 type = -1;

	for (i = 0; i < E_MAX; i++) {
		if (strncmp(string, cus_amp_type[i], sizeof(short)) == 0)
			type = i;
	}

	if (type < 0) {
		pr_err("[CUS INI PARSER] can't get right type %s\n", string);
		return -1;
	}

	switch (type) {
	case E_BYTE:		/* cmd : f0  = 1 */
		*cmd = SINGLE_REGISTER;
		*size = SINGLE_REGISTER_BYTE;
		break;
	case E_SINGLE_RAM:	/* cmd : f1  = 2 */
		*cmd = SINGLE_RAM;
		if (format_type == AD82088_TYPE)
			*size = AD82088_SINGLE_RAM_BYTE;
		if (format_type == AD82088D_TYPE)
			*size = AD82088D_SINGLE_RAM_BYTE;
		break;
	case E_MULTIPLE_RAM:	/* cmd : f2  = 3 */
		*cmd = MULTIPLE_RAM;
		if (format_type == AD82088_TYPE)
			*size = AD82088_MULTIPLE_RAM_BYTE;
		if (format_type == AD82088D_TYPE)
			*size = AD82088D_MULTIPLE_RAM_BYTE;
		break;
	default:
		break;
	}

	return 0;
}

static int cus_ini_get_size(const struct firmware *fw, int count, u8 format_type, char *string)
{
	char *buf = NULL;
	char idx_str[IDX_MAX] = {0};
	char idx_str_end[] = ",";
	char temp_str[COUNT_MAX] = {0};
	char *loc = NULL, *loc_end = NULL;
	int pos = 0, end_pos = 0, diff = 0, ret = 0;
	int total_size = 0;
	u8 cmd = 0, size = 0;
	int i = 0;

	buf = (char *)fw->data;

	loc = strstr(buf, string);
	if (loc)
		pos = loc - buf;
	else
		return -1;

	buf += pos;

	for (i = 0; i < count; i++) {
		ret = scnprintf(idx_str, sizeof(idx_str), "index_%d = ", i);
		if (ret < 0) {
			pr_err("[CUS INI PARSER]scnprintf fail\n");
			return -1;
		}

		loc = strstr(buf, idx_str);
		if (loc) {
			pos = loc - buf;
			pos += strlen(idx_str);

			loc_end = strstr(buf + pos, idx_str_end);
			if (loc_end) {
				end_pos = loc_end - buf;
				diff = end_pos - pos;
				if (diff <= 0) {
					pr_err("[CUS INI PARSER]get index fail\n");
					return -1;
				}
			} else {
				pr_err("[CUS INI PARSER]get idx_str_end %s fail\n", idx_str_end);
				return -1;
			}
			/* get cmd : index_xx = "f0, or f1, or f2, */
			strncpy(temp_str, (buf + pos + 1), sizeof(short));
			temp_str[diff] = '\0';
			/* get cmd f0/f1/f2 size */
			ret = cus_convert_cmd(temp_str, format_type, &cmd, &size);
			if (ret < 0) {
				pr_err("[CUS INI PARSER]read index fail\n");
				return -1;
			}
			buf += pos;
		} else {
			pr_err("[CUS INI PARSER]parse %s fail\n", idx_str);
			return -1;
		}

		total_size += size;
	}

	total_size += count;	// add address count

	return total_size;
}

int cus_ini_fill_init_table(const struct firmware *fw, u8 *init_table, int count,
	u8 format_type, char *string)
{
	char *buf = NULL;
	char idx_str[IDX_MAX] = {0};
	char idx_str_end[] = ",";
	char value_str[] = "0x";
	char temp_str[COUNT_MAX] = {0};
	char *loc = NULL, *loc_end = NULL;
	int pos = 0, end_pos = 0, diff = 0;
	u16 value = 0;
	u8 cmd = 0, size = 0;
	int i = 0, j = 0;
	int ret = 0;

	buf = (char *)fw->data;

	loc = strstr(buf, string);
	if (loc)
		pos = loc - buf;
	else
		return -1;

	buf += pos;

	for (i = 0; i < count; i++) {
		ret = scnprintf(idx_str, sizeof(idx_str), "index_%d = ", i);
		if (ret < 0) {
			pr_err("[CUS INI PARSER]scnprintf fail\n");
			return -1;
		}

		loc = strstr(buf, idx_str);
		if (loc) {
			pos = loc - buf;
			pos += strlen(idx_str);
			loc_end = strstr(buf + pos, idx_str_end);
			if (loc_end) {
				end_pos = loc_end - buf;
				diff = end_pos - pos;
				if (diff <= 0) {
					pr_err("[CUS INI PARSER]get index fail\n");
					return -1;
				}
			} else {
				pr_err("[CUS INI PARSER]get idx_str_end %s fail\n", idx_str_end);
				return -1;
			}
			/* get cmd : index_xx = "f0, or f1, or f2, */
			strncpy(temp_str, (buf + pos + 1), sizeof(int));
			temp_str[diff] = '\0';
			/* get cmd f0/f1/f2 value and fill to init table */
			ret = cus_convert_cmd(temp_str, format_type, &cmd, &size);
			if (ret != 0) {
				pr_err("[CUS INI PARSER]read index fail\n");
				return -1;
			}
			buf += pos;
			*init_table = cmd;
			init_table++;
		} else {
			pr_err("[CUS INI PARSER]parse %s fail\n", idx_str);
			return -1;
		}

		/* get address & value then fill to init table */
		for (j = 0; j < size; j++) {
			loc = strstr(buf, value_str);
			if (loc) {
				pos = loc - buf;
				pos += strlen(value_str);

				strncpy(temp_str, (buf + pos), sizeof(u16));
				temp_str[2] = '\0';
				ret = kstrtou16(temp_str, 16, &value);
				if (ret != 0) {
					pr_err("[ALSA MTK]read mask fail\n");
					return -1;
				}
				buf += pos;
				*init_table = value;
				init_table++;
			}
		}
	}
	// end flag
	*init_table = 0;

	return 0;
}

static int cus_parser_sound_mode(const struct firmware *fw, struct cus_param *param)
{
	char sound_mode_str[SOUND_MODE_SIZE_MAX] = {0};
	int count = 0, size = 0, ret = 0;
	int i;
	/* parse sound mode, EX [sound_mode_x] (x = 0~9) */
	for (i = 0; i < SOUND_MODE_MAX; i++) {
		ret = scnprintf(sound_mode_str, sizeof(sound_mode_str), "sound_mode_%d", i);

		if (ret < 0) {
			pr_err("[CUS INI PARSER]scnprintf fail\n");
			continue;
		}

		count = cus_ini_get_count(fw, sound_mode_str);
		if (count <= 0) {
			pr_err("[CUS INI PARSER]can't get ini count\n");
			continue;
		}

		size = cus_ini_get_size(fw, count, param->format_type, sound_mode_str);
		if (size <= 0) {
			pr_err("[CUS INI PARSER]can't get ini size\n");
			continue;
		}

		param->sound_mode[i][0] = vmalloc(sizeof(u8) * (size + 1));
		if (param->sound_mode[i][0] == NULL)
			return -1;

		ret = cus_ini_fill_init_table(fw, param->sound_mode[i][0], count,
			param->format_type, sound_mode_str);
		if (ret != 0)
			continue;
	}
	return 0;
}

static int cus_parser_volume_sound_mode(const struct firmware *fw, struct cus_param *param)
{
	char sound_mode_str[VOLUME_SOUND_MODE_SIZE_MAX] = {0};
	int count = 0, size = 0, ret = 0;
	int i, j;
	/* parse volume config sound mode, EX: [sound_mode_x_voly] (x=0~9, y=0~100) */
	for (i = 0; i < SOUND_MODE_MAX; i++) {
		for (j = 1; j < param->volume_config; j++)  {
			ret = scnprintf(sound_mode_str, sizeof(sound_mode_str), "sound_mode_%d_%s",
				i, param->vol_interval_name[j]);

			if (ret < 0) {
				pr_err("[CUS INI PARSER]scnprintf fail\n");
				continue;
			}

			count = cus_ini_get_count(fw, sound_mode_str);
			if (count <= 0) {
				pr_err("[CUS INI PARSER]can't get ini count\n");
				continue;
			}

			size = cus_ini_get_size(fw, count, param->format_type, sound_mode_str);
			if (size <= 0) {
				pr_err("[CUS INI PARSER]can't get ini size\n");
				continue;
			}

			param->sound_mode[i][j] = vmalloc(sizeof(u8) * (size + 1));
			if (param->sound_mode[i][j] == NULL)
				return -1;

			ret = cus_ini_fill_init_table(fw, param->sound_mode[i][j],
				count, param->format_type,
				sound_mode_str);
			if (ret != 0)
				continue;
		}
	}
	return 0;
}

static int cus_parser_volume_config(const struct firmware *fw, struct cus_param *param)
{
	char *buf = NULL;
	char *temp_buf = NULL;
	char volume_index[VOL_CONFIG_SIZE_MAX] = {0};
	char temp1_str[VOL_THRESHOLD_LEN] = {0};
	char vol_str[] = "volume_thresholds = ";
	char idx_str_end[] = "\n";
	char *loc = NULL, *loc_end = NULL;
	int pos = 0, end_pos = 0;
	int ret = 0;
	int i = 0;
	char *str = NULL;
	int diff = 0;
	char *cmp_str = NULL;

	buf = (char *)fw->data;
	/* parse volume_config */
	loc = strstr(buf, volume_str);
	if (loc)
		pos = loc - buf;
	else
		return -1;

	/* parse volume_thresholds */
	loc = strstr(buf, volume_threshold);
	if (loc)
		pos = loc - buf;
	else
		return -1;

	temp_buf = buf + pos;
	param->volume_config = 1;
/* parse volume_thresholds string has how many intervals. Ex:volume thresholds = "0,10,50,70,100" */
	while (temp_buf[i] != '\n') {
		/* if there are n chars of ',' , there will be n+1 intervals */
		if (temp_buf[i] == ',')
			param->volume_config++;
		i++;
	}

	/* copy volume_thresholds string and replace last '"' to ',' for strsep usage */
	if (i > 0) {
		strncpy(temp1_str, temp_buf, sizeof(char) * i);
		temp1_str[i - 1] = ',';
		temp1_str[VOL_THRESHOLD_LEN-1] = '\0';
	}

	/* use strsep to parse each interval value */
	cmp_str = temp1_str;
	/* pass "volume_thresholds = ""*/
	cmp_str += strlen(vol_str);
	cmp_str++;
	for (i = 0; i < param->volume_config; i++) {
		str = strsep(&cmp_str, ",");
		if (str != NULL) {
			ret = kstrtou16(str, 10, &param->vol_interval_range[i]);
			if (ret != 0) {
				pr_err("[CUS INI PARSER]read vol interval range value fail\n");
				return -1;
			}
		}
	}
/* parse volume interval string, EX: volume_5 = "vol0"/volume_70 = "vol1"/volume_100 = "vol2" */
	for (i = 1; i < param->volume_config; i++) {
		ret = scnprintf(volume_index, sizeof(volume_index), "volume_%d = ",
			param->vol_interval_range[i]);
		if (ret < 0) {
			pr_err("[CUS INI PARSER]scnprintf fail\n");
			return -1;
		}

		loc = strstr(buf, volume_index);
		if (loc) {
			pos = loc - buf;

			pos += strlen(volume_index);
			loc_end = strstr(buf + pos, idx_str_end);
			if (loc_end) {
				end_pos = loc_end - buf;

				diff = end_pos - pos;
				if (diff <= 0) {
					pr_err("[CUS INI PARSER]get index fail\n");
					return -1;
				}
			} else {
				pr_err("[CUS INI PARSER]get idx_str_end %s fail\n", idx_str_end);
				return -1;
			}
			/* copy "volx" string to vol_interval_name for parse [sound_mode_x_volx] */
			if (i <= 10)	/* vol0 ~ vol9 */
				strncpy(param->vol_interval_name[i], (buf + pos + 1),
					strlen("vol0"));
			else		/* vol10 ~ vol99 */
				strncpy(param->vol_interval_name[i], (buf + pos + 1),
					strlen("vol10"));

			param->vol_interval_name[i][VOLUME_INTERVAL_NAME_LEN-1] = '\0';
			buf += pos;

		} else
			pr_err("[CUS INI PARSER]get volume string %s fail\n", volume_index);
	}
	return 0;
}

int cus_parser(const struct firmware *fw, struct cus_param *param)
{
	if (fw == NULL) {
		pr_err("[CUS INI PARSER]fw = NULL, request ini not avail\n");
		return -1;
	}

	/* parse sound mode, EX [sound_mode_x] (x = 0~9)*/
	cus_parser_sound_mode(fw, param);
	/* parse volume config & volume_thresholds */
	cus_parser_volume_config(fw, param);
	/* parse volume config sound mode, EX: [sound_mode_x_voly] (x=0~9, y=0~100) */
	if (param->volume_config)
		cus_parser_volume_sound_mode(fw, param);

	return 0;
}

int cus_common_parser(const struct firmware *fw, struct cus_param *param)
{
	int count = 0, size = 0, ret = 0;

	if (fw == NULL) {
		pr_err("[CUS INI PARSER]fw = NULL, request ini not avail\n");
		return -1;
	}

	param->format_type = cus_ini_get_format(fw);
	if (param->format_type > 2) {
		pr_err("[CUS INI PARSER]can't get ini format\n");
		return -1;
	}

	count = cus_ini_get_count(fw, common_str);
	if (count <= 0) {
		pr_err("[CUS INI PARSER]can't get ini count\n");
		return -1;
	}

	size = cus_ini_get_size(fw, count, param->format_type, common_str);
	if (size <= 0) {
		pr_err("[CUS INI PARSER]can't get ini size\n");
		return -1;
	}

	param->init_table = vmalloc(sizeof(u8) * (size + 1));
	if (param->init_table == NULL)
		return -1;

	ret = cus_ini_fill_init_table(fw, param->init_table, count, param->format_type, common_str);
	if (ret == 0)
		return 0;

	return 0;
}

