On Mon, Jan 07, 2019 at 12:51:10PM -0800, Chandan Uddaraju wrote: > Add the needed displayPort files to enable DP driver > on msm target. > > "dp_display" module is the main module that calls into > other sub-modules. "dp_drm" file represents the interface > between DRM framework and DP driver. > > changes in v2: > -- Update copyright markings on all relevant files. > -- Change pr_err() to DRM_ERROR() > -- Use APIs directly instead of function pointers. > -- Use drm_display_mode structure to store link parameters in the driver. > -- Use macros for register definitions instead of hardcoded values. > -- Replace writel_relaxed/readl_relaxed with writel/readl > and remove memory barriers. > -- Remove unnecessary NULL checks. > -- Use drm helper functions for dpcd read/write. > -- Use DRM_DEBUG_DP for debug msgs. > > Signed-off-by: Chandan Uddaraju <chandanu@xxxxxxxxxxxxxx> > --- > drivers/gpu/drm/msm/Kconfig | 9 + > drivers/gpu/drm/msm/Makefile | 12 + > drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 20 + > drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c | 2 + > drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h | 26 + > drivers/gpu/drm/msm/dp/dp_aux.c | 548 +++++++++ > drivers/gpu/drm/msm/dp/dp_aux.h | 37 + > drivers/gpu/drm/msm/dp/dp_catalog.c | 996 +++++++++++++++++ > drivers/gpu/drm/msm/dp/dp_catalog.h | 108 ++ > drivers/gpu/drm/msm/dp/dp_ctrl.c | 1611 +++++++++++++++++++++++++++ > drivers/gpu/drm/msm/dp/dp_ctrl.h | 36 + > drivers/gpu/drm/msm/dp/dp_display.c | 941 ++++++++++++++++ > drivers/gpu/drm/msm/dp/dp_display.h | 28 + > drivers/gpu/drm/msm/dp/dp_drm.c | 189 ++++ > drivers/gpu/drm/msm/dp/dp_drm.h | 20 + > drivers/gpu/drm/msm/dp/dp_extcon.c | 228 ++++ > drivers/gpu/drm/msm/dp/dp_extcon.h | 85 ++ > drivers/gpu/drm/msm/dp/dp_link.c | 1216 ++++++++++++++++++++ > drivers/gpu/drm/msm/dp/dp_link.h | 132 +++ > drivers/gpu/drm/msm/dp/dp_panel.c | 504 +++++++++ > drivers/gpu/drm/msm/dp/dp_panel.h | 91 ++ > drivers/gpu/drm/msm/dp/dp_parser.c | 621 +++++++++++ > drivers/gpu/drm/msm/dp/dp_parser.h | 196 ++++ > drivers/gpu/drm/msm/dp/dp_power.c | 733 ++++++++++++ > drivers/gpu/drm/msm/dp/dp_power.h | 51 + > drivers/gpu/drm/msm/dp/dp_reg.h | 475 ++++++++ > drivers/gpu/drm/msm/msm_drv.c | 2 + > drivers/gpu/drm/msm/msm_drv.h | 41 + > 28 files changed, 8958 insertions(+) > create mode 100644 drivers/gpu/drm/msm/dp/dp_aux.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_aux.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_catalog.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_catalog.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_ctrl.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_ctrl.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_display.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_display.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_drm.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_drm.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_extcon.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_extcon.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_link.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_link.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_panel.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_panel.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_parser.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_parser.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_power.c > create mode 100644 drivers/gpu/drm/msm/dp/dp_power.h > create mode 100644 drivers/gpu/drm/msm/dp/dp_reg.h > > diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig > index cf549f1..c6b2e07 100644 > --- a/drivers/gpu/drm/msm/Kconfig > +++ b/drivers/gpu/drm/msm/Kconfig > @@ -49,6 +49,15 @@ config DRM_MSM_HDMI_HDCP > help > Choose this option to enable HDCP state machine > > +config DRM_MSM_DP > + bool "Enable DP support in MSM DRM driver" > + depends on DRM_MSM > + default n default 'n' is the default so you shouldn't need this. > + help > + Compile in support for DP driver in msm drm driver. DP external > + display support is enabled through this config option. It can > + be primary or secondary display on device. > + > config DRM_MSM_DSI > bool "Enable DSI support in MSM DRM driver" > depends on DRM_MSM > diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile > index 56a70c7..45166ef 100644 > --- a/drivers/gpu/drm/msm/Makefile > +++ b/drivers/gpu/drm/msm/Makefile > @@ -2,6 +2,7 @@ > ccflags-y := -Idrivers/gpu/drm/msm > ccflags-y += -Idrivers/gpu/drm/msm/disp/dpu1 > ccflags-$(CONFIG_DRM_MSM_DSI) += -Idrivers/gpu/drm/msm/dsi > +ccflags-$(CONFIG_DRM_MSM_DP) += -Idrivers/gpu/drm/msm/dp > > msm-y := \ > adreno/adreno_device.o \ > @@ -96,6 +97,17 @@ msm-y := \ > > msm-$(CONFIG_DEBUG_FS) += adreno/a5xx_debugfs.o > > +msm-$(CONFIG_DRM_MSM_DP)+= dp/dp_aux.o \ > + dp/dp_catalog.o \ > + dp/dp_ctrl.o \ > + dp/dp_display.o \ > + dp/dp_drm.o \ > + dp/dp_extcon.o \ > + dp/dp_link.o \ > + dp/dp_panel.o \ > + dp/dp_parser.o \ > + dp/dp_power.o > + > msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o > msm-$(CONFIG_COMMON_CLK) += disp/mdp4/mdp4_lvds_pll.o > msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_pll_8960.o > diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c > index 0dda4a6..e6f81a1 100644 > --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c > +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c > @@ -991,6 +991,9 @@ static void dpu_encoder_virt_mode_set(struct drm_encoder *drm_enc, > > trace_dpu_enc_mode_set(DRMID(drm_enc)); > > + if (drm_enc->encoder_type == DRM_MODE_ENCODER_TMDS && priv->dp) > + msm_dp_display_mode_set(priv->dp, drm_enc, mode, adj_mode); > + > list_for_each_entry(conn_iter, connector_list, head) > if (conn_iter->encoder == drm_enc) > conn = conn_iter; > @@ -1114,6 +1117,7 @@ static void dpu_encoder_virt_enable(struct drm_encoder *drm_enc) > { > struct dpu_encoder_virt *dpu_enc = NULL; > int ret = 0; > + struct msm_drm_private *priv; > struct drm_display_mode *cur_mode = NULL; > > if (!drm_enc) { > @@ -1124,6 +1128,7 @@ static void dpu_encoder_virt_enable(struct drm_encoder *drm_enc) > > mutex_lock(&dpu_enc->enc_lock); > cur_mode = &dpu_enc->base.crtc->state->adjusted_mode; > + priv = drm_enc->dev->dev_private; > > trace_dpu_enc_enable(DRMID(drm_enc), cur_mode->hdisplay, > cur_mode->vdisplay); > @@ -1144,6 +1149,15 @@ static void dpu_encoder_virt_enable(struct drm_encoder *drm_enc) > > _dpu_encoder_virt_enable_helper(drm_enc); > > + if (drm_enc->encoder_type == DRM_MODE_ENCODER_TMDS && priv->dp) { > + ret = msm_dp_display_enable(priv->dp, > + drm_enc); > + if (ret) { > + DPU_ERROR_ENC(dpu_enc, "dp display enable failed: %d\n", > + ret); > + goto out; > + } > + } > dpu_enc->enabled = true; > > out: > @@ -1180,6 +1194,12 @@ static void dpu_encoder_virt_disable(struct drm_encoder *drm_enc) > priv = drm_enc->dev->dev_private; > dpu_kms = to_dpu_kms(priv->kms); > > + if (drm_enc->encoder_type == DRM_MODE_ENCODER_TMDS && priv->dp) { > + if (msm_dp_display_disable(priv->dp, drm_enc)) { > + DPU_ERROR_ENC(dpu_enc, "dp display disable failed\n"); > + return; > + } > + } > trace_dpu_enc_disable(DRMID(drm_enc)); > > /* wait for idle */ > diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c > index 78833c2..b4b1db0 100644 > --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c > +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c > @@ -14,7 +14,9 @@ > #include <linux/clk.h> > #include <linux/clk/clk-conf.h> > #include <linux/err.h> > +#include <linux/io.h> > #include <linux/delay.h> > +#include <drm/drmP.h> What are these for? There aren't any other changes to the file. The second one especially shouldn't be needed for this particular file. > > #include <drm/drm_print.h> > > diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h > index bc07381..a1d3e05 100644 > --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h > +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h > @@ -15,6 +15,7 @@ > > #include <linux/gpio.h> > #include <linux/platform_device.h> > +#include <linux/regulator/consumer.h> > #include <linux/types.h> > > #define DEV_DBG(fmt, args...) pr_debug(fmt, ##args) > @@ -22,6 +23,29 @@ > #define DEV_WARN(fmt, args...) pr_warn(fmt, ##args) > #define DEV_ERR(fmt, args...) pr_err(fmt, ##args) > > +struct dss_io_data { > + u32 len; > + void __iomem *base; > +}; > + > +enum dss_vreg_type { > + DSS_REG_LDO, > + DSS_REG_VS, > +}; > + > +struct dss_vreg { > + struct regulator *vreg; /* vreg handle */ > + char vreg_name[32]; > + int min_voltage; > + int max_voltage; > + int enable_load; > + int disable_load; > + int pre_on_sleep; > + int post_on_sleep; > + int pre_off_sleep; > + int post_off_sleep; > +}; > + > struct dss_gpio { > unsigned int gpio; > unsigned int value; > @@ -42,6 +66,8 @@ struct dss_clk { > }; > > struct dss_module_power { > + unsigned int num_vreg; > + struct dss_vreg *vreg_config; > unsigned int num_gpio; > struct dss_gpio *gpio_config; > unsigned int num_clk; > diff --git a/drivers/gpu/drm/msm/dp/dp_aux.c b/drivers/gpu/drm/msm/dp/dp_aux.c > new file mode 100644 > index 0000000..ef0be10 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_aux.c > @@ -0,0 +1,548 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include <linux/delay.h> > + > +#include "dp_aux.h" > + > +#define DP_AUX_ENUM_STR(x) #x > + > +enum { > + DP_AUX_DATA_INDEX_WRITE = BIT(31), > +}; An anonymous enum of 1 isn't useful, especially when it is for an obvious bitmask. This would do better as a #define > + > +struct dp_aux_private { > + struct device *dev; > + struct dp_aux dp_aux; > + struct dp_catalog_aux *catalog; > + > + struct mutex mutex; > + struct completion comp; > + > + u32 aux_error_num; > + u32 retry_cnt; > + bool cmd_busy; > + bool native; > + bool read; > + bool no_send_addr; > + bool no_send_stop; > + u32 offset; > + u32 segment; > + > + struct drm_dp_aux drm_aux; > +}; > + > +static char *dp_aux_get_error(u32 aux_error) > +{ > + switch (aux_error) { > + case DP_AUX_ERR_NONE: > + return DP_AUX_ENUM_STR(DP_AUX_ERR_NONE); > + case DP_AUX_ERR_ADDR: > + return DP_AUX_ENUM_STR(DP_AUX_ERR_ADDR); > + case DP_AUX_ERR_TOUT: > + return DP_AUX_ENUM_STR(DP_AUX_ERR_TOUT); > + case DP_AUX_ERR_NACK: > + return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK); > + case DP_AUX_ERR_DEFER: > + return DP_AUX_ENUM_STR(DP_AUX_ERR_DEFER); > + case DP_AUX_ERR_NACK_DEFER: > + return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK_DEFER); > + default: > + return "unknown"; >From the rest of the code context, it seems like this isn't possible? You always set the error to a known value. > + } > +} This function goes out of its way to try to use stringification which seems unnecessary and doesn't save you any keystrokes. return "DP_AUX_ERR_NONE" works just as well; > + > +static u32 dp_aux_write(struct dp_aux_private *aux, > + struct drm_dp_aux_msg *msg) > +{ > + u32 data[4], reg, len; > + u8 *msgdata = msg->buffer; > + int const aux_cmd_fifo_len = 128; Please don't use const registers. If you have to use a #define but its also okay to use constant numbers for things like this. > + int i = 0; > + > + if (aux->read) > + len = 4; > + else > + len = msg->size + 4; > + > + /* > + * cmd fifo only has depth of 144 bytes > + * limit buf length to 128 bytes here > + */ > + if (len > aux_cmd_fifo_len) { Especially here, where you specifically mention that you are limiting to 128 bytes. It is a lot easier to read if (len > 128) than it is to go back and figure out what aux_cmd_fifo_len is. > + DRM_ERROR("buf len error\n"); Perhaps more information is useful here if this is an expected runtime issue. If this is just a potential developer mistake a WARN_ON() would be better than a custom string. > + return 0; > + } > + > + /* Pack cmd and write to HW */ > + data[0] = (msg->address >> 16) & 0xf; /* addr[19:16] */ > + if (aux->read) > + data[0] |= BIT(4); /* R/W */ > + > + data[1] = (msg->address >> 8) & 0xff; /* addr[15:8] */ > + data[2] = msg->address & 0xff; /* addr[7:0] */ > + data[3] = (msg->size - 1) & 0xff; /* len[7:0] */ > + > + for (i = 0; i < len; i++) { > + reg = (i < 4) ? data[i] : msgdata[i - 4]; > + reg = ((reg) << 8) & 0x0000ff00; /* index = 0, write */ > + if (i == 0) > + reg |= DP_AUX_DATA_INDEX_WRITE; > + aux->catalog->data = reg; > + dp_catalog_aux_write_data(aux->catalog); > + } > + > + dp_catalog_aux_clear_trans(aux->catalog, false); > + > + reg = 0; /* Transaction number == 1 */ > + if (!aux->native) { /* i2c */ > + reg |= BIT(8); > + > + if (aux->no_send_addr) > + reg |= BIT(10); > + > + if (aux->no_send_stop) > + reg |= BIT(11); > + } > + > + reg |= BIT(9); > + aux->catalog->data = reg; > + dp_catalog_aux_write_trans(aux->catalog); > + > + return len; > +} > + > +static int dp_aux_cmd_fifo_tx(struct dp_aux_private *aux, > + struct drm_dp_aux_msg *msg) > +{ > + u32 ret = 0, len = 0, timeout; > + int const aux_timeout_ms = HZ/4; > + > + reinit_completion(&aux->comp); > + > + len = dp_aux_write(aux, msg); > + if (len == 0) { > + DRM_ERROR("DP AUX write failed\n"); There is only one way for dp_aux_write() to fail, and that path already prints a log message. Another one is not needed. > + return -EINVAL; > + } > + > + timeout = wait_for_completion_timeout(&aux->comp, aux_timeout_ms); > + if (!timeout) { > + DRM_ERROR("aux %s timeout\n", (aux->read ? "read" : "write")); > + return -ETIMEDOUT; > + } > + > + if (aux->aux_error_num == DP_AUX_ERR_NONE) { > + ret = len; > + } else { > + DRM_ERROR_RATELIMITED("aux err: %s\n", > + dp_aux_get_error(aux->aux_error_num)); > + > + ret = -EINVAL; > + } > + > + return ret; > +} > + > +static void dp_aux_cmd_fifo_rx(struct dp_aux_private *aux, > + struct drm_dp_aux_msg *msg) > +{ > + u32 data; > + u8 *dp; > + u32 i, actual_i; > + u32 len = msg->size; > + > + dp_catalog_aux_clear_trans(aux->catalog, true); > + > + data = 0; > + data |= DP_AUX_DATA_INDEX_WRITE; /* INDEX_WRITE */ > + data |= BIT(0); /* read */ > + > + aux->catalog->data = data; > + dp_catalog_aux_write_data(aux->catalog); > + > + dp = msg->buffer; > + > + /* discard first byte */ > + data = dp_catalog_aux_read_data(aux->catalog); > + > + for (i = 0; i < len; i++) { > + data = dp_catalog_aux_read_data(aux->catalog); > + *dp++ = (u8)((data >> 8) & 0xff); > + > + actual_i = (data >> 16) & 0xFF; > + if (i != actual_i) > + pr_warn("Index mismatch: expected %d, found %d\n", > + i, actual_i); This should match the other DRM_ macros. A more informative error message would be nice - I'm not quite sure from the code context what a index mismatch is or how it could possibly happen. > + } > +} > + > +static void dp_aux_native_handler(struct dp_aux_private *aux) > +{ > + u32 isr = aux->catalog->isr; > + > + if (isr & DP_INTR_AUX_I2C_DONE) > + aux->aux_error_num = DP_AUX_ERR_NONE; > + else if (isr & DP_INTR_WRONG_ADDR) > + aux->aux_error_num = DP_AUX_ERR_ADDR; > + else if (isr & DP_INTR_TIMEOUT) > + aux->aux_error_num = DP_AUX_ERR_TOUT; > + if (isr & DP_INTR_NACK_DEFER) > + aux->aux_error_num = DP_AUX_ERR_NACK; > + > + complete(&aux->comp); > +} > + > +static void dp_aux_i2c_handler(struct dp_aux_private *aux) > +{ > + u32 isr = aux->catalog->isr; > + > + if (isr & DP_INTR_AUX_I2C_DONE) { > + if (isr & (DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER)) > + aux->aux_error_num = DP_AUX_ERR_NACK; > + else > + aux->aux_error_num = DP_AUX_ERR_NONE; > + } else { > + if (isr & DP_INTR_WRONG_ADDR) > + aux->aux_error_num = DP_AUX_ERR_ADDR; > + else if (isr & DP_INTR_TIMEOUT) > + aux->aux_error_num = DP_AUX_ERR_TOUT; > + if (isr & DP_INTR_NACK_DEFER) > + aux->aux_error_num = DP_AUX_ERR_NACK_DEFER; > + if (isr & DP_INTR_I2C_NACK) > + aux->aux_error_num = DP_AUX_ERR_NACK; > + if (isr & DP_INTR_I2C_DEFER) > + aux->aux_error_num = DP_AUX_ERR_DEFER; > + } > + > + complete(&aux->comp); > +} > + > +static void dp_aux_update_offset_and_segment(struct dp_aux_private *aux, > + struct drm_dp_aux_msg *input_msg) > +{ > + u32 const edid_address = 0x50; > + u32 const segment_address = 0x30; As above, prefer if you used #define or inline constants rather than const here and throughout this code. > + bool i2c_read = input_msg->request & > + (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); > + u8 *data = NULL; > + > + if (aux->native || i2c_read || ((input_msg->address != edid_address) && > + (input_msg->address != segment_address))) > + return; > + > + > + data = input_msg->buffer; > + if (input_msg->address == segment_address) > + aux->segment = *data; > + else > + aux->offset = *data; > +} > + > +/** > + * dp_aux_transfer_helper() - helper function for EDID read transactions > + * > + * @aux: DP AUX private structure > + * @input_msg: input message from DRM upstream APIs > + * > + * return: void > + * > + * This helper function is used to fix EDID reads for non-compliant > + * sinks that do not handle the i2c middle-of-transaction flag correctly. > + */ > +static void dp_aux_transfer_helper(struct dp_aux_private *aux, > + struct drm_dp_aux_msg *input_msg) > +{ > + struct drm_dp_aux_msg helper_msg; > + u32 const message_size = 0x10; > + u32 const segment_address = 0x30; > + bool i2c_mot = input_msg->request & DP_AUX_I2C_MOT; > + bool i2c_read = input_msg->request & > + (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); > + > + if (!i2c_mot || !i2c_read || (input_msg->size == 0)) > + return; > + > + aux->read = false; > + aux->cmd_busy = true; > + aux->no_send_addr = true; > + aux->no_send_stop = true; > + > + /* > + * Send the segment address for every i2c read in which the > + * middle-of-tranaction flag is set. This is required to support EDID > + * reads of more than 2 blocks as the segment address is reset to 0 > + * since we are overriding the middle-of-transaction flag for read > + * transactions. > + */ > + memset(&helper_msg, 0, sizeof(helper_msg)); > + helper_msg.address = segment_address; > + helper_msg.buffer = &aux->segment; > + helper_msg.size = 1; > + dp_aux_cmd_fifo_tx(aux, &helper_msg); > + > + /* > + * Send the offset address for every i2c read in which the > + * middle-of-transaction flag is set. This will ensure that the sink > + * will update its read pointer and return the correct portion of the > + * EDID buffer in the subsequent i2c read trasntion triggered in the > + * native AUX transfer function. > + */ > + memset(&helper_msg, 0, sizeof(helper_msg)); > + helper_msg.address = input_msg->address; > + helper_msg.buffer = &aux->offset; > + helper_msg.size = 1; > + dp_aux_cmd_fifo_tx(aux, &helper_msg); > + aux->offset += message_size; > + > + if (aux->offset == 0x80 || aux->offset == 0x100) > + aux->segment = 0x0; /* reset segment at end of block */ > +} > + > +/* > + * This function does the real job to process an AUX transaction. > + * It will call aux_reset() function to reset the AUX channel, > + * if the waiting is timeout. > + */ > +static ssize_t dp_aux_transfer(struct drm_dp_aux *drm_aux, > + struct drm_dp_aux_msg *msg) > +{ > + ssize_t ret; > + int const aux_cmd_native_max = 16; > + int const aux_cmd_i2c_max = 128; > + int const retry_count = 5; > + struct dp_aux_private *aux = container_of(drm_aux, > + struct dp_aux_private, drm_aux); > + > + mutex_lock(&aux->mutex); > + > + aux->native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ); > + > + /* Ignore address only message */ > + if ((msg->size == 0) || (msg->buffer == NULL)) { > + msg->reply = aux->native ? > + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; > + ret = msg->size; > + goto unlock_exit; > + } > + > + /* msg sanity check */ > + if ((aux->native && (msg->size > aux_cmd_native_max)) || > + (msg->size > aux_cmd_i2c_max)) { > + DRM_ERROR("%s: invalid msg: size(%zu), request(%x)\n", > + __func__, msg->size, msg->request); > + ret = -EINVAL; > + goto unlock_exit; > + } > + > + dp_aux_update_offset_and_segment(aux, msg); > + dp_aux_transfer_helper(aux, msg); > + > + aux->read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); > + aux->cmd_busy = true; > + > + if (aux->read) { > + aux->no_send_addr = true; > + aux->no_send_stop = false; > + } else { > + aux->no_send_addr = true; > + aux->no_send_stop = true; > + } > + > + ret = dp_aux_cmd_fifo_tx(aux, msg); > + if ((ret < 0) && aux->native) { > + aux->retry_cnt++; > + if (!(aux->retry_cnt % retry_count)) > + dp_catalog_aux_update_cfg(aux->catalog, > + aux->dp_aux.cfg, PHY_AUX_CFG1); > + dp_catalog_aux_reset(aux->catalog); > + goto unlock_exit; > + } else if (ret < 0) { > + goto unlock_exit; > + } > + > + if (aux->aux_error_num == DP_AUX_ERR_NONE) { > + if (aux->read) > + dp_aux_cmd_fifo_rx(aux, msg); > + > + msg->reply = aux->native ? > + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; > + } else { > + /* Reply defer to retry */ > + msg->reply = aux->native ? > + DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER; > + } > + > + /* Return requested size for success or retry */ > + ret = msg->size; > + aux->retry_cnt = 0; > + > +unlock_exit: > + aux->cmd_busy = false; > + mutex_unlock(&aux->mutex); > + return ret; > +} > + > +static void dp_aux_reset_phy_config_indices(struct dp_aux_cfg *aux_cfg) > +{ > + int i = 0; > + > + for (i = 0; i < PHY_AUX_CFG_MAX; i++) > + aux_cfg[i].current_index = 0; > +} > + > +void dp_aux_isr(struct dp_aux *dp_aux) > +{ > + struct dp_aux_private *aux; > + > + if (!dp_aux) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); > + > + dp_catalog_aux_get_irq(aux->catalog, aux->cmd_busy); > + > + if (!aux->cmd_busy) > + return; > + > + if (aux->native) > + dp_aux_native_handler(aux); > + else > + dp_aux_i2c_handler(aux); > +} > + > +void dp_aux_reconfig(struct dp_aux *dp_aux) > +{ > + struct dp_aux_private *aux; > + > + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); > + > + dp_catalog_aux_update_cfg(aux->catalog, > + dp_aux->cfg, PHY_AUX_CFG1); > + dp_catalog_aux_reset(aux->catalog); > +} > + > +void dp_aux_init(struct dp_aux *dp_aux, struct dp_aux_cfg *aux_cfg) > +{ > + struct dp_aux_private *aux; > + > + if (!dp_aux || !aux_cfg) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); > + > + dp_aux_reset_phy_config_indices(aux_cfg); > + dp_catalog_aux_setup(aux->catalog, aux_cfg); > + dp_catalog_aux_reset(aux->catalog); > + dp_catalog_aux_enable(aux->catalog, true); > + aux->retry_cnt = 0; > +} > + > +void dp_aux_deinit(struct dp_aux *dp_aux) > +{ > + struct dp_aux_private *aux; > + > + if (!dp_aux) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); > + > + dp_catalog_aux_enable(aux->catalog, false); > +} > + > +int dp_aux_register(struct dp_aux *dp_aux) > +{ > + struct dp_aux_private *aux; > + int ret = 0; > + > + if (!dp_aux) { > + DRM_ERROR("invalid input\n"); This can just be return -EINVAL; > + ret = -EINVAL; > + goto exit; > + } > + > + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); > + > + aux->drm_aux.name = "dpu_dp_aux"; > + aux->drm_aux.dev = aux->dev; > + aux->drm_aux.transfer = dp_aux_transfer; > + ret = drm_dp_aux_register(&aux->drm_aux); > + if (ret) { > + DRM_ERROR("%s: failed to register drm aux: %d\n", __func__, ret); > + goto exit; This can just be return ret; > + } > + dp_aux->drm_aux = &aux->drm_aux; > +exit: > + return ret; The label can go away and this will be just return 0; > +} > + > +void dp_aux_unregister(struct dp_aux *dp_aux) > +{ > + struct dp_aux_private *aux; > + > + if (!dp_aux) { > + DRM_ERROR("invalid input\n"); A error message isn't needed here, you can just quietly return. > + return; > + } > + > + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); > + drm_dp_aux_unregister(&aux->drm_aux); > +} > + > +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, > + struct dp_aux_cfg *aux_cfg) > +{ > + int rc = 0; > + struct dp_aux_private *aux; > + struct dp_aux *dp_aux; > + > + if (!catalog || !aux_cfg) { > + DRM_ERROR("invalid input\n"); This can just be return ERR_PTR(-ENODEV); > + rc = -ENODEV; > + goto error; > + } > + > + aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL); > + if (!aux) { This can just be return ERR_PTR(-ENOMEM); > + rc = -ENOMEM; > + goto error; > + } > + > + init_completion(&aux->comp); > + aux->cmd_busy = false; > + mutex_init(&aux->mutex); > + > + aux->dev = dev; > + aux->catalog = catalog; > + aux->dp_aux.cfg = aux_cfg; > + dp_aux = &aux->dp_aux; > + aux->retry_cnt = 0; > + > + return dp_aux; > +error: > + return ERR_PTR(rc); Label is not needed > +} > + > +void dp_aux_put(struct dp_aux *dp_aux) > +{ > + struct dp_aux_private *aux; > + > + if (!dp_aux) > + return; > + > + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); > + > + mutex_destroy(&aux->mutex); You shouldn't need this if the memory is being freed immediately after this. > + devm_kfree(aux->dev, aux); You shouldn't need to call this if this function is only being called right before the component or device is removed. If that isn't the case (i.e. the memory is being allocated and freed during runtime) then don't use devm_. If devm_ is indeed appropriate then none of this function is useful and it can be removed. > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_aux.h b/drivers/gpu/drm/msm/dp/dp_aux.h > new file mode 100644 > index 0000000..98495c5 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_aux.h > @@ -0,0 +1,37 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_AUX_H_ > +#define _DP_AUX_H_ > + > +#include "dp_catalog.h" > +#include <drm/drm_dp_helper.h> > + > +enum dp_aux_error { > + DP_AUX_ERR_NONE = 0, > + DP_AUX_ERR_ADDR = -1, > + DP_AUX_ERR_TOUT = -2, > + DP_AUX_ERR_NACK = -3, > + DP_AUX_ERR_DEFER = -4, > + DP_AUX_ERR_NACK_DEFER = -5, > +}; For me, a enum with negative values is never useful because it wouldn't take advantage of any of the limited features of an enum. This can just as easily be #define. > + > +struct dp_aux { > + struct drm_dp_aux *drm_aux; > + struct dp_aux_cfg *cfg; > +}; > + > +int dp_aux_register(struct dp_aux *aux); > +void dp_aux_unregister(struct dp_aux *aux); > +void dp_aux_isr(struct dp_aux *aux); > +void dp_aux_init(struct dp_aux *aux, struct dp_aux_cfg *aux_cfg); > +void dp_aux_deinit(struct dp_aux *aux); > +void dp_aux_reconfig(struct dp_aux *aux); > + > +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, > + struct dp_aux_cfg *aux_cfg); > +void dp_aux_put(struct dp_aux *aux); > + > +#endif /*__DP_AUX_H_*/ > diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.c b/drivers/gpu/drm/msm/dp/dp_catalog.c > new file mode 100644 > index 0000000..d6c461b > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_catalog.c > @@ -0,0 +1,996 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include <linux/delay.h> > +#include <linux/iopoll.h> > +#include <drm/drm_dp_helper.h> > + > +#include "dp_catalog.h" > +#include "dp_reg.h" > + > +#define POLLING_SLEEP_US 1000 > +#define POLLING_TIMEOUT_US 10000 > + > +#define REFTIMER_DEFAULT_VALUE 0x20000 > +#define SCRAMBLER_RESET_COUNT_VALUE 0xFC > + > +#define dp_catalog_get_priv(x) { \ > + struct dp_catalog *dp_catalog; \ > + dp_catalog = container_of(x, struct dp_catalog, x); \ > + catalog = container_of(dp_catalog, struct dp_catalog_private, \ > + dp_catalog); \ > +} This should be a static inline function. > +#define DP_INTERRUPT_STATUS1 \ > + (DP_INTR_AUX_I2C_DONE| \ > + DP_INTR_WRONG_ADDR | DP_INTR_TIMEOUT | \ > + DP_INTR_NACK_DEFER | DP_INTR_WRONG_DATA_CNT | \ > + DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER | \ > + DP_INTR_PLL_UNLOCKED | DP_INTR_AUX_ERROR) > + > +#define DP_INTERRUPT_STATUS1_MASK (DP_INTERRUPT_STATUS1 << 2) > + > +#define DP_INTERRUPT_STATUS2 \ > + (DP_INTR_READY_FOR_VIDEO | DP_INTR_IDLE_PATTERN_SENT | \ > + DP_INTR_FRAME_END | DP_INTR_CRC_UPDATED) > + > +#define DP_INTERRUPT_STATUS2_MASK (DP_INTERRUPT_STATUS2 << 2) > + > +static u8 const vm_pre_emphasis[4][4] = { > + {0x00, 0x0B, 0x12, 0xFF}, /* pe0, 0 db */ > + {0x00, 0x0A, 0x12, 0xFF}, /* pe1, 3.5 db */ > + {0x00, 0x0C, 0xFF, 0xFF}, /* pe2, 6.0 db */ > + {0xFF, 0xFF, 0xFF, 0xFF} /* pe3, 9.5 db */ > +}; > + > +/* voltage swing, 0.2v and 1.0v are not support */ > +static u8 const vm_voltage_swing[4][4] = { > + {0x07, 0x0F, 0x14, 0xFF}, /* sw0, 0.4v */ > + {0x11, 0x1D, 0x1F, 0xFF}, /* sw1, 0.6 v */ > + {0x18, 0x1F, 0xFF, 0xFF}, /* sw1, 0.8 v */ > + {0xFF, 0xFF, 0xFF, 0xFF} /* sw1, 1.2 v, optional */ > +}; > + > +struct dp_catalog_private { > + struct device *dev; > + struct dp_io *io; > + struct dp_catalog dp_catalog; > +}; > + > +static inline u32 dp_read_aux(struct dp_catalog_private *catalog, u32 offset) Don't mark this inline, let the compiler decide, here and for all the following functions. > +{ > + return readl_relaxed(catalog->io->dp_aux.base + offset); > +} > + > +static inline void dp_write_aux(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + /* > + * To make sure aux reg writes happens before any other operation, > + * this function uses writel() instread of writel_relaxed() > + */ > + writel(data, catalog->io->dp_aux.base + offset); > +} > + > +static inline u32 dp_read_ahb(struct dp_catalog_private *catalog, u32 offset) > +{ > + return readl_relaxed(catalog->io->dp_ahb.base + offset); > +} > + > +static inline void dp_write_ahb(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + /* > + * To make sure phy reg writes happens before any other operation, > + * this function uses writel() instread of writel_relaxed() > + */ > + writel(data, catalog->io->dp_ahb.base + offset); > +} > + > +static inline u32 dp_read_cc(struct dp_catalog_private *catalog, u32 offset) > +{ > + return readl_relaxed(catalog->io->dp_cc_io.base + offset); > +} > + > +static inline void dp_write_phy(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + /* > + * To make sure phy reg writes happens before any other operation, > + * this function uses writel() instread of writel_relaxed() > + */ > + writel(data, catalog->io->phy_io.base + offset); > +} > + > +static inline void dp_write_pll(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + writel_relaxed(data, catalog->io->dp_pll_io.base + offset); > +} > + > +static inline void dp_write_ln_tx0(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + writel_relaxed(data, catalog->io->ln_tx0_io.base + offset); > +} > + > +static inline void dp_write_ln_tx1(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + writel_relaxed(data, catalog->io->ln_tx1_io.base + offset); > +} > + > +static inline void dp_write_usb_cm(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + /* > + * To make sure usb reg writes happens before any other operation, > + * this function uses writel() instread of writel_relaxed() > + */ > + writel(data, catalog->io->usb3_dp_com.base + offset); > +} > + > +static inline void dp_write_p0(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + /* > + * To make sure interface reg writes happens before any other operation, > + * this function uses writel() instread of writel_relaxed() > + */ > + writel(data, catalog->io->dp_p0.base + offset); > +} > + > +static inline u32 dp_read_link(struct dp_catalog_private *catalog, u32 offset) > +{ > + return readl_relaxed(catalog->io->dp_link.base + offset); > +} > + > +static inline void dp_write_link(struct dp_catalog_private *catalog, > + u32 offset, u32 data) > +{ > + /* > + * To make sure link reg writes happens before any other operation, > + * this function uses writel() instread of writel_relaxed() > + */ > + writel(data, catalog->io->dp_link.base + offset); > +} > + > +/* aux related catalog functions */ > +u32 dp_catalog_aux_read_data(struct dp_catalog_aux *aux) > +{ > + struct dp_catalog_private *catalog; > + > + if (!aux) { > + DRM_ERROR("invalid input\n"); > + return 0; > + } > + > + dp_catalog_get_priv(aux); > + > + return dp_read_aux(catalog, REG_DP_AUX_DATA); > +} > + > +int dp_catalog_aux_write_data(struct dp_catalog_aux *aux) > +{ > + int rc = 0; > + struct dp_catalog_private *catalog; > + > + if (!aux) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + dp_catalog_get_priv(aux); > + dp_write_aux(catalog, REG_DP_AUX_DATA, aux->data); > + return rc; rc is only used to return 0 so you don't need it, and just return 0; > +} > + > +int dp_catalog_aux_write_trans(struct dp_catalog_aux *aux) > +{ > + int rc = 0; > + struct dp_catalog_private *catalog; > + > + if (!aux) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + dp_catalog_get_priv(aux); > + dp_write_aux(catalog, REG_DP_AUX_TRANS_CTRL, aux->data); > + return rc; Same as above. > +} > + > +int dp_catalog_aux_clear_trans(struct dp_catalog_aux *aux, bool read) > +{ > + int rc = 0; > + u32 data = 0; > + struct dp_catalog_private *catalog; > + > + if (!aux) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + dp_catalog_get_priv(aux); > + > + if (read) { > + data = dp_read_aux(catalog, REG_DP_AUX_TRANS_CTRL); > + data &= ~DP_AUX_TRANS_CTRL_GO; > + dp_write_aux(catalog, REG_DP_AUX_TRANS_CTRL, data); > + } else { > + dp_write_aux(catalog, REG_DP_AUX_TRANS_CTRL, 0); > + } > + return rc; Same as above. > +} > + > +void dp_catalog_aux_reset(struct dp_catalog_aux *aux) > +{ > + u32 aux_ctrl; > + struct dp_catalog_private *catalog; > + > + if (!aux) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(aux); > + > + aux_ctrl = dp_read_aux(catalog, REG_DP_AUX_CTRL); > + > + aux_ctrl |= DP_AUX_CTRL_RESET; > + dp_write_aux(catalog, REG_DP_AUX_CTRL, aux_ctrl); > + usleep_range(1000, 1100); /* h/w recommended delay */ > + > + aux_ctrl &= ~DP_AUX_CTRL_RESET; > + dp_write_aux(catalog, REG_DP_AUX_CTRL, aux_ctrl); > +} > + > +void dp_catalog_aux_enable(struct dp_catalog_aux *aux, bool enable) > +{ > + u32 aux_ctrl; > + struct dp_catalog_private *catalog; > + > + if (!aux) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(aux); > + > + aux_ctrl = dp_read_aux(catalog, REG_DP_AUX_CTRL); > + > + if (enable) { > + dp_write_aux(catalog, REG_DP_TIMEOUT_COUNT, 0xffff); > + dp_write_aux(catalog, REG_DP_AUX_LIMITS, 0xffff); > + aux_ctrl |= DP_AUX_CTRL_ENABLE; > + } else { > + aux_ctrl &= ~DP_AUX_CTRL_ENABLE; > + } > + > + dp_write_aux(catalog, REG_DP_AUX_CTRL, aux_ctrl); > +} > + > +void dp_catalog_aux_update_cfg(struct dp_catalog_aux *aux, > + struct dp_aux_cfg *cfg, enum dp_phy_aux_config_type type) > +{ > + struct dp_catalog_private *catalog; > + u32 new_index = 0, current_index = 0; > + > + if (!aux || !cfg || (type >= PHY_AUX_CFG_MAX)) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(aux); > + > + current_index = cfg[type].current_index; > + new_index = (current_index + 1) % cfg[type].cfg_cnt; > + DRM_DEBUG_DP("Updating %s from 0x%08x to 0x%08x\n", > + dp_phy_aux_config_type_to_string(type), > + cfg[type].lut[current_index], cfg[type].lut[new_index]); > + > + dp_write_phy(catalog, cfg[type].offset, > + cfg[type].lut[new_index]); > + cfg[type].current_index = new_index; > +} > + > +void dp_catalog_aux_setup(struct dp_catalog_aux *aux, > + struct dp_aux_cfg *cfg) > +{ > + struct dp_catalog_private *catalog; > + int i = 0; > + > + if (!aux || !cfg) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(aux); > + > + dp_write_phy(catalog, REG_DP_PHY_PD_CTL, DP_PHY_PD_CTL_PWRDN | > + DP_PHY_PD_CTL_AUX_PWRDN | DP_PHY_PD_CTL_PLL_PWRDN | > + DP_PHY_PD_CTL_DP_CLAMP_EN); > + > + /* Turn on BIAS current for PHY/PLL */ > + dp_write_pll(catalog, > + QSERDES_COM_BIAS_EN_CLKBUFLR_EN, QSERDES_COM_BIAS_EN | > + QSERDES_COM_BIAS_EN_MUX | QSERDES_COM_CLKBUF_L_EN | > + QSERDES_COM_EN_SYSCLK_TX_SEL); > + > + /* DP AUX CFG register programming */ > + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { > + DRM_DEBUG_DP("%s: offset=0x%08x, value=0x%08x\n", > + dp_phy_aux_config_type_to_string(i), > + cfg[i].offset, cfg[i].lut[cfg[i].current_index]); > + dp_write_phy(catalog, cfg[i].offset, > + cfg[i].lut[cfg[i].current_index]); > + } > + > + dp_write_phy(catalog, REG_DP_PHY_AUX_INTERRUPT_MASK, PHY_AUX_STOP_ERR_MASK | > + PHY_AUX_DEC_ERR_MASK | PHY_AUX_SYNC_ERR_MASK | > + PHY_AUX_ALIGN_ERR_MASK | PHY_AUX_REQ_ERR_MASK); > +} > + > +void dp_catalog_aux_get_irq(struct dp_catalog_aux *aux, bool cmd_busy) > +{ > + u32 ack; > + struct dp_catalog_private *catalog; > + > + if (!aux) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(aux); > + > + aux->isr = dp_read_ahb(catalog, REG_DP_INTR_STATUS); > + aux->isr &= ~DP_INTERRUPT_STATUS1_MASK; > + ack = aux->isr & DP_INTERRUPT_STATUS1; > + ack <<= 1; > + ack |= DP_INTERRUPT_STATUS1_MASK; > + dp_write_ahb(catalog, REG_DP_INTR_STATUS, ack); > +} > + > +/* controller related catalog functions */ > +void dp_catalog_ctrl_update_transfer_unit(struct dp_catalog_ctrl *ctrl) > +{ > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } This check is done throughout this file. I haven't worked my way through all the code yet and it is hard to audit all the callers in a email client but this seems like mostly a sanity check on the developer and not an actual thing that can happen at runtime. If that is the case, you should save yourself the dozens and dozens of extra lines by either removing this or changing it: if (!WARN_ON(ctrl)) return; > + dp_catalog_get_priv(ctrl); > + > + dp_write_link(catalog, REG_DP_VALID_BOUNDARY, ctrl->valid_boundary); > + dp_write_link(catalog, REG_DP_TU, ctrl->dp_tu); > + dp_write_link(catalog, REG_DP_VALID_BOUNDARY_2, ctrl->valid_boundary2); > +} > + > +void dp_catalog_ctrl_state_ctrl(struct dp_catalog_ctrl *ctrl, u32 state) > +{ > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + dp_write_link(catalog, REG_DP_STATE_CTRL, state); > +} > + > +void dp_catalog_ctrl_config_ctrl(struct dp_catalog_ctrl *ctrl, u32 cfg) > +{ > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + DRM_DEBUG_DP("DP_CONFIGURATION_CTRL=0x%x\n", cfg); > + > + dp_write_link(catalog, REG_DP_CONFIGURATION_CTRL, cfg); > +} > + > +void dp_catalog_ctrl_lane_mapping(struct dp_catalog_ctrl *ctrl) > +{ > + struct dp_catalog_private *catalog; > + u32 ln_0 = 0, ln_1 = 1, ln_2 = 2, ln_3 = 3; /* One-to-One mapping */ You can safely use inline constants in the four lines below. This is much more confusing. > + u32 ln_mapping; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + ln_mapping = ln_0 << LANE0_MAPPING_SHIFT; > + ln_mapping |= ln_1 << LANE1_MAPPING_SHIFT; > + ln_mapping |= ln_2 << LANE2_MAPPING_SHIFT; > + ln_mapping |= ln_3 << LANE3_MAPPING_SHIFT; > + > + dp_write_link(catalog, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING, ln_mapping); > +} > + > +void dp_catalog_ctrl_mainlink_ctrl(struct dp_catalog_ctrl *ctrl, > + bool enable) > +{ > + u32 mainlink_ctrl; > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + if (enable) { > + /* > + * To make sure link reg writes happens before any other operation, > + * dp_write_link() function uses writel() instread of writel_relaxed() > + */ > + dp_write_link(catalog, REG_DP_MAINLINK_CTRL, DP_MAINLINK_FB_BOUNDARY_SEL); > + dp_write_link(catalog, REG_DP_MAINLINK_CTRL, > + DP_MAINLINK_FB_BOUNDARY_SEL | DP_MAINLINK_CTRL_RESET); > + dp_write_link(catalog, REG_DP_MAINLINK_CTRL, DP_MAINLINK_FB_BOUNDARY_SEL); > + dp_write_link(catalog, REG_DP_MAINLINK_CTRL, > + DP_MAINLINK_FB_BOUNDARY_SEL | DP_MAINLINK_CTRL_ENABLE); > + } else { > + mainlink_ctrl = dp_read_link(catalog, REG_DP_MAINLINK_CTRL); > + mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE; > + dp_write_link(catalog, REG_DP_MAINLINK_CTRL, mainlink_ctrl); > + } > +} > + > +void dp_catalog_ctrl_config_misc(struct dp_catalog_ctrl *ctrl, > + u32 colorimetry_cfg, u32 test_bits_depth) > +{ > + u32 misc_val; > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + misc_val = dp_read_link(catalog, REG_DP_MISC1_MISC0); > + misc_val |= colorimetry_cfg << DP_MISC0_COLORIMETRY_CFG_SHIFT; > + misc_val |= test_bits_depth << DP_MISC0_TEST_BITS_DEPTH_SHIFT; > + /* Configure clock to synchronous mode */ > + misc_val |= DP_MISC0_SYNCHRONOUS_CLK; > + > + DRM_DEBUG_DP("misc settings = 0x%x\n", misc_val); > + dp_write_link(catalog, REG_DP_MISC1_MISC0, misc_val); > +} > + > +void dp_catalog_ctrl_config_msa(struct dp_catalog_ctrl *ctrl, > + u32 rate, u32 stream_rate_khz, > + bool fixed_nvid) > +{ > + u32 pixel_m, pixel_n; > + u32 mvid, nvid; > + u64 mvid_calc; > + u32 const nvid_fixed = 0x8000; > + u32 const link_rate_hbr2 = 540000; > + u32 const link_rate_hbr3 = 810000; > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + if (fixed_nvid) { > + DRM_DEBUG_DP("use fixed NVID=0x%x\n", nvid_fixed); > + nvid = nvid_fixed; > + > + DRM_DEBUG_DP("link rate=%dkbps, stream_rate_khz=%uKhz", > + rate, stream_rate_khz); > + > + /* > + * For intermediate results, use 64 bit arithmetic to avoid > + * loss of precision. > + */ > + mvid_calc = (u64) stream_rate_khz * nvid; > + mvid_calc = div_u64(mvid_calc, rate); > + > + /* > + * truncate back to 32 bits as this final divided value will > + * always be within the range of a 32 bit unsigned int. > + */ > + mvid = (u32) mvid_calc; > + } else { > + pixel_m = dp_read_cc(catalog, MMSS_DP_PIXEL_M); > + pixel_n = dp_read_cc(catalog, MMSS_DP_PIXEL_N); > + DRM_DEBUG_DP("pixel_m=0x%x, pixel_n=0x%x\n", pixel_m, pixel_n); > + > + mvid = (pixel_m & 0xFFFF) * 5; > + nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF); > + > + DRM_DEBUG_DP("rate = %d\n", rate); > + > + if (link_rate_hbr2 == rate) > + nvid *= 2; > + > + if (link_rate_hbr3 == rate) > + nvid *= 3; > + } > + > + DRM_DEBUG_DP("mvid=0x%x, nvid=0x%x\n", mvid, nvid); > + dp_write_link(catalog, REG_DP_SOFTWARE_MVID, mvid); > + dp_write_link(catalog, REG_DP_SOFTWARE_NVID, nvid); > +} > + > +int dp_catalog_ctrl_set_pattern(struct dp_catalog_ctrl *ctrl, > + u32 pattern) > +{ > + int bit, ret; > + u32 data; > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + dp_catalog_get_priv(ctrl); > + > + bit = BIT(pattern - 1); > + DRM_DEBUG_DP("hw: bit=%d train=%d\n", bit, pattern); > + dp_write_link(catalog, REG_DP_STATE_CTRL, bit); > + > + bit = BIT(pattern - 1) << DP_MAINLINK_READY_LINK_TRAINING_SHIFT; > + > + /* Poll for mainlink ready status */ > + ret = readx_poll_timeout(readl, catalog->io->dp_link.base + REG_DP_MAINLINK_READY, > + data, data & bit, > + POLLING_SLEEP_US, POLLING_TIMEOUT_US); > + if (ret < 0) { > + DRM_ERROR("set pattern for link_train=%d failed\n", pattern); > + return ret; You don't need return ret, just drop out and then return ret at the bottom of the function. > + } > + return 0; > +} > + > +void dp_catalog_ctrl_usb_reset(struct dp_catalog_ctrl *ctrl, bool flip) > +{ > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_RESET_OVRD_CTRL, > + USB3_DP_COM_OVRD_CTRL_SW_DPPHY_RESET_MUX | > + USB3_DP_COM_OVRD_CTRL_SW_USB3PHY_RESET_MUX); > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_PHY_MODE_CTRL, > + USB3_DP_COM_PHY_MODE_DP); > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_SW_RESET, > + USB3_DP_COM_SW_RESET_SET); > + > + if (!flip) /* CC1 */ > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_TYPEC_CTRL, > + USB3_DP_COM_TYPEC_CTRL_PORTSEL_MUX); > + else /* CC2 */ > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_TYPEC_CTRL, > + USB3_DP_COM_TYPEC_CTRL_PORTSEL_MUX | > + USB3_DP_COM_TYPEC_CTRL_PORTSEL); > + > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_SWI_CTRL, 0x00); > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_SW_RESET, 0x00); > + > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_POWER_DOWN_CTRL, > + USB3_DP_COM_POWER_DOWN_CTRL_SW_PWRDN); > + dp_write_usb_cm(catalog, REG_USB3_DP_COM_RESET_OVRD_CTRL, 0x00); > + > +} > + > +void dp_catalog_ctrl_reset(struct dp_catalog_ctrl *ctrl) > +{ > + u32 sw_reset; > + struct dp_catalog_private *catalog; > + void __iomem *base; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + base = catalog->io->dp_ahb.base; Base appears to be unused here. > + > + sw_reset = dp_read_ahb(catalog, REG_DP_SW_RESET); > + > + sw_reset |= DP_SW_RESET; > + dp_write_ahb(catalog, REG_DP_SW_RESET, sw_reset); > + usleep_range(1000, 1100); /* h/w recommended delay */ > + > + sw_reset &= ~DP_SW_RESET; > + dp_write_ahb(catalog, REG_DP_SW_RESET, sw_reset); > +} > + > +bool dp_catalog_ctrl_mainlink_ready(struct dp_catalog_ctrl *ctrl) > +{ > + u32 data; > + int ret; > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return false; > + } > + > + dp_catalog_get_priv(ctrl); > + > + /* Poll for mainlink ready status */ > + ret = readx_poll_timeout(readl, > + catalog->io->dp_link.base + REG_DP_MAINLINK_READY, > + data, data & DP_MAINLINK_READY_FOR_VIDEO, > + POLLING_SLEEP_US, POLLING_TIMEOUT_US); > + if (ret < 0) { > + DRM_ERROR("mainlink not ready\n"); > + return false; > + } > + > + return true; > +} > + > +void dp_catalog_ctrl_enable_irq(struct dp_catalog_ctrl *ctrl, > + bool enable) > +{ > + struct dp_catalog_private *catalog; > + void __iomem *base; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + base = catalog->io->dp_ahb.base; base also appears to be unused here. > + > + if (enable) { > + dp_write_ahb(catalog, REG_DP_INTR_STATUS, DP_INTERRUPT_STATUS1_MASK); > + dp_write_ahb(catalog, REG_DP_INTR_STATUS2, DP_INTERRUPT_STATUS2_MASK); > + } else { > + dp_write_ahb(catalog, REG_DP_INTR_STATUS, 0x00); > + dp_write_ahb(catalog, REG_DP_INTR_STATUS2, 0x00); > + } > +} > + > +void dp_catalog_ctrl_hpd_config(struct dp_catalog_ctrl *ctrl, bool en) > +{ > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + if (en) { > + u32 reftimer = dp_read_aux(catalog, REG_DP_DP_HPD_REFTIMER); > + > + dp_write_aux(catalog, REG_DP_DP_HPD_INT_ACK, > + DP_DP_HPD_PLUG_INT_ACK | DP_DP_IRQ_HPD_INT_ACK | > + DP_DP_HPD_REPLUG_INT_ACK | DP_DP_HPD_UNPLUG_INT_ACK); > + dp_write_aux(catalog, REG_DP_DP_HPD_INT_MASK, > + DP_DP_HPD_PLUG_INT_MASK | DP_DP_IRQ_HPD_INT_MASK | > + DP_DP_HPD_REPLUG_INT_MASK | DP_DP_HPD_UNPLUG_INT_MASK); > + > + /* Configure REFTIMER */ > + reftimer |= REFTIMER_DEFAULT_VALUE; > + dp_write_aux(catalog, REG_DP_DP_HPD_REFTIMER, reftimer); > + /* Enable HPD */ > + dp_write_aux(catalog, REG_DP_DP_HPD_CTRL, DP_DP_HPD_CTRL_HPD_EN); > + } else { > + /* Disable HPD */ > + dp_write_aux(catalog, REG_DP_DP_HPD_CTRL, 0x0); > + } > +} > + > +void dp_catalog_ctrl_get_interrupt(struct dp_catalog_ctrl *ctrl) > +{ > + u32 ack = 0; > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + ctrl->isr = dp_read_ahb(catalog, REG_DP_INTR_STATUS2); > + ctrl->isr &= ~DP_INTERRUPT_STATUS2_MASK; > + ack = ctrl->isr & DP_INTERRUPT_STATUS2; > + ack <<= 1; > + ack |= DP_INTERRUPT_STATUS2_MASK; > + dp_write_ahb(catalog, REG_DP_INTR_STATUS2, ack); > +} > + > +void dp_catalog_ctrl_phy_reset(struct dp_catalog_ctrl *ctrl) > +{ > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + dp_write_ahb(catalog, REG_DP_PHY_CTRL, > + DP_PHY_CTRL_SW_RESET_PLL | DP_PHY_CTRL_SW_RESET); > + usleep_range(1000, 1100); /* h/w recommended delay */ > + dp_write_ahb(catalog, REG_DP_PHY_CTRL, 0x0); > +} > + > +void dp_catalog_ctrl_phy_lane_cfg(struct dp_catalog_ctrl *ctrl, > + bool flipped, u8 ln_cnt) > +{ > + u32 info = 0x0; > + struct dp_catalog_private *catalog; > + u8 orientation = BIT(!!flipped); > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + info = ln_cnt & DP_PHY_SPARE0_MASK; > + info |= (orientation & DP_PHY_SPARE0_MASK) << DP_PHY_SPARE0_ORIENTATION_INFO_SHIFT; > + DRM_DEBUG_DP("Shared Info = 0x%x\n", info); > + > + dp_write_phy(catalog, REG_DP_PHY_SPARE0, info); > +} > + > +int dp_catalog_ctrl_update_vx_px(struct dp_catalog_ctrl *ctrl, > + u8 v_level, u8 p_level) > +{ > + struct dp_catalog_private *catalog; > + u8 value0, value1; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + dp_catalog_get_priv(ctrl); > + > + DRM_DEBUG_DP("hw: v=%d p=%d\n", v_level, p_level); > + > + value0 = vm_voltage_swing[v_level][p_level]; > + value1 = vm_pre_emphasis[v_level][p_level]; > + > + if (value0 == 0xFF && value1 == 0xFF) { > + DRM_ERROR("invalid vx (0x%x=0x%x), px (0x%x=0x%x\n", > + v_level, value0, p_level, value1); > + return -EINVAL; > + } > + /* program default setting first */ > + dp_write_ln_tx0(catalog, TXn_TX_DRV_LVL, 0x2A); > + dp_write_ln_tx1(catalog, TXn_TX_DRV_LVL, 0x2A); > + dp_write_ln_tx0(catalog, TXn_TX_EMP_POST1_LVL, 0x20); > + dp_write_ln_tx1(catalog, TXn_TX_EMP_POST1_LVL, 0x20); > + > + /* Enable MUX to use Cursor values from these registers */ > + value0 |= BIT(5); > + value1 |= BIT(5); > + > + /* Configure host and panel only if both values are allowed */ > + dp_write_ln_tx0(catalog, TXn_TX_DRV_LVL, value0); > + dp_write_ln_tx1(catalog, TXn_TX_DRV_LVL, value0); > + dp_write_ln_tx0(catalog, TXn_TX_EMP_POST1_LVL, value1); > + dp_write_ln_tx1(catalog, TXn_TX_EMP_POST1_LVL, value1); > + DRM_DEBUG_DP("hw: vx_value=0x%x px_value=0x%x\n", > + value0, value1); > + > + return 0; > +} > + > +void dp_catalog_ctrl_send_phy_pattern(struct dp_catalog_ctrl *ctrl, > + u32 pattern) > +{ > + struct dp_catalog_private *catalog; > + u32 value = 0x0; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(ctrl); > + > + /* Make sure to clear the current pattern before starting a new one */ > + dp_write_link(catalog, REG_DP_STATE_CTRL, 0x0); > + > + switch (pattern) { > + case DP_LINK_QUAL_PATTERN_D10_2: > + dp_write_link(catalog, REG_DP_STATE_CTRL, DP_STATE_CTRL_LINK_TRAINING_PATTERN1); > + break; > + case DP_LINK_QUAL_PATTERN_ERROR_RATE: > + value &= ~DP_HBR2_ERM_PATTERN; > + dp_write_link(catalog, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); > + value |= SCRAMBLER_RESET_COUNT_VALUE; > + dp_write_link(catalog, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); > + dp_write_link(catalog, REG_DP_MAINLINK_LEVELS, DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2); > + dp_write_link(catalog, REG_DP_STATE_CTRL, DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE); > + break; > + case DP_LINK_QUAL_PATTERN_PRBS7: > + dp_write_link(catalog, REG_DP_STATE_CTRL, DP_STATE_CTRL_LINK_PRBS7); > + break; > + case DP_LINK_QUAL_PATTERN_80BIT_CUSTOM: > + dp_write_link(catalog, REG_DP_STATE_CTRL, DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN); > + /* 00111110000011111000001111100000 */ > + dp_write_link(catalog, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0, 0x3E0F83E0); > + /* 00001111100000111110000011111000 */ > + dp_write_link(catalog, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1, 0x0F83E0F8); > + /* 1111100000111110 */ > + dp_write_link(catalog, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2, 0x0000F83E); > + break; > + case DP_LINK_QUAL_PATTERN_HBR2_EYE: > + value = DP_HBR2_ERM_PATTERN; > + dp_write_link(catalog, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); > + value |= SCRAMBLER_RESET_COUNT_VALUE; > + dp_write_link(catalog, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); > + dp_write_link(catalog, REG_DP_MAINLINK_LEVELS, DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2); > + dp_write_link(catalog, REG_DP_STATE_CTRL, DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE); > + break; > + default: > + DRM_DEBUG_DP("No valid test pattern requested: 0x%x\n", pattern); > + return; > + } > +} > + > +u32 dp_catalog_ctrl_read_phy_pattern(struct dp_catalog_ctrl *ctrl) > +{ > + struct dp_catalog_private *catalog; > + > + if (!ctrl) { > + DRM_ERROR("invalid input\n"); > + return 0; > + } > + > + dp_catalog_get_priv(ctrl); > + > + return dp_read_link(catalog, REG_DP_MAINLINK_READY); > +} > + > +/* panel related catalog functions */ > +int dp_catalog_panel_timing_cfg(struct dp_catalog_panel *panel) > +{ > + struct dp_catalog_private *catalog; > + > + if (!panel) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + dp_catalog_get_priv(panel); > + > + dp_write_link(catalog, REG_DP_TOTAL_HOR_VER, panel->total); > + dp_write_link(catalog, REG_DP_START_HOR_VER_FROM_SYNC, panel->sync_start); > + dp_write_link(catalog, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY, panel->width_blanking); > + dp_write_link(catalog, REG_DP_ACTIVE_HOR_VER, panel->dp_active); > + return 0; > +} > + > +void dp_catalog_panel_tpg_enable(struct dp_catalog_panel *panel) > +{ > + struct dp_catalog_private *catalog; > + > + if (!panel) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(panel); > + > + dp_write_p0(catalog, MMSS_DP_INTF_CONFIG, 0x0); > + dp_write_p0(catalog, MMSS_DP_INTF_HSYNC_CTL, panel->hsync_ctl); > + dp_write_p0(catalog, MMSS_DP_INTF_VSYNC_PERIOD_F0, panel->vsync_period * > + panel->hsync_period); > + dp_write_p0(catalog, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0, panel->v_sync_width * > + panel->hsync_period); > + dp_write_p0(catalog, MMSS_DP_INTF_VSYNC_PERIOD_F1, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_DISPLAY_HCTL, panel->display_hctl); > + dp_write_p0(catalog, MMSS_DP_INTF_ACTIVE_HCTL, 0); > + dp_write_p0(catalog, MMSS_INTF_DISPLAY_V_START_F0, panel->display_v_start); > + dp_write_p0(catalog, MMSS_DP_INTF_DISPLAY_V_END_F0, panel->display_v_end); > + dp_write_p0(catalog, MMSS_INTF_DISPLAY_V_START_F1, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_DISPLAY_V_END_F1, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_ACTIVE_V_START_F0, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_ACTIVE_V_END_F0, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_ACTIVE_V_START_F1, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_ACTIVE_V_END_F1, 0); > + dp_write_p0(catalog, MMSS_DP_INTF_POLARITY_CTL, 0); > + > + dp_write_p0(catalog, MMSS_DP_TPG_MAIN_CONTROL, DP_TPG_CHECKERED_RECT_PATTERN); > + dp_write_p0(catalog, MMSS_DP_TPG_VIDEO_CONFIG, > + DP_TPG_VIDEO_CONFIG_BPP_8BIT | > + DP_TPG_VIDEO_CONFIG_RGB); > + dp_write_p0(catalog, MMSS_DP_BIST_ENABLE, DP_BIST_ENABLE_DPBIST_EN); > + dp_write_p0(catalog, MMSS_DP_TIMING_ENGINE_EN, DP_TIMING_ENGINE_EN_EN); > + DRM_DEBUG_DP("%s: enabled tpg\n", __func__); > +} > + > +void dp_catalog_panel_tpg_disable(struct dp_catalog_panel *panel) > +{ > + struct dp_catalog_private *catalog; > + > + if (!panel) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + dp_catalog_get_priv(panel); > + > + dp_write_p0(catalog, MMSS_DP_TPG_MAIN_CONTROL, 0x0); > + dp_write_p0(catalog, MMSS_DP_BIST_ENABLE, 0x0); > + dp_write_p0(catalog, MMSS_DP_TIMING_ENGINE_EN, 0x0); > + return; Don't need a bare return at the bottom of the function. > +} > + > +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_io *io) > +{ > + int rc = 0; > + struct dp_catalog *dp_catalog; > + struct dp_catalog_private *catalog; > + > + if (!io) { > + DRM_ERROR("invalid input\n"); > + rc = -EINVAL; > + goto error; This can just be return ERR_PTR(-EINVAL) assuming that io being NULL is an actual thing that can happen at run time and not just a developer goof. > + } > + > + catalog = devm_kzalloc(dev, sizeof(*catalog), GFP_KERNEL); > + if (!catalog) { > + rc = -ENOMEM; > + goto error; return ERR_PTR(-ENOMEM) > + } > + > + catalog->dev = dev; > + catalog->io = io; > + > + dp_catalog = &catalog->dp_catalog; > + > + return dp_catalog; > +error: > + return ERR_PTR(rc); And then the label isn't needed > +} > + > +void dp_catalog_put(struct dp_catalog *dp_catalog) > +{ > + struct dp_catalog_private *catalog; > + > + if (!dp_catalog) > + return; > + > + catalog = container_of(dp_catalog, struct dp_catalog_private, > + dp_catalog); > + > + devm_kfree(catalog->dev, catalog); As above, devm_kfree shouldn't need to be called on memory that is about to be cleaned up by the component or the device so assuming this is a persistent memory object for the life of the component this entire function can go way. > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.h b/drivers/gpu/drm/msm/dp/dp_catalog.h > new file mode 100644 > index 0000000..6509310 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_catalog.h > @@ -0,0 +1,108 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_CATALOG_H_ > +#define _DP_CATALOG_H_ > + > +#include "dp_parser.h" > + > +/* interrupts */ > +#define DP_INTR_HPD BIT(0) > +#define DP_INTR_AUX_I2C_DONE BIT(3) > +#define DP_INTR_WRONG_ADDR BIT(6) > +#define DP_INTR_TIMEOUT BIT(9) > +#define DP_INTR_NACK_DEFER BIT(12) > +#define DP_INTR_WRONG_DATA_CNT BIT(15) > +#define DP_INTR_I2C_NACK BIT(18) > +#define DP_INTR_I2C_DEFER BIT(21) > +#define DP_INTR_PLL_UNLOCKED BIT(24) > +#define DP_INTR_AUX_ERROR BIT(27) > + > +#define DP_INTR_READY_FOR_VIDEO BIT(0) > +#define DP_INTR_IDLE_PATTERN_SENT BIT(3) > +#define DP_INTR_FRAME_END BIT(6) > +#define DP_INTR_CRC_UPDATED BIT(9) > + > +struct dp_catalog_aux { > + u32 data; > + u32 isr; > +}; > + > +struct dp_catalog_ctrl { > + u32 dp_tu; > + u32 valid_boundary; > + u32 valid_boundary2; > + u32 isr; > +}; > + > +struct dp_catalog_panel { > + u32 total; > + u32 sync_start; > + u32 width_blanking; > + u32 dp_active; > + > + /* TPG */ > + u32 hsync_period; > + u32 vsync_period; > + u32 display_v_start; > + u32 display_v_end; > + u32 v_sync_width; > + u32 hsync_ctl; > + u32 display_hctl; > +}; > + > +struct dp_catalog { > + struct dp_catalog_aux aux; > + struct dp_catalog_ctrl ctrl; > + struct dp_catalog_panel panel; > +}; > + > +/* AUX APIs */ > +u32 dp_catalog_aux_read_data(struct dp_catalog_aux *aux); > +int dp_catalog_aux_write_data(struct dp_catalog_aux *aux); > +int dp_catalog_aux_write_trans(struct dp_catalog_aux *aux); > +int dp_catalog_aux_clear_trans(struct dp_catalog_aux *aux, bool read); > +void dp_catalog_aux_reset(struct dp_catalog_aux *aux); > +void dp_catalog_aux_enable(struct dp_catalog_aux *aux, bool enable); > +void dp_catalog_aux_update_cfg(struct dp_catalog_aux *aux, > + struct dp_aux_cfg *cfg, enum dp_phy_aux_config_type type); > +void dp_catalog_aux_setup(struct dp_catalog_aux *aux, > + struct dp_aux_cfg *aux_cfg); > +void dp_catalog_aux_get_irq(struct dp_catalog_aux *aux, bool cmd_busy); > + > +/* DP Controller APIs */ > +void dp_catalog_ctrl_state_ctrl(struct dp_catalog_ctrl *ctrl, u32 state); > +void dp_catalog_ctrl_config_ctrl(struct dp_catalog_ctrl *ctrl, u32 config); > +void dp_catalog_ctrl_lane_mapping(struct dp_catalog_ctrl *ctrl); > +void dp_catalog_ctrl_mainlink_ctrl(struct dp_catalog_ctrl *ctrl, bool enable); > +void dp_catalog_ctrl_config_misc(struct dp_catalog_ctrl *ctrl, u32 cc, u32 tb); > +void dp_catalog_ctrl_config_msa(struct dp_catalog_ctrl *ctrl, u32 rate, > + u32 stream_rate_khz, bool fixed_nvid); > +int dp_catalog_ctrl_set_pattern(struct dp_catalog_ctrl *ctrl, u32 pattern); > +void dp_catalog_ctrl_reset(struct dp_catalog_ctrl *ctrl); > +void dp_catalog_ctrl_usb_reset(struct dp_catalog_ctrl *ctrl, bool flip); > +bool dp_catalog_ctrl_mainlink_ready(struct dp_catalog_ctrl *ctrl); > +void dp_catalog_ctrl_enable_irq(struct dp_catalog_ctrl *ctrl, bool enable); > +void dp_catalog_ctrl_hpd_config(struct dp_catalog_ctrl *ctrl, bool enable); > +void dp_catalog_ctrl_phy_reset(struct dp_catalog_ctrl *ctrl); > +void dp_catalog_ctrl_phy_lane_cfg(struct dp_catalog_ctrl *ctrl, bool flipped, > + u8 lane_cnt); > +int dp_catalog_ctrl_update_vx_px(struct dp_catalog_ctrl *ctrl, u8 v_level, > + u8 p_level); > +void dp_catalog_ctrl_get_interrupt(struct dp_catalog_ctrl *ctrl); > +void dp_catalog_ctrl_update_transfer_unit(struct dp_catalog_ctrl *ctrl); > +void dp_catalog_ctrl_send_phy_pattern(struct dp_catalog_ctrl *ctrl, > + u32 pattern); > +u32 dp_catalog_ctrl_read_phy_pattern(struct dp_catalog_ctrl *ctrl); > + > +/* DP Panel APIs */ > +int dp_catalog_panel_timing_cfg(struct dp_catalog_panel *panel); > +void dp_catalog_panel_tpg_enable(struct dp_catalog_panel *panel); > +void dp_catalog_panel_tpg_disable(struct dp_catalog_panel *panel); > + > +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_io *io); > +void dp_catalog_put(struct dp_catalog *catalog); > + > +#endif /* _DP_CATALOG_H_ */ > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c > new file mode 100644 > index 0000000..a13ab24 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c > @@ -0,0 +1,1611 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include <linux/types.h> > +#include <linux/completion.h> > +#include <linux/delay.h> > +#include <drm/drm_fixed.h> > + > +#include "dp_reg.h" > +#include "dp_ctrl.h" > + > +#define DP_KHZ_TO_HZ 1000 > +#define IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES (30 * HZ / 1000) /* 30 ms */ This looks like it should be msecs_to_jiffies() > +#define WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES (HZ / 2) Maybe this one too, since I'm guessing the intended timeout is 500ms > + > +#define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0) > +#define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3) > + > +#define MR_LINK_TRAINING1 0x8 > +#define MR_LINK_SYMBOL_ERM 0x80 > +#define MR_LINK_PRBS7 0x100 > +#define MR_LINK_CUSTOM80 0x200 > + > +struct dp_tu_calc_input { > + u64 lclk; /* 162, 270, 540 and 810 */ > + u64 pclk_khz; /* in KHz */ > + u64 hactive; /* active h-width */ > + u64 hporch; /* bp + fp + pulse */ > + int nlanes; /* no.of.lanes */ > + int bpp; /* bits */ > + int pixel_enc; /* 444, 420, 422 */ > + int dsc_en; /* dsc on/off */ > + int async_en; /* async mode */ > +}; > + > +struct dp_vc_tu_mapping_table { > + u8 valid_boundary_link; > + u16 delay_start_link; > + bool boundary_moderation_en; > + u8 valid_lower_boundary_link; > + u8 upper_boundary_count; > + u8 lower_boundary_count; > + u8 tu_size_minus1; > +}; > + > +struct dp_ctrl_private { > + struct dp_ctrl dp_ctrl; > + > + struct device *dev; > + struct dp_aux *aux; > + struct dp_panel *panel; > + struct dp_link *link; > + struct dp_power *power; > + struct dp_parser *parser; > + struct dp_catalog_ctrl *catalog; > + > + struct completion idle_comp; > + struct mutex push_idle_mutex; > + struct completion video_comp; > +}; > + > +void dp_ctrl_abort(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + atomic_set(&ctrl->dp_ctrl.aborted, 1); > +} > + > +void dp_ctrl_push_idle(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + mutex_lock(&ctrl->push_idle_mutex); > + > + reinit_completion(&ctrl->idle_comp); > + dp_catalog_ctrl_state_ctrl(ctrl->catalog, DP_STATE_CTRL_PUSH_IDLE); > + > + if (!wait_for_completion_timeout(&ctrl->idle_comp, > + IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES)) > + pr_warn("PUSH_IDLE pattern timedout\n"); > + > + mutex_unlock(&ctrl->push_idle_mutex); > + pr_debug("mainlink off done\n"); > +} > + > +static void dp_ctrl_config_ctrl(struct dp_ctrl_private *ctrl) > +{ > + u32 config = 0, tbd; > + u8 *dpcd = ctrl->panel->dpcd; > + > + /* Default-> LSCLK DIV: 1/4 LCLK */ > + config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); > + > + /* Scrambler reset enable */ > + if (dpcd[DP_EDP_CONFIGURATION_CAP] & DP_ALTERNATE_SCRAMBLER_RESET_CAP) > + config |= DP_CONFIGURATION_CTRL_ASSR; > + > + tbd = dp_link_get_test_bits_depth(ctrl->link, > + ctrl->panel->dp_mode.bpp); > + > + if (tbd == DP_TEST_BIT_DEPTH_UNKNOWN) { > + pr_debug("BIT_DEPTH not set. Configure default\n"); > + tbd = DP_TEST_BIT_DEPTH_8; > + } > + > + config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; > + > + /* Num of Lanes */ > + config |= ((ctrl->link->link_params.num_lanes - 1) > + << DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); > + > + if (drm_dp_enhanced_frame_cap(dpcd)) > + config |= DP_CONFIGURATION_CTRL_ENHANCED_FRAMING; > + > + config |= DP_CONFIGURATION_CTRL_P_INTERLACED; /* progressive video */ > + > + /* sync clock & static Mvid */ > + config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; > + config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; > + > + dp_catalog_ctrl_config_ctrl(ctrl->catalog, config); > +} > + > +/** > + * dp_ctrl_configure_source_params() - configures DP transmitter source params > + * @ctrl: Display Port Driver data > + * > + * Configures the DP transmitter source params including details such as lane > + * configuration, output format and sink/panel timing information. > + */ > +static void dp_ctrl_configure_source_params(struct dp_ctrl_private *ctrl) > +{ > + u32 cc, tb; > + > + dp_catalog_ctrl_lane_mapping(ctrl->catalog); > + dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, true); > + > + dp_ctrl_config_ctrl(ctrl); > + > + tb = dp_link_get_test_bits_depth(ctrl->link, > + ctrl->panel->dp_mode.bpp); > + cc = dp_link_get_colorimetry_config(ctrl->link); > + dp_catalog_ctrl_config_misc(ctrl->catalog, cc, tb); > + dp_panel_timing_cfg(ctrl->panel); > +} > + > +/* > + * The structure and few functions present below are IP/Hardware > + * specific implementation. Most of the implementation will not > + * have coding comments > + */ > +struct tu_algo_data { > + s64 lclk_fp; > + s64 pclk_fp; > + s64 lwidth; > + s64 hbp_relative_to_pclk; > + int nlanes; > + int bpp; > + int pixelEnc; > + int dsc_en; > + int async_en; > + int bpc; > + > + uint delay_start_link_extra_pixclk; > + int extra_buffer_margin; > + s64 ratio_fp; > + s64 original_ratio_fp; > + > + s64 err_fp; > + s64 n_err_fp; > + s64 n_n_err_fp; > + int tu_size; > + int tu_size_desired; > + int tu_size_minus1; > + > + int valid_boundary_link; > + s64 resulting_valid_fp; > + s64 total_valid_fp; > + s64 effective_valid_fp; > + s64 effective_valid_recorded_fp; > + int n_tus; > + int n_tus_per_lane; > + int paired_tus; > + int remainder_tus; > + int remainder_tus_upper; > + int remainder_tus_lower; > + int extra_bytes; > + int filler_size; > + int delay_start_link; > + > + int extra_pclk_cycles; > + int extra_pclk_cycles_in_link_clk; > + s64 ratio_by_tu_fp; > + s64 average_valid2_fp; > + int new_valid_boundary_link; > + int remainder_symbols_exist; > + int n_symbols; > + s64 n_remainder_symbols_per_lane_fp; > + s64 last_partial_tu_fp; > + s64 TU_ratio_err_fp; > + > + int n_tus_incl_last_incomplete_tu; > + int extra_pclk_cycles_tmp; > + int extra_pclk_cycles_in_link_clk_tmp; > + int extra_required_bytes_new_tmp; > + int filler_size_tmp; > + int lower_filler_size_tmp; > + int delay_start_link_tmp; > + > + bool boundary_moderation_en; > + int boundary_mod_lower_err; > + int upper_boundary_count; > + int lower_boundary_count; > + int i_upper_boundary_count; > + int i_lower_boundary_count; > + int valid_lower_boundary_link; > + int even_distribution_BF; > + int even_distribution_legacy; > + int even_distribution; > + int min_hblank_violated; > + s64 delay_start_time_fp; > + s64 hbp_time_fp; > + s64 hactive_time_fp; > + s64 diff_abs_fp; > + > + s64 ratio; > +}; > + > +static int _tu_param_compare(s64 a, s64 b) > +{ > + u32 a_int, a_frac; > + u32 b_int, b_frac; > + > + if (a == b) > + return 0; > + > + a_int = (a >> 32) & 0x7FFFFFFF; > + a_frac = a & 0xFFFFFFFF; > + > + b_int = (b >> 32) & 0x7FFFFFFF; > + b_frac = b & 0xFFFFFFFF; > + > + if (a_int > b_int) > + return 1; > + else if (a_int < b_int) > + return 2; > + else if (a_frac > b_frac) > + return 1; > + else > + return 2; > +} > + > +static void _tu_valid_boundary_calc(struct tu_algo_data *tu) > +{ > + s64 temp1_fp, temp2_fp, temp, temp1, temp2; > + int compare_result_1, compare_result_2, compare_result_3; > + > + temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1); > + temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp); > + > + tu->new_valid_boundary_link = drm_fixp2int_ceil(temp2_fp); > + > + temp = (tu->i_upper_boundary_count * > + tu->new_valid_boundary_link + > + tu->i_lower_boundary_count * > + (tu->new_valid_boundary_link-1)); > + tu->average_valid2_fp = drm_fixp_from_fraction(temp, > + (tu->i_upper_boundary_count + > + tu->i_lower_boundary_count)); > + > + temp1_fp = drm_fixp_from_fraction(tu->bpp, 8); > + temp2_fp = drm_fixp_from_fraction(tu->lwidth, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + temp2_fp = drm_fixp_div(temp1_fp, tu->average_valid2_fp); > + tu->n_tus = drm_fixp2int(temp2_fp); > + > + temp1_fp = drm_fixp_from_fraction(tu->n_tus, 1); > + temp2_fp = drm_fixp_mul(temp1_fp, tu->average_valid2_fp); > + temp1_fp = drm_fixp_from_fraction(tu->n_symbols, 1); > + temp2_fp = temp1_fp - temp2_fp; > + temp1_fp = drm_fixp_from_fraction(tu->nlanes, 1); > + temp2_fp = drm_fixp_div(temp2_fp, temp1_fp); > + tu->n_remainder_symbols_per_lane_fp = temp2_fp; > + > + temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1); > + tu->last_partial_tu_fp = > + drm_fixp_div(tu->n_remainder_symbols_per_lane_fp, > + temp1_fp); > + > + if (tu->n_remainder_symbols_per_lane_fp != 0) > + tu->remainder_symbols_exist = 1; > + else > + tu->remainder_symbols_exist = 0; > + > + temp1_fp = drm_fixp_from_fraction(tu->n_tus, tu->nlanes); > + tu->n_tus_per_lane = drm_fixp2int(temp1_fp); > + > + tu->paired_tus = (int)((tu->n_tus_per_lane) / > + (tu->i_upper_boundary_count + > + tu->i_lower_boundary_count)); > + > + tu->remainder_tus = tu->n_tus_per_lane - tu->paired_tus * > + (tu->i_upper_boundary_count + > + tu->i_lower_boundary_count); > + > + if ((tu->remainder_tus - tu->i_upper_boundary_count) > 0) { > + tu->remainder_tus_upper = tu->i_upper_boundary_count; > + tu->remainder_tus_lower = tu->remainder_tus - > + tu->i_upper_boundary_count; > + } else { > + tu->remainder_tus_upper = tu->remainder_tus; > + tu->remainder_tus_lower = 0; > + } > + > + temp = tu->paired_tus * (tu->i_upper_boundary_count * > + tu->new_valid_boundary_link + > + tu->i_lower_boundary_count * > + (tu->new_valid_boundary_link - 1)) + > + (tu->remainder_tus_upper * > + tu->new_valid_boundary_link) + > + (tu->remainder_tus_lower * > + (tu->new_valid_boundary_link - 1)); > + tu->total_valid_fp = drm_fixp_from_fraction(temp, 1); > + > + if (tu->remainder_symbols_exist) { > + temp1_fp = tu->total_valid_fp + > + tu->n_remainder_symbols_per_lane_fp; > + temp2_fp = drm_fixp_from_fraction(tu->n_tus_per_lane, 1); > + temp2_fp = temp2_fp + tu->last_partial_tu_fp; > + temp1_fp = drm_fixp_div(temp1_fp, temp2_fp); > + } else { > + temp2_fp = drm_fixp_from_fraction(tu->n_tus_per_lane, 1); > + temp1_fp = drm_fixp_div(tu->total_valid_fp, temp2_fp); > + } > + tu->effective_valid_fp = temp1_fp; > + > + temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1); > + temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp); > + tu->n_n_err_fp = tu->effective_valid_fp - temp2_fp; > + > + temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1); > + temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp); > + tu->n_err_fp = tu->average_valid2_fp - temp2_fp; > + > + tu->even_distribution = tu->n_tus % tu->nlanes == 0 ? 1 : 0; > + > + temp1_fp = drm_fixp_from_fraction(tu->bpp, 8); > + temp2_fp = drm_fixp_from_fraction(tu->lwidth, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + temp2_fp = drm_fixp_div(temp1_fp, tu->average_valid2_fp); > + > + if (temp2_fp) > + tu->n_tus_incl_last_incomplete_tu = drm_fixp2int_ceil(temp2_fp); > + else > + tu->n_tus_incl_last_incomplete_tu = 0; > + > + temp1 = 0; > + temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1); > + temp2_fp = drm_fixp_mul(tu->original_ratio_fp, temp1_fp); > + temp1_fp = tu->average_valid2_fp - temp2_fp; > + temp2_fp = drm_fixp_from_fraction(tu->n_tus_incl_last_incomplete_tu, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + > + if (temp1_fp) > + temp1 = drm_fixp2int_ceil(temp1_fp); > + > + temp = tu->i_upper_boundary_count * tu->nlanes; > + temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1); > + temp2_fp = drm_fixp_mul(tu->original_ratio_fp, temp1_fp); > + temp1_fp = drm_fixp_from_fraction(tu->new_valid_boundary_link, 1); > + temp2_fp = temp1_fp - temp2_fp; > + temp1_fp = drm_fixp_from_fraction(temp, 1); > + temp2_fp = drm_fixp_mul(temp1_fp, temp2_fp); > + > + if (temp2_fp) > + temp2 = drm_fixp2int_ceil(temp2_fp); > + else > + temp2 = 0; > + tu->extra_required_bytes_new_tmp = (int)(temp1 + temp2); > + > + temp1_fp = drm_fixp_from_fraction(8, tu->bpp); > + temp2_fp = drm_fixp_from_fraction( > + tu->extra_required_bytes_new_tmp, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + > + if (temp1_fp) > + tu->extra_pclk_cycles_tmp = drm_fixp2int_ceil(temp1_fp); > + else > + tu->extra_pclk_cycles_tmp = 0; > + > + temp1_fp = drm_fixp_from_fraction(tu->extra_pclk_cycles_tmp, 1); > + temp2_fp = drm_fixp_div(tu->lclk_fp, tu->pclk_fp); > + temp1_fp = drm_fixp_mul(temp1_fp, temp2_fp); > + > + if (temp1_fp) > + tu->extra_pclk_cycles_in_link_clk_tmp = > + drm_fixp2int_ceil(temp1_fp); > + else > + tu->extra_pclk_cycles_in_link_clk_tmp = 0; > + > + tu->filler_size_tmp = tu->tu_size - tu->new_valid_boundary_link; > + > + tu->lower_filler_size_tmp = tu->filler_size_tmp + 1; > + > + tu->delay_start_link_tmp = tu->extra_pclk_cycles_in_link_clk_tmp + > + tu->lower_filler_size_tmp + > + tu->extra_buffer_margin; > + > + temp1_fp = drm_fixp_from_fraction(tu->delay_start_link_tmp, 1); > + tu->delay_start_time_fp = drm_fixp_div(temp1_fp, tu->lclk_fp); > + > + compare_result_1 = _tu_param_compare(tu->n_n_err_fp, tu->diff_abs_fp); > + if (compare_result_1 == 2) > + compare_result_1 = 1; > + else > + compare_result_1 = 0; > + > + compare_result_2 = _tu_param_compare(tu->n_n_err_fp, tu->err_fp); > + if (compare_result_2 == 2) > + compare_result_2 = 1; > + else > + compare_result_2 = 0; > + > + compare_result_3 = _tu_param_compare(tu->hbp_time_fp, > + tu->delay_start_time_fp); > + if (compare_result_3 == 2) > + compare_result_3 = 0; > + else > + compare_result_3 = 1; > + > + if (((tu->even_distribution == 1) || > + ((tu->even_distribution_BF == 0) && > + (tu->even_distribution_legacy == 0))) && > + tu->n_err_fp >= 0 && tu->n_n_err_fp >= 0 && > + compare_result_2 && > + (compare_result_1 || (tu->min_hblank_violated == 1)) && > + (tu->new_valid_boundary_link - 1) > 0 && > + compare_result_3 && > + (tu->delay_start_link_tmp <= 1023)) { > + tu->upper_boundary_count = tu->i_upper_boundary_count; > + tu->lower_boundary_count = tu->i_lower_boundary_count; > + tu->err_fp = tu->n_n_err_fp; > + tu->boundary_moderation_en = true; > + tu->tu_size_desired = tu->tu_size; > + tu->valid_boundary_link = tu->new_valid_boundary_link; > + tu->effective_valid_recorded_fp = tu->effective_valid_fp; > + tu->even_distribution_BF = 1; > + tu->delay_start_link = tu->delay_start_link_tmp; > + } else if (tu->boundary_mod_lower_err == 0) { > + compare_result_1 = _tu_param_compare(tu->n_n_err_fp, > + tu->diff_abs_fp); > + if (compare_result_1 == 2) > + tu->boundary_mod_lower_err = 1; > + } > +} > + > +static void _dp_ctrl_calc_tu(struct dp_tu_calc_input *in, > + struct dp_vc_tu_mapping_table *tu_table) > +{ > + struct tu_algo_data tu; > + int compare_result_1, compare_result_2; > + u64 temp = 0; > + s64 temp_fp = 0, temp1_fp = 0, temp2_fp = 0; > + > + s64 LCLK_FAST_SKEW_fp = drm_fixp_from_fraction(6, 10000); /* 0.0006 */ > + s64 const_p49_fp = drm_fixp_from_fraction(49, 100); /* 0.49 */ > + s64 const_p56_fp = drm_fixp_from_fraction(56, 100); /* 0.56 */ > + s64 RATIO_SCALE_fp = drm_fixp_from_fraction(1001, 1000); > + > + u8 DP_BRUTE_FORCE = 1; > + s64 BRUTE_FORCE_THRESHOLD_fp = drm_fixp_from_fraction(1, 10); /* 0.1 */ > + uint EXTRA_PIXCLK_CYCLE_DELAY = 4; > + uint HBLANK_MARGIN = 4; I would prefer if you used lower case names for all variables unless these are constants in disguise and then if so move them to #define. > + > + memset(&tu, 0, sizeof(tu)); > + > + tu.lclk_fp = drm_fixp_from_fraction(in->lclk, 1); > + tu.pclk_fp = drm_fixp_from_fraction(in->pclk_khz, 1000); > + tu.lwidth = in->hactive; > + tu.hbp_relative_to_pclk = in->hporch; > + tu.nlanes = in->nlanes; > + tu.bpp = in->bpp; > + tu.pixelEnc = in->pixel_enc; > + tu.dsc_en = in->dsc_en; > + tu.async_en = in->async_en; > + > + tu.err_fp = drm_fixp_from_fraction(1000, 1); /* 1000 */ > + > + if (tu.pixelEnc == 420) { > + temp_fp = drm_fixp_from_fraction(2, 1); > + tu.pclk_fp = drm_fixp_div(tu.pclk_fp, temp_fp); > + tu.lwidth /= 2; > + tu.hbp_relative_to_pclk /= 2; > + } > + > + if (tu.pixelEnc == 422) { > + switch (tu.bpp) { > + case 24: > + tu.bpp = 16; > + tu.bpc = 8; > + break; > + case 30: > + tu.bpp = 20; > + tu.bpc = 10; > + break; > + default: > + tu.bpp = 16; > + tu.bpc = 8; > + break; > + } > + } else { > + tu.bpc = tu.bpp/3; > + } > + > + temp1_fp = drm_fixp_from_fraction(4, 1); > + temp2_fp = drm_fixp_mul(temp1_fp, tu.lclk_fp); > + temp_fp = drm_fixp_div(temp2_fp, tu.pclk_fp); > + tu.extra_buffer_margin = drm_fixp2int_ceil(temp_fp); > + > + temp1_fp = drm_fixp_from_fraction(tu.bpp, 8); > + temp2_fp = drm_fixp_mul(tu.pclk_fp, temp1_fp); > + temp1_fp = drm_fixp_from_fraction(tu.nlanes, 1); > + temp2_fp = drm_fixp_div(temp2_fp, temp1_fp); > + tu.ratio_fp = drm_fixp_div(temp2_fp, tu.lclk_fp); > + > + tu.original_ratio_fp = tu.ratio_fp; > + tu.boundary_moderation_en = false; > + tu.upper_boundary_count = 0; > + tu.lower_boundary_count = 0; > + tu.i_upper_boundary_count = 0; > + tu.i_lower_boundary_count = 0; > + tu.valid_lower_boundary_link = 0; > + tu.even_distribution_BF = 0; > + tu.even_distribution_legacy = 0; > + tu.even_distribution = 0; > + tu.delay_start_time_fp = 0; > + > + tu.err_fp = drm_fixp_from_fraction(1000, 1); > + tu.n_err_fp = 0; > + tu.n_n_err_fp = 0; > + > + tu.ratio = drm_fixp2int(tu.ratio_fp); > + if ((((u32)tu.lwidth % tu.nlanes) != 0) && > + !tu.ratio && tu.dsc_en == 0) { > + tu.ratio_fp = drm_fixp_mul(tu.ratio_fp, RATIO_SCALE_fp); > + tu.ratio = drm_fixp2int(tu.ratio_fp); > + if (tu.ratio) > + tu.ratio_fp = drm_fixp_from_fraction(1, 1); > + } > + > + if (tu.ratio > 1) > + tu.ratio = 1; > + > + if (tu.ratio == 1) > + goto tu_size_calc; > + > + compare_result_1 = _tu_param_compare(tu.ratio_fp, const_p49_fp); > + if (!compare_result_1 || compare_result_1 == 1) > + compare_result_1 = 1; > + else > + compare_result_1 = 0; > + > + compare_result_2 = _tu_param_compare(tu.ratio_fp, const_p56_fp); > + if (!compare_result_2 || compare_result_2 == 2) > + compare_result_2 = 1; > + else > + compare_result_2 = 0; > + > + if (tu.dsc_en && compare_result_1 && compare_result_2) { > + HBLANK_MARGIN += 4; > + DRM_DEBUG_DP("Info: increase HBLANK_MARGIN to %d\n", HBLANK_MARGIN); You seem to mix and match DRM_ logging macros and pr_logging macros in this file. > + } > + > +tu_size_calc: > + for (tu.tu_size = 32; tu.tu_size <= 64; tu.tu_size++) { > + temp1_fp = drm_fixp_from_fraction(tu.tu_size, 1); > + temp2_fp = drm_fixp_mul(tu.ratio_fp, temp1_fp); > + temp = drm_fixp2int_ceil(temp2_fp); > + temp1_fp = drm_fixp_from_fraction(temp, 1); > + tu.n_err_fp = temp1_fp - temp2_fp; > + > + if (tu.n_err_fp < tu.err_fp) { > + tu.err_fp = tu.n_err_fp; > + tu.tu_size_desired = tu.tu_size; > + } > + } > + > + tu.tu_size_minus1 = tu.tu_size_desired - 1; > + > + temp1_fp = drm_fixp_from_fraction(tu.tu_size_desired, 1); > + temp2_fp = drm_fixp_mul(tu.ratio_fp, temp1_fp); > + tu.valid_boundary_link = drm_fixp2int_ceil(temp2_fp); > + > + temp1_fp = drm_fixp_from_fraction(tu.bpp, 8); > + temp2_fp = drm_fixp_from_fraction(tu.lwidth, 1); > + temp2_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + > + temp1_fp = drm_fixp_from_fraction(tu.valid_boundary_link, 1); > + temp2_fp = drm_fixp_div(temp2_fp, temp1_fp); > + tu.n_tus = drm_fixp2int(temp2_fp); > + > + tu.even_distribution_legacy = tu.n_tus % tu.nlanes == 0 ? 1 : 0; > + DRM_DEBUG_DP("Info: n_sym = %d, num_of_tus = %d\n", > + tu.valid_boundary_link, tu.n_tus); > + > + temp1_fp = drm_fixp_from_fraction(tu.tu_size_desired, 1); > + temp2_fp = drm_fixp_mul(tu.original_ratio_fp, temp1_fp); > + temp1_fp = drm_fixp_from_fraction(tu.valid_boundary_link, 1); > + temp2_fp = temp1_fp - temp2_fp; > + temp1_fp = drm_fixp_from_fraction(tu.n_tus + 1, 1); > + temp2_fp = drm_fixp_mul(temp1_fp, temp2_fp); > + > + temp = drm_fixp2int(temp2_fp); > + if (temp && temp2_fp) > + tu.extra_bytes = drm_fixp2int_ceil(temp2_fp); > + else > + tu.extra_bytes = 0; > + > + temp1_fp = drm_fixp_from_fraction(tu.extra_bytes, 1); > + temp2_fp = drm_fixp_from_fraction(8, tu.bpp); > + temp1_fp = drm_fixp_mul(temp1_fp, temp2_fp); > + > + temp = drm_fixp2int(temp1_fp); > + if (temp && temp1_fp) > + tu.extra_pclk_cycles = drm_fixp2int_ceil(temp1_fp); > + else > + tu.extra_pclk_cycles = 0; > + > + temp1_fp = drm_fixp_div(tu.lclk_fp, tu.pclk_fp); > + temp2_fp = drm_fixp_from_fraction(tu.extra_pclk_cycles, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + > + temp = drm_fixp2int(temp1_fp); > + if (temp && temp1_fp) > + tu.extra_pclk_cycles_in_link_clk = drm_fixp2int_ceil(temp1_fp); > + else > + tu.extra_pclk_cycles_in_link_clk = 0; > + > + tu.filler_size = tu.tu_size_desired - tu.valid_boundary_link; > + > + temp1_fp = drm_fixp_from_fraction(tu.tu_size_desired, 1); > + tu.ratio_by_tu_fp = drm_fixp_mul(tu.ratio_fp, temp1_fp); > + > + tu.delay_start_link = tu.extra_pclk_cycles_in_link_clk + > + tu.filler_size + tu.extra_buffer_margin; > + > + tu.resulting_valid_fp = > + drm_fixp_from_fraction(tu.valid_boundary_link, 1); > + > + temp1_fp = drm_fixp_from_fraction(tu.tu_size_desired, 1); > + temp2_fp = drm_fixp_div(tu.resulting_valid_fp, temp1_fp); > + tu.TU_ratio_err_fp = temp2_fp - tu.original_ratio_fp; > + > + temp1_fp = drm_fixp_from_fraction( > + (tu.hbp_relative_to_pclk - HBLANK_MARGIN), 1); > + tu.hbp_time_fp = drm_fixp_div(temp1_fp, tu.pclk_fp); > + > + temp1_fp = drm_fixp_from_fraction(tu.delay_start_link, 1); > + tu.delay_start_time_fp = drm_fixp_div(temp1_fp, tu.lclk_fp); > + > + compare_result_1 = _tu_param_compare(tu.hbp_time_fp, > + tu.delay_start_time_fp); > + if (compare_result_1 == 2) /* if (hbp_time_fp < delay_start_time_fp) */ > + tu.min_hblank_violated = 1; > + > + temp1_fp = drm_fixp_from_fraction(tu.lwidth, 1); > + tu.hactive_time_fp = drm_fixp_div(temp1_fp, tu.pclk_fp); > + > + compare_result_2 = _tu_param_compare(tu.hactive_time_fp, > + tu.delay_start_time_fp); > + if (compare_result_2 == 2) > + tu.min_hblank_violated = 1; > + > + tu.delay_start_time_fp = 0; > + > + /* brute force */ > + > + tu.delay_start_link_extra_pixclk = EXTRA_PIXCLK_CYCLE_DELAY; > + tu.diff_abs_fp = tu.resulting_valid_fp - tu.ratio_by_tu_fp; > + > + temp = drm_fixp2int(tu.diff_abs_fp); > + if (!temp && tu.diff_abs_fp <= 0xffff) > + tu.diff_abs_fp = 0; > + > + /* if(diff_abs < 0) diff_abs *= -1 */ > + if (tu.diff_abs_fp < 0) > + tu.diff_abs_fp = drm_fixp_mul(tu.diff_abs_fp, -1); > + > + tu.boundary_mod_lower_err = 0; > + if ((tu.diff_abs_fp != 0 && > + ((tu.diff_abs_fp > BRUTE_FORCE_THRESHOLD_fp) || > + (tu.even_distribution_legacy == 0) || > + (DP_BRUTE_FORCE == 1))) || > + (tu.min_hblank_violated == 1)) { > + do { > + tu.err_fp = drm_fixp_from_fraction(1000, 1); > + > + temp1_fp = drm_fixp_div(tu.lclk_fp, tu.pclk_fp); > + temp2_fp = drm_fixp_from_fraction( > + tu.delay_start_link_extra_pixclk, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + > + if (temp1_fp) > + tu.extra_buffer_margin = > + drm_fixp2int_ceil(temp1_fp); > + else > + tu.extra_buffer_margin = 0; > + > + temp1_fp = drm_fixp_from_fraction(tu.bpp, 8); > + temp2_fp = drm_fixp_from_fraction(tu.lwidth, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + > + if (temp1_fp) > + tu.n_symbols = drm_fixp2int_ceil(temp1_fp); > + else > + tu.n_symbols = 0; > + > + for (tu.tu_size = 32; tu.tu_size <= 64; tu.tu_size++) { > + for (tu.i_upper_boundary_count = 1; > + tu.i_upper_boundary_count <= 15; > + tu.i_upper_boundary_count++) { > + for (tu.i_lower_boundary_count = 1; > + tu.i_lower_boundary_count <= 15; > + tu.i_lower_boundary_count++) { > + _tu_valid_boundary_calc(&tu); > + } > + } > + } > + tu.delay_start_link_extra_pixclk--; > + } while (tu.boundary_moderation_en != true && > + tu.boundary_mod_lower_err == 1 && > + tu.delay_start_link_extra_pixclk != 0); > + > + if (tu.boundary_moderation_en == true) { > + temp1_fp = drm_fixp_from_fraction( > + (tu.upper_boundary_count * > + tu.valid_boundary_link + > + tu.lower_boundary_count * > + (tu.valid_boundary_link - 1)), 1); > + temp2_fp = drm_fixp_from_fraction( > + (tu.upper_boundary_count + > + tu.lower_boundary_count), 1); > + tu.resulting_valid_fp = > + drm_fixp_div(temp1_fp, temp2_fp); > + > + temp1_fp = drm_fixp_from_fraction( > + tu.tu_size_desired, 1); > + tu.ratio_by_tu_fp = > + drm_fixp_mul(tu.original_ratio_fp, temp1_fp); > + > + tu.valid_lower_boundary_link = > + tu.valid_boundary_link - 1; > + > + temp1_fp = drm_fixp_from_fraction(tu.bpp, 8); > + temp2_fp = drm_fixp_from_fraction(tu.lwidth, 1); > + temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp); > + temp2_fp = drm_fixp_div(temp1_fp, > + tu.resulting_valid_fp); > + tu.n_tus = drm_fixp2int(temp2_fp); > + > + tu.tu_size_minus1 = tu.tu_size_desired - 1; > + tu.even_distribution_BF = 1; > + > + temp1_fp = > + drm_fixp_from_fraction(tu.tu_size_desired, 1); > + temp2_fp = > + drm_fixp_div(tu.resulting_valid_fp, temp1_fp); > + tu.TU_ratio_err_fp = temp2_fp - tu.original_ratio_fp; > + } > + } > + > + temp1_fp = drm_fixp_from_fraction(tu.lwidth, 1); > + temp2_fp = drm_fixp_mul(LCLK_FAST_SKEW_fp, temp1_fp); > + > + if (temp2_fp) > + temp = drm_fixp2int_ceil(temp2_fp); > + else > + temp = 0; > + > + temp1_fp = drm_fixp_from_fraction(tu.nlanes, 1); > + temp2_fp = drm_fixp_mul(tu.original_ratio_fp, temp1_fp); > + temp1_fp = drm_fixp_from_fraction(tu.bpp, 8); > + temp2_fp = drm_fixp_div(temp1_fp, temp2_fp); > + temp1_fp = drm_fixp_from_fraction(temp, 1); > + temp2_fp = drm_fixp_mul(temp1_fp, temp2_fp); > + temp = drm_fixp2int(temp2_fp); > + > + if (tu.async_en) > + tu.delay_start_link += (int)temp; > + > + temp1_fp = drm_fixp_from_fraction(tu.delay_start_link, 1); > + tu.delay_start_time_fp = drm_fixp_div(temp1_fp, tu.lclk_fp); > + > + /* OUTPUTS */ > + tu_table->valid_boundary_link = tu.valid_boundary_link; > + tu_table->delay_start_link = tu.delay_start_link; > + tu_table->boundary_moderation_en = tu.boundary_moderation_en; > + tu_table->valid_lower_boundary_link = tu.valid_lower_boundary_link; > + tu_table->upper_boundary_count = tu.upper_boundary_count; > + tu_table->lower_boundary_count = tu.lower_boundary_count; > + tu_table->tu_size_minus1 = tu.tu_size_minus1; > + > + DRM_DEBUG_DP("TU: valid_boundary_link: %d\n", tu_table->valid_boundary_link); > + DRM_DEBUG_DP("TU: delay_start_link: %d\n", tu_table->delay_start_link); > + DRM_DEBUG_DP("TU: boundary_moderation_en: %d\n", > + tu_table->boundary_moderation_en); > + DRM_DEBUG_DP("TU: valid_lower_boundary_link: %d\n", > + tu_table->valid_lower_boundary_link); > + DRM_DEBUG_DP("TU: upper_boundary_count: %d\n", > + tu_table->upper_boundary_count); > + DRM_DEBUG_DP("TU: lower_boundary_count: %d\n", > + tu_table->lower_boundary_count); > + DRM_DEBUG_DP("TU: tu_size_minus1: %d\n", tu_table->tu_size_minus1); > +} > + > +static void dp_ctrl_calc_tu_parameters(struct dp_ctrl_private *ctrl, > + struct dp_vc_tu_mapping_table *tu_table) > +{ > + struct dp_tu_calc_input in; > + struct drm_display_mode *drm_mode; > + > + drm_mode = &ctrl->panel->dp_mode.drm_mode; > + > + in.lclk = ctrl->link->link_params.rate / 1000; > + in.pclk_khz = drm_mode->clock; > + in.hactive = drm_mode->hdisplay; > + in.hporch = drm_mode->htotal - drm_mode->hdisplay; > + in.nlanes = ctrl->link->link_params.num_lanes; > + in.bpp = ctrl->panel->dp_mode.bpp; > + in.pixel_enc = 444; > + in.dsc_en = 0; > + in.async_en = 0; > + > + _dp_ctrl_calc_tu(&in, tu_table); > +} > + > +static void dp_ctrl_setup_tr_unit(struct dp_ctrl_private *ctrl) > +{ > + u32 dp_tu = 0x0; > + u32 valid_boundary = 0x0; > + u32 valid_boundary2 = 0x0; > + struct dp_vc_tu_mapping_table tu_calc_table; > + > + dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table); > + > + dp_tu |= tu_calc_table.tu_size_minus1; > + valid_boundary |= tu_calc_table.valid_boundary_link; > + valid_boundary |= (tu_calc_table.delay_start_link << 16); > + > + valid_boundary2 |= (tu_calc_table.valid_lower_boundary_link << 1); > + valid_boundary2 |= (tu_calc_table.upper_boundary_count << 16); > + valid_boundary2 |= (tu_calc_table.lower_boundary_count << 20); > + > + if (tu_calc_table.boundary_moderation_en) > + valid_boundary2 |= BIT(0); > + > + pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n", > + dp_tu, valid_boundary, valid_boundary2); Like here - this should be DRM_DEBUG_DP? > + > + ctrl->catalog->dp_tu = dp_tu; > + ctrl->catalog->valid_boundary = valid_boundary; > + ctrl->catalog->valid_boundary2 = valid_boundary2; > + > + dp_catalog_ctrl_update_transfer_unit(ctrl->catalog); > +} > + > +static int dp_ctrl_wait4video_ready(struct dp_ctrl_private *ctrl) > +{ > + int ret = 0; > + > + if (!wait_for_completion_timeout(&ctrl->video_comp, > + WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES)) { > + DRM_ERROR("Link Train timedout\n"); > + ret = -ETIMEDOUT; > + } > + return ret; > +} > + > +static int dp_ctrl_update_vx_px(struct dp_ctrl_private *ctrl) > +{ > + struct dp_link *link = ctrl->link; > + int ret = 0, lane; > + u8 buf[4]; > + u32 max_level_reached = 0; > + u32 voltage_swing_level = link->phy_params.v_level; > + u32 pre_emphasis_level = link->phy_params.p_level; > + > + ret = dp_catalog_ctrl_update_vx_px(ctrl->catalog, > + voltage_swing_level, pre_emphasis_level); > + if (ret) > + return ret; > + > + if (voltage_swing_level == DP_TRAIN_VOLTAGE_SWING_MAX) { > + pr_debug("max. voltage swing level reached %d\n", > + voltage_swing_level); > + max_level_reached |= DP_TRAIN_MAX_SWING_REACHED; > + } > + > + if (pre_emphasis_level == DP_TRAIN_PRE_EMPHASIS_MAX) { > + pr_debug("max. pre-emphasis level reached %d\n", > + pre_emphasis_level); > + max_level_reached |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; > + } > + > + pre_emphasis_level <<= DP_TRAIN_PRE_EMPHASIS_SHIFT; > + > + for (lane = 0; lane < 4; lane++) > + buf[lane] = voltage_swing_level | pre_emphasis_level | max_level_reached; > + > + pr_debug("sink: p|v=0x%x\n", voltage_swing_level | pre_emphasis_level); > + ret = drm_dp_dpcd_write(ctrl->aux->drm_aux, DP_TRAINING_LANE0_SET, buf, 4); > + if (ret == 4) > + ret = 0; > + > + return ret; > +} > + > +static bool dp_ctrl_train_pattern_set(struct dp_ctrl_private *ctrl, > + u8 pattern) > +{ > + u8 buf[4]; > + int ret = 0; > + > + pr_debug("sink: pattern=%x\n", pattern); > + > + buf[0] = pattern; > + ret = drm_dp_dpcd_writeb(ctrl->aux->drm_aux, DP_TRAINING_PATTERN_SET, buf[0]); > + return ret == 1; > +} > + > +static int dp_ctrl_read_link_status(struct dp_ctrl_private *ctrl, > + u8 *link_status) > +{ > + int len = 0; > + u32 const offset = DP_LANE_ALIGN_STATUS_UPDATED - DP_LANE0_1_STATUS; > + u32 link_status_read_max_retries = 100; > + > + while (--link_status_read_max_retries) { > + len = drm_dp_dpcd_read_link_status(ctrl->aux->drm_aux, > + link_status); > + if (len != DP_LINK_STATUS_SIZE) { > + DRM_ERROR("DP link status read failed, err: %d\n", len); > + return len; > + } > + > + if (!(link_status[offset] & DP_LINK_STATUS_UPDATED)) > + return 0; > + } > + > + return -ETIMEDOUT; > +} > + > +static int dp_ctrl_link_train_1(struct dp_ctrl_private *ctrl) > +{ > + int tries, old_v_level, ret = 0; > + u8 link_status[DP_LINK_STATUS_SIZE]; > + int const maximum_retries = 5; > + > + dp_catalog_ctrl_state_ctrl(ctrl->catalog, 0); > + > + ret = dp_catalog_ctrl_set_pattern(ctrl->catalog, DP_TRAINING_PATTERN_1); > + if (ret) > + return ret; > + dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 | > + DP_LINK_SCRAMBLING_DISABLE); > + ret = dp_ctrl_update_vx_px(ctrl); > + if (ret) > + return ret; > + > + tries = 0; > + old_v_level = ctrl->link->phy_params.v_level; > + for (tries = 0; tries < maximum_retries; tries++) { > + drm_dp_link_train_clock_recovery_delay(ctrl->panel->dpcd); > + > + ret = dp_ctrl_read_link_status(ctrl, link_status); > + if (ret) > + return ret; > + > + if (drm_dp_clock_recovery_ok(link_status, > + ctrl->link->link_params.num_lanes)) { > + return ret; > + } > + > + if (ctrl->link->phy_params.v_level == DP_TRAIN_VOLTAGE_SWING_MAX) { > + DRM_ERROR_RATELIMITED("max v_level reached\n"); > + return -EAGAIN; > + } > + > + if (old_v_level != ctrl->link->phy_params.v_level) { > + tries = 0; > + old_v_level = ctrl->link->phy_params.v_level; > + } > + > + pr_debug("clock recovery not done, adjusting vx px\n"); > + > + dp_link_adjust_levels(ctrl->link, link_status); > + ret = dp_ctrl_update_vx_px(ctrl); > + if (ret) > + return ret; > + } > + > + DRM_ERROR("max tries reached\n"); > + return -ETIMEDOUT; > +} > + > +static int dp_ctrl_link_rate_down_shift(struct dp_ctrl_private *ctrl) > +{ > + int ret = 0; > + > + switch (ctrl->link->link_params.rate) { > + case 810000: > + ctrl->link->link_params.rate = 540000; > + break; > + case 540000: > + ctrl->link->link_params.rate = 270000; > + break; > + case 270000: > + case 162000: > + default: > + ctrl->link->link_params.rate = 162000; > + break; > + }; > + > + pr_debug("new rate=0x%x\n", ctrl->link->link_params.rate); > + > + return ret; ret is unused in this function - return 0; > +} > + > +static void dp_ctrl_clear_training_pattern(struct dp_ctrl_private *ctrl) > +{ > + dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_DISABLE); > + drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd); > +} > + > +static int dp_ctrl_link_training_2(struct dp_ctrl_private *ctrl) > +{ > + int tries = 0, ret = 0; > + char pattern; > + int const maximum_retries = 5; > + u8 link_status[DP_LINK_STATUS_SIZE]; > + > + dp_catalog_ctrl_state_ctrl(ctrl->catalog, 0); > + > + if (drm_dp_tps3_supported(ctrl->panel->dpcd)) > + pattern = DP_TRAINING_PATTERN_3; > + else > + pattern = DP_TRAINING_PATTERN_2; > + > + ret = dp_ctrl_update_vx_px(ctrl); > + if (ret) > + return ret; > + > + ret = dp_catalog_ctrl_set_pattern(ctrl->catalog, pattern); > + if (ret) > + return ret; > + > + dp_ctrl_train_pattern_set(ctrl, pattern | DP_RECOVERED_CLOCK_OUT_EN); > + > + for (tries = 0; tries <= maximum_retries; tries++) { > + drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd); > + > + ret = dp_ctrl_read_link_status(ctrl, link_status); > + if (ret) > + return ret; > + > + if (drm_dp_channel_eq_ok(link_status, > + ctrl->link->link_params.num_lanes)) > + return ret; > + > + dp_link_adjust_levels(ctrl->link, link_status); > + ret = dp_ctrl_update_vx_px(ctrl); > + if (ret) > + return ret; > + > + } > + > + return -ETIMEDOUT; > +} > + > +static int dp_ctrl_link_train(struct dp_ctrl_private *ctrl) > +{ > + int ret = 0; > + u8 encoding = DP_SET_ANSI_8B10B; > + struct drm_dp_link link_info = {0}; > + > + ctrl->link->phy_params.p_level = 0; > + ctrl->link->phy_params.v_level = 0; > + > + dp_ctrl_config_ctrl(ctrl); > + > + ret = drm_dp_link_probe(ctrl->aux->drm_aux, &link_info); > + if (ret < 0) { > + DRM_ERROR("failed to retrieve link capabilities. ret=%d\n", ret); > + dp_catalog_ctrl_state_ctrl(ctrl->catalog, 0); > + return ret; > + } > + > + drm_dp_link_configure(ctrl->aux->drm_aux, &link_info); > + drm_dp_dpcd_write(ctrl->aux->drm_aux, DP_MAIN_LINK_CHANNEL_CODING_SET, > + &encoding, 1); > + > + ret = dp_ctrl_link_train_1(ctrl); > + if (ret) { > + DRM_ERROR("link training #1 failed. ret=%d\n", ret); > + goto end; > + } > + > + /* print success info as this is a result of user initiated action */ > + DRM_DEBUG_DP("link training #1 successful\n"); > + > + ret = dp_ctrl_link_training_2(ctrl); > + if (ret) { > + DRM_ERROR("link training #2 failed. ret=%d\n", ret); > + goto end; > + } > + > + /* print success info as this is a result of user initiated action */ > + DRM_DEBUG_DP("link training #2 successful\n"); > + > +end: > + dp_catalog_ctrl_state_ctrl(ctrl->catalog, 0); > + > + dp_ctrl_clear_training_pattern(ctrl); > + return ret; > +} > + > +static int dp_ctrl_setup_main_link(struct dp_ctrl_private *ctrl, bool train) > +{ > + bool mainlink_ready = false; > + int ret = 0; > + > + dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, true); > + > + ret = dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, false); > + if (ret) > + return ret; > + > + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) > + return ret; > + > + if (!train) > + goto send_video; > + > + /* > + * As part of previous calls, DP controller state might have > + * transitioned to PUSH_IDLE. In order to start transmitting a link > + * training pattern, we have to first do a DP software reset. > + */ > + dp_catalog_ctrl_reset(ctrl->catalog); > + > + ret = dp_ctrl_link_train(ctrl); > + if (ret) > + return ret; > + > +send_video: > + /* > + * Set up transfer unit values and set controller state to send > + * video. > + */ > + dp_ctrl_setup_tr_unit(ctrl); > + dp_catalog_ctrl_state_ctrl(ctrl->catalog, DP_STATE_CTRL_SEND_VIDEO); > + > + ret = dp_ctrl_wait4video_ready(ctrl); > + if (ret) > + return ret; > + > + mainlink_ready = dp_catalog_ctrl_mainlink_ready(ctrl->catalog); > + DRM_DEBUG_DP("mainlink %s\n", mainlink_ready ? "READY" : "NOT READY"); > + return ret; > +} > + > +static void dp_ctrl_set_clock_rate(struct dp_ctrl_private *ctrl, > + char *name, u32 rate) > +{ > + u32 num = ctrl->parser->mp[DP_CTRL_PM].num_clk; > + struct dss_clk *cfg = ctrl->parser->mp[DP_CTRL_PM].clk_config; > + > + while (num && strcmp(cfg->clk_name, name)) { > + num--; > + cfg++; > + } Why are you walking backwards in this list? Seems that going forward is clearer. for(num = 0; num < ctrl->parser->mp[DP_CTRL_PM].num_clk; num++, cfg++) { if (strcmp(cfg->clk_name, name)) { cfg->rate = rate; return; } } /* error message */ > + > + DRM_DEBUG_DP("setting rate=%d on clk=%s\n", rate, name); > + > + if (num) > + cfg->rate = rate; > + else > + DRM_ERROR("%s clock could not be set with rate %d\n", name, rate); This isn't exactly accurate - you aren't setting anything in the hardware but the clock doesn't exist in your list. You should say that it doesn't. > +} > + > +static int dp_ctrl_enable_mainlink_clocks(struct dp_ctrl_private *ctrl) > +{ > + int ret = 0; > + > + dp_power_set_link_clk_parent(ctrl->power); > + dp_power_set_pixel_clk_parent(ctrl->power); > + > + dp_ctrl_set_clock_rate(ctrl, "ctrl_link_clk", ctrl->link->link_params.rate); It will probably come up elsewhere too, but the clock names shouldn't have the _clk extension - they are only named in one spot and we know they are clocks. You should just name it "ctrl_link" (and update the DT and bindings of course). > + > + dp_ctrl_set_clock_rate(ctrl, "ctrl_pixel_clk", ctrl->dp_ctrl.pixel_rate); Same. > + > + ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, true); > + if (ret) > + DRM_ERROR("Unabled to start link clocks. ret=%d\n", ret); > + > + return ret; > +} > + > +int dp_ctrl_host_init(struct dp_ctrl *dp_ctrl, bool flip) > +{ > + struct dp_ctrl_private *ctrl; > + > + if (!dp_ctrl) { > + DRM_ERROR("Invalid input data\n"); > + return -EINVAL; > + } > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + ctrl->dp_ctrl.orientation = flip; > + > + dp_catalog_ctrl_usb_reset(ctrl->catalog, flip); > + dp_catalog_ctrl_phy_reset(ctrl->catalog); > + dp_catalog_ctrl_enable_irq(ctrl->catalog, true); > + > + return 0; > +} > + > +/** > + * dp_ctrl_host_deinit() - Uninitialize DP controller > + * @ctrl: Display Port Driver data > + * > + * Perform required steps to uninitialize DP controller > + * and its resources. > + */ > +void dp_ctrl_host_deinit(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + > + if (!dp_ctrl) { > + DRM_ERROR("Invalid input data\n"); If you are de-initing, a error message isn't needed here. > + return; > + } > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + dp_catalog_ctrl_enable_irq(ctrl->catalog, false); > + > + DRM_DEBUG_DP("Host deinitialized successfully\n"); > +} > + > +static bool dp_ctrl_use_fixed_nvid(struct dp_ctrl_private *ctrl) > +{ > + u8 *dpcd = ctrl->panel->dpcd; > + > + /* > + * For better interop experience, used a fixed NVID=0x8000 > + * whenever connected to a VGA dongle downstream. > + */ > + if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) { > + u8 type = dpcd[DP_DOWNSTREAMPORT_PRESENT] & > + DP_DWN_STRM_PORT_TYPE_MASK; > + if (type == DP_DWN_STRM_PORT_TYPE_ANALOG) > + return true; > + } > + > + return false; > +} > + > +static int dp_ctrl_link_maintenance(struct dp_ctrl_private *ctrl) > +{ > + int ret = 0; > + > + dp_ctrl_push_idle(&ctrl->dp_ctrl); > + dp_ctrl_reset(&ctrl->dp_ctrl); > + > + ctrl->dp_ctrl.pixel_rate = ctrl->panel->dp_mode.drm_mode.clock; > + > + do { > + if (ret == -EAGAIN) { > + /* try with lower link rate */ > + dp_ctrl_link_rate_down_shift(ctrl); > + > + dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false); > + } > + > + dp_catalog_ctrl_phy_lane_cfg(ctrl->catalog, > + ctrl->dp_ctrl.orientation, ctrl->link->link_params.num_lanes); > + > + /* > + * Disable and re-enable the mainlink clock since the > + * link clock might have been adjusted as part of the > + * link maintenance. > + */ > + ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false); > + if (ret) { > + DRM_ERROR("Failed to disable clocks. ret=%d\n", ret); > + break; > + } > + > + ret = dp_ctrl_enable_mainlink_clocks(ctrl); > + if (ret) { > + DRM_ERROR("Failed to enable mainlink clks. ret=%d\n", ret); > + break; > + } > + > + dp_ctrl_configure_source_params(ctrl); > + > + dp_catalog_ctrl_config_msa(ctrl->catalog, > + ctrl->link->link_params.rate, > + ctrl->dp_ctrl.pixel_rate, dp_ctrl_use_fixed_nvid(ctrl)); > + > + reinit_completion(&ctrl->idle_comp); > + > + ret = dp_ctrl_setup_main_link(ctrl, true); > + } while (ret == -EAGAIN); > + > + return ret; > +} > + > +static void dp_ctrl_process_phy_test_request(struct dp_ctrl_private *ctrl) > +{ > + int ret = 0; > + > + if (!ctrl->link->phy_params.phy_test_pattern_sel) { > + DRM_DEBUG_DP("no test pattern selected by sink\n"); > + return; > + } > + > + dp_ctrl_push_idle(&ctrl->dp_ctrl); > + /* > + * The global reset will need DP link related clocks to be > + * running. Add the global reset just before disabling the > + * link clocks and core clocks. > + */ > + dp_ctrl_reset(&ctrl->dp_ctrl); > + dp_ctrl_off(&ctrl->dp_ctrl); > + > + ret = dp_ctrl_on(&ctrl->dp_ctrl); > + if (ret) > + DRM_ERROR("failed to enable DP controller\n"); > + > +} > + > +static bool dp_ctrl_send_phy_test_pattern(struct dp_ctrl_private *ctrl) > +{ > + bool success = false; > + u32 pattern_sent = 0x0; > + u32 pattern_requested = ctrl->link->phy_params.phy_test_pattern_sel; > + > + DRM_DEBUG_DP("request: 0x%x\n", pattern_requested); > + > + if (dp_catalog_ctrl_update_vx_px(ctrl->catalog, > + ctrl->link->phy_params.v_level, > + ctrl->link->phy_params.p_level)) { > + DRM_ERROR("Failed to set v/p levels\n"); > + return false; > + } > + dp_catalog_ctrl_send_phy_pattern(ctrl->catalog, pattern_requested); > + dp_link_send_test_response(ctrl->link); > + > + pattern_sent = dp_catalog_ctrl_read_phy_pattern(ctrl->catalog); > + > + switch (pattern_sent) { > + case MR_LINK_TRAINING1: > + success = pattern_requested == > + DP_LINK_QUAL_PATTERN_D10_2; > + break; > + case MR_LINK_SYMBOL_ERM: > + success = (pattern_requested == > + DP_LINK_QUAL_PATTERN_ERROR_RATE) > + || (pattern_requested == > + DP_LINK_QUAL_PATTERN_HBR2_EYE); > + break; > + case MR_LINK_PRBS7: > + success = pattern_requested == DP_LINK_QUAL_PATTERN_PRBS7; > + break; > + case MR_LINK_CUSTOM80: > + success = pattern_requested == > + DP_LINK_QUAL_PATTERN_80BIT_CUSTOM; > + break; > + default: > + success = false; > + } > + > + DRM_DEBUG_DP("%s: test->0x%x\n", success ? "success" : "failed", > + pattern_requested); > + return success; > +} > + > +void dp_ctrl_handle_sink_request(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + u32 sink_request = 0x0; > + > + if (!dp_ctrl) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + sink_request = ctrl->link->sink_request; > + > + if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { > + DRM_DEBUG_DP("PHY_TEST_PATTERN request\n"); > + dp_ctrl_process_phy_test_request(ctrl); > + } > + > + if (sink_request & DP_LINK_STATUS_UPDATED) > + dp_ctrl_link_maintenance(ctrl); > + > + if (sink_request & DP_TEST_LINK_TRAINING) { > + dp_link_send_test_response(ctrl->link); > + dp_ctrl_link_maintenance(ctrl); > + } > +} > + > +void dp_ctrl_reset(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + > + if (!dp_ctrl) { > + DRM_ERROR("invalid params\n"); > + return; > + } > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + dp_catalog_ctrl_reset(ctrl->catalog); > +} > + > +int dp_ctrl_on(struct dp_ctrl *dp_ctrl) > +{ > + int rc = 0; > + struct dp_ctrl_private *ctrl; > + u32 rate = 0; > + u32 link_train_max_retries = 100; > + u32 const phy_cts_pixel_clk_khz = 148500; > + > + if (!dp_ctrl) { > + rc = -EINVAL; This can just return -EINVAL; > + goto end; > + } > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + atomic_set(&ctrl->dp_ctrl.aborted, 0); > + rate = ctrl->panel->link_info.rate; > + > + dp_power_clk_enable(ctrl->power, DP_CORE_PM, true); > + dp_catalog_ctrl_hpd_config(ctrl->catalog, true); > + > + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { > + DRM_DEBUG_DP("using phy test link parameters\n"); > + if (!ctrl->panel->dp_mode.drm_mode.clock) > + ctrl->dp_ctrl.pixel_rate = phy_cts_pixel_clk_khz; > + } else { > + ctrl->link->link_params.rate = rate; > + ctrl->link->link_params.num_lanes = > + ctrl->panel->link_info.num_lanes; > + ctrl->dp_ctrl.pixel_rate = ctrl->panel->dp_mode.drm_mode.clock; > + } > + > + DRM_DEBUG_DP("rate=%d, num_lanes=%d, pixel_rate=%d\n", > + ctrl->link->link_params.rate, > + ctrl->link->link_params.num_lanes, ctrl->dp_ctrl.pixel_rate); > + > + dp_catalog_ctrl_phy_lane_cfg(ctrl->catalog, > + ctrl->dp_ctrl.orientation, ctrl->link->link_params.num_lanes); > + > + rc = dp_ctrl_enable_mainlink_clocks(ctrl); > + if (rc) Return rc; > + goto end; > + > + reinit_completion(&ctrl->idle_comp); > + > + dp_ctrl_configure_source_params(ctrl); > + > + while (--link_train_max_retries && !atomic_read(&ctrl->dp_ctrl.aborted)) { > + dp_catalog_ctrl_config_msa(ctrl->catalog, > + ctrl->link->link_params.rate, > + ctrl->dp_ctrl.pixel_rate, dp_ctrl_use_fixed_nvid(ctrl)); > + > + rc = dp_ctrl_setup_main_link(ctrl, true); > + if (!rc) > + break; > + > + /* try with lower link rate */ > + dp_ctrl_link_rate_down_shift(ctrl); > + > + dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false); > + > + rc = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false); > + if (rc) { > + DRM_ERROR("Failed to disable clocks. ret=%d\n", rc); > + break; > + } > + /* hw recommended delay before re-enabling clocks */ > + msleep(20); > + > + dp_ctrl_enable_mainlink_clocks(ctrl); > + } > + > + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) > + dp_ctrl_send_phy_test_pattern(ctrl); > + > + DRM_DEBUG_DP("End-\n"); > + > +end: Label isn't needed. > + return rc; > +} > + > +void dp_ctrl_off(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + > + if (!dp_ctrl) > + return; > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false); > + dp_catalog_ctrl_reset(ctrl->catalog); > + dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false); > + > + DRM_DEBUG_DP("DP off done\n"); > +} > + > +void dp_ctrl_isr(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + > + if (!dp_ctrl) > + return; > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + dp_catalog_ctrl_get_interrupt(ctrl->catalog); > + > + if (ctrl->catalog->isr & DP_CTRL_INTR_READY_FOR_VIDEO) { > + DRM_DEBUG_DP("dp_video_ready\n"); > + complete(&ctrl->video_comp); > + } > + > + if (ctrl->catalog->isr & DP_CTRL_INTR_IDLE_PATTERN_SENT) { > + DRM_DEBUG_DP("idle_patterns_sent\n"); > + complete(&ctrl->idle_comp); > + } > +} > + > +struct dp_ctrl *dp_ctrl_get(struct device *dev, struct dp_link *link, struct dp_panel *panel, > + struct dp_aux *aux, struct dp_power *power, struct dp_catalog_ctrl *catalog, > + struct dp_parser *parser) > +{ > + int rc = 0; > + struct dp_ctrl_private *ctrl; > + struct dp_ctrl *dp_ctrl; > + > + if (!dev || !panel || !aux || > + !link || !catalog) { Return ERR_PTR(-EINVAL) but this feels like one of those situations where you can feel pretty sure that the caller will populate everything correctly. > + DRM_ERROR("invalid input\n"); > + rc = -EINVAL; > + goto error; > + } > + > + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); > + if (!ctrl) { > + rc = -ENOMEM; return ERR_PTR(-ENOMEM) > + goto error; > + } > + > + init_completion(&ctrl->idle_comp); > + init_completion(&ctrl->video_comp); > + mutex_init(&ctrl->push_idle_mutex); > + > + /* in parameters */ > + ctrl->parser = parser; > + ctrl->panel = panel; > + ctrl->power = power; > + ctrl->aux = aux; > + ctrl->link = link; > + ctrl->catalog = catalog; > + ctrl->dev = dev; > + > + dp_ctrl = &ctrl->dp_ctrl; > + > + return dp_ctrl; > +error: > + return ERR_PTR(rc); Not needed. > +} > + > +void dp_ctrl_put(struct dp_ctrl *dp_ctrl) > +{ > + struct dp_ctrl_private *ctrl; > + > + if (!dp_ctrl) > + return; > + > + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); > + > + mutex_destroy(&ctrl->push_idle_mutex); You don't need to destroy the mutex if the memory is getting cleared in the next step. > + > + devm_kfree(ctrl->dev, ctrl); As with the other usages of devm_kfree() > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h > new file mode 100644 > index 0000000..d14c807 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h > @@ -0,0 +1,36 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_CTRL_H_ > +#define _DP_CTRL_H_ > + > +#include "dp_aux.h" > +#include "dp_panel.h" > +#include "dp_link.h" > +#include "dp_parser.h" > +#include "dp_power.h" > +#include "dp_catalog.h" > + > +struct dp_ctrl { > + bool orientation; > + atomic_t aborted; > + u32 pixel_rate; > +}; > + > +int dp_ctrl_host_init(struct dp_ctrl *dp_ctrl, bool flip); > +void dp_ctrl_host_deinit(struct dp_ctrl *dp_ctrl); > +int dp_ctrl_on(struct dp_ctrl *dp_ctrl); > +void dp_ctrl_off(struct dp_ctrl *dp_ctrl); > +void dp_ctrl_push_idle(struct dp_ctrl *dp_ctrl); > +void dp_ctrl_abort(struct dp_ctrl *dp_ctrl); > +void dp_ctrl_isr(struct dp_ctrl *dp_ctrl); > +void dp_ctrl_reset(struct dp_ctrl *dp_ctrl); > +void dp_ctrl_handle_sink_request(struct dp_ctrl *dp_ctrl); > +struct dp_ctrl *dp_ctrl_get(struct device *dev, struct dp_link *link, struct dp_panel *panel, > + struct dp_aux *aux, struct dp_power *power, struct dp_catalog_ctrl *catalog, > + struct dp_parser *parser); > +void dp_ctrl_put(struct dp_ctrl *dp_ctrl); > + > +#endif /* _DP_CTRL_H_ */ > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c > new file mode 100644 > index 0000000..1b0682a > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_display.c > @@ -0,0 +1,941 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include <linux/module.h> > +#include <linux/slab.h> > +#include <linux/uaccess.h> > +#include <linux/debugfs.h> > +#include <linux/component.h> > +#include <linux/of_irq.h> > + > +#include "msm_drv.h" > +#include "dp_extcon.h" > +#include "dp_parser.h" > +#include "dp_power.h" > +#include "dp_catalog.h" > +#include "dp_aux.h" > +#include "dp_link.h" > +#include "dp_panel.h" > +#include "dp_ctrl.h" > +#include "dp_display.h" > +#include "dp_drm.h" > + > +static struct msm_dp *g_dp_display; > +#define HPD_STRING_SIZE 30 > + > +struct dp_display_private { > + char *name; > + int irq; > + > + /* state variables */ > + bool core_initialized; > + bool power_on; > + bool hpd_irq_on; > + bool audio_supported; > + > + struct platform_device *pdev; > + struct dentry *root; > + struct completion notification_comp; > + > + struct dp_usbpd *usbpd; > + struct dp_parser *parser; > + struct dp_power *power; > + struct dp_catalog *catalog; > + struct dp_aux *aux; > + struct dp_link *link; > + struct dp_panel *panel; > + struct dp_ctrl *ctrl; > + > + struct dp_usbpd_cb usbpd_cb; > + struct dp_display_mode dp_mode; > + struct msm_dp dp_display; > + > +}; > + > +static const struct of_device_id dp_dt_match[] = { > + {.compatible = "qcom,dp-display"}, > + {} This line should have a comma after it. > +}; > + > +static irqreturn_t dp_display_irq(int irq, void *dev_id) > +{ > + struct dp_display_private *dp = dev_id; > + > + if (!dp) { > + DRM_ERROR("invalid data\n"); > + return IRQ_NONE; > + } It is not possible for 'dp' to be NULL. > + > + /* DP controller isr */ > + dp_ctrl_isr(dp->ctrl); > + > + /* DP aux isr */ > + dp_aux_isr(dp->aux); > + > + return IRQ_HANDLED; > +} > + > +static int dp_display_bind(struct device *dev, struct device *master, > + void *data) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + struct drm_device *drm; > + struct msm_drm_private *priv; > + struct platform_device *pdev = to_platform_device(dev); > + > + if (!dev || !pdev || !master) { > + DRM_ERROR("invalid param(s), dev %pK, pdev %pK, master %pK\n", > + dev, pdev, master); > + rc = -EINVAL; > + goto end; > + } It is not possible for any of these to be NULL > + > + drm = dev_get_drvdata(master); > + dp = platform_get_drvdata(pdev); > + if (!drm || !dp) { > + DRM_ERROR("invalid param(s), drm %pK, dp %pK\n", > + drm, dp); > + rc = -EINVAL; > + goto end; > + } It should not be possible for any of these to be NULL either. > + > + dp->dp_display.drm_dev = drm; > + priv = drm->dev_private; > + priv->dp = &(dp->dp_display); > + > + rc = dp->parser->parse(dp->parser); > + if (rc) { > + DRM_ERROR("device tree parsing failed\n"); > + goto end; return rc; > + } > + > + rc = dp_aux_register(dp->aux); > + if (rc) { > + DRM_ERROR("DRM DP AUX register failed\n"); > + goto end; return rc; > + } > + > + rc = dp_power_client_init(dp->power); > + if (rc) { > + DRM_ERROR("Power client create failed\n"); > + goto end; return rc; > + } > + > +end: > + return rc; Label isn't needed and you can just return 0; > +} > + > +static void dp_display_unbind(struct device *dev, struct device *master, > + void *data) > +{ > + struct dp_display_private *dp; > + struct platform_device *pdev = to_platform_device(dev); > + struct drm_device *drm = dev_get_drvdata(master); > + struct msm_drm_private *priv = drm->dev_private; > + > + if (!dev || !pdev) { > + DRM_ERROR("invalid param(s)\n"); > + return; > + } These can never be NULL. > + > + dp = platform_get_drvdata(pdev); > + if (!dp) { > + DRM_ERROR("Invalid params\n"); > + return; > + } Neither can this, I don't believe. > + > + (void)dp_power_client_deinit(dp->power); dp_power_client_deinit is already returning void. > + (void)dp_aux_unregister(dp->aux); Same. > + priv->dp = NULL; > +} > + > +static const struct component_ops dp_display_comp_ops = { > + .bind = dp_display_bind, > + .unbind = dp_display_unbind, > +}; > + > +static bool dp_display_is_ds_bridge(struct dp_panel *panel) > +{ > + return (panel->dpcd[DP_DOWNSTREAMPORT_PRESENT] & > + DP_DWN_STRM_PORT_PRESENT); > +} > + > +static bool dp_display_is_sink_count_zero(struct dp_display_private *dp) > +{ > + return dp_display_is_ds_bridge(dp->panel) && > + (dp->link->sink_count == 0); > +} > + > +static void dp_display_send_hpd_event(struct msm_dp *dp_display) > +{ > + struct dp_display_private *dp; > + struct drm_connector *connector; > + > + dp = container_of(dp_display, struct dp_display_private, dp_display); > + if (!dp) { The result from container_of() can never be NULL. > + DRM_ERROR("invalid params\n"); > + return; > + } > + > + connector = dp->dp_display.connector; > + drm_helper_hpd_irq_event(connector->dev); > +} > + > +static int dp_display_send_hpd_notification(struct dp_display_private *dp, > + bool hpd) > +{ > + if ((hpd && dp->dp_display.is_connected) || > + (!hpd && !dp->dp_display.is_connected)) { > + DRM_DEBUG_DP("HPD already %s\n", (hpd ? "on" : "off")); > + return 0; > + } > + > + /* reset video pattern flag on disconnect */ > + if (!hpd) > + dp->panel->video_test = false; > + > + dp->dp_display.is_connected = hpd; > + reinit_completion(&dp->notification_comp); > + dp_display_send_hpd_event(&dp->dp_display); > + > + if (!wait_for_completion_timeout(&dp->notification_comp, HZ * 2)) { > + pr_warn("%s timeout\n", hpd ? "connect" : "disconnect"); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int dp_display_process_hpd_high(struct dp_display_private *dp) > +{ > + int rc = 0; > + struct edid *edid; > + > + dp_aux_init(dp->aux, dp->parser->aux_cfg); > + > + if (dp->link->psm_enabled) > + goto notify; > + > + rc = dp_panel_read_sink_caps(dp->panel, dp->dp_display.connector); > + if (rc) > + goto notify; > + > + dp_link_process_request(dp->link); > + > + if (dp_display_is_sink_count_zero(dp)) { > + DRM_DEBUG_DP("no downstream devices connected\n"); > + rc = -EINVAL; You can can just return -EINVAL; > + goto end; > + } > + > + edid = dp->panel->edid; > + > + dp->audio_supported = drm_detect_monitor_audio(edid); > + > + dp_panel_handle_sink_request(dp->panel); > + > + dp->dp_display.max_pclk_khz = dp->parser->max_pclk_khz; > +notify: > + dp_display_send_hpd_notification(dp, true); > + > +end: > + return rc; Return 0 and the label isn't needed. > +} > + > +static void dp_display_host_init(struct dp_display_private *dp) > +{ > + bool flip = false; > + > + if (dp->core_initialized) { > + DRM_DEBUG_DP("DP core already initialized\n"); > + return; > + } > + > + if (dp->usbpd->orientation == ORIENTATION_CC2) > + flip = true; > + > + dp_power_init(dp->power, flip); > + dp_ctrl_host_init(dp->ctrl, flip); > + enable_irq(dp->irq); > + dp->core_initialized = true; > +} > + > +static void dp_display_host_deinit(struct dp_display_private *dp) > +{ > + if (!dp->core_initialized) { > + DRM_DEBUG_DP("DP core already off\n"); > + return; > + } > + > + dp_ctrl_host_deinit(dp->ctrl); > + dp_aux_deinit(dp->aux); > + dp_power_deinit(dp->power); > + disable_irq(dp->irq); > + dp->core_initialized = false; > +} > + > +static void dp_display_process_hpd_low(struct dp_display_private *dp) > +{ > + /* cancel any pending request */ > + dp_ctrl_abort(dp->ctrl); > + > + dp_display_send_hpd_notification(dp, false); > + > + dp_aux_deinit(dp->aux); > +} > + > +static int dp_display_usbpd_configure_cb(struct device *dev) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + if (!dev) { It doesn't appear to me that this will ever be true - there is only one caller. > + DRM_ERROR("invalid dev\n"); Return -EINVAL; > + rc = -EINVAL; > + goto end; > + } > + > + dp = dev_get_drvdata(dev); > + if (!dp) { > + DRM_ERROR("no driver data found\n"); return -ENODEV; > + rc = -ENODEV; > + goto end; > + } > + > + dp_display_host_init(dp); > + > + if (dp->usbpd->hpd_high) > + dp_display_process_hpd_high(dp); > +end: > + return rc; return 0 and the label isn't needed > +} > + > +static void dp_display_clean(struct dp_display_private *dp) > +{ > + dp_ctrl_push_idle(dp->ctrl); > + dp_ctrl_off(dp->ctrl); > +} > + > +static int dp_display_usbpd_disconnect_cb(struct device *dev) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + if (!dev) { As above, I don't think this will ever be NULL > + DRM_ERROR("invalid dev\n"); > + rc = -EINVAL; but if it is, just return -EINVAL; > + goto end; > + } > + > + dp = dev_get_drvdata(dev); > + if (!dp) { > + DRM_ERROR("no driver data found\n"); > + rc = -ENODEV; Return -ENODEV; > + goto end; > + } > + > + /* cancel any pending request */ > + dp_ctrl_abort(dp->ctrl); > + > + rc = dp_display_send_hpd_notification(dp, false); > + > + /* if cable is disconnected, reset psm_enabled flag */ > + if (!dp->usbpd->alt_mode_cfg_done) > + dp->link->psm_enabled = false; > + > + if ((rc < 0) && dp->power_on) > + dp_display_clean(dp); > + > + dp_display_host_deinit(dp); > +end: > + return rc; Remove the label and return 0; > +} > + > +static void dp_display_handle_video_request(struct dp_display_private *dp) > +{ > + if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN) { > + /* force disconnect followed by connect */ > + dp->usbpd->connect(dp->usbpd, false); > + dp->panel->video_test = true; > + dp->usbpd->connect(dp->usbpd, true); > + dp_link_send_test_response(dp->link); > + } > +} > + > +static int dp_display_handle_hpd_irq(struct dp_display_private *dp) > +{ > + if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) { > + dp_display_send_hpd_notification(dp, false); > + > + if (dp_display_is_sink_count_zero(dp)) { > + DRM_DEBUG_DP("sink count is zero, nothing to do\n"); > + return 0; > + } > + > + return dp_display_process_hpd_high(dp); > + } > + > + dp_ctrl_handle_sink_request(dp->ctrl); > + > + dp_display_handle_video_request(dp); > + > + return 0; > +} > + > +static int dp_display_usbpd_attention_cb(struct device *dev) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + if (!dev) { > + DRM_ERROR("invalid dev\n"); > + return -EINVAL; > + } > + > + dp = dev_get_drvdata(dev); > + if (!dp) { > + DRM_ERROR("no driver data found\n"); > + return -ENODEV; > + } > + > + if (dp->usbpd->hpd_irq) { > + dp->hpd_irq_on = true; > + > + rc = dp_link_process_request(dp->link); > + /* check for any test request issued by sink */ > + if (!rc) > + dp_display_handle_hpd_irq(dp); > + > + dp->hpd_irq_on = false; > + goto end; > + } > + > + if (!dp->usbpd->hpd_high) { > + dp_display_process_hpd_low(dp); > + goto end; > + } > + > + if (dp->usbpd->alt_mode_cfg_done) > + dp_display_process_hpd_high(dp); > +end: > + return rc; > +} > + > +static void dp_display_deinit_sub_modules(struct dp_display_private *dp) > +{ > + dp_ctrl_put(dp->ctrl); > + dp_link_put(dp->link); > + dp_panel_put(dp->panel); > + dp_aux_put(dp->aux); > + dp_power_put(dp->power); > + dp_catalog_put(dp->catalog); > + dp_parser_put(dp->parser); > +} > + > +static int dp_init_sub_modules(struct dp_display_private *dp) > +{ > + int rc = 0; > + struct device *dev = &dp->pdev->dev; > + struct dp_usbpd_cb *cb = &dp->usbpd_cb; > + struct dp_panel_in panel_in = { > + .dev = dev, > + }; > + > + /* Callback APIs used for cable status change event */ > + cb->configure = dp_display_usbpd_configure_cb; > + cb->disconnect = dp_display_usbpd_disconnect_cb; > + cb->attention = dp_display_usbpd_attention_cb; > + > + dp->parser = dp_parser_get(dp->pdev); > + if (IS_ERR(dp->parser)) { > + rc = PTR_ERR(dp->parser); > + DRM_ERROR("failed to initialize parser, rc = %d\n", rc); > + dp->parser = NULL; > + goto error_parser; > + } > + > + dp->catalog = dp_catalog_get(dev, &dp->parser->io); > + if (IS_ERR(dp->catalog)) { > + rc = PTR_ERR(dp->catalog); > + DRM_ERROR("failed to initialize catalog, rc = %d\n", rc); > + dp->catalog = NULL; > + goto error_catalog; > + } > + > + dp->power = dp_power_get(dp->parser); > + if (IS_ERR(dp->power)) { > + rc = PTR_ERR(dp->power); > + DRM_ERROR("failed to initialize power, rc = %d\n", rc); > + dp->power = NULL; > + goto error_power; > + } > + > + dp->aux = dp_aux_get(dev, &dp->catalog->aux, dp->parser->aux_cfg); > + if (IS_ERR(dp->aux)) { > + rc = PTR_ERR(dp->aux); > + DRM_ERROR("failed to initialize aux, rc = %d\n", rc); > + dp->aux = NULL; > + goto error_aux; > + } > + > + dp->link = dp_link_get(dev, dp->aux); > + if (IS_ERR(dp->link)) { > + rc = PTR_ERR(dp->link); > + DRM_ERROR("failed to initialize link, rc = %d\n", rc); > + dp->link = NULL; > + goto error_link; > + } > + > + panel_in.aux = dp->aux; > + panel_in.catalog = &dp->catalog->panel; > + panel_in.link = dp->link; > + > + dp->panel = dp_panel_get(&panel_in); > + if (IS_ERR(dp->panel)) { > + rc = PTR_ERR(dp->panel); > + DRM_ERROR("failed to initialize panel, rc = %d\n", rc); > + dp->panel = NULL; > + goto error_panel; > + } > + > + dp->ctrl = dp_ctrl_get(dev, dp->link, dp->panel, dp->aux, > + dp->power, &dp->catalog->ctrl, dp->parser); > + if (IS_ERR(dp->ctrl)) { > + rc = PTR_ERR(dp->ctrl); > + DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); > + dp->ctrl = NULL; > + goto error_ctrl; > + } > + > + return rc; > +error_ctrl: > + dp_panel_put(dp->panel); > +error_panel: > + dp_link_put(dp->link); > +error_link: > + dp_aux_put(dp->aux); > +error_aux: > + dp_power_put(dp->power); > +error_power: > + dp_catalog_put(dp->catalog); > +error_catalog: > + dp_parser_put(dp->parser); > +error_parser: > + return rc; > +} > + > +static int dp_display_set_mode(struct msm_dp *dp_display, > + struct dp_display_mode *mode) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + dp = container_of(dp_display, struct dp_display_private, dp_display); > + > + dp->panel->dp_mode.drm_mode = mode->drm_mode; > + dp->panel->dp_mode.bpp = mode->bpp; > + dp->panel->dp_mode.capabilities = mode->capabilities; > + dp_panel_init_panel_info(dp->panel); > + return rc; rc is only ever 0. > +} > + > +static int dp_display_prepare(struct msm_dp *dp) > +{ > + return 0; > +} > + > +static int dp_display_enable(struct msm_dp *dp_display) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + dp = container_of(dp_display, struct dp_display_private, dp_display); > + > + if (dp->power_on) { > + DRM_DEBUG_DP("Link already setup, return\n"); > + return 0; > + } > + > + dp_aux_init(dp->aux, dp->parser->aux_cfg); > + > + rc = dp_ctrl_on(dp->ctrl); > + if (!rc) > + dp->power_on = true; > + > + return rc; > +} > + > +static int dp_display_post_enable(struct msm_dp *dp_display) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + dp = container_of(dp_display, struct dp_display_private, dp_display); > + > + complete_all(&dp->notification_comp); > + return rc; rc is only ever 0. > +} > + > +static int dp_display_pre_disable(struct msm_dp *dp_display) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + dp = container_of(dp_display, struct dp_display_private, dp_display); > + > + if (dp->usbpd->alt_mode_cfg_done && (dp->usbpd->hpd_high || > + dp->usbpd->forced_disconnect)) > + dp_link_psm_config(dp->link, &dp->panel->link_info, true); > + > + dp_ctrl_push_idle(dp->ctrl); > + return rc; rc is only ever 0. > +} > + > +static int dp_display_disable(struct msm_dp *dp_display) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + dp = container_of(dp_display, struct dp_display_private, dp_display); > + > + if (!dp->power_on || !dp->core_initialized) > + goto error; return 0; > + > + dp_ctrl_off(dp->ctrl); > + > + dp->power_on = false; > + > + complete_all(&dp->notification_comp); > +error: > + return rc; rc is only ever 0 and you don't need the label. > +} > + > +int dp_display_request_irq(struct msm_dp *dp_display) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + if (!dp_display) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + dp = container_of(dp_display, struct dp_display_private, dp_display); > + > + dp->irq = irq_of_parse_and_map(dp->pdev->dev.of_node, 0); > + if (dp->irq < 0) { > + rc = dp->irq; > + DRM_ERROR("failed to get irq: %d\n", rc); > + return rc; > + } > + > + rc = devm_request_irq(&dp->pdev->dev, dp->irq, dp_display_irq, > + IRQF_TRIGGER_HIGH, "dp_display_isr", dp); > + if (rc < 0) { > + DRM_ERROR("failed to request IRQ%u: %d\n", > + dp->irq, rc); > + return rc; > + } > + disable_irq(dp->irq); > + > + return 0; > +} > + > +static int dp_display_unprepare(struct msm_dp *dp) > +{ > + return 0; > +} > + > +int dp_display_validate_mode(struct msm_dp *dp, u32 mode_pclk_khz) > +{ > + const u32 num_components = 3, default_bpp = 24; > + struct dp_display_private *dp_display; > + struct drm_dp_link *link_info; > + u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; > + > + if (!dp || !mode_pclk_khz || !dp->connector) { > + DRM_ERROR("invalid params\n"); > + return -EINVAL; > + } > + > + dp_display = container_of(dp, struct dp_display_private, dp_display); > + link_info = &dp_display->panel->link_info; > + > + mode_bpp = dp->connector->display_info.bpc * num_components; > + if (!mode_bpp) > + mode_bpp = default_bpp; > + > + mode_bpp = dp_panel_get_mode_bpp(dp_display->panel, > + mode_bpp, mode_pclk_khz); > + > + mode_rate_khz = mode_pclk_khz * mode_bpp; > + supported_rate_khz = link_info->num_lanes * link_info->rate * 8; > + > + if (mode_rate_khz > supported_rate_khz) > + return MODE_BAD; > + > + return MODE_OK; > +} > + > +int dp_display_get_modes(struct msm_dp *dp, > + struct dp_display_mode *dp_mode) > +{ > + struct dp_display_private *dp_display; > + int ret = 0; > + > + if (!dp) { > + DRM_ERROR("invalid params\n"); > + return 0; > + } > + > + dp_display = container_of(dp, struct dp_display_private, dp_display); > + > + ret = dp_panel_get_modes(dp_display->panel, > + dp->connector, dp_mode); > + if (dp_mode->drm_mode.clock) > + dp->max_pclk_khz = dp_mode->drm_mode.clock; > + return ret; > +} > + > +bool dp_display_check_video_test(struct msm_dp *dp) > +{ > + struct dp_display_private *dp_display; > + > + if (!dp) { > + DRM_ERROR("invalid params\n"); > + return false; > + } > + > + dp_display = container_of(dp, struct dp_display_private, dp_display); > + > + if (dp_display->panel->video_test) > + return true; > + > + return false; > +} > + > +int dp_display_get_test_bpp(struct msm_dp *dp) > +{ > + struct dp_display_private *dp_display; > + > + if (!dp) { > + DRM_ERROR("invalid params\n"); > + return 0; > + } > + > + dp_display = container_of(dp, struct dp_display_private, dp_display); > + > + return dp_link_bit_depth_to_bpp( > + dp_display->link->test_video.test_bit_depth); > +} > + > +static int dp_display_probe(struct platform_device *pdev) > +{ > + int rc = 0; > + struct dp_display_private *dp; > + > + if (!pdev || !pdev->dev.of_node) { > + DRM_ERROR("pdev not found\n"); > + return -ENODEV; > + } Neither pdev or pdev->dev.of_node can be NULL. > + > + dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL); > + if (!dp) > + return -ENOMEM; > + > + init_completion(&dp->notification_comp); > + > + dp->pdev = pdev; > + dp->name = "drm_dp"; > + > + rc = dp_init_sub_modules(dp); > + if (rc) { > + DRM_ERROR("init sub module failed\n"); > + devm_kfree(&pdev->dev, dp); You don't need to devm_kfree, it will be done for you when the probe fails. > + return -EPROBE_DEFER; > + } > + > + platform_set_drvdata(pdev, dp); > + > + g_dp_display = &dp->dp_display; > + > + rc = component_add(&pdev->dev, &dp_display_comp_ops); > + if (rc) { > + DRM_ERROR("component add failed, rc=%d\n", rc); > + dp_display_deinit_sub_modules(dp); > + devm_kfree(&pdev->dev, dp); Same. > + } > + > + return rc; > +} > + > +static int dp_display_remove(struct platform_device *pdev) > +{ > + struct dp_display_private *dp; > + > + if (!pdev) > + return -EINVAL; Not possible. > + > + dp = platform_get_drvdata(pdev); > + > + dp_display_deinit_sub_modules(dp); > + > + platform_set_drvdata(pdev, NULL); This is unneeded since the device is about to go poof. > + devm_kfree(&pdev->dev, dp); You don't need to devm_kfree, it will be done when the device is destroyed. > + > + return 0; > +} > + > +static struct platform_driver dp_display_driver = { > + .probe = dp_display_probe, > + .remove = dp_display_remove, > + .driver = { > + .name = "msm-dp-display", > + .of_match_table = dp_dt_match, > + }, > +}; > + > +int __init msm_dp_register(void) > +{ > + int ret; > + > + ret = platform_driver_register(&dp_display_driver); > + if (ret) { > + DRM_ERROR("driver register failed"); > + return ret; You don't need this, just drop through. > + } > + > + return ret; > +} > + > +void __exit msm_dp_unregister(void) > +{ > + platform_driver_unregister(&dp_display_driver); > +} > + > +int msm_dp_modeset_init(struct msm_dp *dp_display, struct drm_device *dev, > + struct drm_encoder *encoder) > +{ > + struct msm_drm_private *priv; > + int ret; > + > + if (WARN_ON(!encoder) || WARN_ON(!dp_display) || WARN_ON(!dev)) > + return -EINVAL; This could also be WARN_ON(!encoder || !dp_display || !dev) > + > + priv = dev->dev_private; > + dp_display->drm_dev = dev; > + > + ret = dp_display_request_irq(dp_display); > + if (ret) { > + DRM_ERROR("request_irq failed, ret=%d\n", ret); dp_display_request_irq already prints error messages for you. > + return ret; > + } > + > + dp_display->encoder = encoder; > + > + dp_display->connector = dp_drm_connector_init(dp_display); > + if (IS_ERR(dp_display->connector)) { > + ret = PTR_ERR(dp_display->connector); > + DRM_DEV_ERROR(dev->dev, > + "failed to create dp connector: %d\n", ret); > + dp_display->connector = NULL; > + return ret; > + } > + > + priv->connectors[priv->num_connectors++] = dp_display->connector; > + return 0; > +} > + > +int msm_dp_display_enable(struct msm_dp *dp, struct drm_encoder *encoder) > +{ > + int rc = 0; > + struct dp_display_private *dp_display; > + > + dp_display = container_of(dp, struct dp_display_private, dp_display); > + if (!dp_display->dp_mode.drm_mode.clock) { > + DRM_ERROR("invalid params\n"); > + return -EINVAL; > + } > + > + rc = dp_display_set_mode(dp, &dp_display->dp_mode); > + if (rc) { > + DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); > + return rc; > + } > + > + rc = dp_display_prepare(dp); > + if (rc) { > + DRM_ERROR("DP display prepare failed, rc=%d\n", rc); > + return rc; > + } > + > + rc = dp_display_enable(dp); > + if (rc) { > + DRM_ERROR("DP display enable failed, rc=%d\n", rc); > + dp_display_unprepare(dp); > + return rc; > + } > + > + rc = dp_display_post_enable(dp); > + if (rc) { > + DRM_ERROR("DP display post enable failed, rc=%d\n", rc); > + dp_display_disable(dp); > + dp_display_unprepare(dp); > + } > + return rc; > +} > + > +int msm_dp_display_disable(struct msm_dp *dp, struct drm_encoder *encoder) > +{ > + int rc = 0; > + > + rc = dp_display_pre_disable(dp); > + if (rc) { > + DRM_ERROR("DP display pre disable failed, rc=%d\n", rc); > + return rc; > + } > + > + rc = dp_display_disable(dp); > + if (rc) { > + DRM_ERROR("DP display disable failed, rc=%d\n", rc); > + return rc; > + } > + > + rc = dp_display_unprepare(dp); > + if (rc) { > + DRM_ERROR("DP display unprepare failed, rc=%d\n", rc); > + } > + return rc; > +} > + > +void msm_dp_display_mode_set(struct msm_dp *dp, struct drm_encoder *encoder, > + struct drm_display_mode *mode, > + struct drm_display_mode *adjusted_mode) > +{ > + struct dp_display_private *dp_display; > + > + dp_display = container_of(dp, struct dp_display_private, dp_display); > + > + memset(&dp_display->dp_mode, 0x0, sizeof(struct dp_display_mode)); > + > + if (dp_display_check_video_test(dp)) > + dp_display->dp_mode.bpp = dp_display_get_test_bpp(dp); > + else /* Default num_components per pixel = 3 */ > + dp_display->dp_mode.bpp = dp->connector->display_info.bpc * 3; > + > + if (!dp_display->dp_mode.bpp) > + dp_display->dp_mode.bpp = 24; /* Default bpp */ > + > + drm_mode_copy(&dp_display->dp_mode.drm_mode, adjusted_mode); > + > + dp_display->dp_mode.v_active_low = > + !!(dp_display->dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC); > + > + dp_display->dp_mode.h_active_low = > + !!(dp_display->dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC); > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h > new file mode 100644 > index 0000000..ce09bb6 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_display.h > @@ -0,0 +1,28 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_DISPLAY_H_ > +#define _DP_DISPLAY_H_ > + > +#include <drm/drmP.h> > + > +#include "dp_panel.h" > + > +struct msm_dp { > + struct drm_device *drm_dev; > + struct drm_connector *connector; > + struct drm_encoder *encoder; > + bool is_connected; > + u32 max_pclk_khz; > +}; > + > +int dp_display_validate_mode(struct msm_dp *dp_display, u32 mode_pclk_khz); > +int dp_display_get_modes(struct msm_dp *dp_display, > + struct dp_display_mode *dp_mode); > +int dp_display_request_irq(struct msm_dp *dp_display); > +bool dp_display_check_video_test(struct msm_dp *dp_display); > +int dp_display_get_test_bpp(struct msm_dp *dp_display); > + > +#endif /* _DP_DISPLAY_H_ */ > diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c > new file mode 100644 > index 0000000..5316ec57 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_drm.c > @@ -0,0 +1,189 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp]: %s: " fmt, __func__ > + > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_crtc.h> > + > +#include "msm_drv.h" > +#include "msm_kms.h" > +#include "dp_drm.h" > + > +struct dp_connector { > + struct drm_connector base; > + struct msm_dp *dp_display; > +}; > +#define to_dp_connector(x) container_of(x, struct dp_connector, base) Prefer if you use a function for this for typechecking purposes. > + > +/** > + * dp_connector_detect - callback to determine if connector is connected > + * @connector: Pointer to drm connector structure > + * @force: Force detect setting from drm framework > + * Returns: Connector 'is connected' status > + */ > +static enum drm_connector_status dp_connector_detect(struct drm_connector *conn, > + bool force) > +{ > + enum drm_connector_status status = connector_status_unknown; > + static enum drm_connector_status current_status = connector_status_unknown; > + struct msm_dp *dp; > + struct dp_connector *dp_conn; > + struct msm_drm_private *priv = conn->dev->dev_private; > + struct msm_kms *kms = priv->kms; > + > + dp_conn = to_dp_connector(conn); > + dp = dp_conn->dp_display; > + > + DRM_DEBUG_DP("is_connected = %s\n", > + (dp->is_connected) ? "true" : "false"); > + > + status = (dp->is_connected) ? connector_status_connected : > + connector_status_disconnected; > + > + if (dp->is_connected && (current_status != status) > + && kms->funcs->set_encoder_mode) { > + kms->funcs->set_encoder_mode(kms, > + dp->encoder, false); > + DRM_DEBUG_DP("call set_encoder_mode\n"); > + } > + current_status = status; > + return status; > +} > + > +/** > + * dp_connector_get_modes - callback to add drm modes via drm_mode_probed_add() > + * @connector: Pointer to drm connector structure > + * Returns: Number of modes added > + */ > +static int dp_connector_get_modes(struct drm_connector *connector) > +{ > + int rc = 0; > + struct msm_dp *dp; > + struct dp_display_mode *dp_mode = NULL; > + struct drm_display_mode *m, drm_mode; > + struct dp_connector *dp_conn; > + > + if (!connector) > + return 0; > + > + dp_conn = to_dp_connector(connector); > + dp = dp_conn->dp_display; > + > + dp_mode = kzalloc(sizeof(*dp_mode), GFP_KERNEL); > + if (!dp_mode) > + return 0; How big is dp_mode? Why do you need to kzalloc it and not have it it on the stack? > + > + /* pluggable case assumes EDID is read when HPD */ > + if (dp->is_connected) { > + /* > + *The get_modes() function might return one mode that is stored > + * in dp_mode when compliance test is in progress. If not, the > + * return value is equal to the total number of modes supported > + * by the sink > + */ > + rc = dp_display_get_modes(dp, dp_mode); > + if (rc <= 0) { > + DRM_ERROR("failed to get DP sink modes, rc=%d\n", rc); ARe you missing a kfree here? > + return rc; > + } > + if (dp_mode->drm_mode.clock) { /* valid DP mode */ > + memset(&drm_mode, 0x0, sizeof(drm_mode)); > + drm_mode_copy(&drm_mode, &dp_mode->drm_mode); > + m = drm_mode_duplicate(connector->dev, &drm_mode); > + if (!m) { > + DRM_ERROR("failed to add mode %ux%u\n", > + drm_mode.hdisplay, > + drm_mode.vdisplay); > + kfree(dp_mode); > + return 0; > + } > + drm_mode_probed_add(connector, m); > + } > + } else { > + DRM_DEBUG_DP("No sink connected\n"); > + } > + kfree(dp_mode); > + > + return rc; > +} > + > +/** > + * dp_connector_mode_valid - callback to determine if specified mode is valid > + * @connector: Pointer to drm connector structure > + * @mode: Pointer to drm mode structure > + * Returns: Validity status for specified mode > + */ > +static enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector, > + struct drm_display_mode *mode) > +{ > + struct msm_dp *dp_disp; > + struct dp_connector *dp_conn; > + > + if (!mode || !connector) { > + DRM_ERROR("invalid params\n"); > + return MODE_ERROR; > + } I don't think these can be NULL if they are called from the drm_connector helper functions. > + > + dp_conn = to_dp_connector(connector); > + dp_disp = dp_conn->dp_display; > + > + mode->vrefresh = drm_mode_vrefresh(mode); > + > + if ((dp_disp->max_pclk_khz <= 0) || > + (dp_disp->max_pclk_khz > DP_MAX_PIXEL_CLK_KHZ) || > + (mode->clock > dp_disp->max_pclk_khz)) > + return MODE_BAD; > + > + return dp_display_validate_mode(dp_disp, mode->clock); > +} > + > +static const struct drm_connector_funcs dp_connector_funcs = { > + .detect = dp_connector_detect, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = drm_connector_cleanup, > + .reset = drm_atomic_helper_connector_reset, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > +}; > + > +static const struct drm_connector_helper_funcs dp_connector_helper_funcs = { > + .get_modes = dp_connector_get_modes, > + .mode_valid = dp_connector_mode_valid, > +}; > + > +/* connector initialization */ > +struct drm_connector *dp_drm_connector_init(struct msm_dp *dp_display) > +{ > + struct drm_connector *connector = NULL; > + struct dp_connector *dp_connector; > + int ret; > + > + dp_connector = devm_kzalloc(dp_display->drm_dev->dev, sizeof(*dp_connector), > + GFP_KERNEL); > + if (!dp_connector) > + return ERR_PTR(-ENOMEM); > + > + dp_connector->dp_display = dp_display; > + > + connector = &dp_connector->base; > + > + ret = drm_connector_init(dp_display->drm_dev, connector, &dp_connector_funcs, > + DRM_MODE_CONNECTOR_DisplayPort); > + if (ret) > + return ERR_PTR(ret); > + > + drm_connector_helper_add(connector, &dp_connector_helper_funcs); > + > + /* > + * Enable HPD to let hpd event is handled when cable is attached to the host. > + */ > + connector->polled = DRM_CONNECTOR_POLL_HPD; > + > + drm_connector_attach_encoder(connector, dp_display->encoder); > + > + return connector; > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h > new file mode 100644 > index 0000000..cf472c5 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_drm.h > @@ -0,0 +1,20 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_DRM_H_ > +#define _DP_DRM_H_ > + > +#include <linux/types.h> > +#include <drm/drmP.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_crtc_helper.h> > + > +#include "msm_drv.h" > +#include "dp_display.h" What are all these headers used for? It looks like this prototype only needs struct drm_connector and struct msm_dp. > + > +struct drm_connector *dp_drm_connector_init(struct msm_dp *dp_display); > + > +#endif /* _DP_DRM_H_ */ > + > diff --git a/drivers/gpu/drm/msm/dp/dp_extcon.c b/drivers/gpu/drm/msm/dp/dp_extcon.c > new file mode 100644 > index 0000000..c5ad5ac > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_extcon.c > @@ -0,0 +1,228 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include <linux/slab.h> > +#include <linux/device.h> > +#include <linux/extcon.h> > +#include <drm/drmP.h> > + > +#include "dp_extcon.h" > + > +/* DP specific VDM commands */ > +#define DP_USBPD_VDM_STATUS 0x10 > +#define DP_USBPD_VDM_CONFIGURE 0x11 > + > +/* USBPD-TypeC specific Macros */ > +#define VDM_VERSION 0x0 > +#define USB_C_DP_SID 0xFF01 > + > +struct dp_extcon_private { > + u32 vdo; > + struct device *dev; > + struct notifier_block extcon_nb; > + struct extcon_dev *extcon; > + struct workqueue_struct *extcon_wq; > + struct work_struct event_work; > + struct usbpd *pd; > + struct dp_usbpd_cb *dp_cb; > + struct dp_usbpd dp_usbpd; > +}; > + > +static int dp_extcon_connect(struct dp_usbpd *dp_usbpd, bool hpd) > +{ > + int rc = 0; > + struct dp_extcon_private *extcon; > + > + extcon = container_of(dp_usbpd, struct dp_extcon_private, dp_usbpd); > + > + dp_usbpd->hpd_high = hpd; > + dp_usbpd->forced_disconnect = !hpd; > + > + if (hpd) > + extcon->dp_cb->configure(extcon->dev); > + else > + extcon->dp_cb->disconnect(extcon->dev); > + > + return rc; > +} > + > +static int dp_extcon_get_lanes(struct dp_extcon_private *extcon_priv) > +{ > + union extcon_property_value property; > + > + extcon_get_property(extcon_priv->extcon, > + EXTCON_DISP_DP, > + EXTCON_PROP_USB_SS, > + &property); > + return ((property.intval) ? 2 : 4); > +} > + > + > +static void dp_extcon_event_work(struct work_struct *work) > +{ > + struct dp_extcon_private *extcon_priv; > + int dp_state, ret; > + int lanes; > + union extcon_property_value property; > + > + extcon_priv = container_of(work, > + struct dp_extcon_private, event_work); > + > + if (!extcon_priv || !extcon_priv->extcon) { > + DRM_ERROR("Invalid extcon device handler\n"); > + return; > + } > + > + dp_state = extcon_get_state(extcon_priv->extcon, EXTCON_DISP_DP); > + > + if (dp_state > 0) { > + ret = extcon_get_property(extcon_priv->extcon, > + EXTCON_DISP_DP, > + EXTCON_PROP_USB_TYPEC_POLARITY, > + &property); > + if (ret) { > + DRM_ERROR("Get Polarity property failed\n"); > + return; > + } > + extcon_priv->dp_usbpd.orientation = > + (property.intval) ? ORIENTATION_CC2 : ORIENTATION_CC1; > + > + lanes = dp_extcon_get_lanes(extcon_priv); > + if (!lanes) > + return; > + extcon_priv->dp_usbpd.multi_func = > + ((lanes == 2) ? false : true); > + > + if (extcon_priv->dp_cb && extcon_priv->dp_cb->configure) { > + ret = dp_extcon_connect(&extcon_priv->dp_usbpd, true); > + if (ret) { > + DRM_ERROR("extcon_connect->true failed\n"); > + return; > + } > + } > + } else { > + if (extcon_priv->dp_cb && extcon_priv->dp_cb->disconnect) { > + ret = dp_extcon_connect(&extcon_priv->dp_usbpd, false); > + if (ret) { > + DRM_ERROR("extcon_connect->false failed\n"); > + return; > + } > + } > + } > +} > + > +static int dp_extcon_event_notify(struct notifier_block *nb, > + unsigned long event, void *priv) > +{ > + struct dp_extcon_private *extcon_priv; > + > + extcon_priv = container_of(nb, struct dp_extcon_private, > + extcon_nb); > + > + queue_work(extcon_priv->extcon_wq, &extcon_priv->event_work); > + return NOTIFY_DONE; > +} > + > +int dp_extcon_register(struct dp_usbpd *dp_usbpd) > +{ > + struct dp_extcon_private *extcon_priv; > + int ret = 0; > + > + if (!dp_usbpd) > + return -EINVAL; > + > + extcon_priv = container_of(dp_usbpd, struct dp_extcon_private, dp_usbpd); > + > + extcon_priv->extcon_nb.notifier_call = dp_extcon_event_notify; > + ret = devm_extcon_register_notifier(extcon_priv->dev, extcon_priv->extcon, > + EXTCON_DISP_DP, > + &extcon_priv->extcon_nb); > + if (ret) { > + DRM_DEV_ERROR(extcon_priv->dev, > + "register EXTCON_DISP_DP notifier err\n"); > + ret = -EINVAL; > + return ret; > + } > + > + extcon_priv->extcon_wq = create_singlethread_workqueue("drm_dp_extcon"); > + if (IS_ERR_OR_NULL(extcon_priv->extcon_wq)) { > + DRM_ERROR("Failed to create workqueue\n"); > + dp_extcon_unregister(dp_usbpd); > + return -EPERM; > + } > + > + INIT_WORK(&extcon_priv->event_work, dp_extcon_event_work); > + return ret; > +} > + > +void dp_extcon_unregister(struct dp_usbpd *dp_usbpd) > +{ > + struct dp_extcon_private *extcon_priv; > + > + if (!dp_usbpd) { > + DRM_ERROR("Invalid input\n"); If you are unregistering this log message isn't useful. > + return; > + } > + > + extcon_priv = container_of(dp_usbpd, struct dp_extcon_private, dp_usbpd); > + > + devm_extcon_unregister_notifier(extcon_priv->dev, extcon_priv->extcon, > + EXTCON_DISP_DP, > + &extcon_priv->extcon_nb); > + > + if (extcon_priv->extcon_wq) > + destroy_workqueue(extcon_priv->extcon_wq); > + > + return; Don't do a bare return at the bottom of a void function. > +} > + > +struct dp_usbpd *dp_extcon_get(struct device *dev, struct dp_usbpd_cb *cb) > +{ > + int rc = 0; > + struct dp_extcon_private *dp_extcon; > + struct dp_usbpd *dp_usbpd; > + > + if (!cb) { > + DRM_ERROR("invalid cb data\n"); > + rc = -EINVAL; > + return ERR_PTR(rc); This could just be return ERR_PTR(-EINVAL) > + } > + > + dp_extcon = devm_kzalloc(dev, sizeof(*dp_extcon), GFP_KERNEL); > + if (!dp_extcon) { > + rc = -ENOMEM; > + return ERR_PTR(rc); return ERR_PTR(-ENOMEM) > + } > + > + dp_extcon->extcon = extcon_get_edev_by_phandle(dev, 0); > + if (!dp_extcon->extcon) { > + DRM_ERROR("invalid extcon data\n"); > + rc = -EINVAL; > + devm_kfree(dev, dp_extcon); If this is really peristent memory across the live of the driver you don't need to devm_kfree() here because presumably this function failing will end up destroying the component. If that isn't the case (for example, if this struct is runtime) you should just use kzalloc() and manage the memory yourself. > + return ERR_PTR(rc); This can just be return ERR_PTR(-EINVAL) > + } > + > + dp_extcon->dev = dev; > + dp_extcon->dp_cb = cb; > + > + dp_extcon->dp_usbpd.connect = dp_extcon_connect; > + dp_usbpd = &dp_extcon->dp_usbpd; > + > + return dp_usbpd; > +} > + > +void dp_extcon_put(struct dp_usbpd *dp_usbpd) > +{ > + struct dp_extcon_private *extcon; > + > + if (!dp_usbpd) > + return; > + > + extcon = container_of(dp_usbpd, struct dp_extcon_private, dp_usbpd); > + > + devm_kfree(extcon->dev, extcon); As with the other devm_kfree() calls. > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_extcon.h b/drivers/gpu/drm/msm/dp/dp_extcon.h > new file mode 100644 > index 0000000..28c7f09 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_extcon.h > @@ -0,0 +1,85 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_EXTCON_H_ > +#define _DP_EXTCON_H_ > + > +//#include <linux/usb/usbpd.h> > + > +#include <linux/types.h> > +#include <linux/device.h> > + > +enum plug_orientation { > + ORIENTATION_NONE, > + ORIENTATION_CC1, > + ORIENTATION_CC2, > +}; > + > +/** > + * struct dp_usbpd - DisplayPort status > + * > + * @orientation: plug orientation configuration > + * @low_pow_st: low power state > + * @adaptor_dp_en: adaptor functionality enabled > + * @multi_func: multi-function preferred > + * @usb_config_req: request to switch to usb > + * @exit_dp_mode: request exit from displayport mode > + * @hpd_high: Hot Plug Detect signal is high. > + * @hpd_irq: Change in the status since last message > + * @alt_mode_cfg_done: bool to specify alt mode status > + * @debug_en: bool to specify debug mode > + * @connect: simulate disconnect or connect for debug mode > + */ > +struct dp_usbpd { > + enum plug_orientation orientation; > + bool low_pow_st; > + bool adaptor_dp_en; > + bool multi_func; > + bool usb_config_req; > + bool exit_dp_mode; > + bool hpd_high; > + bool hpd_irq; > + bool alt_mode_cfg_done; > + bool debug_en; > + bool forced_disconnect; > + > + int (*connect)(struct dp_usbpd *dp_usbpd, bool hpd); > +}; > + > +/** > + * struct dp_usbpd_cb - callback functions provided by the client > + * > + * @configure: called by usbpd module when PD communication has > + * been completed and the usb peripheral has been configured on > + * dp mode. > + * @disconnect: notify the cable disconnect issued by usb. > + * @attention: notify any attention message issued by usb. > + */ > +struct dp_usbpd_cb { > + int (*configure)(struct device *dev); > + int (*disconnect)(struct device *dev); > + int (*attention)(struct device *dev); > +}; > + > +/** > + * dp_extcon_get() - setup usbpd module > + * > + * @dev: device instance of the caller > + * @cb: struct containing callback function pointers. > + * > + * This function allows the client to initialize the usbpd > + * module. The module will communicate with usb driver and > + * handles the power delivery (PD) communication with the > + * sink/usb device. This module will notify the client using > + * the callback functions about the connection and status. > + */ > +struct dp_usbpd *dp_extcon_get(struct device *dev, struct dp_usbpd_cb *cb); > + > +void dp_extcon_put(struct dp_usbpd *pd); > + > +int dp_extcon_register(struct dp_usbpd *dp_usbpd); > +void dp_extcon_unregister(struct dp_usbpd *dp_usbpd); > + > +#endif /* _DP_EXTCON_H_ */ > diff --git a/drivers/gpu/drm/msm/dp/dp_link.c b/drivers/gpu/drm/msm/dp/dp_link.c > new file mode 100644 > index 0000000..49745bc > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_link.c > @@ -0,0 +1,1216 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include "dp_link.h" > +#include "dp_panel.h" > + > +#define DP_TEST_REQUEST_MASK 0x7F > + > +enum audio_sample_rate { > + AUDIO_SAMPLE_RATE_32_KHZ = 0x00, > + AUDIO_SAMPLE_RATE_44_1_KHZ = 0x01, > + AUDIO_SAMPLE_RATE_48_KHZ = 0x02, > + AUDIO_SAMPLE_RATE_88_2_KHZ = 0x03, > + AUDIO_SAMPLE_RATE_96_KHZ = 0x04, > + AUDIO_SAMPLE_RATE_176_4_KHZ = 0x05, > + AUDIO_SAMPLE_RATE_192_KHZ = 0x06, > +}; > + > +enum audio_pattern_type { > + AUDIO_TEST_PATTERN_OPERATOR_DEFINED = 0x00, > + AUDIO_TEST_PATTERN_SAWTOOTH = 0x01, > +}; > + > +struct dp_link_request { > + u32 test_requested; > + u32 test_link_rate; > + u32 test_lane_count; > +}; > + > +struct dp_link_private { > + u32 prev_sink_count; > + struct device *dev; > + struct dp_aux *aux; > + struct dp_link dp_link; > + > + struct dp_link_request request; > + u8 link_status[DP_LINK_STATUS_SIZE]; > +}; > + > +static int dp_link_get_period(struct dp_link_private *link, int const addr) > +{ > + int ret = 0; > + u8 data; > + u32 const max_audio_period = 0xA; > + > + /* TEST_AUDIO_PERIOD_CH_XX */ > + if (drm_dp_dpcd_readb(link->aux->drm_aux, addr, &data) < 0) { > + DRM_ERROR("failed to read test_audio_period (0x%x)\n", addr); > + ret = -EINVAL; > + goto exit; > + } > + > + /* Period - Bits 3:0 */ > + data = data & 0xF; > + if ((int)data > max_audio_period) { > + DRM_ERROR("invalid test_audio_period_ch_1 = 0x%x\n", data); > + ret = -EINVAL; > + goto exit; > + } > + > + ret = data; > +exit: > + return ret; Instead of a goto that returns a value, just return the value directly. > +} > + > +static int dp_link_parse_audio_channel_period(struct dp_link_private *link) > +{ > + int ret = 0; > + struct dp_link_test_audio *req = &link->dp_link.test_audio; > + > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH1); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_1 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_1 = 0x%x\n", ret); > + > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH2); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_2 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_2 = 0x%x\n", ret); > + > + /* TEST_AUDIO_PERIOD_CH_3 (Byte 0x275) */ > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH3); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_3 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_3 = 0x%x\n", ret); > + > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH4); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_4 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_4 = 0x%x\n", ret); > + > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH5); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_5 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_5 = 0x%x\n", ret); > + > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH6); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_6 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_6 = 0x%x\n", ret); > + > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH7); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_7 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_7 = 0x%x\n", ret); > + > + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH8); > + if (ret == -EINVAL) > + goto exit; > + > + req->test_audio_period_ch_8 = ret; > + DRM_DEBUG_DP("test_audio_period_ch_8 = 0x%x\n", ret); > +exit: > + return ret; Same > +} > + > +static int dp_link_parse_audio_pattern_type(struct dp_link_private *link) > +{ > + int ret = 0; > + u8 data; > + ssize_t rlen; > + int const max_audio_pattern_type = 0x1; > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, > + DP_TEST_AUDIO_PATTERN_TYPE, &data); > + if (rlen < 0) { > + DRM_ERROR("failed to read link audio mode. rlen=%zd\n", rlen); > + return rlen; > + } > + > + /* Audio Pattern Type - Bits 7:0 */ > + if ((int)data > max_audio_pattern_type) { > + DRM_ERROR("invalid audio pattern type = 0x%x\n", data); > + ret = -EINVAL; This can be return -EINVAL; > + goto exit; > + } > + > + link->dp_link.test_audio.test_audio_pattern_type = data; > + DRM_DEBUG_DP("audio pattern type = 0x%x\n", data); > +exit: Label isn't needed, you can just return 0 and then I don't think ret is needed either. > + return ret; > +} > + > +static int dp_link_parse_audio_mode(struct dp_link_private *link) > +{ > + int ret = 0; > + u8 data; > + ssize_t rlen; > + int const max_audio_sampling_rate = 0x6; > + int const max_audio_channel_count = 0x8; > + int sampling_rate = 0x0; > + int channel_count = 0x0; > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_TEST_AUDIO_MODE, &data); > + if (rlen < 0) { > + DRM_ERROR("failed to read link audio mode. rlen=%zd\n", rlen); > + return rlen; > + } > + > + /* Sampling Rate - Bits 3:0 */ > + sampling_rate = data & 0xF; > + if (sampling_rate > max_audio_sampling_rate) { > + DRM_ERROR("sampling rate (0x%x) greater than max (0x%x)\n", > + sampling_rate, max_audio_sampling_rate); > + ret = -EINVAL; > + goto exit; > + } > + > + /* Channel Count - Bits 7:4 */ > + channel_count = ((data & 0xF0) >> 4) + 1; > + if (channel_count > max_audio_channel_count) { > + DRM_ERROR("channel_count (0x%x) greater than max (0x%x)\n", > + channel_count, max_audio_channel_count); > + ret = -EINVAL; > + goto exit; > + } > + > + link->dp_link.test_audio.test_audio_sampling_rate = sampling_rate; > + link->dp_link.test_audio.test_audio_channel_count = channel_count; > + DRM_DEBUG_DP("sampling_rate = 0x%x, channel_count = 0x%x\n", > + sampling_rate, channel_count); > +exit: > + return ret; As above - return directly instead of using the goto and I think ret is otherwise unused again. > +} > + > +/** > + * dp_parse_audio_pattern_params() - parses audio pattern parameters from DPCD > + * @link: Display Port Driver data > + * > + * Returns 0 if it successfully parses the audio link pattern parameters. > + */ > +static int dp_link_parse_audio_pattern_params(struct dp_link_private *link) > +{ > + int ret = 0; > + > + ret = dp_link_parse_audio_mode(link); > + if (ret) return ret; > + goto exit; > + > + ret = dp_link_parse_audio_pattern_type(link); > + if (ret) return ret; > + goto exit; > + > + ret = dp_link_parse_audio_channel_period(link); > + > +exit: Label isn't needed. > + return ret; > +} > + > +/** > + * dp_link_is_video_pattern_valid() - validates the video pattern > + * @pattern: video pattern requested by the sink > + * > + * Returns true if the requested video pattern is supported. > + */ > +static bool dp_link_is_video_pattern_valid(u32 pattern) > +{ > + switch (pattern) { > + case DP_NO_TEST_PATTERN: > + case DP_COLOR_RAMP: > + case DP_BLACK_AND_WHITE_VERTICAL_LINES: > + case DP_COLOR_SQUARE: > + return true; > + default: > + return false; > + } > +} > + > +/** > + * dp_link_is_bit_depth_valid() - validates the bit depth requested > + * @bit_depth: bit depth requested by the sink > + * > + * Returns true if the requested bit depth is supported. > + */ > +static bool dp_link_is_bit_depth_valid(u32 tbd) > +{ > + /* DP_TEST_VIDEO_PATTERN_NONE is treated as invalid */ > + switch (tbd) { > + case DP_TEST_BIT_DEPTH_6: > + case DP_TEST_BIT_DEPTH_8: > + case DP_TEST_BIT_DEPTH_10: > + return true; > + default: > + return false; > + } > +} > + > +static int dp_link_parse_timing_params1(struct dp_link_private *link, > + int addr, int len, u32 *val) > +{ > + u8 bp[2]; > + int rlen; > + > + if (len != 2) > + return -EINVAL; > + > + /* Read the requested video link pattern (Byte 0x221). */ > + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, bp, len); > + if (rlen < len) { > + DRM_ERROR("failed to read 0x%x\n", addr); > + return -EINVAL; > + } > + > + *val = bp[1] | (bp[0] << 8); > + > + return 0; > +} > + > +static int dp_link_parse_timing_params2(struct dp_link_private *link, > + int addr, int len, > + u32 *val1, u32 *val2) > +{ > + u8 bp[2]; > + int rlen; > + > + if (len != 2) > + return -EINVAL; > + > + /* Read the requested video link pattern (Byte 0x221). */ > + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, bp, len); > + if (rlen < len) { > + DRM_ERROR("failed to read 0x%x\n", addr); > + return -EINVAL; > + } > + > + *val1 = (bp[0] & BIT(7)) >> 7; > + *val2 = bp[1] | ((bp[0] & 0x7F) << 8); > + > + return 0; > +} > + > +static int dp_link_parse_timing_params3(struct dp_link_private *link, > + int addr, u32 *val) > +{ > + u8 bp; > + u32 len = 1; > + int rlen; > + > + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, &bp, len); > + if (rlen < 1) { > + DRM_ERROR("failed to read 0x%x\n", addr); > + return -EINVAL; > + } > + *val = bp; > + > + return 0; > +} > + > +/** > + * dp_parse_video_pattern_params() - parses video pattern parameters from DPCD > + * @link: Display Port Driver data > + * > + * Returns 0 if it successfully parses the video link pattern and the link > + * bit depth requested by the sink and, and if the values parsed are valid. > + */ > +static int dp_link_parse_video_pattern_params(struct dp_link_private *link) > +{ > + int ret = 0; > + ssize_t rlen; > + u8 bp; > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_TEST_PATTERN, &bp); > + if (rlen < 0) { > + DRM_ERROR("failed to read link video pattern. rlen=%zd\n", rlen); > + return rlen; > + } > + > + if (!dp_link_is_video_pattern_valid(bp)) { > + DRM_ERROR("invalid link video pattern = 0x%x\n", bp); Return -EINVAL; > + ret = -EINVAL; > + return ret; > + } > + > + link->dp_link.test_video.test_video_pattern = bp; > + > + /* Read the requested color bit depth and dynamic range (Byte 0x232) */ > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_TEST_MISC0, &bp); > + if (rlen < 0) { > + DRM_ERROR("failed to read link bit depth. rlen=%zd\n", rlen); > + return rlen; > + } > + > + /* Dynamic Range */ > + link->dp_link.test_video.test_dyn_range = (bp & DP_TEST_DYNAMIC_RANGE_CEA); > + > + /* Color bit depth */ > + bp &= DP_TEST_BIT_DEPTH_MASK; > + if (!dp_link_is_bit_depth_valid(bp)) { > + DRM_ERROR("invalid link bit depth = 0x%x\n", bp); > + ret = -EINVAL; > + return ret; Return -EINVAL; > + } > + > + link->dp_link.test_video.test_bit_depth = bp; > + > + /* resolution timing params */ > + ret = dp_link_parse_timing_params1(link, DP_TEST_H_TOTAL_HI, 2, > + &link->dp_link.test_video.test_h_total); > + if (ret) { > + DRM_ERROR("failed to parse test_h_total (DP_TEST_H_TOTAL_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params1(link, DP_TEST_V_TOTAL_HI, 2, > + &link->dp_link.test_video.test_v_total); > + if (ret) { > + DRM_ERROR("failed to parse test_v_total (DP_TEST_V_TOTAL_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params1(link, DP_TEST_H_START_HI, 2, > + &link->dp_link.test_video.test_h_start); > + if (ret) { > + DRM_ERROR("failed to parse test_h_start (DP_TEST_H_START_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params1(link, DP_TEST_V_START_HI, 2, > + &link->dp_link.test_video.test_v_start); > + if (ret) { > + DRM_ERROR("failed to parse test_v_start (DP_TEST_V_START_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params2(link, DP_TEST_HSYNC_HI, 2, > + &link->dp_link.test_video.test_hsync_pol, > + &link->dp_link.test_video.test_hsync_width); > + if (ret) { > + DRM_ERROR("failed to parse (DP_TEST_HSYNC_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params2(link, DP_TEST_VSYNC_HI, 2, > + &link->dp_link.test_video.test_vsync_pol, > + &link->dp_link.test_video.test_vsync_width); > + if (ret) { > + DRM_ERROR("failed to parse (DP_TEST_VSYNC_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params1(link, DP_TEST_H_WIDTH_HI, 2, > + &link->dp_link.test_video.test_h_width); > + if (ret) { > + DRM_ERROR("failed to parse test_h_width (DP_TEST_H_WIDTH_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params1(link, DP_TEST_V_HEIGHT_HI, 2, > + &link->dp_link.test_video.test_v_height); > + if (ret) { > + DRM_ERROR("failed to parse test_v_height (DP_TEST_V_HEIGHT_HI)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params3(link, DP_TEST_MISC1, > + &link->dp_link.test_video.test_rr_d); > + link->dp_link.test_video.test_rr_d &= DP_TEST_REFRESH_DENOMINATOR; > + if (ret) { > + DRM_ERROR("failed to parse test_rr_d (DP_TEST_MISC1)\n"); > + return ret; > + } > + > + ret = dp_link_parse_timing_params3(link, DP_TEST_REFRESH_RATE_NUMERATOR, > + &link->dp_link.test_video.test_rr_n); > + if (ret) { > + DRM_ERROR("failed to parse test_rr_n (DP_TEST_REFRESH_RATE_NUMERATOR)\n"); > + return ret; > + } > + > + DRM_DEBUG_DP("link video pattern = 0x%x\n" \ > + "link dynamic range = 0x%x\n" \ > + "link bit depth = 0x%x\n" \ > + "TEST_H_TOTAL = %d, TEST_V_TOTAL = %d\n" \ > + "TEST_H_START = %d, TEST_V_START = %d\n" \ > + "TEST_HSYNC_POL = %d\n" \ > + "TEST_HSYNC_WIDTH = %d\n" \ > + "TEST_VSYNC_POL = %d\n" \ > + "TEST_VSYNC_WIDTH = %d\n" \ > + "TEST_H_WIDTH = %d\n" \ > + "TEST_V_HEIGHT = %d\n" \ > + "TEST_REFRESH_DENOMINATOR = %d\n" \ > + "TEST_REFRESH_NUMERATOR = %d\n", > + link->dp_link.test_video.test_video_pattern, > + link->dp_link.test_video.test_dyn_range, > + link->dp_link.test_video.test_bit_depth, > + link->dp_link.test_video.test_h_total, > + link->dp_link.test_video.test_v_total, > + link->dp_link.test_video.test_h_start, > + link->dp_link.test_video.test_v_start, > + link->dp_link.test_video.test_hsync_pol, > + link->dp_link.test_video.test_hsync_width, > + link->dp_link.test_video.test_vsync_pol, > + link->dp_link.test_video.test_vsync_width, > + link->dp_link.test_video.test_h_width, > + link->dp_link.test_video.test_v_height, > + link->dp_link.test_video.test_rr_d, > + link->dp_link.test_video.test_rr_n); > + > + return ret; This can just be return 0; > +} > + > +/** > + * dp_link_parse_link_training_params() - parses link training parameters from > + * DPCD > + * @link: Display Port Driver data > + * > + * Returns 0 if it successfully parses the link rate (Byte 0x219) and lane > + * count (Byte 0x220), and if these values parse are valid. > + */ > +static int dp_link_parse_link_training_params(struct dp_link_private *link) > +{ > + u8 bp; > + ssize_t rlen; > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_TEST_LINK_RATE, &bp); > + if (rlen < 0) { > + DRM_ERROR("failed to read link rate. rlen=%zd\n", rlen); > + return rlen; > + } > + > + if (!is_link_rate_valid(bp)) { > + DRM_ERROR("invalid link rate = 0x%x\n", bp); > + return -EINVAL; > + } > + > + link->request.test_link_rate = bp; > + DRM_DEBUG_DP("link rate = 0x%x\n", link->request.test_link_rate); > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_TEST_LANE_COUNT, &bp); > + if (rlen < 0) { > + DRM_ERROR("failed to read lane count. rlen=%zd\n", rlen); > + return rlen; > + } > + bp &= DP_MAX_LANE_COUNT_MASK; > + > + if (!is_lane_count_valid(bp)) { > + DRM_ERROR("invalid lane count = 0x%x\n", bp); > + return -EINVAL; > + } > + > + link->request.test_lane_count = bp; > + DRM_DEBUG_DP("lane count = 0x%x\n", link->request.test_lane_count); > + return 0; > +} > + > +static bool dp_link_is_phy_test_pattern_supported(u32 phy_test_pattern_sel) > +{ > + switch (phy_test_pattern_sel) { > + case DP_LINK_QUAL_PATTERN_DISABLE: > + case DP_LINK_QUAL_PATTERN_D10_2: > + case DP_LINK_QUAL_PATTERN_ERROR_RATE: > + case DP_LINK_QUAL_PATTERN_PRBS7: > + case DP_LINK_QUAL_PATTERN_80BIT_CUSTOM: > + case DP_LINK_QUAL_PATTERN_HBR2_EYE: > + return true; > + default: > + return false; > + } > +} > + > +/** > + * dp_parse_phy_test_params() - parses the phy link parameters > + * @link: Display Port Driver data > + * > + * Parses the DPCD (Byte 0x248) for the DP PHY link pattern that is being > + * requested. > + */ > +static int dp_link_parse_phy_test_params(struct dp_link_private *link) > +{ > + u8 data; > + ssize_t rlen; > + int ret = 0; > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_TEST_PHY_PATTERN, &data); > + if (rlen < 0) { > + DRM_ERROR("failed to read phy link pattern. rlen=%zd\n", rlen); > + return rlen; > + } > + > + link->dp_link.phy_params.phy_test_pattern_sel = data; > + > + DRM_DEBUG_DP("phy_test_pattern_sel = 0x%x\n", data); > + > + if (!dp_link_is_phy_test_pattern_supported(data)) > + ret = -EINVAL; > + > + return ret; > +} > + > +/** > + * dp_link_is_video_audio_test_requested() - checks for audio/video link request > + * @link: link requested by the sink > + * > + * Returns true if the requested link is a permitted audio/video link. > + */ > +static bool dp_link_is_video_audio_test_requested(u32 link) > +{ > + u8 video_audio_test = (DP_TEST_LINK_VIDEO_PATTERN | > + DP_TEST_LINK_AUDIO_PATTERN | > + DP_TEST_LINK_AUDIO_DISABLED_VIDEO); > + > + return ((link & video_audio_test) && > + !(link & ~video_audio_test)); > +} > + > +/** > + * dp_link_parse_request() - parses link request parameters from sink > + * @link: Display Port Driver data > + * > + * Parses the DPCD to check if an automated link is requested (Byte 0x201), > + * and what type of link automation is being requested (Byte 0x218). > + */ > +static int dp_link_parse_request(struct dp_link_private *link) > +{ > + int ret = 0; > + u8 data; > + ssize_t rlen; > + > + /** > + * Read the device service IRQ vector (Byte 0x201) to determine > + * whether an automated link has been requested by the sink. > + */ > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, > + DP_DEVICE_SERVICE_IRQ_VECTOR, &data); > + if (rlen < 0) { > + DRM_ERROR("aux read failed. rlen=%zd\n", rlen); > + return rlen; > + } > + > + DRM_DEBUG_DP("device service irq vector = 0x%x\n", data); > + > + if (!(data & DP_AUTOMATED_TEST_REQUEST)) { > + DRM_DEBUG_DP("no test requested\n"); > + return 0; > + } > + > + /** > + * Read the link request byte (Byte 0x218) to determine what type > + * of automated link has been requested by the sink. > + */ > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_TEST_REQUEST, &data); > + if (rlen < 0) { > + DRM_ERROR("aux read failed. rlen=%zd\n", rlen); > + return rlen; > + } > + > + if (!data || (data == DP_TEST_LINK_FAUX_PATTERN)) { > + DRM_DEBUG_DP("link 0x%x not supported\n", data); > + goto end; > + } > + > + DRM_DEBUG_DP("Test:(0x%x) requested\n", data); > + link->request.test_requested = data; > + > + if (link->request.test_requested == DP_TEST_LINK_PHY_TEST_PATTERN) { > + ret = dp_link_parse_phy_test_params(link); > + if (ret) > + goto end; > + ret = dp_link_parse_link_training_params(link); > + if (ret) > + goto end; > + } > + > + if (link->request.test_requested == DP_TEST_LINK_TRAINING) { > + ret = dp_link_parse_link_training_params(link); > + if (ret) > + goto end; > + } > + > + if (dp_link_is_video_audio_test_requested( > + link->request.test_requested)) { > + ret = dp_link_parse_video_pattern_params(link); > + if (ret) > + goto end; > + > + ret = dp_link_parse_audio_pattern_params(link); > + } > +end: > + /** > + * Send a DP_TEST_ACK if all link parameters are valid, otherwise send > + * a DP_TEST_NAK. > + */ > + if (ret) { > + link->dp_link.test_response = DP_TEST_NAK; > + } else { > + if (link->request.test_requested != DP_TEST_LINK_EDID_READ) > + link->dp_link.test_response = DP_TEST_ACK; > + else > + link->dp_link.test_response = > + DP_TEST_EDID_CHECKSUM_WRITE; > + } > + > + return ret; > +} > + > +/** > + * dp_link_parse_sink_count() - parses the sink count > + * > + * Parses the DPCD to check if there is an update to the sink count > + * (Byte 0x200), and whether all the sink devices connected have Content > + * Protection enabled. > + */ > +static int dp_link_parse_sink_count(struct dp_link *dp_link) > +{ > + ssize_t rlen; > + bool cp_ready; > + > + struct dp_link_private *link = container_of(dp_link, > + struct dp_link_private, dp_link); > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_SINK_COUNT, > + &link->dp_link.sink_count); > + if (rlen < 0) { > + DRM_ERROR("sink count read failed. rlen=%zd\n", rlen); > + return rlen; > + } > + > + cp_ready = link->dp_link.sink_count & DP_SINK_CP_READY; > + > + link->dp_link.sink_count = > + DP_GET_SINK_COUNT(link->dp_link.sink_count); > + > + DRM_DEBUG_DP("sink_count = 0x%x, cp_ready = 0x%x\n", > + link->dp_link.sink_count, cp_ready); > + return 0; > +} > + > +static void dp_link_parse_sink_status_field(struct dp_link_private *link) > +{ > + int len = 0; > + > + link->prev_sink_count = link->dp_link.sink_count; > + dp_link_parse_sink_count(&link->dp_link); > + > + len = drm_dp_dpcd_read_link_status(link->aux->drm_aux, > + link->link_status); > + if (len < DP_LINK_STATUS_SIZE) > + DRM_ERROR("DP link status read failed\n"); > + dp_link_parse_request(link); > +} > + > +/** > + * dp_link_process_link_training_request() - processes new training requests > + * @link: Display Port link data > + * > + * This function will handle new link training requests that are initiated by > + * the sink. In particular, it will update the requested lane count and link > + * link rate, and then trigger the link retraining procedure. > + * > + * The function will return 0 if a link training request has been processed, > + * otherwise it will return -EINVAL. > + */ > +static int dp_link_process_link_training_request(struct dp_link_private *link) > +{ > + if (link->request.test_requested != DP_TEST_LINK_TRAINING) > + return -EINVAL; > + > + DRM_DEBUG_DP("Test:0x%x link rate = 0x%x, lane count = 0x%x\n", > + DP_TEST_LINK_TRAINING, > + link->request.test_link_rate, > + link->request.test_lane_count); > + > + link->dp_link.link_params.num_lanes = link->request.test_lane_count; > + link->dp_link.link_params.rate = link->request.test_link_rate; > + > + return 0; > +} > + > +bool dp_link_send_test_response(struct dp_link *dp_link) > +{ > + struct dp_link_private *link = NULL; > + int ret = 0; > + > + if (!dp_link) { > + DRM_ERROR("invalid input\n"); > + return false; > + } > + > + link = container_of(dp_link, struct dp_link_private, dp_link); > + > + ret = drm_dp_dpcd_writeb(link->aux->drm_aux, DP_TEST_RESPONSE, > + dp_link->test_response); > + return ret == 1; > +} > + > +int dp_link_psm_config(struct dp_link *dp_link, > + struct drm_dp_link *link_info, bool enable) > +{ > + struct dp_link_private *link = NULL; > + int ret = 0; > + > + if (!dp_link) { > + DRM_ERROR("invalid params\n"); > + return -EINVAL; > + } > + > + link = container_of(dp_link, struct dp_link_private, dp_link); > + > + if (enable) > + ret = drm_dp_link_power_down(link->aux->drm_aux, link_info); > + else > + ret = drm_dp_link_power_up(link->aux->drm_aux, link_info); > + > + if (ret) > + DRM_ERROR("Failed to %s low power mode\n", enable ? "enter" : "exit"); > + else > + dp_link->psm_enabled = enable; > + > + return ret; > +} > + > +bool dp_link_send_edid_checksum(struct dp_link *dp_link, u8 checksum) > +{ > + struct dp_link_private *link = NULL; > + int ret = 0; > + > + if (!dp_link) { > + DRM_ERROR("invalid input\n"); > + return false; > + } > + > + link = container_of(dp_link, struct dp_link_private, dp_link); > + > + ret = drm_dp_dpcd_writeb(link->aux->drm_aux, DP_TEST_EDID_CHECKSUM, checksum); > + return ret == 1; > +} > + > +static int dp_link_parse_vx_px(struct dp_link_private *link) > +{ > + u8 data; > + int ret = 0; > + u32 v0, p0, v1, p1, v2, p2, v3, p3; > + ssize_t rlen; > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_ADJUST_REQUEST_LANE0_1, &data); > + if (rlen < 0) { > + DRM_ERROR("failed to read lanes. rlen=%zd\n", rlen); > + return rlen; > + } > + > + DRM_DEBUG_DP("lanes 0/1 (Byte 0x206): 0x%x\n", data); > + > + v0 = data & 0x3; > + data = data >> 2; > + p0 = data & 0x3; > + data = data >> 2; > + > + v1 = data & 0x3; > + data = data >> 2; > + p1 = data & 0x3; > + data = data >> 2; > + > + rlen = drm_dp_dpcd_readb(link->aux->drm_aux, DP_ADJUST_REQUEST_LANE2_3, &data); > + if (rlen < 0) { > + DRM_ERROR("failed to read lanes 2/3. rlen=%zd\n", rlen); > + return rlen; > + } > + > + DRM_DEBUG_DP("lanes 2/3 (Byte 0x207): 0x%x\n", data); > + > + v2 = data & 0x3; > + data = data >> 2; > + p2 = data & 0x3; > + data = data >> 2; > + > + v3 = data & 0x3; > + data = data >> 2; > + p3 = data & 0x3; > + data = data >> 2; > + > + DRM_DEBUG_DP("vx: 0=%d, 1=%d, 2=%d, 3=%d\n", v0, v1, v2, v3); > + DRM_DEBUG_DP("px: 0=%d, 1=%d, 2=%d, 3=%d\n", p0, p1, p2, p3); > + > + /** > + * Update the voltage and pre-emphasis levels as per DPCD request > + * vector. > + */ > + DRM_DEBUG_DP("Current: v_level = 0x%x, p_level = 0x%x\n", > + link->dp_link.phy_params.v_level, > + link->dp_link.phy_params.p_level); > + DRM_DEBUG_DP("Requested: v_level = 0x%x, p_level = 0x%x\n", v0, p0); > + link->dp_link.phy_params.v_level = v0; > + link->dp_link.phy_params.p_level = p0; > + > + return ret; ret is unused in this function. You can return 0; > +} > + > +/** > + * dp_link_process_phy_test_pattern_request() - process new phy link requests > + * @link: Display Port Driver data > + * > + * This function will handle new phy link pattern requests that are initiated > + * by the sink. The function will return 0 if a phy link pattern has been > + * processed, otherwise it will return -EINVAL. > + */ > +static int dp_link_process_phy_test_pattern_request( > + struct dp_link_private *link) > +{ > + int ret = 0; > + > + if (!(link->request.test_requested & DP_TEST_LINK_PHY_TEST_PATTERN)) { > + DRM_DEBUG_DP("no phy test\n"); > + return -EINVAL; > + } > + > + if (!is_link_rate_valid(link->request.test_link_rate) || > + !is_lane_count_valid(link->request.test_lane_count)) { > + DRM_ERROR("Invalid params: link rate = 0x%x, lane count = 0x%x\n", > + link->request.test_link_rate, link->request.test_lane_count); > + return -EINVAL; > + } > + > + DRM_DEBUG_DP("Current: rate = 0x%x, lane count = 0x%x\n", > + link->dp_link.link_params.rate, > + link->dp_link.link_params.num_lanes); > + > + DRM_DEBUG_DP("Requested: rate = 0x%x, lane count = 0x%x\n", > + link->request.test_link_rate, link->request.test_lane_count); > + > + link->dp_link.link_params.num_lanes = link->request.test_lane_count; > + link->dp_link.link_params.rate = link->request.test_link_rate; > + > + ret = dp_link_parse_vx_px(link); > + if (ret) > + DRM_ERROR("parse_vx_px failed. ret=%d\n", ret); > + > + return ret; > +} > + > +static u8 get_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r) > +{ > + return link_status[r - DP_LANE0_1_STATUS]; > +} > + > +/** > + * dp_link_process_link_status_update() - processes link status updates > + * @link: Display Port link module data > + * > + * This function will check for changes in the link status, e.g. clock > + * recovery done on all lanes, and trigger link training if there is a > + * failure/error on the link. > + * > + * The function will return 0 if the a link status update has been processed, > + * otherwise it will return -EINVAL. > + */ > +static int dp_link_process_link_status_update(struct dp_link_private *link) > +{ > + if (!(get_link_status(link->link_status, DP_LANE_ALIGN_STATUS_UPDATED) & > + DP_LINK_STATUS_UPDATED) || > + (drm_dp_clock_recovery_ok(link->link_status, > + link->dp_link.link_params.num_lanes) && > + drm_dp_channel_eq_ok(link->link_status, > + link->dp_link.link_params.num_lanes))) > + return -EINVAL; > + > + DRM_DEBUG_DP("channel_eq_done = %d, clock_recovery_done = %d\n", > + drm_dp_clock_recovery_ok(link->link_status, > + link->dp_link.link_params.num_lanes), > + drm_dp_clock_recovery_ok(link->link_status, > + link->dp_link.link_params.num_lanes)); > + > + return 0; > +} > + > +/** > + * dp_link_process_downstream_port_status_change() - process port status changes > + * @link: Display Port Driver data > + * > + * This function will handle downstream port updates that are initiated by > + * the sink. If the downstream port status has changed, the EDID is read via > + * AUX. > + * > + * The function will return 0 if a downstream port update has been > + * processed, otherwise it will return -EINVAL. > + */ > +static int dp_link_process_ds_port_status_change(struct dp_link_private *link) > +{ > + if (get_link_status(link->link_status, DP_LANE_ALIGN_STATUS_UPDATED) & > + DP_DOWNSTREAM_PORT_STATUS_CHANGED) > + goto reset; > + > + if (link->prev_sink_count == link->dp_link.sink_count) > + return -EINVAL; > + > +reset: > + /* reset prev_sink_count */ > + link->prev_sink_count = link->dp_link.sink_count; > + > + return 0; > +} > + > +static bool dp_link_is_video_pattern_requested(struct dp_link_private *link) > +{ > + return (link->request.test_requested & DP_TEST_LINK_VIDEO_PATTERN) > + && !(link->request.test_requested & > + DP_TEST_LINK_AUDIO_DISABLED_VIDEO); > +} > + > +static bool dp_link_is_audio_pattern_requested(struct dp_link_private *link) > +{ > + return (link->request.test_requested & DP_TEST_LINK_AUDIO_PATTERN); > +} > + > +static void dp_link_reset_data(struct dp_link_private *link) > +{ > + link->request = (const struct dp_link_request){ 0 }; > + link->dp_link.test_video = (const struct dp_link_test_video){ 0 }; > + link->dp_link.test_video.test_bit_depth = DP_TEST_BIT_DEPTH_UNKNOWN; > + link->dp_link.test_audio = (const struct dp_link_test_audio){ 0 }; > + link->dp_link.phy_params.phy_test_pattern_sel = 0; > + link->dp_link.sink_request = 0; > + link->dp_link.test_response = 0; > +} > + > +/** > + * dp_link_process_request() - handle HPD IRQ transition to HIGH > + * @link: pointer to link module data > + * > + * This function will handle the HPD IRQ state transitions from LOW to HIGH > + * (including cases when there are back to back HPD IRQ HIGH) indicating > + * the start of a new link training request or sink status update. > + */ > +int dp_link_process_request(struct dp_link *dp_link) > +{ > + int ret = 0; > + struct dp_link_private *link; > + > + if (!dp_link) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + link = container_of(dp_link, struct dp_link_private, dp_link); > + > + dp_link_reset_data(link); > + > + dp_link_parse_sink_status_field(link); > + > + if (link->request.test_requested == DP_TEST_LINK_EDID_READ) { > + dp_link->sink_request |= DP_TEST_LINK_EDID_READ; > + return ret; > + } > + > + ret = dp_link_process_ds_port_status_change(link); > + if (!ret) { > + dp_link->sink_request |= DS_PORT_STATUS_CHANGED; > + return ret; > + } > + > + ret = dp_link_process_link_training_request(link); > + if (!ret) { > + dp_link->sink_request |= DP_TEST_LINK_TRAINING; > + return ret; > + } > + > + ret = dp_link_process_phy_test_pattern_request(link); > + if (!ret) { > + dp_link->sink_request |= DP_TEST_LINK_PHY_TEST_PATTERN; > + return ret; > + } > + > + ret = dp_link_process_link_status_update(link); > + if (!ret) { > + dp_link->sink_request |= DP_LINK_STATUS_UPDATED; > + return ret; > + } > + > + if (dp_link_is_video_pattern_requested(link)) { > + dp_link->sink_request |= DP_TEST_LINK_VIDEO_PATTERN; > + return -EINVAL; > + } > + > + if (dp_link_is_audio_pattern_requested(link)) { > + dp_link->sink_request |= DP_TEST_LINK_AUDIO_PATTERN; > + return -EINVAL; > + } > + > + return ret; > +} > + > +int dp_link_get_colorimetry_config(struct dp_link *dp_link) > +{ > + u32 cc; > + struct dp_link_private *link; > + > + if (!dp_link) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + link = container_of(dp_link, struct dp_link_private, dp_link); > + > + /* > + * Unless a video pattern CTS test is ongoing, use RGB_VESA > + * Only RGB_VESA and RGB_CEA supported for now > + */ > + if (dp_link_is_video_pattern_requested(link)) > + cc = link->dp_link.test_video.test_dyn_range; > + else > + cc = DP_TEST_DYNAMIC_RANGE_VESA; > + > + return cc; > +} > + > +int dp_link_adjust_levels(struct dp_link *dp_link, u8 *link_status) > +{ > + int i; > + int max = 0; > + u8 data; > + struct dp_link_private *link; > + > + if (!dp_link) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + link = container_of(dp_link, struct dp_link_private, dp_link); > + > + /* use the max level across lanes */ > + for (i = 0; i < dp_link->link_params.num_lanes; i++) { > + data = drm_dp_get_adjust_request_voltage(link_status, i); > + DRM_DEBUG_DP("lane=%d req_voltage_swing=%d\n", i, data); > + if (max < data) > + max = data; > + } > + > + dp_link->phy_params.v_level = max >> DP_TRAIN_VOLTAGE_SWING_SHIFT; > + > + /* use the max level across lanes */ > + max = 0; > + for (i = 0; i < dp_link->link_params.num_lanes; i++) { > + data = drm_dp_get_adjust_request_pre_emphasis(link_status, i); > + DRM_DEBUG_DP("lane=%d req_pre_emphasis=%d\n", i, data); > + if (max < data) > + max = data; > + } > + > + dp_link->phy_params.p_level = max >> DP_TRAIN_PRE_EMPHASIS_SHIFT; > + > + /** > + * Adjust the voltage swing and pre-emphasis level combination to within > + * the allowable range. > + */ > + if (dp_link->phy_params.v_level > DP_TRAIN_VOLTAGE_SWING_MAX) { > + DRM_DEBUG_DP("Requested vSwingLevel=%d, change to %d\n", > + dp_link->phy_params.v_level, DP_TRAIN_VOLTAGE_SWING_MAX); > + dp_link->phy_params.v_level = DP_TRAIN_VOLTAGE_SWING_MAX; > + } > + > + if (dp_link->phy_params.p_level > DP_TRAIN_PRE_EMPHASIS_MAX) { > + DRM_DEBUG_DP("Requested preEmphasisLevel=%d, change to %d\n", > + dp_link->phy_params.p_level, DP_TRAIN_PRE_EMPHASIS_MAX); > + dp_link->phy_params.p_level = DP_TRAIN_PRE_EMPHASIS_MAX; > + } > + > + if ((dp_link->phy_params.p_level > DP_TRAIN_PRE_EMPHASIS_LVL_1) > + && (dp_link->phy_params.v_level == DP_TRAIN_VOLTAGE_SWING_LVL_2)) { > + DRM_DEBUG_DP("Requested preEmphasisLevel=%d, change to %d\n", > + dp_link->phy_params.p_level, > + DP_TRAIN_PRE_EMPHASIS_LVL_1); > + dp_link->phy_params.p_level = DP_TRAIN_PRE_EMPHASIS_LVL_1; > + } > + > + DRM_DEBUG_DP("adjusted: v_level=%d, p_level=%d\n", > + dp_link->phy_params.v_level, dp_link->phy_params.p_level); > + > + return 0; > +} > + > +u32 dp_link_get_test_bits_depth(struct dp_link *dp_link, u32 bpp) > +{ > + u32 tbd; > + > + /* > + * Few simplistic rules and assumptions made here: > + * 1. Test bit depth is bit depth per color component > + * 2. Assume 3 color components > + */ > + switch (bpp) { > + case 18: > + tbd = DP_TEST_BIT_DEPTH_6; > + break; > + case 24: > + tbd = DP_TEST_BIT_DEPTH_8; > + break; > + case 30: > + tbd = DP_TEST_BIT_DEPTH_10; > + break; > + default: > + tbd = DP_TEST_BIT_DEPTH_UNKNOWN; > + break; > + } > + > + if (tbd != DP_TEST_BIT_DEPTH_UNKNOWN) > + tbd = (tbd >> DP_TEST_BIT_DEPTH_SHIFT); > + > + return tbd; > +} > + > +struct dp_link *dp_link_get(struct device *dev, struct dp_aux *aux) > +{ > + int rc = 0; > + struct dp_link_private *link; > + struct dp_link *dp_link; > + > + if (!dev || !aux) { > + DRM_ERROR("invalid input\n"); return ERR_PTR(-EINVAL) > + rc = -EINVAL; > + goto error; > + } > + > + link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL); > + if (!link) { This should be ERR_PTR(-ENOMEM) > + rc = -EINVAL; > + goto error; > + } > + > + link->dev = dev; > + link->aux = aux; > + > + dp_link = &link->dp_link; > + > + return dp_link; > +error: > + return ERR_PTR(rc); The label and return isn't needed. > +} > + > +void dp_link_put(struct dp_link *dp_link) > +{ > + struct dp_link_private *link; > + > + if (!dp_link) > + return; > + > + link = container_of(dp_link, struct dp_link_private, dp_link); > + > + devm_kfree(link->dev, link); Same as the other devm_kfree() usages > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_link.h b/drivers/gpu/drm/msm/dp/dp_link.h > new file mode 100644 > index 0000000..607904b > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_link.h > @@ -0,0 +1,132 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_LINK_H_ > +#define _DP_LINK_H_ > + > +#include "dp_aux.h" > + > +#define DS_PORT_STATUS_CHANGED 0x200 > +#define DP_TEST_BIT_DEPTH_UNKNOWN 0xFFFFFFFF > + > +enum dp_link_voltage_level { > + DP_TRAIN_VOLTAGE_SWING_LVL_0 = 0, > + DP_TRAIN_VOLTAGE_SWING_LVL_1 = 1, > + DP_TRAIN_VOLTAGE_SWING_LVL_2 = 2, > + DP_TRAIN_VOLTAGE_SWING_MAX = DP_TRAIN_VOLTAGE_SWING_LVL_2, > +}; > + > +enum dp_link_preemaphasis_level { > + DP_TRAIN_PRE_EMPHASIS_LVL_0 = 0, > + DP_TRAIN_PRE_EMPHASIS_LVL_1 = 1, > + DP_TRAIN_PRE_EMPHASIS_LVL_2 = 2, > + DP_TRAIN_PRE_EMPHASIS_MAX = DP_TRAIN_PRE_EMPHASIS_LVL_2, > +}; > + > +struct dp_link_test_video { > + u32 test_video_pattern; > + u32 test_bit_depth; > + u32 test_dyn_range; > + u32 test_h_total; > + u32 test_v_total; > + u32 test_h_start; > + u32 test_v_start; > + u32 test_hsync_pol; > + u32 test_hsync_width; > + u32 test_vsync_pol; > + u32 test_vsync_width; > + u32 test_h_width; > + u32 test_v_height; > + u32 test_rr_d; > + u32 test_rr_n; > +}; > + > +struct dp_link_test_audio { > + u32 test_audio_sampling_rate; > + u32 test_audio_channel_count; > + u32 test_audio_pattern_type; > + u32 test_audio_period_ch_1; > + u32 test_audio_period_ch_2; > + u32 test_audio_period_ch_3; > + u32 test_audio_period_ch_4; > + u32 test_audio_period_ch_5; > + u32 test_audio_period_ch_6; > + u32 test_audio_period_ch_7; > + u32 test_audio_period_ch_8; > +}; > + > +struct dp_link_phy_params { > + u32 phy_test_pattern_sel; > + u8 v_level; > + u8 p_level; > +}; > + > +struct dp_link { > + u32 sink_request; > + u32 test_response; > + bool psm_enabled; > + > + u8 sink_count; > + struct dp_link_test_video test_video; > + struct dp_link_test_audio test_audio; > + struct dp_link_phy_params phy_params; > + struct drm_dp_link link_params; > +}; > + > +/** > + * mdss_dp_test_bit_depth_to_bpp() - convert test bit depth to bpp > + * @tbd: test bit depth > + * > + * Returns the bits per pixel (bpp) to be used corresponding to the > + * git bit depth value. This function assumes that bit depth has > + * already been validated. > + */ > +static inline u32 dp_link_bit_depth_to_bpp(u32 tbd) > +{ > + /* > + * Few simplistic rules and assumptions made here: > + * 1. Bit depth is per color component > + * 2. If bit depth is unknown return 0 > + * 3. Assume 3 color components > + */ > + switch (tbd) { > + case DP_TEST_BIT_DEPTH_6: > + return 18; > + case DP_TEST_BIT_DEPTH_8: > + return 24; > + case DP_TEST_BIT_DEPTH_10: > + return 30; > + case DP_TEST_BIT_DEPTH_UNKNOWN: > + default: > + return 0; > + } > +} > + > +u32 dp_link_get_test_bits_depth(struct dp_link *dp_link, u32 bpp); > +int dp_link_process_request(struct dp_link *dp_link); > +int dp_link_get_colorimetry_config(struct dp_link *dp_link); > +int dp_link_adjust_levels(struct dp_link *dp_link, u8 *link_status); > +bool dp_link_send_test_response(struct dp_link *dp_link); > +int dp_link_psm_config(struct dp_link *dp_link, > + struct drm_dp_link *link_info, bool enable); > +bool dp_link_send_edid_checksum(struct dp_link *dp_link, u8 checksum); > + > +/** > + * dp_link_get() - get the functionalities of dp test module > + * > + * > + * return: a pointer to dp_link struct > + */ > +struct dp_link *dp_link_get(struct device *dev, struct dp_aux *aux); > + > +/** > + * dp_link_put() - releases the dp test module's resources > + * > + * @dp_link: an instance of dp_link module > + * > + */ > +void dp_link_put(struct dp_link *dp_link); > + > +#endif /* _DP_LINK_H_ */ > diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c > new file mode 100644 > index 0000000..d7e165f1 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_panel.c > @@ -0,0 +1,504 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include "dp_panel.h" > + > +#include <drm/drm_connector.h> > +#include <drm/drm_edid.h> > + > +#define DP_MAX_DS_PORT_COUNT 1 > + > +struct dp_panel_private { > + struct device *dev; > + struct dp_panel dp_panel; > + struct dp_aux *aux; > + struct dp_link *link; > + struct dp_catalog_panel *catalog; > + bool panel_on; > + bool aux_cfg_update_done; > +}; > + > +static int dp_panel_read_dpcd(struct dp_panel *dp_panel) > +{ > + int rlen, rc = 0; > + struct dp_panel_private *panel; > + struct drm_dp_link *link_info; > + u8 *dpcd, major = 0, minor = 0; > + u32 dfp_count = 0; > + unsigned long caps = DP_LINK_CAP_ENHANCED_FRAMING; > + > + dpcd = dp_panel->dpcd; > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + link_info = &dp_panel->link_info; > + > + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_DPCD_REV, > + dpcd, (DP_RECEIVER_CAP_SIZE + 1)); > + if (rlen < (DP_RECEIVER_CAP_SIZE + 1)) { > + DRM_ERROR("dpcd read failed, rlen=%d\n", rlen); > + return -EINVAL; > + } > + > + link_info->revision = dp_panel->dpcd[DP_DPCD_REV]; > + > + major = (link_info->revision >> 4) & 0x0f; > + minor = link_info->revision & 0x0f; > + DRM_DEBUG_DP("version: %d.%d\n", major, minor); > + > + link_info->rate = > + drm_dp_bw_code_to_link_rate(dp_panel->dpcd[DP_MAX_LINK_RATE]); > + DRM_DEBUG_DP("link_rate=%d\n", link_info->rate); > + > + link_info->num_lanes = dp_panel->dpcd[DP_MAX_LANE_COUNT] & > + DP_MAX_LANE_COUNT_MASK; > + > + DRM_DEBUG_DP("lane_count=%d\n", link_info->num_lanes); > + > + if (drm_dp_enhanced_frame_cap(dpcd)) > + link_info->capabilities |= caps; > + > + dfp_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & > + DP_DOWN_STREAM_PORT_COUNT; > + if (dfp_count > DP_MAX_DS_PORT_COUNT) > + DRM_ERROR("DS port count %d greater that max (%d) supported\n", > + dfp_count, DP_MAX_DS_PORT_COUNT); > + > + return rc; rc is unused in this function - yo ucan return 0; > +} > + > +static int dp_panel_read_edid(struct dp_panel *dp_panel, > + struct drm_connector *connector) > +{ > + int retry_cnt = 0; > + const int max_retry = 10; > + struct dp_panel_private *panel; > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + > + do { > + kfree(dp_panel->edid); > + dp_panel->edid = drm_get_edid(connector, > + &panel->aux->drm_aux->ddc); > + if (!dp_panel->edid) { > + DRM_ERROR("EDID read failed\n"); > + retry_cnt++; > + dp_aux_reconfig(panel->aux); > + panel->aux_cfg_update_done = true; > + } else { > + return 0; > + } > + } while (retry_cnt < max_retry); > + > + return -EINVAL; > +} > + > +static u32 dp_panel_get_supported_bpp(struct dp_panel *dp_panel, > + u32 mode_edid_bpp, u32 mode_pclk_khz) > +{ > + struct drm_dp_link *link_info; > + const u32 max_supported_bpp = 30, min_supported_bpp = 18; > + u32 bpp = 0, data_rate_khz = 0; > + > + bpp = min_t(u32, mode_edid_bpp, max_supported_bpp); > + > + link_info = &dp_panel->link_info; > + data_rate_khz = link_info->num_lanes * link_info->rate * 8; > + > + while (bpp > min_supported_bpp) { > + if (mode_pclk_khz * bpp <= data_rate_khz) > + break; > + bpp -= 6; > + } > + > + return bpp; > +} > + > +static void dp_panel_set_test_mode(struct dp_panel_private *panel, > + struct dp_display_mode *mode) > +{ > + struct drm_display_mode *drm_mode = NULL; > + struct dp_link_test_video *test_info = NULL; > + > + if (!panel) { > + DRM_ERROR("invalid params\n"); > + return; > + } It doesn't look like panel can ever be NULL here. > + > + drm_mode = &mode->drm_mode; > + test_info = &panel->link->test_video; > + > + drm_mode->hdisplay = test_info->test_h_width; > + drm_mode->hsync_start = drm_mode->hdisplay + test_info->test_h_total - > + (test_info->test_h_start + test_info->test_h_width); > + drm_mode->hsync_end = drm_mode->hsync_start + test_info->test_hsync_width; > + drm_mode->htotal = drm_mode->hsync_end + test_info->test_h_start - > + test_info->test_hsync_width; > + > + drm_mode->vdisplay = test_info->test_v_height; > + drm_mode->vsync_start = drm_mode->vdisplay + test_info->test_v_total - > + (test_info->test_v_start + test_info->test_v_height); > + drm_mode->vsync_end = drm_mode->vsync_start + test_info->test_vsync_width; > + drm_mode->vtotal = drm_mode->vsync_end + test_info->test_v_start - > + test_info->test_vsync_width; > + > + drm_mode->vrefresh = test_info->test_rr_n; > + drm_mode->clock = test_info->test_h_total * > + test_info->test_v_total * drm_mode->vrefresh; > + > + drm_mode->type = 0x48; > + drm_mode_set_name(drm_mode); > + > + if (test_info->test_rr_d == 0) > + drm_mode->clock /= 1000; > + else > + drm_mode->clock /= 1001; > + > + if (test_info->test_h_width == 640) > + drm_mode->clock = 25170; > +} > + > +static int dp_panel_update_modes(struct drm_connector *connector, > + struct edid *edid) > +{ > + int rc = 0; > + > + if (edid) { > + rc = drm_connector_update_edid_property(connector, edid); > + if (rc) { > + DRM_ERROR("failed to update edid property %d\n", rc); > + return rc; > + } > + rc = drm_add_edid_modes(connector, edid); > + DRM_DEBUG_DP("%s -", __func__); > + return rc; > + } > + > + rc = drm_connector_update_edid_property(connector, NULL); > + if (rc) > + DRM_ERROR("failed to update edid property %d\n", rc); > + > + return rc; > +} > + > +int dp_panel_read_sink_caps(struct dp_panel *dp_panel, > + struct drm_connector *connector) > +{ > + int rc = 0, bw_code; > + struct dp_panel_private *panel; > + > + if (!dp_panel || !connector) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + > + rc = dp_panel_read_dpcd(dp_panel); > + bw_code = drm_dp_link_rate_to_bw_code(dp_panel->link_info.rate); > + if (rc || !is_link_rate_valid(bw_code) || > + !is_lane_count_valid(dp_panel->link_info.num_lanes) || > + (bw_code > dp_panel->max_bw_code)) { > + DRM_ERROR("read dpcd failed %d\n", rc); > + return rc; > + } > + > + rc = dp_panel_read_edid(dp_panel, connector); > + if (rc) { > + DRM_ERROR("panel edid read failed, set failsafe mode.rc=%d\n", rc); > + return rc; > + } > + > + if (panel->aux_cfg_update_done) { > + DRM_DEBUG_DP("read DPCD with updated AUX config\n"); > + rc = dp_panel_read_dpcd(dp_panel); > + bw_code = drm_dp_link_rate_to_bw_code(dp_panel->link_info.rate); > + if (rc || !is_link_rate_valid(bw_code) || > + !is_lane_count_valid(dp_panel->link_info.num_lanes) || > + (bw_code > dp_panel->max_bw_code)) { > + DRM_ERROR("read dpcd failed %d\n", rc); > + return rc; > + } > + panel->aux_cfg_update_done = false; > + } > + > + return 0; > +} > + > +u32 dp_panel_get_mode_bpp(struct dp_panel *dp_panel, > + u32 mode_edid_bpp, u32 mode_pclk_khz) > +{ > + struct dp_panel_private *panel; > + u32 bpp = mode_edid_bpp; > + > + if (!dp_panel || !mode_edid_bpp || !mode_pclk_khz) { > + DRM_ERROR("invalid input\n"); > + return 0; > + } > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + > + if (dp_panel->video_test) > + bpp = dp_link_bit_depth_to_bpp( > + panel->link->test_video.test_bit_depth); > + else > + bpp = dp_panel_get_supported_bpp(dp_panel, mode_edid_bpp, > + mode_pclk_khz); > + > + return bpp; > +} > + > +int dp_panel_get_modes(struct dp_panel *dp_panel, > + struct drm_connector *connector, struct dp_display_mode *mode) > +{ > + struct dp_panel_private *panel; > + > + if (!dp_panel) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + > + if (dp_panel->video_test) { > + dp_panel_set_test_mode(panel, mode); > + return 1; > + } else if (dp_panel->edid) { > + return dp_panel_update_modes(connector, dp_panel->edid); > + } > + > + return 0; > +} > + > +u8 dp_panel_get_edid_checksum(struct edid *edid) > +{ > + struct edid *last_block; > + u8 *raw_edid; > + bool is_edid_corrupt; > + > + if (!edid) { > + DRM_ERROR("invalid edid input\n"); > + return 0; > + } > + > + raw_edid = (u8 *)edid; > + raw_edid += (edid->extensions * EDID_LENGTH); > + last_block = (struct edid *)raw_edid; > + > + /* block type extension */ > + drm_edid_block_valid(raw_edid, 1, false, &is_edid_corrupt); > + if (!is_edid_corrupt) > + return last_block->checksum; > + > + DRM_ERROR("Invalid block, no checksum\n"); > + return 0; > +} > + > +void dp_panel_handle_sink_request(struct dp_panel *dp_panel) > +{ > + struct dp_panel_private *panel; > + > + if (!dp_panel) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + > + if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) { > + u8 checksum = dp_panel_get_edid_checksum(dp_panel->edid); > + > + dp_link_send_edid_checksum(panel->link, checksum); > + dp_link_send_test_response(panel->link); > + } > +} > + > +void dp_panel_tpg_config(struct dp_panel *dp_panel, bool enable) > +{ > + u32 hsync_start_x, hsync_end_x; > + struct dp_catalog_panel *catalog; > + struct dp_panel_private *panel; > + struct drm_display_mode *drm_mode; > + > + if (!dp_panel) { > + DRM_ERROR("invalid input\n"); > + return; > + } > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + catalog = panel->catalog; > + drm_mode = &panel->dp_panel.dp_mode.drm_mode; > + > + if (!panel->panel_on) { > + DRM_DEBUG_DP("DP panel not enabled, handle TPG on next panel on\n"); > + return; > + } > + > + if (!enable) { > + dp_catalog_panel_tpg_disable(catalog); > + return; > + } > + > + /* TPG config */ > + catalog->hsync_period = drm_mode->htotal; > + catalog->vsync_period = drm_mode->vtotal; > + > + catalog->display_v_start = ((drm_mode->vtotal - drm_mode->vsync_start) * > + catalog->hsync_period); > + catalog->display_v_end = ((catalog->vsync_period - drm_mode->vsync_start - > + drm_mode->vdisplay) * catalog->hsync_period) - 1; > + > + catalog->display_v_start += drm_mode->htotal - drm_mode->hsync_start; > + catalog->display_v_end -= drm_mode->hsync_start - drm_mode->hdisplay; > + > + hsync_start_x = drm_mode->htotal - drm_mode->hsync_start; > + hsync_end_x = catalog->hsync_period - drm_mode->hsync_start - > + drm_mode->hdisplay - 1; > + > + catalog->v_sync_width = drm_mode->vsync_end - drm_mode->vsync_start; > + > + catalog->hsync_ctl = (catalog->hsync_period << 16) | > + (drm_mode->hsync_end - drm_mode->hsync_start); > + catalog->display_hctl = (hsync_end_x << 16) | hsync_start_x; > + > + DRM_DEBUG_DP("%s: calling catalog tpg_enable\n", __func__); > + dp_catalog_panel_tpg_enable(catalog); > +} > + > +int dp_panel_timing_cfg(struct dp_panel *dp_panel) > +{ > + int rc = 0; > + u32 data, total_ver, total_hor; > + struct dp_catalog_panel *catalog; > + struct dp_panel_private *panel; > + struct drm_display_mode *drm_mode; > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + catalog = panel->catalog; > + drm_mode = &panel->dp_panel.dp_mode.drm_mode; > + > + DRM_DEBUG_DP("width=%d hporch= %d %d %d\n", > + drm_mode->hdisplay, drm_mode->htotal - drm_mode->hsync_end, > + drm_mode->hsync_start - drm_mode->hdisplay, > + drm_mode->hsync_end - drm_mode->hsync_start); > + > + DRM_DEBUG_DP("height=%d vporch= %d %d %d\n", > + drm_mode->vdisplay, drm_mode->vtotal - drm_mode->vsync_end, > + drm_mode->vsync_start - drm_mode->vdisplay, > + drm_mode->vsync_end - drm_mode->vsync_start); > + > + total_hor = drm_mode->htotal; > + > + total_ver = drm_mode->vtotal; > + > + data = total_ver; > + data <<= 16; > + data |= total_hor; > + > + catalog->total = data; > + > + data = (drm_mode->vtotal - drm_mode->vsync_start); > + data <<= 16; > + data |= (drm_mode->htotal - drm_mode->hsync_start); > + > + catalog->sync_start = data; > + > + data = drm_mode->vsync_end - drm_mode->vsync_start; > + data <<= 16; > + data |= (panel->dp_panel.dp_mode.v_active_low << 31); > + data |= drm_mode->hsync_end - drm_mode->hsync_start; > + data |= (panel->dp_panel.dp_mode.h_active_low << 15); > + > + catalog->width_blanking = data; > + > + data = drm_mode->vdisplay; > + data <<= 16; > + data |= drm_mode->hdisplay; > + > + catalog->dp_active = data; > + > + dp_catalog_panel_timing_cfg(catalog); > + panel->panel_on = true; > + > + return rc; rc is unused in this function. You can return 0; > +} > + > +int dp_panel_init_panel_info(struct dp_panel *dp_panel) > +{ > + int rc = 0; > + struct drm_display_mode *drm_mode; > + > + drm_mode = &dp_panel->dp_mode.drm_mode; > + > + /* > + * print resolution info as this is a result > + * of user initiated action of cable connection > + */ > + DRM_DEBUG_DP("SET NEW RESOLUTION:\n"); > + DRM_DEBUG_DP("%dx%d@%dfps\n", drm_mode->hdisplay, > + drm_mode->vdisplay, drm_mode->vrefresh); > + DRM_DEBUG_DP("h_porches(back|front|width) = (%d|%d|%d)\n", > + drm_mode->htotal - drm_mode->hsync_end, > + drm_mode->hsync_start - drm_mode->hdisplay, > + drm_mode->hsync_end - drm_mode->hsync_start); > + DRM_DEBUG_DP("v_porches(back|front|width) = (%d|%d|%d)\n", > + drm_mode->vtotal - drm_mode->vsync_end, > + drm_mode->vsync_start - drm_mode->vdisplay, > + drm_mode->vsync_end - drm_mode->vsync_start); > + DRM_DEBUG_DP("pixel clock (KHz)=(%d)\n", drm_mode->clock); > + DRM_DEBUG_DP("bpp = %d\n", dp_panel->dp_mode.bpp); > + > + dp_panel->dp_mode.bpp = max_t(u32, 18, min_t(u32, dp_panel->dp_mode.bpp, 30)); > + DRM_DEBUG_DP("updated bpp = %d\n", dp_panel->dp_mode.bpp); > + > + return rc; rc is unset in this function. > +} > + > +struct dp_panel *dp_panel_get(struct dp_panel_in *in) > +{ > + int rc = 0; > + struct dp_panel_private *panel; > + struct dp_panel *dp_panel; > + > + if (!in->dev || !in->catalog || !in->aux || !in->link) { return ERR_PTR(-EINVAL) but really evaluate if this is possible since the caller would presumably have populated everything before calling. > + DRM_ERROR("invalid input\n"); > + rc = -EINVAL; > + goto error; > + } > + > + panel = devm_kzalloc(in->dev, sizeof(*panel), GFP_KERNEL); > + if (!panel) { > + rc = -ENOMEM; return ERR_PRTR(-ENOMEM) > + goto error; > + } > + > + panel->dev = in->dev; > + panel->aux = in->aux; > + panel->catalog = in->catalog; > + panel->link = in->link; > + > + dp_panel = &panel->dp_panel; > + dp_panel->max_bw_code = DP_LINK_BW_8_1; > + panel->aux_cfg_update_done = false; > + > + return dp_panel; > +error: > + return ERR_PTR(rc); > +} > + > +void dp_panel_put(struct dp_panel *dp_panel) > +{ > + struct dp_panel_private *panel; > + > + if (!dp_panel) > + return; > + > + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); > + > + kfree(dp_panel->edid); > + dp_panel->edid = NULL; > + devm_kfree(panel->dev, panel); Same as the other callers of devm_kfree() > +} > diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h > new file mode 100644 > index 0000000..0d36a1a > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_panel.h > @@ -0,0 +1,91 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_PANEL_H_ > +#define _DP_PANEL_H_ > + > +#include <drm/msm_drm.h> > + > +#include "dp_aux.h" > +#include "dp_link.h" > +#include "dp_extcon.h" > + > +struct edid; > + > +#define DP_MAX_DOWNSTREAM_PORTS 0x10 > + > +struct dp_display_mode { > + struct drm_display_mode drm_mode; > + u32 capabilities; > + u32 bpp; > + u32 h_active_low; > + u32 v_active_low; > +}; > + > +struct dp_panel_in { > + struct device *dev; > + struct dp_aux *aux; > + struct dp_link *link; > + struct dp_catalog_panel *catalog; > +}; > + > +struct dp_panel { > + /* dpcd raw data */ > + u8 dpcd[DP_RECEIVER_CAP_SIZE + 1]; > + > + struct drm_dp_link link_info; > + struct edid *edid; > + struct drm_connector *connector; > + struct dp_display_mode dp_mode; > + bool video_test; > + > + u32 vic; > + u32 max_pclk_khz; > + > + u32 max_bw_code; > +}; > + > +int dp_panel_init_panel_info(struct dp_panel *dp_panel); > +int dp_panel_deinit(struct dp_panel *dp_panel); > +int dp_panel_timing_cfg(struct dp_panel *dp_panel); > +int dp_panel_read_sink_caps(struct dp_panel *dp_panel, > + struct drm_connector *connector); > +u32 dp_panel_get_mode_bpp(struct dp_panel *dp_panel, u32 mode_max_bpp, > + u32 mode_pclk_khz); > +int dp_panel_get_modes(struct dp_panel *dp_panel, > + struct drm_connector *connector, struct dp_display_mode *mode); > +void dp_panel_handle_sink_request(struct dp_panel *dp_panel); > +void dp_panel_tpg_config(struct dp_panel *dp_panel, bool enable); > + > +/** > + * is_link_rate_valid() - validates the link rate > + * @lane_rate: link rate requested by the sink > + * > + * Returns true if the requested link rate is supported. > + */ > +static inline bool is_link_rate_valid(u32 bw_code) > +{ > + return (bw_code == DP_LINK_BW_1_62 || > + bw_code == DP_LINK_BW_2_7 || > + bw_code == DP_LINK_BW_5_4 || > + bw_code == DP_LINK_BW_8_1); > +} > + > +/** > + * dp_link_is_lane_count_valid() - validates the lane count > + * @lane_count: lane count requested by the sink > + * > + * Returns true if the requested lane count is supported. > + */ > +static inline bool is_lane_count_valid(u32 lane_count) > +{ > + return (lane_count == 1 || > + lane_count == 2 || > + lane_count == 4); > +} > + > +struct dp_panel *dp_panel_get(struct dp_panel_in *in); > +void dp_panel_put(struct dp_panel *dp_panel); > +#endif /* _DP_PANEL_H_ */ > diff --git a/drivers/gpu/drm/msm/dp/dp_parser.c b/drivers/gpu/drm/msm/dp/dp_parser.c > new file mode 100644 > index 0000000..5020d10 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_parser.c > @@ -0,0 +1,621 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include <linux/of_gpio.h> > + > +#include "dp_parser.h" > + > +static int msm_dss_ioremap_byname(struct platform_device *pdev, > + struct dss_io_data *io_data, const char *name) > +{ > + struct resource *res = NULL; > + > + if (!io_data) { > + DRM_ERROR("%pS->%s: invalid input\n", > + __builtin_return_address(0), __func__); > + return -EINVAL; > + } > + > + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); > + if (!res) { > + DRM_ERROR("%pS->%s: '%s' msm_dss_get_res_byname failed\n", > + __builtin_return_address(0), __func__, name); > + return -ENODEV; > + } > + > + io_data->len = (u32)resource_size(res); > + io_data->base = ioremap(res->start, io_data->len); > + if (!io_data->base) { > + DRM_ERROR("%pS->%s: '%s' ioremap failed\n", > + __builtin_return_address(0), __func__, name); > + return -EIO; > + } > + > + return 0; > +} msm_drv already has a msm_ioremap() function - you should consider reusing that. > + > +static void msm_dss_iounmap(struct dss_io_data *io_data) > +{ > + if (!io_data) { > + DRM_ERROR("%pS->%s: invalid input\n", > + __builtin_return_address(0), __func__); > + return; > + } > + > + if (io_data->base) { > + iounmap(io_data->base); > + io_data->base = NULL; > + } > + io_data->len = 0; > +} Bonus that msm_ioremap() uses dev_ioremap() so you probably don't need this function. > +static void dp_parser_unmap_io_resources(struct dp_parser *parser) > +{ > + struct dp_io *io = &parser->io; > + > + msm_dss_iounmap(&io->dp_ahb); > + msm_dss_iounmap(&io->dp_aux); > + msm_dss_iounmap(&io->dp_link); > + msm_dss_iounmap(&io->dp_p0); > + msm_dss_iounmap(&io->phy_io); > + msm_dss_iounmap(&io->ln_tx0_io); > + msm_dss_iounmap(&io->ln_tx0_io); > + msm_dss_iounmap(&io->dp_pll_io); > + msm_dss_iounmap(&io->dp_cc_io); > + msm_dss_iounmap(&io->usb3_dp_com); > + msm_dss_iounmap(&io->qfprom_io); > +} Or this function. > +static int dp_parser_ctrl_res(struct dp_parser *parser) > +{ > + int rc = 0; > + u32 index; > + struct platform_device *pdev = parser->pdev; > + struct device_node *of_node = parser->pdev->dev.of_node; > + struct dp_io *io = &parser->io; > + > + rc = of_property_read_u32(of_node, "cell-index", &index); > + if (rc) { > + DRM_ERROR("cell-index not specified, %d\n", rc); > + return rc; > + } Why is this fatal? > + rc = msm_dss_ioremap_byname(pdev, &io->dp_ahb, "dp_ahb"); > + if (rc) { > + DRM_ERROR("unable to remap dp io resources, %d\n", rc); > + goto err; If you follow the above suggestions, then you don't need to call dp_parser_unmap_io_resources and you can return rc (well, actually, the PTR_ERR of the return pointer). Also, msm_ioremap() already prints a log message so you don't need one. > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->dp_aux, "dp_aux"); > + if (rc) { > + DRM_ERROR("unable to remap dp io resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->dp_link, "dp_link"); > + if (rc) { > + DRM_ERROR("unable to remap dp io resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->dp_p0, "dp_p0"); > + if (rc) { > + DRM_ERROR("unable to remap dp io resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->phy_io, "dp_phy"); > + if (rc) { > + DRM_ERROR("unable to remap dp PHY resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->ln_tx0_io, "dp_ln_tx0"); > + if (rc) { > + DRM_ERROR("unable to remap dp TX0 resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->ln_tx1_io, "dp_ln_tx1"); > + if (rc) { > + DRM_ERROR("unable to remap dp TX1 resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->dp_pll_io, "dp_pll"); > + if (rc) { > + DRM_ERROR("unable to remap DP PLL resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->usb3_dp_com, "usb3_dp_com"); > + if (rc) { > + DRM_ERROR("unable to remap USB3 DP com resources, %d\n", rc); > + goto err; > + } > + > + rc = msm_dss_ioremap_byname(pdev, &io->dp_cc_io, "dp_mmss_cc"); > + if (rc) { > + DRM_ERROR("unable to remap dp MMSS_CC resources\n"); > + goto err; > + } > + > + if (msm_dss_ioremap_byname(pdev, &io->qfprom_io, "qfprom_physical")) > + pr_warn("unable to remap dp qfprom resources\n"); > + > + return 0; > +err: > + dp_parser_unmap_io_resources(parser); > + return rc; > +} > + > +static const char *dp_get_phy_aux_config_property(u32 cfg_type) > +{ > + switch (cfg_type) { > + case PHY_AUX_CFG0: > + return "qcom,aux-cfg0-settings"; > + case PHY_AUX_CFG1: > + return "qcom,aux-cfg1-settings"; > + case PHY_AUX_CFG2: > + return "qcom,aux-cfg2-settings"; > + case PHY_AUX_CFG3: > + return "qcom,aux-cfg3-settings"; > + case PHY_AUX_CFG4: > + return "qcom,aux-cfg4-settings"; > + case PHY_AUX_CFG5: > + return "qcom,aux-cfg5-settings"; > + case PHY_AUX_CFG6: > + return "qcom,aux-cfg6-settings"; > + case PHY_AUX_CFG7: > + return "qcom,aux-cfg7-settings"; > + case PHY_AUX_CFG8: > + return "qcom,aux-cfg8-settings"; > + case PHY_AUX_CFG9: > + return "qcom,aux-cfg9-settings"; > + default: > + return "unknown"; > + } > +} > + > +static int dp_parser_aux(struct dp_parser *parser) > +{ > + struct device_node *of_node = parser->pdev->dev.of_node; > + int len = 0, i = 0, j = 0, config_count = 0; > + const char *data; > + > + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { > + const char *property = dp_get_phy_aux_config_property(i); > + It looks like this is basically the same as: char property[24]; snprintf(property, sizeof(property), "qcom,aux-cfg%d-settings", i); > + data = of_get_property(of_node, property, &len); > + if (!data) { > + DRM_ERROR("Unable to read %s\n", property); > + goto error; > + } > + > + config_count = len - 1; > + if (config_count < 1 || /* minimum config count = 1 */ > + config_count > DP_AUX_CFG_MAX_VALUE_CNT) { > + DRM_ERROR("Invalid config count (%d) configs for %s\n", > + config_count, property); > + goto error; > + } > + > + parser->aux_cfg[i].offset = data[0]; > + parser->aux_cfg[i].cfg_cnt = config_count; > + DRM_DEBUG_DP("%s offset=0x%x, cfg_cnt=%d\n", > + property, > + parser->aux_cfg[i].offset, > + parser->aux_cfg[i].cfg_cnt); > + for (j = 1; j < len; j++) { > + parser->aux_cfg[i].lut[j - 1] = data[j]; > + DRM_DEBUG_DP("%s lut[%d]=0x%x\n", > + property, > + i, > + parser->aux_cfg[i].lut[j - 1]); > + } > + } > + return 0; > + > +error: > + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { > + parser->aux_cfg[i] = (const struct dp_aux_cfg){ 0 }; > + } > + return -EINVAL; > +} > + > +static int dp_parser_misc(struct dp_parser *parser) > +{ > + int rc = 0; > + struct device_node *of_node = parser->pdev->dev.of_node; > + of_property_read_u32 doesn't change the value of the pointer if it has an error, so this can be: parser->max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ; of_property_read_u32(of_node, "qcom,max-pclk-frequency-khz", &parser->max_pclk_khz); And thats it. In fact, this might not even qualify as a function - it seems like this would be a lot happier if it was done inline. > + rc = of_property_read_u32(of_node, > + "qcom,max-pclk-frequency-khz", &parser->max_pclk_khz); > + if (rc) > + parser->max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ; > + > + return 0; > +} > + > +static int dp_parser_pinctrl(struct dp_parser *parser) > +{ > + struct dp_pinctrl *pinctrl = &parser->pinctrl; > + > + pinctrl->pin = devm_pinctrl_get(&parser->pdev->dev); > + > + if (IS_ERR_OR_NULL(pinctrl->pin)) { > + DRM_ERROR("failed to get pinctrl, rc=%ld\n", PTR_ERR(pinctrl->pin)); > + return -EINVAL; > + } > + > + pinctrl->state_active = pinctrl_lookup_state(pinctrl->pin, > + "mdss_dp_active"); > + if (IS_ERR_OR_NULL(pinctrl->state_active)) { > + DRM_ERROR("failed to get pinctrl active state, %ld\n", > + PTR_ERR(pinctrl->state_active)); > + return -EINVAL; > + } > + > + pinctrl->state_suspend = pinctrl_lookup_state(pinctrl->pin, > + "mdss_dp_sleep"); > + if (IS_ERR_OR_NULL(pinctrl->state_suspend)) { > + DRM_ERROR("failed to get pinctrl suspend state, %ld\n", > + PTR_ERR(pinctrl->state_suspend)); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int dp_parser_gpio(struct dp_parser *parser) > +{ > + int i = 0; > + struct device *dev = &parser->pdev->dev; > + struct device_node *of_node = dev->of_node; > + struct dss_module_power *mp = &parser->mp[DP_CORE_PM]; > + static const char * const dp_gpios[] = { > + "qcom,aux-en-gpio", > + "qcom,aux-sel-gpio", > + "qcom,usbplug-cc-gpio", > + }; > + > + mp->gpio_config = devm_kzalloc(dev, > + sizeof(struct dss_gpio) * ARRAY_SIZE(dp_gpios), GFP_KERNEL); use devm_kcalloc() here instead. > + if (!mp->gpio_config) > + return -ENOMEM; > + > + mp->num_gpio = ARRAY_SIZE(dp_gpios); > + > + for (i = 0; i < mp->num_gpio; i++) { > + mp->gpio_config[i].gpio = of_get_named_gpio(of_node, > + dp_gpios[i], 0); > + > + if (!gpio_is_valid(mp->gpio_config[i].gpio)) { > + DRM_ERROR("%s gpio not specified\n", dp_gpios[i]); > + return -EINVAL; > + } > + > + strlcpy(mp->gpio_config[i].gpio_name, dp_gpios[i], > + sizeof(mp->gpio_config[i].gpio_name)); > + > + mp->gpio_config[i].value = 0; > + } > + > + return 0; > +} > + > +static const char *dp_parser_supply_node_name(enum dp_pm_type module) > +{ > + switch (module) { > + case DP_CORE_PM: return "qcom,core-supply-entries"; > + case DP_CTRL_PM: return "qcom,ctrl-supply-entries"; > + case DP_PHY_PM: return "qcom,phy-supply-entries"; > + default: return "???"; Is this even possible? > + } > +} > + > +static int dp_parser_get_vreg(struct dp_parser *parser, > + enum dp_pm_type module) > +{ > + int i = 0, rc = 0; > + u32 tmp = 0; > + const char *pm_supply_name = NULL; > + struct device_node *supply_node = NULL; > + struct device_node *of_node = parser->pdev->dev.of_node; > + struct device_node *supply_root_node = NULL; > + struct dss_module_power *mp = &parser->mp[module]; > + > + mp->num_vreg = 0; > + pm_supply_name = dp_parser_supply_node_name(module); > + supply_root_node = of_get_child_by_name(of_node, pm_supply_name); > + if (!supply_root_node) { > + DRM_ERROR("no supply entry present: %s\n", pm_supply_name); > + return 0; > + } > + > + mp->num_vreg = of_get_available_child_count(supply_root_node); > + > + if (mp->num_vreg == 0) { > + DRM_DEBUG_DP("no vreg\n"); > + return 0; > + } > + > + DRM_DEBUG_DP("vreg found. count=%d\n", mp->num_vreg); > + > + mp->vreg_config = devm_kzalloc(&parser->pdev->dev, > + sizeof(struct dss_vreg) * mp->num_vreg, GFP_KERNEL); use devm_kcalloc() > + if (!mp->vreg_config) { > + mp->num_vreg = 0; > + return -ENOMEM; > + } > + > + for_each_child_of_node(supply_root_node, supply_node) { > + const char *st = NULL; > + /* vreg-name */ > + rc = of_property_read_string(supply_node, > + "qcom,supply-name", &st); > + if (rc) { > + DRM_ERROR("error reading name. rc=%d\n", rc); > + mp->num_vreg = 0; > + return rc; > + } > + snprintf(mp->vreg_config[i].vreg_name, > + ARRAY_SIZE((mp->vreg_config[i].vreg_name)), "%s", st); > + /* vreg-min-voltage */ > + rc = of_property_read_u32(supply_node, > + "qcom,supply-min-voltage", &tmp); > + if (rc) { > + DRM_ERROR("error reading min volt. rc=%d\n", rc); > + mp->num_vreg = 0; > + return rc; > + } > + mp->vreg_config[i].min_voltage = tmp; > + > + /* vreg-max-voltage */ > + rc = of_property_read_u32(supply_node, > + "qcom,supply-max-voltage", &tmp); > + if (rc) { > + DRM_ERROR("error reading max volt. rc=%d\n", rc); > + mp->num_vreg = 0; > + return rc; > + } > + mp->vreg_config[i].max_voltage = tmp; > + > + /* enable-load */ > + rc = of_property_read_u32(supply_node, > + "qcom,supply-enable-load", &tmp); > + if (rc) { > + DRM_ERROR("error reading enable load. rc=%d\n", rc); > + mp->num_vreg = 0; > + return rc; > + } > + mp->vreg_config[i].enable_load = tmp; > + > + /* disable-load */ > + rc = of_property_read_u32(supply_node, > + "qcom,supply-disable-load", &tmp); > + if (rc) { > + DRM_ERROR("error reading disable load. rc=%d\n", rc); > + mp->num_vreg = 0; > + return rc; > + } > + mp->vreg_config[i].disable_load = tmp; > + > + DRM_DEBUG_DP("%s min=%d, max=%d, enable=%d, disable=%d\n", > + mp->vreg_config[i].vreg_name, > + mp->vreg_config[i].min_voltage, > + mp->vreg_config[i].max_voltage, > + mp->vreg_config[i].enable_load, > + mp->vreg_config[i].disable_load > + ); > + ++i; > + } > + > + return 0; > +} > + > +static int dp_parser_regulator(struct dp_parser *parser) > +{ > + int i, rc = 0; > + > + /* Parse the regulator information */ > + for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { > + rc = dp_parser_get_vreg(parser, i); > + if (rc) { > + DRM_ERROR("get_dt_vreg_data failed for %s. rc=%d\n", > + dp_parser_pm_name(i), rc); dp_parser_get_vreg prints a LOT of error messages. You don't need any more. > + i--; This line doesn't seem useful. > + return rc; > + } > + } > + > + return 0; > +} > + > +static inline bool dp_parser_check_prefix(const char *clk_prefix, const char *clk_name) > +{ > + return !strncmp(clk_prefix, clk_name, strlen(clk_prefix)); > +} > + > +static int dp_parser_init_clk_data(struct dp_parser *parser) > +{ > + int num_clk = 0, i = 0, rc = 0; > + int core_clk_count = 0, ctrl_clk_count = 0; > + const char *clk_name; > + struct device *dev = &parser->pdev->dev; > + struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; > + struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; > + > + num_clk = of_property_count_strings(dev->of_node, "clock-names"); > + if (num_clk <= 0) { > + DRM_ERROR("no clocks are defined\n"); > + return -EINVAL; > + } > + > + for (i = 0; i < num_clk; i++) { > + rc = of_property_read_string_index(dev->of_node, > + "clock-names", i, &clk_name); > + if (rc) { > + DRM_ERROR("error reading clock-names %d\n", rc); > + return rc; > + } > + > + if (dp_parser_check_prefix("core", clk_name)) > + core_clk_count++; > + > + if (dp_parser_check_prefix("ctrl", clk_name)) > + ctrl_clk_count++; > + } > + > + /* Initialize the CORE power module */ > + if (core_clk_count <= 0) { > + DRM_ERROR("no core clocks are defined\n"); > + return -EINVAL; > + } > + > + core_power->num_clk = core_clk_count; > + core_power->clk_config = devm_kzalloc(dev, > + sizeof(struct dss_clk) * core_power->num_clk, > + GFP_KERNEL); use devm_kcalloc() > + if (!core_power->clk_config) { > + return -EINVAL; return -ENOMEM; > + } > + > + /* Initialize the CTRL power module */ > + if (ctrl_clk_count <= 0) { > + DRM_ERROR("no ctrl clocks are defined\n"); > + return -EINVAL; > + } > + > + ctrl_power->num_clk = ctrl_clk_count; > + ctrl_power->clk_config = devm_kzalloc(dev, > + sizeof(struct dss_clk) * ctrl_power->num_clk, use devm_kcalloc() > + GFP_KERNEL); > + if (!ctrl_power->clk_config) { > + ctrl_power->num_clk = 0; > + return -EINVAL; return -ENOMEM; > + } > + > + return 0; > +} > + > +static int dp_parser_clock(struct dp_parser *parser) > +{ > + int rc = 0, i = 0; > + int num_clk = 0; > + int core_clk_index = 0, ctrl_clk_index = 0; > + int core_clk_count = 0, ctrl_clk_count = 0; > + const char *clk_name; > + struct device *dev = &parser->pdev->dev; > + struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; > + struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; > + > + core_power = &parser->mp[DP_CORE_PM]; > + ctrl_power = &parser->mp[DP_CTRL_PM]; > + > + rc = dp_parser_init_clk_data(parser); > + if (rc) { > + DRM_ERROR("failed to initialize power data %d\n", rc); Plenty of error messages in dp_parser_init_clk_data; > + return -EINVAL; You should return the value that dp_parser_init_clk_data returned. > + } > + > + core_clk_count = core_power->num_clk; > + ctrl_clk_count = ctrl_power->num_clk; > + > + num_clk = core_clk_count + ctrl_clk_count; > + > + for (i = 0; i < num_clk; i++) { > + rc = of_property_read_string_index(dev->of_node, "clock-names", > + i, &clk_name); > + if (rc) { > + DRM_ERROR("error reading clock-names %d\n", rc); > + return rc; > + } > + if (dp_parser_check_prefix("core", clk_name) && > + core_clk_index < core_clk_count) { > + struct dss_clk *clk = > + &core_power->clk_config[core_clk_index]; > + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); > + clk->type = DSS_CLK_AHB; > + core_clk_index++; > + } else if (dp_parser_check_prefix("ctrl", clk_name) && > + ctrl_clk_index < ctrl_clk_count) { > + struct dss_clk *clk = > + &ctrl_power->clk_config[ctrl_clk_index]; > + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); > + ctrl_clk_index++; > + > + if (!strncmp(clk_name, "ctrl_link_clk", strlen("ctrl_link_clk")) || > + !strncmp(clk_name, "ctrl_pixel_clk", strlen("ctrl_pixel_clk"))) As discussed above, the "clk" extension is frowned upon. And I don't think you want to use strncmp here because you are comparing the entire string so use strcmp() instead. > + clk->type = DSS_CLK_PCLK; > + else > + clk->type = DSS_CLK_AHB; > + } > + } > + > + DRM_DEBUG_DP("clock parsing successful\n"); > + > + return 0; > +} > + > +static int dp_parser_parse(struct dp_parser *parser) > +{ > + int rc = 0; > + > + if (!parser) { > + DRM_ERROR("invalid input\n"); > + return -EINVAL; > + } This won't be NULL. > + > + rc = dp_parser_ctrl_res(parser); > + if (rc) > + return rc; > + > + rc = dp_parser_aux(parser); > + if (rc) > + return rc; > + > + rc = dp_parser_misc(parser); > + if (rc) > + return rc; > + > + rc = dp_parser_clock(parser); > + if (rc) > + return rc; > + > + rc = dp_parser_regulator(parser); > + if (rc) > + return rc; > + > + rc = dp_parser_gpio(parser); > + if (rc) { > + DRM_ERROR("unable to parse GPIOs\n"); > + return rc; > + } > + > + rc = dp_parser_pinctrl(parser); > + > + return rc; > +} > + > +struct dp_parser *dp_parser_get(struct platform_device *pdev) > +{ > + struct dp_parser *parser; > + > + parser = devm_kzalloc(&pdev->dev, sizeof(*parser), GFP_KERNEL); > + if (!parser) > + return ERR_PTR(-ENOMEM); > + > + parser->parse = dp_parser_parse; > + parser->pdev = pdev; > + > + return parser; > +} > + > +void dp_parser_put(struct dp_parser *parser) > +{ > + > +} Why does this stub even exist? > diff --git a/drivers/gpu/drm/msm/dp/dp_parser.h b/drivers/gpu/drm/msm/dp/dp_parser.h > new file mode 100644 > index 0000000..e35a50b > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_parser.h > @@ -0,0 +1,196 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#ifndef _DP_PARSER_H_ > +#define _DP_PARSER_H_ > + > +#include "dpu_io_util.h" > + > +#define DP_LABEL "MDSS DP DISPLAY" > +#define AUX_CFG_LEN 10 > +#define DP_MAX_PIXEL_CLK_KHZ 675000 > + > +enum dp_pm_type { > + DP_CORE_PM, > + DP_CTRL_PM, > + DP_PHY_PM, > + DP_MAX_PM Comma needed after this line. > +}; > + > +static inline const char *dp_parser_pm_name(enum dp_pm_type module) > +{ > + switch (module) { > + case DP_CORE_PM: return "DP_CORE_PM"; > + case DP_CTRL_PM: return "DP_CTRL_PM"; > + case DP_PHY_PM: return "DP_PHY_PM"; > + default: return "???"; As with the other function that does something very similar, I don't think this is possible. > + } > +} > + > +/** > + * struct dp_display_data - display related device tree data. > + * > + * @ctrl_node: referece to controller device refrece -> reference > + * @phy_node: reference to phy device > + * @is_active: is the controller currently active > + * @name: name of the display > + * @display_type: type of the display > + */ > +struct dp_display_data { > + struct device_node *ctrl_node; > + struct device_node *phy_node; > + bool is_active; > + const char *name; > + const char *display_type; > +}; > + > +/** > + * struct dp_ctrl_resource - controller's IO related data > + * > + * @dp_ahb: controller's ahb mapped memory address > + * @dp_aux: controller's aux mapped memory address > + * @dp_link: controller's link mapped memory address > + * @dp_p0: controller's p0 mapped memory address > + * @phy_io: phy's mapped memory address > + * @ln_tx0_io: USB-DP lane TX0's mapped memory address > + * @ln_tx1_io: USB-DP lane TX1's mapped memory address > + * @dp_cc_io: DP cc's mapped memory address > + * @qfprom_io: qfprom's mapped memory address > + * @dp_pll_io: DP PLL mapped memory address > + * @usb3_dp_com: USB3 DP PHY combo mapped memory address > + */ > +struct dp_io { > + struct dss_io_data ctrl_io; > + struct dss_io_data dp_ahb; > + struct dss_io_data dp_aux; > + struct dss_io_data dp_link; > + struct dss_io_data dp_p0; > + struct dss_io_data phy_io; > + struct dss_io_data ln_tx0_io; > + struct dss_io_data ln_tx1_io; > + struct dss_io_data dp_cc_io; > + struct dss_io_data qfprom_io; > + struct dss_io_data dp_pll_io; > + struct dss_io_data usb3_dp_com; > +}; > + > +/** > + * struct dp_pinctrl - DP's pin control > + * > + * @pin: pin-controller's instance > + * @state_active: active state pin control > + * @state_hpd_active: hpd active state pin control > + * @state_suspend: suspend state pin control > + */ > +struct dp_pinctrl { > + struct pinctrl *pin; > + struct pinctrl_state *state_active; > + struct pinctrl_state *state_hpd_active; > + struct pinctrl_state *state_suspend; > +}; > + > +#define DP_ENUM_STR(x) #x > +#define DP_AUX_CFG_MAX_VALUE_CNT 3 > +/** > + * struct dp_aux_cfg - DP's AUX configuration settings > + * > + * @cfg_cnt: count of the configurable settings for the AUX register > + * @current_index: current index of the AUX config lut > + * @offset: register offset of the AUX config register > + * @lut: look up table for the AUX config values for this register > + */ > +struct dp_aux_cfg { > + u32 cfg_cnt; > + u32 current_index; > + u32 offset; > + u32 lut[DP_AUX_CFG_MAX_VALUE_CNT]; > +}; > + > +/* PHY AUX config registers */ > +enum dp_phy_aux_config_type { > + PHY_AUX_CFG0, > + PHY_AUX_CFG1, > + PHY_AUX_CFG2, > + PHY_AUX_CFG3, > + PHY_AUX_CFG4, > + PHY_AUX_CFG5, > + PHY_AUX_CFG6, > + PHY_AUX_CFG7, > + PHY_AUX_CFG8, > + PHY_AUX_CFG9, > + PHY_AUX_CFG_MAX, > +}; > + > +static inline char *dp_phy_aux_config_type_to_string(u32 cfg_type) > +{ > + switch (cfg_type) { > + case PHY_AUX_CFG0: > + return DP_ENUM_STR(PHY_AUX_CFG0); > + case PHY_AUX_CFG1: > + return DP_ENUM_STR(PHY_AUX_CFG1); > + case PHY_AUX_CFG2: > + return DP_ENUM_STR(PHY_AUX_CFG2); > + case PHY_AUX_CFG3: > + return DP_ENUM_STR(PHY_AUX_CFG3); > + case PHY_AUX_CFG4: > + return DP_ENUM_STR(PHY_AUX_CFG4); > + case PHY_AUX_CFG5: > + return DP_ENUM_STR(PHY_AUX_CFG5); > + case PHY_AUX_CFG6: > + return DP_ENUM_STR(PHY_AUX_CFG6); > + case PHY_AUX_CFG7: > + return DP_ENUM_STR(PHY_AUX_CFG7); > + case PHY_AUX_CFG8: > + return DP_ENUM_STR(PHY_AUX_CFG8); > + case PHY_AUX_CFG9: > + return DP_ENUM_STR(PHY_AUX_CFG9); > + default: > + return "unknown"; > + } > +} > + > +/** > + * struct dp_parser - DP parser's data exposed to clients > + * > + * @pdev: platform data of the client > + * @mp: gpio, regulator and clock related data > + * @pinctrl: pin-control related data > + * @disp_data: controller's display related data > + * @parse: function to be called by client to parse device tree. > + */ > +struct dp_parser { > + struct platform_device *pdev; > + struct dss_module_power mp[DP_MAX_PM]; > + struct dp_pinctrl pinctrl; > + struct dp_io io; > + struct dp_display_data disp_data; > + > + u8 l_map[4]; > + struct dp_aux_cfg aux_cfg[AUX_CFG_LEN]; > + u32 max_pclk_khz; > + > + int (*parse)(struct dp_parser *parser); > +}; > + > +/** > + * dp_parser_get() - get the DP's device tree parser module > + * > + * @pdev: platform data of the client > + * return: pointer to dp_parser structure. > + * > + * This function provides client capability to parse the > + * device tree and populate the data structures. The data > + * related to clock, regulators, pin-control and other > + * can be parsed using this module. > + */ > +struct dp_parser *dp_parser_get(struct platform_device *pdev); > + > +/** > + * dp_parser_put() - cleans the dp_parser module > + * > + * @parser: pointer to the parser's data. > + */ > +void dp_parser_put(struct dp_parser *parser); > +#endif > diff --git a/drivers/gpu/drm/msm/dp/dp_power.c b/drivers/gpu/drm/msm/dp/dp_power.c > new file mode 100644 > index 0000000..478de70 > --- /dev/null > +++ b/drivers/gpu/drm/msm/dp/dp_power.c > @@ -0,0 +1,733 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. > + */ > + > +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ > + > +#include <linux/clk.h> > +#include <linux/regulator/consumer.h> > +#include "dp_power.h" > + > +#define DP_CLIENT_NAME_SIZE 20 > + > +struct dp_power_private { > + struct dp_parser *parser; > + struct platform_device *pdev; > + struct clk *pixel_clk_rcg; > + struct clk *link_clk_src; > + struct clk *pixel_provider; > + struct clk *link_provider; > + > + struct dp_power dp_power; > +}; > + > +static int msm_dss_init_vreg(struct device *dev, struct dss_vreg *in_vreg, > + int num_vreg) > +{ > + int i = 0, rc = 0; > + struct dss_vreg *curr_vreg = NULL; > + enum dss_vreg_type type; > + > + if (!in_vreg || !num_vreg) > + return rc; This can just be return 0 > + > + for (i = 0; i < num_vreg; i++) { > + curr_vreg = &in_vreg[i]; > + curr_vreg->vreg = devm_regulator_get(dev, > + curr_vreg->vreg_name); > + rc = PTR_RET(curr_vreg->vreg); > + if (rc) { > + DRM_DEV_ERROR(dev, "%pS->%s: %s get failed. rc=%d\n", > + __builtin_return_address(0), __func__, this function is only called from one spot, so printing the return address isn't useful. > + curr_vreg->vreg_name, rc); > + curr_vreg->vreg = NULL; > + goto vreg_get_fail; > + } > + type = (regulator_count_voltages(curr_vreg->vreg) > 0) > + ? DSS_REG_LDO : DSS_REG_VS; > + if (type == DSS_REG_LDO) { > + rc = regulator_set_voltage( > + curr_vreg->vreg, > + curr_vreg->min_voltage, > + curr_vreg->max_voltage); > + if (rc < 0) { > + DRM_DEV_ERROR(dev, "%pS->%s:%s set vltg fail\n", > + __builtin_return_address(0), Same > + __func__, > + curr_vreg->vreg_name); > + goto vreg_set_voltage_fail; > + } > + } > + } > + return 0; > + > +vreg_unconfig: > +if (type == DSS_REG_LDO) > + regulator_set_load(curr_vreg->vreg, 0); > + > +vreg_set_voltage_fail: > + devm_regulator_put(curr_vreg->vreg); This will be done when the device/component is destroyed. > + curr_vreg->vreg = NULL; > + > +vreg_get_fail: > + for (i--; i >= 0; i--) { > + curr_vreg = &in_vreg[i]; > + type = (regulator_count_voltages(curr_vreg->vreg) > 0) > + ? DSS_REG_LDO : DSS_REG_VS; > + goto vreg_unconfig; > + } All of this is spaghetti code that could be turned into just a few lines: for( ; i >= 0; i--) { if (in_vreg[i].vreg) regulator_set_load(in_vreg[i].vreg, 0); } > + return rc; > +} > + > +static int msm_dss_deinit_vreg(struct device *dev, struct dss_vreg *in_vreg, > + int num_vreg) > +{ > + int i = 0, rc = 0; > + struct dss_vreg *curr_vreg = NULL; > + enum dss_vreg_type type; > + > + if (!in_vreg || !num_vreg) > + return rc; > + > + for (i = num_vreg-1; i >= 0; i--) { > + curr_vreg = &in_vreg[i]; > + if (curr_vreg->vreg) { > + type = (regulator_count_voltages( > + curr_vreg->vreg) > 0) > + ? DSS_REG_LDO : DSS_REG_VS; > + if (type == DSS_REG_LDO) { > + regulator_set_voltage(curr_vreg->vreg, > + 0, curr_vreg->max_voltage); > + } > + devm_regulator_put(curr_vreg->vreg); This will be done for you when the component/device is destroyed. > + curr_vreg->vreg = NULL; > + } > + } > + return 0; > +} > + > +static int msm_dss_enable_vreg(struct dss_vreg *in_vreg, int num_vreg, int enable) > +{ > + int i = 0, rc = 0; > + bool need_sleep; > + > + if (enable) { > + for (i = 0; i < num_vreg; i++) { > + if (IS_ERR_OR_NULL(in_vreg[i].vreg)) { > + rc = PTR_ERR(in_vreg[i].vreg); > + DRM_ERROR("%pS->%s: %s vreg error. rc=%d\n", > + __builtin_return_address(0), __func__, As above, this is only called from one function. > + in_vreg[i].vreg_name, rc); > + goto vreg_set_opt_mode_fail; > + } > + need_sleep = !regulator_is_enabled(in_vreg[i].vreg); > + if (in_vreg[i].pre_on_sleep && need_sleep) > + usleep_range(in_vreg[i].pre_on_sleep * 1000, > + in_vreg[i].pre_on_sleep * 1000); > + rc = regulator_set_load(in_vreg[i].vreg, > + in_vreg[i].enable_load); > + if (rc < 0) { > + DRM_ERROR("%pS->%s: %s set opt m fail\n", > + __builtin_return_address(0), __func__, As above. > + in_vreg[i].vreg_name); > + goto vreg_set_opt_mode_fail; > + } > + rc = regulator_enable(in_vreg[i].vreg); > + if (in_vreg[i].post_on_sleep && need_sleep) > + usleep_range(in_vreg[i].post_on_sleep * 1000, > + in_vreg[i].post_on_sleep * 1000); > + if (rc < 0) { > + DRM_ERROR("%pS->%s: %s enable failed\n", > + __builtin_return_address(0), __func__, As above > + in_vreg[i].vreg_name); > + goto disable_vreg; > + } > + } > + } else { > + for (i = num_vreg-1; i >= 0; i--) { > + if (in_vreg[i].pre_off_sleep) > + usleep_range(in_vreg[i].pre_off_sleep * 1000, > + in_vreg[i].pre_off_sleep * 1000); > + regulator_set_load(in_vreg[i].vreg, > + in_vreg[i].disable_load); > + regulator_disable(in_vreg[i].vreg); > + if (in_vreg[i].post_off_sleep) > + usleep_range(in_vreg[i].post_off_sleep * 1000, > + in_vreg[i].post_off_sleep * 1000); > + } > + } > + return rc; > + > +disable_vreg: > + regulator_set_load(in_vreg[i].vreg, in_vreg[i].disable_load); > + > +vreg_set_opt_mode_fail: > + for (i--; i >= 0; i--) { > + if (in_vreg[i].pre_off_sleep) > + usleep_range(in_vreg[i].pre_off_sleep * 1000, > + in_vreg[i].pre_off_sleep * 1000); > + regulator_set_load(in_vreg[i].vreg, > + in_vreg[i].disable_load); > + regulator_disable(in_vreg[i].vreg); > + if (in_vreg[i].post_off_sleep) > + usleep_range(in_vreg[i].post_off_sleep * 1000, > + in_vreg[i].post_off_sleep * 1000); > + } > + > + return rc; > +} /* msm_dss_enable_vreg */ > + > +static int dp_power_regulator_init(struct dp_power_private *power) > +{ > + int rc = 0, i = 0, j = 0; > + struct platform_device *pdev; > + struct dp_parser *parser; > + > + parser = power->parser; > + pdev = power->pdev; > + > + for (i = DP_CORE_PM; !rc && (i < DP_MAX_PM); i++) { > + rc = msm_dss_init_vreg(&pdev->dev, > + parser->mp[i].vreg_config, > + parser->mp[i].num_vreg); > + if (rc) { > + DRM_ERROR("failed to init vregs for %s\n", > + dp_parser_pm_name(i)); The calling function prints ever so many error messages. > + for (j = i - 1; j >= DP_CORE_PM; j--) { > + msm_dss_deinit_vreg(&pdev->dev, > + parser->mp[j].vreg_config, > + parser->mp[j].num_vreg); > + } > + > + goto error; return rc; > + } > + } > +error: > + return rc; Label isn't needed and you can return 0; > +} > + > +static void dp_power_regulator_deinit(struct dp_power_private *power) > +{ > + int rc = 0, i = 0; > + struct platform_device *pdev; > + struct dp_parser *parser; > + > + parser = power->parser; > + pdev = power->pdev; > + > + for (i = DP_CORE_PM; (i < DP_MAX_PM); i++) { > + rc = msm_dss_deinit_vreg(&pdev->dev, > + parser->mp[i].vreg_config, > + parser->mp[i].num_vreg); > + if (rc) > + DRM_ERROR("failed to deinit vregs for %s\n", > + dp_parser_pm_name(i)); The function you are calling prints error messages and anyway, you are in deinit so the error messages are probably not as interesting. > + } > +} > + > +static int dp_power_regulator_ctrl(struct dp_power_private *power, bool enable) > +{ > + int rc = 0, i = 0, j = 0; > + struct dp_parser *parser = power->parser; > + > + for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { > + rc = msm_dss_enable_vreg( > + parser->mp[i].vreg_config, > + parser->mp[i].num_vreg, enable); > + if (rc) { > + DRM_ERROR("failed to '%s' vregs for %s\n", > + enable ? "enable" : "disable", > + dp_parser_pm_name(i)); The called function has many error messages. > + if (enable) { > + for (j = i-1; j >= DP_CORE_PM; j--) { > + msm_dss_enable_vreg( > + parser->mp[j].vreg_config, > + parser->mp[j].num_vreg, 0); > + } > + } > + goto error; return rc; > + } > + } > +error: > + return rc; Label not needed and you can return 0; > +} > + > +static int dp_power_pinctrl_set(struct dp_power_private *power, bool active) > +{ > + int rc = -EFAULT; > + struct pinctrl_state *pin_state; > + struct dp_parser *parser = power->parser; > + > + if (IS_ERR_OR_NULL(parser->pinctrl.pin)) > + return PTR_ERR(parser->pinctrl.pin); if parser->pinctrl.pin is NULL this will return 0 which may not be your intention? > + > + pin_state = active ? parser->pinctrl.state_active > + : parser->pinctrl.state_suspend; > + > + rc = pinctrl_select_state(parser->pinctrl.pin, pin_state); > + if (rc) { > + DRM_ERROR("can not set %s pins\n", > + active ? "dp_active" : "dp_sleep"); > + return rc; You can skip this, drop out and return rc; > + } > + > + return 0; > +} > + > +static int dp_power_clk_init(struct dp_power_private *power) > +{ > + int rc = 0; > + struct dss_module_power *core, *ctrl; > + struct device *dev = &power->pdev->dev; > + > + core = &power->parser->mp[DP_CORE_PM]; > + ctrl = &power->parser->mp[DP_CTRL_PM]; > + > + if (power->parser->pll && power->parser->pll->get_provider) { > + rc = power->parser->pll->get_provider(power->parser->pll, > + &power->link_provider, &power->pixel_provider); > + if (rc) { > + pr_info("%s: can't get provider from pll, don't set parent\n", > + __func__); Do you want to use DRM_INFO here? > + return 0; > + } > + } > + > + rc = msm_dss_get_clk(dev, core->clk_config, core->num_clk); > + if (rc) { > + DRM_ERROR("failed to get %s clk. err=%d\n", > + dp_parser_pm_name(DP_CORE_PM), rc); > + return rc; > + } > + > + rc = msm_dss_get_clk(dev, ctrl->clk_config, ctrl->num_clk); > + if (rc) { > + DRM_ERROR("failed to get %s clk. err=%d\n", > + dp_parser_pm_name(DP_CTRL_PM), rc); > + msm_dss_put_clk(core->clk_config, core->num_clk); > + return -ENODEV; > + } > + > + power->pixel_clk_rcg = devm_clk_get(dev, "pixel_clk_rcg"); > + if (IS_ERR(power->pixel_clk_rcg)) { > + DRM_DEBUG_DP("Unable to get DP pixel clk RCG\n"); > + power->pixel_clk_rcg = NULL; > + msm_dss_put_clk(core->clk_config, core->num_clk); > + return -ENODEV; > + } > + > + return 0; > +} > + > +static int dp_power_clk_deinit(struct dp_power_private *power) > +{ > + struct dss_module_power *core, *ctrl; > + > + core = &power->parser->mp[DP_CORE_PM]; > + ctrl = &power->parser->mp[DP_CTRL_PM]; > + > + if (!core || !ctrl) { > + DRM_ERROR("invalid power_data\n"); You don't need a log message if you are in deinit > + return -EINVAL; > + } > + > + msm_dss_put_clk(ctrl->clk_config, ctrl->num_clk); > + msm_dss_put_clk(core->clk_config, core->num_clk); > + return 0; > +} > + > +static int dp_power_clk_set_rate(struct dp_power_private *power, > + enum dp_pm_type module, bool enable) > +{ > + int rc = 0; > + struct dss_module_power *mp = &power->parser->mp[module]; > + > + if (enable) { > + rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); > + if (rc) { > + DRM_ERROR("failed to set clks rate.\n"); > + return rc; > + } > + } > + > + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, enable); > + if (rc) { > + DRM_ERROR("failed to %d clks, err: %d\n", enable, rc); > + return rc; You can drop out and return rc; > + } > + > + return 0; > +} > + > +int dp_power_clk_enable(struct dp_power *dp_power, > + enum dp_pm_type pm_type, bool enable) > +{ > + int rc = 0; > + struct dss_module_power *mp; > + struct dp_power_private *power; > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + mp = &power->parser->mp[pm_type]; > + > + if (pm_type != DP_CORE_PM && pm_type != DP_CTRL_PM) { > + DRM_ERROR("unsupported power module: %s\n", > + dp_parser_pm_name(pm_type)); > + return -EINVAL; > + } > + > + if (enable) { > + if (pm_type == DP_CORE_PM && dp_power->core_clks_on) { > + DRM_DEBUG_DP("core clks already enabled\n"); > + return 0; > + } > + > + if (pm_type == DP_CTRL_PM && dp_power->link_clks_on) { > + DRM_DEBUG_DP("links clks already enabled\n"); > + return 0; > + } > + > + if ((pm_type == DP_CTRL_PM) && (!dp_power->core_clks_on)) { > + DRM_DEBUG_DP("Need to enable core clks before link clks\n"); > + > + rc = dp_power_clk_set_rate(power, DP_CORE_PM, enable); > + if (rc) { > + DRM_ERROR("failed to enable clks: %s. err=%d\n", > + dp_parser_pm_name(DP_CORE_PM), rc); > + return rc; > + } > + dp_power->core_clks_on = true; > + } > + } > + > + rc = dp_power_clk_set_rate(power, pm_type, enable); > + if (rc) { > + DRM_ERROR("failed to '%s' clks for: %s. err=%d\n", > + enable ? "enable" : "disable", > + dp_parser_pm_name(pm_type), rc); > + return rc; > + } > + > + if (pm_type == DP_CORE_PM) > + dp_power->core_clks_on = enable; > + else > + dp_power->link_clks_on = enable; > + > + DRM_DEBUG_DP("%s clocks for %s\n", > + enable ? "enable" : "disable", > + dp_parser_pm_name(pm_type)); > + DRM_DEBUG_DP("link_clks:%s core_clks:%s\n", > + dp_power->link_clks_on ? "on" : "off", > + dp_power->core_clks_on ? "on" : "off"); > + > + return 0; > +} > + > +static int dp_power_request_gpios(struct dp_power_private *power) > +{ > + int rc = 0, i; > + struct device *dev = &power->pdev->dev; > + struct dss_module_power *mp = &power->parser->mp[DP_CORE_PM]; > + static const char * const gpio_names[] = { > + "aux_enable", "aux_sel", "usbplug_cc", > + }; > + > + if (!power) { > + DRM_ERROR("invalid power data\n"); > + return -EINVAL; > + } > It doesn't look like power can be NULL here. > + for (i = 0; i < ARRAY_SIZE(gpio_names); i++) { > + unsigned int gpio = mp->gpio_config[i].gpio; > + > + if (!gpio_is_valid(gpio)) > + continue; > + > + rc = devm_gpio_request(dev, gpio, gpio_names[i]); > + if (rc) { > + DRM_ERROR("request %s gpio failed, rc=%d\n", > + gpio_names[i], rc); > + return rc; > + } > + } > + return 0; > +} > + > +static bool dp_power_find_gpio(const char *gpio1, const char *gpio2) > +{ > + return !!strnstr(gpio1, gpio2, strlen(gpio1)); > +} > + > +static void dp_power_set_gpio(struct dp_power_private *power, bool flip) > +{ > + int i; > + struct dss_module_power *mp = &power->parser->mp[DP_CORE_PM]; > + struct dss_gpio *config = mp->gpio_config; > + > + for (i = 0; i < mp->num_gpio; i++) { > + if (dp_power_find_gpio(config->gpio_name, "aux-sel")) > + config->value = flip; > + > + if (gpio_is_valid(config->gpio)) { > + DRM_DEBUG_DP("gpio %s, value %d\n", config->gpio_name, > + config->value); > + > + if (dp_power_find_gpio(config->gpio_name, "aux-en") || > + dp_power_find_gpio(config->gpio_name, "aux-sel")) > + gpio_direction_output(config->gpio, > + config->value); > + else > + gpio_set_value(config->gpio, config->value); > + > + } > + config++; > + } > +} > + > +static int dp_power_config_gpios(struct dp_power_private *power, bool flip, > + bool enable) > +{ > + int rc = 0, i; > + struct dss_module_power *mp = &power->parser->mp[DP_CORE_PM]; > + struct dss_gpio *config = mp->gpio_config; > + > + mp = &power->parser->mp[DP_CORE_PM]; > + config = mp->gpio_config; > + > + if (enable) { > + rc = dp_power_request_gpios(power); > + if (rc) { > + DRM_ERROR("gpio request failed\n"); the called function already prints error messages. > + return rc; > + } > + > + dp_power_set_gpio(power, flip); > + } else { > + for (i = 0; i < mp->num_gpio; i++) { > + gpio_set_value(config[i].gpio, 0); > + gpio_free(config[i].gpio); > + } > + } > + > + return 0; > +} > + > +int dp_power_client_init(struct dp_power *dp_power) > +{ > + int rc = 0; > + struct dp_power_private *power; > + > + if (!dp_power) { > + DRM_ERROR("invalid power data\n"); > + return -EINVAL; > + } > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + > + pm_runtime_enable(&power->pdev->dev); > + > + rc = dp_power_regulator_init(power); > + if (rc) { > + DRM_ERROR("failed to init regulators %d\n", rc); The called function and friends prints lots of error messages. > + goto error_power; > + } > + > + rc = dp_power_clk_init(power); > + if (rc) { > + DRM_ERROR("failed to init clocks %d\n", rc); Same. > + goto error_clk; > + } > + return 0; > + > +error_clk: > + dp_power_regulator_deinit(power); > +error_power: > + pm_runtime_disable(&power->pdev->dev); > + return rc; > +} > + > +void dp_power_client_deinit(struct dp_power *dp_power) > +{ > + struct dp_power_private *power; > + > + if (!dp_power) { > + DRM_ERROR("invalid power data\n"); This isn't a useful log message while you are tearing things down. > + return; > + } > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + > + dp_power_clk_deinit(power); > + dp_power_regulator_deinit(power); > + pm_runtime_disable(&power->pdev->dev); > + > +} > + > +int dp_power_set_link_clk_parent(struct dp_power *dp_power) > +{ > + int rc = 0; > + struct dp_power_private *power; > + u32 num; > + struct dss_clk *cfg; > + char *name = "ctrl_link_clk"; > + > + if (!dp_power) { > + DRM_ERROR("invalid power data\n"); return -EINVAL; > + rc = -EINVAL; > + goto exit; > + } > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + > + num = power->parser->mp[DP_CTRL_PM].num_clk; > + cfg = power->parser->mp[DP_CTRL_PM].clk_config; > + > + while (num && strcmp(cfg->clk_name, name)) { > + num--; > + cfg++; > + } > + > + if (num && power->link_provider) { > + power->link_clk_src = clk_get_parent(cfg->clk); > + if (power->link_clk_src) { > + clk_set_parent(power->link_clk_src, power->link_provider); > + DRM_DEBUG_DP("%s: is the parent of clk=%s\n", > + __clk_get_name(power->link_provider), > + __clk_get_name(power->link_clk_src)); > + } else { > + DRM_ERROR("couldn't get parent for clk=%s\n", name); > + rc = -EINVAL; > + } > + } else { > + DRM_ERROR("%s clock could not be set parent\n", name); > + rc = -EINVAL; > + } > +exit: Lable is not needed > + return rc; > +} > + > +int dp_power_set_pixel_clk_parent(struct dp_power *dp_power) > +{ > + int rc = 0; > + struct dp_power_private *power; > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + > + if (power->pixel_clk_rcg && power->pixel_provider) { > + rc = clk_set_parent(power->pixel_clk_rcg, power->pixel_provider); > + if (rc) { > + DRM_ERROR("failed to set parent clk src, %d\n", rc); > + return rc; > + } > + DRM_DEBUG_DP("%s: is the parent of clk=%s\n", > + __clk_get_name(power->pixel_provider), > + __clk_get_name(power->pixel_clk_rcg)); > + } > + > + return 0; > +} > + > +int dp_power_init(struct dp_power *dp_power, bool flip) > +{ > + int rc = 0; > + struct dp_power_private *power; > + > + if (!dp_power) { > + DRM_ERROR("invalid power data\n"); > + rc = -EINVAL; > + goto exit; This will call pm_runtime_put_sync which is what you don't want, just return -EINVAL; > + } > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + > + pm_runtime_get_sync(&power->pdev->dev); All of the functions here should be printing sufficient error messages so you don't need any more. > + rc = dp_power_regulator_ctrl(power, true); > + if (rc) { > + DRM_ERROR("failed to enable regulators, %d\n", rc); > + goto exit; > + } > + > + rc = dp_power_pinctrl_set(power, true); > + if (rc) { > + DRM_ERROR("failed to set pinctrl state, %d\n", rc); > + goto err_pinctrl; > + } > + > + rc = dp_power_config_gpios(power, flip, true); > + if (rc) { > + DRM_ERROR("failed to enable gpios, %d\n", rc); > + goto err_gpio; > + } > + > + rc = dp_power_clk_enable(dp_power, DP_CORE_PM, true); > + if (rc) { > + DRM_ERROR("failed to enable DP core clocks, %d\n", rc); > + goto err_clk; > + } > + > + return 0; > + > +err_clk: > + dp_power_config_gpios(power, flip, false); > +err_gpio: > + dp_power_pinctrl_set(power, false); > +err_pinctrl: > + dp_power_regulator_ctrl(power, false); > +exit: > + pm_runtime_put_sync(&power->pdev->dev); > + return rc; > +} > + > +int dp_power_deinit(struct dp_power *dp_power) > +{ > + struct dp_power_private *power; > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + > + dp_power_clk_enable(dp_power, DP_CORE_PM, false); > + dp_power_config_gpios(power, false, false); > + dp_power_pinctrl_set(power, false); > + dp_power_regulator_ctrl(power, false); > + pm_runtime_put_sync(&power->pdev->dev); > + return 0; > +} > + > +struct dp_power *dp_power_get(struct dp_parser *parser) > +{ > + int rc = 0; > + struct dp_power_private *power; > + struct dp_power *dp_power; > + > + if (!parser) { > + DRM_ERROR("invalid input\n"); return ERR_PTR(rc); > + rc = -EINVAL; > + goto error; > + } > + > + power = devm_kzalloc(&parser->pdev->dev, sizeof(*power), GFP_KERNEL); > + if (!power) { > + rc = -ENOMEM; return ERR_PTR(-ENOMEM); > + goto error; > + } > + > + power->parser = parser; > + power->pdev = parser->pdev; > + > + dp_power = &power->dp_power; > + > + return dp_power; > +error: > + return ERR_PTR(rc); > +} > + > +void dp_power_put(struct dp_power *dp_power) > +{ > + struct dp_power_private *power = NULL; > + > + if (!dp_power) > + return; > + > + power = container_of(dp_power, struct dp_power_private, dp_power); > + > + devm_kfree(&power->pdev->dev, power); Same comment as all of the other devm_kfree() callers > +} <snip> I have no problem with the rest. -- The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel