// SPDX-License-Identifier: GPL-2.0
/*
 * kcontrol_uevent.c  --  Mediatek Soundbar kcontrol
 *
 * Copyright (c) 2020 MediaTek Inc.
 * Author: Wen Cai <wen.cai@mediatek.com>
 *
 */
#include "kcontrol_uevent.h"
#define UEVENT_CLASS_NAME "control"

struct class *kcontrol_class;
static atomic_t device_count;

char integer_array_value_buf[MAX_UEVENT_VALUE_ARRAY_LEN] = {'\0'};
char integer_array_value_string[MAX_UEVENT_VALUE_ARRAY_LEN] = {'\0'};

static ssize_t type_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret;
	struct notify_dev *sdev = (struct notify_dev *)
		dev_get_drvdata(dev);

	if (sdev->print_type) {
		ret = sdev->print_type(sdev, buf);
		if (ret >= 0)
			return ret;
	}
	return sprintf(buf, "%d\n", sdev->control_type);
}
static DEVICE_ATTR_RO(type);

static ssize_t value_show(struct device *dev,
		struct device_attribute *attr,
		char *buf)
{
	int ret;
	struct notify_dev *sdev = (struct notify_dev *)
		dev_get_drvdata(dev);

	if (sdev->print_value) {
		ret = sdev->print_value(sdev, buf);
		if (ret >= 0)
			return ret;
	}
	return sprintf(buf, "%s\n", sdev->control_value);
}
static DEVICE_ATTR_RO(value);

int kcontrol_notify_uevent_integer(
		struct notify_dev *sdev, int type, int value)
{
	int ret = -1;
	char *envp[4];
	char type_buf[MAX_UEVENT_TYPE_LEN] = {'\0'};
	char value_buf[MAX_UEVENT_VALUE_SINGLE_LEN] = {'\0'};
	char time_buf[MAX_UEVENT_TIME_LEN] = {'\0'};
	struct timespec64 notify_time;

	if (sdev == NULL)
		return -1;

	ktime_get_real_ts64(&notify_time);
	if (sdev->control_type != type)
		sdev->control_type = type;
	if (sdev->control_value_integer != value)
		sdev->control_value_integer = value;

	ret = snprintf(sdev->control_value,
			sizeof(sdev->control_value), "%d",
			sdev->control_value_integer);
	if (ret < 0 || ret >= sizeof(sdev->control_value)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}

