Skip to content

Commit

Permalink
drm/msm/sde: Introduce KCAL color control
Browse files Browse the repository at this point in the history
Use PCC and HSIC interfaces to manually calibrate the colors of the
DSI panel via sysfs.  This is a simple driver which uses predefined
API to do this. As it is a purely platform driver, sysfs is located
at /sys/devices/platform/kcal_ctrl.0

Supported PCC and HSIC features:
- RGB control,
- Color distortion control,
- Saturation intensity control,
- Contrast intensity control,
- Color brightness control,
- Grayscale mode,
- Astronomy mode;

We already have atomic ops to alter DSI colors, but users prefer
this interface over that.

Signed-off-by: Alesaiko <solcmdr@gmail.com>
Signed-off-by: Mrinal Ghosh <mg712702@gmail.com>
  • Loading branch information
alesaiko authored and vantoman committed Oct 22, 2021
1 parent c2a0950 commit 2914580
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 0 deletions.
9 changes: 9 additions & 0 deletions drivers/gpu/drm/msm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ config DRM_MSM_DSI_14NM_PHY
help
Choose this option if DSI PHY on 8996 is used on the platform.

config DRM_MSM_KCAL_CTRL
bool "Enable kernel-level color calibration control"
depends on DRM_MSM
default y
help
Choose this option for kernel-level color calibration control.
This will adjust every atomic operation pcc and hsic call
configuration with the values from kernel.

config DRM_SDE_EVTLOG_DEBUG
bool "Enable event logging in MSM DRM"
depends on DRM_MSM
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/msm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ msm_drm-y := \
sde_dbg_evtlog.o \
xiaomi_frame_stat.o

msm_drm-$(CONFIG_DRM_MSM_KCAL_CTRL) += sde/sde_hw_kcal_ctrl.o

msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi.o \
hdmi/hdmi_audio.o \
hdmi/hdmi_bridge.o \
Expand Down
201 changes: 201 additions & 0 deletions drivers/gpu/drm/msm/sde/sde_hw_kcal_ctrl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* Copyright (C) 2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#define KCAL_CTRL "kcal_ctrl"

#include <linux/module.h>
#include <linux/platform_device.h>
#include <uapi/drm/msm_drm_pp.h>
#include "sde_hw_kcal_ctrl.h"

static struct sde_hw_kcal kcal_lut_data = {
.enabled = SDE_HW_KCAL_ENABLED,
.min_value = SDE_HW_KCAL_MIN_VALUE,

.pcc = (typeof(kcal_lut_data.pcc)) {
.red = SDE_HW_KCAL_INIT_RED,
.green = SDE_HW_KCAL_INIT_GREEN,
.blue = SDE_HW_KCAL_INIT_BLUE,
},

.hsic = (typeof(kcal_lut_data.hsic)) {
.hue = SDE_HW_KCAL_INIT_HUE,
.saturation = SDE_HW_KCAL_INIT_ADJ,
.value = SDE_HW_KCAL_INIT_ADJ,
.contrast = SDE_HW_KCAL_INIT_ADJ,
},
};

struct sde_hw_kcal *sde_hw_kcal_get(void)
{
return &kcal_lut_data;
}

struct drm_msm_pa_hsic sde_hw_kcal_hsic_struct(void)
{
return (struct drm_msm_pa_hsic) {
.flags = PA_HSIC_HUE_ENABLE |
PA_HSIC_SAT_ENABLE |
PA_HSIC_VAL_ENABLE |
PA_HSIC_CONT_ENABLE,
.hue = kcal_lut_data.hsic.hue,
.saturation = kcal_lut_data.hsic.saturation,
.value = kcal_lut_data.hsic.value,
.contrast = kcal_lut_data.hsic.contrast,
};
}

void sde_hw_kcal_pcc_adjust(u32 *data, int plane)
{
struct sde_hw_kcal_pcc *pcc = &kcal_lut_data.pcc;
u32 palette[3] = { pcc->red, pcc->green, pcc->blue };
int idx = plane + 3 * (plane + 1);

data[idx] *= palette[plane];
data[idx] /= 256;
}

#define create_one_rw_node(node) \
static DEVICE_ATTR(node, 0644, show_##node, store_##node)

#define define_one_kcal_node(node, object, min, max) \
static ssize_t show_##node(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
return scnprintf(buf, 6, "%u\n", kcal_lut_data.object); \
} \
\
static ssize_t store_##node(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t count) \
{ \
u32 val; \
int ret; \
\
ret = kstrtouint(buf, 10, &val); \
if (ret || val < min || val > max) \
return -EINVAL; \
\
kcal_lut_data.object = val; \
\
return count; \
} \
\
create_one_rw_node(node)

