/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include "mtk_drm_crtc.h" #include "mtk_drm_ddp_comp.h" #include "mtk_drm_drv.h" #include "mtk_log.h" #include "mtk_disp_gamma.h" #include "mtk_dump.h" #define DISP_GAMMA_EN 0x0000 #define DISP_GAMMA_CFG 0x0020 #define DISP_GAMMA_SIZE 0x0030 #define DISP_GAMMA_LUT 0x0700 #define LUT_10BIT_MASK 0x03ff #define GAMMA_EN BIT(0) #define GAMMA_LUT_EN BIT(1) #define GAMMA_RELAYMODE BIT(0) static unsigned int g_gamma_relay_value[DISP_GAMMA_TOTAL]; #define index_of_gamma(module) ((module == DDP_COMPONENT_GAMMA0) ? 0 : 1) // It's a work around for no comp assigned in functions. static struct mtk_ddp_comp *default_comp; static struct DISP_GAMMA_LUT_T *g_disp_gamma_lut[DISP_GAMMA_TOTAL] = { NULL }; static DEFINE_MUTEX(g_gamma_global_lock); static atomic_t g_gamma_is_clock_on[DISP_GAMMA_TOTAL] = { ATOMIC_INIT(0), ATOMIC_INIT(0)}; static DEFINE_SPINLOCK(g_gamma_clock_lock); /* TODO */ /* static ddp_module_notify g_gamma_ddp_notify; */ enum GAMMA_IOCTL_CMD { SET_GAMMALUT = 0, BYPASS_GAMMA }; struct mtk_disp_gamma_data { bool support_shadow; }; struct mtk_disp_gamma { struct mtk_ddp_comp ddp_comp; struct drm_crtc *crtc; const struct mtk_disp_gamma_data *data; }; static void mtk_gamma_init(struct mtk_ddp_comp *comp, struct mtk_ddp_config *cfg, struct cmdq_pkt *handle) { unsigned int width; if (comp->mtk_crtc->is_dual_pipe) width = cfg->w / 2; else width = cfg->w; cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_SIZE, (width << 16) | cfg->h, ~0); cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_EN, GAMMA_EN, ~0); } static void mtk_gamma_config(struct mtk_ddp_comp *comp, struct mtk_ddp_config *cfg, struct cmdq_pkt *handle) { /* TODO: only call init function if frame dirty */ mtk_gamma_init(comp, cfg, handle); //cmdq_pkt_write(handle, comp->cmdq_base, // comp->regs_pa + DISP_GAMMA_SIZE, // (cfg->w << 16) | cfg->h, ~0); //cmdq_pkt_write(handle, comp->cmdq_base, // comp->regs_pa + DISP_GAMMA_CFG, // GAMMA_RELAYMODE, BIT(0)); } static int mtk_gamma_write_lut_reg(struct mtk_ddp_comp *comp, struct cmdq_pkt *handle, int lock) { struct DISP_GAMMA_LUT_T *gamma_lut; int i; int ret = 0; int id = index_of_gamma(comp->id); if (lock) mutex_lock(&g_gamma_global_lock); gamma_lut = g_disp_gamma_lut[id]; if (gamma_lut == NULL) { DDPINFO("%s: table [%d] not initialized\n", __func__, id); ret = -EFAULT; goto gamma_write_lut_unlock; } for (i = 0; i < DISP_GAMMA_LUT_SIZE; i++) { cmdq_pkt_write(handle, comp->cmdq_base, (comp->regs_pa + DISP_GAMMA_LUT + i * 4), gamma_lut->lut[i], ~0); if ((i & 0x3f) == 0) { DDPINFO("[0x%08lx](%d) = 0x%x\n", (long)(comp->regs_pa + DISP_GAMMA_LUT + i * 4), i, gamma_lut->lut[i]); } } i--; DDPINFO("[0x%08lx](%d) = 0x%x\n", (long)(comp->regs_pa + DISP_GAMMA_LUT + i * 4), i, gamma_lut->lut[i]); if ((int)(gamma_lut->lut[0] & 0x3FF) - (int)(gamma_lut->lut[510] & 0x3FF) > 0) { cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_CFG, 0x1 << 2, 0x4); DDPINFO("decreasing LUT\n"); } else { cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_CFG, 0x0 << 2, 0x4); DDPINFO("Incremental LUT\n"); } cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_CFG, 0x2 | g_gamma_relay_value[id], 0x3); gamma_write_lut_unlock: if (lock) mutex_unlock(&g_gamma_global_lock); return ret; } static int mtk_gamma_set_lut(struct mtk_ddp_comp *comp, struct cmdq_pkt *handle, struct DISP_GAMMA_LUT_T *user_gamma_lut) { /* TODO: use CPU to write register */ int ret = 0; int id; struct DISP_GAMMA_LUT_T *gamma_lut, *old_lut; DDPINFO("%s\n", __func__); gamma_lut = kmalloc(sizeof(struct DISP_GAMMA_LUT_T), GFP_KERNEL); if (gamma_lut == NULL) { DDPPR_ERR("%s: no memory\n", __func__); return -EFAULT; } if (user_gamma_lut == NULL) { ret = -EFAULT; kfree(gamma_lut); } else { memcpy(gamma_lut, user_gamma_lut, sizeof(struct DISP_GAMMA_LUT_T)); id = index_of_gamma(comp->id); if (id >= 0 && id < DISP_GAMMA_TOTAL) { mutex_lock(&g_gamma_global_lock); old_lut = g_disp_gamma_lut[id]; g_disp_gamma_lut[id] = gamma_lut; DDPINFO("%s: Set module(%d) lut\n", __func__, comp->id); ret = mtk_gamma_write_lut_reg(comp, handle, 0); mutex_unlock(&g_gamma_global_lock); if (old_lut != NULL) kfree(old_lut); if (comp->mtk_crtc != NULL) mtk_crtc_check_trigger(comp->mtk_crtc, false, false); } else { DDPPR_ERR("%s: invalid ID = %d\n", __func__, comp->id); ret = -EFAULT; } } return ret; } int mtk_drm_ioctl_set_gammalut(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct mtk_drm_private *private = dev->dev_private; struct mtk_ddp_comp *comp = private->ddp_comp[DDP_COMPONENT_GAMMA0]; struct drm_crtc *crtc = private->crtc[0]; return mtk_crtc_user_cmd(crtc, comp, SET_GAMMALUT, data); } static void mtk_gamma_start(struct mtk_ddp_comp *comp, struct cmdq_pkt *handle) { DDPINFO("%s\n", __func__); cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_EN, GAMMA_EN, ~0); mtk_gamma_write_lut_reg(comp, handle, 0); } static void mtk_gamma_stop(struct mtk_ddp_comp *comp, struct cmdq_pkt *handle) { cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_EN, 0x0, ~0); } static void mtk_gamma_bypass(struct mtk_ddp_comp *comp, int bypass, struct cmdq_pkt *handle) { DDPINFO("%s\n", __func__); cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_CFG, bypass, 0x1); g_gamma_relay_value[index_of_gamma(comp->id)] = bypass; } static void mtk_gamma_set(struct mtk_ddp_comp *comp, struct drm_crtc_state *state, struct cmdq_pkt *handle) { unsigned int i; struct drm_color_lut *lut; u32 word = 0; u32 word_first = 0; u32 word_last = 0; DDPINFO("%s\n", __func__); if (state->gamma_lut) { cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_CFG, 1<gamma_lut->data; for (i = 0; i < MTK_LUT_SIZE; i++) { word = GAMMA_ENTRY(lut[i].red >> 6, lut[i].green >> 6, lut[i].blue >> 6); cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + (DISP_GAMMA_LUT + i * 4), word, ~0); // first & last word for // decreasing/incremental LUT if (i == 0) word_first = word; else if (i == MTK_LUT_SIZE - 1) word_last = word; } } if ((word_first - word_last) > 0) { cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_CFG, 0x1 << 2, 0x4); DDPINFO("decreasing LUT\n"); } else { cmdq_pkt_write(handle, comp->cmdq_base, comp->regs_pa + DISP_GAMMA_CFG, 0x0 << 2, 0x4); DDPINFO("Incremental LUT\n"); } } static int mtk_gamma_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 SET_GAMMALUT: { struct DISP_GAMMA_LUT_T *config = data; if (mtk_gamma_set_lut(comp, handle, config) < 0) { DDPPR_ERR("%s: failed\n", __func__); return -EFAULT; } if (comp->mtk_crtc->is_dual_pipe) { struct mtk_drm_crtc *mtk_crtc = comp->mtk_crtc; struct drm_crtc *crtc = &mtk_crtc->base; struct mtk_drm_private *priv = crtc->dev->dev_private; struct mtk_ddp_comp *comp_gamma1 = priv->ddp_comp[DDP_COMPONENT_GAMMA1]; if (mtk_gamma_set_lut(comp_gamma1, handle, config) < 0) { DDPPR_ERR("%s: comp_gamma1 failed\n", __func__); return -EFAULT; } } } break; case BYPASS_GAMMA: { int *value = data; mtk_gamma_bypass(comp, *value, handle); } break; default: DDPPR_ERR("%s: error cmd: %d\n", __func__, cmd); return -EINVAL; } return 0; } struct gamma_backup { unsigned int GAMMA_CFG; }; static struct gamma_backup g_gamma_backup; static void ddp_dither_backup(struct mtk_ddp_comp *comp) { g_gamma_backup.GAMMA_CFG = readl(comp->regs + DISP_GAMMA_CFG); } static void ddp_dither_restore(struct mtk_ddp_comp *comp) { writel(g_gamma_backup.GAMMA_CFG, comp->regs + DISP_GAMMA_CFG); } static void mtk_gamma_prepare(struct mtk_ddp_comp *comp) { mtk_ddp_comp_clk_prepare(comp); atomic_set(&g_gamma_is_clock_on[index_of_gamma(comp->id)], 1); ddp_dither_restore(comp); } static void mtk_gamma_unprepare(struct mtk_ddp_comp *comp) { unsigned long flags; DDPINFO("%s @ %d......... spin_trylock_irqsave ++ ", __func__, __LINE__); spin_lock_irqsave(&g_gamma_clock_lock, flags); DDPINFO("%s @ %d......... spin_trylock_irqsave -- ", __func__, __LINE__); atomic_set(&g_gamma_is_clock_on[index_of_gamma(comp->id)], 0); spin_unlock_irqrestore(&g_gamma_clock_lock, flags); DDPINFO("%s @ %d......... spin_unlock_irqrestore ", __func__, __LINE__); ddp_dither_backup(comp); mtk_ddp_comp_clk_unprepare(comp); } static const struct mtk_ddp_comp_funcs mtk_disp_gamma_funcs = { .gamma_set = mtk_gamma_set, .config = mtk_gamma_config, .start = mtk_gamma_start, .stop = mtk_gamma_stop, .bypass = mtk_gamma_bypass, .user_cmd = mtk_gamma_user_cmd, .prepare = mtk_gamma_prepare, .unprepare = mtk_gamma_unprepare, }; static int mtk_disp_gamma_bind(struct device *dev, struct device *master, void *data) { struct mtk_disp_gamma *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_gamma_unbind(struct device *dev, struct device *master, void *data) { struct mtk_disp_gamma *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_gamma_component_ops = { .bind = mtk_disp_gamma_bind, .unbind = mtk_disp_gamma_unbind, }; void mtk_gamma_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, 0x24, 0x28); } static int mtk_disp_gamma_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk_disp_gamma *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_GAMMA); if ((int)comp_id < 0) { DDPPR_ERR("Failed to identify by alias: %d\n", comp_id); return comp_id; } ret = mtk_ddp_comp_init(dev, dev->of_node, &priv->ddp_comp, comp_id, &mtk_disp_gamma_funcs); if (ret != 0) { DDPPR_ERR("Failed to initialize component: %d\n", ret); return ret; } if (!default_comp) default_comp = &priv->ddp_comp; platform_set_drvdata(pdev, priv); pm_runtime_enable(dev); ret = component_add(dev, &mtk_disp_gamma_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_gamma_remove(struct platform_device *pdev) { component_del(&pdev->dev, &mtk_disp_gamma_component_ops); pm_runtime_disable(&pdev->dev); return 0; } static const struct mtk_disp_gamma_data mt6779_gamma_driver_data = { .support_shadow = false, }; static const struct mtk_disp_gamma_data mt6885_gamma_driver_data = { .support_shadow = false, }; static const struct mtk_disp_gamma_data mt6873_gamma_driver_data = { .support_shadow = false, }; static const struct mtk_disp_gamma_data mt6781_gamma_driver_data = { .support_shadow = false, }; static const struct mtk_disp_gamma_data mt6853_gamma_driver_data = { .support_shadow = false, }; static const struct mtk_disp_gamma_data mt6877_gamma_driver_data = { .support_shadow = false, }; static const struct mtk_disp_gamma_data mt6833_gamma_driver_data = { .support_shadow = false, }; static const struct of_device_id mtk_disp_gamma_driver_dt_match[] = { { .compatible = "mediatek,mt6779-disp-gamma", .data = &mt6779_gamma_driver_data}, { .compatible = "mediatek,mt6885-disp-gamma", .data = &mt6885_gamma_driver_data}, { .compatible = "mediatek,mt6873-disp-gamma", .data = &mt6873_gamma_driver_data}, { .compatible = "mediatek,mt6853-disp-gamma", .data = &mt6853_gamma_driver_data}, { .compatible = "mediatek,mt6877-disp-gamma", .data = &mt6877_gamma_driver_data}, { .compatible = "mediatek,mt6833-disp-gamma", .data = &mt6833_gamma_driver_data}, { .compatible = "mediatek,mt6781-disp-gamma", .data = &mt6833_gamma_driver_data}, {}, }; MODULE_DEVICE_TABLE(of, mtk_disp_gamma_driver_dt_match); struct platform_driver mtk_disp_gamma_driver = { .probe = mtk_disp_gamma_probe, .remove = mtk_disp_gamma_remove, .driver = { .name = "mediatek-disp-gamma", .owner = THIS_MODULE, .of_match_table = mtk_disp_gamma_driver_dt_match, }, }; void disp_gamma_set_bypass(struct drm_crtc *crtc, int bypass) { int ret; ret = mtk_crtc_user_cmd(crtc, default_comp, BYPASS_GAMMA, &bypass); DDPFUNC("ret = %d", ret); }