blob: cb4d8b287d27424a6f2aea9c377e726b650e07b5 [file] [log] [blame]
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <utils/debug.h>
#include "hw_primary.h"
#define __CLASS__ "HWPrimary"
namespace sdm {
DisplayError HWPrimaryInterface::Create(HWPrimaryInterface **intf, HWInfoInterface *hw_info_intf,
BufferSyncHandler *buffer_sync_handler) {
DisplayError error = kErrorNone;
HWPrimary *hw_primary = NULL;
hw_primary = new HWPrimary(buffer_sync_handler, hw_info_intf);
error = hw_primary->Init();
if (error != kErrorNone) {
delete hw_primary;
} else {
*intf = hw_primary;
}
return error;
}
DisplayError HWPrimaryInterface::Destroy(HWPrimaryInterface *intf) {
HWPrimary *hw_primary = static_cast<HWPrimary *>(intf);
hw_primary->Deinit();
delete hw_primary;
return kErrorNone;
}
HWPrimary::HWPrimary(BufferSyncHandler *buffer_sync_handler, HWInfoInterface *hw_info_intf)
: HWDevice(buffer_sync_handler), event_thread_name_("SDM_EventThread"), fake_vsync_(false),
exit_threads_(false), config_changed_(true) {
HWDevice::device_type_ = kDevicePrimary;
HWDevice::device_name_ = "Primary Display Device";
HWDevice::hw_info_intf_ = hw_info_intf;
}
DisplayError HWPrimary::Init() {
DisplayError error = kErrorNone;
char node_path[kMaxStringLength] = {0};
char data[kMaxStringLength] = {0};
const char* event_name[kNumDisplayEvents] = {"vsync_event", "show_blank_event", "idle_notify",
"msm_fb_thermal_level"};
error = HWDevice::Init();
if (error != kErrorNone) {
goto CleanupOnError;
}
// Open nodes for polling
for (int event = 0; event < kNumDisplayEvents; event++) {
poll_fds_[event].fd = -1;
}
if (!fake_vsync_) {
for (int event = 0; event < kNumDisplayEvents; event++) {
pollfd &poll_fd = poll_fds_[event];
if ((hw_panel_info_.mode == kModeCommand) &&
(!strncmp(event_name[event], "idle_notify", strlen("idle_notify")))) {
continue;
}
snprintf(node_path, sizeof(node_path), "%s%d/%s", fb_path_, fb_node_index_,
event_name[event]);
poll_fd.fd = open_(node_path, O_RDONLY);
if (poll_fd.fd < 0) {
DLOGE("open failed for event=%d, error=%s", event, strerror(errno));
error = kErrorHardware;
goto CleanupOnError;
}
// Read once on all fds to clear data on all fds.
pread_(poll_fd.fd, data , kMaxStringLength, 0);
poll_fd.events = POLLPRI | POLLERR;
}
}
// Start the Event thread
if (pthread_create(&event_thread_, NULL, &DisplayEventThread, this) < 0) {
DLOGE("Failed to start %s, error = %s", event_thread_name_);
error = kErrorResources;
goto CleanupOnError;
}
// Disable HPD at start if HDMI is external, it will be enabled later when the display powers on
// This helps for framework reboot or adb shell stop/start
EnableHotPlugDetection(0);
return kErrorNone;
CleanupOnError:
// Close all poll fds
for (int event = 0; event < kNumDisplayEvents; event++) {
int &fd = poll_fds_[event].fd;
if (fd >= 0) {
close_(fd);
}
}
return error;
}
DisplayError HWPrimary::Deinit() {
exit_threads_ = true;
pthread_join(event_thread_, NULL);
for (int event = 0; event < kNumDisplayEvents; event++) {
close_(poll_fds_[event].fd);
}
return kErrorNone;
}
DisplayError HWPrimary::Open(HWEventHandler *eventhandler) {
return HWDevice::Open(eventhandler);
}
DisplayError HWPrimary::Close() {
return HWDevice::Close();
}
DisplayError HWPrimary::GetNumDisplayAttributes(uint32_t *count) {
return HWDevice::GetNumDisplayAttributes(count);
}
DisplayError HWPrimary::GetDisplayAttributes(HWDisplayAttributes *display_attributes,
uint32_t index) {
if (!display_attributes) {
return kErrorParameters;
}
if (config_changed_) {
PopulateDisplayAttributes();
config_changed_ = false;
}
*display_attributes = display_attributes_;
return kErrorNone;
}
DisplayError HWPrimary::PopulateDisplayAttributes() {
DTRACE_SCOPED();
// Variable screen info
STRUCT_VAR(fb_var_screeninfo, var_screeninfo);
if (ioctl_(device_fd_, FBIOGET_VSCREENINFO, &var_screeninfo) < 0) {
IOCTL_LOGE(FBIOGET_VSCREENINFO, device_type_);
return kErrorHardware;
}
// Frame rate
STRUCT_VAR(msmfb_metadata, meta_data);
meta_data.op = metadata_op_frame_rate;
if (ioctl_(device_fd_, MSMFB_METADATA_GET, &meta_data) < 0) {
IOCTL_LOGE(MSMFB_METADATA_GET, device_type_);
return kErrorHardware;
}
// If driver doesn't return width/height information, default to 160 dpi
if (INT(var_screeninfo.width) <= 0 || INT(var_screeninfo.height) <= 0) {
var_screeninfo.width = INT(((FLOAT(var_screeninfo.xres) * 25.4f)/160.0f) + 0.5f);
var_screeninfo.height = INT(((FLOAT(var_screeninfo.yres) * 25.4f)/160.0f) + 0.5f);
}
display_attributes_.x_pixels = var_screeninfo.xres;
display_attributes_.y_pixels = var_screeninfo.yres;
display_attributes_.v_total = var_screeninfo.yres + var_screeninfo.lower_margin +
var_screeninfo.upper_margin + var_screeninfo.vsync_len;
uint32_t h_blanking = var_screeninfo.right_margin + var_screeninfo.left_margin +
var_screeninfo.hsync_len;
display_attributes_.h_total = var_screeninfo.xres + var_screeninfo.right_margin +
var_screeninfo.left_margin + var_screeninfo.hsync_len;
display_attributes_.x_dpi =
(FLOAT(var_screeninfo.xres) * 25.4f) / FLOAT(var_screeninfo.width);
display_attributes_.y_dpi =
(FLOAT(var_screeninfo.yres) * 25.4f) / FLOAT(var_screeninfo.height);
display_attributes_.fps = meta_data.data.panel_frame_rate;
display_attributes_.vsync_period_ns = UINT32(1000000000L / display_attributes_.fps);
display_attributes_.is_device_split = (hw_panel_info_.split_info.left_split ||
(var_screeninfo.xres > hw_resource_.max_mixer_width)) ? true : false;
display_attributes_.split_left = hw_panel_info_.split_info.left_split ?
hw_panel_info_.split_info.left_split : display_attributes_.x_pixels / 2;
display_attributes_.always_src_split = hw_panel_info_.split_info.always_src_split;
display_attributes_.h_total += display_attributes_.is_device_split ? h_blanking : 0;
return kErrorNone;
}
DisplayError HWPrimary::SetDisplayAttributes(uint32_t index) {
return HWDevice::SetDisplayAttributes(index);
}
DisplayError HWPrimary::SetRefreshRate(uint32_t refresh_rate) {
char node_path[kMaxStringLength] = {0};
DLOGI("Setting refresh rate to = %d fps", refresh_rate);
snprintf(node_path, sizeof(node_path), "%s%d/dynamic_fps", fb_path_, fb_node_index_);
int fd = open_(node_path, O_WRONLY);
if (fd < 0) {
DLOGE("Failed to open %s with error %s", node_path, strerror(errno));
return kErrorFileDescriptor;
}
char refresh_rate_string[kMaxStringLength];
snprintf(refresh_rate_string, sizeof(refresh_rate_string), "%d", refresh_rate);
ssize_t len = pwrite_(fd, refresh_rate_string, strlen(refresh_rate_string), 0);
if (len < 0) {
DLOGE("Failed to write %d with error %s", refresh_rate, strerror(errno));
close_(fd);
return kErrorUndefined;
}
close_(fd);
config_changed_ = true;
synchronous_commit_ = true;
return kErrorNone;
}
DisplayError HWPrimary::GetConfigIndex(uint32_t mode, uint32_t *index) {
return HWDevice::GetConfigIndex(mode, index);
}
DisplayError HWPrimary::PowerOn() {
return HWDevice::PowerOn();
}
DisplayError HWPrimary::PowerOff() {
if (ioctl_(device_fd_, FBIOBLANK, FB_BLANK_POWERDOWN) < 0) {
IOCTL_LOGE(FB_BLANK_POWERDOWN, device_type_);
return kErrorHardware;
}
return kErrorNone;
}
DisplayError HWPrimary::Doze() {
if (ioctl_(device_fd_, FBIOBLANK, FB_BLANK_NORMAL) < 0) {
IOCTL_LOGE(FB_BLANK_NORMAL, device_type_);
return kErrorHardware;
}
return kErrorNone;
}
DisplayError HWPrimary::DozeSuspend() {
if (ioctl_(device_fd_, FBIOBLANK, FB_BLANK_VSYNC_SUSPEND) < 0) {
IOCTL_LOGE(FB_BLANK_VSYNC_SUSPEND, device_type_);
return kErrorHardware;
}
return kErrorNone;
}
DisplayError HWPrimary::Standby() {
return HWDevice::Standby();
}
DisplayError HWPrimary::Validate(HWLayers *hw_layers) {
HWDevice::ResetDisplayParams();
mdp_layer_commit_v1 &mdp_commit = mdp_disp_commit_.commit_v1;
LayerRect left_roi = hw_layers->info.left_partial_update;
LayerRect right_roi = hw_layers->info.right_partial_update;
mdp_commit.left_roi.x = INT(left_roi.left);
mdp_commit.left_roi.y = INT(left_roi.top);
mdp_commit.left_roi.w = INT(left_roi.right - left_roi.left);
mdp_commit.left_roi.h = INT(left_roi.bottom - left_roi.top);
// SDM treats ROI as one full coordinate system.
// In case source split is disabled, However, Driver assumes Mixer to operate in
// different co-ordinate system.
if (!hw_resource_.is_src_split) {
mdp_commit.right_roi.x = INT(right_roi.left) - hw_panel_info_.split_info.left_split;
mdp_commit.right_roi.y = INT(right_roi.top);
mdp_commit.right_roi.w = INT(right_roi.right - right_roi.left);
mdp_commit.right_roi.h = INT(right_roi.bottom - right_roi.top);
}
return HWDevice::Validate(hw_layers);
}
DisplayError HWPrimary::Commit(HWLayers *hw_layers) {
return HWDevice::Commit(hw_layers);
}
DisplayError HWPrimary::Flush() {
return HWDevice::Flush();
}
DisplayError HWPrimary::GetHWPanelInfo(HWPanelInfo *panel_info) {
return HWDevice::GetHWPanelInfo(panel_info);
}
void* HWPrimary::DisplayEventThread(void *context) {
if (context) {
return reinterpret_cast<HWPrimary *>(context)->DisplayEventThreadHandler();
}
return NULL;
}
void* HWPrimary::DisplayEventThreadHandler() {
char data[kMaxStringLength] = {0};
prctl(PR_SET_NAME, event_thread_name_, 0, 0, 0);
setpriority(PRIO_PROCESS, 0, kThreadPriorityUrgent);
if (fake_vsync_) {
while (!exit_threads_) {
// Fake vsync is used only when set explicitly through a property(todo) or when
// the vsync timestamp node cannot be opened at bootup. There is no
// fallback to fake vsync from the true vsync loop, ever, as the
// condition can easily escape detection.
// Also, fake vsync is delivered only for the primary display.
usleep(16666);
STRUCT_VAR(timeval, time_now);
gettimeofday(&time_now, NULL);
uint64_t ts = uint64_t(time_now.tv_sec)*1000000000LL +uint64_t(time_now.tv_usec)*1000LL;
// Send Vsync event for primary display(0)
event_handler_->VSync(ts);
}
pthread_exit(0);
}
typedef void (HWPrimary::*EventHandler)(char*);
EventHandler event_handler[kNumDisplayEvents] = { &HWPrimary::HandleVSync,
&HWPrimary::HandleBlank,
&HWPrimary::HandleIdleTimeout,
&HWPrimary::HandleThermal };
while (!exit_threads_) {
int error = poll_(poll_fds_, kNumDisplayEvents, -1);
if (error < 0) {
DLOGW("poll failed. error = %s", strerror(errno));
continue;
}
for (int event = 0; event < kNumDisplayEvents; event++) {
pollfd &poll_fd = poll_fds_[event];
if (poll_fd.revents & POLLPRI) {
ssize_t length = pread_(poll_fd.fd, data, kMaxStringLength, 0);
if (length < 0) {
// If the read was interrupted - it is not a fatal error, just continue.
DLOGW("pread failed. event = %d, error = %s", event, strerror(errno));
continue;
}
(this->*event_handler[event])(data);
}
}
}
pthread_exit(0);
return NULL;
}
void HWPrimary::HandleVSync(char *data) {
int64_t timestamp = 0;
if (!strncmp(data, "VSYNC=", strlen("VSYNC="))) {
timestamp = strtoull(data + strlen("VSYNC="), NULL, 0);
}
event_handler_->VSync(timestamp);
}
void HWPrimary::HandleBlank(char *data) {
// TODO(user): Need to send blank Event
}
void HWPrimary::HandleIdleTimeout(char *data) {
event_handler_->IdleTimeout();
}
void HWPrimary::HandleThermal(char *data) {
int64_t thermal_level = 0;
if (!strncmp(data, "thermal_level=", strlen("thermal_level="))) {
thermal_level = strtoull(data + strlen("thermal_level="), NULL, 0);
}
DLOGI("Received thermal notification with thermal level = %d", thermal_level);
event_handler_->ThermalEvent(thermal_level);
}
void HWPrimary::SetIdleTimeoutMs(uint32_t timeout_ms) {
char node_path[kMaxStringLength] = {0};
DLOGI("Setting idle timeout to = %d ms", timeout_ms);
snprintf(node_path, sizeof(node_path), "%s%d/idle_time", fb_path_, fb_node_index_);
// Open a sysfs node to send the timeout value to driver.
int fd = open_(node_path, O_WRONLY);
if (fd < 0) {
DLOGE("Unable to open %s, node %s", node_path, strerror(errno));
return;
}
char timeout_string[64];
snprintf(timeout_string, sizeof(timeout_string), "%d", timeout_ms);
// Notify driver about the timeout value
ssize_t length = pwrite_(fd, timeout_string, strlen(timeout_string), 0);
if (length < -1) {
DLOGE("Unable to write into %s, node %s", node_path, strerror(errno));
}
close_(fd);
}
DisplayError HWPrimary::SetVSyncState(bool enable) {
DTRACE_SCOPED();
int vsync_on = enable ? 1 : 0;
if (ioctl_(device_fd_, MSMFB_OVERLAY_VSYNC_CTRL, &vsync_on) < 0) {
IOCTL_LOGE(MSMFB_OVERLAY_VSYNC_CTRL, device_type_);
return kErrorHardware;
}
return kErrorNone;
}
DisplayError HWPrimary::SetDisplayMode(const HWDisplayMode hw_display_mode) {
DisplayError error = kErrorNone;
uint32_t mode = -1;
switch (hw_display_mode) {
case kModeVideo:
mode = kModeLPMVideo;
break;
case kModeCommand:
mode = kModeLPMCommand;
break;
default:
DLOGW("Failed to translate SDE display mode %d to a MSMFB_LPM_ENABLE mode",
hw_display_mode);
return kErrorParameters;
}
if (ioctl_(device_fd_, MSMFB_LPM_ENABLE, &mode) < 0) {
IOCTL_LOGE(MSMFB_LPM_ENABLE, device_type_);
return kErrorHardware;
}
DLOGI("Triggering display mode change to %d on next commit.", hw_display_mode);
synchronous_commit_ = true;
return kErrorNone;
}
} // namespace sdm