static ssize_t show_kcal(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct sde_hw_kcal_pcc *pcc = &kcal_lut_data.pcc;

return scnprintf(buf, 13, "%u %u %u\n",
pcc->red, pcc->green, pcc->blue);
}

static ssize_t store_kcal(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct sde_hw_kcal_pcc *pcc = &kcal_lut_data.pcc;
u32 kcal_r, kcal_g, kcal_b;
int ret;

ret = sscanf(buf, "%u %u %u", &kcal_r, &kcal_g, &kcal_b);
if (ret != 3 ||
kcal_r < 1 || kcal_r > 256 ||
kcal_g < 1 || kcal_g > 256 ||
kcal_b < 1 || kcal_b > 256)
return -EINVAL;

pcc->red = max(kcal_r, kcal_lut_data.min_value);
pcc->green = max(kcal_g, kcal_lut_data.min_value);
pcc->blue = max(kcal_b, kcal_lut_data.min_value);

return count;
}

create_one_rw_node(kcal);
define_one_kcal_node(kcal_enable, enabled, 0, 1);
define_one_kcal_node(kcal_min, min_value, 1, 256);
define_one_kcal_node(kcal_hue, hsic.hue, 0, 1536);
define_one_kcal_node(kcal_sat, hsic.saturation, 128, 383);
define_one_kcal_node(kcal_val, hsic.value, 128, 383);
define_one_kcal_node(kcal_cont, hsic.contrast, 128, 383);

static int sde_hw_kcal_ctrl_probe(struct platform_device *pdev)
{
int ret;

ret = device_create_file(&pdev->dev, &dev_attr_kcal);
ret |= device_create_file(&pdev->dev, &dev_attr_kcal_enable);
ret |= device_create_file(&pdev->dev, &dev_attr_kcal_min);
ret |= device_create_file(&pdev->dev, &dev_attr_kcal_hue);
ret |= device_create_file(&pdev->dev, &dev_attr_kcal_sat);
ret |= device_create_file(&pdev->dev, &dev_attr_kcal_val);
ret |= device_create_file(&pdev->dev, &dev_attr_kcal_cont);
if (ret)
pr_err("Unable to create sysfs nodes\n");

return ret;
}

static int sde_hw_kcal_ctrl_remove(struct platform_device *pdev)
{
device_remove_file(&pdev->dev, &dev_attr_kcal_cont);
device_remove_file(&pdev->dev, &dev_attr_kcal_val);
device_remove_file(&pdev->dev, &dev_attr_kcal_sat);
device_remove_file(&pdev->dev, &dev_attr_kcal_hue);
device_remove_file(&pdev->dev, &dev_attr_kcal_min);
device_remove_file(&pdev->dev, &dev_attr_kcal_enable);
device_remove_file(&pdev->dev, &dev_attr_kcal);

return 0;
}

static struct platform_driver sde_hw_kcal_ctrl_driver = {
.probe = sde_hw_kcal_ctrl_probe,
.remove = sde_hw_kcal_ctrl_remove,
.driver = {
.name = KCAL_CTRL,
.owner = THIS_MODULE,
},
};

static struct platform_device sde_hw_kcal_ctrl_device = {
.name = KCAL_CTRL,
};

static int __init sde_hw_kcal_ctrl_init(void)
{
int ret;

ret = platform_driver_register(&sde_hw_kcal_ctrl_driver);
if (ret) {
pr_err("Unable to register platform driver\n");
return ret;
}

ret = platform_device_register(&sde_hw_kcal_ctrl_device);
if (ret) {
pr_err("Unable to register platform device\n");
platform_driver_unregister(&sde_hw_kcal_ctrl_driver);
return ret;
}

return 0;
}
late_initcall(sde_hw_kcal_ctrl_init);
81 changes: 81 additions & 0 deletions drivers/gpu/drm/msm/sde/sde_hw_kcal_ctrl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#define SDE_HW_KCAL_ENABLED (1)

#define SDE_HW_KCAL_MIN_VALUE (20)
#define SDE_HW_KCAL_INIT_RED (256)
#define SDE_HW_KCAL_INIT_GREEN (256)
#define SDE_HW_KCAL_INIT_BLUE (256)

#define SDE_HW_KCAL_INIT_HUE (0)
#define SDE_HW_KCAL_INIT_ADJ (255)

struct sde_hw_kcal_pcc {
u32 red;
u32 green;
u32 blue;
};

struct sde_hw_kcal_hsic {
u32 hue;
u32 saturation;
u32 value;
u32 contrast;
};

