// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2015 Google, Inc
 * Written by Simon Glass <sjg@chromium.org>
 */

#include <common.h>
#include <dm.h>
#include <errno.h>
#include <led.h>
#include <asm/gpio.h>
#include <dm/lists.h>
#include <string.h>
#include <iniparser.h>
#include <utility.h>

#define LED_CFG_PARTITION_PATH		"persist"
#define LED_CFG_INI_PATH		"config/led_ui_cfg.ini"
#define LED_HIGH_DUTY		(100)
#define LED_MID_DUTY		(60)
#define LED_LOW_DUTY		(30)

struct led_control_priv {
	struct udevice *subled_dev;
	bool control;
	int duty;
	int breath_period;
	int blink_period;
	enum led_state_t state;
	int led_ui_state;
};

int get_led_cfg_info(const char *partition, const char *file, struct led_control_priv *priv)
{
	unsigned char *ini_file_buf;
	loff_t size;
	iniparser_handle_t ini_handle = NULL;
	struct section *section_entry;
	char *section_name = "STANDBY_LED";
	char *led_state = "Standby_Light_UI_State";
	char *led_duty = "Standby_Light_UI_Duty";
	char buf[10] = {};
	int ret = 0;

	ini_file_buf = read_storage_file_to_memory(partition, file,  &size);
	if (!ini_file_buf) {
		free(ini_file_buf);
		dev_info(dev, "Read ini file to DRAM failure\n");
		return -1;
	}

	ret = iniparser_create(ini_file_buf, size, &ini_handle);
	free(ini_file_buf);
	if (ret < 0) {
		dev_info(dev, "Parser ini [%s] failure\n", file);
		return -1;
	}

	ret = iniparser_get_section(ini_handle, section_name, &section_entry);
	if (ret < 0) {
		dev_info(dev, "Get ini section[%s] failure\n", section_name);
		goto EXIT;
	}

	ret = iniparser_getstring(section_entry, led_state, "", buf, 64);
	if (ret < 0) {
		dev_info(dev, "Get ini key[%s] failure\n", led_state);
		goto EXIT;
	}
	if (!strcmp(buf, "on"))
		priv->led_ui_state = true;

	ret = iniparser_getint(section_entry, led_duty, 0, &priv->duty);
	ret = iniparser_getstring(section_entry, led_duty, "", buf, 64);
	if (ret < 0) {
		dev_info(dev, "Get ini key[%s] failure\n", led_duty);
		goto EXIT;
	}
	dev_info(dev, "%s led duty is %s\n", __func__, buf);
	if (strcmp(buf, "high") == 0) {
		priv->duty = LED_HIGH_DUTY;
	} else if (strcmp(buf, "medium") == 0) {
		priv->duty = LED_MID_DUTY;
	} else if (strcmp(buf, "low") == 0) {
		priv->duty = LED_LOW_DUTY;
	} else {
		priv->duty = LED_HIGH_DUTY;
		dev_info(dev, " Standby_Light_UI_Duty config is invalid, default set to on.\n");
	}
EXIT:
	iniparser_destroy(ini_handle);
	return ret;
}

static int control_led_set_state(struct udevice *dev, enum led_state_t state)
{
	struct led_control_priv *priv = dev_get_priv(dev);
	int ret = 0;

	if (!priv->control)
		return ret;
	get_led_cfg_info(LED_CFG_PARTITION_PATH, LED_CFG_INI_PATH, priv);

	switch (state) {
	case LEDST_OFF:
		break;
	case LEDST_ON:
		led_set_duty(priv->subled_dev, priv->duty);
		break;
	case LEDST_BLINK:
		// to do
		break;
	case LEDST_BREATH:
		// to do
		break;
	default:
		return -EINVAL;
	}

	ret = led_set_state(priv->subled_dev, state);
	if (ret) {
		debug("set %s failed: %d\n", __func__, ret);
		return -ret;
	}
	priv->state = state;

	return ret;
}

static enum led_state_t control_led_get_state(struct udevice *dev)
{
	struct led_control_priv *priv = dev_get_priv(dev);

	return priv->state;
}

static int control_led_set_period(struct udevice *dev, int period_ms)
{
	struct led_control_priv *priv = dev_get_priv(dev);

	if (period_ms < 0)
		return -EINVAL;

	priv->blink_period = period_ms / 100;
	debug("\nPeriod = %dms\n", period_ms);

	return 0;
}

static int control_led_set_duty(struct udevice *dev, int duty)
{
	struct led_control_priv *priv = dev_get_priv(dev);

	if (duty > 100)
		return -EINVAL;
	priv->duty = duty;
	debug("\nduty = %dms\n", duty);

	return 0;
}

static int control_led_set_breath(struct udevice *dev, int breath_period)
{
	struct led_control_priv *priv = dev_get_priv(dev);

	if (breath_period < 0)
		return -EINVAL;

	priv->breath_period = breath_period;
	debug("\nbreath_period = %dms\n", breath_period);

	return 0;
}

static int led_control_probe(struct udevice *dev)
{
	struct led_uc_plat *uc_plat = dev_get_uclass_platdata(dev);
	struct led_control_priv *priv = dev_get_priv(dev);
	int ret;

	if (!uc_plat->label)
		return 0;
	priv = dev_get_priv(dev);
	ret = led_get_by_label("mtk-tv:breath-led", &priv->subled_dev);
	if (ret)
	    dev_info(dev, "%s get breath led failed: %d\n", __func__, ret);

	priv->control = dev_read_bool(dev, "control_enable");
	dev_info(dev, "%s led state is %s\n", __func__, priv->control ? "true" : "false");
	priv->state = LEDST_OFF;

	return 0;
}

static int led_control_bind(struct udevice *parent)
{
	struct udevice *dev;
	ofnode node;
	int ret = 0;

	dev_for_each_subnode(node, parent) {
		struct led_uc_plat *uc_plat;
		const char *label;

		label = ofnode_read_string(node, "label");
		if (!label) {
			debug("%s: node %s has no label\n", __func__,
			      ofnode_get_name(node));
			return -EINVAL;
		}
		debug("%s: label %s\n", __func__, label);
		ret = device_bind_driver_to_node(parent, "control_led",
						 ofnode_get_name(node),
						 node, &dev);
		if (ret)
			return ret;
		uc_plat = dev_get_uclass_platdata(dev);
		uc_plat->label = label;
	}

	return ret;
}

static const struct led_ops control_led_ops = {
	.set_state	= control_led_set_state,
	.get_state	= control_led_get_state,
	.set_period	= control_led_set_period,
	.set_duty	= control_led_set_duty,
	.set_breath	= control_led_set_breath,
};

static const struct udevice_id led_control_ids[] = {
	{ .compatible = "control-leds" },
	{ }
};

U_BOOT_DRIVER(led_control) = {
	.name	= "control_led",
	.id	= UCLASS_LED,
	.of_match = led_control_ids,
	.ops	= &control_led_ops,
	.priv_auto_alloc_size = sizeof(struct led_control_priv),
	.bind	= led_control_bind,
	.probe	= led_control_probe,
};
