421 lines
12 KiB
C
421 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Phytium display drm driver
|
|
*
|
|
* Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
|
|
*/
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/display/drm_dp.h>
|
|
#include <drm/drm_modes.h>
|
|
#include "phytium_display_drv.h"
|
|
#include "phytium_dp.h"
|
|
#include "phytium_panel.h"
|
|
|
|
static int
|
|
phytium_dp_aux_set_backlight(struct phytium_panel *panel, unsigned int level)
|
|
{
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
unsigned char vals[2] = { 0x0 };
|
|
|
|
vals[0] = level;
|
|
if (phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) {
|
|
vals[0] = (level & 0xFF00) >> 8;
|
|
vals[1] = (level & 0xFF);
|
|
}
|
|
|
|
if (drm_dp_dpcd_write(&phytium_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB,
|
|
vals, sizeof(vals)) < 0) {
|
|
DRM_DEBUG_KMS("Failed to write aux backlight level\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int phytium_dp_aux_get_backlight(struct phytium_panel *panel)
|
|
{
|
|
unsigned char read_val[2] = { 0x0 };
|
|
unsigned char level = 0;
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
if (drm_dp_dpcd_read(&phytium_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB,
|
|
&read_val, sizeof(read_val)) < 0) {
|
|
DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n",
|
|
DP_EDP_BACKLIGHT_BRIGHTNESS_MSB);
|
|
return 0;
|
|
}
|
|
|
|
level = read_val[0];
|
|
if (phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT)
|
|
level = (read_val[0] << 8 | read_val[1]);
|
|
|
|
return level;
|
|
}
|
|
|
|
static void set_aux_backlight_enable(struct phytium_panel *panel, bool enable)
|
|
{
|
|
u8 reg_val = 0;
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
if (!(phytium_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP))
|
|
return;
|
|
|
|
if (drm_dp_dpcd_readb(&phytium_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER,
|
|
®_val) < 0) {
|
|
DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n",
|
|
DP_EDP_DISPLAY_CONTROL_REGISTER);
|
|
return;
|
|
}
|
|
|
|
if (enable)
|
|
reg_val |= DP_EDP_BACKLIGHT_ENABLE;
|
|
else
|
|
reg_val &= ~(DP_EDP_BACKLIGHT_ENABLE);
|
|
|
|
if (drm_dp_dpcd_writeb(&phytium_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER,
|
|
reg_val) != 1) {
|
|
DRM_DEBUG_KMS("Failed to %s aux backlight\n",
|
|
enable ? "enable" : "disable");
|
|
}
|
|
}
|
|
|
|
static void phytium_dp_aux_enable_backlight(struct phytium_panel *panel)
|
|
{
|
|
unsigned char dpcd_buf, new_dpcd_buf, edp_backlight_mode;
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
if (drm_dp_dpcd_readb(&phytium_dp->aux,
|
|
DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf) != 1) {
|
|
DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n",
|
|
DP_EDP_BACKLIGHT_MODE_SET_REGISTER);
|
|
return;
|
|
}
|
|
|
|
new_dpcd_buf = dpcd_buf;
|
|
edp_backlight_mode = dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK;
|
|
|
|
switch (edp_backlight_mode) {
|
|
case DP_EDP_BACKLIGHT_CONTROL_MODE_PWM:
|
|
case DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET:
|
|
case DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT:
|
|
new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK;
|
|
new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD;
|
|
break;
|
|
|
|
/* Do nothing when it is already DPCD mode */
|
|
case DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (new_dpcd_buf != dpcd_buf) {
|
|
if (drm_dp_dpcd_writeb(&phytium_dp->aux,
|
|
DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf) < 0) {
|
|
DRM_DEBUG_KMS("Failed to write aux backlight mode\n");
|
|
}
|
|
}
|
|
|
|
set_aux_backlight_enable(panel, true);
|
|
phytium_dp_aux_set_backlight(panel, panel->level);
|
|
}
|
|
|
|
static void phytium_dp_aux_disable_backlight(struct phytium_panel *panel)
|
|
{
|
|
set_aux_backlight_enable(panel, false);
|
|
}
|
|
|
|
static void phytium_dp_aux_setup_backlight(struct phytium_panel *panel)
|
|
{
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
if (phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT)
|
|
phytium_dp->panel.max = 0xFFFF;
|
|
else
|
|
phytium_dp->panel.max = 0xFF;
|
|
|
|
phytium_dp->panel.min = 0;
|
|
phytium_dp->panel.level = phytium_dp_aux_get_backlight(panel);
|
|
phytium_dp->panel.backlight_enabled = (phytium_dp->panel.level != 0);
|
|
}
|
|
|
|
static void phytium_dp_hw_poweron_panel(struct phytium_panel *panel)
|
|
{
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
phytium_dp->funcs->dp_hw_poweron_panel(phytium_dp);
|
|
}
|
|
|
|
static void phytium_dp_hw_poweroff_panel(struct phytium_panel *panel)
|
|
{
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
phytium_dp->funcs->dp_hw_poweroff_panel(phytium_dp);
|
|
}
|
|
|
|
static int
|
|
phytium_dp_hw_set_backlight(struct phytium_panel *panel, uint32_t level)
|
|
{
|
|
int ret;
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
ret = phytium_dp->funcs->dp_hw_set_backlight(phytium_dp, level);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t phytium_dp_hw_get_backlight(struct phytium_panel *panel)
|
|
{
|
|
uint32_t ret;
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
ret = phytium_dp->funcs->dp_hw_get_backlight(phytium_dp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void phytium_dp_hw_enable_backlight(struct phytium_panel *panel)
|
|
{
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
phytium_dp->funcs->dp_hw_set_backlight(phytium_dp, phytium_dp->panel.level);
|
|
phytium_dp->funcs->dp_hw_enable_backlight(phytium_dp);
|
|
}
|
|
|
|
static void phytium_dp_hw_disable_backlight(struct phytium_panel *panel)
|
|
{
|
|
struct phytium_dp_device *phytium_dp = panel_to_dp_device(panel);
|
|
|
|
phytium_dp->funcs->dp_hw_disable_backlight(phytium_dp);
|
|
}
|
|
|
|
static void phytium_dp_hw_setup_backlight(struct phytium_panel *panel)
|
|
{
|
|
struct drm_device *dev = panel->dev;
|
|
struct phytium_display_private *priv = dev->dev_private;
|
|
|
|
panel->max = priv->info.backlight_max;
|
|
panel->min = 0;
|
|
panel->level = phytium_dp_hw_get_backlight(panel);
|
|
}
|
|
|
|
void phytium_dp_panel_init_backlight_funcs(struct phytium_dp_device *phytium_dp)
|
|
{
|
|
if (phytium_dp->edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP &&
|
|
(phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP) &&
|
|
!(phytium_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) {
|
|
DRM_DEBUG_KMS("AUX Backlight Control Supported!\n");
|
|
phytium_dp->panel.setup_backlight = phytium_dp_aux_setup_backlight;
|
|
phytium_dp->panel.enable_backlight = phytium_dp_aux_enable_backlight;
|
|
phytium_dp->panel.disable_backlight = phytium_dp_aux_disable_backlight;
|
|
phytium_dp->panel.set_backlight = phytium_dp_aux_set_backlight;
|
|
phytium_dp->panel.get_backlight = phytium_dp_aux_get_backlight;
|
|
} else {
|
|
DRM_DEBUG_KMS("SE Backlight Control Supported!\n");
|
|
phytium_dp->panel.setup_backlight = phytium_dp_hw_setup_backlight;
|
|
phytium_dp->panel.enable_backlight = phytium_dp_hw_enable_backlight;
|
|
phytium_dp->panel.disable_backlight = phytium_dp_hw_disable_backlight;
|
|
phytium_dp->panel.set_backlight = phytium_dp_hw_set_backlight;
|
|
phytium_dp->panel.get_backlight = phytium_dp_hw_get_backlight;
|
|
}
|
|
phytium_dp->panel.poweron = phytium_dp_hw_poweron_panel;
|
|
phytium_dp->panel.poweroff = phytium_dp_hw_poweroff_panel;
|
|
mutex_init(&phytium_dp->panel.panel_lock);
|
|
phytium_dp->panel.dev = phytium_dp->dev;
|
|
|
|
/* Upper limits from eDP 1.3 spec */
|
|
phytium_dp->panel.panel_power_up_delay = 210; /* t1_t3 */
|
|
phytium_dp->panel.backlight_on_delay = 50; /* t7 */
|
|
phytium_dp->panel.backlight_off_delay = 50;
|
|
phytium_dp->panel.panel_power_down_delay = 0; /* t10 */
|
|
phytium_dp->panel.panel_power_cycle_delay = 510; /* t11 + t12 */
|
|
}
|
|
|
|
void phytium_dp_panel_release_backlight_funcs(struct phytium_dp_device *phytium_dp)
|
|
{
|
|
phytium_dp->panel.setup_backlight = NULL;
|
|
phytium_dp->panel.enable_backlight = NULL;
|
|
phytium_dp->panel.disable_backlight = NULL;
|
|
phytium_dp->panel.set_backlight = NULL;
|
|
phytium_dp->panel.get_backlight = NULL;
|
|
phytium_dp->panel.poweron = NULL;
|
|
phytium_dp->panel.poweroff = NULL;
|
|
}
|
|
|
|
void phytium_panel_enable_backlight(struct phytium_panel *panel)
|
|
{
|
|
|
|
if (panel->enable_backlight) {
|
|
mutex_lock(&panel->panel_lock);
|
|
msleep(panel->backlight_on_delay);
|
|
panel->enable_backlight(panel);
|
|
panel->backlight_enabled = true;
|
|
mutex_unlock(&panel->panel_lock);
|
|
}
|
|
}
|
|
|
|
void phytium_panel_disable_backlight(struct phytium_panel *panel)
|
|
{
|
|
if (panel->disable_backlight) {
|
|
mutex_lock(&panel->panel_lock);
|
|
panel->disable_backlight(panel);
|
|
panel->backlight_enabled = false;
|
|
msleep(panel->backlight_off_delay);
|
|
mutex_unlock(&panel->panel_lock);
|
|
}
|
|
}
|
|
|
|
void phytium_panel_poweron(struct phytium_panel *panel)
|
|
{
|
|
if (panel->poweron) {
|
|
mutex_lock(&panel->panel_lock);
|
|
panel->poweron(panel);
|
|
panel->power_enabled = true;
|
|
msleep(panel->panel_power_up_delay);
|
|
mutex_unlock(&panel->panel_lock);
|
|
}
|
|
}
|
|
|
|
void phytium_panel_poweroff(struct phytium_panel *panel)
|
|
{
|
|
if (panel->poweroff) {
|
|
mutex_lock(&panel->panel_lock);
|
|
msleep(panel->panel_power_down_delay);
|
|
panel->poweroff(panel);
|
|
panel->power_enabled = false;
|
|
mutex_unlock(&panel->panel_lock);
|
|
}
|
|
}
|
|
|
|
static uint32_t phytium_scale(uint32_t source_val,
|
|
uint32_t source_min, uint32_t source_max,
|
|
uint32_t target_min, uint32_t target_max)
|
|
{
|
|
uint64_t target_val;
|
|
|
|
WARN_ON(source_min > source_max);
|
|
WARN_ON(target_min > target_max);
|
|
|
|
/* defensive */
|
|
source_val = clamp(source_val, source_min, source_max);
|
|
|
|
/* avoid overflows */
|
|
target_val = mul_u32_u32(source_val - source_min, target_max - target_min);
|
|
target_val = DIV_ROUND_CLOSEST_ULL(target_val, source_max - source_min);
|
|
target_val += target_min;
|
|
|
|
return target_val;
|
|
}
|
|
|
|
static inline uint32_t
|
|
phytium_scale_hw_to_user(struct phytium_panel *panel, uint32_t hw_level, uint32_t user_max)
|
|
{
|
|
return phytium_scale(hw_level, panel->min, panel->max,
|
|
0, user_max);
|
|
}
|
|
|
|
static inline uint32_t
|
|
phytium_scale_user_to_hw(struct phytium_panel *panel, u32 user_level, u32 user_max)
|
|
{
|
|
return phytium_scale(user_level, 0, user_max,
|
|
panel->min, panel->max);
|
|
}
|
|
|
|
static int phytium_backlight_device_update_status(struct backlight_device *bd)
|
|
{
|
|
struct phytium_panel *panel = bl_get_data(bd);
|
|
struct drm_device *dev = panel->dev;
|
|
uint32_t hw_level = 0;
|
|
int ret = 0;
|
|
|
|
DRM_DEBUG_KMS("updating phytium_backlight, brightness=%d/%d\n",
|
|
bd->props.brightness, bd->props.max_brightness);
|
|
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
|
|
hw_level = phytium_scale_user_to_hw(panel, bd->props.brightness, bd->props.max_brightness);
|
|
|
|
if ((panel->set_backlight) && (panel->backlight_enabled)) {
|
|
mutex_lock(&panel->panel_lock);
|
|
ret = panel->set_backlight(panel, hw_level);
|
|
panel->level = hw_level;
|
|
mutex_unlock(&panel->panel_lock);
|
|
}
|
|
drm_modeset_unlock(&dev->mode_config.connection_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int phytium_backlight_device_get_brightness(struct backlight_device *bd)
|
|
{
|
|
struct phytium_panel *panel = bl_get_data(bd);
|
|
struct drm_device *dev = panel->dev;
|
|
uint32_t hw_level = 0;
|
|
int ret;
|
|
|
|
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
|
|
if (panel->get_backlight && panel->backlight_enabled) {
|
|
mutex_lock(&panel->panel_lock);
|
|
hw_level = panel->get_backlight(panel);
|
|
panel->level = hw_level;
|
|
mutex_unlock(&panel->panel_lock);
|
|
}
|
|
drm_modeset_unlock(&dev->mode_config.connection_mutex);
|
|
ret = phytium_scale_hw_to_user(panel, hw_level, bd->props.max_brightness);
|
|
DRM_DEBUG_KMS("get phytium_backlight, brightness=%d/%d\n",
|
|
ret, bd->props.max_brightness);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct backlight_ops phytium_backlight_device_ops = {
|
|
.update_status = phytium_backlight_device_update_status,
|
|
.get_brightness = phytium_backlight_device_get_brightness,
|
|
};
|
|
|
|
int phytium_edp_backlight_device_register(struct phytium_dp_device *phytium_dp)
|
|
{
|
|
struct backlight_properties props;
|
|
char bl_name[16];
|
|
|
|
if (phytium_dp->panel.setup_backlight) {
|
|
mutex_lock(&phytium_dp->panel.panel_lock);
|
|
phytium_dp->panel.setup_backlight(&phytium_dp->panel);
|
|
mutex_unlock(&phytium_dp->panel.panel_lock);
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&props, 0, sizeof(props));
|
|
props.max_brightness = PHYTIUM_MAX_BL_LEVEL;
|
|
props.type = BACKLIGHT_RAW;
|
|
props.brightness = phytium_scale_hw_to_user(&phytium_dp->panel, phytium_dp->panel.level,
|
|
props.max_brightness);
|
|
snprintf(bl_name, sizeof(bl_name), "phytium_bl%d", phytium_dp->port);
|
|
|
|
phytium_dp->panel.bl_device =
|
|
backlight_device_register(bl_name,
|
|
phytium_dp->connector.kdev,
|
|
&phytium_dp->panel,
|
|
&phytium_backlight_device_ops,
|
|
&props);
|
|
|
|
if (IS_ERR(phytium_dp->panel.bl_device)) {
|
|
DRM_ERROR("Failed to register backlight: %ld\n",
|
|
PTR_ERR(phytium_dp->panel.bl_device));
|
|
phytium_dp->panel.bl_device = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
DRM_DEBUG_KMS("Connector %s backlight sysfs interface registered\n",
|
|
phytium_dp->connector.name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void phytium_edp_backlight_device_unregister(struct phytium_dp_device *phytium_dp)
|
|
{
|
|
if (phytium_dp->panel.bl_device) {
|
|
backlight_device_unregister(phytium_dp->panel.bl_device);
|
|
phytium_dp->panel.bl_device = NULL;
|
|
}
|
|
}
|