struct sde_hw_kcal {
struct sde_hw_kcal_pcc pcc;
struct sde_hw_kcal_hsic hsic;

u32 enabled:1;
u32 min_value;
};

#ifdef CONFIG_DRM_MSM_KCAL_CTRL
/**
* sde_hw_kcal_get() - get a handle to internal kcal calibration plan.
*
* Pointer is used here for performance reasons. Races are expected in
* color processing code.
*/
struct sde_hw_kcal *sde_hw_kcal_get(void);

/**
* sde_hw_kcal_hsic_struct() - get hsic configuration structure with
* applied adjustments from kcal.
*/
struct drm_msm_pa_hsic sde_hw_kcal_hsic_struct(void);

/**
* sde_hw_kcal_pcc_adjust() - change rgb colors according to kcal setup.
* @data: data array of pcc coefficients.
* @plane: index of pcc color plane.
*/
void sde_hw_kcal_pcc_adjust(u32 *data, int plane);
#else
static inline struct sde_hw_kcal sde_hw_kcal_get(void)
{
return (struct sde_hw_kcal) { .enabled = false };
}

static inline struct drm_msm_pa_hsic sde_hw_kcal_hsic_struct(void)
{
return (struct drm_msm_pa_hsic) { .flags = 0 };
}

static inline void sde_hw_kcal_pcc_adjust(u32 *data, int plane)
{

}
#endif
23 changes: 23 additions & 0 deletions drivers/gpu/drm/msm/sde/sde_hw_reg_dma_v1_color_proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "sde_reg_dma.h"
#include "sde_hw_reg_dma_v1_color_proc.h"
#include "sde_hw_color_proc_common_v4.h"
#include "sde_hw_kcal_ctrl.h"
#include "sde_hw_ctl.h"
#include "sde_hw_sspp.h"
#include "sde_hwio.h"
Expand Down Expand Up @@ -1030,11 +1031,23 @@ reg_dmav1_setup_dspp_pa_hsicv17_apply(struct sde_hw_dspp *ctx,
return rc;
}

static inline void
reg_dmav1_setup_dspp_pa_hsicv17_kcal(struct sde_hw_dspp *ctx, void *ctl)
{
struct drm_msm_pa_hsic hsic_cfg = sde_hw_kcal_hsic_struct();
int rc;

rc = reg_dmav1_setup_dspp_pa_hsicv17_apply(ctx, &hsic_cfg, ctl);
if (rc)
pr_err("kernel hsic application failed ret %d\n", rc);
}

void reg_dmav1_setup_dspp_pccv4(struct sde_hw_dspp *ctx, void *cfg)
{
struct sde_hw_reg_dma_ops *dma_ops;
struct sde_reg_dma_kickoff_cfg kick_off;
struct sde_hw_cp_cfg *hw_cfg = cfg;
struct sde_hw_kcal *kcal = sde_hw_kcal_get();
struct sde_reg_dma_setup_ops_cfg dma_write_cfg;
struct drm_msm_pcc *pcc_cfg;
struct drm_msm_pcc_coeff *coeffs = NULL;
Expand Down Expand Up @@ -1106,6 +1119,10 @@ void reg_dmav1_setup_dspp_pccv4(struct sde_hw_dspp *ctx, void *cfg)
data[i + 3] = coeffs->r;
data[i + 6] = coeffs->g;
data[i + 9] = coeffs->b;

if (kcal->enabled)
sde_hw_kcal_pcc_adjust(data, i);

data[i + 12] = coeffs->rg;
data[i + 15] = coeffs->rb;
data[i + 18] = coeffs->gb;
Expand Down Expand Up @@ -1138,16 +1155,22 @@ void reg_dmav1_setup_dspp_pccv4(struct sde_hw_dspp *ctx, void *cfg)
if (rc)
DRM_ERROR("failed to kick off ret %d\n", rc);

if (kcal->enabled)
reg_dmav1_setup_dspp_pa_hsicv17_kcal(ctx, hw_cfg->ctl);
exit:
kfree(data);
}

void reg_dmav1_setup_dspp_pa_hsicv17(struct sde_hw_dspp *ctx, void *cfg)
{
struct sde_hw_cp_cfg *hw_cfg = cfg;
struct sde_hw_kcal *kcal = sde_hw_kcal_get();
u32 opcode = 0;
int rc;

if (kcal->enabled)
return;

opcode = SDE_REG_READ(&ctx->hw, ctx->cap->sblk->hsic.base);

rc = reg_dma_dspp_check(ctx, cfg, HSIC);
Expand Down

0 comments on commit 2914580

Please sign in to comment.