// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2022 Svyatoslav Ryhel <clamor95@gmail.com>
 */

#define LOG_CATEGORY UCLASS_PANEL_BACKLIGHT

#include <backlight.h>
#include <dm.h>
#include <i2c.h>
#include <log.h>
#include <linux/delay.h>
#include <linux/err.h>

#include <asm/io.h>
#include <asm/gpio.h>

#include "dc.h"

#define TEGRA_PWM_BL_MIN_BRIGHTNESS	0x10
#define TEGRA_PWM_BL_MAX_BRIGHTNESS	0xFF

#define TEGRA_PWM_BL_PERIOD		0xFF
#define TEGRA_PWM_BL_CLK_DIV		0x14
#define TEGRA_PWM_BL_CLK_SELECT		0x00

#define PM_PERIOD_SHIFT                 18
#define PM_CLK_DIVIDER_SHIFT		4

#define TEGRA_PWM_PM0			0
#define TEGRA_PWM_PM1			1

struct tegra_pwm_backlight_priv {
	struct dc_ctlr *dc;		/* Display controller regmap */

	u32 pwm_source;
	u32 period;
	u32 clk_div;
	u32 clk_select;
	u32 dft_brightness;
};

static int tegra_pwm_backlight_set_brightness(struct udevice *dev, int percent)
{
	struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev);
	struct dc_cmd_reg *cmd = &priv->dc->cmd;
	struct dc_com_reg *com = &priv->dc->com;
	unsigned int ctrl;
	unsigned long out_sel;
	unsigned long cmd_state;

	if (percent == BACKLIGHT_DEFAULT)
		percent = priv->dft_brightness;

	if (percent < TEGRA_PWM_BL_MIN_BRIGHTNESS)
		percent = TEGRA_PWM_BL_MIN_BRIGHTNESS;

	if (percent > TEGRA_PWM_BL_MAX_BRIGHTNESS)
		percent = TEGRA_PWM_BL_MAX_BRIGHTNESS;

	ctrl = ((priv->period << PM_PERIOD_SHIFT) |
		(priv->clk_div << PM_CLK_DIVIDER_SHIFT) |
		 priv->clk_select);

	/* The new value should be effected immediately */
	cmd_state = readl(&cmd->state_access);
	writel((cmd_state | (1 << 2)), &cmd->state_access);

	switch (priv->pwm_source) {
	case TEGRA_PWM_PM0:
		/* Select the LM0 on PM0 */
		out_sel = readl(&com->pin_output_sel[5]);
		out_sel &= ~(7 << 0);
		out_sel |= (3 << 0);
		writel(out_sel, &com->pin_output_sel[5]);
		writel(ctrl, &com->pm0_ctrl);
		writel(percent, &com->pm0_duty_cycle);
		break;
	case TEGRA_PWM_PM1:
		/* Select the LM1 on PM1 */
		out_sel = readl(&com->pin_output_sel[5]);
		out_sel &= ~(7 << 4);
		out_sel |= (3 << 4);
		writel(out_sel, &com->pin_output_sel[5]);
		writel(ctrl, &com->pm1_ctrl);
		writel(percent, &com->pm1_duty_cycle);
		break;
	default:
		break;
	}

	writel(cmd_state, &cmd->state_access);
	return 0;
}

static int tegra_pwm_backlight_enable(struct udevice *dev)
{
	struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev);

	return tegra_pwm_backlight_set_brightness(dev, priv->dft_brightness);
}

static int tegra_pwm_backlight_probe(struct udevice *dev)
{
	struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev);
	ofnode dc = ofnode_get_parent(dev_ofnode(dev));

	priv->dc = (struct dc_ctlr *)ofnode_get_addr(dc);
	if (!priv->dc) {
		log_err("%s: failed to get DC controller\n", __func__);
		return -EINVAL;
	}

	priv->pwm_source =
		dev_read_u32_default(dev, "nvidia,pwm-source",
				     TEGRA_PWM_PM0);
	priv->period =
		dev_read_u32_default(dev, "nvidia,period",
				     TEGRA_PWM_BL_PERIOD);
	priv->clk_div =
		dev_read_u32_default(dev, "nvidia,clock-div",
				     TEGRA_PWM_BL_CLK_DIV);
	priv->clk_select =
		dev_read_u32_default(dev, "nvidia,clock-select",
				     TEGRA_PWM_BL_CLK_SELECT);
	priv->dft_brightness =
		dev_read_u32_default(dev, "nvidia,default-brightness",
				     TEGRA_PWM_BL_MAX_BRIGHTNESS);

	return 0;
}

static const struct backlight_ops tegra_pwm_backlight_ops = {
	.enable = tegra_pwm_backlight_enable,
	.set_brightness = tegra_pwm_backlight_set_brightness,
};

static const struct udevice_id tegra_pwm_backlight_ids[] = {
	{ .compatible = "nvidia,tegra-pwm-backlight" },
	{ }
};

U_BOOT_DRIVER(tegra_pwm_backlight) = {
	.name		= "tegra_pwm_backlight",
	.id		= UCLASS_PANEL_BACKLIGHT,
	.of_match	= tegra_pwm_backlight_ids,
	.probe		= tegra_pwm_backlight_probe,
	.ops		= &tegra_pwm_backlight_ops,
	.priv_auto	= sizeof(struct tegra_pwm_backlight_priv),
};
