| /* |
| * 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 |
| |