diff options
Diffstat (limited to 'drivers/gpu/drm/exynos')
45 files changed, 8278 insertions, 3297 deletions
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 4752f223e5b..178d2a9672a 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -2,6 +2,7 @@ config DRM_EXYNOS tristate "DRM Support for Samsung SoC EXYNOS Series" depends on OF && DRM && (PLAT_SAMSUNG || ARCH_MULTIPLATFORM) select DRM_KMS_HELPER + select DRM_KMS_FB_HELPER select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT @@ -25,11 +26,35 @@ config DRM_EXYNOS_DMABUF config DRM_EXYNOS_FIMD bool "Exynos DRM FIMD" - depends on DRM_EXYNOS && !FB_S3C && !ARCH_MULTIPLATFORM + depends on DRM_EXYNOS && !FB_S3C select FB_MODE_HELPERS help Choose this option if you want to use Exynos FIMD for DRM. +config DRM_EXYNOS_DPI + bool "EXYNOS DRM parallel output support" + depends on DRM_EXYNOS_FIMD + select DRM_PANEL + default n + help + This enables support for Exynos parallel output. + +config DRM_EXYNOS_DSI + bool "EXYNOS DRM MIPI-DSI driver support" + depends on DRM_EXYNOS_FIMD + select DRM_MIPI_DSI + select DRM_PANEL + default n + help + This enables support for Exynos MIPI-DSI device. + +config DRM_EXYNOS_DP + bool "EXYNOS DRM DP driver support" + depends on DRM_EXYNOS_FIMD && ARCH_EXYNOS && (DRM_PTN3460=n || DRM_PTN3460=y || DRM_PTN3460=DRM_EXYNOS) + default DRM_EXYNOS + help + This enables support for DP device. + config DRM_EXYNOS_HDMI bool "Exynos DRM HDMI" depends on DRM_EXYNOS && !VIDEO_SAMSUNG_S5P_TV @@ -50,13 +75,13 @@ config DRM_EXYNOS_G2D config DRM_EXYNOS_IPP bool "Exynos DRM IPP" - depends on DRM_EXYNOS && !ARCH_MULTIPLATFORM + depends on DRM_EXYNOS help Choose this option if you want to use IPP feature for DRM. config DRM_EXYNOS_FIMC bool "Exynos DRM FIMC" - depends on DRM_EXYNOS_IPP && MFD_SYSCON && OF + depends on DRM_EXYNOS_IPP && MFD_SYSCON help Choose this option if you want to use Exynos FIMC for DRM. @@ -68,6 +93,6 @@ config DRM_EXYNOS_ROTATOR config DRM_EXYNOS_GSC bool "Exynos DRM GSC" - depends on DRM_EXYNOS_IPP && ARCH_EXYNOS5 + depends on DRM_EXYNOS_IPP && ARCH_EXYNOS5 && !ARCH_MULTIPLATFORM help Choose this option if you want to use Exynos GSC for DRM. diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 639b49e1ec0..33ae3652b8d 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -3,7 +3,7 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/exynos -exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \ +exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o \ exynos_drm_crtc.o exynos_drm_fbdev.o exynos_drm_fb.o \ exynos_drm_buf.o exynos_drm_gem.o exynos_drm_core.o \ exynos_drm_plane.o @@ -11,9 +11,10 @@ exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \ exynosdrm-$(CONFIG_DRM_EXYNOS_IOMMU) += exynos_drm_iommu.o exynosdrm-$(CONFIG_DRM_EXYNOS_DMABUF) += exynos_drm_dmabuf.o exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o -exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o \ - exynos_ddc.o exynos_hdmiphy.o \ - exynos_drm_hdmi.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp_core.o exynos_dp_reg.o +exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o exynosdrm-$(CONFIG_DRM_EXYNOS_IPP) += exynos_drm_ipp.o diff --git a/drivers/gpu/drm/exynos/exynos_ddc.c b/drivers/gpu/drm/exynos/exynos_ddc.c deleted file mode 100644 index 6a8c84e7c83..00000000000 --- a/drivers/gpu/drm/exynos/exynos_ddc.c +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2011 Samsung Electronics Co.Ltd - * Authors: - * Seung-Woo Kim <sw0312.kim@samsung.com> - * Inki Dae <inki.dae@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ - -#include <drm/drmP.h> - -#include <linux/kernel.h> -#include <linux/i2c.h> -#include <linux/of.h> - -#include "exynos_drm_drv.h" -#include "exynos_hdmi.h" - -static int s5p_ddc_probe(struct i2c_client *client, - const struct i2c_device_id *dev_id) -{ - hdmi_attach_ddc_client(client); - - dev_info(&client->adapter->dev, - "attached %s into i2c adapter successfully\n", - client->name); - - return 0; -} - -static int s5p_ddc_remove(struct i2c_client *client) -{ - dev_info(&client->adapter->dev, - "detached %s from i2c adapter successfully\n", - client->name); - - return 0; -} - -static struct of_device_id hdmiddc_match_types[] = { - { - .compatible = "samsung,exynos5-hdmiddc", - }, { - .compatible = "samsung,exynos4210-hdmiddc", - }, { - /* end node */ - } -}; - -struct i2c_driver ddc_driver = { - .driver = { - .name = "exynos-hdmiddc", - .owner = THIS_MODULE, - .of_match_table = hdmiddc_match_types, - }, - .probe = s5p_ddc_probe, - .remove = s5p_ddc_remove, - .command = NULL, -}; diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c new file mode 100644 index 00000000000..a8ffc8c1477 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -0,0 +1,1393 @@ +/* + * Samsung SoC DP (Display Port) interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/gpio.h> +#include <linux/component.h> +#include <linux/phy/phy.h> +#include <video/of_display_timing.h> +#include <video/of_videomode.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/bridge/ptn3460.h> + +#include "exynos_drm_drv.h" +#include "exynos_dp_core.h" + +#define ctx_from_connector(c) container_of(c, struct exynos_dp_device, \ + connector) + +struct bridge_init { + struct i2c_client *client; + struct device_node *node; +}; + +static int exynos_dp_init_dp(struct exynos_dp_device *dp) +{ + exynos_dp_reset(dp); + + exynos_dp_swreset(dp); + + exynos_dp_init_analog_param(dp); + exynos_dp_init_interrupt(dp); + + /* SW defined function Normal operation */ + exynos_dp_enable_sw_function(dp); + + exynos_dp_config_interrupt(dp); + exynos_dp_init_analog_func(dp); + + exynos_dp_init_hpd(dp); + exynos_dp_init_aux(dp); + + return 0; +} + +static int exynos_dp_detect_hpd(struct exynos_dp_device *dp) +{ + int timeout_loop = 0; + + while (exynos_dp_get_plug_in_status(dp) != 0) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "failed to get hpd plug status\n"); + return -ETIMEDOUT; + } + usleep_range(10, 11); + } + + return 0; +} + +static unsigned char exynos_dp_calc_edid_check_sum(unsigned char *edid_data) +{ + int i; + unsigned char sum = 0; + + for (i = 0; i < EDID_BLOCK_LENGTH; i++) + sum = sum + edid_data[i]; + + return sum; +} + +static int exynos_dp_read_edid(struct exynos_dp_device *dp) +{ + unsigned char edid[EDID_BLOCK_LENGTH * 2]; + unsigned int extend_block = 0; + unsigned char sum; + unsigned char test_vector; + int retval; + + /* + * EDID device address is 0x50. + * However, if necessary, you must have set upper address + * into E-EDID in I2C device, 0x30. + */ + + /* Read Extension Flag, Number of 128-byte EDID extension blocks */ + retval = exynos_dp_read_byte_from_i2c(dp, I2C_EDID_DEVICE_ADDR, + EDID_EXTENSION_FLAG, + &extend_block); + if (retval) + return retval; + + if (extend_block > 0) { + dev_dbg(dp->dev, "EDID data includes a single extension!\n"); + + /* Read EDID data */ + retval = exynos_dp_read_bytes_from_i2c(dp, I2C_EDID_DEVICE_ADDR, + EDID_HEADER_PATTERN, + EDID_BLOCK_LENGTH, + &edid[EDID_HEADER_PATTERN]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = exynos_dp_calc_edid_check_sum(edid); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + /* Read additional EDID data */ + retval = exynos_dp_read_bytes_from_i2c(dp, + I2C_EDID_DEVICE_ADDR, + EDID_BLOCK_LENGTH, + EDID_BLOCK_LENGTH, + &edid[EDID_BLOCK_LENGTH]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = exynos_dp_calc_edid_check_sum(&edid[EDID_BLOCK_LENGTH]); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + exynos_dp_read_byte_from_dpcd(dp, DP_TEST_REQUEST, + &test_vector); + if (test_vector & DP_TEST_LINK_EDID_READ) { + exynos_dp_write_byte_to_dpcd(dp, + DP_TEST_EDID_CHECKSUM, + edid[EDID_BLOCK_LENGTH + EDID_CHECKSUM]); + exynos_dp_write_byte_to_dpcd(dp, + DP_TEST_RESPONSE, + DP_TEST_EDID_CHECKSUM_WRITE); + } + } else { + dev_info(dp->dev, "EDID data does not include any extensions.\n"); + + /* Read EDID data */ + retval = exynos_dp_read_bytes_from_i2c(dp, + I2C_EDID_DEVICE_ADDR, + EDID_HEADER_PATTERN, + EDID_BLOCK_LENGTH, + &edid[EDID_HEADER_PATTERN]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = exynos_dp_calc_edid_check_sum(edid); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + exynos_dp_read_byte_from_dpcd(dp, + DP_TEST_REQUEST, + &test_vector); + if (test_vector & DP_TEST_LINK_EDID_READ) { + exynos_dp_write_byte_to_dpcd(dp, + DP_TEST_EDID_CHECKSUM, + edid[EDID_CHECKSUM]); + exynos_dp_write_byte_to_dpcd(dp, + DP_TEST_RESPONSE, + DP_TEST_EDID_CHECKSUM_WRITE); + } + } + + dev_err(dp->dev, "EDID Read success!\n"); + return 0; +} + +static int exynos_dp_handle_edid(struct exynos_dp_device *dp) +{ + u8 buf[12]; + int i; + int retval; + + /* Read DPCD DP_DPCD_REV~RECEIVE_PORT1_CAP_1 */ + retval = exynos_dp_read_bytes_from_dpcd(dp, DP_DPCD_REV, + 12, buf); + if (retval) + return retval; + + /* Read EDID */ + for (i = 0; i < 3; i++) { + retval = exynos_dp_read_edid(dp); + if (!retval) + break; + } + + return retval; +} + +static void exynos_dp_enable_rx_to_enhanced_mode(struct exynos_dp_device *dp, + bool enable) +{ + u8 data; + + exynos_dp_read_byte_from_dpcd(dp, DP_LANE_COUNT_SET, &data); + + if (enable) + exynos_dp_write_byte_to_dpcd(dp, DP_LANE_COUNT_SET, + DP_LANE_COUNT_ENHANCED_FRAME_EN | + DPCD_LANE_COUNT_SET(data)); + else + exynos_dp_write_byte_to_dpcd(dp, DP_LANE_COUNT_SET, + DPCD_LANE_COUNT_SET(data)); +} + +static int exynos_dp_is_enhanced_mode_available(struct exynos_dp_device *dp) +{ + u8 data; + int retval; + + exynos_dp_read_byte_from_dpcd(dp, DP_MAX_LANE_COUNT, &data); + retval = DPCD_ENHANCED_FRAME_CAP(data); + + return retval; +} + +static void exynos_dp_set_enhanced_mode(struct exynos_dp_device *dp) +{ + u8 data; + + data = exynos_dp_is_enhanced_mode_available(dp); + exynos_dp_enable_rx_to_enhanced_mode(dp, data); + exynos_dp_enable_enhanced_mode(dp, data); +} + +static void exynos_dp_training_pattern_dis(struct exynos_dp_device *dp) +{ + exynos_dp_set_training_pattern(dp, DP_NONE); + + exynos_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); +} + +static void exynos_dp_set_lane_lane_pre_emphasis(struct exynos_dp_device *dp, + int pre_emphasis, int lane) +{ + switch (lane) { + case 0: + exynos_dp_set_lane0_pre_emphasis(dp, pre_emphasis); + break; + case 1: + exynos_dp_set_lane1_pre_emphasis(dp, pre_emphasis); + break; + + case 2: + exynos_dp_set_lane2_pre_emphasis(dp, pre_emphasis); + break; + + case 3: + exynos_dp_set_lane3_pre_emphasis(dp, pre_emphasis); + break; + } +} + +static int exynos_dp_link_start(struct exynos_dp_device *dp) +{ + u8 buf[4]; + int lane, lane_count, pll_tries, retval; + + lane_count = dp->link_train.lane_count; + + dp->link_train.lt_state = CLOCK_RECOVERY; + dp->link_train.eq_loop = 0; + + for (lane = 0; lane < lane_count; lane++) + dp->link_train.cr_loop[lane] = 0; + + /* Set link rate and count as you want to establish*/ + exynos_dp_set_link_bandwidth(dp, dp->link_train.link_rate); + exynos_dp_set_lane_count(dp, dp->link_train.lane_count); + + /* Setup RX configuration */ + buf[0] = dp->link_train.link_rate; + buf[1] = dp->link_train.lane_count; + retval = exynos_dp_write_bytes_to_dpcd(dp, DP_LINK_BW_SET, + 2, buf); + if (retval) + return retval; + + /* Set TX pre-emphasis to minimum */ + for (lane = 0; lane < lane_count; lane++) + exynos_dp_set_lane_lane_pre_emphasis(dp, + PRE_EMPHASIS_LEVEL_0, lane); + + /* Wait for PLL lock */ + pll_tries = 0; + while (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + if (pll_tries == DP_TIMEOUT_LOOP_COUNT) { + dev_err(dp->dev, "Wait for PLL lock timed out\n"); + return -ETIMEDOUT; + } + + pll_tries++; + usleep_range(90, 120); + } + + /* Set training pattern 1 */ + exynos_dp_set_training_pattern(dp, TRAINING_PTN1); + + /* Set RX training pattern */ + retval = exynos_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | DP_TRAINING_PATTERN_1); + if (retval) + return retval; + + for (lane = 0; lane < lane_count; lane++) + buf[lane] = DP_TRAIN_PRE_EMPHASIS_0 | + DP_TRAIN_VOLTAGE_SWING_400; + + retval = exynos_dp_write_bytes_to_dpcd(dp, DP_TRAINING_LANE0_SET, + lane_count, buf); + + return retval; +} + +static unsigned char exynos_dp_get_lane_status(u8 link_status[2], int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = link_status[lane>>1]; + + return (link_value >> shift) & 0xf; +} + +static int exynos_dp_clock_recovery_ok(u8 link_status[2], int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = exynos_dp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return -EINVAL; + } + return 0; +} + +static int exynos_dp_channel_eq_ok(u8 link_status[2], u8 link_align, + int lane_count) +{ + int lane; + u8 lane_status; + + if ((link_align & DP_INTERLANE_ALIGN_DONE) == 0) + return -EINVAL; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = exynos_dp_get_lane_status(link_status, lane); + lane_status &= DP_CHANNEL_EQ_BITS; + if (lane_status != DP_CHANNEL_EQ_BITS) + return -EINVAL; + } + + return 0; +} + +static unsigned char exynos_dp_get_adjust_request_voltage(u8 adjust_request[2], + int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane>>1]; + + return (link_value >> shift) & 0x3; +} + +static unsigned char exynos_dp_get_adjust_request_pre_emphasis( + u8 adjust_request[2], + int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane>>1]; + + return ((link_value >> shift) & 0xc) >> 2; +} + +static void exynos_dp_set_lane_link_training(struct exynos_dp_device *dp, + u8 training_lane_set, int lane) +{ + switch (lane) { + case 0: + exynos_dp_set_lane0_link_training(dp, training_lane_set); + break; + case 1: + exynos_dp_set_lane1_link_training(dp, training_lane_set); + break; + + case 2: + exynos_dp_set_lane2_link_training(dp, training_lane_set); + break; + + case 3: + exynos_dp_set_lane3_link_training(dp, training_lane_set); + break; + } +} + +static unsigned int exynos_dp_get_lane_link_training( + struct exynos_dp_device *dp, + int lane) +{ + u32 reg; + + switch (lane) { + case 0: + reg = exynos_dp_get_lane0_link_training(dp); + break; + case 1: + reg = exynos_dp_get_lane1_link_training(dp); + break; + case 2: + reg = exynos_dp_get_lane2_link_training(dp); + break; + case 3: + reg = exynos_dp_get_lane3_link_training(dp); + break; + default: + WARN_ON(1); + return 0; + } + + return reg; +} + +static void exynos_dp_reduce_link_rate(struct exynos_dp_device *dp) +{ + exynos_dp_training_pattern_dis(dp); + exynos_dp_set_enhanced_mode(dp); + + dp->link_train.lt_state = FAILED; +} + +static void exynos_dp_get_adjust_training_lane(struct exynos_dp_device *dp, + u8 adjust_request[2]) +{ + int lane, lane_count; + u8 voltage_swing, pre_emphasis, training_lane; + + lane_count = dp->link_train.lane_count; + for (lane = 0; lane < lane_count; lane++) { + voltage_swing = exynos_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = exynos_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + training_lane = DPCD_VOLTAGE_SWING_SET(voltage_swing) | + DPCD_PRE_EMPHASIS_SET(pre_emphasis); + + if (voltage_swing == VOLTAGE_LEVEL_3) + training_lane |= DP_TRAIN_MAX_SWING_REACHED; + if (pre_emphasis == PRE_EMPHASIS_LEVEL_3) + training_lane |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + dp->link_train.training_lane[lane] = training_lane; + } +} + +static int exynos_dp_process_clock_recovery(struct exynos_dp_device *dp) +{ + int lane, lane_count, retval; + u8 voltage_swing, pre_emphasis, training_lane; + u8 link_status[2], adjust_request[2]; + + usleep_range(100, 101); + + lane_count = dp->link_train.lane_count; + + retval = exynos_dp_read_bytes_from_dpcd(dp, + DP_LANE0_1_STATUS, 2, link_status); + if (retval) + return retval; + + retval = exynos_dp_read_bytes_from_dpcd(dp, + DP_ADJUST_REQUEST_LANE0_1, 2, adjust_request); + if (retval) + return retval; + + if (exynos_dp_clock_recovery_ok(link_status, lane_count) == 0) { + /* set training pattern 2 for EQ */ + exynos_dp_set_training_pattern(dp, TRAINING_PTN2); + + retval = exynos_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_2); + if (retval) + return retval; + + dev_info(dp->dev, "Link Training Clock Recovery success\n"); + dp->link_train.lt_state = EQUALIZER_TRAINING; + } else { + for (lane = 0; lane < lane_count; lane++) { + training_lane = exynos_dp_get_lane_link_training( + dp, lane); + voltage_swing = exynos_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = exynos_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + + if (DPCD_VOLTAGE_SWING_GET(training_lane) == + voltage_swing && + DPCD_PRE_EMPHASIS_GET(training_lane) == + pre_emphasis) + dp->link_train.cr_loop[lane]++; + + if (dp->link_train.cr_loop[lane] == MAX_CR_LOOP || + voltage_swing == VOLTAGE_LEVEL_3 || + pre_emphasis == PRE_EMPHASIS_LEVEL_3) { + dev_err(dp->dev, "CR Max reached (%d,%d,%d)\n", + dp->link_train.cr_loop[lane], + voltage_swing, pre_emphasis); + exynos_dp_reduce_link_rate(dp); + return -EIO; + } + } + } + + exynos_dp_get_adjust_training_lane(dp, adjust_request); + + for (lane = 0; lane < lane_count; lane++) + exynos_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], lane); + + retval = exynos_dp_write_bytes_to_dpcd(dp, + DP_TRAINING_LANE0_SET, lane_count, + dp->link_train.training_lane); + if (retval) + return retval; + + return retval; +} + +static int exynos_dp_process_equalizer_training(struct exynos_dp_device *dp) +{ + int lane, lane_count, retval; + u32 reg; + u8 link_align, link_status[2], adjust_request[2]; + + usleep_range(400, 401); + + lane_count = dp->link_train.lane_count; + + retval = exynos_dp_read_bytes_from_dpcd(dp, + DP_LANE0_1_STATUS, 2, link_status); + if (retval) + return retval; + + if (exynos_dp_clock_recovery_ok(link_status, lane_count)) { + exynos_dp_reduce_link_rate(dp); + return -EIO; + } + + retval = exynos_dp_read_bytes_from_dpcd(dp, + DP_ADJUST_REQUEST_LANE0_1, 2, adjust_request); + if (retval) + return retval; + + retval = exynos_dp_read_byte_from_dpcd(dp, + DP_LANE_ALIGN_STATUS_UPDATED, &link_align); + if (retval) + return retval; + + exynos_dp_get_adjust_training_lane(dp, adjust_request); + + if (!exynos_dp_channel_eq_ok(link_status, link_align, lane_count)) { + /* traing pattern Set to Normal */ + exynos_dp_training_pattern_dis(dp); + + dev_info(dp->dev, "Link Training success!\n"); + + exynos_dp_get_link_bandwidth(dp, ®); + dp->link_train.link_rate = reg; + dev_dbg(dp->dev, "final bandwidth = %.2x\n", + dp->link_train.link_rate); + + exynos_dp_get_lane_count(dp, ®); + dp->link_train.lane_count = reg; + dev_dbg(dp->dev, "final lane count = %.2x\n", + dp->link_train.lane_count); + + /* set enhanced mode if available */ + exynos_dp_set_enhanced_mode(dp); + dp->link_train.lt_state = FINISHED; + + return 0; + } + + /* not all locked */ + dp->link_train.eq_loop++; + + if (dp->link_train.eq_loop > MAX_EQ_LOOP) { + dev_err(dp->dev, "EQ Max loop\n"); + exynos_dp_reduce_link_rate(dp); + return -EIO; + } + + for (lane = 0; lane < lane_count; lane++) + exynos_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], lane); + + retval = exynos_dp_write_bytes_to_dpcd(dp, DP_TRAINING_LANE0_SET, + lane_count, dp->link_train.training_lane); + + return retval; +} + +static void exynos_dp_get_max_rx_bandwidth(struct exynos_dp_device *dp, + u8 *bandwidth) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps + */ + exynos_dp_read_byte_from_dpcd(dp, DP_MAX_LINK_RATE, &data); + *bandwidth = data; +} + +static void exynos_dp_get_max_rx_lane_count(struct exynos_dp_device *dp, + u8 *lane_count) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum number of Main Link lanes + * 0x01 = 1 lane, 0x02 = 2 lanes, 0x04 = 4 lanes + */ + exynos_dp_read_byte_from_dpcd(dp, DP_MAX_LANE_COUNT, &data); + *lane_count = DPCD_MAX_LANE_COUNT(data); +} + +static void exynos_dp_init_training(struct exynos_dp_device *dp, + enum link_lane_count_type max_lane, + enum link_rate_type max_rate) +{ + /* + * MACRO_RST must be applied after the PLL_LOCK to avoid + * the DP inter pair skew issue for at least 10 us + */ + exynos_dp_reset_macro(dp); + + /* Initialize by reading RX's DPCD */ + exynos_dp_get_max_rx_bandwidth(dp, &dp->link_train.link_rate); + exynos_dp_get_max_rx_lane_count(dp, &dp->link_train.lane_count); + + if ((dp->link_train.link_rate != LINK_RATE_1_62GBPS) && + (dp->link_train.link_rate != LINK_RATE_2_70GBPS)) { + dev_err(dp->dev, "Rx Max Link Rate is abnormal :%x !\n", + dp->link_train.link_rate); + dp->link_train.link_rate = LINK_RATE_1_62GBPS; + } + + if (dp->link_train.lane_count == 0) { + dev_err(dp->dev, "Rx Max Lane count is abnormal :%x !\n", + dp->link_train.lane_count); + dp->link_train.lane_count = (u8)LANE_COUNT1; + } + + /* Setup TX lane count & rate */ + if (dp->link_train.lane_count > max_lane) + dp->link_train.lane_count = max_lane; + if (dp->link_train.link_rate > max_rate) + dp->link_train.link_rate = max_rate; + + /* All DP analog module power up */ + exynos_dp_set_analog_power_down(dp, POWER_ALL, 0); +} + +static int exynos_dp_sw_link_training(struct exynos_dp_device *dp) +{ + int retval = 0, training_finished = 0; + + dp->link_train.lt_state = START; + + /* Process here */ + while (!retval && !training_finished) { + switch (dp->link_train.lt_state) { + case START: + retval = exynos_dp_link_start(dp); + if (retval) + dev_err(dp->dev, "LT link start failed!\n"); + break; + case CLOCK_RECOVERY: + retval = exynos_dp_process_clock_recovery(dp); + if (retval) + dev_err(dp->dev, "LT CR failed!\n"); + break; + case EQUALIZER_TRAINING: + retval = exynos_dp_process_equalizer_training(dp); + if (retval) + dev_err(dp->dev, "LT EQ failed!\n"); + break; + case FINISHED: + training_finished = 1; + break; + case FAILED: + return -EREMOTEIO; + } + } + if (retval) + dev_err(dp->dev, "eDP link training failed (%d)\n", retval); + + return retval; +} + +static int exynos_dp_set_link_train(struct exynos_dp_device *dp, + u32 count, + u32 bwtype) +{ + int i; + int retval; + + for (i = 0; i < DP_TIMEOUT_LOOP_COUNT; i++) { + exynos_dp_init_training(dp, count, bwtype); + retval = exynos_dp_sw_link_training(dp); + if (retval == 0) + break; + + usleep_range(100, 110); + } + + return retval; +} + +static int exynos_dp_config_video(struct exynos_dp_device *dp) +{ + int retval = 0; + int timeout_loop = 0; + int done_count = 0; + + exynos_dp_config_video_slave_mode(dp); + + exynos_dp_set_video_color_format(dp); + + if (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + dev_err(dp->dev, "PLL is not locked yet.\n"); + return -EINVAL; + } + + for (;;) { + timeout_loop++; + if (exynos_dp_is_slave_video_stream_clock_on(dp) == 0) + break; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "Timeout of video streamclk ok\n"); + return -ETIMEDOUT; + } + + usleep_range(1, 2); + } + + /* Set to use the register calculated M/N video */ + exynos_dp_set_video_cr_mn(dp, CALCULATED_M, 0, 0); + + /* For video bist, Video timing must be generated by register */ + exynos_dp_set_video_timing_mode(dp, VIDEO_TIMING_FROM_CAPTURE); + + /* Disable video mute */ + exynos_dp_enable_video_mute(dp, 0); + + /* Configure video slave mode */ + exynos_dp_enable_video_master(dp, 0); + + /* Enable video */ + exynos_dp_start_video(dp); + + timeout_loop = 0; + + for (;;) { + timeout_loop++; + if (exynos_dp_is_video_stream_on(dp) == 0) { + done_count++; + if (done_count > 10) + break; + } else if (done_count) { + done_count = 0; + } + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "Timeout of video streamclk ok\n"); + return -ETIMEDOUT; + } + + usleep_range(1000, 1001); + } + + if (retval != 0) + dev_err(dp->dev, "Video stream is not detected!\n"); + + return retval; +} + +static void exynos_dp_enable_scramble(struct exynos_dp_device *dp, bool enable) +{ + u8 data; + + if (enable) { + exynos_dp_enable_scrambling(dp); + + exynos_dp_read_byte_from_dpcd(dp, + DP_TRAINING_PATTERN_SET, + &data); + exynos_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + (u8)(data & ~DP_LINK_SCRAMBLING_DISABLE)); + } else { + exynos_dp_disable_scrambling(dp); + + exynos_dp_read_byte_from_dpcd(dp, + DP_TRAINING_PATTERN_SET, + &data); + exynos_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + (u8)(data | DP_LINK_SCRAMBLING_DISABLE)); + } +} + +static irqreturn_t exynos_dp_irq_handler(int irq, void *arg) +{ + struct exynos_dp_device *dp = arg; + + enum dp_irq_type irq_type; + + irq_type = exynos_dp_get_irq_type(dp); + switch (irq_type) { + case DP_IRQ_TYPE_HP_CABLE_IN: + dev_dbg(dp->dev, "Received irq - cable in\n"); + schedule_work(&dp->hotplug_work); + exynos_dp_clear_hotplug_interrupts(dp); + break; + case DP_IRQ_TYPE_HP_CABLE_OUT: + dev_dbg(dp->dev, "Received irq - cable out\n"); + exynos_dp_clear_hotplug_interrupts(dp); + break; + case DP_IRQ_TYPE_HP_CHANGE: + /* + * We get these change notifications once in a while, but there + * is nothing we can do with them. Just ignore it for now and + * only handle cable changes. + */ + dev_dbg(dp->dev, "Received irq - hotplug change; ignoring.\n"); + exynos_dp_clear_hotplug_interrupts(dp); + break; + default: + dev_err(dp->dev, "Received irq - unknown type!\n"); + break; + } + return IRQ_HANDLED; +} + +static void exynos_dp_hotplug(struct work_struct *work) +{ + struct exynos_dp_device *dp; + int ret; + + dp = container_of(work, struct exynos_dp_device, hotplug_work); + + ret = exynos_dp_detect_hpd(dp); + if (ret) { + /* Cable has been disconnected, we're done */ + return; + } + + ret = exynos_dp_handle_edid(dp); + if (ret) { + dev_err(dp->dev, "unable to handle edid\n"); + return; + } + + ret = exynos_dp_set_link_train(dp, dp->video_info->lane_count, + dp->video_info->link_rate); + if (ret) { + dev_err(dp->dev, "unable to do link train\n"); + return; + } + + exynos_dp_enable_scramble(dp, 1); + exynos_dp_enable_rx_to_enhanced_mode(dp, 1); + exynos_dp_enable_enhanced_mode(dp, 1); + + exynos_dp_set_lane_count(dp, dp->video_info->lane_count); + exynos_dp_set_link_bandwidth(dp, dp->video_info->link_rate); + + exynos_dp_init_video(dp); + ret = exynos_dp_config_video(dp); + if (ret) + dev_err(dp->dev, "unable to config video\n"); +} + +static enum drm_connector_status exynos_dp_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void exynos_dp_connector_destroy(struct drm_connector *connector) +{ +} + +static struct drm_connector_funcs exynos_dp_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = exynos_dp_detect, + .destroy = exynos_dp_connector_destroy, +}; + +static int exynos_dp_get_modes(struct drm_connector *connector) +{ + struct exynos_dp_device *dp = ctx_from_connector(connector); + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_ERROR("failed to create a new display mode.\n"); + return 0; + } + + drm_display_mode_from_videomode(&dp->panel.vm, mode); + mode->width_mm = dp->panel.width_mm; + mode->height_mm = dp->panel.height_mm; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + return 1; +} + +static struct drm_encoder *exynos_dp_best_encoder( + struct drm_connector *connector) +{ + struct exynos_dp_device *dp = ctx_from_connector(connector); + + return dp->encoder; +} + +static struct drm_connector_helper_funcs exynos_dp_connector_helper_funcs = { + .get_modes = exynos_dp_get_modes, + .best_encoder = exynos_dp_best_encoder, +}; + +static bool find_bridge(const char *compat, struct bridge_init *bridge) +{ + bridge->client = NULL; + bridge->node = of_find_compatible_node(NULL, NULL, compat); + if (!bridge->node) + return false; + + bridge->client = of_find_i2c_device_by_node(bridge->node); + if (!bridge->client) + return false; + + return true; +} + +/* returns the number of bridges attached */ +static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, + struct drm_encoder *encoder) +{ + struct bridge_init bridge; + int ret; + + if (find_bridge("nxp,ptn3460", &bridge)) { + ret = ptn3460_init(dev, encoder, bridge.client, bridge.node); + if (!ret) + return 1; + } + return 0; +} + +static int exynos_dp_create_connector(struct exynos_drm_display *display, + struct drm_encoder *encoder) +{ + struct exynos_dp_device *dp = display->ctx; + struct drm_connector *connector = &dp->connector; + int ret; + + dp->encoder = encoder; + + /* Pre-empt DP connector creation if there's a bridge */ + ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder); + if (ret) + return 0; + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(dp->drm_dev, connector, + &exynos_dp_connector_funcs, DRM_MODE_CONNECTOR_eDP); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(connector, &exynos_dp_connector_helper_funcs); + drm_sysfs_connector_add(connector); + drm_mode_connector_attach_encoder(connector, encoder); + + return 0; +} + +static void exynos_dp_phy_init(struct exynos_dp_device *dp) +{ + if (dp->phy) { + phy_power_on(dp->phy); + } else if (dp->phy_addr) { + u32 reg; + + reg = __raw_readl(dp->phy_addr); + reg |= dp->enable_mask; + __raw_writel(reg, dp->phy_addr); + } +} + +static void exynos_dp_phy_exit(struct exynos_dp_device *dp) +{ + if (dp->phy) { + phy_power_off(dp->phy); + } else if (dp->phy_addr) { + u32 reg; + + reg = __raw_readl(dp->phy_addr); + reg &= ~(dp->enable_mask); + __raw_writel(reg, dp->phy_addr); + } +} + +static void exynos_dp_poweron(struct exynos_dp_device *dp) +{ + if (dp->dpms_mode == DRM_MODE_DPMS_ON) + return; + + clk_prepare_enable(dp->clock); + exynos_dp_phy_init(dp); + exynos_dp_init_dp(dp); + enable_irq(dp->irq); +} + +static void exynos_dp_poweroff(struct exynos_dp_device *dp) +{ + if (dp->dpms_mode != DRM_MODE_DPMS_ON) + return; + + disable_irq(dp->irq); + flush_work(&dp->hotplug_work); + exynos_dp_phy_exit(dp); + clk_disable_unprepare(dp->clock); +} + +static void exynos_dp_dpms(struct exynos_drm_display *display, int mode) +{ + struct exynos_dp_device *dp = display->ctx; + + switch (mode) { + case DRM_MODE_DPMS_ON: + exynos_dp_poweron(dp); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + exynos_dp_poweroff(dp); + break; + default: + break; + } + dp->dpms_mode = mode; +} + +static struct exynos_drm_display_ops exynos_dp_display_ops = { + .create_connector = exynos_dp_create_connector, + .dpms = exynos_dp_dpms, +}; + +static struct exynos_drm_display exynos_dp_display = { + .type = EXYNOS_DISPLAY_TYPE_LCD, + .ops = &exynos_dp_display_ops, +}; + +static struct video_info *exynos_dp_dt_parse_pdata(struct device *dev) +{ + struct device_node *dp_node = dev->of_node; + struct video_info *dp_video_config; + + dp_video_config = devm_kzalloc(dev, + sizeof(*dp_video_config), GFP_KERNEL); + if (!dp_video_config) + return ERR_PTR(-ENOMEM); + + dp_video_config->h_sync_polarity = + of_property_read_bool(dp_node, "hsync-active-high"); + + dp_video_config->v_sync_polarity = + of_property_read_bool(dp_node, "vsync-active-high"); + + dp_video_config->interlaced = + of_property_read_bool(dp_node, "interlaced"); + + if (of_property_read_u32(dp_node, "samsung,color-space", + &dp_video_config->color_space)) { + dev_err(dev, "failed to get color-space\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_u32(dp_node, "samsung,dynamic-range", + &dp_video_config->dynamic_range)) { + dev_err(dev, "failed to get dynamic-range\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_u32(dp_node, "samsung,ycbcr-coeff", + &dp_video_config->ycbcr_coeff)) { + dev_err(dev, "failed to get ycbcr-coeff\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_u32(dp_node, "samsung,color-depth", + &dp_video_config->color_depth)) { + dev_err(dev, "failed to get color-depth\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_u32(dp_node, "samsung,link-rate", + &dp_video_config->link_rate)) { + dev_err(dev, "failed to get link-rate\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_u32(dp_node, "samsung,lane-count", + &dp_video_config->lane_count)) { + dev_err(dev, "failed to get lane-count\n"); + return ERR_PTR(-EINVAL); + } + + return dp_video_config; +} + +static int exynos_dp_dt_parse_phydata(struct exynos_dp_device *dp) +{ + struct device_node *dp_phy_node = of_node_get(dp->dev->of_node); + u32 phy_base; + int ret = 0; + + dp_phy_node = of_find_node_by_name(dp_phy_node, "dptx-phy"); + if (!dp_phy_node) { + dp->phy = devm_phy_get(dp->dev, "dp"); + return PTR_ERR_OR_ZERO(dp->phy); + } + + if (of_property_read_u32(dp_phy_node, "reg", &phy_base)) { + dev_err(dp->dev, "failed to get reg for dptx-phy\n"); + ret = -EINVAL; + goto err; + } + + if (of_property_read_u32(dp_phy_node, "samsung,enable-mask", + &dp->enable_mask)) { + dev_err(dp->dev, "failed to get enable-mask for dptx-phy\n"); + ret = -EINVAL; + goto err; + } + + dp->phy_addr = ioremap(phy_base, SZ_4); + if (!dp->phy_addr) { + dev_err(dp->dev, "failed to ioremap dp-phy\n"); + ret = -ENOMEM; + goto err; + } + +err: + of_node_put(dp_phy_node); + + return ret; +} + +static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp) +{ + int ret; + + ret = of_get_videomode(dp->dev->of_node, &dp->panel.vm, + OF_USE_NATIVE_MODE); + if (ret) { + DRM_ERROR("failed: of_get_videomode() : %d\n", ret); + return ret; + } + return 0; +} + +static int exynos_dp_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm_dev = data; + struct resource *res; + struct exynos_dp_device *dp; + unsigned int irq_flags; + + int ret = 0; + + dp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_dp_device), + GFP_KERNEL); + if (!dp) + return -ENOMEM; + + dp->dev = &pdev->dev; + dp->dpms_mode = DRM_MODE_DPMS_OFF; + + dp->video_info = exynos_dp_dt_parse_pdata(&pdev->dev); + if (IS_ERR(dp->video_info)) + return PTR_ERR(dp->video_info); + + ret = exynos_dp_dt_parse_phydata(dp); + if (ret) + return ret; + + ret = exynos_dp_dt_parse_panel(dp); + if (ret) + return ret; + + dp->clock = devm_clk_get(&pdev->dev, "dp"); + if (IS_ERR(dp->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(dp->clock); + } + + clk_prepare_enable(dp->clock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dp->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dp->reg_base)) + return PTR_ERR(dp->reg_base); + + dp->hpd_gpio = of_get_named_gpio(dev->of_node, "samsung,hpd-gpio", 0); + + if (gpio_is_valid(dp->hpd_gpio)) { + /* + * Set up the hotplug GPIO from the device tree as an interrupt. + * Simply specifying a different interrupt in the device tree + * doesn't work since we handle hotplug rather differently when + * using a GPIO. We also need the actual GPIO specifier so + * that we can get the current state of the GPIO. + */ + ret = devm_gpio_request_one(&pdev->dev, dp->hpd_gpio, GPIOF_IN, + "hpd_gpio"); + if (ret) { + dev_err(&pdev->dev, "failed to get hpd gpio\n"); + return ret; + } + dp->irq = gpio_to_irq(dp->hpd_gpio); + irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + } else { + dp->hpd_gpio = -ENODEV; + dp->irq = platform_get_irq(pdev, 0); + irq_flags = 0; + } + + if (dp->irq == -ENXIO) { + dev_err(&pdev->dev, "failed to get irq\n"); + return -ENODEV; + } + + INIT_WORK(&dp->hotplug_work, exynos_dp_hotplug); + + exynos_dp_phy_init(dp); + + exynos_dp_init_dp(dp); + + ret = devm_request_irq(&pdev->dev, dp->irq, exynos_dp_irq_handler, + irq_flags, "exynos-dp", dp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + disable_irq(dp->irq); + + dp->drm_dev = drm_dev; + exynos_dp_display.ctx = dp; + + platform_set_drvdata(pdev, &exynos_dp_display); + + return exynos_drm_create_enc_conn(drm_dev, &exynos_dp_display); +} + +static void exynos_dp_unbind(struct device *dev, struct device *master, + void *data) +{ + struct exynos_drm_display *display = dev_get_drvdata(dev); + struct exynos_dp_device *dp = display->ctx; + struct drm_encoder *encoder = dp->encoder; + + exynos_dp_dpms(display, DRM_MODE_DPMS_OFF); + + encoder->funcs->destroy(encoder); + drm_connector_cleanup(&dp->connector); +} + +static const struct component_ops exynos_dp_ops = { + .bind = exynos_dp_bind, + .unbind = exynos_dp_unbind, +}; + +static int exynos_dp_probe(struct platform_device *pdev) +{ + int ret; + + ret = exynos_drm_component_add(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR, + exynos_dp_display.type); + if (ret) + return ret; + + ret = component_add(&pdev->dev, &exynos_dp_ops); + if (ret) + exynos_drm_component_del(&pdev->dev, + EXYNOS_DEVICE_TYPE_CONNECTOR); + + return ret; +} + +static int exynos_dp_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &exynos_dp_ops); + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_dp_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_drm_display *display = platform_get_drvdata(pdev); + + exynos_dp_dpms(display, DRM_MODE_DPMS_OFF); + return 0; +} + +static int exynos_dp_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_drm_display *display = platform_get_drvdata(pdev); + + exynos_dp_dpms(display, DRM_MODE_DPMS_ON); + return 0; +} +#endif + +static const struct dev_pm_ops exynos_dp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_dp_suspend, exynos_dp_resume) +}; + +static const struct of_device_id exynos_dp_match[] = { + { .compatible = "samsung,exynos5-dp" }, + {}, +}; + +struct platform_driver dp_driver = { + .probe = exynos_dp_probe, + .remove = exynos_dp_remove, + .driver = { + .name = "exynos-dp", + .owner = THIS_MODULE, + .pm = &exynos_dp_pm_ops, + .of_match_table = exynos_dp_match, + }, +}; + +MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); +MODULE_DESCRIPTION("Samsung SoC DP Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.h b/drivers/gpu/drm/exynos/exynos_dp_core.h new file mode 100644 index 00000000000..02cc4f9ab90 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_dp_core.h @@ -0,0 +1,279 @@ +/* + * Header file for Samsung DP (Display Port) interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _EXYNOS_DP_CORE_H +#define _EXYNOS_DP_CORE_H + +#include <drm/drm_crtc.h> +#include <drm/drm_dp_helper.h> +#include <drm/exynos_drm.h> + +#define DP_TIMEOUT_LOOP_COUNT 100 +#define MAX_CR_LOOP 5 +#define MAX_EQ_LOOP 5 + +enum link_rate_type { + LINK_RATE_1_62GBPS = 0x06, + LINK_RATE_2_70GBPS = 0x0a +}; + +enum link_lane_count_type { + LANE_COUNT1 = 1, + LANE_COUNT2 = 2, + LANE_COUNT4 = 4 +}; + +enum link_training_state { + START, + CLOCK_RECOVERY, + EQUALIZER_TRAINING, + FINISHED, + FAILED +}; + +enum voltage_swing_level { + VOLTAGE_LEVEL_0, + VOLTAGE_LEVEL_1, + VOLTAGE_LEVEL_2, + VOLTAGE_LEVEL_3, +}; + +enum pre_emphasis_level { + PRE_EMPHASIS_LEVEL_0, + PRE_EMPHASIS_LEVEL_1, + PRE_EMPHASIS_LEVEL_2, + PRE_EMPHASIS_LEVEL_3, +}; + +enum pattern_set { + PRBS7, + D10_2, + TRAINING_PTN1, + TRAINING_PTN2, + DP_NONE +}; + +enum color_space { + COLOR_RGB, + COLOR_YCBCR422, + COLOR_YCBCR444 +}; + +enum color_depth { + COLOR_6, + COLOR_8, + COLOR_10, + COLOR_12 +}; + +enum color_coefficient { + COLOR_YCBCR601, + COLOR_YCBCR709 +}; + +enum dynamic_range { + VESA, + CEA +}; + +enum pll_status { + PLL_UNLOCKED, + PLL_LOCKED +}; + +enum clock_recovery_m_value_type { + CALCULATED_M, + REGISTER_M +}; + +enum video_timing_recognition_type { + VIDEO_TIMING_FROM_CAPTURE, + VIDEO_TIMING_FROM_REGISTER +}; + +enum analog_power_block { + AUX_BLOCK, + CH0_BLOCK, + CH1_BLOCK, + CH2_BLOCK, + CH3_BLOCK, + ANALOG_TOTAL, + POWER_ALL +}; + +enum dp_irq_type { + DP_IRQ_TYPE_HP_CABLE_IN, + DP_IRQ_TYPE_HP_CABLE_OUT, + DP_IRQ_TYPE_HP_CHANGE, + DP_IRQ_TYPE_UNKNOWN, +}; + +struct video_info { + char *name; + + bool h_sync_polarity; + bool v_sync_polarity; + bool interlaced; + + enum color_space color_space; + enum dynamic_range dynamic_range; + enum color_coefficient ycbcr_coeff; + enum color_depth color_depth; + + enum link_rate_type link_rate; + enum link_lane_count_type lane_count; +}; + +struct link_train { + int eq_loop; + int cr_loop[4]; + + u8 link_rate; + u8 lane_count; + u8 training_lane[4]; + + enum link_training_state lt_state; +}; + +struct exynos_dp_device { + struct device *dev; + struct drm_device *drm_dev; + struct drm_connector connector; + struct drm_encoder *encoder; + struct clk *clock; + unsigned int irq; + void __iomem *reg_base; + void __iomem *phy_addr; + unsigned int enable_mask; + + struct video_info *video_info; + struct link_train link_train; + struct work_struct hotplug_work; + struct phy *phy; + int dpms_mode; + int hpd_gpio; + + struct exynos_drm_panel_info panel; +}; + +/* exynos_dp_reg.c */ +void exynos_dp_enable_video_mute(struct exynos_dp_device *dp, bool enable); +void exynos_dp_stop_video(struct exynos_dp_device *dp); +void exynos_dp_lane_swap(struct exynos_dp_device *dp, bool enable); +void exynos_dp_init_analog_param(struct exynos_dp_device *dp); +void exynos_dp_init_interrupt(struct exynos_dp_device *dp); +void exynos_dp_reset(struct exynos_dp_device *dp); +void exynos_dp_swreset(struct exynos_dp_device *dp); +void exynos_dp_config_interrupt(struct exynos_dp_device *dp); +enum pll_status exynos_dp_get_pll_lock_status(struct exynos_dp_device *dp); +void exynos_dp_set_pll_power_down(struct exynos_dp_device *dp, bool enable); +void exynos_dp_set_analog_power_down(struct exynos_dp_device *dp, + enum analog_power_block block, + bool enable); +void exynos_dp_init_analog_func(struct exynos_dp_device *dp); +void exynos_dp_init_hpd(struct exynos_dp_device *dp); +enum dp_irq_type exynos_dp_get_irq_type(struct exynos_dp_device *dp); +void exynos_dp_clear_hotplug_interrupts(struct exynos_dp_device *dp); +void exynos_dp_reset_aux(struct exynos_dp_device *dp); +void exynos_dp_init_aux(struct exynos_dp_device *dp); +int exynos_dp_get_plug_in_status(struct exynos_dp_device *dp); +void exynos_dp_enable_sw_function(struct exynos_dp_device *dp); +int exynos_dp_start_aux_transaction(struct exynos_dp_device *dp); +int exynos_dp_write_byte_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char data); +int exynos_dp_read_byte_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char *data); +int exynos_dp_write_bytes_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]); +int exynos_dp_read_bytes_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]); +int exynos_dp_select_i2c_device(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr); +int exynos_dp_read_byte_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int *data); +int exynos_dp_read_bytes_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int count, + unsigned char edid[]); +void exynos_dp_set_link_bandwidth(struct exynos_dp_device *dp, u32 bwtype); +void exynos_dp_get_link_bandwidth(struct exynos_dp_device *dp, u32 *bwtype); +void exynos_dp_set_lane_count(struct exynos_dp_device *dp, u32 count); +void exynos_dp_get_lane_count(struct exynos_dp_device *dp, u32 *count); +void exynos_dp_enable_enhanced_mode(struct exynos_dp_device *dp, bool enable); +void exynos_dp_set_training_pattern(struct exynos_dp_device *dp, + enum pattern_set pattern); +void exynos_dp_set_lane0_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane1_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane2_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane3_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane0_link_training(struct exynos_dp_device *dp, + u32 training_lane); +void exynos_dp_set_lane1_link_training(struct exynos_dp_device *dp, + u32 training_lane); +void exynos_dp_set_lane2_link_training(struct exynos_dp_device *dp, + u32 training_lane); +void exynos_dp_set_lane3_link_training(struct exynos_dp_device *dp, + u32 training_lane); +u32 exynos_dp_get_lane0_link_training(struct exynos_dp_device *dp); +u32 exynos_dp_get_lane1_link_training(struct exynos_dp_device *dp); +u32 exynos_dp_get_lane2_link_training(struct exynos_dp_device *dp); +u32 exynos_dp_get_lane3_link_training(struct exynos_dp_device *dp); +void exynos_dp_reset_macro(struct exynos_dp_device *dp); +void exynos_dp_init_video(struct exynos_dp_device *dp); + +void exynos_dp_set_video_color_format(struct exynos_dp_device *dp); +int exynos_dp_is_slave_video_stream_clock_on(struct exynos_dp_device *dp); +void exynos_dp_set_video_cr_mn(struct exynos_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value); +void exynos_dp_set_video_timing_mode(struct exynos_dp_device *dp, u32 type); +void exynos_dp_enable_video_master(struct exynos_dp_device *dp, bool enable); +void exynos_dp_start_video(struct exynos_dp_device *dp); +int exynos_dp_is_video_stream_on(struct exynos_dp_device *dp); +void exynos_dp_config_video_slave_mode(struct exynos_dp_device *dp); +void exynos_dp_enable_scrambling(struct exynos_dp_device *dp); +void exynos_dp_disable_scrambling(struct exynos_dp_device *dp); + +/* I2C EDID Chip ID, Slave Address */ +#define I2C_EDID_DEVICE_ADDR 0x50 +#define I2C_E_EDID_DEVICE_ADDR 0x30 + +#define EDID_BLOCK_LENGTH 0x80 +#define EDID_HEADER_PATTERN 0x00 +#define EDID_EXTENSION_FLAG 0x7e +#define EDID_CHECKSUM 0x7f + +/* DP_MAX_LANE_COUNT */ +#define DPCD_ENHANCED_FRAME_CAP(x) (((x) >> 7) & 0x1) +#define DPCD_MAX_LANE_COUNT(x) ((x) & 0x1f) + +/* DP_LANE_COUNT_SET */ +#define DPCD_LANE_COUNT_SET(x) ((x) & 0x1f) + +/* DP_TRAINING_LANE0_SET */ +#define DPCD_PRE_EMPHASIS_SET(x) (((x) & 0x3) << 3) +#define DPCD_PRE_EMPHASIS_GET(x) (((x) >> 3) & 0x3) +#define DPCD_VOLTAGE_SWING_SET(x) (((x) & 0x3) << 0) +#define DPCD_VOLTAGE_SWING_GET(x) (((x) >> 0) & 0x3) + +#endif /* _EXYNOS_DP_CORE_H */ diff --git a/drivers/gpu/drm/exynos/exynos_dp_reg.c b/drivers/gpu/drm/exynos/exynos_dp_reg.c new file mode 100644 index 00000000000..c1f87a2a928 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_dp_reg.c @@ -0,0 +1,1263 @@ +/* + * Samsung DP (Display port) register interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/gpio.h> + +#include "exynos_dp_core.h" +#include "exynos_dp_reg.h" + +#define COMMON_INT_MASK_1 0 +#define COMMON_INT_MASK_2 0 +#define COMMON_INT_MASK_3 0 +#define COMMON_INT_MASK_4 (HOTPLUG_CHG | HPD_LOST | PLUG) +#define INT_STA_MASK INT_HPD + +void exynos_dp_enable_video_mute(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg |= HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg &= ~HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + } +} + +void exynos_dp_stop_video(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg &= ~VIDEO_EN; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); +} + +void exynos_dp_lane_swap(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) + reg = LANE3_MAP_LOGIC_LANE_0 | LANE2_MAP_LOGIC_LANE_1 | + LANE1_MAP_LOGIC_LANE_2 | LANE0_MAP_LOGIC_LANE_3; + else + reg = LANE3_MAP_LOGIC_LANE_3 | LANE2_MAP_LOGIC_LANE_2 | + LANE1_MAP_LOGIC_LANE_1 | LANE0_MAP_LOGIC_LANE_0; + + writel(reg, dp->reg_base + EXYNOS_DP_LANE_MAP); +} + +void exynos_dp_init_analog_param(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = TX_TERMINAL_CTRL_50_OHM; + writel(reg, dp->reg_base + EXYNOS_DP_ANALOG_CTL_1); + + reg = SEL_24M | TX_DVDD_BIT_1_0625V; + writel(reg, dp->reg_base + EXYNOS_DP_ANALOG_CTL_2); + + reg = DRIVE_DVDD_BIT_1_0625V | VCO_BIT_600_MICRO; + writel(reg, dp->reg_base + EXYNOS_DP_ANALOG_CTL_3); + + reg = PD_RING_OSC | AUX_TERMINAL_CTRL_50_OHM | + TX_CUR1_2X | TX_CUR_16_MA; + writel(reg, dp->reg_base + EXYNOS_DP_PLL_FILTER_CTL_1); + + reg = CH3_AMP_400_MV | CH2_AMP_400_MV | + CH1_AMP_400_MV | CH0_AMP_400_MV; + writel(reg, dp->reg_base + EXYNOS_DP_TX_AMP_TUNING_CTL); +} + +void exynos_dp_init_interrupt(struct exynos_dp_device *dp) +{ + /* Set interrupt pin assertion polarity as high */ + writel(INT_POL1 | INT_POL0, dp->reg_base + EXYNOS_DP_INT_CTL); + + /* Clear pending regisers */ + writel(0xff, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); + writel(0x4f, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_2); + writel(0xe0, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_3); + writel(0xe7, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); + writel(0x63, dp->reg_base + EXYNOS_DP_INT_STA); + + /* 0:mask,1: unmask */ + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_1); + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_2); + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_3); + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_4); + writel(0x00, dp->reg_base + EXYNOS_DP_INT_STA_MASK); +} + +void exynos_dp_reset(struct exynos_dp_device *dp) +{ + u32 reg; + + exynos_dp_stop_video(dp); + exynos_dp_enable_video_mute(dp, 0); + + reg = MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N | + AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N | + HDCP_FUNC_EN_N | SW_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); + + reg = SSC_FUNC_EN_N | AUX_FUNC_EN_N | + SERDES_FIFO_FUNC_EN_N | + LS_CLK_DOMAIN_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); + + usleep_range(20, 30); + + exynos_dp_lane_swap(dp, 0); + + writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_1); + writel(0x40, dp->reg_base + EXYNOS_DP_SYS_CTL_2); + writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_3); + writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + + writel(0x0, dp->reg_base + EXYNOS_DP_PKT_SEND_CTL); + writel(0x0, dp->reg_base + EXYNOS_DP_HDCP_CTL); + + writel(0x5e, dp->reg_base + EXYNOS_DP_HPD_DEGLITCH_L); + writel(0x1a, dp->reg_base + EXYNOS_DP_HPD_DEGLITCH_H); + + writel(0x10, dp->reg_base + EXYNOS_DP_LINK_DEBUG_CTL); + + writel(0x0, dp->reg_base + EXYNOS_DP_PHY_TEST); + + writel(0x0, dp->reg_base + EXYNOS_DP_VIDEO_FIFO_THRD); + writel(0x20, dp->reg_base + EXYNOS_DP_AUDIO_MARGIN); + + writel(0x4, dp->reg_base + EXYNOS_DP_M_VID_GEN_FILTER_TH); + writel(0x2, dp->reg_base + EXYNOS_DP_M_AUD_GEN_FILTER_TH); + + writel(0x00000101, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); +} + +void exynos_dp_swreset(struct exynos_dp_device *dp) +{ + writel(RESET_DP_TX, dp->reg_base + EXYNOS_DP_TX_SW_RESET); +} + +void exynos_dp_config_interrupt(struct exynos_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_1; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_1); + + reg = COMMON_INT_MASK_2; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_2); + + reg = COMMON_INT_MASK_3; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_3); + + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + EXYNOS_DP_INT_STA_MASK); +} + +enum pll_status exynos_dp_get_pll_lock_status(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_DEBUG_CTL); + if (reg & PLL_LOCK) + return PLL_LOCKED; + else + return PLL_UNLOCKED; +} + +void exynos_dp_set_pll_power_down(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PLL_CTL); + reg |= DP_PLL_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PLL_CTL); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PLL_CTL); + reg &= ~DP_PLL_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PLL_CTL); + } +} + +void exynos_dp_set_analog_power_down(struct exynos_dp_device *dp, + enum analog_power_block block, + bool enable) +{ + u32 reg; + + switch (block) { + case AUX_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= AUX_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~AUX_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH0_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH0_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH0_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH1_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH1_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH1_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH2_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH2_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH2_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH3_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH3_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH3_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case ANALOG_TOTAL: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= DP_PHY_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~DP_PHY_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case POWER_ALL: + if (enable) { + reg = DP_PHY_PD | AUX_PD | CH3_PD | CH2_PD | + CH1_PD | CH0_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + writel(0x00, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + default: + break; + } +} + +void exynos_dp_init_analog_func(struct exynos_dp_device *dp) +{ + u32 reg; + int timeout_loop = 0; + + exynos_dp_set_analog_power_down(dp, POWER_ALL, 0); + + reg = PLL_LOCK_CHG; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); + + reg = readl(dp->reg_base + EXYNOS_DP_DEBUG_CTL); + reg &= ~(F_PLL_LOCK | PLL_LOCK_CTRL); + writel(reg, dp->reg_base + EXYNOS_DP_DEBUG_CTL); + + /* Power up PLL */ + if (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + exynos_dp_set_pll_power_down(dp, 0); + + while (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "failed to get pll lock status\n"); + return; + } + usleep_range(10, 20); + } + } + + /* Enable Serdes FIFO function and Link symbol clock domain module */ + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); + reg &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N + | AUX_FUNC_EN_N); + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); +} + +void exynos_dp_clear_hotplug_interrupts(struct exynos_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) + return; + + reg = HOTPLUG_CHG | HPD_LOST | PLUG; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); + + reg = INT_HPD; + writel(reg, dp->reg_base + EXYNOS_DP_INT_STA); +} + +void exynos_dp_init_hpd(struct exynos_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) + return; + + exynos_dp_clear_hotplug_interrupts(dp); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + reg &= ~(F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); +} + +enum dp_irq_type exynos_dp_get_irq_type(struct exynos_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) { + reg = gpio_get_value(dp->hpd_gpio); + if (reg) + return DP_IRQ_TYPE_HP_CABLE_IN; + else + return DP_IRQ_TYPE_HP_CABLE_OUT; + } else { + /* Parse hotplug interrupt status register */ + reg = readl(dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); + + if (reg & PLUG) + return DP_IRQ_TYPE_HP_CABLE_IN; + + if (reg & HPD_LOST) + return DP_IRQ_TYPE_HP_CABLE_OUT; + + if (reg & HOTPLUG_CHG) + return DP_IRQ_TYPE_HP_CHANGE; + + return DP_IRQ_TYPE_UNKNOWN; + } +} + +void exynos_dp_reset_aux(struct exynos_dp_device *dp) +{ + u32 reg; + + /* Disable AUX channel module */ + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); + reg |= AUX_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); +} + +void exynos_dp_init_aux(struct exynos_dp_device *dp) +{ + u32 reg; + + /* Clear inerrupts related to AUX channel */ + reg = RPLY_RECEIV | AUX_ERR; + writel(reg, dp->reg_base + EXYNOS_DP_INT_STA); + + exynos_dp_reset_aux(dp); + + /* Disable AUX transaction H/W retry */ + reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3) | AUX_HW_RETRY_COUNT_SEL(0)| + AUX_HW_RETRY_INTERVAL_600_MICROSECONDS; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_HW_RETRY_CTL); + + /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ + reg = DEFER_CTRL_EN | DEFER_COUNT(1); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_DEFER_CTL); + + /* Enable AUX channel module */ + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); + reg &= ~AUX_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); +} + +int exynos_dp_get_plug_in_status(struct exynos_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) { + if (gpio_get_value(dp->hpd_gpio)) + return 0; + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + if (reg & HPD_STATUS) + return 0; + } + + return -EINVAL; +} + +void exynos_dp_enable_sw_function(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_1); + reg &= ~SW_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); +} + +int exynos_dp_start_aux_transaction(struct exynos_dp_device *dp) +{ + int reg; + int retval = 0; + int timeout_loop = 0; + + /* Enable AUX CH operation */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + reg |= AUX_EN; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + + /* Is AUX CH command reply received? */ + reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); + while (!(reg & RPLY_RECEIV)) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "AUX CH command reply failed!\n"); + return -ETIMEDOUT; + } + reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); + usleep_range(10, 11); + } + + /* Clear interrupt source for AUX CH command reply */ + writel(RPLY_RECEIV, dp->reg_base + EXYNOS_DP_INT_STA); + + /* Clear interrupt source for AUX CH access error */ + reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); + if (reg & AUX_ERR) { + writel(AUX_ERR, dp->reg_base + EXYNOS_DP_INT_STA); + return -EREMOTEIO; + } + + /* Check AUX CH error access status */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_STA); + if ((reg & AUX_STATUS_MASK) != 0) { + dev_err(dp->dev, "AUX CH error happens: %d\n\n", + reg & AUX_STATUS_MASK); + return -EREMOTEIO; + } + + return retval; +} + +int exynos_dp_write_byte_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* Write data buffer */ + reg = (unsigned int)data; + writel(reg, dp->reg_base + EXYNOS_DP_BUF_DATA_0); + + /* + * Set DisplayPort transaction and write 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + + return retval; +} + +int exynos_dp_read_byte_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char *data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* + * Set DisplayPort transaction and read 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + + /* Read data buffer */ + reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0); + *data = (unsigned char)(reg & 0xff); + + return retval; +} + +int exynos_dp_write_bytes_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]) +{ + u32 reg; + unsigned int start_offset; + unsigned int cur_data_count; + unsigned int cur_data_idx; + int i; + int retval = 0; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + start_offset = 0; + while (start_offset < count) { + /* Buffer size of AUX CH is 16 * 4bytes */ + if ((count - start_offset) > 16) + cur_data_count = 16; + else + cur_data_count = count - start_offset; + + for (i = 0; i < 3; i++) { + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + for (cur_data_idx = 0; cur_data_idx < cur_data_count; + cur_data_idx++) { + reg = data[start_offset + cur_data_idx]; + writel(reg, dp->reg_base + EXYNOS_DP_BUF_DATA_0 + + 4 * cur_data_idx); + } + + /* + * Set DisplayPort transaction and write + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(cur_data_count) | + AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + + start_offset += cur_data_count; + } + + return retval; +} + +int exynos_dp_read_bytes_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]) +{ + u32 reg; + unsigned int start_offset; + unsigned int cur_data_count; + unsigned int cur_data_idx; + int i; + int retval = 0; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + start_offset = 0; + while (start_offset < count) { + /* Buffer size of AUX CH is 16 * 4bytes */ + if ((count - start_offset) > 16) + cur_data_count = 16; + else + cur_data_count = count - start_offset; + + /* AUX CH Request Transaction process */ + for (i = 0; i < 3; i++) { + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* + * Set DisplayPort transaction and read + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(cur_data_count) | + AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + + for (cur_data_idx = 0; cur_data_idx < cur_data_count; + cur_data_idx++) { + reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0 + + 4 * cur_data_idx); + data[start_offset + cur_data_idx] = + (unsigned char)reg; + } + + start_offset += cur_data_count; + } + + return retval; +} + +int exynos_dp_select_i2c_device(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr) +{ + u32 reg; + int retval; + + /* Set EDID device address */ + reg = device_addr; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + writel(0x0, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + writel(0x0, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* Set offset from base address of EDID device */ + writel(reg_addr, dp->reg_base + EXYNOS_DP_BUF_DATA_0); + + /* + * Set I2C transaction and write address + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_I2C_TRANSACTION | AUX_TX_COMM_MOT | + AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval != 0) + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", __func__); + + return retval; +} + +int exynos_dp_read_byte_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int *data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Select EDID device */ + retval = exynos_dp_select_i2c_device(dp, device_addr, reg_addr); + if (retval != 0) + continue; + + /* + * Set I2C transaction and read data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + + /* Read data */ + if (retval == 0) + *data = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0); + + return retval; +} + +int exynos_dp_read_bytes_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int count, + unsigned char edid[]) +{ + u32 reg; + unsigned int i, j; + unsigned int cur_data_idx; + unsigned int defer = 0; + int retval = 0; + + for (i = 0; i < count; i += 16) { + for (j = 0; j < 3; j++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Set normal AUX CH command */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + reg &= ~ADDR_ONLY; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + + /* + * If Rx sends defer, Tx sends only reads + * request without sending address + */ + if (!defer) + retval = exynos_dp_select_i2c_device(dp, + device_addr, reg_addr + i); + else + defer = 0; + + if (retval == 0) { + /* + * Set I2C transaction and write data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(16) | + AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ; + writel(reg, dp->reg_base + + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_dbg(dp->dev, + "%s: Aux Transaction fail!\n", + __func__); + } + /* Check if Rx sends defer */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_RX_COMM); + if (reg == AUX_RX_COMM_AUX_DEFER || + reg == AUX_RX_COMM_I2C_DEFER) { + dev_err(dp->dev, "Defer: %d\n\n", reg); + defer = 1; + } + } + + for (cur_data_idx = 0; cur_data_idx < 16; cur_data_idx++) { + reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0 + + 4 * cur_data_idx); + edid[i + cur_data_idx] = (unsigned char)reg; + } + } + + return retval; +} + +void exynos_dp_set_link_bandwidth(struct exynos_dp_device *dp, u32 bwtype) +{ + u32 reg; + + reg = bwtype; + if ((bwtype == LINK_RATE_2_70GBPS) || (bwtype == LINK_RATE_1_62GBPS)) + writel(reg, dp->reg_base + EXYNOS_DP_LINK_BW_SET); +} + +void exynos_dp_get_link_bandwidth(struct exynos_dp_device *dp, u32 *bwtype) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LINK_BW_SET); + *bwtype = reg; +} + +void exynos_dp_set_lane_count(struct exynos_dp_device *dp, u32 count) +{ + u32 reg; + + reg = count; + writel(reg, dp->reg_base + EXYNOS_DP_LANE_COUNT_SET); +} + +void exynos_dp_get_lane_count(struct exynos_dp_device *dp, u32 *count) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LANE_COUNT_SET); + *count = reg; +} + +void exynos_dp_enable_enhanced_mode(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg |= ENHANCED; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg &= ~ENHANCED; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + } +} + +void exynos_dp_set_training_pattern(struct exynos_dp_device *dp, + enum pattern_set pattern) +{ + u32 reg; + + switch (pattern) { + case PRBS7: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_PRBS7; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case D10_2: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_D10_2; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN1: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN2: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case DP_NONE: + reg = SCRAMBLING_ENABLE | + LINK_QUAL_PATTERN_SET_DISABLE | + SW_TRAINING_PATTERN_SET_NORMAL; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + default: + break; + } +} + +void exynos_dp_set_lane0_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane1_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane2_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane3_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane0_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane1_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane2_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane3_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); +} + +u32 exynos_dp_get_lane0_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); + return reg; +} + +u32 exynos_dp_get_lane1_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); + return reg; +} + +u32 exynos_dp_get_lane2_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); + return reg; +} + +u32 exynos_dp_get_lane3_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); + return reg; +} + +void exynos_dp_reset_macro(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_PHY_TEST); + reg |= MACRO_RST; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_TEST); + + /* 10 us is the minimum reset time. */ + usleep_range(10, 20); + + reg &= ~MACRO_RST; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_TEST); +} + +void exynos_dp_init_video(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); + + reg = 0x0; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_1); + + reg = CHA_CRI(4) | CHA_CTRL; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_2); + + reg = 0x0; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); + + reg = VID_HRES_TH(2) | VID_VRES_TH(0); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_8); +} + +void exynos_dp_set_video_color_format(struct exynos_dp_device *dp) +{ + u32 reg; + + /* Configure the input color depth, color space, dynamic range */ + reg = (dp->video_info->dynamic_range << IN_D_RANGE_SHIFT) | + (dp->video_info->color_depth << IN_BPC_SHIFT) | + (dp->video_info->color_space << IN_COLOR_F_SHIFT); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_2); + + /* Set Input Color YCbCr Coefficients to ITU601 or ITU709 */ + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_3); + reg &= ~IN_YC_COEFFI_MASK; + if (dp->video_info->ycbcr_coeff) + reg |= IN_YC_COEFFI_ITU709; + else + reg |= IN_YC_COEFFI_ITU601; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_3); +} + +int exynos_dp_is_slave_video_stream_clock_on(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_1); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_1); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_1); + + if (!(reg & DET_STA)) { + dev_dbg(dp->dev, "Input stream clock not detected.\n"); + return -EINVAL; + } + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_2); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_2); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_2); + dev_dbg(dp->dev, "wait SYS_CTL_2.\n"); + + if (reg & CHA_STA) { + dev_dbg(dp->dev, "Input stream clk is changing\n"); + return -EINVAL; + } + + return 0; +} + +void exynos_dp_set_video_cr_mn(struct exynos_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value) +{ + u32 reg; + + if (type == REGISTER_M) { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg |= FIX_M_VID; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg = m_value & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_M_VID_0); + reg = (m_value >> 8) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_M_VID_1); + reg = (m_value >> 16) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_M_VID_2); + + reg = n_value & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_N_VID_0); + reg = (n_value >> 8) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_N_VID_1); + reg = (n_value >> 16) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_N_VID_2); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg &= ~FIX_M_VID; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + + writel(0x00, dp->reg_base + EXYNOS_DP_N_VID_0); + writel(0x80, dp->reg_base + EXYNOS_DP_N_VID_1); + writel(0x00, dp->reg_base + EXYNOS_DP_N_VID_2); + } +} + +void exynos_dp_set_video_timing_mode(struct exynos_dp_device *dp, u32 type) +{ + u32 reg; + + if (type == VIDEO_TIMING_FROM_CAPTURE) { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~FORMAT_SEL; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg |= FORMAT_SEL; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + } +} + +void exynos_dp_enable_video_master(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MASTER_MODE_EN | VIDEO_MODE_MASTER_MODE; + writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + } +} + +void exynos_dp_start_video(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg |= VIDEO_EN; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); +} + +int exynos_dp_is_video_stream_on(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + if (!(reg & STRM_VALID)) { + dev_dbg(dp->dev, "Input video stream is not detected.\n"); + return -EINVAL; + } + + return 0; +} + +void exynos_dp_config_video_slave_mode(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_1); + reg &= ~(MASTER_VID_FUNC_EN_N|SLAVE_VID_FUNC_EN_N); + reg |= MASTER_VID_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~INTERACE_SCAN_CFG; + reg |= (dp->video_info->interlaced << 2); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~VSYNC_POLARITY_CFG; + reg |= (dp->video_info->v_sync_polarity << 1); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~HSYNC_POLARITY_CFG; + reg |= (dp->video_info->h_sync_polarity << 0); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + + reg = AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); +} + +void exynos_dp_enable_scrambling(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + reg &= ~SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); +} + +void exynos_dp_disable_scrambling(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + reg |= SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); +} diff --git a/drivers/gpu/drm/exynos/exynos_dp_reg.h b/drivers/gpu/drm/exynos/exynos_dp_reg.h new file mode 100644 index 00000000000..2e9bd0e0b9f --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_dp_reg.h @@ -0,0 +1,366 @@ +/* + * Register definition file for Samsung DP driver + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * 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. + */ + +#ifndef _EXYNOS_DP_REG_H +#define _EXYNOS_DP_REG_H + +#define EXYNOS_DP_TX_SW_RESET 0x14 +#define EXYNOS_DP_FUNC_EN_1 0x18 +#define EXYNOS_DP_FUNC_EN_2 0x1C +#define EXYNOS_DP_VIDEO_CTL_1 0x20 +#define EXYNOS_DP_VIDEO_CTL_2 0x24 +#define EXYNOS_DP_VIDEO_CTL_3 0x28 + +#define EXYNOS_DP_VIDEO_CTL_8 0x3C +#define EXYNOS_DP_VIDEO_CTL_10 0x44 + +#define EXYNOS_DP_LANE_MAP 0x35C + +#define EXYNOS_DP_ANALOG_CTL_1 0x370 +#define EXYNOS_DP_ANALOG_CTL_2 0x374 +#define EXYNOS_DP_ANALOG_CTL_3 0x378 +#define EXYNOS_DP_PLL_FILTER_CTL_1 0x37C +#define EXYNOS_DP_TX_AMP_TUNING_CTL 0x380 + +#define EXYNOS_DP_AUX_HW_RETRY_CTL 0x390 + +#define EXYNOS_DP_COMMON_INT_STA_1 0x3C4 +#define EXYNOS_DP_COMMON_INT_STA_2 0x3C8 +#define EXYNOS_DP_COMMON_INT_STA_3 0x3CC +#define EXYNOS_DP_COMMON_INT_STA_4 0x3D0 +#define EXYNOS_DP_INT_STA 0x3DC +#define EXYNOS_DP_COMMON_INT_MASK_1 0x3E0 +#define EXYNOS_DP_COMMON_INT_MASK_2 0x3E4 +#define EXYNOS_DP_COMMON_INT_MASK_3 0x3E8 +#define EXYNOS_DP_COMMON_INT_MASK_4 0x3EC +#define EXYNOS_DP_INT_STA_MASK 0x3F8 +#define EXYNOS_DP_INT_CTL 0x3FC + +#define EXYNOS_DP_SYS_CTL_1 0x600 +#define EXYNOS_DP_SYS_CTL_2 0x604 +#define EXYNOS_DP_SYS_CTL_3 0x608 +#define EXYNOS_DP_SYS_CTL_4 0x60C + +#define EXYNOS_DP_PKT_SEND_CTL 0x640 +#define EXYNOS_DP_HDCP_CTL 0x648 + +#define EXYNOS_DP_LINK_BW_SET 0x680 +#define EXYNOS_DP_LANE_COUNT_SET 0x684 +#define EXYNOS_DP_TRAINING_PTN_SET 0x688 +#define EXYNOS_DP_LN0_LINK_TRAINING_CTL 0x68C +#define EXYNOS_DP_LN1_LINK_TRAINING_CTL 0x690 +#define EXYNOS_DP_LN2_LINK_TRAINING_CTL 0x694 +#define EXYNOS_DP_LN3_LINK_TRAINING_CTL 0x698 + +#define EXYNOS_DP_DEBUG_CTL 0x6C0 +#define EXYNOS_DP_HPD_DEGLITCH_L 0x6C4 +#define EXYNOS_DP_HPD_DEGLITCH_H 0x6C8 +#define EXYNOS_DP_LINK_DEBUG_CTL 0x6E0 + +#define EXYNOS_DP_M_VID_0 0x700 +#define EXYNOS_DP_M_VID_1 0x704 +#define EXYNOS_DP_M_VID_2 0x708 +#define EXYNOS_DP_N_VID_0 0x70C +#define EXYNOS_DP_N_VID_1 0x710 +#define EXYNOS_DP_N_VID_2 0x714 + +#define EXYNOS_DP_PLL_CTL 0x71C +#define EXYNOS_DP_PHY_PD 0x720 +#define EXYNOS_DP_PHY_TEST 0x724 + +#define EXYNOS_DP_VIDEO_FIFO_THRD 0x730 +#define EXYNOS_DP_AUDIO_MARGIN 0x73C + +#define EXYNOS_DP_M_VID_GEN_FILTER_TH 0x764 +#define EXYNOS_DP_M_AUD_GEN_FILTER_TH 0x778 +#define EXYNOS_DP_AUX_CH_STA 0x780 +#define EXYNOS_DP_AUX_CH_DEFER_CTL 0x788 +#define EXYNOS_DP_AUX_RX_COMM 0x78C +#define EXYNOS_DP_BUFFER_DATA_CTL 0x790 +#define EXYNOS_DP_AUX_CH_CTL_1 0x794 +#define EXYNOS_DP_AUX_ADDR_7_0 0x798 +#define EXYNOS_DP_AUX_ADDR_15_8 0x79C +#define EXYNOS_DP_AUX_ADDR_19_16 0x7A0 +#define EXYNOS_DP_AUX_CH_CTL_2 0x7A4 + +#define EXYNOS_DP_BUF_DATA_0 0x7C0 + +#define EXYNOS_DP_SOC_GENERAL_CTL 0x800 + +/* EXYNOS_DP_TX_SW_RESET */ +#define RESET_DP_TX (0x1 << 0) + +/* EXYNOS_DP_FUNC_EN_1 */ +#define MASTER_VID_FUNC_EN_N (0x1 << 7) +#define SLAVE_VID_FUNC_EN_N (0x1 << 5) +#define AUD_FIFO_FUNC_EN_N (0x1 << 4) +#define AUD_FUNC_EN_N (0x1 << 3) +#define HDCP_FUNC_EN_N (0x1 << 2) +#define CRC_FUNC_EN_N (0x1 << 1) +#define SW_FUNC_EN_N (0x1 << 0) + +/* EXYNOS_DP_FUNC_EN_2 */ +#define SSC_FUNC_EN_N (0x1 << 7) +#define AUX_FUNC_EN_N (0x1 << 2) +#define SERDES_FIFO_FUNC_EN_N (0x1 << 1) +#define LS_CLK_DOMAIN_FUNC_EN_N (0x1 << 0) + +/* EXYNOS_DP_VIDEO_CTL_1 */ +#define VIDEO_EN (0x1 << 7) +#define HDCP_VIDEO_MUTE (0x1 << 6) + +/* EXYNOS_DP_VIDEO_CTL_1 */ +#define IN_D_RANGE_MASK (0x1 << 7) +#define IN_D_RANGE_SHIFT (7) +#define IN_D_RANGE_CEA (0x1 << 7) +#define IN_D_RANGE_VESA (0x0 << 7) +#define IN_BPC_MASK (0x7 << 4) +#define IN_BPC_SHIFT (4) +#define IN_BPC_12_BITS (0x3 << 4) +#define IN_BPC_10_BITS (0x2 << 4) +#define IN_BPC_8_BITS (0x1 << 4) +#define IN_BPC_6_BITS (0x0 << 4) +#define IN_COLOR_F_MASK (0x3 << 0) +#define IN_COLOR_F_SHIFT (0) +#define IN_COLOR_F_YCBCR444 (0x2 << 0) +#define IN_COLOR_F_YCBCR422 (0x1 << 0) +#define IN_COLOR_F_RGB (0x0 << 0) + +/* EXYNOS_DP_VIDEO_CTL_3 */ +#define IN_YC_COEFFI_MASK (0x1 << 7) +#define IN_YC_COEFFI_SHIFT (7) +#define IN_YC_COEFFI_ITU709 (0x1 << 7) +#define IN_YC_COEFFI_ITU601 (0x0 << 7) +#define VID_CHK_UPDATE_TYPE_MASK (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_SHIFT (4) +#define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) + +/* EXYNOS_DP_VIDEO_CTL_8 */ +#define VID_HRES_TH(x) (((x) & 0xf) << 4) +#define VID_VRES_TH(x) (((x) & 0xf) << 0) + +/* EXYNOS_DP_VIDEO_CTL_10 */ +#define FORMAT_SEL (0x1 << 4) +#define INTERACE_SCAN_CFG (0x1 << 2) +#define VSYNC_POLARITY_CFG (0x1 << 1) +#define HSYNC_POLARITY_CFG (0x1 << 0) + +/* EXYNOS_DP_LANE_MAP */ +#define LANE3_MAP_LOGIC_LANE_0 (0x0 << 6) +#define LANE3_MAP_LOGIC_LANE_1 (0x1 << 6) +#define LANE3_MAP_LOGIC_LANE_2 (0x2 << 6) +#define LANE3_MAP_LOGIC_LANE_3 (0x3 << 6) +#define LANE2_MAP_LOGIC_LANE_0 (0x0 << 4) +#define LANE2_MAP_LOGIC_LANE_1 (0x1 << 4) +#define LANE2_MAP_LOGIC_LANE_2 (0x2 << 4) +#define LANE2_MAP_LOGIC_LANE_3 (0x3 << 4) +#define LANE1_MAP_LOGIC_LANE_0 (0x0 << 2) +#define LANE1_MAP_LOGIC_LANE_1 (0x1 << 2) +#define LANE1_MAP_LOGIC_LANE_2 (0x2 << 2) +#define LANE1_MAP_LOGIC_LANE_3 (0x3 << 2) +#define LANE0_MAP_LOGIC_LANE_0 (0x0 << 0) +#define LANE0_MAP_LOGIC_LANE_1 (0x1 << 0) +#define LANE0_MAP_LOGIC_LANE_2 (0x2 << 0) +#define LANE0_MAP_LOGIC_LANE_3 (0x3 << 0) + +/* EXYNOS_DP_ANALOG_CTL_1 */ +#define TX_TERMINAL_CTRL_50_OHM (0x1 << 4) + +/* EXYNOS_DP_ANALOG_CTL_2 */ +#define SEL_24M (0x1 << 3) +#define TX_DVDD_BIT_1_0625V (0x4 << 0) + +/* EXYNOS_DP_ANALOG_CTL_3 */ +#define DRIVE_DVDD_BIT_1_0625V (0x4 << 5) +#define VCO_BIT_600_MICRO (0x5 << 0) + +/* EXYNOS_DP_PLL_FILTER_CTL_1 */ +#define PD_RING_OSC (0x1 << 6) +#define AUX_TERMINAL_CTRL_50_OHM (0x2 << 4) +#define TX_CUR1_2X (0x1 << 2) +#define TX_CUR_16_MA (0x3 << 0) + +/* EXYNOS_DP_TX_AMP_TUNING_CTL */ +#define CH3_AMP_400_MV (0x0 << 24) +#define CH2_AMP_400_MV (0x0 << 16) +#define CH1_AMP_400_MV (0x0 << 8) +#define CH0_AMP_400_MV (0x0 << 0) + +/* EXYNOS_DP_AUX_HW_RETRY_CTL */ +#define AUX_BIT_PERIOD_EXPECTED_DELAY(x) (((x) & 0x7) << 8) +#define AUX_HW_RETRY_INTERVAL_MASK (0x3 << 3) +#define AUX_HW_RETRY_INTERVAL_600_MICROSECONDS (0x0 << 3) +#define AUX_HW_RETRY_INTERVAL_800_MICROSECONDS (0x1 << 3) +#define AUX_HW_RETRY_INTERVAL_1000_MICROSECONDS (0x2 << 3) +#define AUX_HW_RETRY_INTERVAL_1800_MICROSECONDS (0x3 << 3) +#define AUX_HW_RETRY_COUNT_SEL(x) (((x) & 0x7) << 0) + +/* EXYNOS_DP_COMMON_INT_STA_1 */ +#define VSYNC_DET (0x1 << 7) +#define PLL_LOCK_CHG (0x1 << 6) +#define SPDIF_ERR (0x1 << 5) +#define SPDIF_UNSTBL (0x1 << 4) +#define VID_FORMAT_CHG (0x1 << 3) +#define AUD_CLK_CHG (0x1 << 2) +#define VID_CLK_CHG (0x1 << 1) +#define SW_INT (0x1 << 0) + +/* EXYNOS_DP_COMMON_INT_STA_2 */ +#define ENC_EN_CHG (0x1 << 6) +#define HW_BKSV_RDY (0x1 << 3) +#define HW_SHA_DONE (0x1 << 2) +#define HW_AUTH_STATE_CHG (0x1 << 1) +#define HW_AUTH_DONE (0x1 << 0) + +/* EXYNOS_DP_COMMON_INT_STA_3 */ +#define AFIFO_UNDER (0x1 << 7) +#define AFIFO_OVER (0x1 << 6) +#define R0_CHK_FLAG (0x1 << 5) + +/* EXYNOS_DP_COMMON_INT_STA_4 */ +#define PSR_ACTIVE (0x1 << 7) +#define PSR_INACTIVE (0x1 << 6) +#define SPDIF_BI_PHASE_ERR (0x1 << 5) +#define HOTPLUG_CHG (0x1 << 2) +#define HPD_LOST (0x1 << 1) +#define PLUG (0x1 << 0) + +/* EXYNOS_DP_INT_STA */ +#define INT_HPD (0x1 << 6) +#define HW_TRAINING_FINISH (0x1 << 5) +#define RPLY_RECEIV (0x1 << 1) +#define AUX_ERR (0x1 << 0) + +/* EXYNOS_DP_INT_CTL */ +#define SOFT_INT_CTRL (0x1 << 2) +#define INT_POL1 (0x1 << 1) +#define INT_POL0 (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_1 */ +#define DET_STA (0x1 << 2) +#define FORCE_DET (0x1 << 1) +#define DET_CTRL (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_2 */ +#define CHA_CRI(x) (((x) & 0xf) << 4) +#define CHA_STA (0x1 << 2) +#define FORCE_CHA (0x1 << 1) +#define CHA_CTRL (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_3 */ +#define HPD_STATUS (0x1 << 6) +#define F_HPD (0x1 << 5) +#define HPD_CTRL (0x1 << 4) +#define HDCP_RDY (0x1 << 3) +#define STRM_VALID (0x1 << 2) +#define F_VALID (0x1 << 1) +#define VALID_CTRL (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_4 */ +#define FIX_M_AUD (0x1 << 4) +#define ENHANCED (0x1 << 3) +#define FIX_M_VID (0x1 << 2) +#define M_VID_UPDATE_CTRL (0x3 << 0) + +/* EXYNOS_DP_TRAINING_PTN_SET */ +#define SCRAMBLER_TYPE (0x1 << 9) +#define HW_LINK_TRAINING_PATTERN (0x1 << 8) +#define SCRAMBLING_DISABLE (0x1 << 5) +#define SCRAMBLING_ENABLE (0x0 << 5) +#define LINK_QUAL_PATTERN_SET_MASK (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_PRBS7 (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_D10_2 (0x1 << 2) +#define LINK_QUAL_PATTERN_SET_DISABLE (0x0 << 2) +#define SW_TRAINING_PATTERN_SET_MASK (0x3 << 0) +#define SW_TRAINING_PATTERN_SET_PTN2 (0x2 << 0) +#define SW_TRAINING_PATTERN_SET_PTN1 (0x1 << 0) +#define SW_TRAINING_PATTERN_SET_NORMAL (0x0 << 0) + +/* EXYNOS_DP_LN0_LINK_TRAINING_CTL */ +#define PRE_EMPHASIS_SET_MASK (0x3 << 3) +#define PRE_EMPHASIS_SET_SHIFT (3) + +/* EXYNOS_DP_DEBUG_CTL */ +#define PLL_LOCK (0x1 << 4) +#define F_PLL_LOCK (0x1 << 3) +#define PLL_LOCK_CTRL (0x1 << 2) +#define PN_INV (0x1 << 0) + +/* EXYNOS_DP_PLL_CTL */ +#define DP_PLL_PD (0x1 << 7) +#define DP_PLL_RESET (0x1 << 6) +#define DP_PLL_LOOP_BIT_DEFAULT (0x1 << 4) +#define DP_PLL_REF_BIT_1_1250V (0x5 << 0) +#define DP_PLL_REF_BIT_1_2500V (0x7 << 0) + +/* EXYNOS_DP_PHY_PD */ +#define DP_PHY_PD (0x1 << 5) +#define AUX_PD (0x1 << 4) +#define CH3_PD (0x1 << 3) +#define CH2_PD (0x1 << 2) +#define CH1_PD (0x1 << 1) +#define CH0_PD (0x1 << 0) + +/* EXYNOS_DP_PHY_TEST */ +#define MACRO_RST (0x1 << 5) +#define CH1_TEST (0x1 << 1) +#define CH0_TEST (0x1 << 0) + +/* EXYNOS_DP_AUX_CH_STA */ +#define AUX_BUSY (0x1 << 4) +#define AUX_STATUS_MASK (0xf << 0) + +/* EXYNOS_DP_AUX_CH_DEFER_CTL */ +#define DEFER_CTRL_EN (0x1 << 7) +#define DEFER_COUNT(x) (((x) & 0x7f) << 0) + +/* EXYNOS_DP_AUX_RX_COMM */ +#define AUX_RX_COMM_I2C_DEFER (0x2 << 2) +#define AUX_RX_COMM_AUX_DEFER (0x2 << 0) + +/* EXYNOS_DP_BUFFER_DATA_CTL */ +#define BUF_CLR (0x1 << 7) +#define BUF_DATA_COUNT(x) (((x) & 0x1f) << 0) + +/* EXYNOS_DP_AUX_CH_CTL_1 */ +#define AUX_LENGTH(x) (((x - 1) & 0xf) << 4) +#define AUX_TX_COMM_MASK (0xf << 0) +#define AUX_TX_COMM_DP_TRANSACTION (0x1 << 3) +#define AUX_TX_COMM_I2C_TRANSACTION (0x0 << 3) +#define AUX_TX_COMM_MOT (0x1 << 2) +#define AUX_TX_COMM_WRITE (0x0 << 0) +#define AUX_TX_COMM_READ (0x1 << 0) + +/* EXYNOS_DP_AUX_ADDR_7_0 */ +#define AUX_ADDR_7_0(x) (((x) >> 0) & 0xff) + +/* EXYNOS_DP_AUX_ADDR_15_8 */ +#define AUX_ADDR_15_8(x) (((x) >> 8) & 0xff) + +/* EXYNOS_DP_AUX_ADDR_19_16 */ +#define AUX_ADDR_19_16(x) (((x) >> 16) & 0x0f) + +/* EXYNOS_DP_AUX_CH_CTL_2 */ +#define ADDR_ONLY (0x1 << 1) +#define AUX_EN (0x1 << 0) + +/* EXYNOS_DP_SOC_GENERAL_CTL */ +#define AUDIO_MODE_SPDIF_MODE (0x1 << 8) +#define AUDIO_MODE_MASTER_MODE (0x0 << 8) +#define MASTER_VIDEO_INTERLACE_EN (0x1 << 4) +#define VIDEO_MASTER_CLK_SEL (0x1 << 2) +#define VIDEO_MASTER_MODE_EN (0x1 << 1) +#define VIDEO_MODE_MASK (0x1 << 0) +#define VIDEO_MODE_SLAVE_MODE (0x1 << 0) +#define VIDEO_MODE_MASTER_MODE (0x0 << 0) + +#endif /* _EXYNOS_DP_REG_H */ diff --git a/drivers/gpu/drm/exynos/exynos_drm_buf.c b/drivers/gpu/drm/exynos/exynos_drm_buf.c index 3445a0f3a6b..9c8088462c2 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_buf.c +++ b/drivers/gpu/drm/exynos/exynos_drm_buf.c @@ -63,7 +63,8 @@ static int lowlevel_buffer_allocate(struct drm_device *dev, return -ENOMEM; } - buf->kvaddr = dma_alloc_attrs(dev->dev, buf->size, + buf->kvaddr = (void __iomem *)dma_alloc_attrs(dev->dev, + buf->size, &buf->dma_addr, GFP_KERNEL, &buf->dma_attrs); if (!buf->kvaddr) { @@ -90,9 +91,9 @@ static int lowlevel_buffer_allocate(struct drm_device *dev, } buf->sgt = drm_prime_pages_to_sg(buf->pages, nr_pages); - if (!buf->sgt) { + if (IS_ERR(buf->sgt)) { DRM_ERROR("failed to get sg table.\n"); - ret = -ENOMEM; + ret = PTR_ERR(buf->sgt); goto err_free_attrs; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_connector.c b/drivers/gpu/drm/exynos/exynos_drm_connector.c index e082efb2fec..9a16dbe121d 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_connector.c +++ b/drivers/gpu/drm/exynos/exynos_drm_connector.c @@ -23,27 +23,20 @@ drm_connector) struct exynos_drm_connector { - struct drm_connector drm_connector; - uint32_t encoder_id; - struct exynos_drm_manager *manager; - uint32_t dpms; + struct drm_connector drm_connector; + uint32_t encoder_id; + struct exynos_drm_display *display; }; static int exynos_drm_connector_get_modes(struct drm_connector *connector) { struct exynos_drm_connector *exynos_connector = to_exynos_connector(connector); - struct exynos_drm_manager *manager = exynos_connector->manager; - struct exynos_drm_display_ops *display_ops = manager->display_ops; + struct exynos_drm_display *display = exynos_connector->display; struct edid *edid = NULL; unsigned int count = 0; int ret; - if (!display_ops) { - DRM_DEBUG_KMS("display_ops is null.\n"); - return 0; - } - /* * if get_edid() exists then get_edid() callback of hdmi side * is called to get edid data through i2c interface else @@ -52,8 +45,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector) * P.S. in case of lcd panel, count is always 1 if success * because lcd panel has only one mode. */ - if (display_ops->get_edid) { - edid = display_ops->get_edid(manager->dev, connector); + if (display->ops->get_edid) { + edid = display->ops->get_edid(display, connector); if (IS_ERR_OR_NULL(edid)) { ret = PTR_ERR(edid); edid = NULL; @@ -76,8 +69,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector) return 0; } - if (display_ops->get_panel) - panel = display_ops->get_panel(manager->dev); + if (display->ops->get_panel) + panel = display->ops->get_panel(display); else { drm_mode_destroy(connector->dev, mode); return 0; @@ -106,20 +99,20 @@ static int exynos_drm_connector_mode_valid(struct drm_connector *connector, { struct exynos_drm_connector *exynos_connector = to_exynos_connector(connector); - struct exynos_drm_manager *manager = exynos_connector->manager; - struct exynos_drm_display_ops *display_ops = manager->display_ops; + struct exynos_drm_display *display = exynos_connector->display; int ret = MODE_BAD; DRM_DEBUG_KMS("%s\n", __FILE__); - if (display_ops && display_ops->check_mode) - if (!display_ops->check_mode(manager->dev, mode)) + if (display->ops->check_mode) + if (!display->ops->check_mode(display, mode)) ret = MODE_OK; return ret; } -struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector) +static struct drm_encoder *exynos_drm_best_encoder( + struct drm_connector *connector) { struct drm_device *dev = connector->dev; struct exynos_drm_connector *exynos_connector = @@ -146,48 +139,12 @@ static struct drm_connector_helper_funcs exynos_connector_helper_funcs = { .best_encoder = exynos_drm_best_encoder, }; -void exynos_drm_display_power(struct drm_connector *connector, int mode) -{ - struct drm_encoder *encoder = exynos_drm_best_encoder(connector); - struct exynos_drm_connector *exynos_connector; - struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); - struct exynos_drm_display_ops *display_ops = manager->display_ops; - - exynos_connector = to_exynos_connector(connector); - - if (exynos_connector->dpms == mode) { - DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n"); - return; - } - - if (display_ops && display_ops->power_on) - display_ops->power_on(manager->dev, mode); - - exynos_connector->dpms = mode; -} - -static void exynos_drm_connector_dpms(struct drm_connector *connector, - int mode) -{ - /* - * in case that drm_crtc_helper_set_mode() is called, - * encoder/crtc->funcs->dpms() will be just returned - * because they already were DRM_MODE_DPMS_ON so only - * exynos_drm_display_power() will be called. - */ - drm_helper_connector_dpms(connector, mode); - - exynos_drm_display_power(connector, mode); - -} - static int exynos_drm_connector_fill_modes(struct drm_connector *connector, unsigned int max_width, unsigned int max_height) { struct exynos_drm_connector *exynos_connector = to_exynos_connector(connector); - struct exynos_drm_manager *manager = exynos_connector->manager; - struct exynos_drm_manager_ops *ops = manager->ops; + struct exynos_drm_display *display = exynos_connector->display; unsigned int width, height; width = max_width; @@ -197,8 +154,8 @@ static int exynos_drm_connector_fill_modes(struct drm_connector *connector, * if specific driver want to find desired_mode using maxmum * resolution then get max width and height from that driver. */ - if (ops && ops->get_max_resol) - ops->get_max_resol(manager->dev, &width, &height); + if (display->ops->get_max_resol) + display->ops->get_max_resol(display, &width, &height); return drm_helper_probe_single_connector_modes(connector, width, height); @@ -210,13 +167,11 @@ exynos_drm_connector_detect(struct drm_connector *connector, bool force) { struct exynos_drm_connector *exynos_connector = to_exynos_connector(connector); - struct exynos_drm_manager *manager = exynos_connector->manager; - struct exynos_drm_display_ops *display_ops = - manager->display_ops; + struct exynos_drm_display *display = exynos_connector->display; enum drm_connector_status status = connector_status_disconnected; - if (display_ops && display_ops->is_connected) { - if (display_ops->is_connected(manager->dev)) + if (display->ops->is_connected) { + if (display->ops->is_connected(display)) status = connector_status_connected; else status = connector_status_disconnected; @@ -236,7 +191,7 @@ static void exynos_drm_connector_destroy(struct drm_connector *connector) } static struct drm_connector_funcs exynos_connector_funcs = { - .dpms = exynos_drm_connector_dpms, + .dpms = drm_helper_connector_dpms, .fill_modes = exynos_drm_connector_fill_modes, .detect = exynos_drm_connector_detect, .destroy = exynos_drm_connector_destroy, @@ -246,7 +201,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, struct drm_encoder *encoder) { struct exynos_drm_connector *exynos_connector; - struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); + struct exynos_drm_display *display = exynos_drm_get_display(encoder); struct drm_connector *connector; int type; int err; @@ -257,7 +212,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, connector = &exynos_connector->drm_connector; - switch (manager->display_ops->type) { + switch (display->type) { case EXYNOS_DISPLAY_TYPE_HDMI: type = DRM_MODE_CONNECTOR_HDMIA; connector->interlace_allowed = true; @@ -280,8 +235,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, goto err_connector; exynos_connector->encoder_id = encoder->base.id; - exynos_connector->manager = manager; - exynos_connector->dpms = DRM_MODE_DPMS_OFF; + exynos_connector->display = display; connector->dpms = DRM_MODE_DPMS_OFF; connector->encoder = encoder; diff --git a/drivers/gpu/drm/exynos/exynos_drm_connector.h b/drivers/gpu/drm/exynos/exynos_drm_connector.h index 547c6b59035..4eb20d78379 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_connector.h +++ b/drivers/gpu/drm/exynos/exynos_drm_connector.h @@ -17,8 +17,4 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev, struct drm_encoder *encoder); -struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector); - -void exynos_drm_display_power(struct drm_connector *connector, int mode); - #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_core.c b/drivers/gpu/drm/exynos/exynos_drm_core.c index 1bef6dc7747..4c9f972eaa0 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_core.c +++ b/drivers/gpu/drm/exynos/exynos_drm_core.c @@ -14,43 +14,40 @@ #include <drm/drmP.h> #include "exynos_drm_drv.h" +#include "exynos_drm_crtc.h" #include "exynos_drm_encoder.h" -#include "exynos_drm_connector.h" #include "exynos_drm_fbdev.h" static LIST_HEAD(exynos_drm_subdrv_list); -static int exynos_drm_create_enc_conn(struct drm_device *dev, - struct exynos_drm_subdrv *subdrv) +int exynos_drm_create_enc_conn(struct drm_device *dev, + struct exynos_drm_display *display) { struct drm_encoder *encoder; - struct drm_connector *connector; int ret; + unsigned long possible_crtcs = 0; - subdrv->manager->dev = subdrv->dev; + ret = exynos_drm_crtc_get_pipe_from_type(dev, display->type); + if (ret < 0) + return ret; + + possible_crtcs |= 1 << ret; /* create and initialize a encoder for this sub driver. */ - encoder = exynos_drm_encoder_create(dev, subdrv->manager, - (1 << MAX_CRTC) - 1); + encoder = exynos_drm_encoder_create(dev, display, possible_crtcs); if (!encoder) { DRM_ERROR("failed to create encoder\n"); return -EFAULT; } - /* - * create and initialize a connector for this sub driver and - * attach the encoder created above to the connector. - */ - connector = exynos_drm_connector_create(dev, encoder); - if (!connector) { - DRM_ERROR("failed to create connector\n"); - ret = -EFAULT; + display->encoder = encoder; + + ret = display->ops->create_connector(display, encoder); + if (ret) { + DRM_ERROR("failed to create connector ret = %d\n", ret); goto err_destroy_encoder; } - subdrv->encoder = encoder; - subdrv->connector = connector; - return 0; err_destroy_encoder: @@ -58,97 +55,59 @@ err_destroy_encoder: return ret; } -static void exynos_drm_destroy_enc_conn(struct exynos_drm_subdrv *subdrv) +int exynos_drm_subdrv_register(struct exynos_drm_subdrv *subdrv) { - if (subdrv->encoder) { - struct drm_encoder *encoder = subdrv->encoder; - encoder->funcs->destroy(encoder); - subdrv->encoder = NULL; - } - - if (subdrv->connector) { - struct drm_connector *connector = subdrv->connector; - connector->funcs->destroy(connector); - subdrv->connector = NULL; - } -} + if (!subdrv) + return -EINVAL; -static int exynos_drm_subdrv_probe(struct drm_device *dev, - struct exynos_drm_subdrv *subdrv) -{ - if (subdrv->probe) { - int ret; - - subdrv->drm_dev = dev; - - /* - * this probe callback would be called by sub driver - * after setting of all resources to this sub driver, - * such as clock, irq and register map are done or by load() - * of exynos drm driver. - * - * P.S. note that this driver is considered for modularization. - */ - ret = subdrv->probe(dev, subdrv->dev); - if (ret) - return ret; - } + list_add_tail(&subdrv->list, &exynos_drm_subdrv_list); return 0; } +EXPORT_SYMBOL_GPL(exynos_drm_subdrv_register); -static void exynos_drm_subdrv_remove(struct drm_device *dev, - struct exynos_drm_subdrv *subdrv) +int exynos_drm_subdrv_unregister(struct exynos_drm_subdrv *subdrv) { - if (subdrv->remove) - subdrv->remove(dev, subdrv->dev); + if (!subdrv) + return -EINVAL; + + list_del(&subdrv->list); + + return 0; } +EXPORT_SYMBOL_GPL(exynos_drm_subdrv_unregister); -int exynos_drm_device_register(struct drm_device *dev) +int exynos_drm_device_subdrv_probe(struct drm_device *dev) { struct exynos_drm_subdrv *subdrv, *n; - unsigned int fine_cnt = 0; int err; if (!dev) return -EINVAL; list_for_each_entry_safe(subdrv, n, &exynos_drm_subdrv_list, list) { - err = exynos_drm_subdrv_probe(dev, subdrv); - if (err) { - DRM_DEBUG("exynos drm subdrv probe failed.\n"); - list_del(&subdrv->list); - continue; - } - - /* - * if manager is null then it means that this sub driver - * doesn't need encoder and connector. - */ - if (!subdrv->manager) { - fine_cnt++; - continue; - } - - err = exynos_drm_create_enc_conn(dev, subdrv); - if (err) { - DRM_DEBUG("failed to create encoder and connector.\n"); - exynos_drm_subdrv_remove(dev, subdrv); - list_del(&subdrv->list); - continue; + if (subdrv->probe) { + subdrv->drm_dev = dev; + + /* + * this probe callback would be called by sub driver + * after setting of all resources to this sub driver, + * such as clock, irq and register map are done. + */ + err = subdrv->probe(dev, subdrv->dev); + if (err) { + DRM_DEBUG("exynos drm subdrv probe failed.\n"); + list_del(&subdrv->list); + continue; + } } - - fine_cnt++; } - if (!fine_cnt) - return -EINVAL; - return 0; } -EXPORT_SYMBOL_GPL(exynos_drm_device_register); +EXPORT_SYMBOL_GPL(exynos_drm_device_subdrv_probe); -int exynos_drm_device_unregister(struct drm_device *dev) +int exynos_drm_device_subdrv_remove(struct drm_device *dev) { struct exynos_drm_subdrv *subdrv; @@ -158,35 +117,13 @@ int exynos_drm_device_unregister(struct drm_device *dev) } list_for_each_entry(subdrv, &exynos_drm_subdrv_list, list) { - exynos_drm_subdrv_remove(dev, subdrv); - exynos_drm_destroy_enc_conn(subdrv); + if (subdrv->remove) + subdrv->remove(dev, subdrv->dev); } return 0; } -EXPORT_SYMBOL_GPL(exynos_drm_device_unregister); - -int exynos_drm_subdrv_register(struct exynos_drm_subdrv *subdrv) -{ - if (!subdrv) - return -EINVAL; - - list_add_tail(&subdrv->list, &exynos_drm_subdrv_list); - - return 0; -} -EXPORT_SYMBOL_GPL(exynos_drm_subdrv_register); - -int exynos_drm_subdrv_unregister(struct exynos_drm_subdrv *subdrv) -{ - if (!subdrv) - return -EINVAL; - - list_del(&subdrv->list); - - return 0; -} -EXPORT_SYMBOL_GPL(exynos_drm_subdrv_unregister); +EXPORT_SYMBOL_GPL(exynos_drm_device_subdrv_remove); int exynos_drm_subdrv_open(struct drm_device *dev, struct drm_file *file) { diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index ebc01503d50..95c9435d026 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -33,12 +33,13 @@ enum exynos_crtc_mode { * * @drm_crtc: crtc object. * @drm_plane: pointer of private plane object for this crtc + * @manager: the manager associated with this crtc * @pipe: a crtc index created at load() with a new crtc object creation * and the crtc object would be set to private->crtc array * to get a crtc object corresponding to this pipe from private->crtc - * array when irq interrupt occured. the reason of using this pipe is that + * array when irq interrupt occurred. the reason of using this pipe is that * drm framework doesn't support multiple irq yet. - * we can refer to the crtc to current hardware interrupt occured through + * we can refer to the crtc to current hardware interrupt occurred through * this pipe value. * @dpms: store the crtc dpms value * @mode: store the crtc mode value @@ -46,6 +47,7 @@ enum exynos_crtc_mode { struct exynos_drm_crtc { struct drm_crtc drm_crtc; struct drm_plane *plane; + struct exynos_drm_manager *manager; unsigned int pipe; unsigned int dpms; enum exynos_crtc_mode mode; @@ -56,6 +58,7 @@ struct exynos_drm_crtc { static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); + struct exynos_drm_manager *manager = exynos_crtc->manager; DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode); @@ -71,7 +74,9 @@ static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) drm_vblank_off(crtc->dev, exynos_crtc->pipe); } - exynos_drm_fn_encoder(crtc, &mode, exynos_drm_encoder_crtc_dpms); + if (manager->ops->dpms) + manager->ops->dpms(manager, mode); + exynos_crtc->dpms = mode; } @@ -83,9 +88,15 @@ static void exynos_drm_crtc_prepare(struct drm_crtc *crtc) static void exynos_drm_crtc_commit(struct drm_crtc *crtc) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); + struct exynos_drm_manager *manager = exynos_crtc->manager; exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON); + exynos_plane_commit(exynos_crtc->plane); + + if (manager->ops->commit) + manager->ops->commit(manager); + exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_ON); } @@ -94,7 +105,12 @@ exynos_drm_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { - /* drm framework doesn't check NULL */ + struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); + struct exynos_drm_manager *manager = exynos_crtc->manager; + + if (manager->ops->mode_fixup) + return manager->ops->mode_fixup(manager, mode, adjusted_mode); + return true; } @@ -104,10 +120,10 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_framebuffer *old_fb) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); + struct exynos_drm_manager *manager = exynos_crtc->manager; struct drm_plane *plane = exynos_crtc->plane; unsigned int crtc_w; unsigned int crtc_h; - int pipe = exynos_crtc->pipe; int ret; /* @@ -116,18 +132,20 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, */ memcpy(&crtc->mode, adjusted_mode, sizeof(*adjusted_mode)); - crtc_w = crtc->fb->width - x; - crtc_h = crtc->fb->height - y; + crtc_w = crtc->primary->fb->width - x; + crtc_h = crtc->primary->fb->height - y; - ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h, + if (manager->ops->mode_set) + manager->ops->mode_set(manager, &crtc->mode); + + ret = exynos_plane_mode_set(plane, crtc, crtc->primary->fb, 0, 0, crtc_w, crtc_h, x, y, crtc_w, crtc_h); if (ret) return ret; plane->crtc = crtc; - plane->fb = crtc->fb; - - exynos_drm_fn_encoder(crtc, &pipe, exynos_drm_encoder_crtc_pipe); + plane->fb = crtc->primary->fb; + drm_framebuffer_reference(plane->fb); return 0; } @@ -147,10 +165,10 @@ static int exynos_drm_crtc_mode_set_commit(struct drm_crtc *crtc, int x, int y, return -EPERM; } - crtc_w = crtc->fb->width - x; - crtc_h = crtc->fb->height - y; + crtc_w = crtc->primary->fb->width - x; + crtc_h = crtc->primary->fb->height - y; - ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h, + ret = exynos_plane_mode_set(plane, crtc, crtc->primary->fb, 0, 0, crtc_w, crtc_h, x, y, crtc_w, crtc_h); if (ret) return ret; @@ -168,10 +186,19 @@ static int exynos_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, static void exynos_drm_crtc_disable(struct drm_crtc *crtc) { - struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); + struct drm_plane *plane; + int ret; - exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_OFF); exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); + + drm_for_each_legacy_plane(plane, &crtc->dev->mode_config.plane_list) { + if (plane->crtc != crtc) + continue; + + ret = plane->funcs->disable_plane(plane); + if (ret) + DRM_ERROR("Failed to disable plane %d\n", ret); + } } static struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = { @@ -192,7 +219,7 @@ static int exynos_drm_crtc_page_flip(struct drm_crtc *crtc, struct drm_device *dev = crtc->dev; struct exynos_drm_private *dev_priv = dev->dev_private; struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - struct drm_framebuffer *old_fb = crtc->fb; + struct drm_framebuffer *old_fb = crtc->primary->fb; int ret = -EINVAL; /* when the page flip is requested, crtc's dpms should be on */ @@ -223,11 +250,11 @@ static int exynos_drm_crtc_page_flip(struct drm_crtc *crtc, atomic_set(&exynos_crtc->pending_flip, 1); spin_unlock_irq(&dev->event_lock); - crtc->fb = fb; + crtc->primary->fb = fb; ret = exynos_drm_crtc_mode_set_commit(crtc, crtc->x, crtc->y, NULL); if (ret) { - crtc->fb = old_fb; + crtc->primary->fb = old_fb; spin_lock_irq(&dev->event_lock); drm_vblank_put(dev, exynos_crtc->pipe); @@ -318,31 +345,35 @@ static void exynos_drm_crtc_attach_mode_property(struct drm_crtc *crtc) drm_object_attach_property(&crtc->base, prop, 0); } -int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) +int exynos_drm_crtc_create(struct exynos_drm_manager *manager) { struct exynos_drm_crtc *exynos_crtc; - struct exynos_drm_private *private = dev->dev_private; + struct exynos_drm_private *private = manager->drm_dev->dev_private; struct drm_crtc *crtc; exynos_crtc = kzalloc(sizeof(*exynos_crtc), GFP_KERNEL); if (!exynos_crtc) return -ENOMEM; - exynos_crtc->pipe = nr; - exynos_crtc->dpms = DRM_MODE_DPMS_OFF; init_waitqueue_head(&exynos_crtc->pending_flip_queue); atomic_set(&exynos_crtc->pending_flip, 0); - exynos_crtc->plane = exynos_plane_init(dev, 1 << nr, true); + + exynos_crtc->dpms = DRM_MODE_DPMS_OFF; + exynos_crtc->manager = manager; + exynos_crtc->pipe = manager->pipe; + exynos_crtc->plane = exynos_plane_init(manager->drm_dev, + 1 << manager->pipe, true); if (!exynos_crtc->plane) { kfree(exynos_crtc); return -ENOMEM; } + manager->crtc = &exynos_crtc->drm_crtc; crtc = &exynos_crtc->drm_crtc; - private->crtc[nr] = crtc; + private->crtc[manager->pipe] = crtc; - drm_crtc_init(dev, crtc, &exynos_crtc_funcs); + drm_crtc_init(manager->drm_dev, crtc, &exynos_crtc_funcs); drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs); exynos_drm_crtc_attach_mode_property(crtc); @@ -350,39 +381,41 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) return 0; } -int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc) +int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int pipe) { struct exynos_drm_private *private = dev->dev_private; struct exynos_drm_crtc *exynos_crtc = - to_exynos_crtc(private->crtc[crtc]); + to_exynos_crtc(private->crtc[pipe]); + struct exynos_drm_manager *manager = exynos_crtc->manager; if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) return -EPERM; - exynos_drm_fn_encoder(private->crtc[crtc], &crtc, - exynos_drm_enable_vblank); + if (manager->ops->enable_vblank) + manager->ops->enable_vblank(manager); return 0; } -void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc) +void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int pipe) { struct exynos_drm_private *private = dev->dev_private; struct exynos_drm_crtc *exynos_crtc = - to_exynos_crtc(private->crtc[crtc]); + to_exynos_crtc(private->crtc[pipe]); + struct exynos_drm_manager *manager = exynos_crtc->manager; if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) return; - exynos_drm_fn_encoder(private->crtc[crtc], &crtc, - exynos_drm_disable_vblank); + if (manager->ops->disable_vblank) + manager->ops->disable_vblank(manager); } -void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc) +void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe) { struct exynos_drm_private *dev_priv = dev->dev_private; struct drm_pending_vblank_event *e, *t; - struct drm_crtc *drm_crtc = dev_priv->crtc[crtc]; + struct drm_crtc *drm_crtc = dev_priv->crtc[pipe]; struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(drm_crtc); unsigned long flags; @@ -391,15 +424,87 @@ void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc) list_for_each_entry_safe(e, t, &dev_priv->pageflip_event_list, base.link) { /* if event's pipe isn't same as crtc then ignore it. */ - if (crtc != e->pipe) + if (pipe != e->pipe) continue; list_del(&e->base.link); drm_send_vblank_event(dev, -1, e); - drm_vblank_put(dev, crtc); + drm_vblank_put(dev, pipe); atomic_set(&exynos_crtc->pending_flip, 0); wake_up(&exynos_crtc->pending_flip_queue); } spin_unlock_irqrestore(&dev->event_lock, flags); } + +void exynos_drm_crtc_plane_mode_set(struct drm_crtc *crtc, + struct exynos_drm_overlay *overlay) +{ + struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager; + + if (manager->ops->win_mode_set) + manager->ops->win_mode_set(manager, overlay); +} + +void exynos_drm_crtc_plane_commit(struct drm_crtc *crtc, int zpos) +{ + struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager; + + if (manager->ops->win_commit) + manager->ops->win_commit(manager, zpos); +} + +void exynos_drm_crtc_plane_enable(struct drm_crtc *crtc, int zpos) +{ + struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager; + + if (manager->ops->win_enable) + manager->ops->win_enable(manager, zpos); +} + +void exynos_drm_crtc_plane_disable(struct drm_crtc *crtc, int zpos) +{ + struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager; + + if (manager->ops->win_disable) + manager->ops->win_disable(manager, zpos); +} + +void exynos_drm_crtc_complete_scanout(struct drm_framebuffer *fb) +{ + struct exynos_drm_manager *manager; + struct drm_device *dev = fb->dev; + struct drm_crtc *crtc; + + /* + * make sure that overlay data are updated to real hardware + * for all encoders. + */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + manager = to_exynos_crtc(crtc)->manager; + + /* + * wait for vblank interrupt + * - this makes sure that overlay data are updated to + * real hardware. + */ + if (manager->ops->wait_for_vblank) + manager->ops->wait_for_vblank(manager); + } +} + +int exynos_drm_crtc_get_pipe_from_type(struct drm_device *drm_dev, + unsigned int out_type) +{ + struct drm_crtc *crtc; + + list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) { + struct exynos_drm_crtc *exynos_crtc; + + exynos_crtc = to_exynos_crtc(crtc); + if (exynos_crtc->manager->type == out_type) + return exynos_crtc->manager->pipe; + } + + return -EPERM; +} diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.h b/drivers/gpu/drm/exynos/exynos_drm_crtc.h index 3e197e6ae7d..9f74b10a8a0 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.h +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.h @@ -15,9 +15,25 @@ #ifndef _EXYNOS_DRM_CRTC_H_ #define _EXYNOS_DRM_CRTC_H_ -int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr); -int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc); -void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc); -void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc); +struct drm_device; +struct drm_crtc; +struct exynos_drm_manager; +struct exynos_drm_overlay; + +int exynos_drm_crtc_create(struct exynos_drm_manager *manager); +int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int pipe); +void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int pipe); +void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe); +void exynos_drm_crtc_complete_scanout(struct drm_framebuffer *fb); + +void exynos_drm_crtc_plane_mode_set(struct drm_crtc *crtc, + struct exynos_drm_overlay *overlay); +void exynos_drm_crtc_plane_commit(struct drm_crtc *crtc, int zpos); +void exynos_drm_crtc_plane_enable(struct drm_crtc *crtc, int zpos); +void exynos_drm_crtc_plane_disable(struct drm_crtc *crtc, int zpos); + +/* This function gets pipe value to crtc device matched with out_type. */ +int exynos_drm_crtc_get_pipe_from_type(struct drm_device *drm_dev, + unsigned int out_type); #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c b/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c index 59827cc5e77..2a3ad24276f 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dmabuf.c @@ -224,7 +224,7 @@ struct drm_gem_object *exynos_dmabuf_prime_import(struct drm_device *drm_dev, get_dma_buf(dma_buf); sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL); - if (IS_ERR_OR_NULL(sgt)) { + if (IS_ERR(sgt)) { ret = PTR_ERR(sgt); goto err_buf_detach; } @@ -263,7 +263,7 @@ struct drm_gem_object *exynos_dmabuf_prime_import(struct drm_device *drm_dev, buffer->sgt = sgt; exynos_gem_obj->base.import_attach = attach; - DRM_DEBUG_PRIME("dma_addr = 0x%x, size = 0x%lx\n", buffer->dma_addr, + DRM_DEBUG_PRIME("dma_addr = %pad, size = 0x%lx\n", &buffer->dma_addr, buffer->size); return &exynos_gem_obj->base; diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c new file mode 100644 index 00000000000..9e530f205ad --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c @@ -0,0 +1,347 @@ +/* + * Exynos DRM Parallel output support. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * + * Contacts: Andrzej Hajda <a.hajda@samsung.com> + * + * 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include <linux/regulator/consumer.h> + +#include <video/of_videomode.h> +#include <video/videomode.h> + +#include "exynos_drm_drv.h" + +struct exynos_dpi { + struct device *dev; + struct device_node *panel_node; + + struct drm_panel *panel; + struct drm_connector connector; + struct drm_encoder *encoder; + + struct videomode *vm; + int dpms_mode; +}; + +#define connector_to_dpi(c) container_of(c, struct exynos_dpi, connector) + +static enum drm_connector_status +exynos_dpi_detect(struct drm_connector *connector, bool force) +{ + struct exynos_dpi *ctx = connector_to_dpi(connector); + + if (ctx->panel && !ctx->panel->connector) + drm_panel_attach(ctx->panel, &ctx->connector); + + return connector_status_connected; +} + +static void exynos_dpi_connector_destroy(struct drm_connector *connector) +{ + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs exynos_dpi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = exynos_dpi_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = exynos_dpi_connector_destroy, +}; + +static int exynos_dpi_get_modes(struct drm_connector *connector) +{ + struct exynos_dpi *ctx = connector_to_dpi(connector); + + /* fimd timings gets precedence over panel modes */ + if (ctx->vm) { + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_ERROR("failed to create a new display mode\n"); + return 0; + } + drm_display_mode_from_videomode(ctx->vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + return 1; + } + + if (ctx->panel) + return ctx->panel->funcs->get_modes(ctx->panel); + + return 0; +} + +static struct drm_encoder * +exynos_dpi_best_encoder(struct drm_connector *connector) +{ + struct exynos_dpi *ctx = connector_to_dpi(connector); + + return ctx->encoder; +} + +static struct drm_connector_helper_funcs exynos_dpi_connector_helper_funcs = { + .get_modes = exynos_dpi_get_modes, + .best_encoder = exynos_dpi_best_encoder, +}; + +static int exynos_dpi_create_connector(struct exynos_drm_display *display, + struct drm_encoder *encoder) +{ + struct exynos_dpi *ctx = display->ctx; + struct drm_connector *connector = &ctx->connector; + int ret; + + ctx->encoder = encoder; + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(encoder->dev, connector, + &exynos_dpi_connector_funcs, + DRM_MODE_CONNECTOR_VGA); + if (ret) { + DRM_ERROR("failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(connector, &exynos_dpi_connector_helper_funcs); + drm_sysfs_connector_add(connector); + drm_mode_connector_attach_encoder(connector, encoder); + + return 0; +} + +static void exynos_dpi_poweron(struct exynos_dpi *ctx) +{ + if (ctx->panel) + drm_panel_enable(ctx->panel); +} + +static void exynos_dpi_poweroff(struct exynos_dpi *ctx) +{ + if (ctx->panel) + drm_panel_disable(ctx->panel); +} + +static void exynos_dpi_dpms(struct exynos_drm_display *display, int mode) +{ + struct exynos_dpi *ctx = display->ctx; + + switch (mode) { + case DRM_MODE_DPMS_ON: + if (ctx->dpms_mode != DRM_MODE_DPMS_ON) + exynos_dpi_poweron(ctx); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + if (ctx->dpms_mode == DRM_MODE_DPMS_ON) + exynos_dpi_poweroff(ctx); + break; + default: + break; + } + ctx->dpms_mode = mode; +} + +static struct exynos_drm_display_ops exynos_dpi_display_ops = { + .create_connector = exynos_dpi_create_connector, + .dpms = exynos_dpi_dpms +}; + +static struct exynos_drm_display exynos_dpi_display = { + .type = EXYNOS_DISPLAY_TYPE_LCD, + .ops = &exynos_dpi_display_ops, +}; + +/* of_* functions will be removed after merge of of_graph patches */ +static struct device_node * +of_get_child_by_name_reg(struct device_node *parent, const char *name, u32 reg) +{ + struct device_node *np; + + for_each_child_of_node(parent, np) { + u32 r; + + if (!np->name || of_node_cmp(np->name, name)) + continue; + + if (of_property_read_u32(np, "reg", &r) < 0) + r = 0; + + if (reg == r) + break; + } + + return np; +} + +static struct device_node *of_graph_get_port_by_reg(struct device_node *parent, + u32 reg) +{ + struct device_node *ports, *port; + + ports = of_get_child_by_name(parent, "ports"); + if (ports) + parent = ports; + + port = of_get_child_by_name_reg(parent, "port", reg); + + of_node_put(ports); + + return port; +} + +static struct device_node * +of_graph_get_endpoint_by_reg(struct device_node *port, u32 reg) +{ + return of_get_child_by_name_reg(port, "endpoint", reg); +} + +static struct device_node * +of_graph_get_remote_port_parent(const struct device_node *node) +{ + struct device_node *np; + unsigned int depth; + + np = of_parse_phandle(node, "remote-endpoint", 0); + + /* Walk 3 levels up only if there is 'ports' node. */ + for (depth = 3; depth && np; depth--) { + np = of_get_next_parent(np); + if (depth == 2 && of_node_cmp(np->name, "ports")) + break; + } + return np; +} + +enum { + FIMD_PORT_IN0, + FIMD_PORT_IN1, + FIMD_PORT_IN2, + FIMD_PORT_RGB, + FIMD_PORT_WRB, +}; + +static struct device_node *exynos_dpi_of_find_panel_node(struct device *dev) +{ + struct device_node *np, *ep; + + np = of_graph_get_port_by_reg(dev->of_node, FIMD_PORT_RGB); + if (!np) + return NULL; + + ep = of_graph_get_endpoint_by_reg(np, 0); + of_node_put(np); + if (!ep) + return NULL; + + np = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + + return np; +} + +static int exynos_dpi_parse_dt(struct exynos_dpi *ctx) +{ + struct device *dev = ctx->dev; + struct device_node *dn = dev->of_node; + struct device_node *np; + + ctx->panel_node = exynos_dpi_of_find_panel_node(dev); + + np = of_get_child_by_name(dn, "display-timings"); + if (np) { + struct videomode *vm; + int ret; + + of_node_put(np); + + vm = devm_kzalloc(dev, sizeof(*ctx->vm), GFP_KERNEL); + if (!vm) + return -ENOMEM; + + ret = of_get_videomode(dn, vm, 0); + if (ret < 0) { + devm_kfree(dev, vm); + return ret; + } + + ctx->vm = vm; + + return 0; + } + + if (!ctx->panel_node) + return -EINVAL; + + return 0; +} + +struct exynos_drm_display *exynos_dpi_probe(struct device *dev) +{ + struct exynos_dpi *ctx; + int ret; + + ret = exynos_drm_component_add(dev, + EXYNOS_DEVICE_TYPE_CONNECTOR, + exynos_dpi_display.type); + if (ret) + return ERR_PTR(ret); + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + goto err_del_component; + + ctx->dev = dev; + exynos_dpi_display.ctx = ctx; + ctx->dpms_mode = DRM_MODE_DPMS_OFF; + + ret = exynos_dpi_parse_dt(ctx); + if (ret < 0) { + devm_kfree(dev, ctx); + goto err_del_component; + } + + if (ctx->panel_node) { + ctx->panel = of_drm_find_panel(ctx->panel_node); + if (!ctx->panel) { + exynos_drm_component_del(dev, + EXYNOS_DEVICE_TYPE_CONNECTOR); + return ERR_PTR(-EPROBE_DEFER); + } + } + + return &exynos_dpi_display; + +err_del_component: + exynos_drm_component_del(dev, EXYNOS_DEVICE_TYPE_CONNECTOR); + + return NULL; +} + +int exynos_dpi_remove(struct device *dev) +{ + struct drm_encoder *encoder = exynos_dpi_display.encoder; + struct exynos_dpi *ctx = exynos_dpi_display.ctx; + + exynos_dpi_dpms(&exynos_dpi_display, DRM_MODE_DPMS_OFF); + encoder->funcs->destroy(encoder); + drm_connector_cleanup(&ctx->connector); + + exynos_drm_component_del(dev, EXYNOS_DEVICE_TYPE_CONNECTOR); + + return 0; +} diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index bb82ef78ca8..ab7d182063c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -11,9 +11,13 @@ * option) any later version. */ +#include <linux/pm_runtime.h> #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <linux/anon_inodes.h> +#include <linux/component.h> + #include <drm/exynos_drm.h> #include "exynos_drm_drv.h" @@ -37,9 +41,19 @@ #define VBLANK_OFF_DELAY 50000 -/* platform device pointer for eynos drm device. */ static struct platform_device *exynos_drm_pdev; +static DEFINE_MUTEX(drm_component_lock); +static LIST_HEAD(drm_component_list); + +struct component_dev { + struct list_head list; + struct device *crtc_dev; + struct device *conn_dev; + enum exynos_drm_output_type out_type; + unsigned int dev_type_flag; +}; + static int exynos_drm_load(struct drm_device *dev, unsigned long flags) { struct exynos_drm_private *private; @@ -51,6 +65,7 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags) return -ENOMEM; INIT_LIST_HEAD(&private->pageflip_event_list); + dev_set_drvdata(dev->dev, dev); dev->dev_private = (void *)private; /* @@ -62,73 +77,59 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags) ret = drm_create_iommu_mapping(dev); if (ret < 0) { DRM_ERROR("failed to create iommu mapping.\n"); - goto err_crtc; + goto err_free_private; } drm_mode_config_init(dev); - /* init kms poll for handling hpd */ - drm_kms_helper_poll_init(dev); - exynos_drm_mode_config_init(dev); - /* - * EXYNOS4 is enough to have two CRTCs and each crtc would be used - * without dependency of hardware. - */ - for (nr = 0; nr < MAX_CRTC; nr++) { - ret = exynos_drm_crtc_create(dev, nr); - if (ret) - goto err_release_iommu_mapping; - } - for (nr = 0; nr < MAX_PLANE; nr++) { struct drm_plane *plane; - unsigned int possible_crtcs = (1 << MAX_CRTC) - 1; + unsigned long possible_crtcs = (1 << MAX_CRTC) - 1; plane = exynos_plane_init(dev, possible_crtcs, false); if (!plane) - goto err_release_iommu_mapping; + goto err_mode_config_cleanup; } - ret = drm_vblank_init(dev, MAX_CRTC); - if (ret) - goto err_release_iommu_mapping; + /* init kms poll for handling hpd */ + drm_kms_helper_poll_init(dev); - /* - * probe sub drivers such as display controller and hdmi driver, - * that were registered at probe() of platform driver - * to the sub driver and create encoder and connector for them. - */ - ret = exynos_drm_device_register(dev); + ret = drm_vblank_init(dev, MAX_CRTC); if (ret) - goto err_vblank; + goto err_mode_config_cleanup; /* setup possible_clones. */ exynos_drm_encoder_setup(dev); - /* - * create and configure fb helper and also exynos specific - * fbdev object. - */ - ret = exynos_drm_fbdev_init(dev); - if (ret) { - DRM_ERROR("failed to initialize drm fbdev\n"); - goto err_drm_device; - } - drm_vblank_offdelay = VBLANK_OFF_DELAY; + platform_set_drvdata(dev->platformdev, dev); + + /* Try to bind all sub drivers. */ + ret = component_bind_all(dev->dev, dev); + if (ret) + goto err_cleanup_vblank; + + /* Probe non kms sub drivers and virtual display driver. */ + ret = exynos_drm_device_subdrv_probe(dev); + if (ret) + goto err_unbind_all; + + /* force connectors detection */ + drm_helper_hpd_irq_event(dev); + return 0; -err_drm_device: - exynos_drm_device_unregister(dev); -err_vblank: +err_unbind_all: + component_unbind_all(dev->dev, dev); +err_cleanup_vblank: drm_vblank_cleanup(dev); -err_release_iommu_mapping: - drm_release_iommu_mapping(dev); -err_crtc: +err_mode_config_cleanup: drm_mode_config_cleanup(dev); + drm_release_iommu_mapping(dev); +err_free_private: kfree(private); return ret; @@ -136,8 +137,9 @@ err_crtc: static int exynos_drm_unload(struct drm_device *dev) { + exynos_drm_device_subdrv_remove(dev); + exynos_drm_fbdev_fini(dev); - exynos_drm_device_unregister(dev); drm_vblank_cleanup(dev); drm_kms_helper_poll_fini(dev); drm_mode_config_cleanup(dev); @@ -145,14 +147,55 @@ static int exynos_drm_unload(struct drm_device *dev) drm_release_iommu_mapping(dev); kfree(dev->dev_private); + component_unbind_all(dev->dev, dev); dev->dev_private = NULL; return 0; } +static const struct file_operations exynos_drm_gem_fops = { + .mmap = exynos_drm_gem_mmap_buffer, +}; + +static int exynos_drm_suspend(struct drm_device *dev, pm_message_t state) +{ + struct drm_connector *connector; + + drm_modeset_lock_all(dev); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + int old_dpms = connector->dpms; + + if (connector->funcs->dpms) + connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF); + + /* Set the old mode back to the connector for resume */ + connector->dpms = old_dpms; + } + drm_modeset_unlock_all(dev); + + return 0; +} + +static int exynos_drm_resume(struct drm_device *dev) +{ + struct drm_connector *connector; + + drm_modeset_lock_all(dev); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->funcs->dpms) + connector->funcs->dpms(connector, connector->dpms); + } + drm_modeset_unlock_all(dev); + + drm_helper_resume_force_mode(dev); + + return 0; +} + static int exynos_drm_open(struct drm_device *dev, struct drm_file *file) { struct drm_exynos_file_private *file_priv; + struct file *anon_filp; int ret; file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL); @@ -162,39 +205,68 @@ static int exynos_drm_open(struct drm_device *dev, struct drm_file *file) file->driver_priv = file_priv; ret = exynos_drm_subdrv_open(dev, file); - if (ret) { - kfree(file_priv); - file->driver_priv = NULL; + if (ret) + goto err_file_priv_free; + + anon_filp = anon_inode_getfile("exynos_gem", &exynos_drm_gem_fops, + NULL, 0); + if (IS_ERR(anon_filp)) { + ret = PTR_ERR(anon_filp); + goto err_subdrv_close; } + anon_filp->f_mode = FMODE_READ | FMODE_WRITE; + file_priv->anon_filp = anon_filp; + + return ret; + +err_subdrv_close: + exynos_drm_subdrv_close(dev, file); + +err_file_priv_free: + kfree(file_priv); + file->driver_priv = NULL; return ret; } static void exynos_drm_preclose(struct drm_device *dev, struct drm_file *file) { + exynos_drm_subdrv_close(dev, file); +} + +static void exynos_drm_postclose(struct drm_device *dev, struct drm_file *file) +{ struct exynos_drm_private *private = dev->dev_private; - struct drm_pending_vblank_event *e, *t; + struct drm_exynos_file_private *file_priv; + struct drm_pending_vblank_event *v, *vt; + struct drm_pending_event *e, *et; unsigned long flags; - /* release events of current file */ + if (!file->driver_priv) + return; + + /* Release all events not unhandled by page flip handler. */ spin_lock_irqsave(&dev->event_lock, flags); - list_for_each_entry_safe(e, t, &private->pageflip_event_list, + list_for_each_entry_safe(v, vt, &private->pageflip_event_list, base.link) { - if (e->base.file_priv == file) { - list_del(&e->base.link); - e->base.destroy(&e->base); + if (v->base.file_priv == file) { + list_del(&v->base.link); + drm_vblank_put(dev, v->pipe); + v->base.destroy(&v->base); } } + + /* Release all events handled by page flip handler but not freed. */ + list_for_each_entry_safe(e, et, &file->event_list, link) { + list_del(&e->link); + e->destroy(e); + } spin_unlock_irqrestore(&dev->event_lock, flags); - exynos_drm_subdrv_close(dev, file); -} - -static void exynos_drm_postclose(struct drm_device *dev, struct drm_file *file) -{ - if (!file->driver_priv) - return; + file_priv = file->driver_priv; + if (file_priv->anon_filp) + fput(file_priv->anon_filp); kfree(file->driver_priv); file->driver_priv = NULL; @@ -253,10 +325,11 @@ static const struct file_operations exynos_drm_driver_fops = { }; static struct drm_driver exynos_drm_driver = { - .driver_features = DRIVER_HAVE_IRQ | DRIVER_MODESET | - DRIVER_GEM | DRIVER_PRIME, + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, .load = exynos_drm_load, .unload = exynos_drm_unload, + .suspend = exynos_drm_suspend, + .resume = exynos_drm_resume, .open = exynos_drm_open, .preclose = exynos_drm_preclose, .lastclose = exynos_drm_lastclose, @@ -264,7 +337,6 @@ static struct drm_driver exynos_drm_driver = { .get_vblank_counter = drm_vblank_count, .enable_vblank = exynos_drm_crtc_enable_vblank, .disable_vblank = exynos_drm_crtc_disable_vblank, - .gem_init_object = exynos_drm_gem_init_object, .gem_free_object = exynos_drm_gem_free_object, .gem_vm_ops = &exynos_drm_gem_vm_ops, .dumb_create = exynos_drm_gem_dumb_create, @@ -284,168 +356,343 @@ static struct drm_driver exynos_drm_driver = { .minor = DRIVER_MINOR, }; -static int exynos_drm_platform_probe(struct platform_device *pdev) +#ifdef CONFIG_PM_SLEEP +static int exynos_drm_sys_suspend(struct device *dev) { - pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + struct drm_device *drm_dev = dev_get_drvdata(dev); + pm_message_t message; + + if (pm_runtime_suspended(dev)) + return 0; - return drm_platform_init(&exynos_drm_driver, pdev); + message.event = PM_EVENT_SUSPEND; + return exynos_drm_suspend(drm_dev, message); } -static int exynos_drm_platform_remove(struct platform_device *pdev) +static int exynos_drm_sys_resume(struct device *dev) { - drm_platform_exit(&exynos_drm_driver, pdev); + struct drm_device *drm_dev = dev_get_drvdata(dev); + + if (pm_runtime_suspended(dev)) + return 0; + + return exynos_drm_resume(drm_dev); +} +#endif + +static const struct dev_pm_ops exynos_drm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_drm_sys_suspend, exynos_drm_sys_resume) +}; + +int exynos_drm_component_add(struct device *dev, + enum exynos_drm_device_type dev_type, + enum exynos_drm_output_type out_type) +{ + struct component_dev *cdev; + + if (dev_type != EXYNOS_DEVICE_TYPE_CRTC && + dev_type != EXYNOS_DEVICE_TYPE_CONNECTOR) { + DRM_ERROR("invalid device type.\n"); + return -EINVAL; + } + + mutex_lock(&drm_component_lock); + + /* + * Make sure to check if there is a component which has two device + * objects, for connector and for encoder/connector. + * It should make sure that crtc and encoder/connector drivers are + * ready before exynos drm core binds them. + */ + list_for_each_entry(cdev, &drm_component_list, list) { + if (cdev->out_type == out_type) { + /* + * If crtc and encoder/connector device objects are + * added already just return. + */ + if (cdev->dev_type_flag == (EXYNOS_DEVICE_TYPE_CRTC | + EXYNOS_DEVICE_TYPE_CONNECTOR)) { + mutex_unlock(&drm_component_lock); + return 0; + } + + if (dev_type == EXYNOS_DEVICE_TYPE_CRTC) { + cdev->crtc_dev = dev; + cdev->dev_type_flag |= dev_type; + } + + if (dev_type == EXYNOS_DEVICE_TYPE_CONNECTOR) { + cdev->conn_dev = dev; + cdev->dev_type_flag |= dev_type; + } + + mutex_unlock(&drm_component_lock); + return 0; + } + } + + mutex_unlock(&drm_component_lock); + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + if (dev_type == EXYNOS_DEVICE_TYPE_CRTC) + cdev->crtc_dev = dev; + if (dev_type == EXYNOS_DEVICE_TYPE_CONNECTOR) + cdev->conn_dev = dev; + + cdev->out_type = out_type; + cdev->dev_type_flag = dev_type; + + mutex_lock(&drm_component_lock); + list_add_tail(&cdev->list, &drm_component_list); + mutex_unlock(&drm_component_lock); return 0; } -static struct platform_driver exynos_drm_platform_driver = { - .probe = exynos_drm_platform_probe, - .remove = exynos_drm_platform_remove, - .driver = { - .owner = THIS_MODULE, - .name = "exynos-drm", - }, +void exynos_drm_component_del(struct device *dev, + enum exynos_drm_device_type dev_type) +{ + struct component_dev *cdev, *next; + + mutex_lock(&drm_component_lock); + + list_for_each_entry_safe(cdev, next, &drm_component_list, list) { + if (dev_type == EXYNOS_DEVICE_TYPE_CRTC) { + if (cdev->crtc_dev == dev) { + cdev->crtc_dev = NULL; + cdev->dev_type_flag &= ~dev_type; + } + } + + if (dev_type == EXYNOS_DEVICE_TYPE_CONNECTOR) { + if (cdev->conn_dev == dev) { + cdev->conn_dev = NULL; + cdev->dev_type_flag &= ~dev_type; + } + } + + /* + * Release cdev object only in case that both of crtc and + * encoder/connector device objects are NULL. + */ + if (!cdev->crtc_dev && !cdev->conn_dev) { + list_del(&cdev->list); + kfree(cdev); + } + + break; + } + + mutex_unlock(&drm_component_lock); +} + +static int compare_of(struct device *dev, void *data) +{ + return dev == (struct device *)data; +} + +static int exynos_drm_add_components(struct device *dev, struct master *m) +{ + struct component_dev *cdev; + unsigned int attach_cnt = 0; + + mutex_lock(&drm_component_lock); + + list_for_each_entry(cdev, &drm_component_list, list) { + int ret; + + /* + * Add components to master only in case that crtc and + * encoder/connector device objects exist. + */ + if (!cdev->crtc_dev || !cdev->conn_dev) + continue; + + attach_cnt++; + + mutex_unlock(&drm_component_lock); + + /* + * fimd and dpi modules have same device object so add + * only crtc device object in this case. + * + * TODO. if dpi module follows driver-model driver then + * below codes can be removed. + */ + if (cdev->crtc_dev == cdev->conn_dev) { + ret = component_master_add_child(m, compare_of, + cdev->crtc_dev); + if (ret < 0) + return ret; + + goto out_lock; + } + + /* + * Do not chage below call order. + * crtc device first should be added to master because + * connector/encoder need pipe number of crtc when they + * are created. + */ + ret = component_master_add_child(m, compare_of, cdev->crtc_dev); + ret |= component_master_add_child(m, compare_of, + cdev->conn_dev); + if (ret < 0) + return ret; + +out_lock: + mutex_lock(&drm_component_lock); + } + + mutex_unlock(&drm_component_lock); + + return attach_cnt ? 0 : -ENODEV; +} + +static int exynos_drm_bind(struct device *dev) +{ + return drm_platform_init(&exynos_drm_driver, to_platform_device(dev)); +} + +static void exynos_drm_unbind(struct device *dev) +{ + drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops exynos_drm_ops = { + .add_components = exynos_drm_add_components, + .bind = exynos_drm_bind, + .unbind = exynos_drm_unbind, }; -static int __init exynos_drm_init(void) +static int exynos_drm_platform_probe(struct platform_device *pdev) { int ret; + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + exynos_drm_driver.num_ioctls = ARRAY_SIZE(exynos_ioctls); + #ifdef CONFIG_DRM_EXYNOS_FIMD ret = platform_driver_register(&fimd_driver); if (ret < 0) - goto out_fimd; + return ret; #endif -#ifdef CONFIG_DRM_EXYNOS_HDMI - ret = platform_driver_register(&hdmi_driver); - if (ret < 0) - goto out_hdmi; - ret = platform_driver_register(&mixer_driver); +#ifdef CONFIG_DRM_EXYNOS_DP + ret = platform_driver_register(&dp_driver); if (ret < 0) - goto out_mixer; - ret = platform_driver_register(&exynos_drm_common_hdmi_driver); - if (ret < 0) - goto out_common_hdmi; + goto err_unregister_fimd_drv; +#endif - ret = exynos_platform_device_hdmi_register(); +#ifdef CONFIG_DRM_EXYNOS_DSI + ret = platform_driver_register(&dsi_driver); if (ret < 0) - goto out_common_hdmi_dev; + goto err_unregister_dp_drv; #endif -#ifdef CONFIG_DRM_EXYNOS_VIDI - ret = platform_driver_register(&vidi_driver); +#ifdef CONFIG_DRM_EXYNOS_HDMI + ret = platform_driver_register(&mixer_driver); + if (ret < 0) + goto err_unregister_dsi_drv; + ret = platform_driver_register(&hdmi_driver); if (ret < 0) - goto out_vidi; + goto err_unregister_mixer_drv; #endif #ifdef CONFIG_DRM_EXYNOS_G2D ret = platform_driver_register(&g2d_driver); if (ret < 0) - goto out_g2d; + goto err_unregister_hdmi_drv; #endif #ifdef CONFIG_DRM_EXYNOS_FIMC ret = platform_driver_register(&fimc_driver); if (ret < 0) - goto out_fimc; + goto err_unregister_g2d_drv; #endif #ifdef CONFIG_DRM_EXYNOS_ROTATOR ret = platform_driver_register(&rotator_driver); if (ret < 0) - goto out_rotator; + goto err_unregister_fimc_drv; #endif #ifdef CONFIG_DRM_EXYNOS_GSC ret = platform_driver_register(&gsc_driver); if (ret < 0) - goto out_gsc; + goto err_unregister_rotator_drv; #endif #ifdef CONFIG_DRM_EXYNOS_IPP ret = platform_driver_register(&ipp_driver); if (ret < 0) - goto out_ipp; + goto err_unregister_gsc_drv; ret = exynos_platform_device_ipp_register(); if (ret < 0) - goto out_ipp_dev; + goto err_unregister_ipp_drv; #endif - ret = platform_driver_register(&exynos_drm_platform_driver); + ret = component_master_add(&pdev->dev, &exynos_drm_ops); if (ret < 0) - goto out_drm; - - exynos_drm_pdev = platform_device_register_simple("exynos-drm", -1, - NULL, 0); - if (IS_ERR(exynos_drm_pdev)) { - ret = PTR_ERR(exynos_drm_pdev); - goto out; - } + DRM_DEBUG_KMS("re-tried by last sub driver probed later.\n"); return 0; -out: - platform_driver_unregister(&exynos_drm_platform_driver); - -out_drm: #ifdef CONFIG_DRM_EXYNOS_IPP - exynos_platform_device_ipp_unregister(); -out_ipp_dev: +err_unregister_ipp_drv: platform_driver_unregister(&ipp_driver); -out_ipp: +err_unregister_gsc_drv: #endif #ifdef CONFIG_DRM_EXYNOS_GSC platform_driver_unregister(&gsc_driver); -out_gsc: +err_unregister_rotator_drv: #endif #ifdef CONFIG_DRM_EXYNOS_ROTATOR platform_driver_unregister(&rotator_driver); -out_rotator: +err_unregister_fimc_drv: #endif #ifdef CONFIG_DRM_EXYNOS_FIMC platform_driver_unregister(&fimc_driver); -out_fimc: +err_unregister_g2d_drv: #endif #ifdef CONFIG_DRM_EXYNOS_G2D platform_driver_unregister(&g2d_driver); -out_g2d: -#endif - -#ifdef CONFIG_DRM_EXYNOS_VIDI - platform_driver_unregister(&vidi_driver); -out_vidi: +err_unregister_hdmi_drv: #endif #ifdef CONFIG_DRM_EXYNOS_HDMI - exynos_platform_device_hdmi_unregister(); -out_common_hdmi_dev: - platform_driver_unregister(&exynos_drm_common_hdmi_driver); -out_common_hdmi: - platform_driver_unregister(&mixer_driver); -out_mixer: platform_driver_unregister(&hdmi_driver); -out_hdmi: +err_unregister_mixer_drv: + platform_driver_unregister(&mixer_driver); +err_unregister_dsi_drv: +#endif + +#ifdef CONFIG_DRM_EXYNOS_DSI + platform_driver_unregister(&dsi_driver); +err_unregister_dp_drv: +#endif + +#ifdef CONFIG_DRM_EXYNOS_DP + platform_driver_unregister(&dp_driver); +err_unregister_fimd_drv: #endif #ifdef CONFIG_DRM_EXYNOS_FIMD platform_driver_unregister(&fimd_driver); -out_fimd: #endif return ret; } -static void __exit exynos_drm_exit(void) +static int exynos_drm_platform_remove(struct platform_device *pdev) { - platform_device_unregister(exynos_drm_pdev); - - platform_driver_unregister(&exynos_drm_platform_driver); - #ifdef CONFIG_DRM_EXYNOS_IPP exynos_platform_device_ipp_unregister(); platform_driver_unregister(&ipp_driver); @@ -468,19 +715,74 @@ static void __exit exynos_drm_exit(void) #endif #ifdef CONFIG_DRM_EXYNOS_HDMI - exynos_platform_device_hdmi_unregister(); - platform_driver_unregister(&exynos_drm_common_hdmi_driver); platform_driver_unregister(&mixer_driver); platform_driver_unregister(&hdmi_driver); #endif +#ifdef CONFIG_DRM_EXYNOS_FIMD + platform_driver_unregister(&fimd_driver); +#endif + +#ifdef CONFIG_DRM_EXYNOS_DSI + platform_driver_unregister(&dsi_driver); +#endif + +#ifdef CONFIG_DRM_EXYNOS_DP + platform_driver_unregister(&dp_driver); +#endif + component_master_del(&pdev->dev, &exynos_drm_ops); + return 0; +} + +static struct platform_driver exynos_drm_platform_driver = { + .probe = exynos_drm_platform_probe, + .remove = exynos_drm_platform_remove, + .driver = { + .owner = THIS_MODULE, + .name = "exynos-drm", + .pm = &exynos_drm_pm_ops, + }, +}; + +static int exynos_drm_init(void) +{ + int ret; + + exynos_drm_pdev = platform_device_register_simple("exynos-drm", -1, + NULL, 0); + if (IS_ERR(exynos_drm_pdev)) + return PTR_ERR(exynos_drm_pdev); + #ifdef CONFIG_DRM_EXYNOS_VIDI - platform_driver_unregister(&vidi_driver); + ret = exynos_drm_probe_vidi(); + if (ret < 0) + goto err_unregister_pd; #endif -#ifdef CONFIG_DRM_EXYNOS_FIMD - platform_driver_unregister(&fimd_driver); + ret = platform_driver_register(&exynos_drm_platform_driver); + if (ret) + goto err_remove_vidi; + + return 0; + +err_remove_vidi: +#ifdef CONFIG_DRM_EXYNOS_VIDI + exynos_drm_remove_vidi(); + +err_unregister_pd: #endif + platform_device_unregister(exynos_drm_pdev); + + return ret; +} + +static void exynos_drm_exit(void) +{ + platform_driver_unregister(&exynos_drm_platform_driver); +#ifdef CONFIG_DRM_EXYNOS_VIDI + exynos_drm_remove_vidi(); +#endif + platform_device_unregister(exynos_drm_pdev); } module_init(exynos_drm_init); diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index eaa19668bf0..06cde450627 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -42,6 +42,13 @@ struct drm_connector; extern unsigned int drm_vblank_offdelay; +/* This enumerates device type. */ +enum exynos_drm_device_type { + EXYNOS_DEVICE_TYPE_NONE, + EXYNOS_DEVICE_TYPE_CRTC, + EXYNOS_DEVICE_TYPE_CONNECTOR, +}; + /* this enumerates display type. */ enum exynos_drm_output_type { EXYNOS_DISPLAY_TYPE_NONE, @@ -54,22 +61,6 @@ enum exynos_drm_output_type { }; /* - * Exynos drm overlay ops structure. - * - * @mode_set: copy drm overlay info to hw specific overlay info. - * @commit: apply hardware specific overlay data to registers. - * @enable: enable hardware specific overlay. - * @disable: disable hardware specific overlay. - */ -struct exynos_drm_overlay_ops { - void (*mode_set)(struct device *subdrv_dev, - struct exynos_drm_overlay *overlay); - void (*commit)(struct device *subdrv_dev, int zpos); - void (*enable)(struct device *subdrv_dev, int zpos); - void (*disable)(struct device *subdrv_dev, int zpos); -}; - -/* * Exynos drm common overlay structure. * * @fb_x: offset x on a framebuffer to be displayed. @@ -138,77 +129,104 @@ struct exynos_drm_overlay { * Exynos DRM Display Structure. * - this structure is common to analog tv, digital tv and lcd panel. * - * @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI. - * @is_connected: check for that display is connected or not. - * @get_edid: get edid modes from display driver. - * @get_panel: get panel object from display driver. + * @remove: cleans up the display for removal + * @mode_fixup: fix mode data comparing to hw specific display mode. + * @mode_set: convert drm_display_mode to hw specific display mode and + * would be called by encoder->mode_set(). * @check_mode: check if mode is valid or not. - * @power_on: display device on or off. + * @dpms: display device on or off. + * @commit: apply changes to hw */ +struct exynos_drm_display; struct exynos_drm_display_ops { + int (*create_connector)(struct exynos_drm_display *display, + struct drm_encoder *encoder); + void (*remove)(struct exynos_drm_display *display); + void (*mode_fixup)(struct exynos_drm_display *display, + struct drm_connector *connector, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); + void (*mode_set)(struct exynos_drm_display *display, + struct drm_display_mode *mode); + int (*check_mode)(struct exynos_drm_display *display, + struct drm_display_mode *mode); + void (*dpms)(struct exynos_drm_display *display, int mode); + void (*commit)(struct exynos_drm_display *display); +}; + +/* + * Exynos drm display structure, maps 1:1 with an encoder/connector + * + * @list: the list entry for this manager + * @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI. + * @encoder: encoder object this display maps to + * @connector: connector object this display maps to + * @ops: pointer to callbacks for exynos drm specific functionality + * @ctx: A pointer to the display's implementation specific context + */ +struct exynos_drm_display { + struct list_head list; enum exynos_drm_output_type type; - bool (*is_connected)(struct device *dev); - struct edid *(*get_edid)(struct device *dev, - struct drm_connector *connector); - void *(*get_panel)(struct device *dev); - int (*check_mode)(struct device *dev, struct drm_display_mode *mode); - int (*power_on)(struct device *dev, int mode); + struct drm_encoder *encoder; + struct drm_connector *connector; + struct exynos_drm_display_ops *ops; + void *ctx; }; /* * Exynos drm manager ops * * @dpms: control device power. - * @apply: set timing, vblank and overlay data to registers. - * @mode_fixup: fix mode data comparing to hw specific display mode. - * @mode_set: convert drm_display_mode to hw specific display mode and - * would be called by encoder->mode_set(). - * @get_max_resol: get maximum resolution to specific hardware. + * @mode_fixup: fix mode data before applying it + * @mode_set: set the given mode to the manager * @commit: set current hw specific display mode to hw. * @enable_vblank: specific driver callback for enabling vblank interrupt. * @disable_vblank: specific driver callback for disabling vblank interrupt. * @wait_for_vblank: wait for vblank interrupt to make sure that * hardware overlay is updated. + * @win_mode_set: copy drm overlay info to hw specific overlay info. + * @win_commit: apply hardware specific overlay data to registers. + * @win_enable: enable hardware specific overlay. + * @win_disable: disable hardware specific overlay. */ +struct exynos_drm_manager; struct exynos_drm_manager_ops { - void (*dpms)(struct device *subdrv_dev, int mode); - void (*apply)(struct device *subdrv_dev); - void (*mode_fixup)(struct device *subdrv_dev, - struct drm_connector *connector, + void (*dpms)(struct exynos_drm_manager *mgr, int mode); + bool (*mode_fixup)(struct exynos_drm_manager *mgr, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); - void (*mode_set)(struct device *subdrv_dev, void *mode); - void (*get_max_resol)(struct device *subdrv_dev, unsigned int *width, - unsigned int *height); - void (*commit)(struct device *subdrv_dev); - int (*enable_vblank)(struct device *subdrv_dev); - void (*disable_vblank)(struct device *subdrv_dev); - void (*wait_for_vblank)(struct device *subdrv_dev); + void (*mode_set)(struct exynos_drm_manager *mgr, + const struct drm_display_mode *mode); + void (*commit)(struct exynos_drm_manager *mgr); + int (*enable_vblank)(struct exynos_drm_manager *mgr); + void (*disable_vblank)(struct exynos_drm_manager *mgr); + void (*wait_for_vblank)(struct exynos_drm_manager *mgr); + void (*win_mode_set)(struct exynos_drm_manager *mgr, + struct exynos_drm_overlay *overlay); + void (*win_commit)(struct exynos_drm_manager *mgr, int zpos); + void (*win_enable)(struct exynos_drm_manager *mgr, int zpos); + void (*win_disable)(struct exynos_drm_manager *mgr, int zpos); }; /* - * Exynos drm common manager structure. + * Exynos drm common manager structure, maps 1:1 with a crtc * - * @dev: pointer to device object for subdrv device driver. - * sub drivers such as display controller or hdmi driver, - * have their own device object. - * @ops: pointer to callbacks for exynos drm specific framebuffer. - * these callbacks should be set by specific drivers such fimd - * or hdmi driver and are used to control hardware global registers. - * @overlay_ops: pointer to callbacks for exynos drm specific framebuffer. - * these callbacks should be set by specific drivers such fimd - * or hdmi driver and are used to control hardware overlay reigsters. - * @display: pointer to callbacks for exynos drm specific framebuffer. - * these callbacks should be set by specific drivers such fimd - * or hdmi driver and are used to control display devices such as - * analog tv, digital tv and lcd panel and also get timing data for them. + * @list: the list entry for this manager + * @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI. + * @drm_dev: pointer to the drm device + * @crtc: crtc object. + * @pipe: the pipe number for this crtc/manager + * @ops: pointer to callbacks for exynos drm specific functionality + * @ctx: A pointer to the manager's implementation specific context */ struct exynos_drm_manager { - struct device *dev; + struct list_head list; + enum exynos_drm_output_type type; + struct drm_device *drm_dev; + struct drm_crtc *crtc; int pipe; struct exynos_drm_manager_ops *ops; - struct exynos_drm_overlay_ops *overlay_ops; - struct exynos_drm_display_ops *display_ops; + void *ctx; }; struct exynos_drm_g2d_private { @@ -226,6 +244,7 @@ struct exynos_drm_ipp_private { struct drm_exynos_file_private { struct exynos_drm_g2d_private *g2d_priv; struct exynos_drm_ipp_private *ipp_priv; + struct file *anon_filp; }; /* @@ -236,7 +255,7 @@ struct drm_exynos_file_private { * otherwise default one. * @da_space_size: size of device address space. * if 0 then default value is used for it. - * @da_space_order: order to device address space. + * @pipe: the pipe number for this crtc/manager. */ struct exynos_drm_private { struct drm_fb_helper *fb_helper; @@ -254,7 +273,8 @@ struct exynos_drm_private { unsigned long da_start; unsigned long da_space_size; - unsigned long da_space_order; + + unsigned int pipe; }; /* @@ -265,21 +285,18 @@ struct exynos_drm_private { * @drm_dev: pointer to drm_device and this pointer would be set * when sub driver calls exynos_drm_subdrv_register(). * @manager: subdrv has its own manager to control a hardware appropriately - * and we can access a hardware drawing on this manager. + * and we can access a hardware drawing on this manager. * @probe: this callback would be called by exynos drm driver after - * subdrv is registered to it. + * subdrv is registered to it. * @remove: this callback is used to release resources created - * by probe callback. + * by probe callback. * @open: this would be called with drm device file open. * @close: this would be called with drm device file close. - * @encoder: encoder object owned by this sub driver. - * @connector: connector object owned by this sub driver. */ struct exynos_drm_subdrv { struct list_head list; struct device *dev; struct drm_device *drm_dev; - struct exynos_drm_manager *manager; int (*probe)(struct drm_device *drm_dev, struct device *dev); void (*remove)(struct drm_device *drm_dev, struct device *dev); @@ -287,34 +304,16 @@ struct exynos_drm_subdrv { struct drm_file *file); void (*close)(struct drm_device *drm_dev, struct device *dev, struct drm_file *file); - - struct drm_encoder *encoder; - struct drm_connector *connector; }; -/* - * this function calls a probe callback registered to sub driver list and - * create its own encoder and connector and then set drm_device object - * to global one. - */ -int exynos_drm_device_register(struct drm_device *dev); -/* - * this function calls a remove callback registered to sub driver list and - * destroy its own encoder and connetor. - */ -int exynos_drm_device_unregister(struct drm_device *dev); - -/* - * this function would be called by sub drivers such as display controller - * or hdmi driver to register this sub driver object to exynos drm driver - * and when a sub driver is registered to exynos drm driver a probe callback - * of the sub driver is called and creates its own encoder and connector. - */ + /* This function would be called by non kms drivers such as g2d and ipp. */ int exynos_drm_subdrv_register(struct exynos_drm_subdrv *drm_subdrv); /* this function removes subdrv list from exynos drm driver */ int exynos_drm_subdrv_unregister(struct exynos_drm_subdrv *drm_subdrv); +int exynos_drm_device_subdrv_probe(struct drm_device *dev); +int exynos_drm_device_subdrv_remove(struct drm_device *dev); int exynos_drm_subdrv_open(struct drm_device *dev, struct drm_file *file); void exynos_drm_subdrv_close(struct drm_device *dev, struct drm_file *file); @@ -339,9 +338,41 @@ int exynos_platform_device_ipp_register(void); */ void exynos_platform_device_ipp_unregister(void); +#ifdef CONFIG_DRM_EXYNOS_DPI +struct exynos_drm_display * exynos_dpi_probe(struct device *dev); +int exynos_dpi_remove(struct device *dev); +#else +static inline struct exynos_drm_display * +exynos_dpi_probe(struct device *dev) { return NULL; } +static inline int exynos_dpi_remove(struct device *dev) { return 0; } +#endif + +/* + * this function registers exynos drm vidi platform device/driver. + */ +int exynos_drm_probe_vidi(void); + +/* + * this function unregister exynos drm vidi platform device/driver. + */ +void exynos_drm_remove_vidi(void); + +/* This function creates a encoder and a connector, and initializes them. */ +int exynos_drm_create_enc_conn(struct drm_device *dev, + struct exynos_drm_display *display); + +int exynos_drm_component_add(struct device *dev, + enum exynos_drm_device_type dev_type, + enum exynos_drm_output_type out_type); + +void exynos_drm_component_del(struct device *dev, + enum exynos_drm_device_type dev_type); + extern struct platform_driver fimd_driver; -extern struct platform_driver hdmi_driver; +extern struct platform_driver dp_driver; +extern struct platform_driver dsi_driver; extern struct platform_driver mixer_driver; +extern struct platform_driver hdmi_driver; extern struct platform_driver exynos_drm_common_hdmi_driver; extern struct platform_driver vidi_driver; extern struct platform_driver g2d_driver; diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c new file mode 100644 index 00000000000..6302aa64f6c --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -0,0 +1,1546 @@ +/* + * Samsung SoC MIPI DSI Master driver. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * + * Contacts: Tomasz Figa <t.figa@samsung.com> + * + * 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h> +#include <linux/component.h> + +#include <video/mipi_display.h> +#include <video/videomode.h> + +#include "exynos_drm_drv.h" + +/* returns true iff both arguments logically differs */ +#define NEQV(a, b) (!(a) ^ !(b)) + +#define DSIM_STATUS_REG 0x0 /* Status register */ +#define DSIM_SWRST_REG 0x4 /* Software reset register */ +#define DSIM_CLKCTRL_REG 0x8 /* Clock control register */ +#define DSIM_TIMEOUT_REG 0xc /* Time out register */ +#define DSIM_CONFIG_REG 0x10 /* Configuration register */ +#define DSIM_ESCMODE_REG 0x14 /* Escape mode register */ + +/* Main display image resolution register */ +#define DSIM_MDRESOL_REG 0x18 +#define DSIM_MVPORCH_REG 0x1c /* Main display Vporch register */ +#define DSIM_MHPORCH_REG 0x20 /* Main display Hporch register */ +#define DSIM_MSYNC_REG 0x24 /* Main display sync area register */ + +/* Sub display image resolution register */ +#define DSIM_SDRESOL_REG 0x28 +#define DSIM_INTSRC_REG 0x2c /* Interrupt source register */ +#define DSIM_INTMSK_REG 0x30 /* Interrupt mask register */ +#define DSIM_PKTHDR_REG 0x34 /* Packet Header FIFO register */ +#define DSIM_PAYLOAD_REG 0x38 /* Payload FIFO register */ +#define DSIM_RXFIFO_REG 0x3c /* Read FIFO register */ +#define DSIM_FIFOTHLD_REG 0x40 /* FIFO threshold level register */ +#define DSIM_FIFOCTRL_REG 0x44 /* FIFO status and control register */ + +/* FIFO memory AC characteristic register */ +#define DSIM_PLLCTRL_REG 0x4c /* PLL control register */ +#define DSIM_PLLTMR_REG 0x50 /* PLL timer register */ +#define DSIM_PHYACCHR_REG 0x54 /* D-PHY AC characteristic register */ +#define DSIM_PHYACCHR1_REG 0x58 /* D-PHY AC characteristic register1 */ + +/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) +#define DSIM_PLL_STABLE (1 << 31) + +/* DSIM_SWRST */ +#define DSIM_FUNCRST (1 << 16) +#define DSIM_SWRST (1 << 0) + +/* DSIM_TIMEOUT */ +#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) +#define DSIM_BTA_TIMEOUT(x) ((x) << 16) + +/* DSIM_CLKCTRL */ +#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) +#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) +#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) +#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) +#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) +#define DSIM_BYTE_CLKEN (1 << 24) +#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) +#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) +#define DSIM_PLL_BYPASS (1 << 27) +#define DSIM_ESC_CLKEN (1 << 28) +#define DSIM_TX_REQUEST_HSCLK (1 << 31) + +/* DSIM_CONFIG */ +#define DSIM_LANE_EN_CLK (1 << 0) +#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) +#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) +#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) +#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) +#define DSIM_SUB_VC (((x) & 0x3) << 16) +#define DSIM_MAIN_VC (((x) & 0x3) << 18) +#define DSIM_HSA_MODE (1 << 20) +#define DSIM_HBP_MODE (1 << 21) +#define DSIM_HFP_MODE (1 << 22) +#define DSIM_HSE_MODE (1 << 23) +#define DSIM_AUTO_MODE (1 << 24) +#define DSIM_VIDEO_MODE (1 << 25) +#define DSIM_BURST_MODE (1 << 26) +#define DSIM_SYNC_INFORM (1 << 27) +#define DSIM_EOT_DISABLE (1 << 28) +#define DSIM_MFLUSH_VS (1 << 29) + +/* DSIM_ESCMODE */ +#define DSIM_TX_TRIGGER_RST (1 << 4) +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_BTA (1 << 16) +#define DSIM_FORCE_STOP_STATE (1 << 20) +#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) +#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21) + +/* DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x) (((x) & 0x7ff) << 16) +#define DSIM_MAIN_HRESOL(x) (((x) & 0X7ff) << 0) + +/* DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW(x) ((x) << 28) +#define DSIM_STABLE_VFP(x) ((x) << 16) +#define DSIM_MAIN_VBP(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0) + +/* DSIM_MHPORCH */ +#define DSIM_MAIN_HFP(x) ((x) << 16) +#define DSIM_MAIN_HBP(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0) + +/* DSIM_MSYNC */ +#define DSIM_MAIN_VSA(x) ((x) << 22) +#define DSIM_MAIN_HSA(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0) + +/* DSIM_SDRESOL */ +#define DSIM_SUB_STANDY(x) ((x) << 31) +#define DSIM_SUB_VRESOL(x) ((x) << 16) +#define DSIM_SUB_HRESOL(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0) + +/* DSIM_INTSRC */ +#define DSIM_INT_PLL_STABLE (1 << 31) +#define DSIM_INT_SW_RST_RELEASE (1 << 30) +#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) +#define DSIM_INT_BTA (1 << 25) +#define DSIM_INT_FRAME_DONE (1 << 24) +#define DSIM_INT_RX_TIMEOUT (1 << 21) +#define DSIM_INT_BTA_TIMEOUT (1 << 20) +#define DSIM_INT_RX_DONE (1 << 18) +#define DSIM_INT_RX_TE (1 << 17) +#define DSIM_INT_RX_ACK (1 << 16) +#define DSIM_INT_RX_ECC_ERR (1 << 15) +#define DSIM_INT_RX_CRC_ERR (1 << 14) + +/* DSIM_FIFOCTRL */ +#define DSIM_RX_DATA_FULL (1 << 25) +#define DSIM_RX_DATA_EMPTY (1 << 24) +#define DSIM_SFR_HEADER_FULL (1 << 23) +#define DSIM_SFR_HEADER_EMPTY (1 << 22) +#define DSIM_SFR_PAYLOAD_FULL (1 << 21) +#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) +#define DSIM_I80_HEADER_FULL (1 << 19) +#define DSIM_I80_HEADER_EMPTY (1 << 18) +#define DSIM_I80_PAYLOAD_FULL (1 << 17) +#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) +#define DSIM_SD_HEADER_FULL (1 << 15) +#define DSIM_SD_HEADER_EMPTY (1 << 14) +#define DSIM_SD_PAYLOAD_FULL (1 << 13) +#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) +#define DSIM_MD_HEADER_FULL (1 << 11) +#define DSIM_MD_HEADER_EMPTY (1 << 10) +#define DSIM_MD_PAYLOAD_FULL (1 << 9) +#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) +#define DSIM_RX_FIFO (1 << 4) +#define DSIM_SFR_FIFO (1 << 3) +#define DSIM_I80_FIFO (1 << 2) +#define DSIM_SD_FIFO (1 << 1) +#define DSIM_MD_FIFO (1 << 0) + +/* DSIM_PHYACCHR */ +#define DSIM_AFC_EN (1 << 14) +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5) + +/* DSIM_PLLCTRL */ +#define DSIM_FREQ_BAND(x) ((x) << 24) +#define DSIM_PLL_EN (1 << 23) +#define DSIM_PLL_P(x) ((x) << 13) +#define DSIM_PLL_M(x) ((x) << 4) +#define DSIM_PLL_S(x) ((x) << 1) + +#define DSI_MAX_BUS_WIDTH 4 +#define DSI_NUM_VIRTUAL_CHANNELS 4 +#define DSI_TX_FIFO_SIZE 2048 +#define DSI_RX_FIFO_SIZE 256 +#define DSI_XFER_TIMEOUT_MS 100 +#define DSI_RX_FIFO_EMPTY 0x30800002 + +enum exynos_dsi_transfer_type { + EXYNOS_DSI_TX, + EXYNOS_DSI_RX, +}; + +struct exynos_dsi_transfer { + struct list_head list; + struct completion completed; + int result; + u8 data_id; + u8 data[2]; + u16 flags; + + const u8 *tx_payload; + u16 tx_len; + u16 tx_done; + + u8 *rx_payload; + u16 rx_len; + u16 rx_done; +}; + +#define DSIM_STATE_ENABLED BIT(0) +#define DSIM_STATE_INITIALIZED BIT(1) +#define DSIM_STATE_CMD_LPM BIT(2) + +struct exynos_dsi { + struct mipi_dsi_host dsi_host; + struct drm_connector connector; + struct drm_encoder *encoder; + struct device_node *panel_node; + struct drm_panel *panel; + struct device *dev; + + void __iomem *reg_base; + struct phy *phy; + struct clk *pll_clk; + struct clk *bus_clk; + struct regulator_bulk_data supplies[2]; + int irq; + + u32 pll_clk_rate; + u32 burst_clk_rate; + u32 esc_clk_rate; + u32 lanes; + u32 mode_flags; + u32 format; + struct videomode vm; + + int state; + struct drm_property *brightness; + struct completion completed; + + spinlock_t transfer_lock; /* protects transfer_list */ + struct list_head transfer_list; +}; + +#define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) +#define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector) + +static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) +{ + if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300))) + return; + + dev_err(dsi->dev, "timeout waiting for reset\n"); +} + +static void exynos_dsi_reset(struct exynos_dsi *dsi) +{ + reinit_completion(&dsi->completed); + writel(DSIM_SWRST, dsi->reg_base + DSIM_SWRST_REG); +} + +#ifndef MHZ +#define MHZ (1000*1000) +#endif + +static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi, + unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s) +{ + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u8 p_min, p_max; + u8 _p, uninitialized_var(best_p); + u16 _m, uninitialized_var(best_m); + u8 _s, uninitialized_var(best_s); + + p_min = DIV_ROUND_UP(fin, (12 * MHZ)); + p_max = fin / (6 * MHZ); + + for (_p = p_min; _p <= p_max; ++_p) { + for (_s = 0; _s <= 5; ++_s) { + u64 tmp; + u32 delta; + + tmp = (u64)fout * (_p << _s); + do_div(tmp, fin); + _m = tmp; + if (_m < 41 || _m > 125) + continue; + + tmp = (u64)_m * fin; + do_div(tmp, _p); + if (tmp < 500 * MHZ || tmp > 1000 * MHZ) + continue; + + tmp = (u64)_m * fin; + do_div(tmp, _p << _s); + + delta = abs(fout - tmp); + if (delta < min_delta) { + best_p = _p; + best_m = _m; + best_s = _s; + min_delta = delta; + best_freq = tmp; + } + } + } + + if (best_freq) { + *p = best_p; + *m = best_m; + *s = best_s; + } + + return best_freq; +} + +static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi, + unsigned long freq) +{ + static const unsigned long freq_bands[] = { + 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ, + 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ, + 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ, + 770 * MHZ, 870 * MHZ, 950 * MHZ, + }; + unsigned long fin, fout; + int timeout, band; + u8 p, s; + u16 m; + u32 reg; + + clk_set_rate(dsi->pll_clk, dsi->pll_clk_rate); + + fin = clk_get_rate(dsi->pll_clk); + if (!fin) { + dev_err(dsi->dev, "failed to get PLL clock frequency\n"); + return 0; + } + + dev_dbg(dsi->dev, "PLL input frequency: %lu\n", fin); + + fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s); + if (!fout) { + dev_err(dsi->dev, + "failed to find PLL PMS for requested frequency\n"); + return -EFAULT; + } + + for (band = 0; band < ARRAY_SIZE(freq_bands); ++band) + if (fout < freq_bands[band]) + break; + + dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d), band %d\n", fout, + p, m, s, band); + + writel(500, dsi->reg_base + DSIM_PLLTMR_REG); + + reg = DSIM_FREQ_BAND(band) | DSIM_PLL_EN + | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s); + writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG); + + timeout = 1000; + do { + if (timeout-- == 0) { + dev_err(dsi->dev, "PLL failed to stabilize\n"); + return -EFAULT; + } + reg = readl(dsi->reg_base + DSIM_STATUS_REG); + } while ((reg & DSIM_PLL_STABLE) == 0); + + return fout; +} + +static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) +{ + unsigned long hs_clk, byte_clk, esc_clk; + unsigned long esc_div; + u32 reg; + + hs_clk = exynos_dsi_set_pll(dsi, dsi->burst_clk_rate); + if (!hs_clk) { + dev_err(dsi->dev, "failed to configure DSI PLL\n"); + return -EFAULT; + } + + byte_clk = hs_clk / 8; + esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate); + esc_clk = byte_clk / esc_div; + + if (esc_clk > 20 * MHZ) { + ++esc_div; + esc_clk = byte_clk / esc_div; + } + + dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n", + hs_clk, byte_clk, esc_clk); + + reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG); + reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS + | DSIM_BYTE_CLK_SRC_MASK); + reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN + | DSIM_ESC_PRESCALER(esc_div) + | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1) + | DSIM_BYTE_CLK_SRC(0) + | DSIM_TX_REQUEST_HSCLK; + writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG); + + return 0; +} + +static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) +{ + u32 reg; + + reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG); + reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK + | DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN); + writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG); + + reg = readl(dsi->reg_base + DSIM_PLLCTRL_REG); + reg &= ~DSIM_PLL_EN; + writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG); +} + +static int exynos_dsi_init_link(struct exynos_dsi *dsi) +{ + int timeout; + u32 reg; + u32 lanes_mask; + + /* Initialize FIFO pointers */ + reg = readl(dsi->reg_base + DSIM_FIFOCTRL_REG); + reg &= ~0x1f; + writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG); + + usleep_range(9000, 11000); + + reg |= 0x1f; + writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG); + + usleep_range(9000, 11000); + + /* DSI configuration */ + reg = 0; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + reg |= DSIM_VIDEO_MODE; + + if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH)) + reg |= DSIM_MFLUSH_VS; + if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)) + reg |= DSIM_EOT_DISABLE; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + reg |= DSIM_SYNC_INFORM; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + reg |= DSIM_BURST_MODE; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT) + reg |= DSIM_AUTO_MODE; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE) + reg |= DSIM_HSE_MODE; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP)) + reg |= DSIM_HFP_MODE; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP)) + reg |= DSIM_HBP_MODE; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA)) + reg |= DSIM_HSA_MODE; + } + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + reg |= DSIM_MAIN_PIX_FORMAT_RGB888; + break; + case MIPI_DSI_FMT_RGB666: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P; + break; + case MIPI_DSI_FMT_RGB565: + reg |= DSIM_MAIN_PIX_FORMAT_RGB565; + break; + default: + dev_err(dsi->dev, "invalid pixel format\n"); + return -EINVAL; + } + + reg |= DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1); + + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + reg |= DSIM_LANE_EN_CLK; + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + lanes_mask = BIT(dsi->lanes) - 1; + reg |= DSIM_LANE_EN(lanes_mask); + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + /* Check clock and data lane state are stop state */ + timeout = 100; + do { + if (timeout-- == 0) { + dev_err(dsi->dev, "waiting for bus lanes timed out\n"); + return -EFAULT; + } + + reg = readl(dsi->reg_base + DSIM_STATUS_REG); + if ((reg & DSIM_STOP_STATE_DAT(lanes_mask)) + != DSIM_STOP_STATE_DAT(lanes_mask)) + continue; + } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK))); + + reg = readl(dsi->reg_base + DSIM_ESCMODE_REG); + reg &= ~DSIM_STOP_STATE_CNT_MASK; + reg |= DSIM_STOP_STATE_CNT(0xf); + writel(reg, dsi->reg_base + DSIM_ESCMODE_REG); + + reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff); + writel(reg, dsi->reg_base + DSIM_TIMEOUT_REG); + + return 0; +} + +static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi) +{ + struct videomode *vm = &dsi->vm; + u32 reg; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + reg = DSIM_CMD_ALLOW(0xf) + | DSIM_STABLE_VFP(vm->vfront_porch) + | DSIM_MAIN_VBP(vm->vback_porch); + writel(reg, dsi->reg_base + DSIM_MVPORCH_REG); + + reg = DSIM_MAIN_HFP(vm->hfront_porch) + | DSIM_MAIN_HBP(vm->hback_porch); + writel(reg, dsi->reg_base + DSIM_MHPORCH_REG); + + reg = DSIM_MAIN_VSA(vm->vsync_len) + | DSIM_MAIN_HSA(vm->hsync_len); + writel(reg, dsi->reg_base + DSIM_MSYNC_REG); + } + + reg = DSIM_MAIN_HRESOL(vm->hactive) | DSIM_MAIN_VRESOL(vm->vactive); + writel(reg, dsi->reg_base + DSIM_MDRESOL_REG); + + dev_dbg(dsi->dev, "LCD size = %dx%d\n", vm->hactive, vm->vactive); +} + +static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable) +{ + u32 reg; + + reg = readl(dsi->reg_base + DSIM_MDRESOL_REG); + if (enable) + reg |= DSIM_MAIN_STAND_BY; + else + reg &= ~DSIM_MAIN_STAND_BY; + writel(reg, dsi->reg_base + DSIM_MDRESOL_REG); +} + +static int exynos_dsi_wait_for_hdr_fifo(struct exynos_dsi *dsi) +{ + int timeout = 2000; + + do { + u32 reg = readl(dsi->reg_base + DSIM_FIFOCTRL_REG); + + if (!(reg & DSIM_SFR_HEADER_FULL)) + return 0; + + if (!cond_resched()) + usleep_range(950, 1050); + } while (--timeout); + + return -ETIMEDOUT; +} + +static void exynos_dsi_set_cmd_lpm(struct exynos_dsi *dsi, bool lpm) +{ + u32 v = readl(dsi->reg_base + DSIM_ESCMODE_REG); + + if (lpm) + v |= DSIM_CMD_LPDT_LP; + else + v &= ~DSIM_CMD_LPDT_LP; + + writel(v, dsi->reg_base + DSIM_ESCMODE_REG); +} + +static void exynos_dsi_force_bta(struct exynos_dsi *dsi) +{ + u32 v = readl(dsi->reg_base + DSIM_ESCMODE_REG); + + v |= DSIM_FORCE_BTA; + writel(v, dsi->reg_base + DSIM_ESCMODE_REG); +} + +static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + struct device *dev = dsi->dev; + const u8 *payload = xfer->tx_payload + xfer->tx_done; + u16 length = xfer->tx_len - xfer->tx_done; + bool first = !xfer->tx_done; + u32 reg; + + dev_dbg(dev, "< xfer %p: tx len %u, done %u, rx len %u, done %u\n", + xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done); + + if (length > DSI_TX_FIFO_SIZE) + length = DSI_TX_FIFO_SIZE; + + xfer->tx_done += length; + + /* Send payload */ + while (length >= 4) { + reg = (payload[3] << 24) | (payload[2] << 16) + | (payload[1] << 8) | payload[0]; + writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG); + payload += 4; + length -= 4; + } + + reg = 0; + switch (length) { + case 3: + reg |= payload[2] << 16; + /* Fall through */ + case 2: + reg |= payload[1] << 8; + /* Fall through */ + case 1: + reg |= payload[0]; + writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG); + break; + case 0: + /* Do nothing */ + break; + } + + /* Send packet header */ + if (!first) + return; + + reg = (xfer->data[1] << 16) | (xfer->data[0] << 8) | xfer->data_id; + if (exynos_dsi_wait_for_hdr_fifo(dsi)) { + dev_err(dev, "waiting for header FIFO timed out\n"); + return; + } + + if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM, + dsi->state & DSIM_STATE_CMD_LPM)) { + exynos_dsi_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM); + dsi->state ^= DSIM_STATE_CMD_LPM; + } + + writel(reg, dsi->reg_base + DSIM_PKTHDR_REG); + + if (xfer->flags & MIPI_DSI_MSG_REQ_ACK) + exynos_dsi_force_bta(dsi); +} + +static void exynos_dsi_read_from_fifo(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + u8 *payload = xfer->rx_payload + xfer->rx_done; + bool first = !xfer->rx_done; + struct device *dev = dsi->dev; + u16 length; + u32 reg; + + if (first) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + + switch (reg & 0x3f) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + if (xfer->rx_len >= 2) { + payload[1] = reg >> 16; + ++xfer->rx_done; + } + /* Fall through */ + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + payload[0] = reg >> 8; + ++xfer->rx_done; + xfer->rx_len = xfer->rx_done; + xfer->result = 0; + goto clear_fifo; + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + dev_err(dev, "DSI Error Report: 0x%04x\n", + (reg >> 8) & 0xffff); + xfer->result = 0; + goto clear_fifo; + } + + length = (reg >> 8) & 0xffff; + if (length > xfer->rx_len) { + dev_err(dev, + "response too long (%u > %u bytes), stripping\n", + xfer->rx_len, length); + length = xfer->rx_len; + } else if (length < xfer->rx_len) + xfer->rx_len = length; + } + + length = xfer->rx_len - xfer->rx_done; + xfer->rx_done += length; + + /* Receive payload */ + while (length >= 4) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + payload[0] = (reg >> 0) & 0xff; + payload[1] = (reg >> 8) & 0xff; + payload[2] = (reg >> 16) & 0xff; + payload[3] = (reg >> 24) & 0xff; + payload += 4; + length -= 4; + } + + if (length) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + switch (length) { + case 3: + payload[2] = (reg >> 16) & 0xff; + /* Fall through */ + case 2: + payload[1] = (reg >> 8) & 0xff; + /* Fall through */ + case 1: + payload[0] = reg & 0xff; + } + } + + if (xfer->rx_done == xfer->rx_len) + xfer->result = 0; + +clear_fifo: + length = DSI_RX_FIFO_SIZE / 4; + do { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + if (reg == DSI_RX_FIFO_EMPTY) + break; + } while (--length); +} + +static void exynos_dsi_transfer_start(struct exynos_dsi *dsi) +{ + unsigned long flags; + struct exynos_dsi_transfer *xfer; + bool start = false; + +again: + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (xfer->tx_len && xfer->tx_done == xfer->tx_len) + /* waiting for RX */ + return; + + exynos_dsi_send_to_fifo(dsi, xfer); + + if (xfer->tx_len || xfer->rx_len) + return; + + xfer->result = 0; + complete(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (start) + goto again; +} + +static bool exynos_dsi_transfer_finish(struct exynos_dsi *dsi) +{ + struct exynos_dsi_transfer *xfer; + unsigned long flags; + bool start = true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return false; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + dev_dbg(dsi->dev, + "> xfer %p, tx_len %u, tx_done %u, rx_len %u, rx_done %u\n", + xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done); + + if (xfer->tx_done != xfer->tx_len) + return true; + + if (xfer->rx_done != xfer->rx_len) + exynos_dsi_read_from_fifo(dsi, xfer); + + if (xfer->rx_done != xfer->rx_len) + return true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (!xfer->rx_len) + xfer->result = 0; + complete(&xfer->completed); + + return start; +} + +static void exynos_dsi_remove_transfer(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + unsigned long flags; + bool start; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (!list_empty(&dsi->transfer_list) && + xfer == list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list)) { + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + if (start) + exynos_dsi_transfer_start(dsi); + return; + } + + list_del_init(&xfer->list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); +} + +static int exynos_dsi_transfer(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + unsigned long flags; + bool stopped; + + xfer->tx_done = 0; + xfer->rx_done = 0; + xfer->result = -ETIMEDOUT; + init_completion(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + stopped = list_empty(&dsi->transfer_list); + list_add_tail(&xfer->list, &dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (stopped) + exynos_dsi_transfer_start(dsi); + + wait_for_completion_timeout(&xfer->completed, + msecs_to_jiffies(DSI_XFER_TIMEOUT_MS)); + if (xfer->result == -ETIMEDOUT) { + exynos_dsi_remove_transfer(dsi, xfer); + dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 2, xfer->data, + xfer->tx_len, xfer->tx_payload); + return -ETIMEDOUT; + } + + /* Also covers hardware timeout condition */ + return xfer->result; +} + +static irqreturn_t exynos_dsi_irq(int irq, void *dev_id) +{ + struct exynos_dsi *dsi = dev_id; + u32 status; + + status = readl(dsi->reg_base + DSIM_INTSRC_REG); + if (!status) { + static unsigned long int j; + if (printk_timed_ratelimit(&j, 500)) + dev_warn(dsi->dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + writel(status, dsi->reg_base + DSIM_INTSRC_REG); + + if (status & DSIM_INT_SW_RST_RELEASE) { + u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY); + writel(mask, dsi->reg_base + DSIM_INTMSK_REG); + complete(&dsi->completed); + return IRQ_HANDLED; + } + + if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY))) + return IRQ_HANDLED; + + if (exynos_dsi_transfer_finish(dsi)) + exynos_dsi_transfer_start(dsi); + + return IRQ_HANDLED; +} + +static int exynos_dsi_init(struct exynos_dsi *dsi) +{ + exynos_dsi_enable_clock(dsi); + exynos_dsi_reset(dsi); + enable_irq(dsi->irq); + exynos_dsi_wait_for_reset(dsi); + exynos_dsi_init_link(dsi); + + return 0; +} + +static int exynos_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct exynos_dsi *dsi = host_to_dsi(host); + + dsi->lanes = device->lanes; + dsi->format = device->format; + dsi->mode_flags = device->mode_flags; + dsi->panel_node = device->dev.of_node; + + if (dsi->connector.dev) + drm_helper_hpd_irq_event(dsi->connector.dev); + + return 0; +} + +static int exynos_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct exynos_dsi *dsi = host_to_dsi(host); + + dsi->panel_node = NULL; + + if (dsi->connector.dev) + drm_helper_hpd_irq_event(dsi->connector.dev); + + return 0; +} + +/* distinguish between short and long DSI packet types */ +static bool exynos_dsi_is_short_dsi_type(u8 type) +{ + return (type & 0x0f) <= 8; +} + +static ssize_t exynos_dsi_host_transfer(struct mipi_dsi_host *host, + struct mipi_dsi_msg *msg) +{ + struct exynos_dsi *dsi = host_to_dsi(host); + struct exynos_dsi_transfer xfer; + int ret; + + if (!(dsi->state & DSIM_STATE_INITIALIZED)) { + ret = exynos_dsi_init(dsi); + if (ret) + return ret; + dsi->state |= DSIM_STATE_INITIALIZED; + } + + if (msg->tx_len == 0) + return -EINVAL; + + xfer.data_id = msg->type | (msg->channel << 6); + + if (exynos_dsi_is_short_dsi_type(msg->type)) { + const char *tx_buf = msg->tx_buf; + + if (msg->tx_len > 2) + return -EINVAL; + xfer.tx_len = 0; + xfer.data[0] = tx_buf[0]; + xfer.data[1] = (msg->tx_len == 2) ? tx_buf[1] : 0; + } else { + xfer.tx_len = msg->tx_len; + xfer.data[0] = msg->tx_len & 0xff; + xfer.data[1] = msg->tx_len >> 8; + xfer.tx_payload = msg->tx_buf; + } + + xfer.rx_len = msg->rx_len; + xfer.rx_payload = msg->rx_buf; + xfer.flags = msg->flags; + + ret = exynos_dsi_transfer(dsi, &xfer); + return (ret < 0) ? ret : xfer.rx_done; +} + +static const struct mipi_dsi_host_ops exynos_dsi_ops = { + .attach = exynos_dsi_host_attach, + .detach = exynos_dsi_host_detach, + .transfer = exynos_dsi_host_transfer, +}; + +static int exynos_dsi_poweron(struct exynos_dsi *dsi) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) { + dev_err(dsi->dev, "cannot enable regulators %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dsi->bus_clk); + if (ret < 0) { + dev_err(dsi->dev, "cannot enable bus clock %d\n", ret); + goto err_bus_clk; + } + + ret = clk_prepare_enable(dsi->pll_clk); + if (ret < 0) { + dev_err(dsi->dev, "cannot enable pll clock %d\n", ret); + goto err_pll_clk; + } + + ret = phy_power_on(dsi->phy); + if (ret < 0) { + dev_err(dsi->dev, "cannot enable phy %d\n", ret); + goto err_phy; + } + + return 0; + +err_phy: + clk_disable_unprepare(dsi->pll_clk); +err_pll_clk: + clk_disable_unprepare(dsi->bus_clk); +err_bus_clk: + regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + + return ret; +} + +static void exynos_dsi_poweroff(struct exynos_dsi *dsi) +{ + int ret; + + usleep_range(10000, 20000); + + if (dsi->state & DSIM_STATE_INITIALIZED) { + dsi->state &= ~DSIM_STATE_INITIALIZED; + + exynos_dsi_disable_clock(dsi); + + disable_irq(dsi->irq); + } + + dsi->state &= ~DSIM_STATE_CMD_LPM; + + phy_power_off(dsi->phy); + + clk_disable_unprepare(dsi->pll_clk); + clk_disable_unprepare(dsi->bus_clk); + + ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) + dev_err(dsi->dev, "cannot disable regulators %d\n", ret); +} + +static int exynos_dsi_enable(struct exynos_dsi *dsi) +{ + int ret; + + if (dsi->state & DSIM_STATE_ENABLED) + return 0; + + ret = exynos_dsi_poweron(dsi); + if (ret < 0) + return ret; + + ret = drm_panel_enable(dsi->panel); + if (ret < 0) { + exynos_dsi_poweroff(dsi); + return ret; + } + + exynos_dsi_set_display_mode(dsi); + exynos_dsi_set_display_enable(dsi, true); + + dsi->state |= DSIM_STATE_ENABLED; + + return 0; +} + +static void exynos_dsi_disable(struct exynos_dsi *dsi) +{ + if (!(dsi->state & DSIM_STATE_ENABLED)) + return; + + exynos_dsi_set_display_enable(dsi, false); + drm_panel_disable(dsi->panel); + exynos_dsi_poweroff(dsi); + + dsi->state &= ~DSIM_STATE_ENABLED; +} + +static void exynos_dsi_dpms(struct exynos_drm_display *display, int mode) +{ + struct exynos_dsi *dsi = display->ctx; + + if (dsi->panel) { + switch (mode) { + case DRM_MODE_DPMS_ON: + exynos_dsi_enable(dsi); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + exynos_dsi_disable(dsi); + break; + default: + break; + } + } +} + +static enum drm_connector_status +exynos_dsi_detect(struct drm_connector *connector, bool force) +{ + struct exynos_dsi *dsi = connector_to_dsi(connector); + + if (!dsi->panel) { + dsi->panel = of_drm_find_panel(dsi->panel_node); + if (dsi->panel) + drm_panel_attach(dsi->panel, &dsi->connector); + } else if (!dsi->panel_node) { + struct exynos_drm_display *display; + + display = platform_get_drvdata(to_platform_device(dsi->dev)); + exynos_dsi_dpms(display, DRM_MODE_DPMS_OFF); + drm_panel_detach(dsi->panel); + dsi->panel = NULL; + } + + if (dsi->panel) + return connector_status_connected; + + return connector_status_disconnected; +} + +static void exynos_dsi_connector_destroy(struct drm_connector *connector) +{ +} + +static struct drm_connector_funcs exynos_dsi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = exynos_dsi_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = exynos_dsi_connector_destroy, +}; + +static int exynos_dsi_get_modes(struct drm_connector *connector) +{ + struct exynos_dsi *dsi = connector_to_dsi(connector); + + if (dsi->panel) + return dsi->panel->funcs->get_modes(dsi->panel); + + return 0; +} + +static int exynos_dsi_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static struct drm_encoder * +exynos_dsi_best_encoder(struct drm_connector *connector) +{ + struct exynos_dsi *dsi = connector_to_dsi(connector); + + return dsi->encoder; +} + +static struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs = { + .get_modes = exynos_dsi_get_modes, + .mode_valid = exynos_dsi_mode_valid, + .best_encoder = exynos_dsi_best_encoder, +}; + +static int exynos_dsi_create_connector(struct exynos_drm_display *display, + struct drm_encoder *encoder) +{ + struct exynos_dsi *dsi = display->ctx; + struct drm_connector *connector = &dsi->connector; + int ret; + + dsi->encoder = encoder; + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(encoder->dev, connector, + &exynos_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(connector, &exynos_dsi_connector_helper_funcs); + drm_sysfs_connector_add(connector); + drm_mode_connector_attach_encoder(connector, encoder); + + return 0; +} + +static void exynos_dsi_mode_set(struct exynos_drm_display *display, + struct drm_display_mode *mode) +{ + struct exynos_dsi *dsi = display->ctx; + struct videomode *vm = &dsi->vm; + + vm->hactive = mode->hdisplay; + vm->vactive = mode->vdisplay; + vm->vfront_porch = mode->vsync_start - mode->vdisplay; + vm->vback_porch = mode->vtotal - mode->vsync_end; + vm->vsync_len = mode->vsync_end - mode->vsync_start; + vm->hfront_porch = mode->hsync_start - mode->hdisplay; + vm->hback_porch = mode->htotal - mode->hsync_end; + vm->hsync_len = mode->hsync_end - mode->hsync_start; +} + +static struct exynos_drm_display_ops exynos_dsi_display_ops = { + .create_connector = exynos_dsi_create_connector, + .mode_set = exynos_dsi_mode_set, + .dpms = exynos_dsi_dpms +}; + +static struct exynos_drm_display exynos_dsi_display = { + .type = EXYNOS_DISPLAY_TYPE_LCD, + .ops = &exynos_dsi_display_ops, +}; + +/* of_* functions will be removed after merge of of_graph patches */ +static struct device_node * +of_get_child_by_name_reg(struct device_node *parent, const char *name, u32 reg) +{ + struct device_node *np; + + for_each_child_of_node(parent, np) { + u32 r; + + if (!np->name || of_node_cmp(np->name, name)) + continue; + + if (of_property_read_u32(np, "reg", &r) < 0) + r = 0; + + if (reg == r) + break; + } + + return np; +} + +static struct device_node *of_graph_get_port_by_reg(struct device_node *parent, + u32 reg) +{ + struct device_node *ports, *port; + + ports = of_get_child_by_name(parent, "ports"); + if (ports) + parent = ports; + + port = of_get_child_by_name_reg(parent, "port", reg); + + of_node_put(ports); + + return port; +} + +static struct device_node * +of_graph_get_endpoint_by_reg(struct device_node *port, u32 reg) +{ + return of_get_child_by_name_reg(port, "endpoint", reg); +} + +static int exynos_dsi_of_read_u32(const struct device_node *np, + const char *propname, u32 *out_value) +{ + int ret = of_property_read_u32(np, propname, out_value); + + if (ret < 0) + pr_err("%s: failed to get '%s' property\n", np->full_name, + propname); + + return ret; +} + +enum { + DSI_PORT_IN, + DSI_PORT_OUT +}; + +static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) +{ + struct device *dev = dsi->dev; + struct device_node *node = dev->of_node; + struct device_node *port, *ep; + int ret; + + ret = exynos_dsi_of_read_u32(node, "samsung,pll-clock-frequency", + &dsi->pll_clk_rate); + if (ret < 0) + return ret; + + port = of_graph_get_port_by_reg(node, DSI_PORT_OUT); + if (!port) { + dev_err(dev, "no output port specified\n"); + return -EINVAL; + } + + ep = of_graph_get_endpoint_by_reg(port, 0); + of_node_put(port); + if (!ep) { + dev_err(dev, "no endpoint specified in output port\n"); + return -EINVAL; + } + + ret = exynos_dsi_of_read_u32(ep, "samsung,burst-clock-frequency", + &dsi->burst_clk_rate); + if (ret < 0) + goto end; + + ret = exynos_dsi_of_read_u32(ep, "samsung,esc-clock-frequency", + &dsi->esc_clk_rate); + +end: + of_node_put(ep); + + return ret; +} + +static int exynos_dsi_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm_dev = data; + struct exynos_dsi *dsi; + int ret; + + ret = exynos_drm_create_enc_conn(drm_dev, &exynos_dsi_display); + if (ret) { + DRM_ERROR("Encoder create [%d] failed with %d\n", + exynos_dsi_display.type, ret); + return ret; + } + + dsi = exynos_dsi_display.ctx; + + return mipi_dsi_host_register(&dsi->dsi_host); +} + +static void exynos_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct exynos_dsi *dsi = exynos_dsi_display.ctx; + struct drm_encoder *encoder = dsi->encoder; + + exynos_dsi_dpms(&exynos_dsi_display, DRM_MODE_DPMS_OFF); + + mipi_dsi_host_unregister(&dsi->dsi_host); + + encoder->funcs->destroy(encoder); + drm_connector_cleanup(&dsi->connector); +} + +static const struct component_ops exynos_dsi_component_ops = { + .bind = exynos_dsi_bind, + .unbind = exynos_dsi_unbind, +}; + +static int exynos_dsi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct exynos_dsi *dsi; + int ret; + + ret = exynos_drm_component_add(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR, + exynos_dsi_display.type); + if (ret) + return ret; + + dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) { + dev_err(&pdev->dev, "failed to allocate dsi object.\n"); + ret = -ENOMEM; + goto err_del_component; + } + + init_completion(&dsi->completed); + spin_lock_init(&dsi->transfer_lock); + INIT_LIST_HEAD(&dsi->transfer_list); + + dsi->dsi_host.ops = &exynos_dsi_ops; + dsi->dsi_host.dev = &pdev->dev; + + dsi->dev = &pdev->dev; + + ret = exynos_dsi_parse_dt(dsi); + if (ret) + goto err_del_component; + + dsi->supplies[0].supply = "vddcore"; + dsi->supplies[1].supply = "vddio"; + ret = devm_regulator_bulk_get(&pdev->dev, ARRAY_SIZE(dsi->supplies), + dsi->supplies); + if (ret) { + dev_info(&pdev->dev, "failed to get regulators: %d\n", ret); + return -EPROBE_DEFER; + } + + dsi->pll_clk = devm_clk_get(&pdev->dev, "pll_clk"); + if (IS_ERR(dsi->pll_clk)) { + dev_info(&pdev->dev, "failed to get dsi pll input clock\n"); + ret = PTR_ERR(dsi->pll_clk); + goto err_del_component; + } + + dsi->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); + if (IS_ERR(dsi->bus_clk)) { + dev_info(&pdev->dev, "failed to get dsi bus clock\n"); + ret = PTR_ERR(dsi->bus_clk); + goto err_del_component; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dsi->reg_base)) { + dev_err(&pdev->dev, "failed to remap io region\n"); + ret = PTR_ERR(dsi->reg_base); + goto err_del_component; + } + + dsi->phy = devm_phy_get(&pdev->dev, "dsim"); + if (IS_ERR(dsi->phy)) { + dev_info(&pdev->dev, "failed to get dsim phy\n"); + ret = PTR_ERR(dsi->phy); + goto err_del_component; + } + + dsi->irq = platform_get_irq(pdev, 0); + if (dsi->irq < 0) { + dev_err(&pdev->dev, "failed to request dsi irq resource\n"); + ret = dsi->irq; + goto err_del_component; + } + + irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, dsi->irq, NULL, + exynos_dsi_irq, IRQF_ONESHOT, + dev_name(&pdev->dev), dsi); + if (ret) { + dev_err(&pdev->dev, "failed to request dsi irq\n"); + goto err_del_component; + } + + exynos_dsi_display.ctx = dsi; + + platform_set_drvdata(pdev, &exynos_dsi_display); + + ret = component_add(&pdev->dev, &exynos_dsi_component_ops); + if (ret) + goto err_del_component; + + return ret; + +err_del_component: + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR); + return ret; +} + +static int exynos_dsi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &exynos_dsi_component_ops); + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR); + + return 0; +} + +static struct of_device_id exynos_dsi_of_match[] = { + { .compatible = "samsung,exynos4210-mipi-dsi" }, + { } +}; + +struct platform_driver dsi_driver = { + .probe = exynos_dsi_probe, + .remove = exynos_dsi_remove, + .driver = { + .name = "exynos-dsi", + .owner = THIS_MODULE, + .of_match_table = exynos_dsi_of_match, + }, +}; + +MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>"); +MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.c b/drivers/gpu/drm/exynos/exynos_drm_encoder.c index 06f1b2a09da..7e282e3d603 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.c +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.c @@ -17,7 +17,6 @@ #include "exynos_drm_drv.h" #include "exynos_drm_encoder.h" -#include "exynos_drm_connector.h" #define to_exynos_encoder(x) container_of(x, struct exynos_drm_encoder,\ drm_encoder) @@ -26,72 +25,22 @@ * exynos specific encoder structure. * * @drm_encoder: encoder object. - * @manager: specific encoder has its own manager to control a hardware - * appropriately and we can access a hardware drawing on this manager. - * @dpms: store the encoder dpms value. - * @updated: indicate whether overlay data updating is needed or not. + * @display: the display structure that maps to this encoder */ struct exynos_drm_encoder { - struct drm_crtc *old_crtc; struct drm_encoder drm_encoder; - struct exynos_drm_manager *manager; - int dpms; - bool updated; + struct exynos_drm_display *display; }; -static void exynos_drm_connector_power(struct drm_encoder *encoder, int mode) -{ - struct drm_device *dev = encoder->dev; - struct drm_connector *connector; - - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - if (exynos_drm_best_encoder(connector) == encoder) { - DRM_DEBUG_KMS("connector[%d] dpms[%d]\n", - connector->base.id, mode); - - exynos_drm_display_power(connector, mode); - } - } -} - static void exynos_drm_encoder_dpms(struct drm_encoder *encoder, int mode) { - struct drm_device *dev = encoder->dev; - struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); - struct exynos_drm_manager_ops *manager_ops = manager->ops; struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); + struct exynos_drm_display *display = exynos_encoder->display; DRM_DEBUG_KMS("encoder dpms: %d\n", mode); - if (exynos_encoder->dpms == mode) { - DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n"); - return; - } - - mutex_lock(&dev->struct_mutex); - - switch (mode) { - case DRM_MODE_DPMS_ON: - if (manager_ops && manager_ops->apply) - if (!exynos_encoder->updated) - manager_ops->apply(manager->dev); - - exynos_drm_connector_power(encoder, mode); - exynos_encoder->dpms = mode; - break; - case DRM_MODE_DPMS_STANDBY: - case DRM_MODE_DPMS_SUSPEND: - case DRM_MODE_DPMS_OFF: - exynos_drm_connector_power(encoder, mode); - exynos_encoder->dpms = mode; - exynos_encoder->updated = false; - break; - default: - DRM_ERROR("unspecified mode %d\n", mode); - break; - } - - mutex_unlock(&dev->struct_mutex); + if (display->ops->dpms) + display->ops->dpms(display, mode); } static bool @@ -100,87 +49,31 @@ exynos_drm_encoder_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *adjusted_mode) { struct drm_device *dev = encoder->dev; + struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); + struct exynos_drm_display *display = exynos_encoder->display; struct drm_connector *connector; - struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder); - struct exynos_drm_manager_ops *manager_ops = manager->ops; list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - if (connector->encoder == encoder) - if (manager_ops && manager_ops->mode_fixup) - manager_ops->mode_fixup(manager->dev, connector, - mode, adjusted_mode); + if (connector->encoder != encoder) + continue; + + if (display->ops->mode_fixup) + display->ops->mode_fixup(display, connector, mode, + adjusted_mode); } return true; } -static void disable_plane_to_crtc(struct drm_device *dev, - struct drm_crtc *old_crtc, - struct drm_crtc *new_crtc) -{ - struct drm_plane *plane; - - /* - * if old_crtc isn't same as encoder->crtc then it means that - * user changed crtc id to another one so the plane to old_crtc - * should be disabled and plane->crtc should be set to new_crtc - * (encoder->crtc) - */ - list_for_each_entry(plane, &dev->mode_config.plane_list, head) { - if (plane->crtc == old_crtc) { - /* - * do not change below call order. - * - * plane->funcs->disable_plane call checks - * if encoder->crtc is same as plane->crtc and if same - * then overlay_ops->disable callback will be called - * to diasble current hw overlay so plane->crtc should - * have new_crtc because new_crtc was set to - * encoder->crtc in advance. - */ - plane->crtc = new_crtc; - plane->funcs->disable_plane(plane); - } - } -} - static void exynos_drm_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { - struct drm_device *dev = encoder->dev; - struct drm_connector *connector; - struct exynos_drm_manager *manager; - struct exynos_drm_manager_ops *manager_ops; - - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - if (connector->encoder == encoder) { - struct exynos_drm_encoder *exynos_encoder; - - exynos_encoder = to_exynos_encoder(encoder); - - if (exynos_encoder->old_crtc != encoder->crtc && - exynos_encoder->old_crtc) { - - /* - * disable a plane to old crtc and change - * crtc of the plane to new one. - */ - disable_plane_to_crtc(dev, - exynos_encoder->old_crtc, - encoder->crtc); - } - - manager = exynos_drm_get_manager(encoder); - manager_ops = manager->ops; - - if (manager_ops && manager_ops->mode_set) - manager_ops->mode_set(manager->dev, - adjusted_mode); + struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); + struct exynos_drm_display *display = exynos_encoder->display; - exynos_encoder->old_crtc = encoder->crtc; - } - } + if (display->ops->mode_set) + display->ops->mode_set(display, adjusted_mode); } static void exynos_drm_encoder_prepare(struct drm_encoder *encoder) @@ -191,53 +84,15 @@ static void exynos_drm_encoder_prepare(struct drm_encoder *encoder) static void exynos_drm_encoder_commit(struct drm_encoder *encoder) { struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); - struct exynos_drm_manager *manager = exynos_encoder->manager; - struct exynos_drm_manager_ops *manager_ops = manager->ops; - - if (manager_ops && manager_ops->commit) - manager_ops->commit(manager->dev); - - /* - * this will avoid one issue that overlay data is updated to - * real hardware two times. - * And this variable will be used to check if the data was - * already updated or not by exynos_drm_encoder_dpms function. - */ - exynos_encoder->updated = true; - - /* - * In case of setcrtc, there is no way to update encoder's dpms - * so update it here. - */ - exynos_encoder->dpms = DRM_MODE_DPMS_ON; -} + struct exynos_drm_display *display = exynos_encoder->display; -void exynos_drm_encoder_complete_scanout(struct drm_framebuffer *fb) -{ - struct exynos_drm_encoder *exynos_encoder; - struct exynos_drm_manager_ops *ops; - struct drm_device *dev = fb->dev; - struct drm_encoder *encoder; + if (display->ops->dpms) + display->ops->dpms(display, DRM_MODE_DPMS_ON); - /* - * make sure that overlay data are updated to real hardware - * for all encoders. - */ - list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { - exynos_encoder = to_exynos_encoder(encoder); - ops = exynos_encoder->manager->ops; - - /* - * wait for vblank interrupt - * - this makes sure that overlay data are updated to - * real hardware. - */ - if (ops->wait_for_vblank) - ops->wait_for_vblank(exynos_encoder->manager->dev); - } + if (display->ops->commit) + display->ops->commit(display); } - static void exynos_drm_encoder_disable(struct drm_encoder *encoder) { struct drm_plane *plane; @@ -246,7 +101,7 @@ static void exynos_drm_encoder_disable(struct drm_encoder *encoder) exynos_drm_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); /* all planes connected to this encoder should be also disabled. */ - list_for_each_entry(plane, &dev->mode_config.plane_list, head) { + drm_for_each_legacy_plane(plane, &dev->mode_config.plane_list) { if (plane->crtc == encoder->crtc) plane->funcs->disable_plane(plane); } @@ -263,10 +118,7 @@ static struct drm_encoder_helper_funcs exynos_encoder_helper_funcs = { static void exynos_drm_encoder_destroy(struct drm_encoder *encoder) { - struct exynos_drm_encoder *exynos_encoder = - to_exynos_encoder(encoder); - - exynos_encoder->manager->pipe = -1; + struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); drm_encoder_cleanup(encoder); kfree(exynos_encoder); @@ -281,13 +133,12 @@ static unsigned int exynos_drm_encoder_clones(struct drm_encoder *encoder) struct drm_encoder *clone; struct drm_device *dev = encoder->dev; struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); - struct exynos_drm_display_ops *display_ops = - exynos_encoder->manager->display_ops; + struct exynos_drm_display *display = exynos_encoder->display; unsigned int clone_mask = 0; int cnt = 0; list_for_each_entry(clone, &dev->mode_config.encoder_list, head) { - switch (display_ops->type) { + switch (display->type) { case EXYNOS_DISPLAY_TYPE_LCD: case EXYNOS_DISPLAY_TYPE_HDMI: case EXYNOS_DISPLAY_TYPE_VIDI: @@ -311,24 +162,20 @@ void exynos_drm_encoder_setup(struct drm_device *dev) struct drm_encoder * exynos_drm_encoder_create(struct drm_device *dev, - struct exynos_drm_manager *manager, - unsigned int possible_crtcs) + struct exynos_drm_display *display, + unsigned long possible_crtcs) { struct drm_encoder *encoder; struct exynos_drm_encoder *exynos_encoder; - if (!manager || !possible_crtcs) - return NULL; - - if (!manager->dev) + if (!possible_crtcs) return NULL; exynos_encoder = kzalloc(sizeof(*exynos_encoder), GFP_KERNEL); if (!exynos_encoder) return NULL; - exynos_encoder->dpms = DRM_MODE_DPMS_OFF; - exynos_encoder->manager = manager; + exynos_encoder->display = display; encoder = &exynos_encoder->drm_encoder; encoder->possible_crtcs = possible_crtcs; @@ -344,149 +191,7 @@ exynos_drm_encoder_create(struct drm_device *dev, return encoder; } -struct exynos_drm_manager *exynos_drm_get_manager(struct drm_encoder *encoder) -{ - return to_exynos_encoder(encoder)->manager; -} - -void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data, - void (*fn)(struct drm_encoder *, void *)) -{ - struct drm_device *dev = crtc->dev; - struct drm_encoder *encoder; - struct exynos_drm_private *private = dev->dev_private; - struct exynos_drm_manager *manager; - - list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { - /* - * if crtc is detached from encoder, check pipe, - * otherwise check crtc attached to encoder - */ - if (!encoder->crtc) { - manager = to_exynos_encoder(encoder)->manager; - if (manager->pipe < 0 || - private->crtc[manager->pipe] != crtc) - continue; - } else { - if (encoder->crtc != crtc) - continue; - } - - fn(encoder, data); - } -} - -void exynos_drm_enable_vblank(struct drm_encoder *encoder, void *data) -{ - struct exynos_drm_manager *manager = - to_exynos_encoder(encoder)->manager; - struct exynos_drm_manager_ops *manager_ops = manager->ops; - int crtc = *(int *)data; - - if (manager->pipe != crtc) - return; - - if (manager_ops->enable_vblank) - manager_ops->enable_vblank(manager->dev); -} - -void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data) -{ - struct exynos_drm_manager *manager = - to_exynos_encoder(encoder)->manager; - struct exynos_drm_manager_ops *manager_ops = manager->ops; - int crtc = *(int *)data; - - if (manager->pipe != crtc) - return; - - if (manager_ops->disable_vblank) - manager_ops->disable_vblank(manager->dev); -} - -void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data) -{ - struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder); - struct exynos_drm_manager *manager = exynos_encoder->manager; - struct exynos_drm_manager_ops *manager_ops = manager->ops; - int mode = *(int *)data; - - if (manager_ops && manager_ops->dpms) - manager_ops->dpms(manager->dev, mode); - - /* - * if this condition is ok then it means that the crtc is already - * detached from encoder and last function for detaching is properly - * done, so clear pipe from manager to prevent repeated call. - */ - if (mode > DRM_MODE_DPMS_ON) { - if (!encoder->crtc) - manager->pipe = -1; - } -} - -void exynos_drm_encoder_crtc_pipe(struct drm_encoder *encoder, void *data) -{ - struct exynos_drm_manager *manager = - to_exynos_encoder(encoder)->manager; - int pipe = *(int *)data; - - /* - * when crtc is detached from encoder, this pipe is used - * to select manager operation - */ - manager->pipe = pipe; -} - -void exynos_drm_encoder_plane_mode_set(struct drm_encoder *encoder, void *data) -{ - struct exynos_drm_manager *manager = - to_exynos_encoder(encoder)->manager; - struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops; - struct exynos_drm_overlay *overlay = data; - - if (overlay_ops && overlay_ops->mode_set) - overlay_ops->mode_set(manager->dev, overlay); -} - -void exynos_drm_encoder_plane_commit(struct drm_encoder *encoder, void *data) +struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder) { - struct exynos_drm_manager *manager = - to_exynos_encoder(encoder)->manager; - struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops; - int zpos = DEFAULT_ZPOS; - - if (data) - zpos = *(int *)data; - - if (overlay_ops && overlay_ops->commit) - overlay_ops->commit(manager->dev, zpos); -} - -void exynos_drm_encoder_plane_enable(struct drm_encoder *encoder, void *data) -{ - struct exynos_drm_manager *manager = - to_exynos_encoder(encoder)->manager; - struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops; - int zpos = DEFAULT_ZPOS; - - if (data) - zpos = *(int *)data; - - if (overlay_ops && overlay_ops->enable) - overlay_ops->enable(manager->dev, zpos); -} - -void exynos_drm_encoder_plane_disable(struct drm_encoder *encoder, void *data) -{ - struct exynos_drm_manager *manager = - to_exynos_encoder(encoder)->manager; - struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops; - int zpos = DEFAULT_ZPOS; - - if (data) - zpos = *(int *)data; - - if (overlay_ops && overlay_ops->disable) - overlay_ops->disable(manager->dev, zpos); + return to_exynos_encoder(encoder)->display; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.h b/drivers/gpu/drm/exynos/exynos_drm_encoder.h index 89e2fb0770a..b7a1620a7e7 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.h +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.h @@ -18,20 +18,8 @@ struct exynos_drm_manager; void exynos_drm_encoder_setup(struct drm_device *dev); struct drm_encoder *exynos_drm_encoder_create(struct drm_device *dev, - struct exynos_drm_manager *mgr, - unsigned int possible_crtcs); -struct exynos_drm_manager * -exynos_drm_get_manager(struct drm_encoder *encoder); -void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data, - void (*fn)(struct drm_encoder *, void *)); -void exynos_drm_enable_vblank(struct drm_encoder *encoder, void *data); -void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data); -void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data); -void exynos_drm_encoder_crtc_pipe(struct drm_encoder *encoder, void *data); -void exynos_drm_encoder_plane_mode_set(struct drm_encoder *encoder, void *data); -void exynos_drm_encoder_plane_commit(struct drm_encoder *encoder, void *data); -void exynos_drm_encoder_plane_enable(struct drm_encoder *encoder, void *data); -void exynos_drm_encoder_plane_disable(struct drm_encoder *encoder, void *data); -void exynos_drm_encoder_complete_scanout(struct drm_framebuffer *fb); + struct exynos_drm_display *mgr, + unsigned long possible_crtcs); +struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder); #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c index ea39e0ef2ae..65a22cad7b3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fb.c @@ -20,9 +20,10 @@ #include "exynos_drm_drv.h" #include "exynos_drm_fb.h" +#include "exynos_drm_fbdev.h" #include "exynos_drm_gem.h" #include "exynos_drm_iommu.h" -#include "exynos_drm_encoder.h" +#include "exynos_drm_crtc.h" #define to_exynos_fb(x) container_of(x, struct exynos_drm_fb, fb) @@ -71,7 +72,7 @@ static void exynos_drm_fb_destroy(struct drm_framebuffer *fb) unsigned int i; /* make sure that overlay data are updated before relesing fb. */ - exynos_drm_encoder_complete_scanout(fb); + exynos_drm_crtc_complete_scanout(fb); drm_framebuffer_cleanup(fb); @@ -300,6 +301,8 @@ static void exynos_drm_output_poll_changed(struct drm_device *dev) if (fb_helper) drm_fb_helper_hotplug_event(fb_helper); + else + exynos_drm_fbdev_init(dev); } static const struct drm_mode_config_funcs exynos_drm_mode_config_funcs = { diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c index 78e868bcf1e..d771b467cf0 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c @@ -90,7 +90,7 @@ static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, /* RGB formats use only one buffer */ buffer = exynos_drm_fb_buffer(fb, 0); if (!buffer) { - DRM_LOG_KMS("buffer is null.\n"); + DRM_DEBUG_KMS("buffer is null.\n"); return -EFAULT; } @@ -99,12 +99,13 @@ static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, if (is_drm_iommu_supported(dev)) { unsigned int nr_pages = buffer->size >> PAGE_SHIFT; - buffer->kvaddr = vmap(buffer->pages, nr_pages, VM_MAP, + buffer->kvaddr = (void __iomem *) vmap(buffer->pages, + nr_pages, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); } else { phys_addr_t dma_addr = buffer->dma_addr; if (dma_addr) - buffer->kvaddr = phys_to_virt(dma_addr); + buffer->kvaddr = (void __iomem *)phys_to_virt(dma_addr); else buffer->kvaddr = (void __iomem *)NULL; } @@ -120,16 +121,8 @@ static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, offset = fbi->var.xoffset * (fb->bits_per_pixel >> 3); offset += fbi->var.yoffset * fb->pitches[0]; - dev->mode_config.fb_base = (resource_size_t)buffer->dma_addr; fbi->screen_base = buffer->kvaddr + offset; - if (is_drm_iommu_supported(dev)) - fbi->fix.smem_start = (unsigned long) - (page_to_phys(sg_page(buffer->sgt->sgl)) + offset); - else - fbi->fix.smem_start = (unsigned long)buffer->dma_addr; - fbi->screen_size = size; - fbi->fix.smem_len = size; return 0; } @@ -236,6 +229,24 @@ static struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = { .fb_probe = exynos_drm_fbdev_create, }; +static bool exynos_drm_fbdev_is_anything_connected(struct drm_device *dev) +{ + struct drm_connector *connector; + bool ret = false; + + mutex_lock(&dev->mode_config.mutex); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->status != connector_status_connected) + continue; + + ret = true; + break; + } + mutex_unlock(&dev->mode_config.mutex); + + return ret; +} + int exynos_drm_fbdev_init(struct drm_device *dev) { struct exynos_drm_fbdev *fbdev; @@ -247,6 +258,9 @@ int exynos_drm_fbdev_init(struct drm_device *dev) if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector) return 0; + if (!exynos_drm_fbdev_is_anything_connected(dev)) + return 0; + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); if (!fbdev) return -ENOMEM; @@ -353,7 +367,5 @@ void exynos_drm_fbdev_restore_mode(struct drm_device *dev) if (!private || !private->fb_helper) return; - drm_modeset_lock_all(dev); - drm_fb_helper_restore_fbdev_mode(private->fb_helper); - drm_modeset_unlock_all(dev); + drm_fb_helper_restore_fbdev_mode_unlocked(private->fb_helper); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimc.c b/drivers/gpu/drm/exynos/exynos_drm_fimc.c index 8adfc8f1e08..831dde9034c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimc.c @@ -18,6 +18,7 @@ #include <linux/clk.h> #include <linux/pm_runtime.h> #include <linux/of.h> +#include <linux/spinlock.h> #include <drm/drmP.h> #include <drm/exynos_drm.h> @@ -57,7 +58,6 @@ #define FIMC_SHFACTOR 10 #define FIMC_BUF_STOP 1 #define FIMC_BUF_START 2 -#define FIMC_REG_SZ 32 #define FIMC_WIDTH_ITU_709 1280 #define FIMC_REFRESH_MAX 60 #define FIMC_REFRESH_MIN 12 @@ -69,9 +69,6 @@ #define get_fimc_context(dev) platform_get_drvdata(to_platform_device(dev)) #define get_ctx_from_ippdrv(ippdrv) container_of(ippdrv,\ struct fimc_context, ippdrv); -#define fimc_read(offset) readl(ctx->regs + (offset)) -#define fimc_write(cfg, offset) writel(cfg, ctx->regs + (offset)) - enum fimc_wb { FIMC_WB_NONE, FIMC_WB_A, @@ -161,7 +158,7 @@ struct fimc_context { struct exynos_drm_ippdrv ippdrv; struct resource *regs_res; void __iomem *regs; - struct mutex lock; + spinlock_t lock; struct clk *clocks[FIMC_CLKS_MAX]; u32 clk_frequency; struct regmap *sysreg; @@ -172,39 +169,53 @@ struct fimc_context { bool suspended; }; +static u32 fimc_read(struct fimc_context *ctx, u32 reg) +{ + return readl(ctx->regs + reg); +} + +static void fimc_write(struct fimc_context *ctx, u32 val, u32 reg) +{ + writel(val, ctx->regs + reg); +} + +static void fimc_set_bits(struct fimc_context *ctx, u32 reg, u32 bits) +{ + void __iomem *r = ctx->regs + reg; + + writel(readl(r) | bits, r); +} + +static void fimc_clear_bits(struct fimc_context *ctx, u32 reg, u32 bits) +{ + void __iomem *r = ctx->regs + reg; + + writel(readl(r) & ~bits, r); +} + static void fimc_sw_reset(struct fimc_context *ctx) { u32 cfg; /* stop dma operation */ - cfg = fimc_read(EXYNOS_CISTATUS); - if (EXYNOS_CISTATUS_GET_ENVID_STATUS(cfg)) { - cfg = fimc_read(EXYNOS_MSCTRL); - cfg &= ~EXYNOS_MSCTRL_ENVID; - fimc_write(cfg, EXYNOS_MSCTRL); - } + cfg = fimc_read(ctx, EXYNOS_CISTATUS); + if (EXYNOS_CISTATUS_GET_ENVID_STATUS(cfg)) + fimc_clear_bits(ctx, EXYNOS_MSCTRL, EXYNOS_MSCTRL_ENVID); - cfg = fimc_read(EXYNOS_CISRCFMT); - cfg |= EXYNOS_CISRCFMT_ITU601_8BIT; - fimc_write(cfg, EXYNOS_CISRCFMT); + fimc_set_bits(ctx, EXYNOS_CISRCFMT, EXYNOS_CISRCFMT_ITU601_8BIT); /* disable image capture */ - cfg = fimc_read(EXYNOS_CIIMGCPT); - cfg &= ~(EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN); - fimc_write(cfg, EXYNOS_CIIMGCPT); + fimc_clear_bits(ctx, EXYNOS_CIIMGCPT, + EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN); /* s/w reset */ - cfg = fimc_read(EXYNOS_CIGCTRL); - cfg |= (EXYNOS_CIGCTRL_SWRST); - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_SWRST); /* s/w reset complete */ - cfg = fimc_read(EXYNOS_CIGCTRL); - cfg &= ~EXYNOS_CIGCTRL_SWRST; - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_clear_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_SWRST); /* reset sequence */ - fimc_write(0x0, EXYNOS_CIFCNTSEQ); + fimc_write(ctx, 0x0, EXYNOS_CIFCNTSEQ); } static int fimc_set_camblk_fimd0_wb(struct fimc_context *ctx) @@ -220,7 +231,7 @@ static void fimc_set_type_ctrl(struct fimc_context *ctx, enum fimc_wb wb) DRM_DEBUG_KMS("wb[%d]\n", wb); - cfg = fimc_read(EXYNOS_CIGCTRL); + cfg = fimc_read(ctx, EXYNOS_CIGCTRL); cfg &= ~(EXYNOS_CIGCTRL_TESTPATTERN_MASK | EXYNOS_CIGCTRL_SELCAM_ITU_MASK | EXYNOS_CIGCTRL_SELCAM_MIPI_MASK | @@ -246,7 +257,7 @@ static void fimc_set_type_ctrl(struct fimc_context *ctx, enum fimc_wb wb) break; } - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_write(ctx, cfg, EXYNOS_CIGCTRL); } static void fimc_set_polarity(struct fimc_context *ctx, @@ -259,7 +270,7 @@ static void fimc_set_polarity(struct fimc_context *ctx, DRM_DEBUG_KMS("inv_href[%d]inv_hsync[%d]\n", pol->inv_href, pol->inv_hsync); - cfg = fimc_read(EXYNOS_CIGCTRL); + cfg = fimc_read(ctx, EXYNOS_CIGCTRL); cfg &= ~(EXYNOS_CIGCTRL_INVPOLPCLK | EXYNOS_CIGCTRL_INVPOLVSYNC | EXYNOS_CIGCTRL_INVPOLHREF | EXYNOS_CIGCTRL_INVPOLHSYNC); @@ -272,7 +283,7 @@ static void fimc_set_polarity(struct fimc_context *ctx, if (pol->inv_hsync) cfg |= EXYNOS_CIGCTRL_INVPOLHSYNC; - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_write(ctx, cfg, EXYNOS_CIGCTRL); } static void fimc_handle_jpeg(struct fimc_context *ctx, bool enable) @@ -281,71 +292,55 @@ static void fimc_handle_jpeg(struct fimc_context *ctx, bool enable) DRM_DEBUG_KMS("enable[%d]\n", enable); - cfg = fimc_read(EXYNOS_CIGCTRL); + cfg = fimc_read(ctx, EXYNOS_CIGCTRL); if (enable) cfg |= EXYNOS_CIGCTRL_CAM_JPEG; else cfg &= ~EXYNOS_CIGCTRL_CAM_JPEG; - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_write(ctx, cfg, EXYNOS_CIGCTRL); } -static void fimc_handle_irq(struct fimc_context *ctx, bool enable, - bool overflow, bool level) +static void fimc_mask_irq(struct fimc_context *ctx, bool enable) { u32 cfg; - DRM_DEBUG_KMS("enable[%d]overflow[%d]level[%d]\n", - enable, overflow, level); + DRM_DEBUG_KMS("enable[%d]\n", enable); - cfg = fimc_read(EXYNOS_CIGCTRL); + cfg = fimc_read(ctx, EXYNOS_CIGCTRL); if (enable) { - cfg &= ~(EXYNOS_CIGCTRL_IRQ_OVFEN | EXYNOS_CIGCTRL_IRQ_LEVEL); - cfg |= EXYNOS_CIGCTRL_IRQ_ENABLE; - if (overflow) - cfg |= EXYNOS_CIGCTRL_IRQ_OVFEN; - if (level) - cfg |= EXYNOS_CIGCTRL_IRQ_LEVEL; + cfg &= ~EXYNOS_CIGCTRL_IRQ_OVFEN; + cfg |= EXYNOS_CIGCTRL_IRQ_ENABLE | EXYNOS_CIGCTRL_IRQ_LEVEL; } else - cfg &= ~(EXYNOS_CIGCTRL_IRQ_OVFEN | EXYNOS_CIGCTRL_IRQ_ENABLE); - - fimc_write(cfg, EXYNOS_CIGCTRL); + cfg &= ~EXYNOS_CIGCTRL_IRQ_ENABLE; + fimc_write(ctx, cfg, EXYNOS_CIGCTRL); } static void fimc_clear_irq(struct fimc_context *ctx) { - u32 cfg; - - cfg = fimc_read(EXYNOS_CIGCTRL); - cfg |= EXYNOS_CIGCTRL_IRQ_CLR; - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_CLR); } static bool fimc_check_ovf(struct fimc_context *ctx) { struct exynos_drm_ippdrv *ippdrv = &ctx->ippdrv; - u32 cfg, status, flag; + u32 status, flag; - status = fimc_read(EXYNOS_CISTATUS); + status = fimc_read(ctx, EXYNOS_CISTATUS); flag = EXYNOS_CISTATUS_OVFIY | EXYNOS_CISTATUS_OVFICB | EXYNOS_CISTATUS_OVFICR; DRM_DEBUG_KMS("flag[0x%x]\n", flag); if (status & flag) { - cfg = fimc_read(EXYNOS_CIWDOFST); - cfg |= (EXYNOS_CIWDOFST_CLROVFIY | EXYNOS_CIWDOFST_CLROVFICB | + fimc_set_bits(ctx, EXYNOS_CIWDOFST, + EXYNOS_CIWDOFST_CLROVFIY | EXYNOS_CIWDOFST_CLROVFICB | EXYNOS_CIWDOFST_CLROVFICR); - - fimc_write(cfg, EXYNOS_CIWDOFST); - - cfg = fimc_read(EXYNOS_CIWDOFST); - cfg &= ~(EXYNOS_CIWDOFST_CLROVFIY | EXYNOS_CIWDOFST_CLROVFICB | + fimc_clear_bits(ctx, EXYNOS_CIWDOFST, + EXYNOS_CIWDOFST_CLROVFIY | EXYNOS_CIWDOFST_CLROVFICB | EXYNOS_CIWDOFST_CLROVFICR); - fimc_write(cfg, EXYNOS_CIWDOFST); - - dev_err(ippdrv->dev, "occured overflow at %d, status 0x%x.\n", + dev_err(ippdrv->dev, "occurred overflow at %d, status 0x%x.\n", ctx->id, status); return true; } @@ -357,7 +352,7 @@ static bool fimc_check_frame_end(struct fimc_context *ctx) { u32 cfg; - cfg = fimc_read(EXYNOS_CISTATUS); + cfg = fimc_read(ctx, EXYNOS_CISTATUS); DRM_DEBUG_KMS("cfg[0x%x]\n", cfg); @@ -365,7 +360,7 @@ static bool fimc_check_frame_end(struct fimc_context *ctx) return false; cfg &= ~(EXYNOS_CISTATUS_FRAMEEND); - fimc_write(cfg, EXYNOS_CISTATUS); + fimc_write(ctx, cfg, EXYNOS_CISTATUS); return true; } @@ -375,7 +370,7 @@ static int fimc_get_buf_id(struct fimc_context *ctx) u32 cfg; int frame_cnt, buf_id; - cfg = fimc_read(EXYNOS_CISTATUS2); + cfg = fimc_read(ctx, EXYNOS_CISTATUS2); frame_cnt = EXYNOS_CISTATUS2_GET_FRAMECOUNT_BEFORE(cfg); if (frame_cnt == 0) @@ -402,13 +397,13 @@ static void fimc_handle_lastend(struct fimc_context *ctx, bool enable) DRM_DEBUG_KMS("enable[%d]\n", enable); - cfg = fimc_read(EXYNOS_CIOCTRL); + cfg = fimc_read(ctx, EXYNOS_CIOCTRL); if (enable) cfg |= EXYNOS_CIOCTRL_LASTENDEN; else cfg &= ~EXYNOS_CIOCTRL_LASTENDEN; - fimc_write(cfg, EXYNOS_CIOCTRL); + fimc_write(ctx, cfg, EXYNOS_CIOCTRL); } @@ -420,18 +415,18 @@ static int fimc_src_set_fmt_order(struct fimc_context *ctx, u32 fmt) DRM_DEBUG_KMS("fmt[0x%x]\n", fmt); /* RGB */ - cfg = fimc_read(EXYNOS_CISCCTRL); + cfg = fimc_read(ctx, EXYNOS_CISCCTRL); cfg &= ~EXYNOS_CISCCTRL_INRGB_FMT_RGB_MASK; switch (fmt) { case DRM_FORMAT_RGB565: cfg |= EXYNOS_CISCCTRL_INRGB_FMT_RGB565; - fimc_write(cfg, EXYNOS_CISCCTRL); + fimc_write(ctx, cfg, EXYNOS_CISCCTRL); return 0; case DRM_FORMAT_RGB888: case DRM_FORMAT_XRGB8888: cfg |= EXYNOS_CISCCTRL_INRGB_FMT_RGB888; - fimc_write(cfg, EXYNOS_CISCCTRL); + fimc_write(ctx, cfg, EXYNOS_CISCCTRL); return 0; default: /* bypass */ @@ -439,7 +434,7 @@ static int fimc_src_set_fmt_order(struct fimc_context *ctx, u32 fmt) } /* YUV */ - cfg = fimc_read(EXYNOS_MSCTRL); + cfg = fimc_read(ctx, EXYNOS_MSCTRL); cfg &= ~(EXYNOS_MSCTRL_ORDER2P_SHIFT_MASK | EXYNOS_MSCTRL_C_INT_IN_2PLANE | EXYNOS_MSCTRL_ORDER422_YCBYCR); @@ -479,7 +474,7 @@ static int fimc_src_set_fmt_order(struct fimc_context *ctx, u32 fmt) return -EINVAL; } - fimc_write(cfg, EXYNOS_MSCTRL); + fimc_write(ctx, cfg, EXYNOS_MSCTRL); return 0; } @@ -492,7 +487,7 @@ static int fimc_src_set_fmt(struct device *dev, u32 fmt) DRM_DEBUG_KMS("fmt[0x%x]\n", fmt); - cfg = fimc_read(EXYNOS_MSCTRL); + cfg = fimc_read(ctx, EXYNOS_MSCTRL); cfg &= ~EXYNOS_MSCTRL_INFORMAT_RGB; switch (fmt) { @@ -527,9 +522,9 @@ static int fimc_src_set_fmt(struct device *dev, u32 fmt) return -EINVAL; } - fimc_write(cfg, EXYNOS_MSCTRL); + fimc_write(ctx, cfg, EXYNOS_MSCTRL); - cfg = fimc_read(EXYNOS_CIDMAPARAM); + cfg = fimc_read(ctx, EXYNOS_CIDMAPARAM); cfg &= ~EXYNOS_CIDMAPARAM_R_MODE_MASK; if (fmt == DRM_FORMAT_NV12MT) @@ -537,7 +532,7 @@ static int fimc_src_set_fmt(struct device *dev, u32 fmt) else cfg |= EXYNOS_CIDMAPARAM_R_MODE_LINEAR; - fimc_write(cfg, EXYNOS_CIDMAPARAM); + fimc_write(ctx, cfg, EXYNOS_CIDMAPARAM); return fimc_src_set_fmt_order(ctx, fmt); } @@ -552,11 +547,11 @@ static int fimc_src_set_transf(struct device *dev, DRM_DEBUG_KMS("degree[%d]flip[0x%x]\n", degree, flip); - cfg1 = fimc_read(EXYNOS_MSCTRL); + cfg1 = fimc_read(ctx, EXYNOS_MSCTRL); cfg1 &= ~(EXYNOS_MSCTRL_FLIP_X_MIRROR | EXYNOS_MSCTRL_FLIP_Y_MIRROR); - cfg2 = fimc_read(EXYNOS_CITRGFMT); + cfg2 = fimc_read(ctx, EXYNOS_CITRGFMT); cfg2 &= ~EXYNOS_CITRGFMT_INROT90_CLOCKWISE; switch (degree) { @@ -595,8 +590,8 @@ static int fimc_src_set_transf(struct device *dev, return -EINVAL; } - fimc_write(cfg1, EXYNOS_MSCTRL); - fimc_write(cfg2, EXYNOS_CITRGFMT); + fimc_write(ctx, cfg1, EXYNOS_MSCTRL); + fimc_write(ctx, cfg2, EXYNOS_CITRGFMT); *swap = (cfg2 & EXYNOS_CITRGFMT_INROT90_CLOCKWISE) ? 1 : 0; return 0; @@ -621,17 +616,17 @@ static int fimc_set_window(struct fimc_context *ctx, * set window offset 1, 2 size * check figure 43-21 in user manual */ - cfg = fimc_read(EXYNOS_CIWDOFST); + cfg = fimc_read(ctx, EXYNOS_CIWDOFST); cfg &= ~(EXYNOS_CIWDOFST_WINHOROFST_MASK | EXYNOS_CIWDOFST_WINVEROFST_MASK); cfg |= (EXYNOS_CIWDOFST_WINHOROFST(h1) | EXYNOS_CIWDOFST_WINVEROFST(v1)); cfg |= EXYNOS_CIWDOFST_WINOFSEN; - fimc_write(cfg, EXYNOS_CIWDOFST); + fimc_write(ctx, cfg, EXYNOS_CIWDOFST); cfg = (EXYNOS_CIWDOFST2_WINHOROFST2(h2) | EXYNOS_CIWDOFST2_WINVEROFST2(v2)); - fimc_write(cfg, EXYNOS_CIWDOFST2); + fimc_write(ctx, cfg, EXYNOS_CIWDOFST2); return 0; } @@ -651,7 +646,7 @@ static int fimc_src_set_size(struct device *dev, int swap, cfg = (EXYNOS_ORGISIZE_HORIZONTAL(img_sz.hsize) | EXYNOS_ORGISIZE_VERTICAL(img_sz.vsize)); - fimc_write(cfg, EXYNOS_ORGISIZE); + fimc_write(ctx, cfg, EXYNOS_ORGISIZE); DRM_DEBUG_KMS("x[%d]y[%d]w[%d]h[%d]\n", pos->x, pos->y, pos->w, pos->h); @@ -663,12 +658,12 @@ static int fimc_src_set_size(struct device *dev, int swap, } /* set input DMA image size */ - cfg = fimc_read(EXYNOS_CIREAL_ISIZE); + cfg = fimc_read(ctx, EXYNOS_CIREAL_ISIZE); cfg &= ~(EXYNOS_CIREAL_ISIZE_HEIGHT_MASK | EXYNOS_CIREAL_ISIZE_WIDTH_MASK); cfg |= (EXYNOS_CIREAL_ISIZE_WIDTH(img_pos.w) | EXYNOS_CIREAL_ISIZE_HEIGHT(img_pos.h)); - fimc_write(cfg, EXYNOS_CIREAL_ISIZE); + fimc_write(ctx, cfg, EXYNOS_CIREAL_ISIZE); /* * set input FIFO image size @@ -677,18 +672,18 @@ static int fimc_src_set_size(struct device *dev, int swap, cfg = (EXYNOS_CISRCFMT_ITU601_8BIT | EXYNOS_CISRCFMT_SOURCEHSIZE(img_sz.hsize) | EXYNOS_CISRCFMT_SOURCEVSIZE(img_sz.vsize)); - fimc_write(cfg, EXYNOS_CISRCFMT); + fimc_write(ctx, cfg, EXYNOS_CISRCFMT); /* offset Y(RGB), Cb, Cr */ cfg = (EXYNOS_CIIYOFF_HORIZONTAL(img_pos.x) | EXYNOS_CIIYOFF_VERTICAL(img_pos.y)); - fimc_write(cfg, EXYNOS_CIIYOFF); + fimc_write(ctx, cfg, EXYNOS_CIIYOFF); cfg = (EXYNOS_CIICBOFF_HORIZONTAL(img_pos.x) | EXYNOS_CIICBOFF_VERTICAL(img_pos.y)); - fimc_write(cfg, EXYNOS_CIICBOFF); + fimc_write(ctx, cfg, EXYNOS_CIICBOFF); cfg = (EXYNOS_CIICROFF_HORIZONTAL(img_pos.x) | EXYNOS_CIICROFF_VERTICAL(img_pos.y)); - fimc_write(cfg, EXYNOS_CIICROFF); + fimc_write(ctx, cfg, EXYNOS_CIICROFF); return fimc_set_window(ctx, &img_pos, &img_sz); } @@ -722,25 +717,25 @@ static int fimc_src_set_addr(struct device *dev, switch (buf_type) { case IPP_BUF_ENQUEUE: config = &property->config[EXYNOS_DRM_OPS_SRC]; - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_Y], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_Y], EXYNOS_CIIYSA(buf_id)); if (config->fmt == DRM_FORMAT_YVU420) { - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CR], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CR], EXYNOS_CIICBSA(buf_id)); - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CB], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CB], EXYNOS_CIICRSA(buf_id)); } else { - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CB], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CB], EXYNOS_CIICBSA(buf_id)); - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CR], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CR], EXYNOS_CIICRSA(buf_id)); } break; case IPP_BUF_DEQUEUE: - fimc_write(0x0, EXYNOS_CIIYSA(buf_id)); - fimc_write(0x0, EXYNOS_CIICBSA(buf_id)); - fimc_write(0x0, EXYNOS_CIICRSA(buf_id)); + fimc_write(ctx, 0x0, EXYNOS_CIIYSA(buf_id)); + fimc_write(ctx, 0x0, EXYNOS_CIICBSA(buf_id)); + fimc_write(ctx, 0x0, EXYNOS_CIICRSA(buf_id)); break; default: /* bypass */ @@ -765,22 +760,22 @@ static int fimc_dst_set_fmt_order(struct fimc_context *ctx, u32 fmt) DRM_DEBUG_KMS("fmt[0x%x]\n", fmt); /* RGB */ - cfg = fimc_read(EXYNOS_CISCCTRL); + cfg = fimc_read(ctx, EXYNOS_CISCCTRL); cfg &= ~EXYNOS_CISCCTRL_OUTRGB_FMT_RGB_MASK; switch (fmt) { case DRM_FORMAT_RGB565: cfg |= EXYNOS_CISCCTRL_OUTRGB_FMT_RGB565; - fimc_write(cfg, EXYNOS_CISCCTRL); + fimc_write(ctx, cfg, EXYNOS_CISCCTRL); return 0; case DRM_FORMAT_RGB888: cfg |= EXYNOS_CISCCTRL_OUTRGB_FMT_RGB888; - fimc_write(cfg, EXYNOS_CISCCTRL); + fimc_write(ctx, cfg, EXYNOS_CISCCTRL); return 0; case DRM_FORMAT_XRGB8888: cfg |= (EXYNOS_CISCCTRL_OUTRGB_FMT_RGB888 | EXYNOS_CISCCTRL_EXTRGB_EXTENSION); - fimc_write(cfg, EXYNOS_CISCCTRL); + fimc_write(ctx, cfg, EXYNOS_CISCCTRL); break; default: /* bypass */ @@ -788,7 +783,7 @@ static int fimc_dst_set_fmt_order(struct fimc_context *ctx, u32 fmt) } /* YUV */ - cfg = fimc_read(EXYNOS_CIOCTRL); + cfg = fimc_read(ctx, EXYNOS_CIOCTRL); cfg &= ~(EXYNOS_CIOCTRL_ORDER2P_MASK | EXYNOS_CIOCTRL_ORDER422_MASK | EXYNOS_CIOCTRL_YCBCR_PLANE_MASK); @@ -830,7 +825,7 @@ static int fimc_dst_set_fmt_order(struct fimc_context *ctx, u32 fmt) return -EINVAL; } - fimc_write(cfg, EXYNOS_CIOCTRL); + fimc_write(ctx, cfg, EXYNOS_CIOCTRL); return 0; } @@ -843,16 +838,16 @@ static int fimc_dst_set_fmt(struct device *dev, u32 fmt) DRM_DEBUG_KMS("fmt[0x%x]\n", fmt); - cfg = fimc_read(EXYNOS_CIEXTEN); + cfg = fimc_read(ctx, EXYNOS_CIEXTEN); if (fmt == DRM_FORMAT_AYUV) { cfg |= EXYNOS_CIEXTEN_YUV444_OUT; - fimc_write(cfg, EXYNOS_CIEXTEN); + fimc_write(ctx, cfg, EXYNOS_CIEXTEN); } else { cfg &= ~EXYNOS_CIEXTEN_YUV444_OUT; - fimc_write(cfg, EXYNOS_CIEXTEN); + fimc_write(ctx, cfg, EXYNOS_CIEXTEN); - cfg = fimc_read(EXYNOS_CITRGFMT); + cfg = fimc_read(ctx, EXYNOS_CITRGFMT); cfg &= ~EXYNOS_CITRGFMT_OUTFORMAT_MASK; switch (fmt) { @@ -885,10 +880,10 @@ static int fimc_dst_set_fmt(struct device *dev, u32 fmt) return -EINVAL; } - fimc_write(cfg, EXYNOS_CITRGFMT); + fimc_write(ctx, cfg, EXYNOS_CITRGFMT); } - cfg = fimc_read(EXYNOS_CIDMAPARAM); + cfg = fimc_read(ctx, EXYNOS_CIDMAPARAM); cfg &= ~EXYNOS_CIDMAPARAM_W_MODE_MASK; if (fmt == DRM_FORMAT_NV12MT) @@ -896,7 +891,7 @@ static int fimc_dst_set_fmt(struct device *dev, u32 fmt) else cfg |= EXYNOS_CIDMAPARAM_W_MODE_LINEAR; - fimc_write(cfg, EXYNOS_CIDMAPARAM); + fimc_write(ctx, cfg, EXYNOS_CIDMAPARAM); return fimc_dst_set_fmt_order(ctx, fmt); } @@ -911,7 +906,7 @@ static int fimc_dst_set_transf(struct device *dev, DRM_DEBUG_KMS("degree[%d]flip[0x%x]\n", degree, flip); - cfg = fimc_read(EXYNOS_CITRGFMT); + cfg = fimc_read(ctx, EXYNOS_CITRGFMT); cfg &= ~EXYNOS_CITRGFMT_FLIP_MASK; cfg &= ~EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE; @@ -951,53 +946,23 @@ static int fimc_dst_set_transf(struct device *dev, return -EINVAL; } - fimc_write(cfg, EXYNOS_CITRGFMT); + fimc_write(ctx, cfg, EXYNOS_CITRGFMT); *swap = (cfg & EXYNOS_CITRGFMT_OUTROT90_CLOCKWISE) ? 1 : 0; return 0; } -static int fimc_get_ratio_shift(u32 src, u32 dst, u32 *ratio, u32 *shift) -{ - DRM_DEBUG_KMS("src[%d]dst[%d]\n", src, dst); - - if (src >= dst * 64) { - DRM_ERROR("failed to make ratio and shift.\n"); - return -EINVAL; - } else if (src >= dst * 32) { - *ratio = 32; - *shift = 5; - } else if (src >= dst * 16) { - *ratio = 16; - *shift = 4; - } else if (src >= dst * 8) { - *ratio = 8; - *shift = 3; - } else if (src >= dst * 4) { - *ratio = 4; - *shift = 2; - } else if (src >= dst * 2) { - *ratio = 2; - *shift = 1; - } else { - *ratio = 1; - *shift = 0; - } - - return 0; -} - static int fimc_set_prescaler(struct fimc_context *ctx, struct fimc_scaler *sc, struct drm_exynos_pos *src, struct drm_exynos_pos *dst) { struct exynos_drm_ippdrv *ippdrv = &ctx->ippdrv; u32 cfg, cfg_ext, shfactor; u32 pre_dst_width, pre_dst_height; - u32 pre_hratio, hfactor, pre_vratio, vfactor; + u32 hfactor, vfactor; int ret = 0; u32 src_w, src_h, dst_w, dst_h; - cfg_ext = fimc_read(EXYNOS_CITRGFMT); + cfg_ext = fimc_read(ctx, EXYNOS_CITRGFMT); if (cfg_ext & EXYNOS_CITRGFMT_INROT90_CLOCKWISE) { src_w = src->h; src_h = src->w; @@ -1014,24 +979,24 @@ static int fimc_set_prescaler(struct fimc_context *ctx, struct fimc_scaler *sc, dst_h = dst->h; } - ret = fimc_get_ratio_shift(src_w, dst_w, &pre_hratio, &hfactor); - if (ret) { + /* fimc_ippdrv_check_property assures that dividers are not null */ + hfactor = fls(src_w / dst_w / 2); + if (hfactor > FIMC_SHFACTOR / 2) { dev_err(ippdrv->dev, "failed to get ratio horizontal.\n"); - return ret; + return -EINVAL; } - ret = fimc_get_ratio_shift(src_h, dst_h, &pre_vratio, &vfactor); - if (ret) { + vfactor = fls(src_h / dst_h / 2); + if (vfactor > FIMC_SHFACTOR / 2) { dev_err(ippdrv->dev, "failed to get ratio vertical.\n"); - return ret; + return -EINVAL; } - pre_dst_width = src_w / pre_hratio; - pre_dst_height = src_h / pre_vratio; + pre_dst_width = src_w >> hfactor; + pre_dst_height = src_h >> vfactor; DRM_DEBUG_KMS("pre_dst_width[%d]pre_dst_height[%d]\n", pre_dst_width, pre_dst_height); - DRM_DEBUG_KMS("pre_hratio[%d]hfactor[%d]pre_vratio[%d]vfactor[%d]\n", - pre_hratio, hfactor, pre_vratio, vfactor); + DRM_DEBUG_KMS("hfactor[%d]vfactor[%d]\n", hfactor, vfactor); sc->hratio = (src_w << 14) / (dst_w << hfactor); sc->vratio = (src_h << 14) / (dst_h << vfactor); @@ -1044,13 +1009,13 @@ static int fimc_set_prescaler(struct fimc_context *ctx, struct fimc_scaler *sc, DRM_DEBUG_KMS("shfactor[%d]\n", shfactor); cfg = (EXYNOS_CISCPRERATIO_SHFACTOR(shfactor) | - EXYNOS_CISCPRERATIO_PREHORRATIO(pre_hratio) | - EXYNOS_CISCPRERATIO_PREVERRATIO(pre_vratio)); - fimc_write(cfg, EXYNOS_CISCPRERATIO); + EXYNOS_CISCPRERATIO_PREHORRATIO(1 << hfactor) | + EXYNOS_CISCPRERATIO_PREVERRATIO(1 << vfactor)); + fimc_write(ctx, cfg, EXYNOS_CISCPRERATIO); cfg = (EXYNOS_CISCPREDST_PREDSTWIDTH(pre_dst_width) | EXYNOS_CISCPREDST_PREDSTHEIGHT(pre_dst_height)); - fimc_write(cfg, EXYNOS_CISCPREDST); + fimc_write(ctx, cfg, EXYNOS_CISCPREDST); return ret; } @@ -1064,7 +1029,7 @@ static void fimc_set_scaler(struct fimc_context *ctx, struct fimc_scaler *sc) DRM_DEBUG_KMS("hratio[%d]vratio[%d]\n", sc->hratio, sc->vratio); - cfg = fimc_read(EXYNOS_CISCCTRL); + cfg = fimc_read(ctx, EXYNOS_CISCCTRL); cfg &= ~(EXYNOS_CISCCTRL_SCALERBYPASS | EXYNOS_CISCCTRL_SCALEUP_H | EXYNOS_CISCCTRL_SCALEUP_V | EXYNOS_CISCCTRL_MAIN_V_RATIO_MASK | @@ -1084,14 +1049,14 @@ static void fimc_set_scaler(struct fimc_context *ctx, struct fimc_scaler *sc) cfg |= (EXYNOS_CISCCTRL_MAINHORRATIO((sc->hratio >> 6)) | EXYNOS_CISCCTRL_MAINVERRATIO((sc->vratio >> 6))); - fimc_write(cfg, EXYNOS_CISCCTRL); + fimc_write(ctx, cfg, EXYNOS_CISCCTRL); - cfg_ext = fimc_read(EXYNOS_CIEXTEN); + cfg_ext = fimc_read(ctx, EXYNOS_CIEXTEN); cfg_ext &= ~EXYNOS_CIEXTEN_MAINHORRATIO_EXT_MASK; cfg_ext &= ~EXYNOS_CIEXTEN_MAINVERRATIO_EXT_MASK; cfg_ext |= (EXYNOS_CIEXTEN_MAINHORRATIO_EXT(sc->hratio) | EXYNOS_CIEXTEN_MAINVERRATIO_EXT(sc->vratio)); - fimc_write(cfg_ext, EXYNOS_CIEXTEN); + fimc_write(ctx, cfg_ext, EXYNOS_CIEXTEN); } static int fimc_dst_set_size(struct device *dev, int swap, @@ -1109,12 +1074,12 @@ static int fimc_dst_set_size(struct device *dev, int swap, cfg = (EXYNOS_ORGOSIZE_HORIZONTAL(img_sz.hsize) | EXYNOS_ORGOSIZE_VERTICAL(img_sz.vsize)); - fimc_write(cfg, EXYNOS_ORGOSIZE); + fimc_write(ctx, cfg, EXYNOS_ORGOSIZE); DRM_DEBUG_KMS("x[%d]y[%d]w[%d]h[%d]\n", pos->x, pos->y, pos->w, pos->h); /* CSC ITU */ - cfg = fimc_read(EXYNOS_CIGCTRL); + cfg = fimc_read(ctx, EXYNOS_CIGCTRL); cfg &= ~EXYNOS_CIGCTRL_CSC_MASK; if (sz->hsize >= FIMC_WIDTH_ITU_709) @@ -1122,7 +1087,7 @@ static int fimc_dst_set_size(struct device *dev, int swap, else cfg |= EXYNOS_CIGCTRL_CSC_ITU601; - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_write(ctx, cfg, EXYNOS_CIGCTRL); if (swap) { img_pos.w = pos->h; @@ -1132,41 +1097,38 @@ static int fimc_dst_set_size(struct device *dev, int swap, } /* target image size */ - cfg = fimc_read(EXYNOS_CITRGFMT); + cfg = fimc_read(ctx, EXYNOS_CITRGFMT); cfg &= ~(EXYNOS_CITRGFMT_TARGETH_MASK | EXYNOS_CITRGFMT_TARGETV_MASK); cfg |= (EXYNOS_CITRGFMT_TARGETHSIZE(img_pos.w) | EXYNOS_CITRGFMT_TARGETVSIZE(img_pos.h)); - fimc_write(cfg, EXYNOS_CITRGFMT); + fimc_write(ctx, cfg, EXYNOS_CITRGFMT); /* target area */ cfg = EXYNOS_CITAREA_TARGET_AREA(img_pos.w * img_pos.h); - fimc_write(cfg, EXYNOS_CITAREA); + fimc_write(ctx, cfg, EXYNOS_CITAREA); /* offset Y(RGB), Cb, Cr */ cfg = (EXYNOS_CIOYOFF_HORIZONTAL(img_pos.x) | EXYNOS_CIOYOFF_VERTICAL(img_pos.y)); - fimc_write(cfg, EXYNOS_CIOYOFF); + fimc_write(ctx, cfg, EXYNOS_CIOYOFF); cfg = (EXYNOS_CIOCBOFF_HORIZONTAL(img_pos.x) | EXYNOS_CIOCBOFF_VERTICAL(img_pos.y)); - fimc_write(cfg, EXYNOS_CIOCBOFF); + fimc_write(ctx, cfg, EXYNOS_CIOCBOFF); cfg = (EXYNOS_CIOCROFF_HORIZONTAL(img_pos.x) | EXYNOS_CIOCROFF_VERTICAL(img_pos.y)); - fimc_write(cfg, EXYNOS_CIOCROFF); + fimc_write(ctx, cfg, EXYNOS_CIOCROFF); return 0; } -static int fimc_dst_get_buf_seq(struct fimc_context *ctx) +static int fimc_dst_get_buf_count(struct fimc_context *ctx) { - u32 cfg, i, buf_num = 0; - u32 mask = 0x00000001; + u32 cfg, buf_num; - cfg = fimc_read(EXYNOS_CIFCNTSEQ); + cfg = fimc_read(ctx, EXYNOS_CIFCNTSEQ); - for (i = 0; i < FIMC_REG_SZ; i++) - if (cfg & (mask << i)) - buf_num++; + buf_num = hweight32(cfg); DRM_DEBUG_KMS("buf_num[%d]\n", buf_num); @@ -1181,13 +1143,14 @@ static int fimc_dst_set_buf_seq(struct fimc_context *ctx, u32 buf_id, u32 cfg; u32 mask = 0x00000001 << buf_id; int ret = 0; + unsigned long flags; DRM_DEBUG_KMS("buf_id[%d]buf_type[%d]\n", buf_id, buf_type); - mutex_lock(&ctx->lock); + spin_lock_irqsave(&ctx->lock, flags); /* mask register set */ - cfg = fimc_read(EXYNOS_CIFCNTSEQ); + cfg = fimc_read(ctx, EXYNOS_CIFCNTSEQ); switch (buf_type) { case IPP_BUF_ENQUEUE: @@ -1205,20 +1168,20 @@ static int fimc_dst_set_buf_seq(struct fimc_context *ctx, u32 buf_id, /* sequence id */ cfg &= ~mask; cfg |= (enable << buf_id); - fimc_write(cfg, EXYNOS_CIFCNTSEQ); + fimc_write(ctx, cfg, EXYNOS_CIFCNTSEQ); /* interrupt enable */ if (buf_type == IPP_BUF_ENQUEUE && - fimc_dst_get_buf_seq(ctx) >= FIMC_BUF_START) - fimc_handle_irq(ctx, true, false, true); + fimc_dst_get_buf_count(ctx) >= FIMC_BUF_START) + fimc_mask_irq(ctx, true); /* interrupt disable */ if (buf_type == IPP_BUF_DEQUEUE && - fimc_dst_get_buf_seq(ctx) <= FIMC_BUF_STOP) - fimc_handle_irq(ctx, false, false, true); + fimc_dst_get_buf_count(ctx) <= FIMC_BUF_STOP) + fimc_mask_irq(ctx, false); err_unlock: - mutex_unlock(&ctx->lock); + spin_unlock_irqrestore(&ctx->lock, flags); return ret; } @@ -1252,25 +1215,25 @@ static int fimc_dst_set_addr(struct device *dev, case IPP_BUF_ENQUEUE: config = &property->config[EXYNOS_DRM_OPS_DST]; - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_Y], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_Y], EXYNOS_CIOYSA(buf_id)); if (config->fmt == DRM_FORMAT_YVU420) { - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CR], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CR], EXYNOS_CIOCBSA(buf_id)); - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CB], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CB], EXYNOS_CIOCRSA(buf_id)); } else { - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CB], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CB], EXYNOS_CIOCBSA(buf_id)); - fimc_write(buf_info->base[EXYNOS_DRM_PLANAR_CR], + fimc_write(ctx, buf_info->base[EXYNOS_DRM_PLANAR_CR], EXYNOS_CIOCRSA(buf_id)); } break; case IPP_BUF_DEQUEUE: - fimc_write(0x0, EXYNOS_CIOYSA(buf_id)); - fimc_write(0x0, EXYNOS_CIOCBSA(buf_id)); - fimc_write(0x0, EXYNOS_CIOCRSA(buf_id)); + fimc_write(ctx, 0x0, EXYNOS_CIOYSA(buf_id)); + fimc_write(ctx, 0x0, EXYNOS_CIOCBSA(buf_id)); + fimc_write(ctx, 0x0, EXYNOS_CIOCRSA(buf_id)); break; default: /* bypass */ @@ -1342,11 +1305,7 @@ static irqreturn_t fimc_irq_handler(int irq, void *dev_id) static int fimc_init_prop_list(struct exynos_drm_ippdrv *ippdrv) { - struct drm_exynos_ipp_prop_list *prop_list; - - prop_list = devm_kzalloc(ippdrv->dev, sizeof(*prop_list), GFP_KERNEL); - if (!prop_list) - return -ENOMEM; + struct drm_exynos_ipp_prop_list *prop_list = &ippdrv->prop_list; prop_list->version = 1; prop_list->writeback = 1; @@ -1371,8 +1330,6 @@ static int fimc_init_prop_list(struct exynos_drm_ippdrv *ippdrv) prop_list->scale_min.hsize = FIMC_SCALE_MIN; prop_list->scale_min.vsize = FIMC_SCALE_MIN; - ippdrv->prop_list = prop_list; - return 0; } @@ -1395,7 +1352,7 @@ static int fimc_ippdrv_check_property(struct device *dev, { struct fimc_context *ctx = get_fimc_context(dev); struct exynos_drm_ippdrv *ippdrv = &ctx->ippdrv; - struct drm_exynos_ipp_prop_list *pp = ippdrv->prop_list; + struct drm_exynos_ipp_prop_list *pp = &ippdrv->prop_list; struct drm_exynos_ipp_config *config; struct drm_exynos_pos *pos; struct drm_exynos_sz *sz; @@ -1508,15 +1465,15 @@ static void fimc_clear_addr(struct fimc_context *ctx) int i; for (i = 0; i < FIMC_MAX_SRC; i++) { - fimc_write(0, EXYNOS_CIIYSA(i)); - fimc_write(0, EXYNOS_CIICBSA(i)); - fimc_write(0, EXYNOS_CIICRSA(i)); + fimc_write(ctx, 0, EXYNOS_CIIYSA(i)); + fimc_write(ctx, 0, EXYNOS_CIICBSA(i)); + fimc_write(ctx, 0, EXYNOS_CIICRSA(i)); } for (i = 0; i < FIMC_MAX_DST; i++) { - fimc_write(0, EXYNOS_CIOYSA(i)); - fimc_write(0, EXYNOS_CIOCBSA(i)); - fimc_write(0, EXYNOS_CIOCRSA(i)); + fimc_write(ctx, 0, EXYNOS_CIOYSA(i)); + fimc_write(ctx, 0, EXYNOS_CIOCBSA(i)); + fimc_write(ctx, 0, EXYNOS_CIOCRSA(i)); } } @@ -1556,7 +1513,7 @@ static int fimc_ippdrv_start(struct device *dev, enum drm_exynos_ipp_cmd cmd) property = &c_node->property; - fimc_handle_irq(ctx, true, false, true); + fimc_mask_irq(ctx, true); for_each_ipp_ops(i) { config = &property->config[i]; @@ -1582,10 +1539,10 @@ static int fimc_ippdrv_start(struct device *dev, enum drm_exynos_ipp_cmd cmd) fimc_handle_lastend(ctx, false); /* setup dma */ - cfg0 = fimc_read(EXYNOS_MSCTRL); + cfg0 = fimc_read(ctx, EXYNOS_MSCTRL); cfg0 &= ~EXYNOS_MSCTRL_INPUT_MASK; cfg0 |= EXYNOS_MSCTRL_INPUT_MEMORY; - fimc_write(cfg0, EXYNOS_MSCTRL); + fimc_write(ctx, cfg0, EXYNOS_MSCTRL); break; case IPP_CMD_WB: fimc_set_type_ctrl(ctx, FIMC_WB_A); @@ -1610,41 +1567,33 @@ static int fimc_ippdrv_start(struct device *dev, enum drm_exynos_ipp_cmd cmd) } /* Reset status */ - fimc_write(0x0, EXYNOS_CISTATUS); + fimc_write(ctx, 0x0, EXYNOS_CISTATUS); - cfg0 = fimc_read(EXYNOS_CIIMGCPT); + cfg0 = fimc_read(ctx, EXYNOS_CIIMGCPT); cfg0 &= ~EXYNOS_CIIMGCPT_IMGCPTEN_SC; cfg0 |= EXYNOS_CIIMGCPT_IMGCPTEN_SC; /* Scaler */ - cfg1 = fimc_read(EXYNOS_CISCCTRL); + cfg1 = fimc_read(ctx, EXYNOS_CISCCTRL); cfg1 &= ~EXYNOS_CISCCTRL_SCAN_MASK; cfg1 |= (EXYNOS_CISCCTRL_PROGRESSIVE | EXYNOS_CISCCTRL_SCALERSTART); - fimc_write(cfg1, EXYNOS_CISCCTRL); + fimc_write(ctx, cfg1, EXYNOS_CISCCTRL); /* Enable image capture*/ cfg0 |= EXYNOS_CIIMGCPT_IMGCPTEN; - fimc_write(cfg0, EXYNOS_CIIMGCPT); + fimc_write(ctx, cfg0, EXYNOS_CIIMGCPT); /* Disable frame end irq */ - cfg0 = fimc_read(EXYNOS_CIGCTRL); - cfg0 &= ~EXYNOS_CIGCTRL_IRQ_END_DISABLE; - fimc_write(cfg0, EXYNOS_CIGCTRL); + fimc_clear_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_END_DISABLE); - cfg0 = fimc_read(EXYNOS_CIOCTRL); - cfg0 &= ~EXYNOS_CIOCTRL_WEAVE_MASK; - fimc_write(cfg0, EXYNOS_CIOCTRL); + fimc_clear_bits(ctx, EXYNOS_CIOCTRL, EXYNOS_CIOCTRL_WEAVE_MASK); if (cmd == IPP_CMD_M2M) { - cfg0 = fimc_read(EXYNOS_MSCTRL); - cfg0 |= EXYNOS_MSCTRL_ENVID; - fimc_write(cfg0, EXYNOS_MSCTRL); + fimc_set_bits(ctx, EXYNOS_MSCTRL, EXYNOS_MSCTRL_ENVID); - cfg0 = fimc_read(EXYNOS_MSCTRL); - cfg0 |= EXYNOS_MSCTRL_ENVID; - fimc_write(cfg0, EXYNOS_MSCTRL); + fimc_set_bits(ctx, EXYNOS_MSCTRL, EXYNOS_MSCTRL_ENVID); } return 0; @@ -1661,10 +1610,10 @@ static void fimc_ippdrv_stop(struct device *dev, enum drm_exynos_ipp_cmd cmd) switch (cmd) { case IPP_CMD_M2M: /* Source clear */ - cfg = fimc_read(EXYNOS_MSCTRL); + cfg = fimc_read(ctx, EXYNOS_MSCTRL); cfg &= ~EXYNOS_MSCTRL_INPUT_MASK; cfg &= ~EXYNOS_MSCTRL_ENVID; - fimc_write(cfg, EXYNOS_MSCTRL); + fimc_write(ctx, cfg, EXYNOS_MSCTRL); break; case IPP_CMD_WB: exynos_drm_ippnb_send_event(IPP_SET_WRITEBACK, (void *)&set_wb); @@ -1675,25 +1624,20 @@ static void fimc_ippdrv_stop(struct device *dev, enum drm_exynos_ipp_cmd cmd) break; } - fimc_handle_irq(ctx, false, false, true); + fimc_mask_irq(ctx, false); /* reset sequence */ - fimc_write(0x0, EXYNOS_CIFCNTSEQ); + fimc_write(ctx, 0x0, EXYNOS_CIFCNTSEQ); /* Scaler disable */ - cfg = fimc_read(EXYNOS_CISCCTRL); - cfg &= ~EXYNOS_CISCCTRL_SCALERSTART; - fimc_write(cfg, EXYNOS_CISCCTRL); + fimc_clear_bits(ctx, EXYNOS_CISCCTRL, EXYNOS_CISCCTRL_SCALERSTART); /* Disable image capture */ - cfg = fimc_read(EXYNOS_CIIMGCPT); - cfg &= ~(EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN); - fimc_write(cfg, EXYNOS_CIIMGCPT); + fimc_clear_bits(ctx, EXYNOS_CIIMGCPT, + EXYNOS_CIIMGCPT_IMGCPTEN_SC | EXYNOS_CIIMGCPT_IMGCPTEN); /* Enable frame end irq */ - cfg = fimc_read(EXYNOS_CIGCTRL); - cfg |= EXYNOS_CIGCTRL_IRQ_END_DISABLE; - fimc_write(cfg, EXYNOS_CIGCTRL); + fimc_set_bits(ctx, EXYNOS_CIGCTRL, EXYNOS_CIGCTRL_IRQ_END_DISABLE); } static void fimc_put_clocks(struct fimc_context *ctx) @@ -1848,7 +1792,7 @@ static int fimc_probe(struct platform_device *pdev) DRM_DEBUG_KMS("id[%d]ippdrv[0x%x]\n", ctx->id, (int)ippdrv); - mutex_init(&ctx->lock); + spin_lock_init(&ctx->lock); platform_set_drvdata(pdev, ctx); pm_runtime_set_active(dev); @@ -1879,7 +1823,6 @@ static int fimc_remove(struct platform_device *pdev) struct exynos_drm_ippdrv *ippdrv = &ctx->ippdrv; exynos_drm_ippdrv_unregister(ippdrv); - mutex_destroy(&ctx->lock); fimc_put_clocks(ctx); pm_runtime_set_suspended(dev); diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index 868a14d5299..33161ad3820 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -19,6 +19,7 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/pm_runtime.h> +#include <linux/component.h> #include <video/of_display_timing.h> #include <video/of_videomode.h> @@ -31,13 +32,14 @@ #include "exynos_drm_iommu.h" /* - * FIMD is stand for Fully Interactive Mobile Display and + * FIMD stands for Fully Interactive Mobile Display and * as a display controller, it transfers contents drawn on memory * to a LCD Panel through Display Interfaces such as RGB or * CPU Interface. */ #define FIMD_DEFAULT_FRAMERATE 60 +#define MIN_FB_WIDTH_FOR_16WORD_BURST 128 /* position control register for hardware window 0, 2 ~ 4.*/ #define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16) @@ -62,7 +64,7 @@ /* FIMD has totally five hardware windows. */ #define WINDOWS_NR 5 -#define get_fimd_context(dev) platform_get_drvdata(to_platform_device(dev)) +#define get_fimd_manager(mgr) platform_get_drvdata(to_platform_device(dev)) struct fimd_driver_data { unsigned int timing_base; @@ -105,25 +107,24 @@ struct fimd_win_data { }; struct fimd_context { - struct exynos_drm_subdrv subdrv; - int irq; - struct drm_crtc *crtc; + struct device *dev; + struct drm_device *drm_dev; struct clk *bus_clk; struct clk *lcd_clk; void __iomem *regs; + struct drm_display_mode mode; struct fimd_win_data win_data[WINDOWS_NR]; - unsigned int clkdiv; unsigned int default_win; unsigned long irq_flags; - u32 vidcon0; u32 vidcon1; bool suspended; - struct mutex lock; + int pipe; wait_queue_head_t wait_vsync_queue; atomic_t wait_vsync_event; struct exynos_drm_panel_info panel; struct fimd_driver_data *driver_data; + struct exynos_drm_display *display; }; static const struct of_device_id fimd_driver_dt_match[] = { @@ -145,153 +146,197 @@ static inline struct fimd_driver_data *drm_fimd_get_driver_data( return (struct fimd_driver_data *)of_id->data; } -static bool fimd_display_is_connected(struct device *dev) +static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr) { - /* TODO. */ + struct fimd_context *ctx = mgr->ctx; - return true; + if (ctx->suspended) + return; + + atomic_set(&ctx->wait_vsync_event, 1); + + /* + * wait for FIMD to signal VSYNC interrupt or return after + * timeout which is set to 50ms (refresh rate of 20). + */ + if (!wait_event_timeout(ctx->wait_vsync_queue, + !atomic_read(&ctx->wait_vsync_event), + HZ/20)) + DRM_DEBUG_KMS("vblank wait timed out.\n"); } -static void *fimd_get_panel(struct device *dev) + +static void fimd_clear_channel(struct exynos_drm_manager *mgr) { - struct fimd_context *ctx = get_fimd_context(dev); + struct fimd_context *ctx = mgr->ctx; + int win, ch_enabled = 0; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + /* Check if any channel is enabled. */ + for (win = 0; win < WINDOWS_NR; win++) { + u32 val = readl(ctx->regs + SHADOWCON); + if (val & SHADOWCON_CHx_ENABLE(win)) { + val &= ~SHADOWCON_CHx_ENABLE(win); + writel(val, ctx->regs + SHADOWCON); + ch_enabled = 1; + } + } - return &ctx->panel; + /* Wait for vsync, as disable channel takes effect at next vsync */ + if (ch_enabled) + fimd_wait_for_vblank(mgr); } -static int fimd_check_mode(struct device *dev, struct drm_display_mode *mode) +static int fimd_mgr_initialize(struct exynos_drm_manager *mgr, + struct drm_device *drm_dev) { - /* TODO. */ + struct fimd_context *ctx = mgr->ctx; + struct exynos_drm_private *priv; + priv = drm_dev->dev_private; + + mgr->drm_dev = ctx->drm_dev = drm_dev; + mgr->pipe = ctx->pipe = priv->pipe++; + + /* + * enable drm irq mode. + * - with irq_enabled = true, we can use the vblank feature. + * + * P.S. note that we wouldn't use drm irq handler but + * just specific driver own one instead because + * drm framework supports only one irq handler. + */ + drm_dev->irq_enabled = true; + + /* + * with vblank_disable_allowed = true, vblank interrupt will be disabled + * by drm timer once a current process gives up ownership of + * vblank event.(after drm_vblank_put function is called) + */ + drm_dev->vblank_disable_allowed = true; + + /* attach this sub driver to iommu mapping if supported. */ + if (is_drm_iommu_supported(ctx->drm_dev)) { + /* + * If any channel is already active, iommu will throw + * a PAGE FAULT when enabled. So clear any channel if enabled. + */ + fimd_clear_channel(mgr); + drm_iommu_attach_device(ctx->drm_dev, ctx->dev); + } return 0; } -static int fimd_display_power_on(struct device *dev, int mode) +static void fimd_mgr_remove(struct exynos_drm_manager *mgr) { - /* TODO */ + struct fimd_context *ctx = mgr->ctx; - return 0; + /* detach this sub driver from iommu mapping if supported. */ + if (is_drm_iommu_supported(ctx->drm_dev)) + drm_iommu_detach_device(ctx->drm_dev, ctx->dev); } -static struct exynos_drm_display_ops fimd_display_ops = { - .type = EXYNOS_DISPLAY_TYPE_LCD, - .is_connected = fimd_display_is_connected, - .get_panel = fimd_get_panel, - .check_mode = fimd_check_mode, - .power_on = fimd_display_power_on, -}; - -static void fimd_dpms(struct device *subdrv_dev, int mode) +static u32 fimd_calc_clkdiv(struct fimd_context *ctx, + const struct drm_display_mode *mode) { - struct fimd_context *ctx = get_fimd_context(subdrv_dev); + unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh; + u32 clkdiv; - DRM_DEBUG_KMS("%d\n", mode); + /* Find the clock divider value that gets us closest to ideal_clk */ + clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->lcd_clk), ideal_clk); - mutex_lock(&ctx->lock); + return (clkdiv < 0x100) ? clkdiv : 0xff; +} - switch (mode) { - case DRM_MODE_DPMS_ON: - /* - * enable fimd hardware only if suspended status. - * - * P.S. fimd_dpms function would be called at booting time so - * clk_enable could be called double time. - */ - if (ctx->suspended) - pm_runtime_get_sync(subdrv_dev); - break; - case DRM_MODE_DPMS_STANDBY: - case DRM_MODE_DPMS_SUSPEND: - case DRM_MODE_DPMS_OFF: - if (!ctx->suspended) - pm_runtime_put_sync(subdrv_dev); - break; - default: - DRM_DEBUG_KMS("unspecified mode %d\n", mode); - break; - } +static bool fimd_mode_fixup(struct exynos_drm_manager *mgr, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + if (adjusted_mode->vrefresh == 0) + adjusted_mode->vrefresh = FIMD_DEFAULT_FRAMERATE; - mutex_unlock(&ctx->lock); + return true; } -static void fimd_apply(struct device *subdrv_dev) +static void fimd_mode_set(struct exynos_drm_manager *mgr, + const struct drm_display_mode *in_mode) { - struct fimd_context *ctx = get_fimd_context(subdrv_dev); - struct exynos_drm_manager *mgr = ctx->subdrv.manager; - struct exynos_drm_manager_ops *mgr_ops = mgr->ops; - struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops; - struct fimd_win_data *win_data; - int i; - - for (i = 0; i < WINDOWS_NR; i++) { - win_data = &ctx->win_data[i]; - if (win_data->enabled && (ovl_ops && ovl_ops->commit)) - ovl_ops->commit(subdrv_dev, i); - } + struct fimd_context *ctx = mgr->ctx; - if (mgr_ops && mgr_ops->commit) - mgr_ops->commit(subdrv_dev); + drm_mode_copy(&ctx->mode, in_mode); } -static void fimd_commit(struct device *dev) +static void fimd_commit(struct exynos_drm_manager *mgr) { - struct fimd_context *ctx = get_fimd_context(dev); - struct exynos_drm_panel_info *panel = &ctx->panel; - struct videomode *vm = &panel->vm; + struct fimd_context *ctx = mgr->ctx; + struct drm_display_mode *mode = &ctx->mode; struct fimd_driver_data *driver_data; - u32 val; + u32 val, clkdiv, vidcon1; + int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd; driver_data = ctx->driver_data; if (ctx->suspended) return; - /* setup polarity values from machine code. */ - writel(ctx->vidcon1, ctx->regs + driver_data->timing_base + VIDCON1); + /* nothing to do if we haven't set the mode yet */ + if (mode->htotal == 0 || mode->vtotal == 0) + return; + + /* setup polarity values */ + vidcon1 = ctx->vidcon1; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + vidcon1 |= VIDCON1_INV_VSYNC; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + vidcon1 |= VIDCON1_INV_HSYNC; + writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1); /* setup vertical timing values. */ - val = VIDTCON0_VBPD(vm->vback_porch - 1) | - VIDTCON0_VFPD(vm->vfront_porch - 1) | - VIDTCON0_VSPW(vm->vsync_len - 1); + vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; + vbpd = mode->crtc_vtotal - mode->crtc_vsync_end; + vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay; + + val = VIDTCON0_VBPD(vbpd - 1) | + VIDTCON0_VFPD(vfpd - 1) | + VIDTCON0_VSPW(vsync_len - 1); writel(val, ctx->regs + driver_data->timing_base + VIDTCON0); /* setup horizontal timing values. */ - val = VIDTCON1_HBPD(vm->hback_porch - 1) | - VIDTCON1_HFPD(vm->hfront_porch - 1) | - VIDTCON1_HSPW(vm->hsync_len - 1); + hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + hbpd = mode->crtc_htotal - mode->crtc_hsync_end; + hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay; + + val = VIDTCON1_HBPD(hbpd - 1) | + VIDTCON1_HFPD(hfpd - 1) | + VIDTCON1_HSPW(hsync_len - 1); writel(val, ctx->regs + driver_data->timing_base + VIDTCON1); /* setup horizontal and vertical display size. */ - val = VIDTCON2_LINEVAL(vm->vactive - 1) | - VIDTCON2_HOZVAL(vm->hactive - 1) | - VIDTCON2_LINEVAL_E(vm->vactive - 1) | - VIDTCON2_HOZVAL_E(vm->hactive - 1); + val = VIDTCON2_LINEVAL(mode->vdisplay - 1) | + VIDTCON2_HOZVAL(mode->hdisplay - 1) | + VIDTCON2_LINEVAL_E(mode->vdisplay - 1) | + VIDTCON2_HOZVAL_E(mode->hdisplay - 1); writel(val, ctx->regs + driver_data->timing_base + VIDTCON2); - /* setup clock source, clock divider, enable dma. */ - val = ctx->vidcon0; - val &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR); - - if (ctx->driver_data->has_clksel) { - val &= ~VIDCON0_CLKSEL_MASK; - val |= VIDCON0_CLKSEL_LCD; - } - - if (ctx->clkdiv > 1) - val |= VIDCON0_CLKVAL_F(ctx->clkdiv - 1) | VIDCON0_CLKDIR; - else - val &= ~VIDCON0_CLKDIR; /* 1:1 clock */ - /* * fields of register with prefix '_F' would be updated * at vsync(same as dma start) */ - val |= VIDCON0_ENVID | VIDCON0_ENVID_F; + val = VIDCON0_ENVID | VIDCON0_ENVID_F; + + if (ctx->driver_data->has_clksel) + val |= VIDCON0_CLKSEL_LCD; + + clkdiv = fimd_calc_clkdiv(ctx, mode); + if (clkdiv > 1) + val |= VIDCON0_CLKVAL_F(clkdiv - 1) | VIDCON0_CLKDIR; + writel(val, ctx->regs + VIDCON0); } -static int fimd_enable_vblank(struct device *dev) +static int fimd_enable_vblank(struct exynos_drm_manager *mgr) { - struct fimd_context *ctx = get_fimd_context(dev); + struct fimd_context *ctx = mgr->ctx; u32 val; if (ctx->suspended) @@ -314,9 +359,9 @@ static int fimd_enable_vblank(struct device *dev) return 0; } -static void fimd_disable_vblank(struct device *dev) +static void fimd_disable_vblank(struct exynos_drm_manager *mgr) { - struct fimd_context *ctx = get_fimd_context(dev); + struct fimd_context *ctx = mgr->ctx; u32 val; if (ctx->suspended) @@ -332,44 +377,16 @@ static void fimd_disable_vblank(struct device *dev) } } -static void fimd_wait_for_vblank(struct device *dev) -{ - struct fimd_context *ctx = get_fimd_context(dev); - - if (ctx->suspended) - return; - - atomic_set(&ctx->wait_vsync_event, 1); - - /* - * wait for FIMD to signal VSYNC interrupt or return after - * timeout which is set to 50ms (refresh rate of 20). - */ - if (!wait_event_timeout(ctx->wait_vsync_queue, - !atomic_read(&ctx->wait_vsync_event), - DRM_HZ/20)) - DRM_DEBUG_KMS("vblank wait timed out.\n"); -} - -static struct exynos_drm_manager_ops fimd_manager_ops = { - .dpms = fimd_dpms, - .apply = fimd_apply, - .commit = fimd_commit, - .enable_vblank = fimd_enable_vblank, - .disable_vblank = fimd_disable_vblank, - .wait_for_vblank = fimd_wait_for_vblank, -}; - -static void fimd_win_mode_set(struct device *dev, - struct exynos_drm_overlay *overlay) +static void fimd_win_mode_set(struct exynos_drm_manager *mgr, + struct exynos_drm_overlay *overlay) { - struct fimd_context *ctx = get_fimd_context(dev); + struct fimd_context *ctx = mgr->ctx; struct fimd_win_data *win_data; int win; unsigned long offset; if (!overlay) { - dev_err(dev, "overlay is NULL\n"); + DRM_ERROR("overlay is NULL\n"); return; } @@ -409,9 +426,8 @@ static void fimd_win_mode_set(struct device *dev, overlay->fb_width, overlay->crtc_width); } -static void fimd_win_set_pixfmt(struct device *dev, unsigned int win) +static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win) { - struct fimd_context *ctx = get_fimd_context(dev); struct fimd_win_data *win_data = &ctx->win_data[win]; unsigned long val; @@ -464,12 +480,24 @@ static void fimd_win_set_pixfmt(struct device *dev, unsigned int win) DRM_DEBUG_KMS("bpp = %d\n", win_data->bpp); + /* + * In case of exynos, setting dma-burst to 16Word causes permanent + * tearing for very small buffers, e.g. cursor buffer. Burst Mode + * switching which is based on overlay size is not recommended as + * overlay size varies alot towards the end of the screen and rapid + * movement causes unstable DMA which results into iommu crash/tear. + */ + + if (win_data->fb_width < MIN_FB_WIDTH_FOR_16WORD_BURST) { + val &= ~WINCONx_BURSTLEN_MASK; + val |= WINCONx_BURSTLEN_4WORD; + } + writel(val, ctx->regs + WINCON(win)); } -static void fimd_win_set_colkey(struct device *dev, unsigned int win) +static void fimd_win_set_colkey(struct fimd_context *ctx, unsigned int win) { - struct fimd_context *ctx = get_fimd_context(dev); unsigned int keycon0 = 0, keycon1 = 0; keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F | @@ -508,9 +536,9 @@ static void fimd_shadow_protect_win(struct fimd_context *ctx, writel(val, ctx->regs + reg); } -static void fimd_win_commit(struct device *dev, int zpos) +static void fimd_win_commit(struct exynos_drm_manager *mgr, int zpos) { - struct fimd_context *ctx = get_fimd_context(dev); + struct fimd_context *ctx = mgr->ctx; struct fimd_win_data *win_data; int win = zpos; unsigned long val, alpha, size; @@ -528,6 +556,12 @@ static void fimd_win_commit(struct device *dev, int zpos) win_data = &ctx->win_data[win]; + /* If suspended, enable this on resume */ + if (ctx->suspended) { + win_data->resume = true; + return; + } + /* * SHADOWCON/PRTCON register is used for enabling timing. * @@ -605,11 +639,11 @@ static void fimd_win_commit(struct device *dev, int zpos) DRM_DEBUG_KMS("osd size = 0x%x\n", (unsigned int)val); } - fimd_win_set_pixfmt(dev, win); + fimd_win_set_pixfmt(ctx, win); /* hardware window 0 doesn't support color key. */ if (win != 0) - fimd_win_set_colkey(dev, win); + fimd_win_set_colkey(ctx, win); /* wincon */ val = readl(ctx->regs + WINCON(win)); @@ -628,9 +662,9 @@ static void fimd_win_commit(struct device *dev, int zpos) win_data->enabled = true; } -static void fimd_win_disable(struct device *dev, int zpos) +static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos) { - struct fimd_context *ctx = get_fimd_context(dev); + struct fimd_context *ctx = mgr->ctx; struct fimd_win_data *win_data; int win = zpos; u32 val; @@ -669,400 +703,326 @@ static void fimd_win_disable(struct device *dev, int zpos) win_data->enabled = false; } -static struct exynos_drm_overlay_ops fimd_overlay_ops = { - .mode_set = fimd_win_mode_set, - .commit = fimd_win_commit, - .disable = fimd_win_disable, -}; - -static struct exynos_drm_manager fimd_manager = { - .pipe = -1, - .ops = &fimd_manager_ops, - .overlay_ops = &fimd_overlay_ops, - .display_ops = &fimd_display_ops, -}; - -static irqreturn_t fimd_irq_handler(int irq, void *dev_id) +static void fimd_window_suspend(struct exynos_drm_manager *mgr) { - struct fimd_context *ctx = (struct fimd_context *)dev_id; - struct exynos_drm_subdrv *subdrv = &ctx->subdrv; - struct drm_device *drm_dev = subdrv->drm_dev; - struct exynos_drm_manager *manager = subdrv->manager; - u32 val; - - val = readl(ctx->regs + VIDINTCON1); - - if (val & VIDINTCON1_INT_FRAME) - /* VSYNC interrupt */ - writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1); + struct fimd_context *ctx = mgr->ctx; + struct fimd_win_data *win_data; + int i; - /* check the crtc is detached already from encoder */ - if (manager->pipe < 0) - goto out; + for (i = 0; i < WINDOWS_NR; i++) { + win_data = &ctx->win_data[i]; + win_data->resume = win_data->enabled; + if (win_data->enabled) + fimd_win_disable(mgr, i); + } + fimd_wait_for_vblank(mgr); +} - drm_handle_vblank(drm_dev, manager->pipe); - exynos_drm_crtc_finish_pageflip(drm_dev, manager->pipe); +static void fimd_window_resume(struct exynos_drm_manager *mgr) +{ + struct fimd_context *ctx = mgr->ctx; + struct fimd_win_data *win_data; + int i; - /* set wait vsync event to zero and wake up queue. */ - if (atomic_read(&ctx->wait_vsync_event)) { - atomic_set(&ctx->wait_vsync_event, 0); - DRM_WAKEUP(&ctx->wait_vsync_queue); + for (i = 0; i < WINDOWS_NR; i++) { + win_data = &ctx->win_data[i]; + win_data->enabled = win_data->resume; + win_data->resume = false; } -out: - return IRQ_HANDLED; } -static int fimd_subdrv_probe(struct drm_device *drm_dev, struct device *dev) +static void fimd_apply(struct exynos_drm_manager *mgr) { - /* - * enable drm irq mode. - * - with irq_enabled = 1, we can use the vblank feature. - * - * P.S. note that we wouldn't use drm irq handler but - * just specific driver own one instead because - * drm framework supports only one irq handler. - */ - drm_dev->irq_enabled = 1; - - /* - * with vblank_disable_allowed = 1, vblank interrupt will be disabled - * by drm timer once a current process gives up ownership of - * vblank event.(after drm_vblank_put function is called) - */ - drm_dev->vblank_disable_allowed = 1; + struct fimd_context *ctx = mgr->ctx; + struct fimd_win_data *win_data; + int i; - /* attach this sub driver to iommu mapping if supported. */ - if (is_drm_iommu_supported(drm_dev)) - drm_iommu_attach_device(drm_dev, dev); + for (i = 0; i < WINDOWS_NR; i++) { + win_data = &ctx->win_data[i]; + if (win_data->enabled) + fimd_win_commit(mgr, i); + else + fimd_win_disable(mgr, i); + } - return 0; + fimd_commit(mgr); } -static void fimd_subdrv_remove(struct drm_device *drm_dev, struct device *dev) +static int fimd_poweron(struct exynos_drm_manager *mgr) { - /* detach this sub driver from iommu mapping if supported. */ - if (is_drm_iommu_supported(drm_dev)) - drm_iommu_detach_device(drm_dev, dev); -} + struct fimd_context *ctx = mgr->ctx; + int ret; -static int fimd_configure_clocks(struct fimd_context *ctx, struct device *dev) -{ - struct videomode *vm = &ctx->panel.vm; - unsigned long clk; + if (!ctx->suspended) + return 0; - ctx->bus_clk = devm_clk_get(dev, "fimd"); - if (IS_ERR(ctx->bus_clk)) { - dev_err(dev, "failed to get bus clock\n"); - return PTR_ERR(ctx->bus_clk); - } + ctx->suspended = false; - ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd"); - if (IS_ERR(ctx->lcd_clk)) { - dev_err(dev, "failed to get lcd clock\n"); - return PTR_ERR(ctx->lcd_clk); + pm_runtime_get_sync(ctx->dev); + + ret = clk_prepare_enable(ctx->bus_clk); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the bus clk [%d]\n", ret); + goto bus_clk_err; } - clk = clk_get_rate(ctx->lcd_clk); - if (clk == 0) { - dev_err(dev, "error getting sclk_fimd clock rate\n"); - return -EINVAL; + ret = clk_prepare_enable(ctx->lcd_clk); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the lcd clk [%d]\n", ret); + goto lcd_clk_err; } - if (vm->pixelclock == 0) { - unsigned long c; - c = vm->hactive + vm->hback_porch + vm->hfront_porch + - vm->hsync_len; - c *= vm->vactive + vm->vback_porch + vm->vfront_porch + - vm->vsync_len; - vm->pixelclock = c * FIMD_DEFAULT_FRAMERATE; - if (vm->pixelclock == 0) { - dev_err(dev, "incorrect display timings\n"); - return -EINVAL; + /* if vblank was enabled status, enable it again. */ + if (test_and_clear_bit(0, &ctx->irq_flags)) { + ret = fimd_enable_vblank(mgr); + if (ret) { + DRM_ERROR("Failed to re-enable vblank [%d]\n", ret); + goto enable_vblank_err; } - dev_warn(dev, "pixel clock recalculated to %luHz (%dHz frame rate)\n", - vm->pixelclock, FIMD_DEFAULT_FRAMERATE); - } - ctx->clkdiv = DIV_ROUND_UP(clk, vm->pixelclock); - if (ctx->clkdiv > 256) { - dev_warn(dev, "calculated pixel clock divider too high (%u), lowered to 256\n", - ctx->clkdiv); - ctx->clkdiv = 256; } - vm->pixelclock = clk / ctx->clkdiv; - DRM_DEBUG_KMS("pixel clock = %lu, clkdiv = %d\n", vm->pixelclock, - ctx->clkdiv); - return 0; -} + fimd_window_resume(mgr); -static void fimd_clear_win(struct fimd_context *ctx, int win) -{ - writel(0, ctx->regs + WINCON(win)); - writel(0, ctx->regs + VIDOSD_A(win)); - writel(0, ctx->regs + VIDOSD_B(win)); - writel(0, ctx->regs + VIDOSD_C(win)); + fimd_apply(mgr); - if (win == 1 || win == 2) - writel(0, ctx->regs + VIDOSD_D(win)); + return 0; - fimd_shadow_protect_win(ctx, win, false); +enable_vblank_err: + clk_disable_unprepare(ctx->lcd_clk); +lcd_clk_err: + clk_disable_unprepare(ctx->bus_clk); +bus_clk_err: + ctx->suspended = true; + return ret; } -static int fimd_clock(struct fimd_context *ctx, bool enable) +static int fimd_poweroff(struct exynos_drm_manager *mgr) { - if (enable) { - int ret; + struct fimd_context *ctx = mgr->ctx; - ret = clk_prepare_enable(ctx->bus_clk); - if (ret < 0) - return ret; + if (ctx->suspended) + return 0; - ret = clk_prepare_enable(ctx->lcd_clk); - if (ret < 0) { - clk_disable_unprepare(ctx->bus_clk); - return ret; - } - } else { - clk_disable_unprepare(ctx->lcd_clk); - clk_disable_unprepare(ctx->bus_clk); - } + /* + * We need to make sure that all windows are disabled before we + * suspend that connector. Otherwise we might try to scan from + * a destroyed buffer later. + */ + fimd_window_suspend(mgr); + + clk_disable_unprepare(ctx->lcd_clk); + clk_disable_unprepare(ctx->bus_clk); + pm_runtime_put_sync(ctx->dev); + + ctx->suspended = true; return 0; } -static void fimd_window_suspend(struct device *dev) +static void fimd_dpms(struct exynos_drm_manager *mgr, int mode) { - struct fimd_context *ctx = get_fimd_context(dev); - struct fimd_win_data *win_data; - int i; + DRM_DEBUG_KMS("%s, %d\n", __FILE__, mode); - for (i = 0; i < WINDOWS_NR; i++) { - win_data = &ctx->win_data[i]; - win_data->resume = win_data->enabled; - fimd_win_disable(dev, i); + switch (mode) { + case DRM_MODE_DPMS_ON: + fimd_poweron(mgr); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + fimd_poweroff(mgr); + break; + default: + DRM_DEBUG_KMS("unspecified mode %d\n", mode); + break; } - fimd_wait_for_vblank(dev); } -static void fimd_window_resume(struct device *dev) -{ - struct fimd_context *ctx = get_fimd_context(dev); - struct fimd_win_data *win_data; - int i; +static struct exynos_drm_manager_ops fimd_manager_ops = { + .dpms = fimd_dpms, + .mode_fixup = fimd_mode_fixup, + .mode_set = fimd_mode_set, + .commit = fimd_commit, + .enable_vblank = fimd_enable_vblank, + .disable_vblank = fimd_disable_vblank, + .wait_for_vblank = fimd_wait_for_vblank, + .win_mode_set = fimd_win_mode_set, + .win_commit = fimd_win_commit, + .win_disable = fimd_win_disable, +}; - for (i = 0; i < WINDOWS_NR; i++) { - win_data = &ctx->win_data[i]; - win_data->enabled = win_data->resume; - win_data->resume = false; - } -} +static struct exynos_drm_manager fimd_manager = { + .type = EXYNOS_DISPLAY_TYPE_LCD, + .ops = &fimd_manager_ops, +}; -static int fimd_activate(struct fimd_context *ctx, bool enable) +static irqreturn_t fimd_irq_handler(int irq, void *dev_id) { - struct device *dev = ctx->subdrv.dev; - if (enable) { - int ret; + struct fimd_context *ctx = (struct fimd_context *)dev_id; + u32 val; - ret = fimd_clock(ctx, true); - if (ret < 0) - return ret; + val = readl(ctx->regs + VIDINTCON1); - ctx->suspended = false; + if (val & VIDINTCON1_INT_FRAME) + /* VSYNC interrupt */ + writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1); - /* if vblank was enabled status, enable it again. */ - if (test_and_clear_bit(0, &ctx->irq_flags)) - fimd_enable_vblank(dev); + /* check the crtc is detached already from encoder */ + if (ctx->pipe < 0 || !ctx->drm_dev) + goto out; - fimd_window_resume(dev); - } else { - fimd_window_suspend(dev); + drm_handle_vblank(ctx->drm_dev, ctx->pipe); + exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); - fimd_clock(ctx, false); - ctx->suspended = true; + /* set wait vsync event to zero and wake up queue. */ + if (atomic_read(&ctx->wait_vsync_event)) { + atomic_set(&ctx->wait_vsync_event, 0); + wake_up(&ctx->wait_vsync_queue); } +out: + return IRQ_HANDLED; +} + +static int fimd_bind(struct device *dev, struct device *master, void *data) +{ + struct fimd_context *ctx = fimd_manager.ctx; + struct drm_device *drm_dev = data; + + fimd_mgr_initialize(&fimd_manager, drm_dev); + exynos_drm_crtc_create(&fimd_manager); + if (ctx->display) + exynos_drm_create_enc_conn(drm_dev, ctx->display); return 0; + } -static int fimd_get_platform_data(struct fimd_context *ctx, struct device *dev) +static void fimd_unbind(struct device *dev, struct device *master, + void *data) { - struct videomode *vm; - int ret; + struct exynos_drm_manager *mgr = dev_get_drvdata(dev); + struct fimd_context *ctx = fimd_manager.ctx; + struct drm_crtc *crtc = mgr->crtc; - vm = &ctx->panel.vm; - ret = of_get_videomode(dev->of_node, vm, OF_USE_NATIVE_MODE); - if (ret) { - DRM_ERROR("failed: of_get_videomode() : %d\n", ret); - return ret; - } + fimd_dpms(mgr, DRM_MODE_DPMS_OFF); - if (vm->flags & DISPLAY_FLAGS_VSYNC_LOW) - ctx->vidcon1 |= VIDCON1_INV_VSYNC; - if (vm->flags & DISPLAY_FLAGS_HSYNC_LOW) - ctx->vidcon1 |= VIDCON1_INV_HSYNC; - if (vm->flags & DISPLAY_FLAGS_DE_LOW) - ctx->vidcon1 |= VIDCON1_INV_VDEN; - if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) - ctx->vidcon1 |= VIDCON1_INV_VCLK; + if (ctx->display) + exynos_dpi_remove(dev); - return 0; + fimd_mgr_remove(mgr); + + crtc->funcs->destroy(crtc); } +static const struct component_ops fimd_component_ops = { + .bind = fimd_bind, + .unbind = fimd_unbind, +}; + static int fimd_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct fimd_context *ctx; - struct exynos_drm_subdrv *subdrv; struct resource *res; - int win; int ret = -EINVAL; - if (!dev->of_node) - return -ENODEV; + ret = exynos_drm_component_add(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC, + fimd_manager.type); + if (ret) + return ret; + + if (!dev->of_node) { + ret = -ENODEV; + goto err_del_component; + } ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; + if (!ctx) { + ret = -ENOMEM; + goto err_del_component; + } - ret = fimd_get_platform_data(ctx, dev); - if (ret) - return ret; + ctx->dev = dev; + ctx->suspended = true; - ret = fimd_configure_clocks(ctx, dev); - if (ret) - return ret; + if (of_property_read_bool(dev->of_node, "samsung,invert-vden")) + ctx->vidcon1 |= VIDCON1_INV_VDEN; + if (of_property_read_bool(dev->of_node, "samsung,invert-vclk")) + ctx->vidcon1 |= VIDCON1_INV_VCLK; + + ctx->bus_clk = devm_clk_get(dev, "fimd"); + if (IS_ERR(ctx->bus_clk)) { + dev_err(dev, "failed to get bus clock\n"); + ret = PTR_ERR(ctx->bus_clk); + goto err_del_component; + } + + ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd"); + if (IS_ERR(ctx->lcd_clk)) { + dev_err(dev, "failed to get lcd clock\n"); + ret = PTR_ERR(ctx->lcd_clk); + goto err_del_component; + } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ctx->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(ctx->regs)) - return PTR_ERR(ctx->regs); + if (IS_ERR(ctx->regs)) { + ret = PTR_ERR(ctx->regs); + goto err_del_component; + } res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "vsync"); if (!res) { dev_err(dev, "irq request failed.\n"); - return -ENXIO; + ret = -ENXIO; + goto err_del_component; } - ctx->irq = res->start; - - ret = devm_request_irq(dev, ctx->irq, fimd_irq_handler, + ret = devm_request_irq(dev, res->start, fimd_irq_handler, 0, "drm_fimd", ctx); if (ret) { dev_err(dev, "irq request failed.\n"); - return ret; + goto err_del_component; } ctx->driver_data = drm_fimd_get_driver_data(pdev); - DRM_INIT_WAITQUEUE(&ctx->wait_vsync_queue); + init_waitqueue_head(&ctx->wait_vsync_queue); atomic_set(&ctx->wait_vsync_event, 0); - subdrv = &ctx->subdrv; + platform_set_drvdata(pdev, &fimd_manager); - subdrv->dev = dev; - subdrv->manager = &fimd_manager; - subdrv->probe = fimd_subdrv_probe; - subdrv->remove = fimd_subdrv_remove; + fimd_manager.ctx = ctx; - mutex_init(&ctx->lock); + ctx->display = exynos_dpi_probe(dev); + if (IS_ERR(ctx->display)) + return PTR_ERR(ctx->display); - platform_set_drvdata(pdev, ctx); + pm_runtime_enable(&pdev->dev); - pm_runtime_enable(dev); - pm_runtime_get_sync(dev); + ret = component_add(&pdev->dev, &fimd_component_ops); + if (ret) + goto err_disable_pm_runtime; - for (win = 0; win < WINDOWS_NR; win++) - fimd_clear_win(ctx, win); + return ret; - exynos_drm_subdrv_register(subdrv); +err_disable_pm_runtime: + pm_runtime_disable(&pdev->dev); - return 0; +err_del_component: + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC); + return ret; } static int fimd_remove(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - struct fimd_context *ctx = platform_get_drvdata(pdev); - - exynos_drm_subdrv_unregister(&ctx->subdrv); - - if (ctx->suspended) - goto out; - - pm_runtime_set_suspended(dev); - pm_runtime_put_sync(dev); - -out: - pm_runtime_disable(dev); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int fimd_suspend(struct device *dev) -{ - struct fimd_context *ctx = get_fimd_context(dev); - - /* - * do not use pm_runtime_suspend(). if pm_runtime_suspend() is - * called here, an error would be returned by that interface - * because the usage_count of pm runtime is more than 1. - */ - if (!pm_runtime_suspended(dev)) - return fimd_activate(ctx, false); - - return 0; -} - -static int fimd_resume(struct device *dev) -{ - struct fimd_context *ctx = get_fimd_context(dev); - - /* - * if entered to sleep when lcd panel was on, the usage_count - * of pm runtime would still be 1 so in this case, fimd driver - * should be on directly not drawing on pm runtime interface. - */ - if (!pm_runtime_suspended(dev)) { - int ret; - - ret = fimd_activate(ctx, true); - if (ret < 0) - return ret; + pm_runtime_disable(&pdev->dev); - /* - * in case of dpms on(standby), fimd_apply function will - * be called by encoder's dpms callback to update fimd's - * registers but in case of sleep wakeup, it's not. - * so fimd_apply function should be called at here. - */ - fimd_apply(dev); - } + component_del(&pdev->dev, &fimd_component_ops); + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC); return 0; } -#endif - -#ifdef CONFIG_PM_RUNTIME -static int fimd_runtime_suspend(struct device *dev) -{ - struct fimd_context *ctx = get_fimd_context(dev); - - return fimd_activate(ctx, false); -} - -static int fimd_runtime_resume(struct device *dev) -{ - struct fimd_context *ctx = get_fimd_context(dev); - - return fimd_activate(ctx, true); -} -#endif - -static const struct dev_pm_ops fimd_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(fimd_suspend, fimd_resume) - SET_RUNTIME_PM_OPS(fimd_runtime_suspend, fimd_runtime_resume, NULL) -}; struct platform_driver fimd_driver = { .probe = fimd_probe, @@ -1070,7 +1030,6 @@ struct platform_driver fimd_driver = { .driver = { .name = "exynos4-fb", .owner = THIS_MODULE, - .pm = &fimd_pm_ops, .of_match_table = fimd_driver_dt_match, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c index 3271fd4b172..80015871447 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_g2d.c +++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.c @@ -383,6 +383,8 @@ out: g2d_userptr->npages, g2d_userptr->vma); + exynos_gem_put_vma(g2d_userptr->vma); + if (!g2d_userptr->out_of_list) list_del_init(&g2d_userptr->list); @@ -465,14 +467,17 @@ static dma_addr_t *g2d_userptr_get_dma_addr(struct drm_device *drm_dev, goto err_free; } + down_read(¤t->mm->mmap_sem); vma = find_vma(current->mm, userptr); if (!vma) { + up_read(¤t->mm->mmap_sem); DRM_ERROR("failed to get vm region.\n"); ret = -EFAULT; goto err_free_pages; } if (vma->vm_end < userptr + size) { + up_read(¤t->mm->mmap_sem); DRM_ERROR("vma is too small.\n"); ret = -EFAULT; goto err_free_pages; @@ -480,6 +485,7 @@ static dma_addr_t *g2d_userptr_get_dma_addr(struct drm_device *drm_dev, g2d_userptr->vma = exynos_gem_get_vma(vma); if (!g2d_userptr->vma) { + up_read(¤t->mm->mmap_sem); DRM_ERROR("failed to copy vma.\n"); ret = -ENOMEM; goto err_free_pages; @@ -490,10 +496,12 @@ static dma_addr_t *g2d_userptr_get_dma_addr(struct drm_device *drm_dev, ret = exynos_gem_get_pages_from_userptr(start & PAGE_MASK, npages, pages, vma); if (ret < 0) { + up_read(¤t->mm->mmap_sem); DRM_ERROR("failed to get user pages from userptr.\n"); goto err_put_vma; } + up_read(¤t->mm->mmap_sem); g2d_userptr->pages = pages; sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); @@ -605,7 +613,7 @@ static enum g2d_reg_type g2d_get_reg_type(int reg_offset) reg_type = REG_TYPE_NONE; DRM_ERROR("Unknown register offset![%d]\n", reg_offset); break; - }; + } return reg_type; } @@ -1124,7 +1132,7 @@ int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data, * G2D interrupt event once current command list execution is * finished. * Otherwise only ACF bit should be set to INTEN register so - * that one interrupt is occured after all command lists + * that one interrupt is occurred after all command lists * have been completed. */ if (node->event) { diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index 49f9cd23275..163a054922c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -338,46 +338,22 @@ int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, &args->offset); } -static struct drm_file *exynos_drm_find_drm_file(struct drm_device *drm_dev, - struct file *filp) -{ - struct drm_file *file_priv; - - /* find current process's drm_file from filelist. */ - list_for_each_entry(file_priv, &drm_dev->filelist, lhead) - if (file_priv->filp == filp) - return file_priv; - - WARN_ON(1); - - return ERR_PTR(-EFAULT); -} - -static int exynos_drm_gem_mmap_buffer(struct file *filp, +int exynos_drm_gem_mmap_buffer(struct file *filp, struct vm_area_struct *vma) { struct drm_gem_object *obj = filp->private_data; struct exynos_drm_gem_obj *exynos_gem_obj = to_exynos_gem_obj(obj); struct drm_device *drm_dev = obj->dev; struct exynos_drm_gem_buf *buffer; - struct drm_file *file_priv; unsigned long vm_size; int ret; + WARN_ON(!mutex_is_locked(&obj->dev->struct_mutex)); + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; vma->vm_private_data = obj; vma->vm_ops = drm_dev->driver->gem_vm_ops; - /* restore it to driver's fops. */ - filp->f_op = fops_get(drm_dev->driver->fops); - - file_priv = exynos_drm_find_drm_file(drm_dev, filp); - if (IS_ERR(file_priv)) - return PTR_ERR(file_priv); - - /* restore it to drm_file. */ - filp->private_data = file_priv; - update_vm_cache_attr(exynos_gem_obj, vma); vm_size = vma->vm_end - vma->vm_start; @@ -411,15 +387,13 @@ static int exynos_drm_gem_mmap_buffer(struct file *filp, return 0; } -static const struct file_operations exynos_drm_gem_fops = { - .mmap = exynos_drm_gem_mmap_buffer, -}; - int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { + struct drm_exynos_file_private *exynos_file_priv; struct drm_exynos_gem_mmap *args = data; struct drm_gem_object *obj; + struct file *anon_filp; unsigned long addr; if (!(dev->driver->driver_features & DRIVER_GEM)) { @@ -427,47 +401,25 @@ int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, return -ENODEV; } + mutex_lock(&dev->struct_mutex); + obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (!obj) { DRM_ERROR("failed to lookup gem object.\n"); + mutex_unlock(&dev->struct_mutex); return -EINVAL; } - /* - * We have to use gem object and its fops for specific mmaper, - * but vm_mmap() can deliver only filp. So we have to change - * filp->f_op and filp->private_data temporarily, then restore - * again. So it is important to keep lock until restoration the - * settings to prevent others from misuse of filp->f_op or - * filp->private_data. - */ - mutex_lock(&dev->struct_mutex); + exynos_file_priv = file_priv->driver_priv; + anon_filp = exynos_file_priv->anon_filp; + anon_filp->private_data = obj; - /* - * Set specific mmper's fops. And it will be restored by - * exynos_drm_gem_mmap_buffer to dev->driver->fops. - * This is used to call specific mapper temporarily. - */ - file_priv->filp->f_op = &exynos_drm_gem_fops; - - /* - * Set gem object to private_data so that specific mmaper - * can get the gem object. And it will be restored by - * exynos_drm_gem_mmap_buffer to drm_file. - */ - file_priv->filp->private_data = obj; - - addr = vm_mmap(file_priv->filp, 0, args->size, - PROT_READ | PROT_WRITE, MAP_SHARED, 0); + addr = vm_mmap(anon_filp, 0, args->size, PROT_READ | PROT_WRITE, + MAP_SHARED, 0); drm_gem_object_unreference(obj); if (IS_ERR_VALUE(addr)) { - /* check filp->f_op, filp->private_data are restored */ - if (file_priv->filp->f_op == &exynos_drm_gem_fops) { - file_priv->filp->f_op = fops_get(dev->driver->fops); - file_priv->filp->private_data = file_priv; - } mutex_unlock(&dev->struct_mutex); return (int)addr; } @@ -630,11 +582,6 @@ void exynos_gem_unmap_sgt_from_dma(struct drm_device *drm_dev, dma_unmap_sg(drm_dev->dev, sgt->sgl, sgt->nents, dir); } -int exynos_drm_gem_init_object(struct drm_gem_object *obj) -{ - return 0; -} - void exynos_drm_gem_free_object(struct drm_gem_object *obj) { struct exynos_drm_gem_obj *exynos_gem_obj; @@ -657,7 +604,7 @@ int exynos_drm_gem_dumb_create(struct drm_file *file_priv, int ret; /* - * alocate memory to be used for framebuffer. + * allocate memory to be used for framebuffer. * - this callback would be called by user application * with DRM_IOCTL_MODE_CREATE_DUMB command. */ @@ -665,22 +612,20 @@ int exynos_drm_gem_dumb_create(struct drm_file *file_priv, args->pitch = args->width * ((args->bpp + 7) / 8); args->size = args->pitch * args->height; - exynos_gem_obj = exynos_drm_gem_create(dev, EXYNOS_BO_CONTIG | - EXYNOS_BO_WC, args->size); - /* - * If physically contiguous memory allocation fails and if IOMMU is - * supported then try to get buffer from non physically contiguous - * memory area. - */ - if (IS_ERR(exynos_gem_obj) && is_drm_iommu_supported(dev)) { - dev_warn(dev->dev, "contiguous FB allocation failed, falling back to non-contiguous\n"); + if (is_drm_iommu_supported(dev)) { + exynos_gem_obj = exynos_drm_gem_create(dev, + EXYNOS_BO_NONCONTIG | EXYNOS_BO_WC, + args->size); + } else { exynos_gem_obj = exynos_drm_gem_create(dev, - EXYNOS_BO_NONCONTIG | EXYNOS_BO_WC, - args->size); + EXYNOS_BO_CONTIG | EXYNOS_BO_WC, + args->size); } - if (IS_ERR(exynos_gem_obj)) + if (IS_ERR(exynos_gem_obj)) { + dev_warn(dev->dev, "FB allocation failed.\n"); return PTR_ERR(exynos_gem_obj); + } ret = exynos_drm_gem_handle_create(&exynos_gem_obj->base, file_priv, &args->handle); diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.h b/drivers/gpu/drm/exynos/exynos_drm_gem.h index 09555afdfe9..1592c0ba7de 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.h +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.h @@ -60,7 +60,7 @@ struct exynos_drm_gem_buf { * @vma: a pointer to vm_area. * @flags: indicate memory type to allocated buffer and cache attruibute. * - * P.S. this object would be transfered to user as kms_bo.handle so + * P.S. this object would be transferred to user as kms_bo.handle so * user can access the buffer through kms_bo.handle. */ struct exynos_drm_gem_obj { @@ -122,6 +122,9 @@ int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +int exynos_drm_gem_mmap_buffer(struct file *filp, + struct vm_area_struct *vma); + /* map user space allocated by malloc to pages. */ int exynos_drm_gem_userptr_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); @@ -135,9 +138,6 @@ unsigned long exynos_drm_gem_get_size(struct drm_device *dev, unsigned int gem_handle, struct drm_file *file_priv); -/* initialize gem object. */ -int exynos_drm_gem_init_object(struct drm_gem_object *obj); - /* free gem object. */ void exynos_drm_gem_free_object(struct drm_gem_object *gem_obj); diff --git a/drivers/gpu/drm/exynos/exynos_drm_gsc.c b/drivers/gpu/drm/exynos/exynos_drm_gsc.c index cd6aebd53bd..9e3ff167296 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gsc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gsc.c @@ -1301,13 +1301,13 @@ static irqreturn_t gsc_irq_handler(int irq, void *dev_id) status = gsc_read(GSC_IRQ); if (status & GSC_IRQ_STATUS_OR_IRQ) { - dev_err(ippdrv->dev, "occured overflow at %d, status 0x%x.\n", + dev_err(ippdrv->dev, "occurred overflow at %d, status 0x%x.\n", ctx->id, status); return IRQ_NONE; } if (status & GSC_IRQ_STATUS_OR_FRM_DONE) { - dev_dbg(ippdrv->dev, "occured frame done at %d, status 0x%x.\n", + dev_dbg(ippdrv->dev, "occurred frame done at %d, status 0x%x.\n", ctx->id, status); buf_id[EXYNOS_DRM_OPS_SRC] = gsc_get_src_buf_index(ctx); @@ -1335,11 +1335,7 @@ static irqreturn_t gsc_irq_handler(int irq, void *dev_id) static int gsc_init_prop_list(struct exynos_drm_ippdrv *ippdrv) { - struct drm_exynos_ipp_prop_list *prop_list; - - prop_list = devm_kzalloc(ippdrv->dev, sizeof(*prop_list), GFP_KERNEL); - if (!prop_list) - return -ENOMEM; + struct drm_exynos_ipp_prop_list *prop_list = &ippdrv->prop_list; prop_list->version = 1; prop_list->writeback = 1; @@ -1363,8 +1359,6 @@ static int gsc_init_prop_list(struct exynos_drm_ippdrv *ippdrv) prop_list->scale_min.hsize = GSC_SCALE_MIN; prop_list->scale_min.vsize = GSC_SCALE_MIN; - ippdrv->prop_list = prop_list; - return 0; } @@ -1387,7 +1381,7 @@ static int gsc_ippdrv_check_property(struct device *dev, { struct gsc_context *ctx = get_gsc_context(dev); struct exynos_drm_ippdrv *ippdrv = &ctx->ippdrv; - struct drm_exynos_ipp_prop_list *pp = ippdrv->prop_list; + struct drm_exynos_ipp_prop_list *pp = &ippdrv->prop_list; struct drm_exynos_ipp_config *config; struct drm_exynos_pos *pos; struct drm_exynos_sz *sz; diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c deleted file mode 100644 index 8548b974bd5..00000000000 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright (C) 2011 Samsung Electronics Co.Ltd - * Authors: - * Inki Dae <inki.dae@samsung.com> - * Seung-Woo Kim <sw0312.kim@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ - -#include <drm/drmP.h> - -#include <linux/kernel.h> -#include <linux/wait.h> -#include <linux/platform_device.h> -#include <linux/pm_runtime.h> - -#include <drm/exynos_drm.h> - -#include "exynos_drm_drv.h" -#include "exynos_drm_hdmi.h" - -#define to_context(dev) platform_get_drvdata(to_platform_device(dev)) -#define to_subdrv(dev) to_context(dev) -#define get_ctx_from_subdrv(subdrv) container_of(subdrv,\ - struct drm_hdmi_context, subdrv); - -/* platform device pointer for common drm hdmi device. */ -static struct platform_device *exynos_drm_hdmi_pdev; - -/* Common hdmi subdrv needs to access the hdmi and mixer though context. -* These should be initialied by the repective drivers */ -static struct exynos_drm_hdmi_context *hdmi_ctx; -static struct exynos_drm_hdmi_context *mixer_ctx; - -/* these callback points shoud be set by specific drivers. */ -static struct exynos_hdmi_ops *hdmi_ops; -static struct exynos_mixer_ops *mixer_ops; - -struct drm_hdmi_context { - struct exynos_drm_subdrv subdrv; - struct exynos_drm_hdmi_context *hdmi_ctx; - struct exynos_drm_hdmi_context *mixer_ctx; - - bool enabled[MIXER_WIN_NR]; -}; - -int exynos_platform_device_hdmi_register(void) -{ - struct platform_device *pdev; - - if (exynos_drm_hdmi_pdev) - return -EEXIST; - - pdev = platform_device_register_simple( - "exynos-drm-hdmi", -1, NULL, 0); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); - - exynos_drm_hdmi_pdev = pdev; - - return 0; -} - -void exynos_platform_device_hdmi_unregister(void) -{ - if (exynos_drm_hdmi_pdev) { - platform_device_unregister(exynos_drm_hdmi_pdev); - exynos_drm_hdmi_pdev = NULL; - } -} - -void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx) -{ - if (ctx) - hdmi_ctx = ctx; -} - -void exynos_mixer_drv_attach(struct exynos_drm_hdmi_context *ctx) -{ - if (ctx) - mixer_ctx = ctx; -} - -void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops) -{ - if (ops) - hdmi_ops = ops; -} - -void exynos_mixer_ops_register(struct exynos_mixer_ops *ops) -{ - if (ops) - mixer_ops = ops; -} - -static bool drm_hdmi_is_connected(struct device *dev) -{ - struct drm_hdmi_context *ctx = to_context(dev); - - if (hdmi_ops && hdmi_ops->is_connected) - return hdmi_ops->is_connected(ctx->hdmi_ctx->ctx); - - return false; -} - -static struct edid *drm_hdmi_get_edid(struct device *dev, - struct drm_connector *connector) -{ - struct drm_hdmi_context *ctx = to_context(dev); - - if (hdmi_ops && hdmi_ops->get_edid) - return hdmi_ops->get_edid(ctx->hdmi_ctx->ctx, connector); - - return NULL; -} - -static int drm_hdmi_check_mode(struct device *dev, - struct drm_display_mode *mode) -{ - struct drm_hdmi_context *ctx = to_context(dev); - int ret = 0; - - /* - * Both, mixer and hdmi should be able to handle the requested mode. - * If any of the two fails, return mode as BAD. - */ - - if (mixer_ops && mixer_ops->check_mode) - ret = mixer_ops->check_mode(ctx->mixer_ctx->ctx, mode); - - if (ret) - return ret; - - if (hdmi_ops && hdmi_ops->check_mode) - return hdmi_ops->check_mode(ctx->hdmi_ctx->ctx, mode); - - return 0; -} - -static int drm_hdmi_power_on(struct device *dev, int mode) -{ - struct drm_hdmi_context *ctx = to_context(dev); - - if (hdmi_ops && hdmi_ops->power_on) - return hdmi_ops->power_on(ctx->hdmi_ctx->ctx, mode); - - return 0; -} - -static struct exynos_drm_display_ops drm_hdmi_display_ops = { - .type = EXYNOS_DISPLAY_TYPE_HDMI, - .is_connected = drm_hdmi_is_connected, - .get_edid = drm_hdmi_get_edid, - .check_mode = drm_hdmi_check_mode, - .power_on = drm_hdmi_power_on, -}; - -static int drm_hdmi_enable_vblank(struct device *subdrv_dev) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - struct exynos_drm_subdrv *subdrv = &ctx->subdrv; - struct exynos_drm_manager *manager = subdrv->manager; - - if (mixer_ops && mixer_ops->enable_vblank) - return mixer_ops->enable_vblank(ctx->mixer_ctx->ctx, - manager->pipe); - - return 0; -} - -static void drm_hdmi_disable_vblank(struct device *subdrv_dev) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - - if (mixer_ops && mixer_ops->disable_vblank) - return mixer_ops->disable_vblank(ctx->mixer_ctx->ctx); -} - -static void drm_hdmi_wait_for_vblank(struct device *subdrv_dev) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - - if (mixer_ops && mixer_ops->wait_for_vblank) - mixer_ops->wait_for_vblank(ctx->mixer_ctx->ctx); -} - -static void drm_hdmi_mode_fixup(struct device *subdrv_dev, - struct drm_connector *connector, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - struct drm_display_mode *m; - int mode_ok; - - drm_mode_set_crtcinfo(adjusted_mode, 0); - - mode_ok = drm_hdmi_check_mode(subdrv_dev, adjusted_mode); - - /* just return if user desired mode exists. */ - if (mode_ok == 0) - return; - - /* - * otherwise, find the most suitable mode among modes and change it - * to adjusted_mode. - */ - list_for_each_entry(m, &connector->modes, head) { - mode_ok = drm_hdmi_check_mode(subdrv_dev, m); - - if (mode_ok == 0) { - struct drm_mode_object base; - struct list_head head; - - DRM_INFO("desired mode doesn't exist so\n"); - DRM_INFO("use the most suitable mode among modes.\n"); - - DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n", - m->hdisplay, m->vdisplay, m->vrefresh); - - /* preserve display mode header while copying. */ - head = adjusted_mode->head; - base = adjusted_mode->base; - memcpy(adjusted_mode, m, sizeof(*m)); - adjusted_mode->head = head; - adjusted_mode->base = base; - break; - } - } -} - -static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - - if (hdmi_ops && hdmi_ops->mode_set) - hdmi_ops->mode_set(ctx->hdmi_ctx->ctx, mode); -} - -static void drm_hdmi_get_max_resol(struct device *subdrv_dev, - unsigned int *width, unsigned int *height) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - - if (hdmi_ops && hdmi_ops->get_max_resol) - hdmi_ops->get_max_resol(ctx->hdmi_ctx->ctx, width, height); -} - -static void drm_hdmi_commit(struct device *subdrv_dev) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - - if (hdmi_ops && hdmi_ops->commit) - hdmi_ops->commit(ctx->hdmi_ctx->ctx); -} - -static void drm_hdmi_dpms(struct device *subdrv_dev, int mode) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - - if (mixer_ops && mixer_ops->dpms) - mixer_ops->dpms(ctx->mixer_ctx->ctx, mode); - - if (hdmi_ops && hdmi_ops->dpms) - hdmi_ops->dpms(ctx->hdmi_ctx->ctx, mode); -} - -static void drm_hdmi_apply(struct device *subdrv_dev) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - int i; - - for (i = 0; i < MIXER_WIN_NR; i++) { - if (!ctx->enabled[i]) - continue; - if (mixer_ops && mixer_ops->win_commit) - mixer_ops->win_commit(ctx->mixer_ctx->ctx, i); - } - - if (hdmi_ops && hdmi_ops->commit) - hdmi_ops->commit(ctx->hdmi_ctx->ctx); -} - -static struct exynos_drm_manager_ops drm_hdmi_manager_ops = { - .dpms = drm_hdmi_dpms, - .apply = drm_hdmi_apply, - .enable_vblank = drm_hdmi_enable_vblank, - .disable_vblank = drm_hdmi_disable_vblank, - .wait_for_vblank = drm_hdmi_wait_for_vblank, - .mode_fixup = drm_hdmi_mode_fixup, - .mode_set = drm_hdmi_mode_set, - .get_max_resol = drm_hdmi_get_max_resol, - .commit = drm_hdmi_commit, -}; - -static void drm_mixer_mode_set(struct device *subdrv_dev, - struct exynos_drm_overlay *overlay) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - - if (mixer_ops && mixer_ops->win_mode_set) - mixer_ops->win_mode_set(ctx->mixer_ctx->ctx, overlay); -} - -static void drm_mixer_commit(struct device *subdrv_dev, int zpos) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos; - - if (win < 0 || win >= MIXER_WIN_NR) { - DRM_ERROR("mixer window[%d] is wrong\n", win); - return; - } - - if (mixer_ops && mixer_ops->win_commit) - mixer_ops->win_commit(ctx->mixer_ctx->ctx, win); - - ctx->enabled[win] = true; -} - -static void drm_mixer_disable(struct device *subdrv_dev, int zpos) -{ - struct drm_hdmi_context *ctx = to_context(subdrv_dev); - int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos; - - if (win < 0 || win >= MIXER_WIN_NR) { - DRM_ERROR("mixer window[%d] is wrong\n", win); - return; - } - - if (mixer_ops && mixer_ops->win_disable) - mixer_ops->win_disable(ctx->mixer_ctx->ctx, win); - - ctx->enabled[win] = false; -} - -static struct exynos_drm_overlay_ops drm_hdmi_overlay_ops = { - .mode_set = drm_mixer_mode_set, - .commit = drm_mixer_commit, - .disable = drm_mixer_disable, -}; - -static struct exynos_drm_manager hdmi_manager = { - .pipe = -1, - .ops = &drm_hdmi_manager_ops, - .overlay_ops = &drm_hdmi_overlay_ops, - .display_ops = &drm_hdmi_display_ops, -}; - -static int hdmi_subdrv_probe(struct drm_device *drm_dev, - struct device *dev) -{ - struct exynos_drm_subdrv *subdrv = to_subdrv(dev); - struct drm_hdmi_context *ctx; - - if (!hdmi_ctx) { - DRM_ERROR("hdmi context not initialized.\n"); - return -EFAULT; - } - - if (!mixer_ctx) { - DRM_ERROR("mixer context not initialized.\n"); - return -EFAULT; - } - - ctx = get_ctx_from_subdrv(subdrv); - - if (!ctx) { - DRM_ERROR("no drm hdmi context.\n"); - return -EFAULT; - } - - ctx->hdmi_ctx = hdmi_ctx; - ctx->mixer_ctx = mixer_ctx; - - ctx->hdmi_ctx->drm_dev = drm_dev; - ctx->mixer_ctx->drm_dev = drm_dev; - - if (mixer_ops->iommu_on) - mixer_ops->iommu_on(ctx->mixer_ctx->ctx, true); - - return 0; -} - -static void hdmi_subdrv_remove(struct drm_device *drm_dev, struct device *dev) -{ - struct drm_hdmi_context *ctx; - struct exynos_drm_subdrv *subdrv = to_subdrv(dev); - - ctx = get_ctx_from_subdrv(subdrv); - - if (mixer_ops->iommu_on) - mixer_ops->iommu_on(ctx->mixer_ctx->ctx, false); -} - -static int exynos_drm_hdmi_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct exynos_drm_subdrv *subdrv; - struct drm_hdmi_context *ctx; - - ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) - return -ENOMEM; - - subdrv = &ctx->subdrv; - - subdrv->dev = dev; - subdrv->manager = &hdmi_manager; - subdrv->probe = hdmi_subdrv_probe; - subdrv->remove = hdmi_subdrv_remove; - - platform_set_drvdata(pdev, subdrv); - - exynos_drm_subdrv_register(subdrv); - - return 0; -} - -static int exynos_drm_hdmi_remove(struct platform_device *pdev) -{ - struct drm_hdmi_context *ctx = platform_get_drvdata(pdev); - - exynos_drm_subdrv_unregister(&ctx->subdrv); - - return 0; -} - -struct platform_driver exynos_drm_common_hdmi_driver = { - .probe = exynos_drm_hdmi_probe, - .remove = exynos_drm_hdmi_remove, - .driver = { - .name = "exynos-drm-hdmi", - .owner = THIS_MODULE, - }, -}; diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h deleted file mode 100644 index 724cab18197..00000000000 --- a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h +++ /dev/null @@ -1,67 +0,0 @@ -/* exynos_drm_hdmi.h - * - * Copyright (c) 2011 Samsung Electronics Co., Ltd. - * Authoer: Inki Dae <inki.dae@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#ifndef _EXYNOS_DRM_HDMI_H_ -#define _EXYNOS_DRM_HDMI_H_ - -#define MIXER_WIN_NR 3 -#define MIXER_DEFAULT_WIN 0 - -/* - * exynos hdmi common context structure. - * - * @drm_dev: pointer to drm_device. - * @ctx: pointer to the context of specific device driver. - * this context should be hdmi_context or mixer_context. - */ -struct exynos_drm_hdmi_context { - struct drm_device *drm_dev; - void *ctx; -}; - -struct exynos_hdmi_ops { - /* display */ - bool (*is_connected)(void *ctx); - struct edid *(*get_edid)(void *ctx, - struct drm_connector *connector); - int (*check_mode)(void *ctx, struct drm_display_mode *mode); - int (*power_on)(void *ctx, int mode); - - /* manager */ - void (*mode_set)(void *ctx, struct drm_display_mode *mode); - void (*get_max_resol)(void *ctx, unsigned int *width, - unsigned int *height); - void (*commit)(void *ctx); - void (*dpms)(void *ctx, int mode); -}; - -struct exynos_mixer_ops { - /* manager */ - int (*iommu_on)(void *ctx, bool enable); - int (*enable_vblank)(void *ctx, int pipe); - void (*disable_vblank)(void *ctx); - void (*wait_for_vblank)(void *ctx); - void (*dpms)(void *ctx, int mode); - - /* overlay */ - void (*win_mode_set)(void *ctx, struct exynos_drm_overlay *overlay); - void (*win_commit)(void *ctx, int zpos); - void (*win_disable)(void *ctx, int zpos); - - /* display */ - int (*check_mode)(void *ctx, struct drm_display_mode *mode); -}; - -void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx); -void exynos_mixer_drv_attach(struct exynos_drm_hdmi_context *ctx); -void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops); -void exynos_mixer_ops_register(struct exynos_mixer_ops *ops); -#endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_iommu.c b/drivers/gpu/drm/exynos/exynos_drm_iommu.c index fb8db037827..b32b291f88f 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_iommu.c +++ b/drivers/gpu/drm/exynos/exynos_drm_iommu.c @@ -36,12 +36,10 @@ int drm_create_iommu_mapping(struct drm_device *drm_dev) priv->da_start = EXYNOS_DEV_ADDR_START; if (!priv->da_space_size) priv->da_space_size = EXYNOS_DEV_ADDR_SIZE; - if (!priv->da_space_order) - priv->da_space_order = EXYNOS_DEV_ADDR_ORDER; mapping = arm_iommu_create_mapping(&platform_bus_type, priv->da_start, - priv->da_space_size, - priv->da_space_order); + priv->da_space_size); + if (IS_ERR(mapping)) return PTR_ERR(mapping); diff --git a/drivers/gpu/drm/exynos/exynos_drm_iommu.h b/drivers/gpu/drm/exynos/exynos_drm_iommu.h index 598e60f57d4..72376d41c51 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_iommu.h +++ b/drivers/gpu/drm/exynos/exynos_drm_iommu.h @@ -14,7 +14,6 @@ #define EXYNOS_DEV_ADDR_START 0x20000000 #define EXYNOS_DEV_ADDR_SIZE 0x40000000 -#define EXYNOS_DEV_ADDR_ORDER 0x0 #ifdef CONFIG_DRM_EXYNOS_IOMMU diff --git a/drivers/gpu/drm/exynos/exynos_drm_ipp.c b/drivers/gpu/drm/exynos/exynos_drm_ipp.c index 824e0705c8d..a1888e128f1 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_ipp.c +++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.c @@ -16,7 +16,6 @@ #include <linux/types.h> #include <linux/clk.h> #include <linux/pm_runtime.h> -#include <plat/map-base.h> #include <drm/drmP.h> #include <drm/exynos_drm.h> @@ -168,6 +167,13 @@ static int ipp_create_id(struct idr *id_idr, struct mutex *lock, void *obj, return 0; } +static void ipp_remove_id(struct idr *id_idr, struct mutex *lock, u32 id) +{ + mutex_lock(lock); + idr_remove(id_idr, id); + mutex_unlock(lock); +} + static void *ipp_find_obj(struct idr *id_idr, struct mutex *lock, u32 id) { void *obj; @@ -277,24 +283,22 @@ static struct exynos_drm_ippdrv *ipp_find_drv_by_handle(u32 prop_id) DRM_DEBUG_KMS("prop_id[%d]\n", prop_id); - if (list_empty(&exynos_drm_ippdrv_list)) { - DRM_DEBUG_KMS("ippdrv_list is empty.\n"); - return ERR_PTR(-ENODEV); - } - /* * This case is search ipp driver by prop_id handle. * sometimes, ipp subsystem find driver by prop_id. - * e.g PAUSE state, queue buf, command contro. + * e.g PAUSE state, queue buf, command control. */ list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) { DRM_DEBUG_KMS("count[%d]ippdrv[0x%x]\n", count++, (int)ippdrv); - if (!list_empty(&ippdrv->cmd_list)) { - list_for_each_entry(c_node, &ippdrv->cmd_list, list) - if (c_node->property.prop_id == prop_id) - return ippdrv; + mutex_lock(&ippdrv->cmd_lock); + list_for_each_entry(c_node, &ippdrv->cmd_list, list) { + if (c_node->property.prop_id == prop_id) { + mutex_unlock(&ippdrv->cmd_lock); + return ippdrv; + } } + mutex_unlock(&ippdrv->cmd_lock); } return ERR_PTR(-ENODEV); @@ -326,6 +330,7 @@ int exynos_drm_ipp_get_property(struct drm_device *drm_dev, void *data, if (!prop_list->ipp_id) { list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) count++; + /* * Supports ippdrv list count for user application. * First step user application getting ippdrv count. @@ -335,7 +340,7 @@ int exynos_drm_ipp_get_property(struct drm_device *drm_dev, void *data, } else { /* * Getting ippdrv capability by ipp_id. - * some deivce not supported wb, output interface. + * some device not supported wb, output interface. * so, user application detect correct ipp driver * using this ioctl. */ @@ -347,7 +352,7 @@ int exynos_drm_ipp_get_property(struct drm_device *drm_dev, void *data, return PTR_ERR(ippdrv); } - prop_list = ippdrv->prop_list; + *prop_list = ippdrv->prop_list; } return 0; @@ -387,9 +392,11 @@ static int ipp_find_and_set_property(struct drm_exynos_ipp_property *property) * when we find this command no using prop_id. * return property information set in this command node. */ + mutex_lock(&ippdrv->cmd_lock); list_for_each_entry(c_node, &ippdrv->cmd_list, list) { if ((c_node->property.prop_id == prop_id) && (c_node->state == IPP_STATE_STOP)) { + mutex_unlock(&ippdrv->cmd_lock); DRM_DEBUG_KMS("found cmd[%d]ippdrv[0x%x]\n", property->cmd, (int)ippdrv); @@ -397,6 +404,7 @@ static int ipp_find_and_set_property(struct drm_exynos_ipp_property *property) return 0; } } + mutex_unlock(&ippdrv->cmd_lock); DRM_ERROR("failed to search property.\n"); @@ -500,7 +508,7 @@ int exynos_drm_ipp_set_property(struct drm_device *drm_dev, void *data, c_node->start_work = ipp_create_cmd_work(); if (IS_ERR(c_node->start_work)) { DRM_ERROR("failed to create start work.\n"); - goto err_clear; + goto err_remove_id; } c_node->stop_work = ipp_create_cmd_work(); @@ -515,7 +523,7 @@ int exynos_drm_ipp_set_property(struct drm_device *drm_dev, void *data, goto err_free_stop; } - mutex_init(&c_node->cmd_lock); + mutex_init(&c_node->lock); mutex_init(&c_node->mem_lock); mutex_init(&c_node->event_lock); @@ -527,7 +535,9 @@ int exynos_drm_ipp_set_property(struct drm_device *drm_dev, void *data, INIT_LIST_HEAD(&c_node->event_list); list_splice_init(&priv->event_list, &c_node->event_list); + mutex_lock(&ippdrv->cmd_lock); list_add_tail(&c_node->list, &ippdrv->cmd_list); + mutex_unlock(&ippdrv->cmd_lock); /* make dedicated state without m2m */ if (!ipp_is_m2m_cmd(property->cmd)) @@ -539,18 +549,24 @@ err_free_stop: kfree(c_node->stop_work); err_free_start: kfree(c_node->start_work); +err_remove_id: + ipp_remove_id(&ctx->prop_idr, &ctx->prop_lock, property->prop_id); err_clear: kfree(c_node); return ret; } -static void ipp_clean_cmd_node(struct drm_exynos_ipp_cmd_node *c_node) +static void ipp_clean_cmd_node(struct ipp_context *ctx, + struct drm_exynos_ipp_cmd_node *c_node) { /* delete list */ list_del(&c_node->list); + ipp_remove_id(&ctx->prop_idr, &ctx->prop_lock, + c_node->property.prop_id); + /* destroy mutex */ - mutex_destroy(&c_node->cmd_lock); + mutex_destroy(&c_node->lock); mutex_destroy(&c_node->mem_lock); mutex_destroy(&c_node->event_lock); @@ -568,17 +584,10 @@ static int ipp_check_mem_list(struct drm_exynos_ipp_cmd_node *c_node) struct list_head *head; int ret, i, count[EXYNOS_DRM_OPS_MAX] = { 0, }; - mutex_lock(&c_node->mem_lock); - for_each_ipp_ops(i) { /* source/destination memory list */ head = &c_node->mem_list[i]; - if (list_empty(head)) { - DRM_DEBUG_KMS("%s memory empty.\n", i ? "dst" : "src"); - continue; - } - /* find memory node entry */ list_for_each_entry(m_node, head, list) { DRM_DEBUG_KMS("%s,count[%d]m_node[0x%x]\n", @@ -603,8 +612,6 @@ static int ipp_check_mem_list(struct drm_exynos_ipp_cmd_node *c_node) ret = max(count[EXYNOS_DRM_OPS_SRC], count[EXYNOS_DRM_OPS_DST]); - mutex_unlock(&c_node->mem_lock); - return ret; } @@ -647,16 +654,13 @@ static int ipp_set_mem_node(struct exynos_drm_ippdrv *ippdrv, return -EFAULT; } - mutex_lock(&c_node->mem_lock); - DRM_DEBUG_KMS("ops_id[%d]\n", m_node->ops_id); /* get operations callback */ ops = ippdrv->ops[m_node->ops_id]; if (!ops) { DRM_ERROR("not support ops.\n"); - ret = -EFAULT; - goto err_unlock; + return -EFAULT; } /* set address and enable irq */ @@ -665,12 +669,10 @@ static int ipp_set_mem_node(struct exynos_drm_ippdrv *ippdrv, m_node->buf_id, IPP_BUF_ENQUEUE); if (ret) { DRM_ERROR("failed to set addr.\n"); - goto err_unlock; + return ret; } } -err_unlock: - mutex_unlock(&c_node->mem_lock); return ret; } @@ -685,11 +687,9 @@ static struct drm_exynos_ipp_mem_node void *addr; int i; - mutex_lock(&c_node->mem_lock); - m_node = kzalloc(sizeof(*m_node), GFP_KERNEL); if (!m_node) - goto err_unlock; + return ERR_PTR(-ENOMEM); /* clear base address for error handling */ memset(&buf_info, 0x0, sizeof(buf_info)); @@ -723,15 +723,14 @@ static struct drm_exynos_ipp_mem_node m_node->filp = file; m_node->buf_info = buf_info; + mutex_lock(&c_node->mem_lock); list_add_tail(&m_node->list, &c_node->mem_list[qbuf->ops_id]); - mutex_unlock(&c_node->mem_lock); + return m_node; err_clear: kfree(m_node); -err_unlock: - mutex_unlock(&c_node->mem_lock); return ERR_PTR(-EFAULT); } @@ -748,13 +747,6 @@ static int ipp_put_mem_node(struct drm_device *drm_dev, return -EFAULT; } - if (list_empty(&m_node->list)) { - DRM_ERROR("empty memory node.\n"); - return -ENOMEM; - } - - mutex_lock(&c_node->mem_lock); - DRM_DEBUG_KMS("ops_id[%d]\n", m_node->ops_id); /* put gem buffer */ @@ -769,8 +761,6 @@ static int ipp_put_mem_node(struct drm_device *drm_dev, list_del(&m_node->list); kfree(m_node); - mutex_unlock(&c_node->mem_lock); - return 0; } @@ -806,7 +796,9 @@ static int ipp_get_event(struct drm_device *drm_dev, e->base.event = &e->event.base; e->base.file_priv = file; e->base.destroy = ipp_free_event; + mutex_lock(&c_node->event_lock); list_add_tail(&e->base.link, &c_node->event_list); + mutex_unlock(&c_node->event_lock); return 0; } @@ -817,16 +809,12 @@ static void ipp_put_event(struct drm_exynos_ipp_cmd_node *c_node, struct drm_exynos_ipp_send_event *e, *te; int count = 0; - if (list_empty(&c_node->event_list)) { - DRM_DEBUG_KMS("event_list is empty.\n"); - return; - } - + mutex_lock(&c_node->event_lock); list_for_each_entry_safe(e, te, &c_node->event_list, base.link) { DRM_DEBUG_KMS("count[%d]e[0x%x]\n", count++, (int)e); /* - * quf == NULL condition means all event deletion. + * qbuf == NULL condition means all event deletion. * stop operations want to delete all event list. * another case delete only same buf id. */ @@ -842,9 +830,13 @@ static void ipp_put_event(struct drm_exynos_ipp_cmd_node *c_node, /* delete list */ list_del(&e->base.link); kfree(e); - return; + goto out_unlock; } } + +out_unlock: + mutex_unlock(&c_node->event_lock); + return; } static void ipp_handle_cmd_work(struct device *dev, @@ -888,7 +880,9 @@ static int ipp_queue_buf_with_run(struct device *dev, return 0; } + mutex_lock(&c_node->mem_lock); if (!ipp_check_mem_list(c_node)) { + mutex_unlock(&c_node->mem_lock); DRM_DEBUG_KMS("empty memory.\n"); return 0; } @@ -905,10 +899,12 @@ static int ipp_queue_buf_with_run(struct device *dev, } else { ret = ipp_set_mem_node(ippdrv, c_node, m_node); if (ret) { + mutex_unlock(&c_node->mem_lock); DRM_ERROR("failed to set m node.\n"); return ret; } } + mutex_unlock(&c_node->mem_lock); return 0; } @@ -919,15 +915,15 @@ static void ipp_clean_queue_buf(struct drm_device *drm_dev, { struct drm_exynos_ipp_mem_node *m_node, *tm_node; - if (!list_empty(&c_node->mem_list[qbuf->ops_id])) { - /* delete list */ - list_for_each_entry_safe(m_node, tm_node, - &c_node->mem_list[qbuf->ops_id], list) { - if (m_node->buf_id == qbuf->buf_id && - m_node->ops_id == qbuf->ops_id) - ipp_put_mem_node(drm_dev, c_node, m_node); - } + /* delete list */ + mutex_lock(&c_node->mem_lock); + list_for_each_entry_safe(m_node, tm_node, + &c_node->mem_list[qbuf->ops_id], list) { + if (m_node->buf_id == qbuf->buf_id && + m_node->ops_id == qbuf->ops_id) + ipp_put_mem_node(drm_dev, c_node, m_node); } + mutex_unlock(&c_node->mem_lock); } int exynos_drm_ipp_queue_buf(struct drm_device *drm_dev, void *data, @@ -999,7 +995,7 @@ int exynos_drm_ipp_queue_buf(struct drm_device *drm_dev, void *data, } break; case IPP_BUF_DEQUEUE: - mutex_lock(&c_node->cmd_lock); + mutex_lock(&c_node->lock); /* put event for destination buffer */ if (qbuf->ops_id == EXYNOS_DRM_OPS_DST) @@ -1007,7 +1003,7 @@ int exynos_drm_ipp_queue_buf(struct drm_device *drm_dev, void *data, ipp_clean_queue_buf(drm_dev, c_node, qbuf); - mutex_unlock(&c_node->cmd_lock); + mutex_unlock(&c_node->lock); break; default: DRM_ERROR("invalid buffer control.\n"); @@ -1110,12 +1106,12 @@ int exynos_drm_ipp_cmd_ctrl(struct drm_device *drm_dev, void *data, case IPP_CTRL_PLAY: if (pm_runtime_suspended(ippdrv->dev)) pm_runtime_get_sync(ippdrv->dev); + c_node->state = IPP_STATE_START; cmd_work = c_node->start_work; cmd_work->ctrl = cmd_ctrl->ctrl; ipp_handle_cmd_work(dev, ippdrv, cmd_work, c_node); - c_node->state = IPP_STATE_START; break; case IPP_CTRL_STOP: cmd_work = c_node->stop_work; @@ -1130,10 +1126,12 @@ int exynos_drm_ipp_cmd_ctrl(struct drm_device *drm_dev, void *data, c_node->state = IPP_STATE_STOP; ippdrv->dedicated = false; - ipp_clean_cmd_node(c_node); + mutex_lock(&ippdrv->cmd_lock); + ipp_clean_cmd_node(ctx, c_node); if (list_empty(&ippdrv->cmd_list)) pm_runtime_put_sync(ippdrv->dev); + mutex_unlock(&ippdrv->cmd_lock); break; case IPP_CTRL_PAUSE: cmd_work = c_node->stop_work; @@ -1261,9 +1259,11 @@ static int ipp_start_property(struct exynos_drm_ippdrv *ippdrv, /* store command info in ippdrv */ ippdrv->c_node = c_node; + mutex_lock(&c_node->mem_lock); if (!ipp_check_mem_list(c_node)) { DRM_DEBUG_KMS("empty memory.\n"); - return -ENOMEM; + ret = -ENOMEM; + goto err_unlock; } /* set current property in ippdrv */ @@ -1271,7 +1271,7 @@ static int ipp_start_property(struct exynos_drm_ippdrv *ippdrv, if (ret) { DRM_ERROR("failed to set property.\n"); ippdrv->c_node = NULL; - return ret; + goto err_unlock; } /* check command */ @@ -1286,7 +1286,7 @@ static int ipp_start_property(struct exynos_drm_ippdrv *ippdrv, if (!m_node) { DRM_ERROR("failed to get node.\n"); ret = -EFAULT; - return ret; + goto err_unlock; } DRM_DEBUG_KMS("m_node[0x%x]\n", (int)m_node); @@ -1294,7 +1294,7 @@ static int ipp_start_property(struct exynos_drm_ippdrv *ippdrv, ret = ipp_set_mem_node(ippdrv, c_node, m_node); if (ret) { DRM_ERROR("failed to set m node.\n"); - return ret; + goto err_unlock; } } break; @@ -1306,7 +1306,7 @@ static int ipp_start_property(struct exynos_drm_ippdrv *ippdrv, ret = ipp_set_mem_node(ippdrv, c_node, m_node); if (ret) { DRM_ERROR("failed to set m node.\n"); - return ret; + goto err_unlock; } } break; @@ -1318,14 +1318,16 @@ static int ipp_start_property(struct exynos_drm_ippdrv *ippdrv, ret = ipp_set_mem_node(ippdrv, c_node, m_node); if (ret) { DRM_ERROR("failed to set m node.\n"); - return ret; + goto err_unlock; } } break; default: DRM_ERROR("invalid operations.\n"); - return -EINVAL; + ret = -EINVAL; + goto err_unlock; } + mutex_unlock(&c_node->mem_lock); DRM_DEBUG_KMS("cmd[%d]\n", property->cmd); @@ -1334,11 +1336,17 @@ static int ipp_start_property(struct exynos_drm_ippdrv *ippdrv, ret = ippdrv->start(ippdrv->dev, property->cmd); if (ret) { DRM_ERROR("failed to start ops.\n"); + ippdrv->c_node = NULL; return ret; } } return 0; + +err_unlock: + mutex_unlock(&c_node->mem_lock); + ippdrv->c_node = NULL; + return ret; } static int ipp_stop_property(struct drm_device *drm_dev, @@ -1355,6 +1363,8 @@ static int ipp_stop_property(struct drm_device *drm_dev, /* put event */ ipp_put_event(c_node, NULL); + mutex_lock(&c_node->mem_lock); + /* check command */ switch (property->cmd) { case IPP_CMD_M2M: @@ -1362,11 +1372,6 @@ static int ipp_stop_property(struct drm_device *drm_dev, /* source/destination memory list */ head = &c_node->mem_list[i]; - if (list_empty(head)) { - DRM_DEBUG_KMS("mem_list is empty.\n"); - break; - } - list_for_each_entry_safe(m_node, tm_node, head, list) { ret = ipp_put_mem_node(drm_dev, c_node, @@ -1382,11 +1387,6 @@ static int ipp_stop_property(struct drm_device *drm_dev, /* destination memory list */ head = &c_node->mem_list[EXYNOS_DRM_OPS_DST]; - if (list_empty(head)) { - DRM_DEBUG_KMS("mem_list is empty.\n"); - break; - } - list_for_each_entry_safe(m_node, tm_node, head, list) { ret = ipp_put_mem_node(drm_dev, c_node, m_node); if (ret) { @@ -1399,11 +1399,6 @@ static int ipp_stop_property(struct drm_device *drm_dev, /* source memory list */ head = &c_node->mem_list[EXYNOS_DRM_OPS_SRC]; - if (list_empty(head)) { - DRM_DEBUG_KMS("mem_list is empty.\n"); - break; - } - list_for_each_entry_safe(m_node, tm_node, head, list) { ret = ipp_put_mem_node(drm_dev, c_node, m_node); if (ret) { @@ -1419,6 +1414,8 @@ static int ipp_stop_property(struct drm_device *drm_dev, } err_clear: + mutex_unlock(&c_node->mem_lock); + /* stop operations */ if (ippdrv->stop) ippdrv->stop(ippdrv->dev, property->cmd); @@ -1447,7 +1444,7 @@ void ipp_sched_cmd(struct work_struct *work) return; } - mutex_lock(&c_node->cmd_lock); + mutex_lock(&c_node->lock); property = &c_node->property; @@ -1495,7 +1492,7 @@ void ipp_sched_cmd(struct work_struct *work) DRM_DEBUG_KMS("ctrl[%d] done.\n", cmd_work->ctrl); err_unlock: - mutex_unlock(&c_node->cmd_lock); + mutex_unlock(&c_node->lock); } static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, @@ -1525,14 +1522,18 @@ static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, return -EINVAL; } + mutex_lock(&c_node->event_lock); if (list_empty(&c_node->event_list)) { DRM_DEBUG_KMS("event list is empty.\n"); - return 0; + ret = 0; + goto err_event_unlock; } + mutex_lock(&c_node->mem_lock); if (!ipp_check_mem_list(c_node)) { DRM_DEBUG_KMS("empty memory.\n"); - return 0; + ret = 0; + goto err_mem_unlock; } /* check command */ @@ -1546,7 +1547,8 @@ static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, struct drm_exynos_ipp_mem_node, list); if (!m_node) { DRM_ERROR("empty memory node.\n"); - return -ENOMEM; + ret = -ENOMEM; + goto err_mem_unlock; } tbuf_id[i] = m_node->buf_id; @@ -1568,7 +1570,8 @@ static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, m_node = ipp_find_mem_node(c_node, &qbuf); if (!m_node) { DRM_ERROR("empty memory node.\n"); - return -ENOMEM; + ret = -ENOMEM; + goto err_mem_unlock; } tbuf_id[EXYNOS_DRM_OPS_DST] = m_node->buf_id; @@ -1585,7 +1588,8 @@ static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, struct drm_exynos_ipp_mem_node, list); if (!m_node) { DRM_ERROR("empty memory node.\n"); - return -ENOMEM; + ret = -ENOMEM; + goto err_mem_unlock; } tbuf_id[EXYNOS_DRM_OPS_SRC] = m_node->buf_id; @@ -1596,8 +1600,10 @@ static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, break; default: DRM_ERROR("invalid operations.\n"); - return -EINVAL; + ret = -EINVAL; + goto err_mem_unlock; } + mutex_unlock(&c_node->mem_lock); if (tbuf_id[EXYNOS_DRM_OPS_DST] != buf_id[EXYNOS_DRM_OPS_DST]) DRM_ERROR("failed to match buf_id[%d %d]prop_id[%d]\n", @@ -1612,11 +1618,6 @@ static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, e = list_first_entry(&c_node->event_list, struct drm_exynos_ipp_send_event, base.link); - if (!e) { - DRM_ERROR("empty event.\n"); - return -EINVAL; - } - do_gettimeofday(&now); DRM_DEBUG_KMS("tv_sec[%ld]tv_usec[%ld]\n", now.tv_sec, now.tv_usec); e->event.tv_sec = now.tv_sec; @@ -1631,11 +1632,18 @@ static int ipp_send_event(struct exynos_drm_ippdrv *ippdrv, list_move_tail(&e->base.link, &e->base.file_priv->event_list); wake_up_interruptible(&e->base.file_priv->event_wait); spin_unlock_irqrestore(&drm_dev->event_lock, flags); + mutex_unlock(&c_node->event_lock); DRM_DEBUG_KMS("done cmd[%d]prop_id[%d]buf_id[%d]\n", property->cmd, property->prop_id, tbuf_id[EXYNOS_DRM_OPS_DST]); return 0; + +err_mem_unlock: + mutex_unlock(&c_node->mem_lock); +err_event_unlock: + mutex_unlock(&c_node->event_lock); + return ret; } void ipp_sched_event(struct work_struct *work) @@ -1677,8 +1685,6 @@ void ipp_sched_event(struct work_struct *work) goto err_completion; } - mutex_lock(&c_node->event_lock); - ret = ipp_send_event(ippdrv, c_node, event_work->buf_id); if (ret) { DRM_ERROR("failed to send event.\n"); @@ -1688,8 +1694,6 @@ void ipp_sched_event(struct work_struct *work) err_completion: if (ipp_is_m2m_cmd(c_node->property.cmd)) complete(&c_node->start_complete); - - mutex_unlock(&c_node->event_lock); } static int ipp_subdrv_probe(struct drm_device *drm_dev, struct device *dev) @@ -1700,23 +1704,21 @@ static int ipp_subdrv_probe(struct drm_device *drm_dev, struct device *dev) /* get ipp driver entry */ list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) { + u32 ipp_id; + ippdrv->drm_dev = drm_dev; ret = ipp_create_id(&ctx->ipp_idr, &ctx->ipp_lock, ippdrv, - &ippdrv->ipp_id); - if (ret) { + &ipp_id); + if (ret || ipp_id == 0) { DRM_ERROR("failed to create id.\n"); - goto err_idr; + goto err; } DRM_DEBUG_KMS("count[%d]ippdrv[0x%x]ipp_id[%d]\n", - count++, (int)ippdrv, ippdrv->ipp_id); + count++, (int)ippdrv, ipp_id); - if (ippdrv->ipp_id == 0) { - DRM_ERROR("failed to get ipp_id[%d]\n", - ippdrv->ipp_id); - goto err_idr; - } + ippdrv->prop_list.ipp_id = ipp_id; /* store parent device for node */ ippdrv->parent_dev = dev; @@ -1725,39 +1727,46 @@ static int ipp_subdrv_probe(struct drm_device *drm_dev, struct device *dev) ippdrv->event_workq = ctx->event_workq; ippdrv->sched_event = ipp_sched_event; INIT_LIST_HEAD(&ippdrv->cmd_list); + mutex_init(&ippdrv->cmd_lock); if (is_drm_iommu_supported(drm_dev)) { ret = drm_iommu_attach_device(drm_dev, ippdrv->dev); if (ret) { DRM_ERROR("failed to activate iommu\n"); - goto err_iommu; + goto err; } } } return 0; -err_iommu: +err: /* get ipp driver entry */ - list_for_each_entry_reverse(ippdrv, &exynos_drm_ippdrv_list, drv_list) + list_for_each_entry_continue_reverse(ippdrv, &exynos_drm_ippdrv_list, + drv_list) { if (is_drm_iommu_supported(drm_dev)) drm_iommu_detach_device(drm_dev, ippdrv->dev); -err_idr: - idr_destroy(&ctx->ipp_idr); - idr_destroy(&ctx->prop_idr); + ipp_remove_id(&ctx->ipp_idr, &ctx->ipp_lock, + ippdrv->prop_list.ipp_id); + } + return ret; } static void ipp_subdrv_remove(struct drm_device *drm_dev, struct device *dev) { struct exynos_drm_ippdrv *ippdrv; + struct ipp_context *ctx = get_ipp_context(dev); /* get ipp driver entry */ list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) { if (is_drm_iommu_supported(drm_dev)) drm_iommu_detach_device(drm_dev, ippdrv->dev); + ipp_remove_id(&ctx->ipp_idr, &ctx->ipp_lock, + ippdrv->prop_list.ipp_id); + ippdrv->drm_dev = NULL; exynos_drm_ippdrv_unregister(ippdrv); } @@ -1788,20 +1797,14 @@ static void ipp_subdrv_close(struct drm_device *drm_dev, struct device *dev, struct drm_exynos_file_private *file_priv = file->driver_priv; struct exynos_drm_ipp_private *priv = file_priv->ipp_priv; struct exynos_drm_ippdrv *ippdrv = NULL; + struct ipp_context *ctx = get_ipp_context(dev); struct drm_exynos_ipp_cmd_node *c_node, *tc_node; int count = 0; DRM_DEBUG_KMS("for priv[0x%x]\n", (int)priv); - if (list_empty(&exynos_drm_ippdrv_list)) { - DRM_DEBUG_KMS("ippdrv_list is empty.\n"); - goto err_clear; - } - list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) { - if (list_empty(&ippdrv->cmd_list)) - continue; - + mutex_lock(&ippdrv->cmd_lock); list_for_each_entry_safe(c_node, tc_node, &ippdrv->cmd_list, list) { DRM_DEBUG_KMS("count[%d]ippdrv[0x%x]\n", @@ -1821,14 +1824,14 @@ static void ipp_subdrv_close(struct drm_device *drm_dev, struct device *dev, } ippdrv->dedicated = false; - ipp_clean_cmd_node(c_node); + ipp_clean_cmd_node(ctx, c_node); if (list_empty(&ippdrv->cmd_list)) pm_runtime_put_sync(ippdrv->dev); } } + mutex_unlock(&ippdrv->cmd_lock); } -err_clear: kfree(priv); return; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_ipp.h b/drivers/gpu/drm/exynos/exynos_drm_ipp.h index 4cadbea7dbd..7aaeaae757c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_ipp.h +++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.h @@ -48,11 +48,11 @@ struct drm_exynos_ipp_cmd_work { /* * A structure of command node. * - * @priv: IPP private infomation. + * @priv: IPP private information. * @list: list head to command queue information. * @event_list: list head of event. * @mem_list: list head to source,destination memory queue information. - * @cmd_lock: lock for synchronization of access to ioctl. + * @lock: lock for synchronization of access to ioctl. * @mem_lock: lock for synchronization of access to memory nodes. * @event_lock: lock for synchronization of access to scheduled event. * @start_complete: completion of start of command. @@ -68,7 +68,7 @@ struct drm_exynos_ipp_cmd_node { struct list_head list; struct list_head event_list; struct list_head mem_list[EXYNOS_DRM_OPS_MAX]; - struct mutex cmd_lock; + struct mutex lock; struct mutex mem_lock; struct mutex event_lock; struct completion start_complete; @@ -83,7 +83,7 @@ struct drm_exynos_ipp_cmd_node { /* * A structure of buffer information. * - * @gem_objs: Y, Cb, Cr each gem object. + * @handles: Y, Cb, Cr each gem object handle. * @base: Y, Cb, Cr each planar address. */ struct drm_exynos_ipp_buf_info { @@ -92,7 +92,7 @@ struct drm_exynos_ipp_buf_info { }; /* - * A structure of wb setting infomation. + * A structure of wb setting information. * * @enable: enable flag for wb. * @refresh: HZ of the refresh rate. @@ -142,12 +142,12 @@ struct exynos_drm_ipp_ops { * @parent_dev: parent device information. * @dev: platform device. * @drm_dev: drm device. - * @ipp_id: id of ipp driver. * @dedicated: dedicated ipp device. * @ops: source, destination operations. * @event_workq: event work queue. * @c_node: current command information. * @cmd_list: list head for command information. + * @cmd_lock: lock for synchronization of access to cmd_list. * @prop_list: property informations of current ipp driver. * @check_property: check property about format, size, buffer. * @reset: reset ipp block. @@ -160,13 +160,13 @@ struct exynos_drm_ippdrv { struct device *parent_dev; struct device *dev; struct drm_device *drm_dev; - u32 ipp_id; bool dedicated; struct exynos_drm_ipp_ops *ops[EXYNOS_DRM_OPS_MAX]; struct workqueue_struct *event_workq; struct drm_exynos_ipp_cmd_node *c_node; struct list_head cmd_list; - struct drm_exynos_ipp_prop_list *prop_list; + struct mutex cmd_lock; + struct drm_exynos_ipp_prop_list prop_list; int (*check_property)(struct device *dev, struct drm_exynos_ipp_property *property); diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.c b/drivers/gpu/drm/exynos/exynos_drm_plane.c index fcb0652e77d..8371cbd7631 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_plane.c +++ b/drivers/gpu/drm/exynos/exynos_drm_plane.c @@ -13,7 +13,7 @@ #include <drm/exynos_drm.h> #include "exynos_drm_drv.h" -#include "exynos_drm_encoder.h" +#include "exynos_drm_crtc.h" #include "exynos_drm_fb.h" #include "exynos_drm_gem.h" #include "exynos_drm_plane.h" @@ -87,7 +87,7 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc, struct exynos_drm_gem_buf *buffer = exynos_drm_fb_buffer(fb, i); if (!buffer) { - DRM_LOG_KMS("buffer is null\n"); + DRM_DEBUG_KMS("buffer is null\n"); return -EFAULT; } @@ -139,7 +139,7 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc, overlay->crtc_x, overlay->crtc_y, overlay->crtc_width, overlay->crtc_height); - exynos_drm_fn_encoder(crtc, overlay, exynos_drm_encoder_plane_mode_set); + exynos_drm_crtc_plane_mode_set(crtc, overlay); return 0; } @@ -149,8 +149,7 @@ void exynos_plane_commit(struct drm_plane *plane) struct exynos_plane *exynos_plane = to_exynos_plane(plane); struct exynos_drm_overlay *overlay = &exynos_plane->overlay; - exynos_drm_fn_encoder(plane->crtc, &overlay->zpos, - exynos_drm_encoder_plane_commit); + exynos_drm_crtc_plane_commit(plane->crtc, overlay->zpos); } void exynos_plane_dpms(struct drm_plane *plane, int mode) @@ -162,17 +161,13 @@ void exynos_plane_dpms(struct drm_plane *plane, int mode) if (exynos_plane->enabled) return; - exynos_drm_fn_encoder(plane->crtc, &overlay->zpos, - exynos_drm_encoder_plane_enable); - + exynos_drm_crtc_plane_enable(plane->crtc, overlay->zpos); exynos_plane->enabled = true; } else { if (!exynos_plane->enabled) return; - exynos_drm_fn_encoder(plane->crtc, &overlay->zpos, - exynos_drm_encoder_plane_disable); - + exynos_drm_crtc_plane_disable(plane->crtc, overlay->zpos); exynos_plane->enabled = false; } } @@ -259,7 +254,7 @@ static void exynos_plane_attach_zpos_property(struct drm_plane *plane) } struct drm_plane *exynos_plane_init(struct drm_device *dev, - unsigned int possible_crtcs, bool priv) + unsigned long possible_crtcs, bool priv) { struct exynos_plane *exynos_plane; int err; diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.h b/drivers/gpu/drm/exynos/exynos_drm_plane.h index 88312458580..84d464c90d3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_plane.h +++ b/drivers/gpu/drm/exynos/exynos_drm_plane.h @@ -17,4 +17,4 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc, void exynos_plane_commit(struct drm_plane *plane); void exynos_plane_dpms(struct drm_plane *plane, int mode); struct drm_plane *exynos_plane_init(struct drm_device *dev, - unsigned int possible_crtcs, bool priv); + unsigned long possible_crtcs, bool priv); diff --git a/drivers/gpu/drm/exynos/exynos_drm_rotator.c b/drivers/gpu/drm/exynos/exynos_drm_rotator.c index 7b901688def..f01fbb6dc1f 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_rotator.c +++ b/drivers/gpu/drm/exynos/exynos_drm_rotator.c @@ -158,8 +158,9 @@ static irqreturn_t rotator_irq_handler(int irq, void *arg) rot->cur_buf_id[EXYNOS_DRM_OPS_DST]; queue_work(ippdrv->event_workq, (struct work_struct *)event_work); - } else + } else { DRM_ERROR("the SFR is set illegally\n"); + } return IRQ_HANDLED; } @@ -469,11 +470,7 @@ static struct exynos_drm_ipp_ops rot_dst_ops = { static int rotator_init_prop_list(struct exynos_drm_ippdrv *ippdrv) { - struct drm_exynos_ipp_prop_list *prop_list; - - prop_list = devm_kzalloc(ippdrv->dev, sizeof(*prop_list), GFP_KERNEL); - if (!prop_list) - return -ENOMEM; + struct drm_exynos_ipp_prop_list *prop_list = &ippdrv->prop_list; prop_list->version = 1; prop_list->flip = (1 << EXYNOS_DRM_FLIP_VERTICAL) | @@ -486,8 +483,6 @@ static int rotator_init_prop_list(struct exynos_drm_ippdrv *ippdrv) prop_list->crop = 0; prop_list->scale = 0; - ippdrv->prop_list = prop_list; - return 0; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.c b/drivers/gpu/drm/exynos/exynos_drm_vidi.c index 4400330e444..2fb8705d646 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_vidi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.c @@ -28,7 +28,9 @@ /* vidi has totally three virtual windows. */ #define WINDOWS_NR 3 -#define get_vidi_context(dev) platform_get_drvdata(to_platform_device(dev)) +#define get_vidi_mgr(dev) platform_get_drvdata(to_platform_device(dev)) +#define ctx_from_connector(c) container_of(c, struct vidi_context, \ + connector) struct vidi_win_data { unsigned int offset_x; @@ -45,8 +47,11 @@ struct vidi_win_data { }; struct vidi_context { - struct exynos_drm_subdrv subdrv; + struct drm_device *drm_dev; struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector connector; + struct exynos_drm_subdrv subdrv; struct vidi_win_data win_data[WINDOWS_NR]; struct edid *raw_edid; unsigned int clkdiv; @@ -58,6 +63,7 @@ struct vidi_context { bool direct_vblank; struct work_struct work; struct mutex lock; + int pipe; }; static const char fake_edid_info[] = { @@ -85,128 +91,34 @@ static const char fake_edid_info[] = { 0x00, 0x00, 0x00, 0x06 }; -static bool vidi_display_is_connected(struct device *dev) -{ - struct vidi_context *ctx = get_vidi_context(dev); - - /* - * connection request would come from user side - * to do hotplug through specific ioctl. - */ - return ctx->connected ? true : false; -} - -static struct edid *vidi_get_edid(struct device *dev, - struct drm_connector *connector) -{ - struct vidi_context *ctx = get_vidi_context(dev); - struct edid *edid; - int edid_len; - - /* - * the edid data comes from user side and it would be set - * to ctx->raw_edid through specific ioctl. - */ - if (!ctx->raw_edid) { - DRM_DEBUG_KMS("raw_edid is null.\n"); - return ERR_PTR(-EFAULT); - } - - edid_len = (1 + ctx->raw_edid->extensions) * EDID_LENGTH; - edid = kmemdup(ctx->raw_edid, edid_len, GFP_KERNEL); - if (!edid) { - DRM_DEBUG_KMS("failed to allocate edid\n"); - return ERR_PTR(-ENOMEM); - } - - return edid; -} - -static void *vidi_get_panel(struct device *dev) -{ - /* TODO. */ - - return NULL; -} - -static int vidi_check_mode(struct device *dev, struct drm_display_mode *mode) +static void vidi_apply(struct exynos_drm_manager *mgr) { - /* TODO. */ - - return 0; -} - -static int vidi_display_power_on(struct device *dev, int mode) -{ - /* TODO */ - - return 0; -} - -static struct exynos_drm_display_ops vidi_display_ops = { - .type = EXYNOS_DISPLAY_TYPE_VIDI, - .is_connected = vidi_display_is_connected, - .get_edid = vidi_get_edid, - .get_panel = vidi_get_panel, - .check_mode = vidi_check_mode, - .power_on = vidi_display_power_on, -}; - -static void vidi_dpms(struct device *subdrv_dev, int mode) -{ - struct vidi_context *ctx = get_vidi_context(subdrv_dev); - - DRM_DEBUG_KMS("%d\n", mode); - - mutex_lock(&ctx->lock); - - switch (mode) { - case DRM_MODE_DPMS_ON: - /* TODO. */ - break; - case DRM_MODE_DPMS_STANDBY: - case DRM_MODE_DPMS_SUSPEND: - case DRM_MODE_DPMS_OFF: - /* TODO. */ - break; - default: - DRM_DEBUG_KMS("unspecified mode %d\n", mode); - break; - } - - mutex_unlock(&ctx->lock); -} - -static void vidi_apply(struct device *subdrv_dev) -{ - struct vidi_context *ctx = get_vidi_context(subdrv_dev); - struct exynos_drm_manager *mgr = ctx->subdrv.manager; + struct vidi_context *ctx = mgr->ctx; struct exynos_drm_manager_ops *mgr_ops = mgr->ops; - struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops; struct vidi_win_data *win_data; int i; for (i = 0; i < WINDOWS_NR; i++) { win_data = &ctx->win_data[i]; - if (win_data->enabled && (ovl_ops && ovl_ops->commit)) - ovl_ops->commit(subdrv_dev, i); + if (win_data->enabled && (mgr_ops && mgr_ops->win_commit)) + mgr_ops->win_commit(mgr, i); } if (mgr_ops && mgr_ops->commit) - mgr_ops->commit(subdrv_dev); + mgr_ops->commit(mgr); } -static void vidi_commit(struct device *dev) +static void vidi_commit(struct exynos_drm_manager *mgr) { - struct vidi_context *ctx = get_vidi_context(dev); + struct vidi_context *ctx = mgr->ctx; if (ctx->suspended) return; } -static int vidi_enable_vblank(struct device *dev) +static int vidi_enable_vblank(struct exynos_drm_manager *mgr) { - struct vidi_context *ctx = get_vidi_context(dev); + struct vidi_context *ctx = mgr->ctx; if (ctx->suspended) return -EPERM; @@ -219,16 +131,16 @@ static int vidi_enable_vblank(struct device *dev) /* * in case of page flip request, vidi_finish_pageflip function * will not be called because direct_vblank is true and then - * that function will be called by overlay_ops->commit callback + * that function will be called by manager_ops->win_commit callback */ schedule_work(&ctx->work); return 0; } -static void vidi_disable_vblank(struct device *dev) +static void vidi_disable_vblank(struct exynos_drm_manager *mgr) { - struct vidi_context *ctx = get_vidi_context(dev); + struct vidi_context *ctx = mgr->ctx; if (ctx->suspended) return; @@ -237,24 +149,16 @@ static void vidi_disable_vblank(struct device *dev) ctx->vblank_on = false; } -static struct exynos_drm_manager_ops vidi_manager_ops = { - .dpms = vidi_dpms, - .apply = vidi_apply, - .commit = vidi_commit, - .enable_vblank = vidi_enable_vblank, - .disable_vblank = vidi_disable_vblank, -}; - -static void vidi_win_mode_set(struct device *dev, - struct exynos_drm_overlay *overlay) +static void vidi_win_mode_set(struct exynos_drm_manager *mgr, + struct exynos_drm_overlay *overlay) { - struct vidi_context *ctx = get_vidi_context(dev); + struct vidi_context *ctx = mgr->ctx; struct vidi_win_data *win_data; int win; unsigned long offset; if (!overlay) { - dev_err(dev, "overlay is NULL\n"); + DRM_ERROR("overlay is NULL\n"); return; } @@ -298,9 +202,9 @@ static void vidi_win_mode_set(struct device *dev, overlay->fb_width, overlay->crtc_width); } -static void vidi_win_commit(struct device *dev, int zpos) +static void vidi_win_commit(struct exynos_drm_manager *mgr, int zpos) { - struct vidi_context *ctx = get_vidi_context(dev); + struct vidi_context *ctx = mgr->ctx; struct vidi_win_data *win_data; int win = zpos; @@ -317,15 +221,15 @@ static void vidi_win_commit(struct device *dev, int zpos) win_data->enabled = true; - DRM_DEBUG_KMS("dma_addr = 0x%x\n", win_data->dma_addr); + DRM_DEBUG_KMS("dma_addr = %pad\n", &win_data->dma_addr); if (ctx->vblank_on) schedule_work(&ctx->work); } -static void vidi_win_disable(struct device *dev, int zpos) +static void vidi_win_disable(struct exynos_drm_manager *mgr, int zpos) { - struct vidi_context *ctx = get_vidi_context(dev); + struct vidi_context *ctx = mgr->ctx; struct vidi_win_data *win_data; int win = zpos; @@ -341,48 +245,64 @@ static void vidi_win_disable(struct device *dev, int zpos) /* TODO. */ } -static struct exynos_drm_overlay_ops vidi_overlay_ops = { - .mode_set = vidi_win_mode_set, - .commit = vidi_win_commit, - .disable = vidi_win_disable, -}; +static int vidi_power_on(struct exynos_drm_manager *mgr, bool enable) +{ + struct vidi_context *ctx = mgr->ctx; -static struct exynos_drm_manager vidi_manager = { - .pipe = -1, - .ops = &vidi_manager_ops, - .overlay_ops = &vidi_overlay_ops, - .display_ops = &vidi_display_ops, -}; + DRM_DEBUG_KMS("%s\n", __FILE__); -static void vidi_fake_vblank_handler(struct work_struct *work) -{ - struct vidi_context *ctx = container_of(work, struct vidi_context, - work); - struct exynos_drm_subdrv *subdrv = &ctx->subdrv; - struct exynos_drm_manager *manager = subdrv->manager; + if (enable != false && enable != true) + return -EINVAL; - if (manager->pipe < 0) - return; + if (enable) { + ctx->suspended = false; - /* refresh rate is about 50Hz. */ - usleep_range(16000, 20000); + /* if vblank was enabled status, enable it again. */ + if (test_and_clear_bit(0, &ctx->irq_flags)) + vidi_enable_vblank(mgr); + + vidi_apply(mgr); + } else { + ctx->suspended = true; + } + + return 0; +} + +static void vidi_dpms(struct exynos_drm_manager *mgr, int mode) +{ + struct vidi_context *ctx = mgr->ctx; + + DRM_DEBUG_KMS("%d\n", mode); mutex_lock(&ctx->lock); - if (ctx->direct_vblank) { - drm_handle_vblank(subdrv->drm_dev, manager->pipe); - ctx->direct_vblank = false; - mutex_unlock(&ctx->lock); - return; + switch (mode) { + case DRM_MODE_DPMS_ON: + vidi_power_on(mgr, true); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + vidi_power_on(mgr, false); + break; + default: + DRM_DEBUG_KMS("unspecified mode %d\n", mode); + break; } mutex_unlock(&ctx->lock); - - exynos_drm_crtc_finish_pageflip(subdrv->drm_dev, manager->pipe); } -static int vidi_subdrv_probe(struct drm_device *drm_dev, struct device *dev) +static int vidi_mgr_initialize(struct exynos_drm_manager *mgr, + struct drm_device *drm_dev) { + struct vidi_context *ctx = mgr->ctx; + struct exynos_drm_private *priv = drm_dev->dev_private; + + mgr->drm_dev = ctx->drm_dev = drm_dev; + mgr->pipe = ctx->pipe = priv->pipe++; + /* * enable drm irq mode. * - with irq_enabled = 1, we can use the vblank feature. @@ -403,36 +323,52 @@ static int vidi_subdrv_probe(struct drm_device *drm_dev, struct device *dev) return 0; } -static void vidi_subdrv_remove(struct drm_device *drm_dev, struct device *dev) -{ - /* TODO. */ -} +static struct exynos_drm_manager_ops vidi_manager_ops = { + .dpms = vidi_dpms, + .commit = vidi_commit, + .enable_vblank = vidi_enable_vblank, + .disable_vblank = vidi_disable_vblank, + .win_mode_set = vidi_win_mode_set, + .win_commit = vidi_win_commit, + .win_disable = vidi_win_disable, +}; + +static struct exynos_drm_manager vidi_manager = { + .type = EXYNOS_DISPLAY_TYPE_VIDI, + .ops = &vidi_manager_ops, +}; -static int vidi_power_on(struct vidi_context *ctx, bool enable) +static void vidi_fake_vblank_handler(struct work_struct *work) { - struct exynos_drm_subdrv *subdrv = &ctx->subdrv; - struct device *dev = subdrv->dev; + struct vidi_context *ctx = container_of(work, struct vidi_context, + work); - if (enable) { - ctx->suspended = false; + if (ctx->pipe < 0) + return; - /* if vblank was enabled status, enable it again. */ - if (test_and_clear_bit(0, &ctx->irq_flags)) - vidi_enable_vblank(dev); + /* refresh rate is about 50Hz. */ + usleep_range(16000, 20000); - vidi_apply(dev); - } else { - ctx->suspended = true; + mutex_lock(&ctx->lock); + + if (ctx->direct_vblank) { + drm_handle_vblank(ctx->drm_dev, ctx->pipe); + ctx->direct_vblank = false; + mutex_unlock(&ctx->lock); + return; } - return 0; + mutex_unlock(&ctx->lock); + + exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); } static int vidi_show_connection(struct device *dev, struct device_attribute *attr, char *buf) { int rc; - struct vidi_context *ctx = get_vidi_context(dev); + struct exynos_drm_manager *mgr = get_vidi_mgr(dev); + struct vidi_context *ctx = mgr->ctx; mutex_lock(&ctx->lock); @@ -447,7 +383,8 @@ static int vidi_store_connection(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { - struct vidi_context *ctx = get_vidi_context(dev); + struct exynos_drm_manager *mgr = get_vidi_mgr(dev); + struct vidi_context *ctx = mgr->ctx; int ret; ret = kstrtoint(buf, 0, &ctx->connected); @@ -469,7 +406,7 @@ static int vidi_store_connection(struct device *dev, DRM_DEBUG_KMS("requested connection.\n"); - drm_helper_hpd_irq_event(ctx->subdrv.drm_dev); + drm_helper_hpd_irq_event(ctx->drm_dev); return len; } @@ -482,10 +419,8 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, { struct vidi_context *ctx = NULL; struct drm_encoder *encoder; - struct exynos_drm_manager *manager; - struct exynos_drm_display_ops *display_ops; + struct exynos_drm_display *display; struct drm_exynos_vidi_connection *vidi = data; - int edid_len; if (!vidi) { DRM_DEBUG_KMS("user data for vidi is null.\n"); @@ -499,11 +434,10 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, list_for_each_entry(encoder, &drm_dev->mode_config.encoder_list, head) { - manager = exynos_drm_get_manager(encoder); - display_ops = manager->display_ops; + display = exynos_drm_get_display(encoder); - if (display_ops->type == EXYNOS_DISPLAY_TYPE_VIDI) { - ctx = get_vidi_context(manager->dev); + if (display->type == EXYNOS_DISPLAY_TYPE_VIDI) { + ctx = display->ctx; break; } } @@ -524,8 +458,7 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, DRM_DEBUG_KMS("edid data is invalid.\n"); return -EINVAL; } - edid_len = (1 + raw_edid->extensions) * EDID_LENGTH; - ctx->raw_edid = kmemdup(raw_edid, edid_len, GFP_KERNEL); + ctx->raw_edid = drm_edid_duplicate(raw_edid); if (!ctx->raw_edid) { DRM_DEBUG_KMS("failed to allocate raw_edid.\n"); return -ENOMEM; @@ -543,19 +476,140 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data, } ctx->connected = vidi->connection; - drm_helper_hpd_irq_event(ctx->subdrv.drm_dev); + drm_helper_hpd_irq_event(ctx->drm_dev); + + return 0; +} + +static enum drm_connector_status vidi_detect(struct drm_connector *connector, + bool force) +{ + struct vidi_context *ctx = ctx_from_connector(connector); + + /* + * connection request would come from user side + * to do hotplug through specific ioctl. + */ + return ctx->connected ? connector_status_connected : + connector_status_disconnected; +} + +static void vidi_connector_destroy(struct drm_connector *connector) +{ +} + +static struct drm_connector_funcs vidi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = vidi_detect, + .destroy = vidi_connector_destroy, +}; + +static int vidi_get_modes(struct drm_connector *connector) +{ + struct vidi_context *ctx = ctx_from_connector(connector); + struct edid *edid; + int edid_len; + + /* + * the edid data comes from user side and it would be set + * to ctx->raw_edid through specific ioctl. + */ + if (!ctx->raw_edid) { + DRM_DEBUG_KMS("raw_edid is null.\n"); + return -EFAULT; + } + + edid_len = (1 + ctx->raw_edid->extensions) * EDID_LENGTH; + edid = kmemdup(ctx->raw_edid, edid_len, GFP_KERNEL); + if (!edid) { + DRM_DEBUG_KMS("failed to allocate edid\n"); + return -ENOMEM; + } + + drm_mode_connector_update_edid_property(connector, edid); + + return drm_add_edid_modes(connector, edid); +} + +static struct drm_encoder *vidi_best_encoder(struct drm_connector *connector) +{ + struct vidi_context *ctx = ctx_from_connector(connector); + + return ctx->encoder; +} + +static struct drm_connector_helper_funcs vidi_connector_helper_funcs = { + .get_modes = vidi_get_modes, + .best_encoder = vidi_best_encoder, +}; + +static int vidi_create_connector(struct exynos_drm_display *display, + struct drm_encoder *encoder) +{ + struct vidi_context *ctx = display->ctx; + struct drm_connector *connector = &ctx->connector; + int ret; + + ctx->encoder = encoder; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(ctx->drm_dev, connector, + &vidi_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(connector, &vidi_connector_helper_funcs); + drm_sysfs_connector_add(connector); + drm_mode_connector_attach_encoder(connector, encoder); + + return 0; +} + + +static struct exynos_drm_display_ops vidi_display_ops = { + .create_connector = vidi_create_connector, +}; + +static struct exynos_drm_display vidi_display = { + .type = EXYNOS_DISPLAY_TYPE_VIDI, + .ops = &vidi_display_ops, +}; + +static int vidi_subdrv_probe(struct drm_device *drm_dev, struct device *dev) +{ + struct exynos_drm_manager *mgr = get_vidi_mgr(dev); + struct vidi_context *ctx = mgr->ctx; + struct drm_crtc *crtc = ctx->crtc; + int ret; + + vidi_mgr_initialize(mgr, drm_dev); + + ret = exynos_drm_crtc_create(&vidi_manager); + if (ret) { + DRM_ERROR("failed to create crtc.\n"); + return ret; + } + + ret = exynos_drm_create_enc_conn(drm_dev, &vidi_display); + if (ret) { + crtc->funcs->destroy(crtc); + DRM_ERROR("failed to create encoder and connector.\n"); + return ret; + } return 0; } static int vidi_probe(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - struct vidi_context *ctx; struct exynos_drm_subdrv *subdrv; + struct vidi_context *ctx; int ret; - ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; @@ -563,58 +617,52 @@ static int vidi_probe(struct platform_device *pdev) INIT_WORK(&ctx->work, vidi_fake_vblank_handler); - subdrv = &ctx->subdrv; - subdrv->dev = dev; - subdrv->manager = &vidi_manager; - subdrv->probe = vidi_subdrv_probe; - subdrv->remove = vidi_subdrv_remove; + vidi_manager.ctx = ctx; + vidi_display.ctx = ctx; mutex_init(&ctx->lock); - platform_set_drvdata(pdev, ctx); + platform_set_drvdata(pdev, &vidi_manager); - ret = device_create_file(dev, &dev_attr_connection); - if (ret < 0) - DRM_INFO("failed to create connection sysfs.\n"); + subdrv = &ctx->subdrv; + subdrv->dev = &pdev->dev; + subdrv->probe = vidi_subdrv_probe; - exynos_drm_subdrv_register(subdrv); + ret = exynos_drm_subdrv_register(subdrv); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register drm vidi device\n"); + return ret; + } + + ret = device_create_file(&pdev->dev, &dev_attr_connection); + if (ret < 0) { + exynos_drm_subdrv_unregister(subdrv); + DRM_INFO("failed to create connection sysfs.\n"); + } return 0; } static int vidi_remove(struct platform_device *pdev) { - struct vidi_context *ctx = platform_get_drvdata(pdev); - - exynos_drm_subdrv_unregister(&ctx->subdrv); + struct exynos_drm_manager *mgr = platform_get_drvdata(pdev); + struct vidi_context *ctx = mgr->ctx; + struct drm_encoder *encoder = ctx->encoder; + struct drm_crtc *crtc = mgr->crtc; if (ctx->raw_edid != (struct edid *)fake_edid_info) { kfree(ctx->raw_edid); ctx->raw_edid = NULL; - } - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int vidi_suspend(struct device *dev) -{ - struct vidi_context *ctx = get_vidi_context(dev); - - return vidi_power_on(ctx, false); -} + return -EINVAL; + } -static int vidi_resume(struct device *dev) -{ - struct vidi_context *ctx = get_vidi_context(dev); + crtc->funcs->destroy(crtc); + encoder->funcs->destroy(encoder); + drm_connector_cleanup(&ctx->connector); - return vidi_power_on(ctx, true); + return 0; } -#endif - -static const struct dev_pm_ops vidi_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(vidi_suspend, vidi_resume) -}; struct platform_driver vidi_driver = { .probe = vidi_probe, @@ -622,6 +670,33 @@ struct platform_driver vidi_driver = { .driver = { .name = "exynos-drm-vidi", .owner = THIS_MODULE, - .pm = &vidi_pm_ops, }, }; + +int exynos_drm_probe_vidi(void) +{ + struct platform_device *pdev; + int ret; + + pdev = platform_device_register_simple("exynos-drm-vidi", -1, NULL, 0); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + ret = platform_driver_register(&vidi_driver); + if (ret) { + platform_device_unregister(pdev); + return ret; + } + + return ret; +} + +void exynos_drm_remove_vidi(void) +{ + struct vidi_context *ctx = vidi_manager.ctx; + struct exynos_drm_subdrv *subdrv = &ctx->subdrv; + struct platform_device *pdev = to_platform_device(subdrv->dev); + + platform_driver_unregister(&vidi_driver); + platform_device_unregister(pdev); +} diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c index a0e10aeb0e6..aa259b0a873 100644 --- a/drivers/gpu/drm/exynos/exynos_hdmi.c +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -33,56 +33,55 @@ #include <linux/regulator/consumer.h> #include <linux/io.h> #include <linux/of.h> +#include <linux/of_address.h> #include <linux/of_gpio.h> +#include <linux/hdmi.h> +#include <linux/component.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> #include <drm/exynos_drm.h> #include "exynos_drm_drv.h" -#include "exynos_drm_hdmi.h" - -#include "exynos_hdmi.h" +#include "exynos_drm_crtc.h" +#include "exynos_mixer.h" #include <linux/gpio.h> #include <media/s5p_hdmi.h> -#define MAX_WIDTH 1920 -#define MAX_HEIGHT 1080 -#define get_hdmi_context(dev) platform_get_drvdata(to_platform_device(dev)) +#define get_hdmi_display(dev) platform_get_drvdata(to_platform_device(dev)) +#define ctx_from_connector(c) container_of(c, struct hdmi_context, connector) + +#define HOTPLUG_DEBOUNCE_MS 1100 /* AVI header and aspect ratio */ #define HDMI_AVI_VERSION 0x02 #define HDMI_AVI_LENGTH 0x0D -#define AVI_PIC_ASPECT_RATIO_16_9 (2 << 4) -#define AVI_SAME_AS_PIC_ASPECT_RATIO 8 /* AUI header info */ #define HDMI_AUI_VERSION 0x01 #define HDMI_AUI_LENGTH 0x0A - -/* HDMI infoframe to configure HDMI out packet header, AUI and AVI */ -enum HDMI_PACKET_TYPE { - /* refer to Table 5-8 Packet Type in HDMI specification v1.4a */ - /* InfoFrame packet type */ - HDMI_PACKET_TYPE_INFOFRAME = 0x80, - /* Vendor-Specific InfoFrame */ - HDMI_PACKET_TYPE_VSI = HDMI_PACKET_TYPE_INFOFRAME + 1, - /* Auxiliary Video information InfoFrame */ - HDMI_PACKET_TYPE_AVI = HDMI_PACKET_TYPE_INFOFRAME + 2, - /* Audio information InfoFrame */ - HDMI_PACKET_TYPE_AUI = HDMI_PACKET_TYPE_INFOFRAME + 4 -}; +#define AVI_SAME_AS_PIC_ASPECT_RATIO 0x8 +#define AVI_4_3_CENTER_RATIO 0x9 +#define AVI_16_9_CENTER_RATIO 0xa enum hdmi_type { HDMI_TYPE13, HDMI_TYPE14, }; +struct hdmi_driver_data { + unsigned int type; + const struct hdmiphy_config *phy_confs; + unsigned int phy_conf_count; + unsigned int is_apb_phy:1; +}; + struct hdmi_resources { struct clk *hdmi; struct clk *sclk_hdmi; struct clk *sclk_pixel; struct clk *sclk_hdmiphy; - struct clk *hdmiphy; struct clk *mout_hdmi; struct regulator_bulk_data *regul_bulk; int regul_count; @@ -174,6 +173,7 @@ struct hdmi_v14_conf { struct hdmi_conf_regs { int pixel_clock; int cea_video_id; + enum hdmi_picture_aspect aspect_ratio; union { struct hdmi_v13_conf v13_conf; struct hdmi_v14_conf v14_conf; @@ -183,25 +183,32 @@ struct hdmi_conf_regs { struct hdmi_context { struct device *dev; struct drm_device *drm_dev; + struct drm_connector connector; + struct drm_encoder *encoder; bool hpd; bool powered; bool dvi_mode; struct mutex hdmi_mutex; void __iomem *regs; - void *parent_ctx; int irq; + struct delayed_work hotplug_work; - struct i2c_client *ddc_port; + struct i2c_adapter *ddc_adpt; struct i2c_client *hdmiphy_port; /* current hdmiphy conf regs */ + struct drm_display_mode current_mode; struct hdmi_conf_regs mode_conf; struct hdmi_resources res; int hpd_gpio; + void __iomem *regs_hdmiphy; + const struct hdmiphy_config *phy_confs; + unsigned int phy_conf_count; + struct regmap *pmureg; enum hdmi_type type; }; @@ -315,6 +322,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = { }, }, { + .pixel_clock = 71000000, + .conf = { + 0x01, 0xd1, 0x3b, 0x35, 0x40, 0x0c, 0x04, 0x08, + 0x85, 0xa0, 0x63, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xad, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 73250000, + .conf = { + 0x01, 0xd1, 0x3d, 0x35, 0x40, 0x18, 0x02, 0x08, + 0x83, 0xa0, 0x6e, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xa8, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { .pixel_clock = 74176000, .conf = { 0x01, 0xd1, 0x3e, 0x35, 0x40, 0x5b, 0xde, 0x08, @@ -360,6 +385,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = { }, }, { + .pixel_clock = 115500000, + .conf = { + 0x01, 0xd1, 0x30, 0x12, 0x40, 0x40, 0x10, 0x08, + 0x80, 0x80, 0x21, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0xaa, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 119000000, + .conf = { + 0x01, 0xd1, 0x32, 0x1a, 0x40, 0x30, 0xd8, 0x08, + 0x04, 0xa0, 0x2a, 0xd9, 0x45, 0xa0, 0xac, 0x80, + 0x08, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86, + 0x54, 0x9d, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { .pixel_clock = 146250000, .conf = { 0x01, 0xd1, 0x3d, 0x15, 0x40, 0x18, 0xfd, 0x08, @@ -379,10 +422,181 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = { }, }; -struct hdmi_infoframe { - enum HDMI_PACKET_TYPE type; - u8 ver; - u8 len; +static const struct hdmiphy_config hdmiphy_5420_configs[] = { + { + .pixel_clock = 25200000, + .conf = { + 0x01, 0x52, 0x3F, 0x55, 0x40, 0x01, 0x00, 0xC8, + 0x82, 0xC8, 0xBD, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x06, 0x80, 0x01, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xF4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27000000, + .conf = { + 0x01, 0xD1, 0x22, 0x51, 0x40, 0x08, 0xFC, 0xE0, + 0x98, 0xE8, 0xCB, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x06, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xE4, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 27027000, + .conf = { + 0x01, 0xD1, 0x2D, 0x72, 0x40, 0x64, 0x12, 0xC8, + 0x43, 0xE8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x26, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xE3, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 36000000, + .conf = { + 0x01, 0x51, 0x2D, 0x55, 0x40, 0x40, 0x00, 0xC8, + 0x02, 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xAB, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 40000000, + .conf = { + 0x01, 0xD1, 0x21, 0x31, 0x40, 0x3C, 0x28, 0xC8, + 0x87, 0xE8, 0xC8, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0x9A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 65000000, + .conf = { + 0x01, 0xD1, 0x36, 0x34, 0x40, 0x0C, 0x04, 0xC8, + 0x82, 0xE8, 0x45, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xBD, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 71000000, + .conf = { + 0x01, 0xD1, 0x3B, 0x35, 0x40, 0x0C, 0x04, 0xC8, + 0x85, 0xE8, 0x63, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0x57, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 73250000, + .conf = { + 0x01, 0xD1, 0x1F, 0x10, 0x40, 0x78, 0x8D, 0xC8, + 0x81, 0xE8, 0xB7, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x56, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xA8, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74176000, + .conf = { + 0x01, 0xD1, 0x1F, 0x10, 0x40, 0x5B, 0xEF, 0xC8, + 0x81, 0xE8, 0xB9, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x56, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xA6, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 74250000, + .conf = { + 0x01, 0xD1, 0x1F, 0x10, 0x40, 0x40, 0xF8, 0x08, + 0x81, 0xE8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x26, 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x66, + 0x54, 0xA5, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 83500000, + .conf = { + 0x01, 0xD1, 0x23, 0x11, 0x40, 0x0C, 0xFB, 0xC8, + 0x85, 0xE8, 0xD1, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0x4A, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 88750000, + .conf = { + 0x01, 0xD1, 0x25, 0x11, 0x40, 0x18, 0xFF, 0xC8, + 0x83, 0xE8, 0xDE, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0x45, 0x24, 0x00, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 106500000, + .conf = { + 0x01, 0xD1, 0x2C, 0x12, 0x40, 0x0C, 0x09, 0xC8, + 0x84, 0xE8, 0x0A, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0x73, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 108000000, + .conf = { + 0x01, 0x51, 0x2D, 0x15, 0x40, 0x01, 0x00, 0xC8, + 0x82, 0xC8, 0x0E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0xC7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 115500000, + .conf = { + 0x01, 0xD1, 0x30, 0x14, 0x40, 0x0C, 0x03, 0xC8, + 0x88, 0xE8, 0x21, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0x6A, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 146250000, + .conf = { + 0x01, 0xD1, 0x3D, 0x15, 0x40, 0x18, 0xFD, 0xC8, + 0x83, 0xE8, 0x6E, 0xD9, 0x45, 0xA0, 0xAC, 0x80, + 0x08, 0x80, 0x09, 0x84, 0x05, 0x02, 0x24, 0x66, + 0x54, 0x54, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80, + }, + }, + { + .pixel_clock = 148500000, + .conf = { + 0x01, 0xD1, 0x1F, 0x00, 0x40, 0x40, 0xF8, 0x08, + 0x81, 0xE8, 0xBA, 0xD8, 0x45, 0xA0, 0xAC, 0x80, + 0x26, 0x80, 0x09, 0x84, 0x05, 0x22, 0x24, 0x66, + 0x54, 0x4B, 0x25, 0x03, 0x00, 0x80, 0x01, 0x80, + }, + }, +}; + +static struct hdmi_driver_data exynos5420_hdmi_driver_data = { + .type = HDMI_TYPE14, + .phy_confs = hdmiphy_5420_configs, + .phy_conf_count = ARRAY_SIZE(hdmiphy_5420_configs), + .is_apb_phy = 1, +}; + +static struct hdmi_driver_data exynos4212_hdmi_driver_data = { + .type = HDMI_TYPE14, + .phy_confs = hdmiphy_v14_configs, + .phy_conf_count = ARRAY_SIZE(hdmiphy_v14_configs), + .is_apb_phy = 0, +}; + +static struct hdmi_driver_data exynos5_hdmi_driver_data = { + .type = HDMI_TYPE14, + .phy_confs = hdmiphy_v13_configs, + .phy_conf_count = ARRAY_SIZE(hdmiphy_v13_configs), + .is_apb_phy = 0, }; static inline u32 hdmi_reg_read(struct hdmi_context *hdata, u32 reg_id) @@ -404,6 +618,48 @@ static inline void hdmi_reg_writemask(struct hdmi_context *hdata, writel(value, hdata->regs + reg_id); } +static int hdmiphy_reg_writeb(struct hdmi_context *hdata, + u32 reg_offset, u8 value) +{ + if (hdata->hdmiphy_port) { + u8 buffer[2]; + int ret; + + buffer[0] = reg_offset; + buffer[1] = value; + + ret = i2c_master_send(hdata->hdmiphy_port, buffer, 2); + if (ret == 2) + return 0; + return ret; + } else { + writeb(value, hdata->regs_hdmiphy + (reg_offset<<2)); + return 0; + } +} + +static int hdmiphy_reg_write_buf(struct hdmi_context *hdata, + u32 reg_offset, const u8 *buf, u32 len) +{ + if ((reg_offset + len) > 32) + return -EINVAL; + + if (hdata->hdmiphy_port) { + int ret; + + ret = i2c_master_send(hdata->hdmiphy_port, buf, len); + if (ret == len) + return 0; + return ret; + } else { + int i; + for (i = 0; i < len; i++) + writeb(buf[i], hdata->regs_hdmiphy + + ((reg_offset + i)<<2)); + return 0; + } +} + static void hdmi_v13_regs_dump(struct hdmi_context *hdata, char *prefix) { #define DUMPREG(reg_id) \ @@ -682,11 +938,10 @@ static u8 hdmi_chksum(struct hdmi_context *hdata, } static void hdmi_reg_infoframe(struct hdmi_context *hdata, - struct hdmi_infoframe *infoframe) + union hdmi_infoframe *infoframe) { u32 hdr_sum; u8 chksum; - u32 aspect_ratio; u32 mod; u32 vic; @@ -700,40 +955,62 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata, return; } - switch (infoframe->type) { - case HDMI_PACKET_TYPE_AVI: + switch (infoframe->any.type) { + case HDMI_INFOFRAME_TYPE_AVI: hdmi_reg_writeb(hdata, HDMI_AVI_CON, HDMI_AVI_CON_EVERY_VSYNC); - hdmi_reg_writeb(hdata, HDMI_AVI_HEADER0, infoframe->type); - hdmi_reg_writeb(hdata, HDMI_AVI_HEADER1, infoframe->ver); - hdmi_reg_writeb(hdata, HDMI_AVI_HEADER2, infoframe->len); - hdr_sum = infoframe->type + infoframe->ver + infoframe->len; + hdmi_reg_writeb(hdata, HDMI_AVI_HEADER0, infoframe->any.type); + hdmi_reg_writeb(hdata, HDMI_AVI_HEADER1, + infoframe->any.version); + hdmi_reg_writeb(hdata, HDMI_AVI_HEADER2, infoframe->any.length); + hdr_sum = infoframe->any.type + infoframe->any.version + + infoframe->any.length; /* Output format zero hardcoded ,RGB YBCR selection */ hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(1), 0 << 5 | AVI_ACTIVE_FORMAT_VALID | AVI_UNDERSCANNED_DISPLAY_VALID); - aspect_ratio = AVI_PIC_ASPECT_RATIO_16_9; - - hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2), aspect_ratio | - AVI_SAME_AS_PIC_ASPECT_RATIO); + /* + * Set the aspect ratio as per the mode, mentioned in + * Table 9 AVI InfoFrame Data Byte 2 of CEA-861-D Standard + */ + switch (hdata->mode_conf.aspect_ratio) { + case HDMI_PICTURE_ASPECT_4_3: + hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2), + hdata->mode_conf.aspect_ratio | + AVI_4_3_CENTER_RATIO); + break; + case HDMI_PICTURE_ASPECT_16_9: + hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2), + hdata->mode_conf.aspect_ratio | + AVI_16_9_CENTER_RATIO); + break; + case HDMI_PICTURE_ASPECT_NONE: + default: + hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2), + hdata->mode_conf.aspect_ratio | + AVI_SAME_AS_PIC_ASPECT_RATIO); + break; + } vic = hdata->mode_conf.cea_video_id; hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(4), vic); chksum = hdmi_chksum(hdata, HDMI_AVI_BYTE(1), - infoframe->len, hdr_sum); + infoframe->any.length, hdr_sum); DRM_DEBUG_KMS("AVI checksum = 0x%x\n", chksum); hdmi_reg_writeb(hdata, HDMI_AVI_CHECK_SUM, chksum); break; - case HDMI_PACKET_TYPE_AUI: + case HDMI_INFOFRAME_TYPE_AUDIO: hdmi_reg_writeb(hdata, HDMI_AUI_CON, 0x02); - hdmi_reg_writeb(hdata, HDMI_AUI_HEADER0, infoframe->type); - hdmi_reg_writeb(hdata, HDMI_AUI_HEADER1, infoframe->ver); - hdmi_reg_writeb(hdata, HDMI_AUI_HEADER2, infoframe->len); - hdr_sum = infoframe->type + infoframe->ver + infoframe->len; + hdmi_reg_writeb(hdata, HDMI_AUI_HEADER0, infoframe->any.type); + hdmi_reg_writeb(hdata, HDMI_AUI_HEADER1, + infoframe->any.version); + hdmi_reg_writeb(hdata, HDMI_AUI_HEADER2, infoframe->any.length); + hdr_sum = infoframe->any.type + infoframe->any.version + + infoframe->any.length; chksum = hdmi_chksum(hdata, HDMI_AUI_BYTE(1), - infoframe->len, hdr_sum); + infoframe->any.length, hdr_sum); DRM_DEBUG_KMS("AUI checksum = 0x%x\n", chksum); hdmi_reg_writeb(hdata, HDMI_AUI_CHECK_SUM, chksum); break; @@ -742,58 +1019,66 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata, } } -static bool hdmi_is_connected(void *ctx) +static enum drm_connector_status hdmi_detect(struct drm_connector *connector, + bool force) { - struct hdmi_context *hdata = ctx; + struct hdmi_context *hdata = ctx_from_connector(connector); + + hdata->hpd = gpio_get_value(hdata->hpd_gpio); - return hdata->hpd; + return hdata->hpd ? connector_status_connected : + connector_status_disconnected; } -static struct edid *hdmi_get_edid(void *ctx, struct drm_connector *connector) +static void hdmi_connector_destroy(struct drm_connector *connector) +{ +} + +static struct drm_connector_funcs hdmi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = hdmi_detect, + .destroy = hdmi_connector_destroy, +}; + +static int hdmi_get_modes(struct drm_connector *connector) { - struct edid *raw_edid; - struct hdmi_context *hdata = ctx; + struct hdmi_context *hdata = ctx_from_connector(connector); + struct edid *edid; - if (!hdata->ddc_port) - return ERR_PTR(-ENODEV); + if (!hdata->ddc_adpt) + return -ENODEV; - raw_edid = drm_get_edid(connector, hdata->ddc_port->adapter); - if (!raw_edid) - return ERR_PTR(-ENODEV); + edid = drm_get_edid(connector, hdata->ddc_adpt); + if (!edid) + return -ENODEV; - hdata->dvi_mode = !drm_detect_hdmi_monitor(raw_edid); + hdata->dvi_mode = !drm_detect_hdmi_monitor(edid); DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n", (hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"), - raw_edid->width_cm, raw_edid->height_cm); + edid->width_cm, edid->height_cm); + + drm_mode_connector_update_edid_property(connector, edid); - return raw_edid; + return drm_add_edid_modes(connector, edid); } static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock) { - const struct hdmiphy_config *confs; - int count, i; - - if (hdata->type == HDMI_TYPE13) { - confs = hdmiphy_v13_configs; - count = ARRAY_SIZE(hdmiphy_v13_configs); - } else if (hdata->type == HDMI_TYPE14) { - confs = hdmiphy_v14_configs; - count = ARRAY_SIZE(hdmiphy_v14_configs); - } else - return -EINVAL; + int i; - for (i = 0; i < count; i++) - if (confs[i].pixel_clock == pixel_clock) + for (i = 0; i < hdata->phy_conf_count; i++) + if (hdata->phy_confs[i].pixel_clock == pixel_clock) return i; DRM_DEBUG_KMS("Could not find phy config for %d\n", pixel_clock); return -EINVAL; } -static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode) +static int hdmi_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) { - struct hdmi_context *hdata = ctx; + struct hdmi_context *hdata = ctx_from_connector(connector); int ret; DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n", @@ -801,12 +1086,93 @@ static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode) (mode->flags & DRM_MODE_FLAG_INTERLACE) ? true : false, mode->clock * 1000); + ret = mixer_check_mode(mode); + if (ret) + return MODE_BAD; + ret = hdmi_find_phy_conf(hdata, mode->clock * 1000); if (ret < 0) + return MODE_BAD; + + return MODE_OK; +} + +static struct drm_encoder *hdmi_best_encoder(struct drm_connector *connector) +{ + struct hdmi_context *hdata = ctx_from_connector(connector); + + return hdata->encoder; +} + +static struct drm_connector_helper_funcs hdmi_connector_helper_funcs = { + .get_modes = hdmi_get_modes, + .mode_valid = hdmi_mode_valid, + .best_encoder = hdmi_best_encoder, +}; + +static int hdmi_create_connector(struct exynos_drm_display *display, + struct drm_encoder *encoder) +{ + struct hdmi_context *hdata = display->ctx; + struct drm_connector *connector = &hdata->connector; + int ret; + + hdata->encoder = encoder; + connector->interlace_allowed = true; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(hdata->drm_dev, connector, + &hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); return ret; + } + + drm_connector_helper_add(connector, &hdmi_connector_helper_funcs); + drm_sysfs_connector_add(connector); + drm_mode_connector_attach_encoder(connector, encoder); + return 0; } +static void hdmi_mode_fixup(struct exynos_drm_display *display, + struct drm_connector *connector, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_display_mode *m; + int mode_ok; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + drm_mode_set_crtcinfo(adjusted_mode, 0); + + mode_ok = hdmi_mode_valid(connector, adjusted_mode); + + /* just return if user desired mode exists. */ + if (mode_ok == MODE_OK) + return; + + /* + * otherwise, find the most suitable mode among modes and change it + * to adjusted_mode. + */ + list_for_each_entry(m, &connector->modes, head) { + mode_ok = hdmi_mode_valid(connector, m); + + if (mode_ok == MODE_OK) { + DRM_INFO("desired mode doesn't exist so\n"); + DRM_INFO("use the most suitable mode among modes.\n"); + + DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n", + m->hdisplay, m->vdisplay, m->vrefresh); + + drm_mode_copy(adjusted_mode, m); + break; + } + } +} + static void hdmi_set_acr(u32 freq, u8 *acr) { u32 n, cts; @@ -967,25 +1333,20 @@ static void hdmi_audio_control(struct hdmi_context *hdata, bool onoff) HDMI_ASP_EN : HDMI_ASP_DIS, HDMI_ASP_MASK); } -static void hdmi_conf_reset(struct hdmi_context *hdata) +static void hdmi_start(struct hdmi_context *hdata, bool start) { - u32 reg; + u32 val = start ? HDMI_TG_EN : 0; - if (hdata->type == HDMI_TYPE13) - reg = HDMI_V13_CORE_RSTOUT; - else - reg = HDMI_CORE_RSTOUT; + if (hdata->current_mode.flags & DRM_MODE_FLAG_INTERLACE) + val |= HDMI_FIELD_EN; - /* resetting HDMI core */ - hdmi_reg_writemask(hdata, reg, 0, HDMI_CORE_SW_RSTOUT); - usleep_range(10000, 12000); - hdmi_reg_writemask(hdata, reg, ~0, HDMI_CORE_SW_RSTOUT); - usleep_range(10000, 12000); + hdmi_reg_writemask(hdata, HDMI_CON_0, val, HDMI_EN); + hdmi_reg_writemask(hdata, HDMI_TG_CMD, val, HDMI_TG_EN | HDMI_FIELD_EN); } static void hdmi_conf_init(struct hdmi_context *hdata) { - struct hdmi_infoframe infoframe; + union hdmi_infoframe infoframe; /* disable HPD interrupts from HDMI IP block, use GPIO instead */ hdmi_reg_writemask(hdata, HDMI_INTC_CON, 0, HDMI_INTC_EN_GLOBAL | @@ -994,6 +1355,8 @@ static void hdmi_conf_init(struct hdmi_context *hdata) /* choose HDMI mode */ hdmi_reg_writemask(hdata, HDMI_MODE_SEL, HDMI_MODE_HDMI_EN, HDMI_MODE_MASK); + /* Apply Video preable and Guard band in HDMI mode only */ + hdmi_reg_writeb(hdata, HDMI_CON_2, 0); /* disable bluescreen */ hdmi_reg_writemask(hdata, HDMI_CON_0, 0, HDMI_BLUE_SCR_EN); @@ -1021,14 +1384,14 @@ static void hdmi_conf_init(struct hdmi_context *hdata) hdmi_reg_writeb(hdata, HDMI_V13_AUI_CON, 0x02); hdmi_reg_writeb(hdata, HDMI_V13_ACR_CON, 0x04); } else { - infoframe.type = HDMI_PACKET_TYPE_AVI; - infoframe.ver = HDMI_AVI_VERSION; - infoframe.len = HDMI_AVI_LENGTH; + infoframe.any.type = HDMI_INFOFRAME_TYPE_AVI; + infoframe.any.version = HDMI_AVI_VERSION; + infoframe.any.length = HDMI_AVI_LENGTH; hdmi_reg_infoframe(hdata, &infoframe); - infoframe.type = HDMI_PACKET_TYPE_AUI; - infoframe.ver = HDMI_AUI_VERSION; - infoframe.len = HDMI_AUI_LENGTH; + infoframe.any.type = HDMI_INFOFRAME_TYPE_AUDIO; + infoframe.any.version = HDMI_AUI_VERSION; + infoframe.any.length = HDMI_AUI_LENGTH; hdmi_reg_infoframe(hdata, &infoframe); /* enable AVI packet every vsync, fixes purple line problem */ @@ -1117,12 +1480,7 @@ static void hdmi_v13_mode_apply(struct hdmi_context *hdata) clk_prepare_enable(hdata->res.sclk_hdmi); /* enable HDMI and timing generator */ - hdmi_reg_writemask(hdata, HDMI_CON_0, ~0, HDMI_EN); - if (core->int_pro_mode[0]) - hdmi_reg_writemask(hdata, HDMI_TG_CMD, ~0, HDMI_TG_EN | - HDMI_FIELD_EN); - else - hdmi_reg_writemask(hdata, HDMI_TG_CMD, ~0, HDMI_TG_EN); + hdmi_start(hdata, true); } static void hdmi_v14_mode_apply(struct hdmi_context *hdata) @@ -1284,12 +1642,7 @@ static void hdmi_v14_mode_apply(struct hdmi_context *hdata) clk_prepare_enable(hdata->res.sclk_hdmi); /* enable HDMI and timing generator */ - hdmi_reg_writemask(hdata, HDMI_CON_0, ~0, HDMI_EN); - if (core->int_pro_mode[0]) - hdmi_reg_writemask(hdata, HDMI_TG_CMD, ~0, HDMI_TG_EN | - HDMI_FIELD_EN); - else - hdmi_reg_writemask(hdata, HDMI_TG_CMD, ~0, HDMI_TG_EN); + hdmi_start(hdata, true); } static void hdmi_mode_apply(struct hdmi_context *hdata) @@ -1330,32 +1683,51 @@ static void hdmiphy_conf_reset(struct hdmi_context *hdata) static void hdmiphy_poweron(struct hdmi_context *hdata) { - if (hdata->type == HDMI_TYPE14) - hdmi_reg_writemask(hdata, HDMI_PHY_CON_0, 0, - HDMI_PHY_POWER_OFF_EN); + if (hdata->type != HDMI_TYPE14) + return; + + DRM_DEBUG_KMS("\n"); + + /* For PHY Mode Setting */ + hdmiphy_reg_writeb(hdata, HDMIPHY_MODE_SET_DONE, + HDMI_PHY_ENABLE_MODE_SET); + /* Phy Power On */ + hdmiphy_reg_writeb(hdata, HDMIPHY_POWER, + HDMI_PHY_POWER_ON); + /* For PHY Mode Setting */ + hdmiphy_reg_writeb(hdata, HDMIPHY_MODE_SET_DONE, + HDMI_PHY_DISABLE_MODE_SET); + /* PHY SW Reset */ + hdmiphy_conf_reset(hdata); } static void hdmiphy_poweroff(struct hdmi_context *hdata) { - if (hdata->type == HDMI_TYPE14) - hdmi_reg_writemask(hdata, HDMI_PHY_CON_0, ~0, - HDMI_PHY_POWER_OFF_EN); + if (hdata->type != HDMI_TYPE14) + return; + + DRM_DEBUG_KMS("\n"); + + /* PHY SW Reset */ + hdmiphy_conf_reset(hdata); + /* For PHY Mode Setting */ + hdmiphy_reg_writeb(hdata, HDMIPHY_MODE_SET_DONE, + HDMI_PHY_ENABLE_MODE_SET); + + /* PHY Power Off */ + hdmiphy_reg_writeb(hdata, HDMIPHY_POWER, + HDMI_PHY_POWER_OFF); + + /* For PHY Mode Setting */ + hdmiphy_reg_writeb(hdata, HDMIPHY_MODE_SET_DONE, + HDMI_PHY_DISABLE_MODE_SET); } static void hdmiphy_conf_apply(struct hdmi_context *hdata) { - const u8 *hdmiphy_data; - u8 buffer[32]; - u8 operation[2]; - u8 read_buffer[32] = {0, }; int ret; int i; - if (!hdata->hdmiphy_port) { - DRM_ERROR("hdmiphy is not attached\n"); - return; - } - /* pixel clock */ i = hdmi_find_phy_conf(hdata, hdata->mode_conf.pixel_clock); if (i < 0) { @@ -1363,39 +1735,21 @@ static void hdmiphy_conf_apply(struct hdmi_context *hdata) return; } - if (hdata->type == HDMI_TYPE13) - hdmiphy_data = hdmiphy_v13_configs[i].conf; - else - hdmiphy_data = hdmiphy_v14_configs[i].conf; - - memcpy(buffer, hdmiphy_data, 32); - ret = i2c_master_send(hdata->hdmiphy_port, buffer, 32); - if (ret != 32) { - DRM_ERROR("failed to configure HDMIPHY via I2C\n"); + ret = hdmiphy_reg_write_buf(hdata, 0, hdata->phy_confs[i].conf, 32); + if (ret) { + DRM_ERROR("failed to configure hdmiphy\n"); return; } usleep_range(10000, 12000); - /* operation mode */ - operation[0] = 0x1f; - operation[1] = 0x80; - - ret = i2c_master_send(hdata->hdmiphy_port, operation, 2); - if (ret != 2) { + ret = hdmiphy_reg_writeb(hdata, HDMIPHY_MODE_SET_DONE, + HDMI_PHY_DISABLE_MODE_SET); + if (ret) { DRM_ERROR("failed to enable hdmiphy\n"); return; } - ret = i2c_master_recv(hdata->hdmiphy_port, read_buffer, 32); - if (ret < 0) { - DRM_ERROR("failed to read hdmiphy config\n"); - return; - } - - for (i = 0; i < ret; i++) - DRM_DEBUG_KMS("hdmiphy[0x%02x] write[0x%02x] - " - "recv [0x%02x]\n", i, buffer[i], read_buffer[i]); } static void hdmi_conf_apply(struct hdmi_context *hdata) @@ -1404,7 +1758,7 @@ static void hdmi_conf_apply(struct hdmi_context *hdata) hdmiphy_conf_apply(hdata); mutex_lock(&hdata->hdmi_mutex); - hdmi_conf_reset(hdata); + hdmi_start(hdata, false); hdmi_conf_init(hdata); mutex_unlock(&hdata->hdmi_mutex); @@ -1435,6 +1789,7 @@ static void hdmi_v13_mode_set(struct hdmi_context *hdata, hdata->mode_conf.cea_video_id = drm_match_cea_mode((struct drm_display_mode *)m); hdata->mode_conf.pixel_clock = m->clock * 1000; + hdata->mode_conf.aspect_ratio = m->picture_aspect_ratio; hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay); hdmi_set_reg(core->h_v_line, 3, (m->htotal << 12) | m->vtotal); @@ -1531,6 +1886,7 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata, hdata->mode_conf.cea_video_id = drm_match_cea_mode((struct drm_display_mode *)m); hdata->mode_conf.pixel_clock = m->clock * 1000; + hdata->mode_conf.aspect_ratio = m->picture_aspect_ratio; hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay); hdmi_set_reg(core->v_line, 2, m->vtotal); @@ -1632,9 +1988,10 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata, hdmi_set_reg(tg->tg_3d, 1, 0x0); } -static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode) +static void hdmi_mode_set(struct exynos_drm_display *display, + struct drm_display_mode *mode) { - struct hdmi_context *hdata = ctx; + struct hdmi_context *hdata = display->ctx; struct drm_display_mode *m = mode; DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%s\n", @@ -1642,22 +1999,18 @@ static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode) m->vrefresh, (m->flags & DRM_MODE_FLAG_INTERLACE) ? "INTERLACED" : "PROGERESSIVE"); + /* preserve mode information for later use. */ + drm_mode_copy(&hdata->current_mode, mode); + if (hdata->type == HDMI_TYPE13) hdmi_v13_mode_set(hdata, mode); else hdmi_v14_mode_set(hdata, mode); } -static void hdmi_get_max_resol(void *ctx, unsigned int *width, - unsigned int *height) +static void hdmi_commit(struct exynos_drm_display *display) { - *width = MAX_WIDTH; - *height = MAX_HEIGHT; -} - -static void hdmi_commit(void *ctx) -{ - struct hdmi_context *hdata = ctx; + struct hdmi_context *hdata = display->ctx; mutex_lock(&hdata->hdmi_mutex); if (!hdata->powered) { @@ -1669,8 +2022,9 @@ static void hdmi_commit(void *ctx) hdmi_conf_apply(hdata); } -static void hdmi_poweron(struct hdmi_context *hdata) +static void hdmi_poweron(struct exynos_drm_display *display) { + struct hdmi_context *hdata = display->ctx; struct hdmi_resources *res = &hdata->res; mutex_lock(&hdata->hdmi_mutex); @@ -1683,18 +2037,25 @@ static void hdmi_poweron(struct hdmi_context *hdata) mutex_unlock(&hdata->hdmi_mutex); + pm_runtime_get_sync(hdata->dev); + if (regulator_bulk_enable(res->regul_count, res->regul_bulk)) DRM_DEBUG_KMS("failed to enable regulator bulk\n"); - clk_prepare_enable(res->hdmiphy); + /* set pmu hdmiphy control bit to enable hdmiphy */ + regmap_update_bits(hdata->pmureg, PMU_HDMI_PHY_CONTROL, + PMU_HDMI_PHY_ENABLE_BIT, 1); + clk_prepare_enable(res->hdmi); clk_prepare_enable(res->sclk_hdmi); hdmiphy_poweron(hdata); + hdmi_commit(display); } -static void hdmi_poweroff(struct hdmi_context *hdata) +static void hdmi_poweroff(struct exynos_drm_display *display) { + struct hdmi_context *hdata = display->ctx; struct hdmi_resources *res = &hdata->res; mutex_lock(&hdata->hdmi_mutex); @@ -1702,42 +2063,62 @@ static void hdmi_poweroff(struct hdmi_context *hdata) goto out; mutex_unlock(&hdata->hdmi_mutex); - /* - * The TV power domain needs any condition of hdmiphy to turn off and - * its reset state seems to meet the condition. - */ - hdmiphy_conf_reset(hdata); + /* HDMI System Disable */ + hdmi_reg_writemask(hdata, HDMI_CON_0, 0, HDMI_EN); + hdmiphy_poweroff(hdata); + cancel_delayed_work(&hdata->hotplug_work); + clk_disable_unprepare(res->sclk_hdmi); clk_disable_unprepare(res->hdmi); - clk_disable_unprepare(res->hdmiphy); + + /* reset pmu hdmiphy control bit to disable hdmiphy */ + regmap_update_bits(hdata->pmureg, PMU_HDMI_PHY_CONTROL, + PMU_HDMI_PHY_ENABLE_BIT, 0); + regulator_bulk_disable(res->regul_count, res->regul_bulk); - mutex_lock(&hdata->hdmi_mutex); + pm_runtime_put_sync(hdata->dev); + mutex_lock(&hdata->hdmi_mutex); hdata->powered = false; out: mutex_unlock(&hdata->hdmi_mutex); } -static void hdmi_dpms(void *ctx, int mode) +static void hdmi_dpms(struct exynos_drm_display *display, int mode) { - struct hdmi_context *hdata = ctx; + struct hdmi_context *hdata = display->ctx; + struct drm_encoder *encoder = hdata->encoder; + struct drm_crtc *crtc = encoder->crtc; + struct drm_crtc_helper_funcs *funcs = NULL; DRM_DEBUG_KMS("mode %d\n", mode); switch (mode) { case DRM_MODE_DPMS_ON: - if (pm_runtime_suspended(hdata->dev)) - pm_runtime_get_sync(hdata->dev); + hdmi_poweron(display); break; case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_OFF: - if (!pm_runtime_suspended(hdata->dev)) - pm_runtime_put_sync(hdata->dev); + /* + * The SFRs of VP and Mixer are updated by Vertical Sync of + * Timing generator which is a part of HDMI so the sequence + * to disable TV Subsystem should be as following, + * VP -> Mixer -> HDMI + * + * Below codes will try to disable Mixer and VP(if used) + * prior to disabling HDMI. + */ + if (crtc) + funcs = crtc->helper_private; + if (funcs && funcs->dpms) + (*funcs->dpms)(crtc, mode); + + hdmi_poweroff(display); break; default: DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode); @@ -1745,30 +2126,39 @@ static void hdmi_dpms(void *ctx, int mode) } } -static struct exynos_hdmi_ops hdmi_ops = { - /* display */ - .is_connected = hdmi_is_connected, - .get_edid = hdmi_get_edid, - .check_mode = hdmi_check_mode, - - /* manager */ +static struct exynos_drm_display_ops hdmi_display_ops = { + .create_connector = hdmi_create_connector, + .mode_fixup = hdmi_mode_fixup, .mode_set = hdmi_mode_set, - .get_max_resol = hdmi_get_max_resol, - .commit = hdmi_commit, .dpms = hdmi_dpms, + .commit = hdmi_commit, }; -static irqreturn_t hdmi_irq_thread(int irq, void *arg) +static struct exynos_drm_display hdmi_display = { + .type = EXYNOS_DISPLAY_TYPE_HDMI, + .ops = &hdmi_display_ops, +}; + +static void hdmi_hotplug_work_func(struct work_struct *work) { - struct exynos_drm_hdmi_context *ctx = arg; - struct hdmi_context *hdata = ctx->ctx; + struct hdmi_context *hdata; + + hdata = container_of(work, struct hdmi_context, hotplug_work.work); mutex_lock(&hdata->hdmi_mutex); hdata->hpd = gpio_get_value(hdata->hpd_gpio); mutex_unlock(&hdata->hdmi_mutex); - if (ctx->drm_dev) - drm_helper_hpd_irq_event(ctx->drm_dev); + if (hdata->drm_dev) + drm_helper_hpd_irq_event(hdata->drm_dev); +} + +static irqreturn_t hdmi_irq_thread(int irq, void *arg) +{ + struct hdmi_context *hdata = arg; + + mod_delayed_work(system_wq, &hdata->hotplug_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); return IRQ_HANDLED; } @@ -1787,37 +2177,35 @@ static int hdmi_resources_init(struct hdmi_context *hdata) DRM_DEBUG_KMS("HDMI resource init\n"); - memset(res, 0, sizeof(*res)); - /* get clocks, power */ res->hdmi = devm_clk_get(dev, "hdmi"); if (IS_ERR(res->hdmi)) { DRM_ERROR("failed to get clock 'hdmi'\n"); + ret = PTR_ERR(res->hdmi); goto fail; } res->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi"); if (IS_ERR(res->sclk_hdmi)) { DRM_ERROR("failed to get clock 'sclk_hdmi'\n"); + ret = PTR_ERR(res->sclk_hdmi); goto fail; } res->sclk_pixel = devm_clk_get(dev, "sclk_pixel"); if (IS_ERR(res->sclk_pixel)) { DRM_ERROR("failed to get clock 'sclk_pixel'\n"); + ret = PTR_ERR(res->sclk_pixel); goto fail; } res->sclk_hdmiphy = devm_clk_get(dev, "sclk_hdmiphy"); if (IS_ERR(res->sclk_hdmiphy)) { DRM_ERROR("failed to get clock 'sclk_hdmiphy'\n"); - goto fail; - } - res->hdmiphy = devm_clk_get(dev, "hdmiphy"); - if (IS_ERR(res->hdmiphy)) { - DRM_ERROR("failed to get clock 'hdmiphy'\n"); + ret = PTR_ERR(res->sclk_hdmiphy); goto fail; } res->mout_hdmi = devm_clk_get(dev, "mout_hdmi"); if (IS_ERR(res->mout_hdmi)) { DRM_ERROR("failed to get clock 'mout_hdmi'\n"); + ret = PTR_ERR(res->mout_hdmi); goto fail; } @@ -1825,8 +2213,10 @@ static int hdmi_resources_init(struct hdmi_context *hdata) res->regul_bulk = devm_kzalloc(dev, ARRAY_SIZE(supply) * sizeof(res->regul_bulk[0]), GFP_KERNEL); - if (!res->regul_bulk) + if (!res->regul_bulk) { + ret = -ENOMEM; goto fail; + } for (i = 0; i < ARRAY_SIZE(supply); ++i) { res->regul_bulk[i].supply = supply[i]; res->regul_bulk[i].consumer = NULL; @@ -1834,28 +2224,14 @@ static int hdmi_resources_init(struct hdmi_context *hdata) ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(supply), res->regul_bulk); if (ret) { DRM_ERROR("failed to get regulators\n"); - goto fail; + return ret; } res->regul_count = ARRAY_SIZE(supply); - return 0; + return ret; fail: DRM_ERROR("HDMI resource init - failed\n"); - return -ENODEV; -} - -static struct i2c_client *hdmi_ddc, *hdmi_hdmiphy; - -void hdmi_attach_ddc_client(struct i2c_client *ddc) -{ - if (ddc) - hdmi_ddc = ddc; -} - -void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy) -{ - if (hdmiphy) - hdmi_hdmiphy = hdmiphy; + return ret; } static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata @@ -1885,51 +2261,110 @@ err_data: static struct of_device_id hdmi_match_types[] = { { .compatible = "samsung,exynos5-hdmi", - .data = (void *)HDMI_TYPE14, + .data = &exynos5_hdmi_driver_data, }, { .compatible = "samsung,exynos4212-hdmi", - .data = (void *)HDMI_TYPE14, + .data = &exynos4212_hdmi_driver_data, + }, { + .compatible = "samsung,exynos5420-hdmi", + .data = &exynos5420_hdmi_driver_data, }, { /* end node */ } }; +static int hdmi_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm_dev = data; + struct hdmi_context *hdata; + + hdata = hdmi_display.ctx; + hdata->drm_dev = drm_dev; + + return exynos_drm_create_enc_conn(drm_dev, &hdmi_display); +} + +static void hdmi_unbind(struct device *dev, struct device *master, void *data) +{ + struct exynos_drm_display *display = get_hdmi_display(dev); + struct drm_encoder *encoder = display->encoder; + struct hdmi_context *hdata = display->ctx; + + encoder->funcs->destroy(encoder); + drm_connector_cleanup(&hdata->connector); +} + +static const struct component_ops hdmi_component_ops = { + .bind = hdmi_bind, + .unbind = hdmi_unbind, +}; + +static struct device_node *hdmi_legacy_ddc_dt_binding(struct device *dev) +{ + const char *compatible_str = "samsung,exynos4210-hdmiddc"; + struct device_node *np; + + np = of_find_compatible_node(NULL, NULL, compatible_str); + if (np) + return of_get_next_parent(np); + + return NULL; +} + +static struct device_node *hdmi_legacy_phy_dt_binding(struct device *dev) +{ + const char *compatible_str = "samsung,exynos4212-hdmiphy"; + + return of_find_compatible_node(NULL, NULL, compatible_str); +} + static int hdmi_probe(struct platform_device *pdev) { + struct device_node *ddc_node, *phy_node; + struct s5p_hdmi_platform_data *pdata; + struct hdmi_driver_data *drv_data; + const struct of_device_id *match; struct device *dev = &pdev->dev; - struct exynos_drm_hdmi_context *drm_hdmi_ctx; struct hdmi_context *hdata; - struct s5p_hdmi_platform_data *pdata; struct resource *res; - const struct of_device_id *match; int ret; - if (!dev->of_node) - return -ENODEV; + ret = exynos_drm_component_add(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR, + hdmi_display.type); + if (ret) + return ret; - pdata = drm_hdmi_dt_parse_pdata(dev); - if (!pdata) - return -EINVAL; + if (!dev->of_node) { + ret = -ENODEV; + goto err_del_component; + } - drm_hdmi_ctx = devm_kzalloc(dev, sizeof(*drm_hdmi_ctx), GFP_KERNEL); - if (!drm_hdmi_ctx) - return -ENOMEM; + pdata = drm_hdmi_dt_parse_pdata(dev); + if (!pdata) { + ret = -EINVAL; + goto err_del_component; + } hdata = devm_kzalloc(dev, sizeof(struct hdmi_context), GFP_KERNEL); - if (!hdata) - return -ENOMEM; + if (!hdata) { + ret = -ENOMEM; + goto err_del_component; + } mutex_init(&hdata->hdmi_mutex); - drm_hdmi_ctx->ctx = (void *)hdata; - hdata->parent_ctx = (void *)drm_hdmi_ctx; - - platform_set_drvdata(pdev, drm_hdmi_ctx); + platform_set_drvdata(pdev, &hdmi_display); match = of_match_node(hdmi_match_types, dev->of_node); - if (!match) - return -ENODEV; - hdata->type = (enum hdmi_type)match->data; + if (!match) { + ret = -ENODEV; + goto err_del_component; + } + + drv_data = (struct hdmi_driver_data *)match->data; + hdata->type = drv_data->type; + hdata->phy_confs = drv_data->phy_confs; + hdata->phy_conf_count = drv_data->phy_conf_count; hdata->hpd_gpio = pdata->hpd_gpio; hdata->dev = dev; @@ -1937,36 +2372,69 @@ static int hdmi_probe(struct platform_device *pdev) ret = hdmi_resources_init(hdata); if (ret) { DRM_ERROR("hdmi_resources_init failed\n"); - return -EINVAL; + return ret; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); hdata->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(hdata->regs)) - return PTR_ERR(hdata->regs); + if (IS_ERR(hdata->regs)) { + ret = PTR_ERR(hdata->regs); + goto err_del_component; + } ret = devm_gpio_request(dev, hdata->hpd_gpio, "HPD"); if (ret) { DRM_ERROR("failed to request HPD gpio\n"); - return ret; + goto err_del_component; } + ddc_node = hdmi_legacy_ddc_dt_binding(dev); + if (ddc_node) + goto out_get_ddc_adpt; + /* DDC i2c driver */ - if (i2c_add_driver(&ddc_driver)) { - DRM_ERROR("failed to register ddc i2c driver\n"); - return -ENOENT; + ddc_node = of_parse_phandle(dev->of_node, "ddc", 0); + if (!ddc_node) { + DRM_ERROR("Failed to find ddc node in device tree\n"); + ret = -ENODEV; + goto err_del_component; } - hdata->ddc_port = hdmi_ddc; +out_get_ddc_adpt: + hdata->ddc_adpt = of_find_i2c_adapter_by_node(ddc_node); + if (!hdata->ddc_adpt) { + DRM_ERROR("Failed to get ddc i2c adapter by node\n"); + return -EPROBE_DEFER; + } + + phy_node = hdmi_legacy_phy_dt_binding(dev); + if (phy_node) + goto out_get_phy_port; /* hdmiphy i2c driver */ - if (i2c_add_driver(&hdmiphy_driver)) { - DRM_ERROR("failed to register hdmiphy i2c driver\n"); - ret = -ENOENT; + phy_node = of_parse_phandle(dev->of_node, "phy", 0); + if (!phy_node) { + DRM_ERROR("Failed to find hdmiphy node in device tree\n"); + ret = -ENODEV; goto err_ddc; } - hdata->hdmiphy_port = hdmi_hdmiphy; +out_get_phy_port: + if (drv_data->is_apb_phy) { + hdata->regs_hdmiphy = of_iomap(phy_node, 0); + if (!hdata->regs_hdmiphy) { + DRM_ERROR("failed to ioremap hdmi phy\n"); + ret = -ENOMEM; + goto err_ddc; + } + } else { + hdata->hdmiphy_port = of_find_i2c_device_by_node(phy_node); + if (!hdata->hdmiphy_port) { + DRM_ERROR("Failed to get hdmi phy i2c client\n"); + ret = -EPROBE_DEFER; + goto err_ddc; + } + } hdata->irq = gpio_to_irq(hdata->hpd_gpio); if (hdata->irq < 0) { @@ -1977,114 +2445,64 @@ static int hdmi_probe(struct platform_device *pdev) hdata->hpd = gpio_get_value(hdata->hpd_gpio); + INIT_DELAYED_WORK(&hdata->hotplug_work, hdmi_hotplug_work_func); + ret = devm_request_threaded_irq(dev, hdata->irq, NULL, hdmi_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - "hdmi", drm_hdmi_ctx); + "hdmi", hdata); if (ret) { DRM_ERROR("failed to register hdmi interrupt\n"); goto err_hdmiphy; } - /* Attach HDMI Driver to common hdmi. */ - exynos_hdmi_drv_attach(drm_hdmi_ctx); - - /* register specific callbacks to common hdmi. */ - exynos_hdmi_ops_register(&hdmi_ops); + hdata->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(hdata->pmureg)) { + DRM_ERROR("syscon regmap lookup failed.\n"); + ret = -EPROBE_DEFER; + goto err_hdmiphy; + } pm_runtime_enable(dev); + hdmi_display.ctx = hdata; - return 0; + ret = component_add(&pdev->dev, &hdmi_component_ops); + if (ret) + goto err_disable_pm_runtime; -err_hdmiphy: - i2c_del_driver(&hdmiphy_driver); -err_ddc: - i2c_del_driver(&ddc_driver); return ret; -} - -static int hdmi_remove(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; +err_disable_pm_runtime: pm_runtime_disable(dev); - /* hdmiphy i2c driver */ - i2c_del_driver(&hdmiphy_driver); - /* DDC i2c driver */ - i2c_del_driver(&ddc_driver); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int hdmi_suspend(struct device *dev) -{ - struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); - struct hdmi_context *hdata = ctx->ctx; - - disable_irq(hdata->irq); - - hdata->hpd = false; - if (ctx->drm_dev) - drm_helper_hpd_irq_event(ctx->drm_dev); - - if (pm_runtime_suspended(dev)) { - DRM_DEBUG_KMS("Already suspended\n"); - return 0; - } - - hdmi_poweroff(hdata); - - return 0; -} - -static int hdmi_resume(struct device *dev) -{ - struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); - struct hdmi_context *hdata = ctx->ctx; - - hdata->hpd = gpio_get_value(hdata->hpd_gpio); - - enable_irq(hdata->irq); - - if (!pm_runtime_suspended(dev)) { - DRM_DEBUG_KMS("Already resumed\n"); - return 0; - } +err_hdmiphy: + if (hdata->hdmiphy_port) + put_device(&hdata->hdmiphy_port->dev); +err_ddc: + put_device(&hdata->ddc_adpt->dev); - hdmi_poweron(hdata); +err_del_component: + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR); - return 0; + return ret; } -#endif -#ifdef CONFIG_PM_RUNTIME -static int hdmi_runtime_suspend(struct device *dev) +static int hdmi_remove(struct platform_device *pdev) { - struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); - struct hdmi_context *hdata = ctx->ctx; + struct hdmi_context *hdata = hdmi_display.ctx; - hdmi_poweroff(hdata); + cancel_delayed_work_sync(&hdata->hotplug_work); - return 0; -} - -static int hdmi_runtime_resume(struct device *dev) -{ - struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); - struct hdmi_context *hdata = ctx->ctx; + put_device(&hdata->hdmiphy_port->dev); + put_device(&hdata->ddc_adpt->dev); - hdmi_poweron(hdata); + pm_runtime_disable(&pdev->dev); + component_del(&pdev->dev, &hdmi_component_ops); + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CONNECTOR); return 0; } -#endif - -static const struct dev_pm_ops hdmi_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(hdmi_suspend, hdmi_resume) - SET_RUNTIME_PM_OPS(hdmi_runtime_suspend, hdmi_runtime_resume, NULL) -}; struct platform_driver hdmi_driver = { .probe = hdmi_probe, @@ -2092,7 +2510,6 @@ struct platform_driver hdmi_driver = { .driver = { .name = "exynos-hdmi", .owner = THIS_MODULE, - .pm = &hdmi_pm_ops, .of_match_table = hdmi_match_types, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.h b/drivers/gpu/drm/exynos/exynos_hdmi.h deleted file mode 100644 index 0ddf3957de1..00000000000 --- a/drivers/gpu/drm/exynos/exynos_hdmi.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * - * Copyright (c) 2011 Samsung Electronics Co., Ltd. - * Authors: - * Inki Dae <inki.dae@samsung.com> - * Seung-Woo Kim <sw0312.kim@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#ifndef _EXYNOS_HDMI_H_ -#define _EXYNOS_HDMI_H_ - -void hdmi_attach_ddc_client(struct i2c_client *ddc); -void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy); - -extern struct i2c_driver hdmiphy_driver; -extern struct i2c_driver ddc_driver; - -#endif diff --git a/drivers/gpu/drm/exynos/exynos_hdmiphy.c b/drivers/gpu/drm/exynos/exynos_hdmiphy.c deleted file mode 100644 index 59abb1494ce..00000000000 --- a/drivers/gpu/drm/exynos/exynos_hdmiphy.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2011 Samsung Electronics Co.Ltd - * Authors: - * Seung-Woo Kim <sw0312.kim@samsung.com> - * Inki Dae <inki.dae@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ - -#include <drm/drmP.h> - -#include <linux/kernel.h> -#include <linux/i2c.h> -#include <linux/of.h> - -#include "exynos_drm_drv.h" -#include "exynos_hdmi.h" - - -static int hdmiphy_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - hdmi_attach_hdmiphy_client(client); - - dev_info(&client->adapter->dev, "attached s5p_hdmiphy " - "into i2c adapter successfully\n"); - - return 0; -} - -static int hdmiphy_remove(struct i2c_client *client) -{ - dev_info(&client->adapter->dev, "detached s5p_hdmiphy " - "from i2c adapter successfully\n"); - - return 0; -} - -static struct of_device_id hdmiphy_match_types[] = { - { - .compatible = "samsung,exynos5-hdmiphy", - }, { - .compatible = "samsung,exynos4210-hdmiphy", - }, { - .compatible = "samsung,exynos4212-hdmiphy", - }, { - /* end node */ - } -}; - -struct i2c_driver hdmiphy_driver = { - .driver = { - .name = "exynos-hdmiphy", - .owner = THIS_MODULE, - .of_match_table = hdmiphy_match_types, - }, - .probe = hdmiphy_probe, - .remove = hdmiphy_remove, - .command = NULL, -}; -EXPORT_SYMBOL(hdmiphy_driver); diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c index 63bc5f92fbb..7529946d0a7 100644 --- a/drivers/gpu/drm/exynos/exynos_mixer.c +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -31,15 +31,19 @@ #include <linux/clk.h> #include <linux/regulator/consumer.h> #include <linux/of.h> +#include <linux/component.h> #include <drm/exynos_drm.h> #include "exynos_drm_drv.h" #include "exynos_drm_crtc.h" -#include "exynos_drm_hdmi.h" #include "exynos_drm_iommu.h" +#include "exynos_mixer.h" -#define get_mixer_context(dev) platform_get_drvdata(to_platform_device(dev)) +#define get_mixer_manager(dev) platform_get_drvdata(to_platform_device(dev)) + +#define MIXER_WIN_NR 3 +#define MIXER_DEFAULT_WIN 0 struct hdmi_win_data { dma_addr_t dma_addr; @@ -82,6 +86,7 @@ enum mixer_version_id { }; struct mixer_context { + struct platform_device *pdev; struct device *dev; struct drm_device *drm_dev; int pipe; @@ -94,7 +99,6 @@ struct mixer_context { struct mixer_resources mixer_res; struct hdmi_win_data win_data[MIXER_WIN_NR]; enum mixer_version_id mxr_ver; - void *parent_ctx; wait_queue_head_t wait_vsync_queue; atomic_t wait_vsync_event; }; @@ -373,6 +377,20 @@ static void mixer_run(struct mixer_context *ctx) mixer_regs_dump(ctx); } +static void mixer_stop(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + int timeout = 20; + + mixer_reg_writemask(res, MXR_STATUS, 0, MXR_STATUS_REG_RUN); + + while (!(mixer_reg_read(res, MXR_STATUS) & MXR_STATUS_REG_IDLE) && + --timeout) + usleep_range(10000, 12000); + + mixer_regs_dump(ctx); +} + static void vp_video_buffer(struct mixer_context *ctx, int win) { struct mixer_resources *res = &ctx->mixer_res; @@ -493,13 +511,8 @@ static void vp_video_buffer(struct mixer_context *ctx, int win) static void mixer_layer_update(struct mixer_context *ctx) { struct mixer_resources *res = &ctx->mixer_res; - u32 val; - - val = mixer_reg_read(res, MXR_CFG); - /* allow one update per vsync only */ - if (!(val & MXR_CFG_LAYER_UPDATE_COUNT_MASK)) - mixer_reg_writemask(res, MXR_CFG, ~0, MXR_CFG_LAYER_UPDATE); + mixer_reg_writemask(res, MXR_CFG, ~0, MXR_CFG_LAYER_UPDATE); } static void mixer_graph_buffer(struct mixer_context *ctx, int win) @@ -685,30 +698,197 @@ static void mixer_win_reset(struct mixer_context *ctx) spin_unlock_irqrestore(&res->reg_slock, flags); } -static int mixer_iommu_on(void *ctx, bool enable) +static irqreturn_t mixer_irq_handler(int irq, void *arg) +{ + struct mixer_context *ctx = arg; + struct mixer_resources *res = &ctx->mixer_res; + u32 val, base, shadow; + + spin_lock(&res->reg_slock); + + /* read interrupt status for handling and clearing flags for VSYNC */ + val = mixer_reg_read(res, MXR_INT_STATUS); + + /* handling VSYNC */ + if (val & MXR_INT_STATUS_VSYNC) { + /* interlace scan need to check shadow register */ + if (ctx->interlace) { + base = mixer_reg_read(res, MXR_GRAPHIC_BASE(0)); + shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0)); + if (base != shadow) + goto out; + + base = mixer_reg_read(res, MXR_GRAPHIC_BASE(1)); + shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1)); + if (base != shadow) + goto out; + } + + drm_handle_vblank(ctx->drm_dev, ctx->pipe); + exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); + + /* set wait vsync event to zero and wake up queue. */ + if (atomic_read(&ctx->wait_vsync_event)) { + atomic_set(&ctx->wait_vsync_event, 0); + wake_up(&ctx->wait_vsync_queue); + } + } + +out: + /* clear interrupts */ + if (~val & MXR_INT_EN_VSYNC) { + /* vsync interrupt use different bit for read and clear */ + val &= ~MXR_INT_EN_VSYNC; + val |= MXR_INT_CLEAR_VSYNC; + } + mixer_reg_write(res, MXR_INT_STATUS, val); + + spin_unlock(&res->reg_slock); + + return IRQ_HANDLED; +} + +static int mixer_resources_init(struct mixer_context *mixer_ctx) { - struct exynos_drm_hdmi_context *drm_hdmi_ctx; - struct mixer_context *mdata = ctx; - struct drm_device *drm_dev; + struct device *dev = &mixer_ctx->pdev->dev; + struct mixer_resources *mixer_res = &mixer_ctx->mixer_res; + struct resource *res; + int ret; + + spin_lock_init(&mixer_res->reg_slock); + + mixer_res->mixer = devm_clk_get(dev, "mixer"); + if (IS_ERR(mixer_res->mixer)) { + dev_err(dev, "failed to get clock 'mixer'\n"); + return -ENODEV; + } + + mixer_res->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi"); + if (IS_ERR(mixer_res->sclk_hdmi)) { + dev_err(dev, "failed to get clock 'sclk_hdmi'\n"); + return -ENODEV; + } + res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(dev, "get memory resource failed.\n"); + return -ENXIO; + } - drm_hdmi_ctx = mdata->parent_ctx; - drm_dev = drm_hdmi_ctx->drm_dev; + mixer_res->mixer_regs = devm_ioremap(dev, res->start, + resource_size(res)); + if (mixer_res->mixer_regs == NULL) { + dev_err(dev, "register mapping failed.\n"); + return -ENXIO; + } - if (is_drm_iommu_supported(drm_dev)) { - if (enable) - return drm_iommu_attach_device(drm_dev, mdata->dev); + res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(dev, "get interrupt resource failed.\n"); + return -ENXIO; + } - drm_iommu_detach_device(drm_dev, mdata->dev); + ret = devm_request_irq(dev, res->start, mixer_irq_handler, + 0, "drm_mixer", mixer_ctx); + if (ret) { + dev_err(dev, "request interrupt failed.\n"); + return ret; } + mixer_res->irq = res->start; + return 0; } -static int mixer_enable_vblank(void *ctx, int pipe) +static int vp_resources_init(struct mixer_context *mixer_ctx) { - struct mixer_context *mixer_ctx = ctx; + struct device *dev = &mixer_ctx->pdev->dev; + struct mixer_resources *mixer_res = &mixer_ctx->mixer_res; + struct resource *res; + + mixer_res->vp = devm_clk_get(dev, "vp"); + if (IS_ERR(mixer_res->vp)) { + dev_err(dev, "failed to get clock 'vp'\n"); + return -ENODEV; + } + mixer_res->sclk_mixer = devm_clk_get(dev, "sclk_mixer"); + if (IS_ERR(mixer_res->sclk_mixer)) { + dev_err(dev, "failed to get clock 'sclk_mixer'\n"); + return -ENODEV; + } + mixer_res->sclk_dac = devm_clk_get(dev, "sclk_dac"); + if (IS_ERR(mixer_res->sclk_dac)) { + dev_err(dev, "failed to get clock 'sclk_dac'\n"); + return -ENODEV; + } + + if (mixer_res->sclk_hdmi) + clk_set_parent(mixer_res->sclk_mixer, mixer_res->sclk_hdmi); + + res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 1); + if (res == NULL) { + dev_err(dev, "get memory resource failed.\n"); + return -ENXIO; + } + + mixer_res->vp_regs = devm_ioremap(dev, res->start, + resource_size(res)); + if (mixer_res->vp_regs == NULL) { + dev_err(dev, "register mapping failed.\n"); + return -ENXIO; + } + + return 0; +} + +static int mixer_initialize(struct exynos_drm_manager *mgr, + struct drm_device *drm_dev) +{ + int ret; + struct mixer_context *mixer_ctx = mgr->ctx; + struct exynos_drm_private *priv; + priv = drm_dev->dev_private; + + mgr->drm_dev = mixer_ctx->drm_dev = drm_dev; + mgr->pipe = mixer_ctx->pipe = priv->pipe++; + + /* acquire resources: regs, irqs, clocks */ + ret = mixer_resources_init(mixer_ctx); + if (ret) { + DRM_ERROR("mixer_resources_init failed ret=%d\n", ret); + return ret; + } + + if (mixer_ctx->vp_enabled) { + /* acquire vp resources: regs, irqs, clocks */ + ret = vp_resources_init(mixer_ctx); + if (ret) { + DRM_ERROR("vp_resources_init failed ret=%d\n", ret); + return ret; + } + } + + if (!is_drm_iommu_supported(mixer_ctx->drm_dev)) + return 0; + + return drm_iommu_attach_device(mixer_ctx->drm_dev, mixer_ctx->dev); +} + +static void mixer_mgr_remove(struct exynos_drm_manager *mgr) +{ + struct mixer_context *mixer_ctx = mgr->ctx; + + if (is_drm_iommu_supported(mixer_ctx->drm_dev)) + drm_iommu_detach_device(mixer_ctx->drm_dev, mixer_ctx->dev); +} + +static int mixer_enable_vblank(struct exynos_drm_manager *mgr) +{ + struct mixer_context *mixer_ctx = mgr->ctx; struct mixer_resources *res = &mixer_ctx->mixer_res; - mixer_ctx->pipe = pipe; + if (!mixer_ctx->powered) { + mixer_ctx->int_en |= MXR_INT_EN_VSYNC; + return 0; + } /* enable vsync interrupt */ mixer_reg_writemask(res, MXR_INT_EN, MXR_INT_EN_VSYNC, @@ -717,19 +897,19 @@ static int mixer_enable_vblank(void *ctx, int pipe) return 0; } -static void mixer_disable_vblank(void *ctx) +static void mixer_disable_vblank(struct exynos_drm_manager *mgr) { - struct mixer_context *mixer_ctx = ctx; + struct mixer_context *mixer_ctx = mgr->ctx; struct mixer_resources *res = &mixer_ctx->mixer_res; /* disable vsync interrupt */ mixer_reg_writemask(res, MXR_INT_EN, 0, MXR_INT_EN_VSYNC); } -static void mixer_win_mode_set(void *ctx, - struct exynos_drm_overlay *overlay) +static void mixer_win_mode_set(struct exynos_drm_manager *mgr, + struct exynos_drm_overlay *overlay) { - struct mixer_context *mixer_ctx = ctx; + struct mixer_context *mixer_ctx = mgr->ctx; struct hdmi_win_data *win_data; int win; @@ -778,9 +958,10 @@ static void mixer_win_mode_set(void *ctx, win_data->scan_flags = overlay->scan_flag; } -static void mixer_win_commit(void *ctx, int win) +static void mixer_win_commit(struct exynos_drm_manager *mgr, int zpos) { - struct mixer_context *mixer_ctx = ctx; + struct mixer_context *mixer_ctx = mgr->ctx; + int win = zpos == DEFAULT_ZPOS ? MIXER_DEFAULT_WIN : zpos; DRM_DEBUG_KMS("win: %d\n", win); @@ -799,10 +980,11 @@ static void mixer_win_commit(void *ctx, int win) mixer_ctx->win_data[win].enabled = true; } -static void mixer_win_disable(void *ctx, int win) +static void mixer_win_disable(struct exynos_drm_manager *mgr, int zpos) { - struct mixer_context *mixer_ctx = ctx; + struct mixer_context *mixer_ctx = mgr->ctx; struct mixer_resources *res = &mixer_ctx->mixer_res; + int win = zpos == DEFAULT_ZPOS ? MIXER_DEFAULT_WIN : zpos; unsigned long flags; DRM_DEBUG_KMS("win: %d\n", win); @@ -826,32 +1008,9 @@ static void mixer_win_disable(void *ctx, int win) mixer_ctx->win_data[win].enabled = false; } -static int mixer_check_mode(void *ctx, struct drm_display_mode *mode) +static void mixer_wait_for_vblank(struct exynos_drm_manager *mgr) { - struct mixer_context *mixer_ctx = ctx; - u32 w, h; - - w = mode->hdisplay; - h = mode->vdisplay; - - DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d\n", - mode->hdisplay, mode->vdisplay, mode->vrefresh, - (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0); - - if (mixer_ctx->mxr_ver == MXR_VER_0_0_0_16 || - mixer_ctx->mxr_ver == MXR_VER_128_0_0_184) - return 0; - - if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) || - (w >= 1024 && w <= 1280 && h >= 576 && h <= 720) || - (w >= 1664 && w <= 1920 && h >= 936 && h <= 1080)) - return 0; - - return -EINVAL; -} -static void mixer_wait_for_vblank(void *ctx) -{ - struct mixer_context *mixer_ctx = ctx; + struct mixer_context *mixer_ctx = mgr->ctx; mutex_lock(&mixer_ctx->mixer_mutex); if (!mixer_ctx->powered) { @@ -860,6 +1019,8 @@ static void mixer_wait_for_vblank(void *ctx) } mutex_unlock(&mixer_ctx->mixer_mutex); + drm_vblank_get(mgr->crtc->dev, mixer_ctx->pipe); + atomic_set(&mixer_ctx->wait_vsync_event, 1); /* @@ -868,25 +1029,29 @@ static void mixer_wait_for_vblank(void *ctx) */ if (!wait_event_timeout(mixer_ctx->wait_vsync_queue, !atomic_read(&mixer_ctx->wait_vsync_event), - DRM_HZ/20)) + HZ/20)) DRM_DEBUG_KMS("vblank wait timed out.\n"); + + drm_vblank_put(mgr->crtc->dev, mixer_ctx->pipe); } -static void mixer_window_suspend(struct mixer_context *ctx) +static void mixer_window_suspend(struct exynos_drm_manager *mgr) { + struct mixer_context *ctx = mgr->ctx; struct hdmi_win_data *win_data; int i; for (i = 0; i < MIXER_WIN_NR; i++) { win_data = &ctx->win_data[i]; win_data->resume = win_data->enabled; - mixer_win_disable(ctx, i); + mixer_win_disable(mgr, i); } - mixer_wait_for_vblank(ctx); + mixer_wait_for_vblank(mgr); } -static void mixer_window_resume(struct mixer_context *ctx) +static void mixer_window_resume(struct exynos_drm_manager *mgr) { + struct mixer_context *ctx = mgr->ctx; struct hdmi_win_data *win_data; int i; @@ -894,11 +1059,14 @@ static void mixer_window_resume(struct mixer_context *ctx) win_data = &ctx->win_data[i]; win_data->enabled = win_data->resume; win_data->resume = false; + if (win_data->enabled) + mixer_win_commit(mgr, i); } } -static void mixer_poweron(struct mixer_context *ctx) +static void mixer_poweron(struct exynos_drm_manager *mgr) { + struct mixer_context *ctx = mgr->ctx; struct mixer_resources *res = &ctx->mixer_res; mutex_lock(&ctx->mixer_mutex); @@ -906,61 +1074,69 @@ static void mixer_poweron(struct mixer_context *ctx) mutex_unlock(&ctx->mixer_mutex); return; } - ctx->powered = true; + mutex_unlock(&ctx->mixer_mutex); + pm_runtime_get_sync(ctx->dev); + clk_prepare_enable(res->mixer); if (ctx->vp_enabled) { clk_prepare_enable(res->vp); clk_prepare_enable(res->sclk_mixer); } + mutex_lock(&ctx->mixer_mutex); + ctx->powered = true; + mutex_unlock(&ctx->mixer_mutex); + + mixer_reg_writemask(res, MXR_STATUS, ~0, MXR_STATUS_SOFT_RESET); + mixer_reg_write(res, MXR_INT_EN, ctx->int_en); mixer_win_reset(ctx); - mixer_window_resume(ctx); + mixer_window_resume(mgr); } -static void mixer_poweroff(struct mixer_context *ctx) +static void mixer_poweroff(struct exynos_drm_manager *mgr) { + struct mixer_context *ctx = mgr->ctx; struct mixer_resources *res = &ctx->mixer_res; mutex_lock(&ctx->mixer_mutex); - if (!ctx->powered) - goto out; + if (!ctx->powered) { + mutex_unlock(&ctx->mixer_mutex); + return; + } mutex_unlock(&ctx->mixer_mutex); - mixer_window_suspend(ctx); + mixer_stop(ctx); + mixer_window_suspend(mgr); ctx->int_en = mixer_reg_read(res, MXR_INT_EN); + mutex_lock(&ctx->mixer_mutex); + ctx->powered = false; + mutex_unlock(&ctx->mixer_mutex); + clk_disable_unprepare(res->mixer); if (ctx->vp_enabled) { clk_disable_unprepare(res->vp); clk_disable_unprepare(res->sclk_mixer); } - mutex_lock(&ctx->mixer_mutex); - ctx->powered = false; - -out: - mutex_unlock(&ctx->mixer_mutex); + pm_runtime_put_sync(ctx->dev); } -static void mixer_dpms(void *ctx, int mode) +static void mixer_dpms(struct exynos_drm_manager *mgr, int mode) { - struct mixer_context *mixer_ctx = ctx; - switch (mode) { case DRM_MODE_DPMS_ON: - if (pm_runtime_suspended(mixer_ctx->dev)) - pm_runtime_get_sync(mixer_ctx->dev); + mixer_poweron(mgr); break; case DRM_MODE_DPMS_STANDBY: case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_OFF: - if (!pm_runtime_suspended(mixer_ctx->dev)) - pm_runtime_put_sync(mixer_ctx->dev); + mixer_poweroff(mgr); break; default: DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode); @@ -968,169 +1144,40 @@ static void mixer_dpms(void *ctx, int mode) } } -static struct exynos_mixer_ops mixer_ops = { - /* manager */ - .iommu_on = mixer_iommu_on, - .enable_vblank = mixer_enable_vblank, - .disable_vblank = mixer_disable_vblank, - .wait_for_vblank = mixer_wait_for_vblank, - .dpms = mixer_dpms, - - /* overlay */ - .win_mode_set = mixer_win_mode_set, - .win_commit = mixer_win_commit, - .win_disable = mixer_win_disable, - - /* display */ - .check_mode = mixer_check_mode, -}; - -static irqreturn_t mixer_irq_handler(int irq, void *arg) +/* Only valid for Mixer version 16.0.33.0 */ +int mixer_check_mode(struct drm_display_mode *mode) { - struct exynos_drm_hdmi_context *drm_hdmi_ctx = arg; - struct mixer_context *ctx = drm_hdmi_ctx->ctx; - struct mixer_resources *res = &ctx->mixer_res; - u32 val, base, shadow; - - spin_lock(&res->reg_slock); - - /* read interrupt status for handling and clearing flags for VSYNC */ - val = mixer_reg_read(res, MXR_INT_STATUS); - - /* handling VSYNC */ - if (val & MXR_INT_STATUS_VSYNC) { - /* interlace scan need to check shadow register */ - if (ctx->interlace) { - base = mixer_reg_read(res, MXR_GRAPHIC_BASE(0)); - shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0)); - if (base != shadow) - goto out; - - base = mixer_reg_read(res, MXR_GRAPHIC_BASE(1)); - shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1)); - if (base != shadow) - goto out; - } - - drm_handle_vblank(drm_hdmi_ctx->drm_dev, ctx->pipe); - exynos_drm_crtc_finish_pageflip(drm_hdmi_ctx->drm_dev, - ctx->pipe); - - /* set wait vsync event to zero and wake up queue. */ - if (atomic_read(&ctx->wait_vsync_event)) { - atomic_set(&ctx->wait_vsync_event, 0); - DRM_WAKEUP(&ctx->wait_vsync_queue); - } - } - -out: - /* clear interrupts */ - if (~val & MXR_INT_EN_VSYNC) { - /* vsync interrupt use different bit for read and clear */ - val &= ~MXR_INT_EN_VSYNC; - val |= MXR_INT_CLEAR_VSYNC; - } - mixer_reg_write(res, MXR_INT_STATUS, val); - - spin_unlock(&res->reg_slock); - - return IRQ_HANDLED; -} - -static int mixer_resources_init(struct exynos_drm_hdmi_context *ctx, - struct platform_device *pdev) -{ - struct mixer_context *mixer_ctx = ctx->ctx; - struct device *dev = &pdev->dev; - struct mixer_resources *mixer_res = &mixer_ctx->mixer_res; - struct resource *res; - int ret; - - spin_lock_init(&mixer_res->reg_slock); - - mixer_res->mixer = devm_clk_get(dev, "mixer"); - if (IS_ERR(mixer_res->mixer)) { - dev_err(dev, "failed to get clock 'mixer'\n"); - return -ENODEV; - } - - mixer_res->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi"); - if (IS_ERR(mixer_res->sclk_hdmi)) { - dev_err(dev, "failed to get clock 'sclk_hdmi'\n"); - return -ENODEV; - } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(dev, "get memory resource failed.\n"); - return -ENXIO; - } + u32 w, h; - mixer_res->mixer_regs = devm_ioremap(dev, res->start, - resource_size(res)); - if (mixer_res->mixer_regs == NULL) { - dev_err(dev, "register mapping failed.\n"); - return -ENXIO; - } + w = mode->hdisplay; + h = mode->vdisplay; - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (res == NULL) { - dev_err(dev, "get interrupt resource failed.\n"); - return -ENXIO; - } + DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d\n", + mode->hdisplay, mode->vdisplay, mode->vrefresh, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0); - ret = devm_request_irq(dev, res->start, mixer_irq_handler, - 0, "drm_mixer", ctx); - if (ret) { - dev_err(dev, "request interrupt failed.\n"); - return ret; - } - mixer_res->irq = res->start; + if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) || + (w >= 1024 && w <= 1280 && h >= 576 && h <= 720) || + (w >= 1664 && w <= 1920 && h >= 936 && h <= 1080)) + return 0; - return 0; + return -EINVAL; } -static int vp_resources_init(struct exynos_drm_hdmi_context *ctx, - struct platform_device *pdev) -{ - struct mixer_context *mixer_ctx = ctx->ctx; - struct device *dev = &pdev->dev; - struct mixer_resources *mixer_res = &mixer_ctx->mixer_res; - struct resource *res; - - mixer_res->vp = devm_clk_get(dev, "vp"); - if (IS_ERR(mixer_res->vp)) { - dev_err(dev, "failed to get clock 'vp'\n"); - return -ENODEV; - } - mixer_res->sclk_mixer = devm_clk_get(dev, "sclk_mixer"); - if (IS_ERR(mixer_res->sclk_mixer)) { - dev_err(dev, "failed to get clock 'sclk_mixer'\n"); - return -ENODEV; - } - mixer_res->sclk_dac = devm_clk_get(dev, "sclk_dac"); - if (IS_ERR(mixer_res->sclk_dac)) { - dev_err(dev, "failed to get clock 'sclk_dac'\n"); - return -ENODEV; - } - - if (mixer_res->sclk_hdmi) - clk_set_parent(mixer_res->sclk_mixer, mixer_res->sclk_hdmi); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (res == NULL) { - dev_err(dev, "get memory resource failed.\n"); - return -ENXIO; - } - - mixer_res->vp_regs = devm_ioremap(dev, res->start, - resource_size(res)); - if (mixer_res->vp_regs == NULL) { - dev_err(dev, "register mapping failed.\n"); - return -ENXIO; - } +static struct exynos_drm_manager_ops mixer_manager_ops = { + .dpms = mixer_dpms, + .enable_vblank = mixer_enable_vblank, + .disable_vblank = mixer_disable_vblank, + .wait_for_vblank = mixer_wait_for_vblank, + .win_mode_set = mixer_win_mode_set, + .win_commit = mixer_win_commit, + .win_disable = mixer_win_disable, +}; - return 0; -} +static struct exynos_drm_manager mixer_manager = { + .type = EXYNOS_DISPLAY_TYPE_HDMI, + .ops = &mixer_manager_ops, +}; static struct mixer_drv_data exynos5420_mxr_drv_data = { .version = MXR_VER_128_0_0_184, @@ -1174,24 +1221,21 @@ static struct of_device_id mixer_match_types[] = { } }; -static int mixer_probe(struct platform_device *pdev) +static int mixer_bind(struct device *dev, struct device *manager, void *data) { - struct device *dev = &pdev->dev; - struct exynos_drm_hdmi_context *drm_hdmi_ctx; + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm_dev = data; struct mixer_context *ctx; struct mixer_drv_data *drv; int ret; dev_info(dev, "probe start\n"); - drm_hdmi_ctx = devm_kzalloc(dev, sizeof(*drm_hdmi_ctx), - GFP_KERNEL); - if (!drm_hdmi_ctx) - return -ENOMEM; - - ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); - if (!ctx) + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + DRM_ERROR("failed to alloc mixer context.\n"); return -ENOMEM; + } mutex_init(&ctx->mixer_mutex); @@ -1204,121 +1248,77 @@ static int mixer_probe(struct platform_device *pdev) platform_get_device_id(pdev)->driver_data; } + ctx->pdev = pdev; ctx->dev = dev; - ctx->parent_ctx = (void *)drm_hdmi_ctx; - drm_hdmi_ctx->ctx = (void *)ctx; ctx->vp_enabled = drv->is_vp_enabled; ctx->mxr_ver = drv->version; - DRM_INIT_WAITQUEUE(&ctx->wait_vsync_queue); + init_waitqueue_head(&ctx->wait_vsync_queue); atomic_set(&ctx->wait_vsync_event, 0); - platform_set_drvdata(pdev, drm_hdmi_ctx); + mixer_manager.ctx = ctx; + ret = mixer_initialize(&mixer_manager, drm_dev); + if (ret) + return ret; - /* acquire resources: regs, irqs, clocks */ - ret = mixer_resources_init(drm_hdmi_ctx, pdev); + platform_set_drvdata(pdev, &mixer_manager); + ret = exynos_drm_crtc_create(&mixer_manager); if (ret) { - DRM_ERROR("mixer_resources_init failed\n"); - goto fail; - } - - if (ctx->vp_enabled) { - /* acquire vp resources: regs, irqs, clocks */ - ret = vp_resources_init(drm_hdmi_ctx, pdev); - if (ret) { - DRM_ERROR("vp_resources_init failed\n"); - goto fail; - } + mixer_mgr_remove(&mixer_manager); + return ret; } - /* attach mixer driver to common hdmi. */ - exynos_mixer_drv_attach(drm_hdmi_ctx); - - /* register specific callback point to common hdmi. */ - exynos_mixer_ops_register(&mixer_ops); - pm_runtime_enable(dev); return 0; - - -fail: - dev_info(dev, "probe failed\n"); - return ret; } -static int mixer_remove(struct platform_device *pdev) +static void mixer_unbind(struct device *dev, struct device *master, void *data) { - dev_info(&pdev->dev, "remove successful\n"); + struct exynos_drm_manager *mgr = dev_get_drvdata(dev); + struct drm_crtc *crtc = mgr->crtc; - pm_runtime_disable(&pdev->dev); + dev_info(dev, "remove successful\n"); - return 0; -} + mixer_mgr_remove(mgr); -#ifdef CONFIG_PM_SLEEP -static int mixer_suspend(struct device *dev) -{ - struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev); - struct mixer_context *ctx = drm_hdmi_ctx->ctx; + pm_runtime_disable(dev); - if (pm_runtime_suspended(dev)) { - DRM_DEBUG_KMS("Already suspended\n"); - return 0; - } - - mixer_poweroff(ctx); - - return 0; + crtc->funcs->destroy(crtc); } -static int mixer_resume(struct device *dev) -{ - struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev); - struct mixer_context *ctx = drm_hdmi_ctx->ctx; - - if (!pm_runtime_suspended(dev)) { - DRM_DEBUG_KMS("Already resumed\n"); - return 0; - } - - mixer_poweron(ctx); - - return 0; -} -#endif +static const struct component_ops mixer_component_ops = { + .bind = mixer_bind, + .unbind = mixer_unbind, +}; -#ifdef CONFIG_PM_RUNTIME -static int mixer_runtime_suspend(struct device *dev) +static int mixer_probe(struct platform_device *pdev) { - struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev); - struct mixer_context *ctx = drm_hdmi_ctx->ctx; + int ret; - mixer_poweroff(ctx); + ret = exynos_drm_component_add(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC, + mixer_manager.type); + if (ret) + return ret; - return 0; + ret = component_add(&pdev->dev, &mixer_component_ops); + if (ret) + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC); + + return ret; } -static int mixer_runtime_resume(struct device *dev) +static int mixer_remove(struct platform_device *pdev) { - struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev); - struct mixer_context *ctx = drm_hdmi_ctx->ctx; - - mixer_poweron(ctx); + component_del(&pdev->dev, &mixer_component_ops); + exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC); return 0; } -#endif - -static const struct dev_pm_ops mixer_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(mixer_suspend, mixer_resume) - SET_RUNTIME_PM_OPS(mixer_runtime_suspend, mixer_runtime_resume, NULL) -}; struct platform_driver mixer_driver = { .driver = { .name = "exynos-mixer", .owner = THIS_MODULE, - .pm = &mixer_pm_ops, .of_match_table = mixer_match_types, }, .probe = mixer_probe, diff --git a/drivers/gpu/drm/exynos/exynos_mixer.h b/drivers/gpu/drm/exynos/exynos_mixer.h new file mode 100644 index 00000000000..3811e417f0e --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_mixer.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _EXYNOS_MIXER_H_ +#define _EXYNOS_MIXER_H_ + +/* This function returns 0 if the given timing is valid for the mixer */ +int mixer_check_mode(struct drm_display_mode *mode); + +#endif diff --git a/drivers/gpu/drm/exynos/regs-hdmi.h b/drivers/gpu/drm/exynos/regs-hdmi.h index ef1b3eb3ba6..3f35ac6d8a4 100644 --- a/drivers/gpu/drm/exynos/regs-hdmi.h +++ b/drivers/gpu/drm/exynos/regs-hdmi.h @@ -578,4 +578,20 @@ #define HDMI_TG_VACT_ST4_H HDMI_TG_BASE(0x0074) #define HDMI_TG_3D HDMI_TG_BASE(0x00F0) +/* HDMI PHY Registers Offsets*/ +#define HDMIPHY_POWER (0x74 >> 2) +#define HDMIPHY_MODE_SET_DONE (0x7c >> 2) + +/* HDMI PHY Values */ +#define HDMI_PHY_POWER_ON 0x80 +#define HDMI_PHY_POWER_OFF 0xff + +/* HDMI PHY Values */ +#define HDMI_PHY_DISABLE_MODE_SET 0x80 +#define HDMI_PHY_ENABLE_MODE_SET 0x00 + +/* PMU Registers for PHY */ +#define PMU_HDMI_PHY_CONTROL 0x700 +#define PMU_HDMI_PHY_ENABLE_BIT BIT(0) + #endif /* SAMSUNG_REGS_HDMI_H */ diff --git a/drivers/gpu/drm/exynos/regs-mixer.h b/drivers/gpu/drm/exynos/regs-mixer.h index 4537026bc38..5f32e1a2941 100644 --- a/drivers/gpu/drm/exynos/regs-mixer.h +++ b/drivers/gpu/drm/exynos/regs-mixer.h @@ -78,6 +78,7 @@ #define MXR_STATUS_BIG_ENDIAN (1 << 3) #define MXR_STATUS_ENDIAN_MASK (1 << 3) #define MXR_STATUS_SYNC_ENABLE (1 << 2) +#define MXR_STATUS_REG_IDLE (1 << 1) #define MXR_STATUS_REG_RUN (1 << 0) /* bits for MXR_CFG */ |
