// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include "sde_rotator_r1_hwio.h" #include "sde_rotator_core.h" #include "sde_rotator_util.h" #include "sde_rotator_r1_internal.h" #include "sde_rotator_r1.h" #include "sde_rotator_r1_debug.h" struct sde_mdp_hw_resource { struct sde_rot_hw_resource hw; struct sde_mdp_ctl *ctl; struct sde_mdp_mixer *mixer; struct sde_mdp_pipe *pipe; struct sde_mdp_writeback *wb; }; struct sde_rotator_r1_data { struct sde_rot_mgr *mgr; int wb_id; int ctl_id; int irq_num; struct sde_mdp_hw_resource *mdp_hw; }; static u32 sde_hw_rotator_input_pixfmts[] = { SDE_PIX_FMT_XRGB_8888, SDE_PIX_FMT_ARGB_8888, SDE_PIX_FMT_ABGR_8888, SDE_PIX_FMT_RGBA_8888, SDE_PIX_FMT_BGRA_8888, SDE_PIX_FMT_RGBX_8888, SDE_PIX_FMT_BGRX_8888, SDE_PIX_FMT_XBGR_8888, SDE_PIX_FMT_RGBA_5551, SDE_PIX_FMT_ARGB_1555, SDE_PIX_FMT_ABGR_1555, SDE_PIX_FMT_BGRA_5551, SDE_PIX_FMT_BGRX_5551, SDE_PIX_FMT_RGBX_5551, SDE_PIX_FMT_XBGR_1555, SDE_PIX_FMT_XRGB_1555, SDE_PIX_FMT_ARGB_4444, SDE_PIX_FMT_RGBA_4444, SDE_PIX_FMT_BGRA_4444, SDE_PIX_FMT_ABGR_4444, SDE_PIX_FMT_RGBX_4444, SDE_PIX_FMT_XRGB_4444, SDE_PIX_FMT_BGRX_4444, SDE_PIX_FMT_XBGR_4444, SDE_PIX_FMT_RGB_888, SDE_PIX_FMT_BGR_888, SDE_PIX_FMT_RGB_565, SDE_PIX_FMT_BGR_565, SDE_PIX_FMT_Y_CB_CR_H2V2, SDE_PIX_FMT_Y_CR_CB_H2V2, SDE_PIX_FMT_Y_CR_CB_GH2V2, SDE_PIX_FMT_Y_CBCR_H2V2, SDE_PIX_FMT_Y_CRCB_H2V2, SDE_PIX_FMT_Y_CBCR_H1V2, SDE_PIX_FMT_Y_CRCB_H1V2, SDE_PIX_FMT_Y_CBCR_H2V1, SDE_PIX_FMT_Y_CRCB_H2V1, SDE_PIX_FMT_YCBYCR_H2V1, SDE_PIX_FMT_Y_CBCR_H2V2_VENUS, SDE_PIX_FMT_Y_CRCB_H2V2_VENUS, SDE_PIX_FMT_RGBA_8888_UBWC, SDE_PIX_FMT_RGBX_8888_UBWC, SDE_PIX_FMT_RGB_565_UBWC, SDE_PIX_FMT_Y_CBCR_H2V2_UBWC, }; static u32 sde_hw_rotator_output_pixfmts[] = { SDE_PIX_FMT_XRGB_8888, SDE_PIX_FMT_ARGB_8888, SDE_PIX_FMT_ABGR_8888, SDE_PIX_FMT_RGBA_8888, SDE_PIX_FMT_BGRA_8888, SDE_PIX_FMT_RGBX_8888, SDE_PIX_FMT_BGRX_8888, SDE_PIX_FMT_XBGR_8888, SDE_PIX_FMT_RGBA_5551, SDE_PIX_FMT_ARGB_1555, SDE_PIX_FMT_ABGR_1555, SDE_PIX_FMT_BGRA_5551, SDE_PIX_FMT_BGRX_5551, SDE_PIX_FMT_RGBX_5551, SDE_PIX_FMT_XBGR_1555, SDE_PIX_FMT_XRGB_1555, SDE_PIX_FMT_ARGB_4444, SDE_PIX_FMT_RGBA_4444, SDE_PIX_FMT_BGRA_4444, SDE_PIX_FMT_ABGR_4444, SDE_PIX_FMT_RGBX_4444, SDE_PIX_FMT_XRGB_4444, SDE_PIX_FMT_BGRX_4444, SDE_PIX_FMT_XBGR_4444, SDE_PIX_FMT_RGB_888, SDE_PIX_FMT_BGR_888, SDE_PIX_FMT_RGB_565, SDE_PIX_FMT_BGR_565, SDE_PIX_FMT_Y_CB_CR_H2V2, SDE_PIX_FMT_Y_CR_CB_H2V2, SDE_PIX_FMT_Y_CR_CB_GH2V2, SDE_PIX_FMT_Y_CBCR_H2V2, SDE_PIX_FMT_Y_CRCB_H2V2, SDE_PIX_FMT_Y_CBCR_H1V2, SDE_PIX_FMT_Y_CRCB_H1V2, SDE_PIX_FMT_Y_CBCR_H2V1, SDE_PIX_FMT_Y_CRCB_H2V1, SDE_PIX_FMT_YCBYCR_H2V1, SDE_PIX_FMT_Y_CBCR_H2V2_VENUS, SDE_PIX_FMT_Y_CRCB_H2V2_VENUS, SDE_PIX_FMT_RGBA_8888_UBWC, SDE_PIX_FMT_RGBX_8888_UBWC, SDE_PIX_FMT_RGB_565_UBWC, SDE_PIX_FMT_Y_CBCR_H2V2_UBWC, }; static struct sde_mdp_hw_resource *sde_rotator_hw_alloc( struct sde_rot_mgr *mgr, u32 ctl_id, u32 wb_id, int irq_num) { struct sde_mdp_hw_resource *mdp_hw; struct sde_rot_data_type *mdata = sde_rot_get_mdata(); int pipe_ndx, offset = ctl_id; int ret = 0; mdp_hw = devm_kzalloc(&mgr->pdev->dev, sizeof(struct sde_mdp_hw_resource), GFP_KERNEL); if (!mdp_hw) return ERR_PTR(-ENOMEM); mdp_hw->ctl = sde_mdp_ctl_alloc(mdata, offset); if (IS_ERR_OR_NULL(mdp_hw->ctl)) { SDEROT_ERR("unable to allocate ctl\n"); ret = -ENODEV; goto error; } mdp_hw->ctl->irq_num = irq_num; mdp_hw->wb = sde_mdp_wb_assign(wb_id, mdp_hw->ctl->num); if (IS_ERR_OR_NULL(mdp_hw->wb)) { SDEROT_ERR("unable to allocate wb\n"); ret = -ENODEV; goto error; } mdp_hw->ctl->wb = mdp_hw->wb; mdp_hw->mixer = sde_mdp_mixer_assign(mdp_hw->wb->num, true); if (IS_ERR_OR_NULL(mdp_hw->mixer)) { SDEROT_ERR("unable to allocate wb mixer\n"); ret = -ENODEV; goto error; } mdp_hw->ctl->mixer_left = mdp_hw->mixer; mdp_hw->mixer->ctl = mdp_hw->ctl; mdp_hw->mixer->rotator_mode = true; switch (mdp_hw->mixer->num) { case SDE_MDP_WB_LAYERMIXER0: mdp_hw->ctl->opmode = SDE_MDP_CTL_OP_ROT0_MODE; break; case SDE_MDP_WB_LAYERMIXER1: mdp_hw->ctl->opmode = SDE_MDP_CTL_OP_ROT1_MODE; break; default: SDEROT_ERR("invalid layer mixer=%d\n", mdp_hw->mixer->num); ret = -EINVAL; goto error; } mdp_hw->ctl->ops.start_fnc = sde_mdp_writeback_start; mdp_hw->ctl->wb_type = SDE_MDP_WB_CTL_TYPE_BLOCK; if (mdp_hw->ctl->ops.start_fnc) ret = mdp_hw->ctl->ops.start_fnc(mdp_hw->ctl); if (ret) goto error; /* override from dt */ pipe_ndx = wb_id; mdp_hw->pipe = sde_mdp_pipe_assign(mdata, mdp_hw->mixer, pipe_ndx); if (IS_ERR_OR_NULL(mdp_hw->pipe)) { SDEROT_ERR("dma pipe allocation failed\n"); ret = -ENODEV; goto error; } mdp_hw->pipe->mixer_left = mdp_hw->mixer; mdp_hw->hw.wb_id = mdp_hw->wb->num; mdp_hw->hw.pending_count = 0; atomic_set(&mdp_hw->hw.num_active, 0); mdp_hw->hw.max_active = 1; init_waitqueue_head(&mdp_hw->hw.wait_queue); return mdp_hw; error: if (!IS_ERR_OR_NULL(mdp_hw->pipe)) sde_mdp_pipe_destroy(mdp_hw->pipe); if (!IS_ERR_OR_NULL(mdp_hw->ctl)) { if (mdp_hw->ctl->ops.stop_fnc) mdp_hw->ctl->ops.stop_fnc(mdp_hw->ctl, 0); sde_mdp_ctl_free(mdp_hw->ctl); } devm_kfree(&mgr->pdev->dev, mdp_hw); return ERR_PTR(ret); } static void sde_rotator_hw_free(struct sde_rot_mgr *mgr, struct sde_mdp_hw_resource *mdp_hw) { struct sde_mdp_mixer *mixer; struct sde_mdp_ctl *ctl; if (!mgr || !mdp_hw) return; mixer = mdp_hw->pipe->mixer_left; sde_mdp_pipe_destroy(mdp_hw->pipe); ctl = sde_mdp_ctl_mixer_switch(mixer->ctl, SDE_MDP_WB_CTL_TYPE_BLOCK); if (ctl) { if (ctl->ops.stop_fnc) ctl->ops.stop_fnc(ctl, 0); sde_mdp_ctl_free(ctl); } devm_kfree(&mgr->pdev->dev, mdp_hw); } static struct sde_rot_hw_resource *sde_rotator_hw_alloc_ext( struct sde_rot_mgr *mgr, u32 pipe_id, u32 wb_id) { struct sde_mdp_hw_resource *mdp_hw; struct sde_rotator_r1_data *hw_data; if (!mgr || !mgr->hw_data) return NULL; hw_data = mgr->hw_data; mdp_hw = hw_data->mdp_hw; return &mdp_hw->hw; } static void sde_rotator_hw_free_ext(struct sde_rot_mgr *mgr, struct sde_rot_hw_resource *hw) { /* currently nothing specific for this device */ } static void sde_rotator_translate_rect(struct sde_rect *dst, struct sde_rect *src) { dst->x = src->x; dst->y = src->y; dst->w = src->w; dst->h = src->h; } static u32 sde_rotator_translate_flags(u32 input) { u32 output = 0; if (input & SDE_ROTATION_NOP) output |= SDE_ROT_NOP; if (input & SDE_ROTATION_FLIP_LR) output |= SDE_FLIP_LR; if (input & SDE_ROTATION_FLIP_UD) output |= SDE_FLIP_UD; if (input & SDE_ROTATION_90) output |= SDE_ROT_90; if (input & SDE_ROTATION_DEINTERLACE) output |= SDE_DEINTERLACE; if (input & SDE_ROTATION_SECURE) output |= SDE_SECURE_OVERLAY_SESSION; return output; } static int sde_rotator_config_hw(struct sde_rot_hw_resource *hw, struct sde_rot_entry *entry) { struct sde_mdp_hw_resource *mdp_hw; struct sde_mdp_pipe *pipe; struct sde_rotation_item *item; int ret; if (!hw || !entry) { SDEROT_ERR("null hw resource/entry"); return -EINVAL; } mdp_hw = container_of(hw, struct sde_mdp_hw_resource, hw); pipe = mdp_hw->pipe; item = &entry->item; pipe->flags = sde_rotator_translate_flags(item->flags); pipe->src_fmt = sde_get_format_params(item->input.format); pipe->img_width = item->input.width; pipe->img_height = item->input.height; sde_rotator_translate_rect(&pipe->src, &item->src_rect); sde_rotator_translate_rect(&pipe->dst, &item->src_rect); pipe->params_changed++; ret = sde_mdp_pipe_queue_data(pipe, &entry->src_buf); SDEROT_DBG("Config pipe. src{%u,%u,%u,%u}f=%u\n" "dst{%u,%u,%u,%u}f=%u session_id=%u\n", item->src_rect.x, item->src_rect.y, item->src_rect.w, item->src_rect.h, item->input.format, item->dst_rect.x, item->dst_rect.y, item->dst_rect.w, item->dst_rect.h, item->output.format, item->session_id); return ret; } static int sde_rotator_cancel_hw(struct sde_rot_hw_resource *hw, struct sde_rot_entry *entry) { return 0; } static int sde_rotator_abort_hw(struct sde_rot_hw_resource *hw, struct sde_rot_entry *entry) { return 0; } static int sde_rotator_kickoff_entry(struct sde_rot_hw_resource *hw, struct sde_rot_entry *entry) { struct sde_mdp_hw_resource *mdp_hw; int ret; struct sde_mdp_writeback_arg wb_args; if (!hw || !entry) { SDEROT_ERR("null hw resource/entry"); return -EINVAL; } wb_args.data = &entry->dst_buf; wb_args.priv_data = entry; mdp_hw = container_of(hw, struct sde_mdp_hw_resource, hw); ret = sde_mdp_writeback_display_commit(mdp_hw->ctl, &wb_args); return ret; } static int sde_rotator_wait_for_entry(struct sde_rot_hw_resource *hw, struct sde_rot_entry *entry) { struct sde_mdp_hw_resource *mdp_hw; int ret; struct sde_mdp_ctl *ctl; if (!hw || !entry) { SDEROT_ERR("null hw resource/entry"); return -EINVAL; } mdp_hw = container_of(hw, struct sde_mdp_hw_resource, hw); ctl = mdp_hw->ctl; ret = sde_mdp_display_wait4comp(ctl); return ret; } static int sde_rotator_hw_validate_entry(struct sde_rot_mgr *mgr, struct sde_rot_entry *entry) { int ret = 0; u16 src_w, src_h, dst_w, dst_h, bit; struct sde_rotation_item *item = &entry->item; struct sde_mdp_format_params *fmt; src_w = item->src_rect.w; src_h = item->src_rect.h; if (item->flags & SDE_ROTATION_90) { dst_w = item->dst_rect.h; dst_h = item->dst_rect.w; } else { dst_w = item->dst_rect.w; dst_h = item->dst_rect.h; } entry->dnsc_factor_w = 0; entry->dnsc_factor_h = 0; if ((src_w != dst_w) || (src_h != dst_h)) { if ((src_w % dst_w) || (src_h % dst_h)) { SDEROT_DBG("non integral scale not support\n"); ret = -EINVAL; goto dnsc_err; } entry->dnsc_factor_w = src_w / dst_w; bit = fls(entry->dnsc_factor_w); if ((entry->dnsc_factor_w & ~BIT(bit - 1)) || (bit > 5)) { SDEROT_DBG("non power-of-2 scale not support\n"); ret = -EINVAL; goto dnsc_err; } entry->dnsc_factor_h = src_h / dst_h; bit = fls(entry->dnsc_factor_h); if ((entry->dnsc_factor_h & ~BIT(bit - 1)) || (bit > 5)) { SDEROT_DBG("non power-of-2 dscale not support\n"); ret = -EINVAL; goto dnsc_err; } } fmt = sde_get_format_params(item->output.format); if (sde_mdp_is_ubwc_format(fmt) && (entry->dnsc_factor_h || entry->dnsc_factor_w)) { SDEROT_DBG("downscale with ubwc not support\n"); ret = -EINVAL; } dnsc_err: /* Downscaler does not support asymmetrical dnsc */ if (entry->dnsc_factor_w != entry->dnsc_factor_h) { SDEROT_DBG("asymmetric downscale not support\n"); ret = -EINVAL; } if (ret) { entry->dnsc_factor_w = 0; entry->dnsc_factor_h = 0; } return ret; } static ssize_t sde_rotator_hw_show_caps(struct sde_rot_mgr *mgr, struct device_attribute *attr, char *buf, ssize_t len) { struct sde_rotator_r1_data *hw_data; int cnt = 0; if (!mgr || !buf) return 0; hw_data = mgr->hw_data; #define SPRINT(fmt, ...) \ (cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__)) SPRINT("wb_id=%d\n", hw_data->wb_id); SPRINT("ctl_id=%d\n", hw_data->ctl_id); return cnt; } static ssize_t sde_rotator_hw_show_state(struct sde_rot_mgr *mgr, struct device_attribute *attr, char *buf, ssize_t len) { struct sde_rotator_r1_data *hw_data; int cnt = 0; if (!mgr || !buf) return 0; hw_data = mgr->hw_data; #define SPRINT(fmt, ...) \ (cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__)) if (hw_data && hw_data->mdp_hw) { struct sde_rot_hw_resource *hw = &hw_data->mdp_hw->hw; SPRINT("irq_num=%d\n", hw_data->irq_num); SPRINT("max_active=%d\n", hw->max_active); SPRINT("num_active=%d\n", atomic_read(&hw->num_active)); SPRINT("pending_cnt=%u\n", hw->pending_count); } return cnt; } /* * sde_hw_rotator_get_pixfmt - get the indexed pixel format * @mgr: Pointer to rotator manager * @index: index of pixel format * @input: true for input port; false for output port * @mode: operating mode */ static u32 sde_hw_rotator_get_pixfmt(struct sde_rot_mgr *mgr, int index, bool input, u32 mode) { if (input) { if (index < ARRAY_SIZE(sde_hw_rotator_input_pixfmts)) return sde_hw_rotator_input_pixfmts[index]; else return 0; } else { if (index < ARRAY_SIZE(sde_hw_rotator_output_pixfmts)) return sde_hw_rotator_output_pixfmts[index]; else return 0; } } /* * sde_hw_rotator_is_valid_pixfmt - verify if the given pixel format is valid * @mgr: Pointer to rotator manager * @pixfmt: pixel format to be verified * @input: true for input port; false for output port * @mode: operating mode */ static int sde_hw_rotator_is_valid_pixfmt(struct sde_rot_mgr *mgr, u32 pixfmt, bool input, u32 mode) { int i; if (input) { for (i = 0; i < ARRAY_SIZE(sde_hw_rotator_input_pixfmts); i++) if (sde_hw_rotator_input_pixfmts[i] == pixfmt) return true; } else { for (i = 0; i < ARRAY_SIZE(sde_hw_rotator_output_pixfmts); i++) if (sde_hw_rotator_output_pixfmts[i] == pixfmt) return true; } return false; } static int sde_rotator_hw_parse_dt(struct sde_rotator_r1_data *hw_data, struct platform_device *dev) { int ret = 0; u32 data; if (!hw_data || !dev) return -EINVAL; ret = of_property_read_u32(dev->dev.of_node, "qcom,mdss-wb-id", &data); if (ret) hw_data->wb_id = -1; else hw_data->wb_id = (int) data; ret = of_property_read_u32(dev->dev.of_node, "qcom,mdss-ctl-id", &data); if (ret) hw_data->ctl_id = -1; else hw_data->ctl_id = (int) data; return ret; } static int sde_rotator_hw_rev_init(struct sde_rot_data_type *mdata) { if (!mdata) { SDEROT_ERR("null rotator data\n"); return -EINVAL; } clear_bit(SDE_QOS_PER_PIPE_IB, mdata->sde_qos_map); set_bit(SDE_QOS_OVERHEAD_FACTOR, mdata->sde_qos_map); clear_bit(SDE_QOS_CDP, mdata->sde_qos_map); set_bit(SDE_QOS_OTLIM, mdata->sde_qos_map); set_bit(SDE_QOS_PER_PIPE_LUT, mdata->sde_qos_map); clear_bit(SDE_QOS_SIMPLIFIED_PREFILL, mdata->sde_qos_map); set_bit(SDE_CAPS_R1_WB, mdata->sde_caps_map); return 0; } enum { SDE_ROTATOR_INTR_WB_0, SDE_ROTATOR_INTR_WB_1, SDE_ROTATOR_INTR_MAX, }; struct intr_callback { void (*func)(void *data); void *arg; }; struct intr_callback sde_intr_cb[SDE_ROTATOR_INTR_MAX]; int sde_mdp_set_intr_callback(u32 intr_type, u32 intf_num, void (*fnc_ptr)(void *), void *arg) { if (intf_num >= SDE_ROTATOR_INTR_MAX) { SDEROT_WARN("invalid intr type=%u intf_num=%u\n", intr_type, intf_num); return -EINVAL; } sde_intr_cb[intf_num].func = fnc_ptr; sde_intr_cb[intf_num].arg = arg; return 0; } static irqreturn_t sde_irq_handler(int irq, void *ptr) { struct sde_rot_data_type *mdata = ptr; irqreturn_t ret = IRQ_NONE; u32 isr; isr = readl_relaxed(mdata->mdp_base + SDE_MDP_REG_INTR_STATUS); SDEROT_DBG("intr_status = %8.8x\n", isr); if (isr & SDE_MDP_INTR_WB_0_DONE) { struct intr_callback *cb = &sde_intr_cb[SDE_ROTATOR_INTR_WB_0]; if (cb->func) { writel_relaxed(SDE_MDP_INTR_WB_0_DONE, mdata->mdp_base + SDE_MDP_REG_INTR_CLEAR); cb->func(cb->arg); ret = IRQ_HANDLED; } } if (isr & SDE_MDP_INTR_WB_1_DONE) { struct intr_callback *cb = &sde_intr_cb[SDE_ROTATOR_INTR_WB_1]; if (cb->func) { writel_relaxed(SDE_MDP_INTR_WB_1_DONE, mdata->mdp_base + SDE_MDP_REG_INTR_CLEAR); cb->func(cb->arg); ret = IRQ_HANDLED; } } return ret; } static void sde_rotator_hw_destroy(struct sde_rot_mgr *mgr) { struct sde_rot_data_type *mdata = sde_rot_get_mdata(); struct sde_rotator_r1_data *hw_data; if (!mgr || !mgr->pdev || !mgr->hw_data) return; hw_data = mgr->hw_data; if (hw_data->irq_num >= 0) devm_free_irq(&mgr->pdev->dev, hw_data->irq_num, mdata); sde_rotator_hw_free(mgr, hw_data->mdp_hw); devm_kfree(&mgr->pdev->dev, mgr->hw_data); mgr->hw_data = NULL; } int sde_rotator_r1_init(struct sde_rot_mgr *mgr) { struct sde_rot_data_type *mdata = sde_rot_get_mdata(); struct sde_rotator_r1_data *hw_data; int ret; if (!mgr || !mgr->pdev) { SDEROT_ERR("null rotator manager/platform device"); return -EINVAL; } hw_data = devm_kzalloc(&mgr->pdev->dev, sizeof(struct sde_rotator_r1_data), GFP_KERNEL); if (hw_data == NULL) return -ENOMEM; mgr->hw_data = hw_data; mgr->ops_config_hw = sde_rotator_config_hw; mgr->ops_cancel_hw = sde_rotator_cancel_hw; mgr->ops_abort_hw = sde_rotator_abort_hw; mgr->ops_kickoff_entry = sde_rotator_kickoff_entry; mgr->ops_wait_for_entry = sde_rotator_wait_for_entry; mgr->ops_hw_alloc = sde_rotator_hw_alloc_ext; mgr->ops_hw_free = sde_rotator_hw_free_ext; mgr->ops_hw_destroy = sde_rotator_hw_destroy; mgr->ops_hw_validate_entry = sde_rotator_hw_validate_entry; mgr->ops_hw_show_caps = sde_rotator_hw_show_caps; mgr->ops_hw_show_state = sde_rotator_hw_show_state; mgr->ops_hw_create_debugfs = sde_rotator_r1_create_debugfs; mgr->ops_hw_get_pixfmt = sde_hw_rotator_get_pixfmt; mgr->ops_hw_is_valid_pixfmt = sde_hw_rotator_is_valid_pixfmt; ret = sde_rotator_hw_parse_dt(mgr->hw_data, mgr->pdev); if (ret) goto error_parse_dt; hw_data->irq_num = platform_get_irq(mgr->pdev, 0); if (hw_data->irq_num < 0) { SDEROT_ERR("fail to get rotator irq\n"); } else { ret = devm_request_threaded_irq(&mgr->pdev->dev, hw_data->irq_num, sde_irq_handler, NULL, 0, "sde_rotator_r1", mdata); if (ret) { SDEROT_ERR("fail to request irq r:%d\n", ret); hw_data->irq_num = -1; } else { disable_irq(hw_data->irq_num); } } hw_data->mdp_hw = sde_rotator_hw_alloc(mgr, hw_data->ctl_id, hw_data->wb_id, hw_data->irq_num); if (IS_ERR_OR_NULL(hw_data->mdp_hw)) goto error_hw_alloc; ret = sde_rotator_hw_rev_init(sde_rot_get_mdata()); if (ret) goto error_hw_rev_init; hw_data->mgr = mgr; return 0; error_hw_rev_init: if (hw_data->irq_num >= 0) devm_free_irq(&mgr->pdev->dev, hw_data->irq_num, mdata); sde_rotator_hw_free(mgr, hw_data->mdp_hw); error_hw_alloc: devm_kfree(&mgr->pdev->dev, mgr->hw_data); error_parse_dt: return ret; }