intel_extreme: improve brightness setting support

- Newer devices use a different layout for the backlight PWM registers
- Get the min brightness level from the BDB

Change-Id: I99745a022dd38733a4c2386f91c4c57016dd2acd
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5162
Reviewed-by: Jérôme Duval <jerome.duval@gmail.com>
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
This commit is contained in:
PulkoMandy 2022-03-28 20:10:59 +02:00 committed by Jérôme Duval
parent 83cb10bcbc
commit 890aa41134
6 changed files with 148 additions and 32 deletions

View File

@ -293,6 +293,7 @@ struct intel_shared_info {
uint32 bytes_per_row; uint32 bytes_per_row;
uint32 bits_per_pixel; uint32 bits_per_pixel;
uint32 dpms_mode; uint32 dpms_mode;
uint16 min_brightness;
area_id registers_area; // area of memory mapped registers area_id registers_area; // area of memory mapped registers
uint32 register_blocks[REGISTER_BLOCK_COUNT]; uint32 register_blocks[REGISTER_BLOCK_COUNT];
@ -1178,11 +1179,21 @@ struct intel_free_graphics_memory {
#define PANEL_DIVISOR_POW_CYCLE_DLY_SHIFT 0x1f #define PANEL_DIVISOR_POW_CYCLE_DLY_SHIFT 0x1f
// Backlight control registers // Backlight control registers
#define PCH_BLC_PWM_CTL2 (0x8250 | REGS_NORTH_SHARED) // These have moved around, initially they were per pipe, then they were moved in the "north" part
#define PCH_BLC_PWM_CTL (0x8254 | REGS_NORTH_SHARED) // of the PCH with a single backlight control (independant of pipes), and then moved again to the
#define PCH_SBLC_PWM_CTL2 (0x8254 | REGS_SOUTH_SHARED) // "south" part of the PCH, with a simplified register layout.
#define PCH_BLC_PWM_CTL2 (0x8250 | REGS_NORTH_SHARED) // Linux BLC_PWM_CPU_CTL2
#define PCH_BLC_PWM_CTL (0x8254 | REGS_NORTH_SHARED) // Linux BLC_PWM_CPU_CTL
// Devices after Cannonlake have a new register layout, with separate registers for the period
// and duty cycle instead of having two 16bit values in a 32bit register
#define PCH_SOUTH_BLC_PWM_CONTROL (0x8250 | REGS_SOUTH_SHARED) // Linux _BXT_BLC_PWM_CTL1
#define PCH_SOUTH_BLC_PWM_PERIOD (0x8254 | REGS_SOUTH_SHARED) // Linux _BXT_BLC_PWM_FREQ1
#define PCH_SOUTH_BLC_PWM_DUTY_CYCLE (0x8258 | REGS_SOUTH_SHARED) // Linux _BXT_BLC_PWM_DUTY1
#define MCH_BLC_PWM_CTL (0x1254 | REGS_NORTH_PIPE_AND_PORT) #define MCH_BLC_PWM_CTL (0x1254 | REGS_NORTH_PIPE_AND_PORT)
// Linux VLV_BLC_PWM_CTL (one register per pipe) or BLC_PWM_CTL (a single register that can be
// programmed for use on either pipe)
// ring buffer commands // ring buffer commands

View File

@ -12,10 +12,10 @@
#include <OS.h> #include <OS.h>
typedef struct lock { struct lock {
sem_id sem; sem_id sem;
int32 count; int32 count;
} lock; };
static inline status_t static inline status_t

View File

@ -10,6 +10,7 @@
*/ */
#include <algorithm>
#include <math.h> #include <math.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -572,14 +573,28 @@ intel_get_edid_info(void* info, size_t size, uint32* _version)
} }
// Get the backlight registers. We need the backlight frequency (we never write it, but we ned to
// know it's value as the duty cycle/brihtness level is proportional to it), and the duty cycle
// register (read to get the current backlight value, written to set it). On older generations,
// the two values are in the same register (16 bits each), on newer ones there are two separate
// registers.
static int32_t static int32_t
intel_get_backlight_register(bool read) intel_get_backlight_register(bool period)
{ {
if (gInfo->shared_info->pch_info >= INTEL_PCH_CNP) {
if (period)
return PCH_SOUTH_BLC_PWM_PERIOD;
else
return PCH_SOUTH_BLC_PWM_DUTY_CYCLE;
}
if (gInfo->shared_info->pch_info == INTEL_PCH_NONE) if (gInfo->shared_info->pch_info == INTEL_PCH_NONE)
return MCH_BLC_PWM_CTL; return MCH_BLC_PWM_CTL;
if (read) // FIXME this mixup of south and north registers seems very strange; it should either be
return PCH_SBLC_PWM_CTL2; // a single register with both period and duty in it, or two separate registers.
if (period)
return PCH_SOUTH_BLC_PWM_PERIOD;
else else
return PCH_BLC_PWM_CTL; return PCH_BLC_PWM_CTL;
} }
@ -593,21 +608,32 @@ intel_set_brightness(float brightness)
if (brightness < 0 || brightness > 1) if (brightness < 0 || brightness > 1)
return B_BAD_VALUE; return B_BAD_VALUE;
uint32_t period = read32(intel_get_backlight_register(true)) >> 16;
// The "duty cycle" is a proportion of the period (0 = backlight off, // The "duty cycle" is a proportion of the period (0 = backlight off,
// period = maximum brightness). The low bit must be masked out because // period = maximum brightness).
// it is apparently used for something else on some Atom machines (no
// reference to that in the documentation that I know of).
// Additionally we don't want it to be completely 0 here, because then // Additionally we don't want it to be completely 0 here, because then
// it becomes hard to turn the display on again (at least until we get // it becomes hard to turn the display on again (at least until we get
// working ACPI keyboard shortcuts for this). So always keep the backlight // working ACPI keyboard shortcuts for this). So always keep the backlight
// at least a little bit on for now. // at least a little bit on for now.
uint32_t duty = (uint32_t)(period * brightness) & 0xfffe;
if (duty == 0 && period != 0)
duty = 2;
write32(intel_get_backlight_register(false), duty | (period << 16)); if (gInfo->shared_info->device_type.Generation() >= 11) {
uint32_t period = read32(intel_get_backlight_register(true));
uint32_t duty = (uint32_t)(period * brightness);
duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
write32(intel_get_backlight_register(false), duty);
} else {
// On older devices there is a single register with both period and duty cycle
uint32_t period = read32(intel_get_backlight_register(true)) >> 16;
// The low bit must be masked out because
// it is apparently used for something else on some Atom machines (no
// reference to that in the documentation that I know of).
uint32_t duty = (uint32_t)(period * brightness) & 0xfffe;
duty = std::max(duty, (uint32_t)gInfo->shared_info->min_brightness);
write32(intel_get_backlight_register(false), duty | (period << 16));
}
return B_OK; return B_OK;
} }
@ -621,8 +647,16 @@ intel_get_brightness(float* brightness)
if (brightness == NULL) if (brightness == NULL)
return B_BAD_VALUE; return B_BAD_VALUE;
uint16_t period = read32(intel_get_backlight_register(true)) >> 16; uint32_t duty;
uint16_t duty = read32(intel_get_backlight_register(false)) & 0xffff; uint32_t period;
if (gInfo->shared_info->device_type.Generation() >= 11) {
period = read32(intel_get_backlight_register(true));
duty = read32(intel_get_backlight_register(false));
} else {
period = read32(intel_get_backlight_register(true)) >> 16;
duty = read32(intel_get_backlight_register(false)) & 0xffff;
}
*brightness = (float)duty / period; *brightness = (float)duty / period;
return B_OK; return B_OK;

