kernel_samsung_a53x/drivers/gpu/drm/samsung/dpu/panel/mcd-panel-samsung-helper.c
2024-06-15 16:02:09 -03:00

478 lines
12 KiB
C
Executable file

/* drivers/gpu/drm/samsung/dpu/panel/mcd-panel-samsung-helper.c
*
* Samsung SoC display driver.
*
* Copyright (c) 2021 Samsung Electronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/of.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/sort.h>
#include <drm/drm_modes.h>
#include "panel-samsung-drv.h"
#include "mcd-panel-samsung-helper.h"
/*
* comparison function used to sort exynos panel mode
* (order:descending)
*/
__visible_for_testing int compare_exynos_panel_mode(const void *a, const void *b)
{
int v1, v2;
/* compare width_mm */
v1 = ((struct exynos_panel_mode *)a)->mode.width_mm;
v2 = ((struct exynos_panel_mode *)b)->mode.width_mm;
if (v1 != v2)
return v2 - v1;
/* compare height_mm */
v1 = ((struct exynos_panel_mode *)a)->mode.height_mm;
v2 = ((struct exynos_panel_mode *)b)->mode.height_mm;
if (v1 != v2)
return v2 - v1;
/* compare horizontal display */
v1 = ((struct exynos_panel_mode *)a)->mode.hdisplay;
v2 = ((struct exynos_panel_mode *)b)->mode.hdisplay;
if (v1 != v2)
return v2 - v1;
/* compare vertical display */
v1 = ((struct exynos_panel_mode *)a)->mode.vdisplay;
v2 = ((struct exynos_panel_mode *)b)->mode.vdisplay;
if (v1 != v2)
return v2 - v1;
/* compare vertical refresh rate */
v1 = drm_mode_vrefresh(&((struct exynos_panel_mode *)a)->mode);
v2 = drm_mode_vrefresh(&((struct exynos_panel_mode *)b)->mode);
if (v1 != v2)
return v2 - v1;
/* compare clock */
v1 = ((struct exynos_panel_mode *)a)->mode.clock;
v2 = ((struct exynos_panel_mode *)b)->mode.clock;
if (v1 != v2)
return v2 - v1;
/* compare vtotal */
v1 = ((struct exynos_panel_mode *)a)->mode.vtotal;
v2 = ((struct exynos_panel_mode *)b)->mode.vtotal;
if (v1 != v2)
return v2 - v1;
/* compare vscan */
v1 = ((struct exynos_panel_mode *)a)->mode.vscan;
v2 = ((struct exynos_panel_mode *)b)->mode.vscan;
if (v1 != v2)
return v2 - v1;
return 0;
}
__visible_for_testing int exynos_mode_snprintf(const struct exynos_display_mode *mode, char *buf, size_t size)
{
if (!mode || !buf || !size)
return 0;
return snprintf(buf, size, "Exynos Modeline " EXYNOS_MODE_FMT "\n", EXYNOS_MODE_ARG(mode));
}
void exynos_mode_debug_printmodeline(const struct exynos_display_mode *mode)
{
char buf[128];
exynos_mode_snprintf(mode, buf, sizeof(buf));
pr_debug("%s", buf);
}
void exynos_mode_info_printmodeline(const struct exynos_display_mode *mode)
{
char buf[128];
exynos_mode_snprintf(mode, buf, sizeof(buf));
pr_info("%s", buf);
}
__visible_for_testing int exynos_panel_mode_snprintf(const struct exynos_panel_mode *mode, char *buf, size_t size)
{
if (!mode || !buf || !size)
return 0;
return snprintf(buf, size, "Exynos Panel Modeline (DRM)" DRM_MODE_FMT " (EXYNOS)" EXYNOS_MODE_FMT "\n",
DRM_MODE_ARG(&mode->mode), EXYNOS_MODE_ARG(&mode->exynos_mode));
}
void exynos_panel_mode_debug_printmodeline(const struct exynos_panel_mode *mode)
{
char buf[128];
exynos_panel_mode_snprintf(mode, buf, sizeof(buf));
pr_debug("%s", buf);
}
void exynos_panel_mode_info_printmodeline(const struct exynos_panel_mode *mode)
{
char buf[128];
exynos_panel_mode_snprintf(mode, buf, sizeof(buf));
pr_info("%s", buf);
}
/**
* exynos_drm_mode_set_name - set the name on a mode
* @mode: name will be set in this mode
* @refresh_mode : extension of exynos_drm_mode for mcd-panel (e.g. ns, hs, phs)
*
* Set the name of @mode to a exynos drm format which is
* <hdisplay>x<vdisplay>@<vrefresh><refreshmode>.
*/
void exynos_drm_mode_set_name(struct drm_display_mode *mode, int refresh_mode)
{
drm_mode_set_name(mode);
snprintf(mode->name + strlen(mode->name),
DRM_DISPLAY_MODE_LEN - strlen(mode->name), "@%d%s",
drm_mode_vrefresh(mode), refresh_mode_to_str(refresh_mode));
}
int drm_display_mode_from_panel_display_mode(struct panel_display_mode *pdm, struct drm_display_mode *ddm)
{
int vscan;
if (!pdm | !ddm)
return -EINVAL;
ddm->hdisplay = pdm->width;
ddm->hsync_start = ddm->hdisplay + pdm->panel_hporch[PANEL_PORCH_HFP];
ddm->hsync_end = ddm->hsync_start + pdm->panel_hporch[PANEL_PORCH_HSA];
ddm->htotal = ddm->hsync_end + pdm->panel_hporch[PANEL_PORCH_HBP];
ddm->vdisplay = pdm->height;
ddm->vsync_start = ddm->vdisplay + pdm->panel_vporch[PANEL_PORCH_VFP];
ddm->vsync_end = ddm->vsync_start + pdm->panel_vporch[PANEL_PORCH_VSA];
ddm->vtotal = ddm->vsync_end + pdm->panel_vporch[PANEL_PORCH_VBP];
/*
* 'vscan' decide how many times real display(e.g. AMOLED-PANEL)
* vertical scan while one frame update.
*/
vscan = panel_mode_vscan(pdm);
if (vscan > 1)
ddm->vscan = vscan;
ddm->clock = ddm->htotal * ddm->vtotal * pdm->refresh_rate * vscan / 1000;
ddm->type = DRM_MODE_TYPE_DRIVER;
exynos_drm_mode_set_name(ddm, pdm->refresh_mode);
return 0;
}
int exynos_display_mode_from_panel_display_mode(struct panel_display_mode *pdm, struct exynos_display_mode *edm)
{
if (!pdm | !edm)
return -EINVAL;
edm->dsc.enabled = pdm->dsc_en;
edm->dsc.dsc_count = pdm->dsc_cnt;
edm->dsc.slice_count = pdm->dsc_slice_num;
edm->dsc.slice_height = pdm->dsc_slice_h;
edm->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS | (pdm->panel_video_mode ? MIPI_DSI_MODE_VIDEO : 0);
edm->bpc = 8;
edm->is_lp_mode = (pdm->panel_refresh_rate <= 30);
return 0;
}
int exynos_panel_mode_from_panel_display_mode(struct panel_display_mode *pdm, struct exynos_panel_mode *epm)
{
if (!pdm | !epm)
return -EINVAL;
drm_display_mode_from_panel_display_mode(pdm, &epm->mode);
exynos_display_mode_from_panel_display_mode(pdm, &epm->exynos_mode);
return 0;
}
struct panel_display_mode *exynos_panel_find_panel_mode(
struct panel_display_modes *pdms, const struct drm_display_mode *pmode)
{
struct drm_display_mode t_pmode;
struct panel_display_mode *pdm = NULL;
int i, ret;
for (i = 0; i < pdms->num_modes; i++) {
memset(&t_pmode, 0, sizeof(t_pmode));
ret = drm_display_mode_from_panel_display_mode(pdms->modes[i], &t_pmode);
if (ret < 0)
continue;
if (!strcmp(t_pmode.name, pmode->name)) {
pdm = pdms->modes[i];
break;
}
}
return pdm;
}
EXPORT_SYMBOL(exynos_panel_find_panel_mode);
/**
* exynos_panel_mode_create - create a new exynos panel mode
* @ctx: exynos_panel
*
* Create a new, cleared exynos_panel_mode with kzalloc, allocate an ID for it
* and return it.
*
* Returns:
* Pointer to new mode on success, NULL on error.
*/
struct exynos_panel_mode *exynos_panel_mode_create(struct exynos_panel *ctx)
{
struct exynos_panel_mode *nmode;
nmode = kzalloc(sizeof(struct exynos_panel_mode), GFP_KERNEL);
if (!nmode)
return NULL;
return nmode;
}
EXPORT_SYMBOL(exynos_panel_mode_create);
/**
* exynos_panel_mode_destroy - remove a mode
* @ctx: exynos_panel
* @mode: mode to remove
*
* Release @mode's unique ID, then free it @mode structure itself using kfree.
*/
void exynos_panel_mode_destroy(struct exynos_panel *ctx, struct exynos_panel_mode *mode)
{
if (!mode)
return;
kfree(mode);
}
EXPORT_SYMBOL(exynos_panel_mode_destroy);
/**
* exynos_panel_desc_create - create a new exynos panel desc
* @ctx: exynos_panel
*
* Create a new, cleared exynos_panel_desc with kzalloc, allocate an ID for it
* and return it.
*
* Returns:
* Pointer to new desc on success, NULL on error.
*/
struct exynos_panel_desc *exynos_panel_desc_create(struct exynos_panel *ctx)
{
struct exynos_panel_desc *ndesc;
ndesc = kzalloc(sizeof(struct exynos_panel_desc), GFP_KERNEL);
if (!ndesc)
return NULL;
return ndesc;
}
EXPORT_SYMBOL(exynos_panel_desc_create);
/**
* exynos_panel_desc_destroy - remove a desc
* @ctx: exynos_panel
* @desc: desc to remove
*
* Release @desc's unique ID, then free it @desc structure itself using kfree.
*/
void exynos_panel_desc_destroy(struct exynos_panel *ctx, struct exynos_panel_desc *desc)
{
if (!desc)
return;
kfree(desc->modes);
kfree(desc->lp_modes);
kfree(desc);
}
EXPORT_SYMBOL(exynos_panel_desc_destroy);
static int exynos_panel_fill_hdr_info(struct exynos_panel *ctx, struct exynos_panel_desc *desc)
{
struct panel_device *panel;
struct panel_hdr_info *hdr;
if (!ctx) {
pr_err("%s ctx is null\n", __func__);
return -EINVAL;
}
if (!desc) {
pr_err("%s desc is null\n", __func__);
return -EINVAL;
}
panel = ctx->mcd_panel_dev;
if (!panel) {
pr_err("%s panel is null\n", __func__);
return -EINVAL;
}
hdr = &panel->hdr;
desc->hdr_formats = hdr->formats;
desc->max_luminance = hdr->max_luma;
desc->max_avg_luminance = hdr->max_avg_luma;
desc->min_luminance = hdr->min_luma; /* TODO should get from mcd-panel */
return 0;
}
struct exynos_panel_desc *
exynos_panel_desc_create_from_panel_display_modes(struct exynos_panel *ctx,
struct panel_display_modes *pdms)
{
struct exynos_panel_desc *desc;
struct exynos_panel_mode *modes = NULL;
struct exynos_panel_mode *lp_modes = NULL;
struct exynos_panel_mode *unique_modes = NULL;
struct exynos_panel_mode native_mode;
struct panel_device *panel;
int num_modes = 0, temp_num_modes = 0;
int num_lp_modes = 0, temp_num_lp_modes = 0;
int num_unique_modes = 0;
int i, j;
desc = exynos_panel_desc_create(ctx);
if (!desc) {
dev_err(ctx->dev, "%s: could not allocate exynos_panel_desc\n", __func__);
return NULL;
}
panel = ctx->mcd_panel_dev;
if (!panel) {
dev_err(ctx->dev, "%s panel is null\n", __func__);
goto modefail;
}
/* create exynos_panel_mode */
unique_modes = kcalloc(pdms->num_modes, sizeof(*unique_modes), GFP_KERNEL);
if (!unique_modes) {
dev_err(ctx->dev, "%s: could not allocate exynos_panel_mode array\n", __func__);
goto modefail;
}
/* store unique exynos_panel_mode */
for (i = 0; i < pdms->num_modes; i++) {
struct exynos_panel_mode mode;
memset(&mode, 0, sizeof(mode));
exynos_panel_mode_from_panel_display_mode(pdms->modes[i], &mode);
/* push unique exynos_panel_mode */
for (j = 0; j < num_unique_modes; j++)
if (!memcmp(&unique_modes[j], &mode, sizeof(mode)))
break;
if (j != num_unique_modes)
continue;
/* copy unique mode */
memcpy(&unique_modes[num_unique_modes++], &mode, sizeof(mode));
if (mode.exynos_mode.is_lp_mode)
num_lp_modes++;
else
num_modes++;
}
/* create lp_modes array */
lp_modes = kcalloc(num_lp_modes, sizeof(*lp_modes), GFP_KERNEL);
if (!lp_modes) {
dev_err(ctx->dev, "%s: could not allocate exynos_panel_mode array\n", __func__);
goto modefail;
}
/* create modes array */
modes = kcalloc(num_modes, sizeof(*modes), GFP_KERNEL);
if (!modes) {
dev_err(ctx->dev, "%s: could not allocate exynos_panel_mode array\n", __func__);
goto modefail;
}
for (i = 0; i < num_unique_modes; i++) {
if (unique_modes[i].exynos_mode.is_lp_mode)
memcpy(&lp_modes[temp_num_lp_modes++], &unique_modes[i], sizeof(struct exynos_panel_mode));
else
memcpy(&modes[temp_num_modes++], &unique_modes[i], sizeof(struct exynos_panel_mode));
}
/*
* sorting exynos_panel_mode list
*/
sort(modes, num_modes, sizeof(modes[0]),
compare_exynos_panel_mode, NULL);
sort(lp_modes, num_lp_modes, sizeof(lp_modes[0]),
compare_exynos_panel_mode, NULL);
/*
* print sorted exynos_panel_mode list
*/
for (i = 0; i < num_modes; i++)
exynos_panel_mode_info_printmodeline(&modes[i]);
for (i = 0; i < num_lp_modes; i++)
exynos_panel_mode_info_printmodeline(&lp_modes[i]);
/* find default mode in sorted exynos_panel_mode */
memset(&native_mode, 0, sizeof(native_mode));
exynos_panel_mode_from_panel_display_mode(pdms->modes[pdms->native_mode], &native_mode);
for (i = 0; i < num_modes; i++) {
if (!memcmp(&modes[i], &native_mode,
sizeof(native_mode))) {
/* set native-mode as prefered mode */
modes[i].mode.type |= DRM_MODE_TYPE_PREFERRED;
ctx->current_mode = &modes[i];
break;
}
}
/* set exynos_panel_desc */
desc->data_lane_cnt = 4; /* TODO: should get from mcd-panel */
desc->max_brightness = 1023; /* TODO should get from mcd-panel */
desc->dft_brightness = 511; /* TODO should get from mcd-panel */
desc->modes = modes;
desc->num_modes = num_modes;
desc->lp_modes = lp_modes;
desc->num_lp_modes = num_lp_modes;
desc->xml_suffix = panel->panel_data.dqe_suffix;
exynos_panel_fill_hdr_info(ctx, desc);
kfree(unique_modes);
return desc;
modefail:
kfree(modes);
kfree(lp_modes);
kfree(unique_modes);
exynos_panel_desc_destroy(ctx, desc);
return NULL;
}
EXPORT_SYMBOL(exynos_panel_desc_create_from_panel_display_modes);
MODULE_AUTHOR("Gwanghui Lee <gwanghui.lee@samsung.com>");
MODULE_DESCRIPTION("MIPI-DSI based mcd panel helper driver");
MODULE_LICENSE("GPL");