162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2021 MediaTek Inc.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/clk.h>
762306a36Sopenharmony_ci#include <linux/component.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/of.h>
1062306a36Sopenharmony_ci#include <linux/platform_device.h>
1162306a36Sopenharmony_ci#include <linux/soc/mediatek/mtk-cmdq.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "mtk_disp_drv.h"
1462306a36Sopenharmony_ci#include "mtk_drm_crtc.h"
1562306a36Sopenharmony_ci#include "mtk_drm_ddp_comp.h"
1662306a36Sopenharmony_ci#include "mtk_drm_drv.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define DISP_GAMMA_EN				0x0000
1962306a36Sopenharmony_ci#define GAMMA_EN					BIT(0)
2062306a36Sopenharmony_ci#define DISP_GAMMA_CFG				0x0020
2162306a36Sopenharmony_ci#define GAMMA_LUT_EN					BIT(1)
2262306a36Sopenharmony_ci#define GAMMA_DITHERING					BIT(2)
2362306a36Sopenharmony_ci#define DISP_GAMMA_SIZE				0x0030
2462306a36Sopenharmony_ci#define DISP_GAMMA_LUT				0x0700
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define LUT_10BIT_MASK				0x03ff
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct mtk_disp_gamma_data {
2962306a36Sopenharmony_ci	bool has_dither;
3062306a36Sopenharmony_ci	bool lut_diff;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/*
3462306a36Sopenharmony_ci * struct mtk_disp_gamma - DISP_GAMMA driver structure
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_cistruct mtk_disp_gamma {
3762306a36Sopenharmony_ci	struct clk *clk;
3862306a36Sopenharmony_ci	void __iomem *regs;
3962306a36Sopenharmony_ci	struct cmdq_client_reg cmdq_reg;
4062306a36Sopenharmony_ci	const struct mtk_disp_gamma_data *data;
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ciint mtk_gamma_clk_enable(struct device *dev)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct mtk_disp_gamma *gamma = dev_get_drvdata(dev);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	return clk_prepare_enable(gamma->clk);
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_civoid mtk_gamma_clk_disable(struct device *dev)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct mtk_disp_gamma *gamma = dev_get_drvdata(dev);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	clk_disable_unprepare(gamma->clk);
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_civoid mtk_gamma_set_common(void __iomem *regs, struct drm_crtc_state *state, bool lut_diff)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	unsigned int i, reg;
6062306a36Sopenharmony_ci	struct drm_color_lut *lut;
6162306a36Sopenharmony_ci	void __iomem *lut_base;
6262306a36Sopenharmony_ci	u32 word;
6362306a36Sopenharmony_ci	u32 diff[3] = {0};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (state->gamma_lut) {
6662306a36Sopenharmony_ci		reg = readl(regs + DISP_GAMMA_CFG);
6762306a36Sopenharmony_ci		reg = reg | GAMMA_LUT_EN;
6862306a36Sopenharmony_ci		writel(reg, regs + DISP_GAMMA_CFG);
6962306a36Sopenharmony_ci		lut_base = regs + DISP_GAMMA_LUT;
7062306a36Sopenharmony_ci		lut = (struct drm_color_lut *)state->gamma_lut->data;
7162306a36Sopenharmony_ci		for (i = 0; i < MTK_LUT_SIZE; i++) {
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci			if (!lut_diff || (i % 2 == 0)) {
7462306a36Sopenharmony_ci				word = (((lut[i].red >> 6) & LUT_10BIT_MASK) << 20) +
7562306a36Sopenharmony_ci					(((lut[i].green >> 6) & LUT_10BIT_MASK) << 10) +
7662306a36Sopenharmony_ci					((lut[i].blue >> 6) & LUT_10BIT_MASK);
7762306a36Sopenharmony_ci			} else {
7862306a36Sopenharmony_ci				diff[0] = (lut[i].red >> 6) - (lut[i - 1].red >> 6);
7962306a36Sopenharmony_ci				diff[1] = (lut[i].green >> 6) - (lut[i - 1].green >> 6);
8062306a36Sopenharmony_ci				diff[2] = (lut[i].blue >> 6) - (lut[i - 1].blue >> 6);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci				word = ((diff[0] & LUT_10BIT_MASK) << 20) +
8362306a36Sopenharmony_ci					((diff[1] & LUT_10BIT_MASK) << 10) +
8462306a36Sopenharmony_ci					(diff[2] & LUT_10BIT_MASK);
8562306a36Sopenharmony_ci			}
8662306a36Sopenharmony_ci			writel(word, (lut_base + i * 4));
8762306a36Sopenharmony_ci		}
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_civoid mtk_gamma_set(struct device *dev, struct drm_crtc_state *state)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct mtk_disp_gamma *gamma = dev_get_drvdata(dev);
9462306a36Sopenharmony_ci	bool lut_diff = false;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (gamma->data)
9762306a36Sopenharmony_ci		lut_diff = gamma->data->lut_diff;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	mtk_gamma_set_common(gamma->regs, state, lut_diff);
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_civoid mtk_gamma_config(struct device *dev, unsigned int w,
10362306a36Sopenharmony_ci		      unsigned int h, unsigned int vrefresh,
10462306a36Sopenharmony_ci		      unsigned int bpc, struct cmdq_pkt *cmdq_pkt)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	struct mtk_disp_gamma *gamma = dev_get_drvdata(dev);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	mtk_ddp_write(cmdq_pkt, h << 16 | w, &gamma->cmdq_reg, gamma->regs,
10962306a36Sopenharmony_ci		      DISP_GAMMA_SIZE);
11062306a36Sopenharmony_ci	if (gamma->data && gamma->data->has_dither)
11162306a36Sopenharmony_ci		mtk_dither_set_common(gamma->regs, &gamma->cmdq_reg, bpc,
11262306a36Sopenharmony_ci				      DISP_GAMMA_CFG, GAMMA_DITHERING, cmdq_pkt);
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_civoid mtk_gamma_start(struct device *dev)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct mtk_disp_gamma *gamma = dev_get_drvdata(dev);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	writel(GAMMA_EN, gamma->regs + DISP_GAMMA_EN);
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_civoid mtk_gamma_stop(struct device *dev)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	struct mtk_disp_gamma *gamma = dev_get_drvdata(dev);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	writel_relaxed(0x0, gamma->regs + DISP_GAMMA_EN);
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int mtk_disp_gamma_bind(struct device *dev, struct device *master,
13062306a36Sopenharmony_ci			       void *data)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	return 0;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic void mtk_disp_gamma_unbind(struct device *dev, struct device *master,
13662306a36Sopenharmony_ci				  void *data)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic const struct component_ops mtk_disp_gamma_component_ops = {
14162306a36Sopenharmony_ci	.bind	= mtk_disp_gamma_bind,
14262306a36Sopenharmony_ci	.unbind = mtk_disp_gamma_unbind,
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic int mtk_disp_gamma_probe(struct platform_device *pdev)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
14862306a36Sopenharmony_ci	struct mtk_disp_gamma *priv;
14962306a36Sopenharmony_ci	struct resource *res;
15062306a36Sopenharmony_ci	int ret;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
15362306a36Sopenharmony_ci	if (!priv)
15462306a36Sopenharmony_ci		return -ENOMEM;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	priv->clk = devm_clk_get(dev, NULL);
15762306a36Sopenharmony_ci	if (IS_ERR(priv->clk)) {
15862306a36Sopenharmony_ci		dev_err(dev, "failed to get gamma clk\n");
15962306a36Sopenharmony_ci		return PTR_ERR(priv->clk);
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
16362306a36Sopenharmony_ci	priv->regs = devm_ioremap_resource(dev, res);
16462306a36Sopenharmony_ci	if (IS_ERR(priv->regs)) {
16562306a36Sopenharmony_ci		dev_err(dev, "failed to ioremap gamma\n");
16662306a36Sopenharmony_ci		return PTR_ERR(priv->regs);
16762306a36Sopenharmony_ci	}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci#if IS_REACHABLE(CONFIG_MTK_CMDQ)
17062306a36Sopenharmony_ci	ret = cmdq_dev_get_client_reg(dev, &priv->cmdq_reg, 0);
17162306a36Sopenharmony_ci	if (ret)
17262306a36Sopenharmony_ci		dev_dbg(dev, "get mediatek,gce-client-reg fail!\n");
17362306a36Sopenharmony_ci#endif
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	priv->data = of_device_get_match_data(dev);
17662306a36Sopenharmony_ci	platform_set_drvdata(pdev, priv);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	ret = component_add(dev, &mtk_disp_gamma_component_ops);
17962306a36Sopenharmony_ci	if (ret)
18062306a36Sopenharmony_ci		dev_err(dev, "Failed to add component: %d\n", ret);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return ret;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic void mtk_disp_gamma_remove(struct platform_device *pdev)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	component_del(&pdev->dev, &mtk_disp_gamma_component_ops);
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic const struct mtk_disp_gamma_data mt8173_gamma_driver_data = {
19162306a36Sopenharmony_ci	.has_dither = true,
19262306a36Sopenharmony_ci};
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic const struct mtk_disp_gamma_data mt8183_gamma_driver_data = {
19562306a36Sopenharmony_ci	.lut_diff = true,
19662306a36Sopenharmony_ci};
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic const struct of_device_id mtk_disp_gamma_driver_dt_match[] = {
19962306a36Sopenharmony_ci	{ .compatible = "mediatek,mt8173-disp-gamma",
20062306a36Sopenharmony_ci	  .data = &mt8173_gamma_driver_data},
20162306a36Sopenharmony_ci	{ .compatible = "mediatek,mt8183-disp-gamma",
20262306a36Sopenharmony_ci	  .data = &mt8183_gamma_driver_data},
20362306a36Sopenharmony_ci	{},
20462306a36Sopenharmony_ci};
20562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtk_disp_gamma_driver_dt_match);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistruct platform_driver mtk_disp_gamma_driver = {
20862306a36Sopenharmony_ci	.probe		= mtk_disp_gamma_probe,
20962306a36Sopenharmony_ci	.remove_new	= mtk_disp_gamma_remove,
21062306a36Sopenharmony_ci	.driver		= {
21162306a36Sopenharmony_ci		.name	= "mediatek-disp-gamma",
21262306a36Sopenharmony_ci		.owner	= THIS_MODULE,
21362306a36Sopenharmony_ci		.of_match_table = mtk_disp_gamma_driver_dt_match,
21462306a36Sopenharmony_ci	},
21562306a36Sopenharmony_ci};
216