View File

@ -46,6 +46,7 @@ struct bdb_header {
enum bdb_block_id { enum bdb_block_id {
BDB_LVDS_OPTIONS = 40, BDB_LVDS_OPTIONS = 40,
BDB_LVDS_LFP_DATA_PTRS = 41, BDB_LVDS_LFP_DATA_PTRS = 41,
BDB_LVDS_BACKLIGHT = 43,
BDB_GENERIC_DTD = 58 BDB_GENERIC_DTD = 58
}; };
@ -62,6 +63,9 @@ enum bdb_block_id {
#define _V_SYNC_OFF(x) ((x[10] >> 4) + ((x[11] & 0x0C) << 2)) #define _V_SYNC_OFF(x) ((x[10] >> 4) + ((x[11] & 0x0C) << 2))
#define _V_SYNC_WIDTH(x) ((x[10] & 0x0F) + ((x[11] & 0x03) << 4)) #define _V_SYNC_WIDTH(x) ((x[10] & 0x0F) + ((x[11] & 0x03) << 4))
#define BDB_BACKLIGHT_TYPE_NONE 0
#define BDB_BACKLIGHT_TYPE_PWM 2
struct lvds_bdb1 { struct lvds_bdb1 {
uint8 id; uint8 id;
@ -137,6 +141,40 @@ struct bdb_generic_dtd {
} __attribute__((packed)); } __attribute__((packed));
struct bdb_lfp_backlight_data_entry {
uint8 type: 2;
uint8 active_low_pwm: 1;
uint8 reserved1: 5;
uint16 pwm_freq_hz;
uint8 min_brightness; // Versions < 234
uint8 reserved2;
uint8 reserved3;
} __attribute__((packed));
struct bdb_lfp_backlight_control_method {
uint8 type: 4;
uint8 controller: 4;
} __attribute__((packed));
struct lfp_brightness_level {
uint16 level;
uint16 reserved;
} __attribute__((packed));
struct bdb_lfp_backlight_data {
uint8 entry_size;
struct bdb_lfp_backlight_data_entry data[16];
uint8 level [16]; // Only for versions < 234
struct bdb_lfp_backlight_control_method backlight_control[16];
struct lfp_brightness_level brightness_level[16]; // Versions >= 234
struct lfp_brightness_level brightness_min_level[16]; // Versions >= 234
uint8 brightness_precision_bits[16]; // Versions >= 236
} __attribute__((packed));
static struct vbios { static struct vbios {
area_id area; area_id area;
uint8* memory; uint8* memory;
@ -384,7 +422,7 @@ sanitize_panel_timing(display_timing& timing)
bool bool
get_lvds_mode_from_bios(display_timing* panelTiming) get_lvds_mode_from_bios(display_timing* panelTiming, uint16* minBrightness)
{ {
int vbtOffset = 0; int vbtOffset = 0;
if (!get_bios(&vbtOffset)) if (!get_bios(&vbtOffset))
@ -431,6 +469,10 @@ get_lvds_mode_from_bios(display_timing* panelTiming)
if (panelType == -1) if (panelType == -1)
break; break;
// on newer versions, check also generic DTD, use LFP panel DTD as a fallback
if (bdb->version >= 229 && panelTimingFound)
break;
struct lvds_bdb2 *lvds2; struct lvds_bdb2 *lvds2;
struct lvds_bdb2_lfp_info *lvds2_lfp_info; struct lvds_bdb2_lfp_info *lvds2_lfp_info;
@ -460,13 +502,9 @@ get_lvds_mode_from_bios(display_timing* panelTiming)
sanitize_panel_timing(*panelTiming); sanitize_panel_timing(*panelTiming);
// on newer versions, check also generic DTD, use LFP panel DTD as a fallback panelTimingFound = true;
if (bdb->version >= 229) { break;
panelTimingFound = true;
break;
}
delete_area(vbios.area);
return true;
} }
case BDB_GENERIC_DTD: case BDB_GENERIC_DTD:
{ {
@ -511,9 +549,41 @@ get_lvds_mode_from_bios(display_timing* panelTiming)
panelTiming->flags |= B_POSITIVE_VSYNC; panelTiming->flags |= B_POSITIVE_VSYNC;
sanitize_panel_timing(*panelTiming); sanitize_panel_timing(*panelTiming);
delete_area(vbios.area); panelTimingFound = true;
return true; break;
}
case BDB_LVDS_BACKLIGHT:
{
TRACE((DEVICE_NAME ": found bdb lvds backlight info\n"));
// First make sure we found block BDB_LVDS_OPTIONS and the panel type
if (panelType == -1)
break;
bdb_lfp_backlight_data* backlightData
= (bdb_lfp_backlight_data*)(vbios.memory + start);
const struct bdb_lfp_backlight_data_entry* entry = &backlightData->data[panelType];
if (entry->type == BDB_BACKLIGHT_TYPE_PWM) {
uint16 minLevel;
if (bdb->version < 234) {
minLevel = entry->min_brightness;
} else {
minLevel = backlightData->brightness_min_level[panelType].level;
if (bdb->version >= 236
&& backlightData->brightness_precision_bits[panelType] == 16) {
TRACE((DEVICE_NAME ": divide level by 255\n"));
minLevel /= 255;
}
}
*minBrightness = minLevel;
TRACE((DEVICE_NAME ": display %d min brightness level is %u\n", panelType,
minLevel));
} else {
TRACE((DEVICE_NAME ": display %d does not have PWM\n", panelType));
}
break;
} }
} }
} }

View File

@ -675,10 +675,11 @@ intel_extreme_init(intel_info &info)
info.shared_info->graphics_memory_size = apertureInfo.size; info.shared_info->graphics_memory_size = apertureInfo.size;
info.shared_info->frame_buffer = 0; info.shared_info->frame_buffer = 0;
info.shared_info->dpms_mode = B_DPMS_ON; info.shared_info->dpms_mode = B_DPMS_ON;
info.shared_info->min_brightness = 2;
// Pull VBIOS panel mode for later use // Pull VBIOS panel mode for later use
info.shared_info->got_vbt = get_lvds_mode_from_bios( info.shared_info->got_vbt = get_lvds_mode_from_bios(
&info.shared_info->panel_timing); &info.shared_info->panel_timing, &info.shared_info->min_brightness);
/* at least 855gm can't drive more than one head at time */ /* at least 855gm can't drive more than one head at time */
if (info.device_type.InFamily(INTEL_FAMILY_8xx)) if (info.device_type.InFamily(INTEL_FAMILY_8xx))

View File

@ -72,7 +72,7 @@ find_reg(const intel_info& info, uint32 target)
} }
extern bool get_lvds_mode_from_bios(display_timing *timing); extern bool get_lvds_mode_from_bios(display_timing *timing, uint16 *minBrightness);
extern status_t intel_free_memory(intel_info& info, addr_t offset); extern status_t intel_free_memory(intel_info& info, addr_t offset);
extern status_t intel_allocate_memory(intel_info& info, size_t size, extern status_t intel_allocate_memory(intel_info& info, size_t size,
size_t alignment, uint32 flags, addr_t* _offset, size_t alignment, uint32 flags, addr_t* _offset,