kernel_samsung_a34x-permissive/drivers/gpu/drm/mediatek/mtk_disp_dither.c
2024-04-28 15:51:13 +02:00

585 lines
16 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <drm/drmP.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/soc/mediatek/mtk-cmdq.h>
#include "mtk_drm_crtc.h"
#include "mtk_drm_ddp_comp.h"
#include "mtk_drm_drv.h"
#include "mtk_log.h"
#include "mtk_dump.h"
#define DISP_DITHER_EN 0x0
#define DISP_REG_DITHER_CFG 0x20
#define DISP_REG_DITHER_SIZE 0x30
#define DISP_DITHER_5 0x0114
#define DISP_DITHER_7 0x011c
#define DISP_DITHER_15 0x013c
#define DISP_DITHER_16 0x0140
#define DITHER_REG(idx) (0x100 + (idx)*4)
#define DITHER_BYPASS_SHADOW BIT(0)
#define DITHER_READ_WRK_REG BIT(2)
#define DISP_DITHERING BIT(2)
#define DITHER_LSB_ERR_SHIFT_R(x) (((x)&0x7) << 28)
#define DITHER_OVFLW_BIT_R(x) (((x)&0x7) << 24)
#define DITHER_ADD_LSHIFT_R(x) (((x)&0x7) << 20)
#define DITHER_ADD_RSHIFT_R(x) (((x)&0x7) << 16)
#define DITHER_NEW_BIT_MODE BIT(0)
#define DITHER_LSB_ERR_SHIFT_B(x) (((x)&0x7) << 28)
#define DITHER_OVFLW_BIT_B(x) (((x)&0x7) << 24)
#define DITHER_ADD_LSHIFT_B(x) (((x)&0x7) << 20)
#define DITHER_ADD_RSHIFT_B(x) (((x)&0x7) << 16)
#define DITHER_LSB_ERR_SHIFT_G(x) (((x)&0x7) << 12)
#define DITHER_OVFLW_BIT_G(x) (((x)&0x7) << 8)
#define DITHER_ADD_LSHIFT_G(x) (((x)&0x7) << 4)
#define DITHER_ADD_RSHIFT_G(x) (((x)&0x7) << 0)
#define DITHER_TOTAL_MODULE_NUM (2)
static unsigned int g_dither_relay_value[DITHER_TOTAL_MODULE_NUM];
#define index_of_dither(module) ((module == DDP_COMPONENT_DITHER0) ? 0 : 1)
static atomic_t g_dither_is_clock_on = ATOMIC_INIT(0);
static DEFINE_SPINLOCK(g_dither_clock_lock);
// It's a work around for no comp assigned in functions.
static struct mtk_ddp_comp *default_comp;
enum COLOR_IOCTL_CMD {
DITHER_SELECT = 0,
BYPASS_DITHER
};
struct mtk_disp_dither_data {
bool support_shadow;
};
struct mtk_disp_dither {
struct mtk_ddp_comp ddp_comp;
struct drm_crtc *crtc;
int pwr_sta;
unsigned int cfg_reg;
const struct mtk_disp_dither_data *data;
};
static void mtk_dither_config(struct mtk_ddp_comp *comp,
struct mtk_ddp_config *cfg,
struct cmdq_pkt *handle)
{
struct mtk_disp_dither *priv = dev_get_drvdata(comp->dev);
unsigned int enable = 1;
unsigned int width;
if (comp->mtk_crtc->is_dual_pipe)
width = cfg->w / 2;
else
width = cfg->w;
DDPINFO("%s: bbp = %u\n", __func__, cfg->bpc);
DDPINFO("%s: width = %u height = %u\n", __func__, cfg->w, cfg->h);
/* skip redundant config */
if (priv->pwr_sta != 0)
return;
priv->pwr_sta = 1;
if (cfg->bpc == 8) { /* 888 */
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(15),
0x20200001, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(16),
0x20202020, ~0);
} else if (cfg->bpc == 5) { /* 565 */
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(15),
0x50500001, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(16),
0x50504040, ~0);
} else if (cfg->bpc == 6) { /* 666 */
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(15),
0x40400001, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(16),
0x40404040, ~0);
} else if (cfg->bpc > 8) {
/* High depth LCM, no need dither */
DDPINFO("%s: High depth LCM (bpp = %u), no dither\n",
__func__, cfg->bpc);
} else {
/* Invalid dither bpp, bypass dither */
/* FIXME: this case would cause dither hang */
DDPINFO("%s: Invalid dither bpp = %u\n",
__func__, cfg->bpc);
enable = 0;
}
if (enable == 1) {
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(5),
0x00000000, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(6),
0x00003002, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(7),
0x00000000, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(8),
0x00000000, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(9),
0x00000000, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(10),
0x00000000, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(11),
0x00000000, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(12),
0x00000011, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(13),
0x00000000, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DITHER_REG(14),
0x00000000, ~0);
}
priv->cfg_reg = enable << 1 | (priv->cfg_reg & ~(0x1 << 1));
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DISP_DITHER_EN, enable, ~0);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DISP_REG_DITHER_CFG,
enable << 1 |
g_dither_relay_value[index_of_dither(comp->id)], 0x3);
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DISP_REG_DITHER_SIZE,
width << 16 | cfg->h, ~0);
}
static void mtk_dither_start(struct mtk_ddp_comp *comp,
struct cmdq_pkt *handle)
{
struct mtk_disp_dither *priv = dev_get_drvdata(comp->dev);
DDPINFO("%s\n", __func__);
priv->pwr_sta = 1;
}
static void mtk_dither_stop(struct mtk_ddp_comp *comp,
struct cmdq_pkt *handle)
{
struct mtk_disp_dither *priv = dev_get_drvdata(comp->dev);
DDPINFO("%s\n", __func__);
priv->pwr_sta = 0;
}
static void mtk_dither_bypass(struct mtk_ddp_comp *comp, int bypass,
struct cmdq_pkt *handle)
{
struct mtk_disp_dither *priv = dev_get_drvdata(comp->dev);
DDPINFO("%s\n", __func__);
g_dither_relay_value[index_of_dither(comp->id)] = bypass;
if (bypass)
priv->cfg_reg = 0x1 | (priv->cfg_reg & ~0x1);
else
priv->cfg_reg = ~0x1 & priv->cfg_reg;
cmdq_pkt_write(handle, comp->cmdq_base,
comp->regs_pa + DISP_REG_DITHER_CFG,
g_dither_relay_value[index_of_dither(comp->id)], 0x1);
}
struct dither_backup {
unsigned int REG_DITHER_CFG;
};
static struct dither_backup g_dither_backup;
static void ddp_dither_backup(struct mtk_ddp_comp *comp)
{
g_dither_backup.REG_DITHER_CFG =
readl(comp->regs + DISP_REG_DITHER_CFG);
}
static void ddp_dither_restore(struct mtk_ddp_comp *comp)
{
writel(g_dither_backup.REG_DITHER_CFG, comp->regs + DISP_REG_DITHER_CFG);
}
static void mtk_dither_prepare(struct mtk_ddp_comp *comp)
{
#if defined(CONFIG_DRM_MTK_SHADOW_REGISTER_SUPPORT)
struct mtk_disp_dither *priv = dev_get_drvdata(comp->dev);
#endif
mtk_ddp_comp_clk_prepare(comp);
atomic_set(&g_dither_is_clock_on, 1);
#if defined(CONFIG_DRM_MTK_SHADOW_REGISTER_SUPPORT)
if (priv->data->support_shadow) {
/* Enable shadow register and read shadow register */
mtk_ddp_write_mask_cpu(comp, 0x0,
DITHER_REG(0), DITHER_BYPASS_SHADOW);
} else {
/* Bypass shadow register and read shadow register */
mtk_ddp_write_mask_cpu(comp, DITHER_BYPASS_SHADOW,
DITHER_REG(0), DITHER_BYPASS_SHADOW);
}
#else
#if defined(CONFIG_MACH_MT6873) || defined(CONFIG_MACH_MT6853) \
|| defined(CONFIG_MACH_MT6833) || defined(CONFIG_MACH_MT6877) \
|| defined(CONFIG_MACH_MT6781)
/* Bypass shadow register and read shadow register */
mtk_ddp_write_mask_cpu(comp, DITHER_BYPASS_SHADOW,
DITHER_REG(0), DITHER_BYPASS_SHADOW);
#endif
#endif
ddp_dither_restore(comp);
}
static void mtk_dither_unprepare(struct mtk_ddp_comp *comp)
{
unsigned long flags;
DDPINFO("%s @ %d......... spin_lock_irqsave ++ ",
__func__, __LINE__);
spin_lock_irqsave(&g_dither_clock_lock, flags);
DDPINFO("%s @ %d......... spin_lock_irqsave -- ",
__func__, __LINE__);
atomic_set(&g_dither_is_clock_on, 0);
spin_unlock_irqrestore(&g_dither_clock_lock, flags);
DDPINFO("%s @ %d......... spin_unlock_irqrestore ",
__func__, __LINE__);
ddp_dither_backup(comp);
mtk_ddp_comp_clk_unprepare(comp);
}
/* TODO */
/* partial update
* static int _dither_partial_update(struct mtk_ddp_comp *comp, void *arg,
* struct cmdq_pkt *handle)
* {
* struct disp_rect *roi = (struct disp_rect *) arg;
* int width = roi->width;
* int height = roi->height;
*
* cmdq_pkt_write(handle, comp->cmdq_base,
* comp->regs_pa + DISP_REG_DITHER_SIZE, width << 16 | height, ~0);
* return 0;
* }
*
* static int mtk_dither_io_cmd(struct mtk_ddp_comp *comp,
* struct cmdq_pkt *handle,
* enum mtk_ddp_io_cmd io_cmd,
* void *params)
* {
* int ret = -1;
* if (io_cmd == DDP_PARTIAL_UPDATE) {
* _dither_partial_update(comp, params, handle);
* ret = 0;
* }
* return ret;
* }
*/
void mtk_dither_select(struct mtk_ddp_comp *comp,
struct cmdq_pkt *handle,
unsigned int bpc)
{
unsigned int enable = 0x1;
if (bpc == 8) { /* 888 */
writel(0x20200001, comp->regs + DITHER_REG(15));
writel(0x20202020, comp->regs + DITHER_REG(16));
} else if (bpc == 5) { /* 565 */
writel(0x50500001, comp->regs + DITHER_REG(15));
writel(0x50504040, comp->regs + DITHER_REG(16));
} else if (bpc == 6) { /* 666 */
writel(0x40400001, comp->regs + DITHER_REG(15));
writel(0x40404040, comp->regs + DITHER_REG(16));
} else if (bpc > 8) {
/* High depth LCM, no need dither */
DDPINFO("%s: High depth LCM (bpp = %u), no dither\n",
__func__, bpc);
} else {
/* Invalid dither bpp, bypass dither */
/* FIXME: this case would cause dither hang */
DDPINFO("%s: Invalid dither bpp = %u\n", __func__, bpc);
enable = 0;
}
if (enable == 1) {
writel(0x00000000, comp->regs + DITHER_REG(5));
writel(0x00003002, comp->regs + DITHER_REG(6));
writel(0x00000000, comp->regs + DITHER_REG(7));
writel(0x00000000, comp->regs + DITHER_REG(8));
writel(0x00000000, comp->regs + DITHER_REG(9));
writel(0x00000000, comp->regs + DITHER_REG(10));
writel(0x00000000, comp->regs + DITHER_REG(11));
writel(0x00000011, comp->regs + DITHER_REG(12));
writel(0x00000000, comp->regs + DITHER_REG(13));
writel(0x00000000, comp->regs + DITHER_REG(14));
}
writel(enable, comp->regs + DISP_DITHER_EN);
writel(enable << 1 | (~enable), comp->regs + DISP_REG_DITHER_CFG);
}
static int mtk_dither_user_cmd(struct mtk_ddp_comp *comp,
struct cmdq_pkt *handle, unsigned int cmd, void *data)
{
DDPINFO("%s: cmd: %d\n", __func__, cmd);
switch (cmd) {
case DITHER_SELECT:
{
unsigned int bpc = *((unsigned int *)data);
mtk_dither_select(comp, NULL, bpc);
}
break;
case BYPASS_DITHER:
{
int *value = data;
mtk_dither_bypass(comp, *value, handle);
}
break;
default:
DDPPR_ERR("%s: error cmd: %d\n", __func__, cmd);
return -EINVAL;
}
return 0;
}
static const struct mtk_ddp_comp_funcs mtk_disp_dither_funcs = {
.config = mtk_dither_config,
.start = mtk_dither_start,
.stop = mtk_dither_stop,
.bypass = mtk_dither_bypass,
.user_cmd = mtk_dither_user_cmd,
.prepare = mtk_dither_prepare,
.unprepare = mtk_dither_unprepare,
/* partial update
* .io_cmd = mtk_dither_io_cmd,
*/
};
static int mtk_disp_dither_bind(struct device *dev, struct device *master,
void *data)
{
struct mtk_disp_dither *priv = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
int ret;
DDPINFO("%s\n", __func__);
ret = mtk_ddp_comp_register(drm_dev, &priv->ddp_comp);
if (ret < 0) {
dev_err(dev, "Failed to register component %s: %d\n",
dev->of_node->full_name, ret);
return ret;
}
return 0;
}
static void mtk_disp_dither_unbind(struct device *dev, struct device *master,
void *data)
{
struct mtk_disp_dither *priv = dev_get_drvdata(dev);
struct drm_device *drm_dev = data;
mtk_ddp_comp_unregister(drm_dev, &priv->ddp_comp);
}
static const struct component_ops mtk_disp_dither_component_ops = {
.bind = mtk_disp_dither_bind,
.unbind = mtk_disp_dither_unbind,
};
void mtk_dither_dump(struct mtk_ddp_comp *comp)
{
void __iomem *baddr = comp->regs;
DDPDUMP("== %s REGS ==\n", mtk_dump_comp_str(comp));
mtk_cust_dump_reg(baddr, 0x0, 0x20, 0x30, -1);
mtk_cust_dump_reg(baddr, 0x24, 0x28, -1, -1);
}
static int mtk_disp_dither_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mtk_disp_dither *priv;
enum mtk_ddp_comp_id comp_id;
int ret;
DDPINFO("%s+\n", __func__);
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (priv == NULL)
return -ENOMEM;
comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_DITHER);
if ((int)comp_id < 0) {
DDPPR_ERR("Failed to identify by alias: %d\n", comp_id);
return comp_id;
}
if (!default_comp)
default_comp = &priv->ddp_comp;
ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id,
&mtk_disp_dither_funcs);
if (ret != 0) {
DDPPR_ERR("Failed to initialize component: %d\n", ret);
return ret;
}
priv->pwr_sta = 0;
priv->cfg_reg = 0x80000100;
platform_set_drvdata(pdev, priv);
pm_runtime_enable(dev);
ret = component_add(dev, &mtk_disp_dither_component_ops);
if (ret != 0) {
dev_err(dev, "Failed to add component: %d\n", ret);
pm_runtime_disable(dev);
}
DDPINFO("%s-\n", __func__);
return ret;
}
static int mtk_disp_dither_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &mtk_disp_dither_component_ops);
pm_runtime_disable(&pdev->dev);
return 0;
}
static const struct mtk_disp_dither_data mt6779_dither_driver_data = {
.support_shadow = false,
};
static const struct mtk_disp_dither_data mt6885_dither_driver_data = {
.support_shadow = false,
};
static const struct mtk_disp_dither_data mt6873_dither_driver_data = {
.support_shadow = false,
};
static const struct mtk_disp_dither_data mt6853_dither_driver_data = {
.support_shadow = false,
};
static const struct mtk_disp_dither_data mt6877_dither_driver_data = {
.support_shadow = false,
};
static const struct mtk_disp_dither_data mt6833_dither_driver_data = {
.support_shadow = false,
};
static const struct mtk_disp_dither_data mt6781_dither_driver_data = {
.support_shadow = false,
};
static const struct of_device_id mtk_disp_dither_driver_dt_match[] = {
{ .compatible = "mediatek,mt6779-disp-dither",
.data = &mt6779_dither_driver_data},
{ .compatible = "mediatek,mt6885-disp-dither",
.data = &mt6885_dither_driver_data},
{ .compatible = "mediatek,mt6873-disp-dither",
.data = &mt6873_dither_driver_data},
{ .compatible = "mediatek,mt6853-disp-dither",
.data = &mt6853_dither_driver_data},
{ .compatible = "mediatek,mt6877-disp-dither",
.data = &mt6877_dither_driver_data},
{ .compatible = "mediatek,mt6833-disp-dither",
.data = &mt6833_dither_driver_data},
{ .compatible = "mediatek,mt6781-disp-dither",
.data = &mt6781_dither_driver_data},
{},
};
MODULE_DEVICE_TABLE(of, mtk_disp_dither_driver_dt_match);
struct platform_driver mtk_disp_dither_driver = {
.probe = mtk_disp_dither_probe,
.remove = mtk_disp_dither_remove,
.driver = {
.name = "mediatek-disp-dither",
.owner = THIS_MODULE,
.of_match_table = mtk_disp_dither_driver_dt_match,
},
};
void dither_test(const char *cmd, char *debug_output, struct mtk_ddp_comp *comp)
{
unsigned int bpc;
debug_output[0] = '\0';
DDPINFO("%s: %s\n", __func__, cmd);
if (strncmp(cmd, "sel:", 4) == 0) {
if (cmd[4] == '0') {
bpc = 0;
mtk_dither_user_cmd(comp, NULL, DITHER_SELECT, &bpc);
DDPINFO("bpc = 0\n");
} else if (cmd[4] == '1') {
bpc = 5;
mtk_dither_user_cmd(comp, NULL, DITHER_SELECT, &bpc);
DDPINFO("bpc = 5\n");
} else if (cmd[4] == '2') {
bpc = 6;
mtk_dither_user_cmd(comp, NULL, DITHER_SELECT, &bpc);
DDPINFO("bpc = 6\n");
} else if (cmd[4] == '3') {
bpc = 7;
mtk_dither_user_cmd(comp, NULL, DITHER_SELECT, &bpc);
DDPINFO("bpc = 7\n");
} else {
DDPINFO("unknown bpc\n");
}
}
}
void disp_dither_set_bypass(struct drm_crtc *crtc, int bypass)
{
int ret;
ret = mtk_crtc_user_cmd(crtc, default_comp, BYPASS_DITHER, &bypass);
DDPFUNC("ret = %d", ret);
}