diff --git a/Makefile b/Makefile index 83cbd7e89da3..35bfbd4063ef 100644 --- a/Makefile +++ b/Makefile @@ -59,3 +59,13 @@ LINUXINCLUDE += -include $(srctree)/techpack/display/config/monacodispconf.h endif obj-$(CONFIG_DRM_MSM) += msm/ + +ifeq ($(CONFIG_ARCH_SDXLEMUR), y) +include $(srctree)/techpack/display/config/sdxlemurdisp.conf +endif + +ifeq ($(CONFIG_ARCH_SDXLEMUR), y) +LINUXINCLUDE += -include $(srctree)/techpack/display/config/sdxlemurdispconf.h +endif + +obj-$(CONFIG_DRM_QPIC_DISPLAY) += tinydrm/ diff --git a/config/sdxlemurdisp.conf b/config/sdxlemurdisp.conf new file mode 100644 index 000000000000..2cb972cac0f3 --- /dev/null +++ b/config/sdxlemurdisp.conf @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (c) 2021, The Linux Foundation. All rights reserved. + +export CONFIG_DRM_QPIC_DISPLAY=y diff --git a/config/sdxlemurdispconf.h b/config/sdxlemurdispconf.h new file mode 100644 index 000000000000..3a219b3335bd --- /dev/null +++ b/config/sdxlemurdispconf.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#define CONFIG_DRM_QPIC_DISPLAY 1 diff --git a/msm/sde/sde_crtc.c b/msm/sde/sde_crtc.c index 260e713c34d2..089e182e0238 100644 --- a/msm/sde/sde_crtc.c +++ b/msm/sde/sde_crtc.c @@ -1668,6 +1668,11 @@ static void _sde_crtc_blend_setup(struct drm_crtc *crtc, return; } + if (test_bit(SDE_CRTC_DIRTY_DIM_LAYERS, &sde_crtc->revalidate_mask)) { + set_bit(SDE_CRTC_DIRTY_DIM_LAYERS, sde_crtc_state->dirty); + clear_bit(SDE_CRTC_DIRTY_DIM_LAYERS, &sde_crtc->revalidate_mask); + } + for (i = 0; i < sde_crtc->num_mixers; i++) { if (!mixer[i].hw_lm) { SDE_ERROR("invalid lm or ctl assigned to mixer\n"); @@ -3977,6 +3982,7 @@ void sde_crtc_reset_sw_state(struct drm_crtc *crtc) { struct sde_crtc_state *cstate = to_sde_crtc_state(crtc->state); struct drm_plane *plane; + struct sde_crtc *sde_crtc = to_sde_crtc(crtc); /* mark planes, mixers, and other blocks dirty for next update */ drm_atomic_crtc_for_each_plane(plane, crtc) @@ -3986,7 +3992,7 @@ void sde_crtc_reset_sw_state(struct drm_crtc *crtc) sde_crtc_clear_cached_mixer_cfg(crtc); /* mark other properties which need to be dirty for next update */ - set_bit(SDE_CRTC_DIRTY_DIM_LAYERS, cstate->dirty); + set_bit(SDE_CRTC_DIRTY_DIM_LAYERS, &sde_crtc->revalidate_mask); if (cstate->num_ds_enabled) set_bit(SDE_CRTC_DIRTY_DEST_SCALER, cstate->dirty); } diff --git a/msm/sde/sde_crtc.h b/msm/sde/sde_crtc.h index 09646cc718ea..e5b924dba454 100644 --- a/msm/sde/sde_crtc.h +++ b/msm/sde/sde_crtc.h @@ -254,6 +254,7 @@ struct sde_crtc_misr_info { * @feature_list : list of color processing features supported on a crtc * @active_list : list of color processing features are active * @dirty_list : list of color processing features are dirty + * @revalidate_mask : stores dirty flags to revalidate after idlepc * @ad_dirty : list containing ad properties that are dirty * @ad_active : list containing ad properties that are active * @crtc_lock : crtc lock around create, destroy and access. @@ -348,6 +349,7 @@ struct sde_crtc { spinlock_t spin_lock; spinlock_t fevent_spin_lock; bool kickoff_in_progress; + unsigned long revalidate_mask; /* for handling internal event thread */ struct sde_crtc_event event_cache[SDE_CRTC_MAX_EVENT_COUNT]; diff --git a/msm/sde/sde_hw_catalog.c b/msm/sde/sde_hw_catalog.c index 74896a8edb2c..c97837507849 100644 --- a/msm/sde/sde_hw_catalog.c +++ b/msm/sde/sde_hw_catalog.c @@ -4740,6 +4740,19 @@ static int _sde_hardware_pre_caps(struct sde_mdss_cfg *sde_cfg, uint32_t hw_rev) sde_cfg->sui_block_xin_mask = 0x1; sde_cfg->has_hdr = false; sde_cfg->has_sui_blendstage = true; + } else if (IS_MONACO_TARGET(hw_rev)) { + sde_cfg->has_cwb_support = false; + sde_cfg->has_qsync = true; + sde_cfg->perf.min_prefill_lines = 24; + sde_cfg->vbif_qos_nlvl = 8; + sde_cfg->ts_prefill_rev = 2; + sde_cfg->ctl_rev = SDE_CTL_CFG_VERSION_1_0_0; + sde_cfg->delay_prg_fetch_start = true; + sde_cfg->sui_ns_allowed = true; + sde_cfg->sui_misr_supported = true; + sde_cfg->sui_block_xin_mask = 0x1; + sde_cfg->has_hdr = false; + sde_cfg->has_sui_blendstage = true; } else if (IS_LAHAINA_TARGET(hw_rev)) { sde_cfg->has_demura = true; sde_cfg->demura_supported[SSPP_DMA1][0] = 0; diff --git a/msm/sde/sde_hw_catalog.h b/msm/sde/sde_hw_catalog.h index 6bfaf2bf5080..f8c142649cb0 100644 --- a/msm/sde/sde_hw_catalog.h +++ b/msm/sde/sde_hw_catalog.h @@ -46,6 +46,7 @@ #define SDE_HW_VER_650 SDE_HW_VER(6, 5, 0) /* scuba */ #define SDE_HW_VER_660 SDE_HW_VER(6, 6, 0) /* holi */ #define SDE_HW_VER_670 SDE_HW_VER(6, 7, 0) /* shima */ +#define SDE_HW_VER_680 SDE_HW_VER(6, 8, 0) /* monaco */ #define SDE_HW_VER_690 SDE_HW_VER(6, 9, 0) /* blair */ #define SDE_HW_VER_700 SDE_HW_VER(7, 0, 0) /* lahaina */ #define SDE_HW_VER_720 SDE_HW_VER(7, 2, 0) /* yupik */ @@ -72,6 +73,7 @@ #define IS_SCUBA_TARGET(rev) IS_SDE_MAJOR_MINOR_SAME((rev), SDE_HW_VER_650) #define IS_HOLI_TARGET(rev) IS_SDE_MAJOR_MINOR_SAME((rev), SDE_HW_VER_660) #define IS_SHIMA_TARGET(rev) IS_SDE_MAJOR_MINOR_SAME((rev), SDE_HW_VER_670) +#define IS_MONACO_TARGET(rev) IS_SDE_MAJOR_MINOR_SAME((rev), SDE_HW_VER_680) #define IS_BLAIR_TARGET(rev) IS_SDE_MAJOR_MINOR_SAME((rev), SDE_HW_VER_690) #define IS_LAHAINA_TARGET(rev) IS_SDE_MAJOR_MINOR_SAME((rev), SDE_HW_VER_700) #define IS_YUPIK_TARGET(rev) IS_SDE_MAJOR_MINOR_SAME((rev), SDE_HW_VER_720) diff --git a/msm/sde/sde_hw_reg_dma_v1_color_proc.c b/msm/sde/sde_hw_reg_dma_v1_color_proc.c index 199550c078d1..7295d8d52870 100644 --- a/msm/sde/sde_hw_reg_dma_v1_color_proc.c +++ b/msm/sde/sde_hw_reg_dma_v1_color_proc.c @@ -529,7 +529,7 @@ void reg_dmav1_setup_dspp_vlutv18(struct sde_hw_dspp *ctx, void *cfg) return; } - data = kzalloc(VLUT_LEN, GFP_KERNEL); + data = kvzalloc(VLUT_LEN, GFP_KERNEL); if (!data) return; @@ -577,7 +577,7 @@ void reg_dmav1_setup_dspp_vlutv18(struct sde_hw_dspp *ctx, void *cfg) } exit: - kfree(data); + kvfree(data); /* update flush bit */ if (!rc && ctl && ctl->ops.update_bitmask_dspp_pavlut) { int dspp_idx; @@ -1198,7 +1198,7 @@ int reg_dmav1_setup_rc_datav1(struct sde_hw_dspp *ctx, void *cfg) } DRM_DEBUG_DRIVER("allocating %u bytes of memory for dma\n", buf_sz); - data = kzalloc(buf_sz, GFP_KERNEL); + data = kvzalloc(buf_sz, GFP_KERNEL); if (!data) { DRM_ERROR("memory allocation failed ret %d\n", rc); return -ENOMEM; @@ -1235,7 +1235,7 @@ int reg_dmav1_setup_rc_datav1(struct sde_hw_dspp *ctx, void *cfg) } exit: - kfree(data); + kvfree(data); return rc; } @@ -1339,7 +1339,7 @@ void reg_dmav1_setup_dspp_pcc_common(struct sde_hw_dspp *ctx, void *cfg) return; } - data = kzalloc(PCC_LEN, GFP_KERNEL); + data = kvzalloc(PCC_LEN, GFP_KERNEL); if (!data) return; @@ -1410,7 +1410,7 @@ void reg_dmav1_setup_dspp_pcc_common(struct sde_hw_dspp *ctx, void *cfg) DRM_ERROR("failed to kick off ret %d\n", rc); exit: - kfree(data); + kvfree(data); } @@ -2424,7 +2424,7 @@ static int reg_dmav1_setup_vig_igc_common(struct sde_hw_reg_dma_ops *dma_ops, hw_cfg->len, sizeof(struct drm_msm_igc_lut)); } - data = kzalloc(VIG_1D_LUT_IGC_LEN * sizeof(u32), GFP_KERNEL); + data = kvzalloc(VIG_1D_LUT_IGC_LEN * sizeof(u32), GFP_KERNEL); if (!data) return -ENOMEM; @@ -2486,7 +2486,7 @@ static int reg_dmav1_setup_vig_igc_common(struct sde_hw_reg_dma_ops *dma_ops, if (rc) DRM_ERROR("setting opcode failed ret %d\n", rc); exit: - kfree(data); + kvfree(data); return rc; } @@ -2693,7 +2693,7 @@ void reg_dmav1_setup_dma_igcv5(struct sde_hw_pipe *ctx, void *cfg, return; } - data = kzalloc(DMA_1D_LUT_IGC_LEN * sizeof(u32), GFP_KERNEL); + data = kvzalloc(DMA_1D_LUT_IGC_LEN * sizeof(u32), GFP_KERNEL); if (!data) { DRM_ERROR("failed to allocate memory for igc\n"); return; @@ -2755,7 +2755,7 @@ void reg_dmav1_setup_dma_igcv5(struct sde_hw_pipe *ctx, void *cfg, if (rc) DRM_ERROR("failed to kick off ret %d\n", rc); igc_exit: - kfree(data); + kvfree(data); } static void dma_gcv5_off(struct sde_hw_pipe *ctx, void *cfg, @@ -4118,7 +4118,7 @@ void reg_dmav2_setup_dspp_igcv4(struct sde_hw_dspp *ctx, void *cfg) if (len % transfer_size_bytes) len = len + (transfer_size_bytes - len % transfer_size_bytes); - data = kzalloc(len, GFP_KERNEL); + data = kvzalloc(len, GFP_KERNEL); if (!data) return; @@ -4166,7 +4166,7 @@ void reg_dmav2_setup_dspp_igcv4(struct sde_hw_dspp *ctx, void *cfg) _perform_sbdma_kickoff(ctx, hw_cfg, dma_ops, blk, IGC); exit: - kfree(data); + kvfree(data); } static void dspp_3d_gamutv43_off(struct sde_hw_dspp *ctx, void *cfg) @@ -4286,7 +4286,7 @@ void reg_dmav2_setup_dspp_3d_gamutv43(struct sde_hw_dspp *ctx, void *cfg) if (len % transfer_size_bytes) len = len + (transfer_size_bytes - len % transfer_size_bytes); - data = kzalloc(len, GFP_KERNEL); + data = kvzalloc(len, GFP_KERNEL); if (!data) return; @@ -4362,7 +4362,7 @@ void reg_dmav2_setup_dspp_3d_gamutv43(struct sde_hw_dspp *ctx, void *cfg) _perform_sbdma_kickoff(ctx, hw_cfg, dma_ops, blk, GAMUT); exit: - kfree(data); + kvfree(data); } void reg_dmav2_setup_vig_gamutv61(struct sde_hw_pipe *ctx, void *cfg) @@ -4440,7 +4440,7 @@ void reg_dmav2_setup_vig_gamutv61(struct sde_hw_pipe *ctx, void *cfg) if (len % transfer_size_bytes) len = len + (transfer_size_bytes - len % transfer_size_bytes); - data = kzalloc(len, GFP_KERNEL); + data = kvzalloc(len, GFP_KERNEL); if (!data) return; @@ -4505,7 +4505,7 @@ void reg_dmav2_setup_vig_gamutv61(struct sde_hw_pipe *ctx, void *cfg) DRM_ERROR("failed to kick off ret %d\n", rc); exit: - kfree(data); + kvfree(data); } int reg_dmav1_setup_spr_cfg3_params(struct sde_hw_dspp *ctx, @@ -4957,7 +4957,7 @@ static int __reg_dmav1_setup_demurav1_cfg0(struct sde_hw_dspp *ctx, return 0; } - temp = kzalloc(sizeof(struct drm_msm_dem_cfg), GFP_KERNEL); + temp = kvzalloc(sizeof(struct drm_msm_dem_cfg), GFP_KERNEL); if (!temp) return -ENOMEM; @@ -5093,7 +5093,7 @@ static int __reg_dmav1_setup_demurav1_cfg0(struct sde_hw_dspp *ctx, goto quit; } quit: - kfree(temp); + kvfree(temp); return rc; } @@ -5113,7 +5113,7 @@ static int __reg_dmav1_setup_demurav1_cfg1(struct sde_hw_dspp *ctx, return 0; } len = ARRAY_SIZE(dcfg->cfg1_param0_c0); - cfg1_data = kcalloc(len, sizeof(u32), GFP_KERNEL); + cfg1_data = kvzalloc((len * sizeof(u32)), GFP_KERNEL); if (!cfg1_data) return -ENOMEM; @@ -5191,7 +5191,7 @@ static int __reg_dmav1_setup_demurav1_cfg1(struct sde_hw_dspp *ctx, } quit: - kfree(cfg1_data); + kvfree(cfg1_data); return rc; } diff --git a/msm/sde/sde_hw_util.c b/msm/sde/sde_hw_util.c index 4890e8c9541c..eabdd4825a62 100644 --- a/msm/sde/sde_hw_util.c +++ b/msm/sde/sde_hw_util.c @@ -64,6 +64,7 @@ static u32 sde_hw_util_log_mask = SDE_DBG_MASK_NONE; #define QSEED4_DEFAULT_PRELOAD_V 0x2 #define QSEED4_DEFAULT_PRELOAD_H 0x4 +#define GET_REG_BLK_ID(c) (c->log_mask ? (ilog2(c->log_mask) + 1) : 0) typedef void (*scaler_lut_type)(struct sde_hw_blk_reg_map *, struct sde_hw_scaler3_cfg *, u32); @@ -76,10 +77,9 @@ void sde_reg_write(struct sde_hw_blk_reg_map *c, if (c->log_mask & sde_hw_util_log_mask) SDE_DEBUG_DRIVER("[%s:0x%X] <= 0x%X\n", name, c->blk_off + reg_off, val); - SDE_EVT32_REGWRITE(c->blk_off, reg_off, val); + SDE_EVT32_REGWRITE(c->blk_off + reg_off, val, GET_REG_BLK_ID(c)); writel_relaxed(val, c->base_off + c->blk_off + reg_off); - SDE_REG_LOG(c->log_mask ? ilog2(c->log_mask)+1 : 0, - val, c->blk_off + reg_off); + SDE_REG_LOG(GET_REG_BLK_ID(c), val, c->blk_off + reg_off); } int sde_reg_read(struct sde_hw_blk_reg_map *c, u32 reg_off) diff --git a/msm/sde/sde_rm.c b/msm/sde/sde_rm.c index f5221aae3f26..f9e7fd9ebf35 100644 --- a/msm/sde/sde_rm.c +++ b/msm/sde/sde_rm.c @@ -2453,11 +2453,14 @@ int sde_rm_reserve( * Poll for rsvp_nxt clear, allow the check_only commit if rsvp_nxt * gets cleared and bailout if it does not get cleared before timeout. */ - if (test_only && rsvp_cur && rsvp_nxt) { + if (test_only && rsvp_nxt) { rsvp_nxt = _sde_rm_poll_get_rsvp_nxt_locked(rm, enc); if (rsvp_nxt) { SDE_ERROR("poll timeout cur %d nxt %d enc %d\n", - rsvp_cur->seq, rsvp_nxt->seq, enc->base.id); + (rsvp_cur) ? rsvp_cur->seq : -1, + rsvp_nxt->seq, enc->base.id); + SDE_EVT32(enc->base.id, (rsvp_cur) ? rsvp_cur->seq : -1, + rsvp_nxt->seq, SDE_EVTLOG_ERROR); ret = -EINVAL; goto end; } diff --git a/msm/sde_io_util.c b/msm/sde_io_util.c index 1961dae2d0a6..a4b04f548e49 100644 --- a/msm/sde_io_util.c +++ b/msm/sde_io_util.c @@ -36,7 +36,8 @@ void dss_reg_w(struct dss_io_data *io, u32 offset, u32 value, u32 debug) DEV_DBG("[%08x] => %08x [%08x]\n", (u32)(unsigned long)(io->base + offset), value, in_val); - SDE_EVT32_REGWRITE(io->base, offset, value, in_val); + SDE_EVT32_REGWRITE(io->base + offset, value, + SDE_REG_LOG_RSCC, in_val); } SDE_REG_LOG(SDE_REG_LOG_RSCC, value, offset); } /* dss_reg_w */ diff --git a/tinydrm/Makefile b/tinydrm/Makefile new file mode 100644 index 000000000000..409a82ff4647 --- /dev/null +++ b/tinydrm/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_DRM_QPIC_DISPLAY) += qpic_display.o \ + qpic_panel_ili9341.o \ + diff --git a/tinydrm/qpic_display.c b/tinydrm/qpic_display.c new file mode 100644 index 000000000000..d000751bdd76 --- /dev/null +++ b/tinydrm/qpic_display.c @@ -0,0 +1,1386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qpic_display.h" +#include "qpic_display_panel.h" + +/* for debugging */ +static bool use_bam = true; +static bool use_irq = true; +static u32 use_vsync; + +/* QPIC display default format */ +static uint32_t qpic_pipe_formats[] = { + DRM_FORMAT_RGB565, +}; + +static const uint64_t qpic_pipe_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +void qpic_lcdc_reset(struct qpic_display_data *qpic_display) +{ + u32 time_end; + + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_RESET, 1 << 0); + /* wait 100 us after reset as suggested by hw */ + usleep_range(100, 110); + + time_end = (u32)ktime_to_ms(ktime_get()) + QPIC_MAX_VSYNC_WAIT_TIME_IN_MS; + while (((QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS) & (1 << 8)) == 0)) { + if ((u32)ktime_to_ms(ktime_get()) > time_end) { + pr_err("%s reset not finished\n", __func__); + break; + } + /* yield 100 us for next polling by experiment*/ + usleep_range(100, 110); + } +} + +static void qpic_lcdc_interrupt_en(struct qpic_display_data *qpic_display, bool en) +{ + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_CLR, 0xff); + if (en && !qpic_display->irq_ena) { + init_completion(&qpic_display->fifo_eof_comp); + qpic_display->irq_ena = true; + enable_irq(qpic_display->irq_id); + } else if (!en && qpic_display->irq_ena) { + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0); + disable_irq(qpic_display->irq_id); + qpic_display->irq_ena = false; + } +} + +static irqreturn_t qpic_lcdc_irq_handler(int irq, void *ptr) +{ + u32 data; + struct qpic_display_data *qpic_display = + (struct qpic_display_data *)ptr; + + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_CLR, 0xff); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0); + + if (data & (BIT(2) | (BIT(4)))) + complete(&qpic_display->fifo_eof_comp); + + return IRQ_HANDLED; +} + +static int qpic_display_pinctrl_set_state(struct qpic_panel_io_desc *qpic_panel_io, + bool active) +{ + struct pinctrl_state *pin_state; + int rc = -EFAULT; + + if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.pinctrl)) + return PTR_ERR(qpic_panel_io->pin_res.pinctrl); + + if (active) + gpio_direction_output(qpic_panel_io->bl_gpio, 1); + else + gpio_direction_output(qpic_panel_io->bl_gpio, 0); + + pin_state = active ? qpic_panel_io->pin_res.gpio_state_active + : qpic_panel_io->pin_res.gpio_state_suspend; + if (!IS_ERR_OR_NULL(pin_state)) { + rc = pinctrl_select_state(qpic_panel_io->pin_res.pinctrl, + pin_state); + if (rc) + pr_err("%s: can not set %s pins\n", __func__, + active ? QPIC_PINCTRL_STATE_DEFAULT + : QPIC_PINCTRL_STATE_SLEEP); + } else { + pr_err("%s: invalid '%s' pinstate\n", __func__, + active ? QPIC_PINCTRL_STATE_DEFAULT + : QPIC_PINCTRL_STATE_SLEEP); + } + + return rc; +} + +static int msm_qpic_bus_set_vote(struct qpic_display_data *qpic_display, bool vote) +{ + struct qpic_display_bus_scale_pdata *bvd = qpic_display->data_bus_pdata; + struct msm_bus_path *usecase = bvd->usecase; + struct bus_scaling_data *vec = usecase[vote].vec; + int ddr_rc; + + if (vote == bvd->curr_vote) + return 0; + + pr_debug("vote:%d qpic-display-data-bus ab:%llu ib:%llu\n", + vote, vec->ab, vec->ib); + ddr_rc = icc_set_bw(bvd->data_bus_hdl, vec->ab, vec->ib); + if (ddr_rc) + pr_err("icc_set() failed\n"); + else + bvd->curr_vote = vote; + + return ddr_rc; +} + +static struct qpic_display_bus_scale_pdata *qpic_display_get_bus_vote_data( + struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *of_node = dev->of_node; + struct qpic_display_bus_scale_pdata *bvd = NULL; + struct msm_bus_path *usecase = NULL; + int ret = 0, i = 0, j, num_paths, len; + const u32 *vec_arr = NULL; + + if (!pdev) { + dev_err(dev, "null platform device!\n"); + return NULL; + } + + bvd = devm_kzalloc(dev, sizeof(*bvd), GFP_KERNEL); + if (!bvd) + return bvd; + + ret = of_property_read_string(of_node, "qcom,msm-bus,name", + &bvd->name); + if (ret) { + dev_err(dev, "Bus name missing err:(%d)\n", ret); + goto out; + } + + ret = of_property_read_u32(of_node, "qcom,msm-bus,num-cases", + &bvd->num_usecase); + if (ret) { + dev_err(dev, "num-usecases not found err:(%d), bus name:%s\n", + ret, bvd->name); + goto out; + } + + if (bvd->num_usecase > 5) { + dev_err(dev, "invalid num_usecase:(%d), bus name:%s\n", + bvd->num_usecase, bvd->name); + goto out; + } + usecase = devm_kzalloc(dev, (sizeof(struct msm_bus_path) * + bvd->num_usecase), GFP_KERNEL); + if (!usecase) + goto out; + + ret = of_property_read_u32(of_node, "qcom,msm-bus,num-paths", &num_paths); + if (ret) { + dev_err(dev, "num_paths not found err:(%d)\n", ret); + goto out; + } + + vec_arr = of_get_property(of_node, "qcom,msm-bus,vectors-KBps", &len); + if (!vec_arr) { + dev_err(dev, "Vector array not found\n"); + goto out; + } + + for (i = 0; i < bvd->num_usecase; i++) { + usecase[i].num_paths = num_paths; + usecase[i].vec = devm_kcalloc(dev, num_paths, + sizeof(struct bus_scaling_data), + GFP_KERNEL); + if (!usecase[i].vec) + goto out; + for (j = 0; j < num_paths; j++) { + int idx = ((i * num_paths) + j) * 2; + + usecase[i].vec[j].ab = (u64) + be32_to_cpu(vec_arr[idx]); + usecase[i].vec[j].ib = (u64) + be32_to_cpu(vec_arr[idx + 1]); + } + } + + bvd->usecase = usecase; + return bvd; +out: + bvd = NULL; + return bvd; +} + +static int qpic_display_bus_register(struct platform_device *pdev, + struct qpic_display_data *qpic_display) +{ + struct qpic_display_bus_scale_pdata *bus_pdata; + struct device *dev = &pdev->dev; + int ret = 0; + + bus_pdata = qpic_display_get_bus_vote_data(dev); + if (!bus_pdata) { + dev_err(&pdev->dev, "Failed to get bus_scale data\n"); + return -EINVAL; + } + qpic_display->data_bus_pdata = bus_pdata; + + bus_pdata->data_bus_hdl = of_icc_get(&pdev->dev, "qpic-display-data-bus"); + if (IS_ERR_OR_NULL(bus_pdata->data_bus_hdl)) { + dev_err(&pdev->dev, "(%ld): failed getting %s path\n", + PTR_ERR(bus_pdata->data_bus_hdl), "qpic-display-data-bus"); + ret = PTR_ERR(bus_pdata->data_bus_hdl); + bus_pdata->data_bus_hdl = NULL; + } + + return ret; +} + +static void qpic_display_bus_unregister(struct qpic_display_data *qpic_display) +{ + struct qpic_display_bus_scale_pdata *bsd = qpic_display->data_bus_pdata; + + if (bsd) + icc_put(bsd->data_bus_hdl); +} + +static void qpic_display_clk_ctrl(struct qpic_display_data *qpic_display, bool enable) +{ + if (enable) { + if (qpic_display->qpic_clk) + clk_prepare_enable(qpic_display->qpic_clk); + if (qpic_display->qpic_a_clk) + clk_prepare_enable(qpic_display->qpic_a_clk); + } else { + if (qpic_display->qpic_a_clk) + clk_disable_unprepare(qpic_display->qpic_a_clk); + if (qpic_display->qpic_clk) + clk_disable_unprepare(qpic_display->qpic_clk); + } +} + +static int qpic_lcdc_wait_for_fifo(struct qpic_display_data *qpic_display) +{ + u32 data, time_end; + int ret = 0; + + if (use_irq) { + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS); + data &= 0x3F; + if (data == 0) + return ret; + reinit_completion(&qpic_display->fifo_eof_comp); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, (1 << 4)); + ret = wait_for_completion_timeout(&qpic_display->fifo_eof_comp, + msecs_to_jiffies(QPIC_MAX_VSYNC_WAIT_TIME_IN_MS)); + if (ret > 0) { + ret = 0; + } else { + pr_err("%s timeout %x\n", __func__, ret); + ret = -ETIMEDOUT; + } + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0); + } else { + time_end = (u32)ktime_to_ms(ktime_get()) + + QPIC_MAX_VSYNC_WAIT_TIME_IN_MS; + while (1) { + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS); + data &= 0x3F; + if (data == 0) + break; + /* yield 10 us for next polling by experiment*/ + usleep_range(10, 11); + if (ktime_to_ms(ktime_get()) > time_end) { + pr_err("%s time out\n", __func__); + ret = -EBUSY; + break; + } + } + } + + return ret; +} + +static int qpic_lcdc_send_pkt_bam(struct qpic_display_data *qpic_display, + u32 cmd, u32 len, u8 *param) +{ + int ret = 0; + u32 phys_addr, cfg2, block_len, flags; + + if ((cmd != OP_WRITE_MEMORY_START) && + (cmd != OP_WRITE_MEMORY_CONTINUE)) { + memcpy((u8 *)qpic_display->cmd_buf_virt, param, len); + phys_addr = qpic_display->cmd_buf_phys; + } else { + phys_addr = (u32)param; + } + cfg2 = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2); + cfg2 &= ~0xFF; + cfg2 |= cmd; + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, cfg2); + block_len = 0x7FF0; + while (len > 0) { + if (len <= 0x7FF0) { + flags = SPS_IOVEC_FLAG_EOT; + block_len = len; + } else { + flags = 0; + } + ret = sps_transfer_one(qpic_display->qpic_endpt.handle, + phys_addr, block_len, NULL, flags); + if (ret) + pr_err("failed to submit command %x ret %d\n", + cmd, ret); + phys_addr += block_len; + len -= block_len; + } + ret = wait_for_completion_timeout( + &qpic_display->qpic_endpt.completion, + msecs_to_jiffies(100 * 4)); + if (ret <= 0) + pr_err("%s timeout %x\n", __func__, ret); + else + ret = 0; + + return ret; +} + +static int qpic_lcdc_wait_for_eof(struct qpic_display_data *qpic_display) +{ + u32 data, time_end; + int ret = 0; + + if (use_irq) { + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS); + if (data & (1 << 2)) + return ret; + reinit_completion(&qpic_display->fifo_eof_comp); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, (1 << 2)); + ret = wait_for_completion_timeout(&qpic_display->fifo_eof_comp, + msecs_to_jiffies(QPIC_MAX_VSYNC_WAIT_TIME_IN_MS)); + if (ret > 0) { + ret = 0; + } else { + pr_err("%s timeout %x\n", __func__, ret); + ret = -ETIMEDOUT; + } + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN, 0); + } else { + time_end = (u32)ktime_to_ms(ktime_get()) + + QPIC_MAX_VSYNC_WAIT_TIME_IN_MS; + while (1) { + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS); + if (data & (1 << 2)) + break; + /* yield 10 us for next polling by experiment*/ + usleep_range(10, 11); + if (ktime_to_ms(ktime_get()) > time_end) { + pr_err("%s wait for eof time out\n", __func__); + ret = -EBUSY; + break; + } + } + } + + return ret; +} + +static int qpic_send_pkt_sw(struct qpic_display_data *qpic_display, + u32 cmd, u32 len, u8 *param) +{ + u32 bytes_left, space, data, cfg2; + int i, ret = 0; + + if (len <= 4) { + len = (len + 3) / 4; /* len in dwords */ + data = 0; + if (param) { + for (i = 0; i < len; i++) + data |= (u32)param[i] << (8 * i); + } + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT, len); + QPIC_OUTP(qpic_display, QPIC_REG_LCD_DEVICE_CMD0 + (4 * cmd), data); + return 0; + } + + if ((len & 0x1) != 0) { + pr_debug("%s: number of bytes needs be even\n", __func__); + len = (len + 1) & (~0x1); + } + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_CLR, 0xff); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT, 0); + cfg2 = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2); + if ((cmd != OP_WRITE_MEMORY_START) && + (cmd != OP_WRITE_MEMORY_CONTINUE)) + cfg2 |= (1 << 24); /* transparent mode */ + else + cfg2 &= ~(1 << 24); + + cfg2 &= ~0xFF; + cfg2 |= cmd; + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, cfg2); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_SOF, 0x0); + bytes_left = len; + + while (bytes_left > 0) { + ret = qpic_lcdc_wait_for_fifo(qpic_display); + if (ret) + goto exit_send_cmd_sw; + + space = 16; + + while ((space > 0) && (bytes_left > 0)) { + /* write to fifo */ + if (bytes_left >= 4) { + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_DATA_PORT0, + *(u32 *)param); + param += 4; + bytes_left -= 4; + space--; + } else if (bytes_left == 2) { + QPIC_OUTPW(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_DATA_PORT0, + *(u16 *)param); + bytes_left -= 2; + } + } + } + /* finished */ + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_EOF, 0x0); + ret = qpic_lcdc_wait_for_eof(qpic_display); +exit_send_cmd_sw: + cfg2 &= ~(1 << 24); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, cfg2); + return ret; +} + +int qpic_init_sps(struct qpic_display_data *qpic_display) +{ + int rc = 0; + struct platform_device *pdev = qpic_display->pdev; + struct qpic_sps_endpt *end_point = &qpic_display->qpic_endpt; + struct sps_pipe *pipe_handle; + struct sps_connect *sps_config = &end_point->config; + struct sps_register_event *sps_event = &end_point->bam_event; + struct sps_bam_props bam = {0}; + unsigned long bam_handle = 0; + + if (qpic_display->sps_init) + return 0; + + bam.phys_addr = qpic_display->qpic_phys + 0x4000; + bam.virt_addr = qpic_display->qpic_base + 0x4000; + bam.irq = qpic_display->irq_id - 4; + bam.manage = SPS_BAM_MGR_DEVICE_REMOTE | SPS_BAM_MGR_MULTI_EE; + + if (sps_phy2h(bam.phys_addr, &bam_handle)) { + if (sps_register_bam_device(&bam, &bam_handle)) { + pr_err("%s bam_handle is NULL\n", __func__); + rc = -ENOMEM; + goto out; + } + } + + pipe_handle = sps_alloc_endpoint(); + if (!pipe_handle) { + pr_err("sps_alloc_endpoint() failed\n"); + rc = -ENOMEM; + goto out; + } + + rc = sps_get_config(pipe_handle, sps_config); + if (rc) { + pr_err("sps_get_config() failed %d\n", rc); + goto free_endpoint; + } + + /* WRITE CASE: source - system memory; destination - BAM */ + sps_config->source = SPS_DEV_HANDLE_MEM; + sps_config->destination = bam_handle; + sps_config->mode = SPS_MODE_DEST; + sps_config->dest_pipe_index = 8; + + sps_config->options = SPS_O_AUTO_ENABLE | SPS_O_EOT; + sps_config->lock_group = 0; + /* + * Descriptor FIFO is a cyclic FIFO. If 64 descriptors + * are allowed to be submitted before we get any ack for any of them, + * the descriptor FIFO size should be: (SPS_MAX_DESC_NUM + 1) * + * sizeof(struct sps_iovec). + */ + sps_config->desc.size = (64) * sizeof(struct sps_iovec); + sps_config->desc.base = dmam_alloc_coherent(&pdev->dev, + sps_config->desc.size, + &sps_config->desc.phys_base, + GFP_KERNEL); + if (!sps_config->desc.base) { + pr_err("dmam_alloc_coherent() failed for size %x\n", + sps_config->desc.size); + rc = -ENOMEM; + goto free_endpoint; + } + memset(sps_config->desc.base, 0x00, sps_config->desc.size); + + rc = sps_connect(pipe_handle, sps_config); + if (rc) { + pr_err("sps_connect() failed %d\n", rc); + goto free_endpoint; + } + + init_completion(&end_point->completion); + sps_event->mode = SPS_TRIGGER_WAIT; + sps_event->options = SPS_O_EOT; + sps_event->xfer_done = &end_point->completion; + sps_event->user = (void *)qpic_display; + + rc = sps_register_event(pipe_handle, sps_event); + if (rc) { + pr_err("sps_register_event() failed %d\n", rc); + goto sps_disconnect; + } + + end_point->handle = pipe_handle; + qpic_display->sps_init = true; + goto out; +sps_disconnect: + sps_disconnect(pipe_handle); +free_endpoint: + sps_free_endpoint(pipe_handle); +out: + return rc; +} + +void qpic_dump_lcdc_reg(struct qpic_display_data *qpic_display) +{ + pr_info("%s\n", __func__); + pr_info("QPIC_REG_QPIC_LCDC_CTRL = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL)); + pr_info("QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT)); + pr_info("QPIC_REG_QPIC_LCDC_CFG0 = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG0)); + pr_info("QPIC_REG_QPIC_LCDC_CFG1 = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG1)); + pr_info("QPIC_REG_QPIC_LCDC_CFG2 = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2)); + pr_info("QPIC_REG_QPIC_LCDC_IRQ_EN = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_EN)); + pr_info("QPIC_REG_QPIC_LCDC_IRQ_STTS = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_IRQ_STTS)); + pr_info("QPIC_REG_QPIC_LCDC_STTS = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_STTS)); + pr_info("QPIC_REG_QPIC_LCDC_FIFO_SOF = %x\n", + QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_FIFO_SOF)); +} + +int qpic_send_pkt(struct qpic_display_data *qpic_display, + u32 cmd, u8 *param, u32 len) +{ + if (!use_bam || ((cmd != OP_WRITE_MEMORY_CONTINUE) && + (cmd != OP_WRITE_MEMORY_START))) + return qpic_send_pkt_sw(qpic_display, cmd, len, param); + else + return qpic_lcdc_send_pkt_bam(qpic_display, cmd, len, param); +} + +int qpic_display_init(struct qpic_display_data *qpic_display) +{ + int ret = 0; + u32 data; + + qpic_lcdc_reset(qpic_display); + + pr_info("%s QPIC version=%x\n", __func__, + QPIC_INP(qpic_display, QPIC_REG_LCDC_VERSION)); + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL); + /* clear vsync wait , bam mode = 0*/ + data &= ~(3 << 0); + data &= ~(0x1f << 3); + data |= (1 << 3); /* threshold */ + data |= (1 << 8); /* lcd_en */ + data &= ~(0x1f << 9); + data |= (1 << 9); /* threshold */ + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL, data); + + if (use_irq && (!qpic_display->irq_requested)) { + ret = devm_request_irq(&qpic_display->pdev->dev, + qpic_display->irq_id, qpic_lcdc_irq_handler, + IRQF_TRIGGER_NONE, "QPIC", &qpic_display); + if (ret) { + pr_err("qpic lcdc irq request failed! irq:%d\n", qpic_display->irq_id); + use_irq = false; + } else { + disable_irq(qpic_display->irq_id); + } + qpic_display->irq_requested = true; + } + + qpic_lcdc_interrupt_en(qpic_display, use_irq); + + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2); + if (qpic_display->panel_config->bpp == 24) { + data &= ~(0xFFF); + data |= 0x200; /* XRGB */ + data |= 0x2C; + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CFG2, data); + } + + if (use_bam) { + qpic_init_sps(qpic_display); + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL); + data |= (1 << 1); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL, data); + } + /* TE enable */ + if (use_vsync) { + data = QPIC_INP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL); + data |= (1 << 0); + QPIC_OUTP(qpic_display, QPIC_REG_QPIC_LCDC_CTRL, data); + } + + return ret; +} + +/* write a frame of pixels to a MIPI screen */ +u32 qpic_send_frame(struct qpic_display_data *qpic_display, + u32 x_start, u32 y_start, u32 x_end, u32 y_end, + u32 *data, u32 total_bytes) +{ + u8 param[4]; + u32 status; + u32 start_0_7; + u32 end_0_7; + u32 start_8_15; + u32 end_8_15; + + /* convert to 16 bit representation */ + x_start = x_start & 0xffff; + y_start = y_start & 0xffff; + x_end = x_end & 0xffff; + y_end = y_end & 0xffff; + + /* set column/page */ + start_0_7 = x_start & 0xff; + end_0_7 = x_end & 0xff; + start_8_15 = (x_start >> 8) & 0xff; + end_8_15 = (x_end >> 8) & 0xff; + param[0] = start_8_15; + param[1] = start_0_7; + param[2] = end_8_15; + param[3] = end_0_7; + status = qpic_send_pkt(qpic_display, OP_SET_COLUMN_ADDRESS, param, 4); + if (status) { + pr_err("Failed to set column address\n"); + return status; + } + + start_0_7 = y_start & 0xff; + end_0_7 = y_end & 0xff; + start_8_15 = (y_start >> 8) & 0xff; + end_8_15 = (y_end >> 8) & 0xff; + param[0] = start_8_15; + param[1] = start_0_7; + param[2] = end_8_15; + param[3] = end_0_7; + status = qpic_send_pkt(qpic_display, OP_SET_PAGE_ADDRESS, param, 4); + if (status) { + pr_err("Failed to set page address\n"); + return status; + } + + status = qpic_send_pkt(qpic_display, OP_WRITE_MEMORY_START, (u8 *)data, total_bytes); + if (status) { + pr_err("Failed to start memory write\n"); + return status; + } + + return 0; +} + +static int qpic_panel_regulator_init(struct qpic_panel_io_desc *panel_io) +{ + int rc; + + if (panel_io->vdd_vreg) { + rc = regulator_set_voltage(panel_io->vdd_vreg, + 1800000, 1800000); + if (rc) { + pr_err("iovdd_vreg->set_voltage failed, rc=%d\n", rc); + return -EINVAL; + } + } + if (panel_io->avdd_vreg) { + rc = regulator_set_voltage(panel_io->avdd_vreg, + 2704000, 2704000); + if (rc) { + pr_err("avdd_vreg->set_voltage failed, rc=%d\n", rc); + return -EINVAL; + } + } + + return 0; +} + +static void panel_io_disable(struct qpic_panel_io_desc *qpic_panel_io) +{ + if (qpic_display_pinctrl_set_state(qpic_panel_io, false)) + pr_warn("%s panel on: pinctrl not enabled\n", __func__); + + if (qpic_panel_io->vdd_vreg) + regulator_disable(qpic_panel_io->vdd_vreg); + if (qpic_panel_io->avdd_vreg) + regulator_disable(qpic_panel_io->avdd_vreg); +} + +static void qpic_display_io_free(struct qpic_panel_io_desc *qpic_panel_io) +{ + if (qpic_panel_io->ad8_gpio) + gpio_free(qpic_panel_io->ad8_gpio); + if (qpic_panel_io->cs_gpio) + gpio_free(qpic_panel_io->cs_gpio); + if (qpic_panel_io->rst_gpio) + gpio_free(qpic_panel_io->rst_gpio); + if (qpic_panel_io->te_gpio) + gpio_free(qpic_panel_io->te_gpio); + if (qpic_panel_io->bl_gpio) + gpio_free(qpic_panel_io->bl_gpio); +} + +static int panel_io_enable(struct qpic_panel_io_desc *qpic_panel_io) +{ + int rc; + + if (qpic_panel_io->vdd_vreg) { + rc = regulator_enable(qpic_panel_io->vdd_vreg); + if (rc) { + pr_err("enable vdd failed, rc=%d\n", rc); + return -ENODEV; + } + } + + if (qpic_panel_io->avdd_vreg) { + rc = regulator_enable(qpic_panel_io->avdd_vreg); + if (rc) { + pr_err("enable avdd failed, rc=%d\n", rc); + goto power_io_error; + } + } + + /* GPIO settings using pinctrl */ + if (qpic_display_pinctrl_set_state(qpic_panel_io, true)) + pr_warn("%s panel on: pinctrl not enabled\n", __func__); + + /* wait for 20 ms after enable gpio as suggested by hw */ + msleep(20); + + return 0; + +power_io_error: + if (qpic_panel_io->avdd_vreg) + regulator_disable(qpic_panel_io->avdd_vreg); + return -EINVAL; +} + +static int qpic_display_io_request(struct qpic_panel_io_desc *qpic_panel_io) +{ + if ((qpic_panel_io->rst_gpio) && + (gpio_request(qpic_panel_io->rst_gpio, "disp_rst_n"))) { + pr_err("%s request reset gpio failed\n", __func__); + goto power_io_error; + } + + if ((qpic_panel_io->cs_gpio) && + (gpio_request(qpic_panel_io->cs_gpio, "disp_cs_n"))) { + pr_err("%s request cs gpio failed\n", __func__); + goto power_io_error; + } + + if ((qpic_panel_io->ad8_gpio) && + (gpio_request(qpic_panel_io->ad8_gpio, "disp_ad8_n"))) { + pr_err("%s request ad8 gpio failed\n", __func__); + goto power_io_error; + } + + if ((qpic_panel_io->te_gpio) && + (gpio_request(qpic_panel_io->te_gpio, "disp_te_n"))) { + pr_err("%s request te gpio failed\n", __func__); + goto power_io_error; + } + + if ((qpic_panel_io->bl_gpio) && + (gpio_request(qpic_panel_io->bl_gpio, "disp_bl_n"))) { + pr_err("%s request bl gpio failed\n", __func__); + goto power_io_error; + } + + return 0; + +power_io_error: + return -EINVAL; +} + +int qpic_display_on(struct qpic_display_data *qpic_display) +{ + int rc = 0; + + qpic_display_clk_ctrl(qpic_display, true); + + if (qpic_display->is_qpic_on) { + pr_info("qpic already enabled\n"); + return rc; + } + + rc = qpic_display_init(qpic_display); + if (rc) + return rc; + + if (qpic_display->panel_on && !qpic_display->is_panel_on) { + panel_io_enable(&qpic_display->panel_io); + rc = qpic_display->panel_on(qpic_display); + if (rc) { + pr_err("failed to enable panel\n"); + return -EINVAL; + } + qpic_display->is_panel_on = true; + } else { + pr_info("panel on function is not specified or panel already on\n"); + return 0; + } + + qpic_display->is_qpic_on = true; + qpic_display->is_panel_on = true; + + return 0; +} + +int qpic_display_off(struct qpic_display_data *qpic_display) +{ + + if (qpic_display->panel_off && qpic_display->is_panel_on) { + qpic_display->panel_off(qpic_display); + qpic_display->is_panel_on = false; + } + + if (use_irq) + qpic_lcdc_interrupt_en(qpic_display, false); + + panel_io_disable(&qpic_display->panel_io); + + qpic_display_clk_ctrl(qpic_display, false); + + qpic_display->is_qpic_on = false; + qpic_display->is_panel_on = false; + + return 0; +} + +static void qpic_display_driver_release(struct drm_device *dev) +{ + DRM_DEBUG_DRIVER("\n"); + + drm_mode_config_cleanup(dev); + drm_dev_fini(dev); +} + +#define DRIVER_NAME "drm_qpic" +#define DRIVER_DESC "DRM QPIC display driver" +#define DRIVER_DATE "2021" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +DEFINE_DRM_GEM_CMA_FOPS(qpic_display_fops); + +static struct drm_driver qpic_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + + .release = qpic_display_driver_release, + .fops = &qpic_display_fops, + DRM_GEM_CMA_VMAP_DRIVER_OPS, +}; + +static const struct drm_mode_config_funcs qpic_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void qpic_display_fb_mark_dirty(struct drm_framebuffer *fb, struct drm_rect *rect) +{ + u32 size; + struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + struct dma_buf_attachment *import_attach = cma_obj->base.import_attach; + struct qpic_display_data *qpic_display = fb->dev->dev_private; + + if (!qpic_display->is_qpic_on || !qpic_display->is_panel_on) { + pr_info("%s: qpic or panel is not enabled\n", __func__); + return; + } + /* currently QPIC display SW can't support partial updates */ + rect->x1 = 0; + rect->x2 = fb->width; + rect->y1 = 0; + rect->y2 = fb->height; + + drm_framebuffer_get(fb); + + if (import_attach) + dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); + + msm_qpic_bus_set_vote(qpic_display, 1); + size = fb->width * fb->height * fb->format->depth / 8; + + qpic_send_frame(qpic_display, rect->x1, rect->x2, + rect->y1 - 1, rect->y2 - 1, + use_bam ? (u32 *)cma_obj->paddr:(u32 *)cma_obj->vaddr, size); + + msm_qpic_bus_set_vote(qpic_display, 0); + drm_framebuffer_put(fb); + + if (import_attach) + dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); + +} + +static void qpic_display_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ + struct qpic_display_data *qpic_display = pipe->crtc.dev->dev_private; + + qpic_display_on(qpic_display); + qpic_display->pipe_enabled = true; +} + +static void qpic_display_pipe_disable(struct drm_simple_display_pipe *pipe) +{ + struct qpic_display_data *qpic_display = pipe->crtc.dev->dev_private; + + qpic_display->pipe_enabled = false; + qpic_display_off(qpic_display); +} + +static void qpic_display_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct drm_plane_state *state = pipe->plane.state; + struct qpic_display_data *qpic_display = pipe->crtc.dev->dev_private; + struct drm_crtc *crtc = &pipe->crtc; + struct drm_rect rect = { 0, 0, + qpic_display->panel_config->xres, qpic_display->panel_config->yres }; + + if (drm_atomic_helper_damage_merged(old_state, state, &rect)) + qpic_display_fb_mark_dirty(pipe->plane.state->fb, &rect); + + if (crtc->state->event) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static int qpic_display_conn_get_modes(struct drm_connector *connector) +{ + struct qpic_display_data *qpic_display = connector->dev->dev_private; + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &qpic_display->drm_mode); + if (!mode) { + DRM_ERROR("Failed to duplicate mode\n"); + return 0; + } + + drm_mode_set_name(mode); + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_connector_helper_funcs qpic_display_conn_helper_funcs = { + .get_modes = qpic_display_conn_get_modes, +}; + +static const struct drm_connector_funcs qpic_display_conn_funcs = { + .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 int qpic_display_conn_init(struct qpic_display_data *qpic_display) +{ + drm_connector_helper_add(&qpic_display->conn, &qpic_display_conn_helper_funcs); + return drm_connector_init(&qpic_display->drm_dev, &qpic_display->conn, + &qpic_display_conn_funcs, DRM_MODE_CONNECTOR_9PinDIN); +} + +static void qpic_display_drm_mode_init(struct qpic_display_data *qpic_display) +{ + struct qpic_panel_config *panel_config = qpic_display->panel_config; + struct drm_display_mode qpic_display_mode = { + DRM_SIMPLE_MODE(panel_config->xres, panel_config->yres, 0, 0), + }; + + drm_mode_copy(&qpic_display->drm_mode, &qpic_display_mode); + + if (panel_config->bpp == 16) + qpic_pipe_formats[0] = DRM_FORMAT_RGB565; + else if (panel_config->bpp == 24) + qpic_pipe_formats[0] = DRM_FORMAT_RGB888; +} + +static const struct drm_simple_display_pipe_funcs qpic_pipe_funcs = { + .enable = qpic_display_pipe_enable, + .disable = qpic_display_pipe_disable, + .update = qpic_display_pipe_update, +}; + +static int qpic_get_panel_config(struct platform_device *pdev, + struct qpic_display_data *qpic_display) +{ + struct device_node *np = pdev->dev.of_node; + static const char *panel_name; + + panel_name = of_get_property(np, "panel-name", NULL); + if (panel_name) + pr_info("%s: panel name = %s\n", __func__, panel_name); + else + pr_info("panel name not specified\n"); + + /* select panel according to panel name */ + if (panel_name && !strcmp(panel_name, "ili_qvga")) { + pr_info("%s: select ili qvga lcdc panel\n"); + get_ili_qvga_panel_config(qpic_display); + } else { + /* select default panel */ + pr_info("%s: select default panel\n"); + get_ili_qvga_panel_config(qpic_display); + } + + if (!qpic_display->panel_config) { + pr_err("get panel config failed\n"); + return -EINVAL; + } + + return 0; +} + +static int qpic_display_pinctrl_init(struct platform_device *pdev, + struct qpic_panel_io_desc *qpic_panel_io) +{ + qpic_panel_io->pin_res.pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.pinctrl)) { + pr_err("%s: failed to get pinctrl\n", __func__); + return PTR_ERR(qpic_panel_io->pin_res.pinctrl); + } + + qpic_panel_io->pin_res.gpio_state_active + = pinctrl_lookup_state(qpic_panel_io->pin_res.pinctrl, + QPIC_PINCTRL_STATE_DEFAULT); + if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.gpio_state_active)) + pr_warn("%s: cannot get default pinstate\n", __func__); + + qpic_panel_io->pin_res.gpio_state_suspend + = pinctrl_lookup_state(qpic_panel_io->pin_res.pinctrl, + QPIC_PINCTRL_STATE_SLEEP); + if (IS_ERR_OR_NULL(qpic_panel_io->pin_res.gpio_state_suspend)) + pr_warn("%s: cannot get sleep pinstate\n", __func__); + + return 0; +} + +int qpic_display_io_init(struct platform_device *pdev, + struct qpic_panel_io_desc *qpic_panel_io) +{ + int rc = 0; + struct device_node *np = pdev->dev.of_node; + int rst_gpio, cs_gpio, te_gpio, ad8_gpio, bl_gpio; + struct regulator *vdd_vreg; + struct regulator *avdd_vreg; + + rc = qpic_display_pinctrl_init(pdev, qpic_panel_io); + if (rc) { + pr_err("%s: failed to get pin resources\n", __func__); + return rc; + } + + rst_gpio = of_get_named_gpio(np, "qcom,rst-gpio", 0); + cs_gpio = of_get_named_gpio(np, "qcom,cs-gpio", 0); + ad8_gpio = of_get_named_gpio(np, "qcom,ad8-gpio", 0); + te_gpio = of_get_named_gpio(np, "qcom,panel-te-gpio", 0); + bl_gpio = of_get_named_gpio(np, "qcom,panel-bl-gpio", 0); + + if (!gpio_is_valid(rst_gpio)) + pr_warn("%s: reset gpio not specified\n", __func__); + else + qpic_panel_io->rst_gpio = rst_gpio; + + if (!gpio_is_valid(cs_gpio)) + pr_warn("%s: cs gpio not specified\n", __func__); + else + qpic_panel_io->cs_gpio = cs_gpio; + + if (!gpio_is_valid(ad8_gpio)) + pr_warn("%s: ad8 gpio not specified\n", __func__); + else + qpic_panel_io->ad8_gpio = ad8_gpio; + + if (!gpio_is_valid(te_gpio)) + pr_warn("%s: te gpio not specified\n", __func__); + else + qpic_panel_io->te_gpio = te_gpio; + + if (!gpio_is_valid(bl_gpio)) + pr_warn("%s: te gpio not specified\n", __func__); + else + qpic_panel_io->bl_gpio = bl_gpio; + + vdd_vreg = devm_regulator_get_optional(&pdev->dev, "vdd"); + if (IS_ERR_OR_NULL(vdd_vreg)) + pr_err("%s could not get vdd\n", __func__); + else + qpic_panel_io->vdd_vreg = vdd_vreg; + + avdd_vreg = devm_regulator_get_optional(&pdev->dev, "avdd"); + if (IS_ERR_OR_NULL(avdd_vreg)) + pr_err("%s could not get avdd\n", __func__); + else + qpic_panel_io->avdd_vreg = avdd_vreg; + + return 0; +} + +int qpic_display_alloc_cmd_buf(struct qpic_display_data *qpic_display) +{ + if (!qpic_display->res_init) + return -EINVAL; + + if (qpic_display->cmd_buf_virt) + return 0; + + qpic_display->cmd_buf_virt = dmam_alloc_coherent( + &qpic_display->pdev->dev, QPIC_MAX_CMD_BUF_SIZE_IN_BYTES, + &qpic_display->cmd_buf_phys, GFP_KERNEL); + if (!qpic_display->cmd_buf_virt) { + pr_err("%s cmd buf allocation failed\n", __func__); + return -ENOMEM; + } + pr_info("%s cmd_buf virt=%x phys=%x\n", __func__, + (int) qpic_display->cmd_buf_virt, + qpic_display->cmd_buf_phys); + + return 0; +} + +int qpic_display_get_resource(struct qpic_display_data *qpic_display) +{ + struct resource *res; + int rc; + struct platform_device *pdev = qpic_display->pdev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qpic_base"); + if (!res) { + pr_err("unable to get QPIC reg base address\n"); + rc = -ENOMEM; + } + + qpic_display->qpic_reg_size = resource_size(res); + qpic_display->qpic_base = devm_ioremap(&pdev->dev, res->start, + qpic_display->qpic_reg_size); + if (unlikely(!qpic_display->qpic_base)) { + pr_err("unable to map MDSS QPIC base\n"); + rc = -ENOMEM; + } + qpic_display->qpic_phys = res->start; + pr_info("MDSS QPIC HW Base phy_Address=0x%x virt=0x%x\n", + (int) res->start, + (int) qpic_display->qpic_base); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + pr_err("unable to get QPIC irq\n"); + rc = -ENODEV; + } + + qpic_display->qpic_clk = devm_clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(qpic_display->qpic_clk)) { + qpic_display->qpic_clk = NULL; + pr_err("%s: Can't find core_clk\n", __func__); + return -ENODEV; + } + qpic_display->irq_id = res->start; + qpic_display->res_init = true; + + return 0; +} + +static int qpic_display_probe(struct platform_device *pdev) +{ + struct drm_device *drm_dev; + struct qpic_display_data *qpic_display; + int rc; + + if (!pdev->dev.of_node) { + pr_err("qpic driver only supports device tree probe\n"); + return -EOPNOTSUPP; + } + + qpic_display = devm_kzalloc(&pdev->dev, sizeof(*qpic_display), GFP_KERNEL); + if (!qpic_display) + return -ENOMEM; + + qpic_display->pdev = pdev; + platform_set_drvdata(pdev, qpic_display); + + rc = qpic_get_panel_config(pdev, qpic_display); + if (rc) { + pr_err("failed to get panel config\n"); + goto out; + } + + qpic_display->qpic_transfer = qpic_send_pkt; + + rc = qpic_display_get_resource(qpic_display); + if (rc) { + pr_err("qpic display get resource failed, rc = %d\n", rc); + goto out; + } + + rc = qpic_display_io_init(pdev, &qpic_display->panel_io); + if (rc) { + pr_err("qpic display IO init failed, rc = %d\n", rc); + goto out; + } + + rc = qpic_panel_regulator_init(&qpic_display->panel_io); + if (rc) { + pr_err("qpic panel regulator init failed, rc = %d\n", rc); + goto out; + } + + rc = qpic_display_io_request(&qpic_display->panel_io); + if (rc) { + pr_err("qpic display io request failed, rc = %d\n", rc); + goto out; + } + + rc = qpic_display_bus_register(pdev, qpic_display); + if (rc) { + pr_err("qpic display bus register failed, rc = %d\n", rc); + goto out; + } + + rc = qpic_display_alloc_cmd_buf(qpic_display); + if (rc) { + pr_err("qpic display allocate cmd buffer failed, rc = %d\n", rc); + goto bus_unregister; + } + + drm_dev = &qpic_display->drm_dev; + rc = drm_dev_init(drm_dev, &qpic_drm_driver, &pdev->dev); + if (rc || !drm_dev) { + pr_err("drm_dev_init failed, rc = %d\n", rc); + goto free_buf; + } + drm_dev->dev_private = qpic_display; + + qpic_display_drm_mode_init(qpic_display); + + drm_mode_config_init(drm_dev); + drm_dev->mode_config.funcs = &qpic_mode_config_funcs; + drm_dev->mode_config.min_width = qpic_display->drm_mode.hdisplay; + drm_dev->mode_config.max_width = qpic_display->drm_mode.hdisplay; + drm_dev->mode_config.min_height = qpic_display->drm_mode.vdisplay; + drm_dev->mode_config.max_height = qpic_display->drm_mode.vdisplay; + + rc = qpic_display_conn_init(qpic_display); + if (rc) { + pr_err("qpic display connector init failed, rc = %d\n", rc); + goto err_put; + } + + /* initialize a simple pipe line for drm driver*/ + rc = drm_simple_display_pipe_init(&qpic_display->drm_dev, + &qpic_display->pipe, + &qpic_pipe_funcs, + qpic_pipe_formats, + ARRAY_SIZE(qpic_pipe_formats), + qpic_pipe_modifiers, + &qpic_display->conn); + if (rc) + goto err_put; + + drm_mode_config_reset(drm_dev); + + rc = drm_dev_register(drm_dev, 0); + if (rc) + goto err_put; + + return rc; + +err_put: + drm_dev_put(drm_dev); +free_buf: + dmam_free_coherent(&qpic_display->pdev->dev, QPIC_MAX_CMD_BUF_SIZE_IN_BYTES, + qpic_display->cmd_buf_virt, qpic_display->cmd_buf_phys); +bus_unregister: + qpic_display_bus_unregister(qpic_display); +out: + return rc; +} + +static int qpic_display_remove(struct platform_device *pdev) +{ + struct qpic_display_data *qpic_display = platform_get_drvdata(pdev); + + drm_dev_unplug(&qpic_display->drm_dev); + drm_atomic_helper_shutdown(&qpic_display->drm_dev); + + qpic_display_io_free(&qpic_display->panel_io); + qpic_display_clk_ctrl(qpic_display, 0); + msm_qpic_bus_set_vote(qpic_display, 0); + qpic_display_bus_unregister(qpic_display); + return 0; +} + +static const struct of_device_id qpic_display_dt_match[] = { + { .compatible = "qcom,mdss_qpic",}, + {} +}; + +static struct platform_driver qpic_display_driver = { + .probe = qpic_display_probe, + .remove = qpic_display_remove, + .driver = { + .name = "qpic_display", + .of_match_table = qpic_display_dt_match, + }, +}; + +static int __init qpic_display_driver_init(void) +{ + int ret; + + ret = platform_driver_register(&qpic_display_driver); + if (ret) + pr_err("qpic_display_register_driver() failed!,ret=%d\n", ret); + return ret; +} + +MODULE_DEVICE_TABLE(of, qpic_display_dt_match); + +module_init(qpic_display_driver_init); + diff --git a/tinydrm/qpic_display.h b/tinydrm/qpic_display.h new file mode 100644 index 000000000000..7e5af6e3a885 --- /dev/null +++ b/tinydrm/qpic_display.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014-2015, 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QPIC_DISPLAY_H__ +#define __QPIC_DISPLAY_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QPIC_REG_QPIC_LCDC_CTRL 0x22000 +#define QPIC_REG_LCDC_VERSION 0x22004 +#define QPIC_REG_QPIC_LCDC_IRQ_EN 0x22008 +#define QPIC_REG_QPIC_LCDC_IRQ_STTS 0x2200C +#define QPIC_REG_QPIC_LCDC_IRQ_CLR 0x22010 +#define QPIC_REG_QPIC_LCDC_STTS 0x22014 +#define QPIC_REG_QPIC_LCDC_CMD_DATA_CYCLE_CNT 0x22018 +#define QPIC_REG_QPIC_LCDC_CFG0 0x22020 +#define QPIC_REG_QPIC_LCDC_CFG1 0x22024 +#define QPIC_REG_QPIC_LCDC_CFG2 0x22028 +#define QPIC_REG_QPIC_LCDC_RESET 0x2202C +#define QPIC_REG_QPIC_LCDC_FIFO_SOF 0x22100 +#define QPIC_REG_LCD_DEVICE_CMD0 0x23000 +#define QPIC_REG_QPIC_LCDC_FIFO_DATA_PORT0 0x22140 +#define QPIC_REG_QPIC_LCDC_FIFO_EOF 0x22180 + +#define QPIC_OUTP(qpic_display, off, data) \ + writel_relaxed((data), (qpic_display)->qpic_base + (off)) +#define QPIC_OUTPW(qpic_display, off, data) \ + writew_relaxed((data), (qpic_display)->qpic_base + (off)) +#define QPIC_INP(qpic_display, off) \ + readl_relaxed((qpic_display)->qpic_base + (off)) + +#define QPIC_MAX_VSYNC_WAIT_TIME_IN_MS 500 + +#define QPIC_MAX_CMD_BUF_SIZE_IN_BYTES 512 +#define QPIC_PINCTRL_STATE_DEFAULT "qpic_display_default" +#define QPIC_PINCTRL_STATE_SLEEP "qpic_display_sleep" + +/* Structure that defines an SPS end point for a BAM pipe. */ +struct qpic_sps_endpt { + struct sps_pipe *handle; + struct sps_connect config; + struct sps_register_event bam_event; + struct completion completion; +}; + +struct qpic_panel_config { + u32 xres; + u32 yres; + u32 bpp; + u32 type; +}; + +struct qpic_pinctrl_res { + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; +}; + +struct qpic_panel_io_desc { + int rst_gpio; + int cs_gpio; + int ad8_gpio; + int te_gpio; + int bl_gpio; + struct regulator *vdd_vreg; + struct regulator *avdd_vreg; + struct qpic_pinctrl_res pin_res; +}; + +struct bus_scaling_data { + u64 ab; + u64 ib; +}; + +struct msm_bus_path { + unsigned int num_paths; + struct bus_scaling_data *vec; +}; + +struct qpic_display_bus_scale_pdata { + const char *name; + unsigned int num_usecase; + struct msm_bus_path *usecase; + struct icc_path *data_bus_hdl; + u32 curr_vote; +}; + +struct qpic_display_data { + u32 rev; + struct platform_device *pdev; + struct drm_device drm_dev; + struct drm_simple_display_pipe pipe; + struct drm_connector conn; + struct drm_display_mode drm_mode; + bool pipe_enabled; + + size_t qpic_reg_size; + u32 qpic_phys; + char __iomem *qpic_base; + u32 irq_id; + bool irq_ena; + u32 res_init; + + void *cmd_buf_virt; + phys_addr_t cmd_buf_phys; + struct qpic_sps_endpt qpic_endpt; + u32 sps_init; + u32 irq_requested; + struct qpic_display_bus_scale_pdata *data_bus_pdata; + struct completion fifo_eof_comp; + bool is_qpic_on; + struct clk *qpic_clk; + struct clk *qpic_a_clk; + int (*qpic_transfer)(struct qpic_display_data *qpic_display, + u32 cmd, u8 *param, u32 len); + + bool is_panel_on; + struct qpic_panel_config *panel_config; + struct qpic_panel_io_desc panel_io; + int (*panel_on)(struct qpic_display_data *qpic_display); + void (*panel_off)(struct qpic_display_data *qpic_display); + +}; + +int get_ili_qvga_panel_config(struct qpic_display_data *qpic_display); + +#endif diff --git a/tinydrm/qpic_display_panel.h b/tinydrm/qpic_display_panel.h new file mode 100644 index 000000000000..5b3121d76821 --- /dev/null +++ b/tinydrm/qpic_display_panel.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014-2015, 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __QPIC_DISPLAY_PANEL_H__ +#define __QPIC_DISPLAY_PANEL_H__ + +#include + +/** + * Macros for coding MIPI commands + */ +#define INV_SIZE 0xFFFF +/* Size of argument to MIPI command is variable */ +#define OP_SIZE_PAIR(op, size) ((op<<16) | size) +/* MIPI {command, argument size} tuple */ +#define LCDC_EXTRACT_OP_SIZE(op_identifier) ((op_identifier&0xFFFF)) +/* extract size from command identifier */ +#define LCDC_EXTRACT_OP_CMD(op_identifier) (((op_identifier>>16)&0xFFFF)) +/* extract command id from command identifier */ + +/* MIPI standard efinitions */ +#define LCDC_ADDRESS_MODE_ORDER_BOTTOM_TO_TOP 0x80 +#define LCDC_ADDRESS_MODE_ORDER_RIGHT_TO_LEFT 0x40 +#define LCDC_ADDRESS_MODE_ORDER_REVERSE 0x20 +#define LCDC_ADDRESS_MODE_ORDER_REFRESH_BOTTOM_TO_TOP 0x10 +#define LCDC_ADDRESS_MODE_ORDER_BGER_RGB 0x08 +#define LCDC_ADDRESS_MODE_ORDER_REFERESH_RIGHT_TO_LEFT 0x04 +#define LCDC_ADDRESS_MODE_FLIP_HORIZONTAL 0x02 +#define LCDC_ADDRESS_MODE_FLIP_VERTICAL 0x01 + +#define LCDC_PIXEL_FORMAT_3_BITS_PER_PIXEL 0x1 +#define LCDC_PIXEL_FORMAT_8_BITS_PER_PIXEL 0x2 +#define LCDC_PIXEL_FORMAT_12_BITS_PER_PIXEL 0x3 +#define LCDC_PIXEL_FORMAT_16_BITS_PER_PIXEL 0x5 +#define LCDC_PIXEL_FORMAT_18_BITS_PER_PIXEL 0x6 +#define LCDC_PIXEL_FORMAT_24_BITS_PER_PIXEL 0x7 + +#define LCDC_CREATE_PIXEL_FORMAT(dpi_format, dbi_format) \ + (dpi_format | (dpi_format<<4)) + +#define POWER_MODE_IDLE_ON 0x40 +#define POWER_MODE_PARTIAL_ON 0x20 +#define POWER_MODE_SLEEP_ON 0x10 +#define POWER_MODE_NORMAL_ON 0x08 +#define POWER_MODE_DISPLAY_ON 0x04 + +#define LCDC_DISPLAY_MODE_SCROLLING_ON 0x80 +#define LCDC_DISPLAY_MODE_INVERSION_ON 0x20 +#define LCDC_DISPLAY_MODE_GAMMA_MASK 0x07 + +/** + * LDCc MIPI Type B supported commands + */ +#define OP_ENTER_IDLE_MODE 0x39 +#define OP_ENTER_INVERT_MODE 0x21 +#define OP_ENTER_NORMAL_MODE 0x13 +#define OP_ENTER_PARTIAL_MODE 0x12 +#define OP_ENTER_SLEEP_MODE 0x10 +#define OP_EXIT_INVERT_MODE 0x20 +#define OP_EXIT_SLEEP_MODE 0x11 +#define OP_EXIT_IDLE_MODE 0x38 +#define OP_GET_ADDRESS_MODE 0x0B /* size 1 */ +#define OP_GET_BLUE_CHANNEL 0x08 /* size 1 */ +#define OP_GET_DIAGNOSTIC 0x0F /* size 2 */ +#define OP_GET_DISPLAY_MODE 0x0D /* size 1 */ +#define OP_GET_GREEN_CHANNEL 0x07 /* size 1 */ +#define OP_GET_PIXEL_FORMAT 0x0C /* size 1 */ +#define OP_GET_POWER_MODE 0x0A /* size 1 */ +#define OP_GET_RED_CHANNEL 0x06 /* size 1 */ +#define OP_GET_SCANLINE 0x45 /* size 1 */ +#define OP_GET_SIGNAL_MODE 0x0E /* size 1 */ +#define OP_NOP 0x00 +#define OP_READ_DDB_CONTINUE 0xA8 /* size not fixed */ +#define OP_READ_DDB_START 0xA1 /* size not fixed */ +#define OP_READ_MEMORY_CONTINUE 0x3E /* size not fixed */ +#define OP_READ_MEMORY_START 0x2E /* size not fixed */ +#define OP_SET_ADDRESS_MODE 0x36 /* size 1 */ +#define OP_SET_COLUMN_ADDRESS 0x2A /* size 4 */ +#define OP_SET_DISPLAY_OFF 0x28 +#define OP_SET_DISPLAY_ON 0x29 +#define OP_SET_GAMMA_CURVE 0x26 /* size 1 */ +#define OP_SET_PAGE_ADDRESS 0x2B /* size 4 */ +#define OP_SET_PARTIAL_COLUMNS 0x31 /* size 4 */ +#define OP_SET_PARTIAL_ROWS 0x30 /* size 4 */ +#define OP_SET_PIXEL_FORMAT 0x3A /* size 1 */ +#define OP_SOFT_RESET 0x01 +#define OP_WRITE_MEMORY_CONTINUE 0x3C /* size not fixed */ +#define OP_WRITE_MEMORY_START 0x2C /* size not fixed */ +#endif diff --git a/tinydrm/qpic_panel_ili9341.c b/tinydrm/qpic_panel_ili9341.c new file mode 100644 index 000000000000..757dfd2a7371 --- /dev/null +++ b/tinydrm/qpic_panel_ili9341.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2015, 2021, The Linux Foundation. All rights reserved. + */ + +#include "qpic_display.h" +#include "qpic_display_panel.h" + +/** + * ILI9341 commands + */ +#define OP_ILI9341_INTERFACE_CONTROL 0xf6 +#define OP_ILI9341_TEARING_EFFECT_LINE_ON 0x35 + +static int ili9341_on(struct qpic_display_data *qpic_display) +{ + u8 param[4]; + + qpic_display->qpic_transfer(qpic_display, OP_SOFT_RESET, NULL, 0); + /* wait for 120 ms after reset as panel spec suggests */ + msleep(120); + qpic_display->qpic_transfer(qpic_display, OP_SET_DISPLAY_OFF, NULL, 0); + /* wait for 20 ms after disply off */ + msleep(20); + + /* set memory access control */ + param[0] = 0x48; + qpic_display->qpic_transfer(qpic_display, OP_SET_ADDRESS_MODE, param, 1); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + param[0] = 0x66; + qpic_display->qpic_transfer(qpic_display, OP_SET_PIXEL_FORMAT, param, 1); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + /* set interface */ + param[0] = 1; + param[1] = 0; + param[2] = 0; + qpic_display->qpic_transfer(qpic_display, OP_ILI9341_INTERFACE_CONTROL, param, 3); + /* wait for 20 ms after command sent */ + msleep(20); + + /* exit sleep mode */ + qpic_display->qpic_transfer(qpic_display, OP_EXIT_SLEEP_MODE, NULL, 0); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + /* normal mode */ + qpic_display->qpic_transfer(qpic_display, OP_ENTER_NORMAL_MODE, NULL, 0); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + /* display on */ + qpic_display->qpic_transfer(qpic_display, OP_SET_DISPLAY_ON, NULL, 0); + /* wait for 20 ms after command sent as panel spec suggests */ + msleep(20); + + param[0] = 0; + qpic_display->qpic_transfer(qpic_display, OP_ILI9341_TEARING_EFFECT_LINE_ON, param, 1); + + return 0; +} + +static void ili9341_off(struct qpic_display_data *qpic_display) +{ + qpic_display->qpic_transfer(qpic_display, OP_SET_DISPLAY_OFF, NULL, 0); + /* wait for 20 ms after display off */ + msleep(20); +} + +static struct qpic_panel_config ili_qvga_panel = { + .xres = 320, + .yres = 480, + .bpp = 16, +}; + +int get_ili_qvga_panel_config(struct qpic_display_data *qpic_display) +{ + qpic_display->panel_config = &ili_qvga_panel; + qpic_display->panel_on = ili9341_on; + qpic_display->panel_off = ili9341_off; +}