	ret = snprintf(type_buf, sizeof(type_buf),
			"CONTROL_TYPE=%d", sdev->control_type);
	if (ret < 0 || ret >= sizeof(type_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[0] = type_buf;

	ret = snprintf(value_buf, sizeof(value_buf),
			"CONTROL_VALUE=%s", sdev->control_value);
	if (ret < 0 || ret >= sizeof(value_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[1] = value_buf;

	ret = snprintf(time_buf, sizeof(time_buf),
			"CONTROL_TIME=%ld,%ld", notify_time.tv_sec,
			notify_time.tv_nsec);
	if (ret < 0 || ret >= sizeof(time_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[2] = time_buf;
	envp[3] = NULL;
	// pr_info("uevent type:%s ,value:%s\n", envp[0], envp[1]);

	kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);

	return 0;
}

int kcontrol_notify_uevent_integer_array(
		struct notify_dev *sdev, int type, int *value, int array_len)
{
	int ret = -1;
	char *envp[4];
	char type_buf[MAX_UEVENT_TYPE_LEN] = {'\0'};
	char time_buf[MAX_UEVENT_TIME_LEN] = {'\0'};
	struct timespec64 notify_time;
	size_t len = 0;
	int i = 0;
	int offset = 0;
	size_t size = sizeof(integer_array_value_string);

	if (sdev == NULL || value == NULL)
		return -1;

	memset(integer_array_value_buf, 0, MAX_UEVENT_VALUE_ARRAY_LEN);
	memset(integer_array_value_string, 0, MAX_UEVENT_VALUE_ARRAY_LEN);
	ktime_get_real_ts64(&notify_time);
	for (i = 0; i < array_len; i++) {
		if (i > 0)
			offset += snprintf(integer_array_value_string + offset,
				size - offset, ",");
		offset += snprintf(integer_array_value_string + offset,
				size - offset, "%d", value[i]);
	}
	if (sdev->control_type != type)
		sdev->control_type = type;
	len = strlen(integer_array_value_string);
	if (strncmp(sdev->control_value, integer_array_value_string, len)) {
		memset(sdev->control_value, '\0', sizeof(sdev->control_value));
		strncpy(sdev->control_value, integer_array_value_string, len);
		sdev->control_value[len + 1] = '\0';
	}

	ret = snprintf(type_buf, sizeof(type_buf),
			"CONTROL_TYPE=%d", sdev->control_type);
	if (ret < 0 || ret >= sizeof(type_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[0] = type_buf;

	ret = snprintf(integer_array_value_buf,
			sizeof(integer_array_value_buf),
			"CONTROL_VALUE=%s", sdev->control_value);
	if (ret < 0 || ret >= sizeof(integer_array_value_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[1] = integer_array_value_buf;

	ret = snprintf(time_buf, sizeof(time_buf),
			"CONTROL_TIME=%ld,%ld", notify_time.tv_sec,
			notify_time.tv_nsec);
	if (ret < 0 || ret >= sizeof(time_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[2] = time_buf;
	envp[3] = NULL;
	//pr_info("uevent type:%s ,value:%s\n", envp[0], envp[1]);

	kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);

	return 0;
}


int kcontrol_notify_uevent(
		struct notify_dev *sdev, int type, char *value)
{
	int ret = -1;
	char *envp[4];
	char type_buf[MAX_UEVENT_TYPE_LEN] = {'\0'};
	char value_buf[MAX_UEVENT_VALUE_MULTI_LEN] = {'\0'};
	char time_buf[MAX_UEVENT_TIME_LEN] = {'\0'};
	struct timespec64 notify_time;
	size_t len = 0;

	if (sdev == NULL || value == NULL)
		return -1;

	ktime_get_real_ts64(&notify_time);
	if (sdev->control_type != type)
		sdev->control_type = type;
	len = strlen(value);
	if (strncmp(sdev->control_value, value, len)) {
		memset(sdev->control_value, '\0', sizeof(sdev->control_value));
		strncpy(sdev->control_value, value, len);
		sdev->control_value[len + 1] = '\0';
	}

	ret = snprintf(type_buf, sizeof(type_buf),
			"CONTROL_TYPE=%d", sdev->control_type);
	if (ret < 0 || ret >= sizeof(type_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[0] = type_buf;

	ret = snprintf(value_buf, sizeof(value_buf),
			"CONTROL_VALUE=%s", sdev->control_value);
	if (ret < 0 || ret >= sizeof(value_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[1] = value_buf;

	ret = snprintf(time_buf, sizeof(time_buf),
			"CONTROL_TIME=%ld,%ld", notify_time.tv_sec,
			notify_time.tv_nsec);
	if (ret < 0 || ret >= sizeof(time_buf)) {
		dev_err(sdev->dev, "snprintf error\n");
		return -1;
	}
	envp[2] = time_buf;
	envp[3] = NULL;
	// pr_info("uevent type:%s ,value:%s\n", envp[0], envp[1]);

	kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);

	return 0;
}


static int create_kcontrol_class(void)
{
	if (!kcontrol_class) {
		kcontrol_class = class_create(THIS_MODULE, UEVENT_CLASS_NAME);
		if (IS_ERR(kcontrol_class))
			return PTR_ERR(kcontrol_class);
		atomic_set(&device_count, 0);
	}
	return 0;
}

int kcontrol_uevent_dev_register(struct notify_dev *sdev)
{
	int ret;

	if (sdev == NULL) {
		pr_info("kcontrol uevent dev register fail.\n");
		return -1;
	}

	if (!kcontrol_class) {
		ret = create_kcontrol_class();

		if (ret == 0)
			pr_info("create_switch_class susesess\n");
		else {
			pr_info("create_switch_class fail\n");
			return ret;
		}
	}

	sdev->index = atomic_inc_return(&device_count);
	sdev->dev = device_create(kcontrol_class, NULL,
			MKDEV(0, sdev->index), NULL, sdev->name);

	if (sdev->dev) {
		pr_info("device create ok,index:0x%x\n", sdev->index);
		ret = 0;
	} else {
		pr_info("device create fail,index:0x%x\n", sdev->index);
		return -1;
	}

	ret = device_create_file(sdev->dev, &dev_attr_type);
	if (ret < 0) {
		device_destroy(kcontrol_class, MKDEV(0, sdev->index));
		pr_info("kcontrol: Failed to register driver %s\n",
				sdev->name);
	}

	ret = device_create_file(sdev->dev, &dev_attr_value);
	if (ret < 0) {
		device_remove_file(sdev->dev, &dev_attr_type);
		pr_info("kcontrol: Failed to register driver %s\n",
				sdev->name);
	}

	dev_set_drvdata(sdev->dev, sdev);

	return ret;
}

