mirror of
https://github.com/torvalds/linux.git
synced 2025-01-01 15:51:46 +00:00
Merge tag 'drm-msm-next-2021-04-11' of https://gitlab.freedesktop.org/drm/msm into drm-next
msm-next from Rob: * Big DSI phy/pll cleanup. Includes some clk patches, acked by maintainer * Initial support for sc7280 * compatibles fixes for sm8150/sm8250 * cleanups for all dpu gens to use same bandwidth scaling paths (\o/) * various shrinker path lock contention optimizations * unpin/swap support for GEM objects (disabled by default, enable with msm.enable_eviction=1 .. due to various combinations of iommu drivers with older gens I want to get more testing on hw I don't have in front of me before enabling by default) * The usual assortment of misc fixes and cleanups Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> From: Rob Clark <robdclark@gmail.com> Link: https://patchwork.freedesktop.org/patch/msgid/CAF6AEGvL=4aw15qoY8fbKG9FCgnx8Y-dCtf7xiFwTQSHopwSQg@mail.gmail.com
This commit is contained in:
commit
af8352f1ff
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/io.h>
|
||||
@ -206,6 +207,40 @@ struct clk_hw *__clk_hw_register_mux(struct device *dev, struct device_node *np,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__clk_hw_register_mux);
|
||||
|
||||
static void devm_clk_hw_release_mux(struct device *dev, void *res)
|
||||
{
|
||||
clk_hw_unregister_mux(*(struct clk_hw **)res);
|
||||
}
|
||||
|
||||
struct clk_hw *__devm_clk_hw_register_mux(struct device *dev, struct device_node *np,
|
||||
const char *name, u8 num_parents,
|
||||
const char * const *parent_names,
|
||||
const struct clk_hw **parent_hws,
|
||||
const struct clk_parent_data *parent_data,
|
||||
unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
|
||||
u8 clk_mux_flags, u32 *table, spinlock_t *lock)
|
||||
{
|
||||
struct clk_hw **ptr, *hw;
|
||||
|
||||
ptr = devres_alloc(devm_clk_hw_release_mux, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
hw = __clk_hw_register_mux(dev, np, name, num_parents, parent_names, parent_hws,
|
||||
parent_data, flags, reg, shift, mask,
|
||||
clk_mux_flags, table, lock);
|
||||
|
||||
if (!IS_ERR(hw)) {
|
||||
*ptr = hw;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return hw;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__devm_clk_hw_register_mux);
|
||||
|
||||
struct clk *clk_register_mux_table(struct device *dev, const char *name,
|
||||
const char * const *parent_names, u8 num_parents,
|
||||
unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
|
||||
|
@ -20,6 +20,7 @@ config DRM_MSM
|
||||
select SND_SOC_HDMI_CODEC if SND_SOC
|
||||
select SYNC_FILE
|
||||
select PM_OPP
|
||||
select NVMEM
|
||||
help
|
||||
DRM/KMS driver for MSM/snapdragon.
|
||||
|
||||
@ -76,14 +77,6 @@ config DRM_MSM_DSI
|
||||
Choose this option if you have a need for MIPI DSI connector
|
||||
support.
|
||||
|
||||
config DRM_MSM_DSI_PLL
|
||||
bool "Enable DSI PLL driver in MSM DRM"
|
||||
depends on DRM_MSM_DSI && COMMON_CLK
|
||||
default y
|
||||
help
|
||||
Choose this option to enable DSI PLL driver which provides DSI
|
||||
source clocks under common clock framework.
|
||||
|
||||
config DRM_MSM_DSI_28NM_PHY
|
||||
bool "Enable DSI 28nm PHY driver in MSM DRM"
|
||||
depends on DRM_MSM_DSI
|
||||
|
@ -136,13 +136,4 @@ msm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/phy/dsi_phy_14nm.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI_10NM_PHY) += dsi/phy/dsi_phy_10nm.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI_7NM_PHY) += dsi/phy/dsi_phy_7nm.o
|
||||
|
||||
ifeq ($(CONFIG_DRM_MSM_DSI_PLL),y)
|
||||
msm-y += dsi/pll/dsi_pll.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/pll/dsi_pll_28nm.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/pll/dsi_pll_28nm_8960.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/pll/dsi_pll_14nm.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI_10NM_PHY) += dsi/pll/dsi_pll_10nm.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI_7NM_PHY) += dsi/pll/dsi_pll_7nm.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_DRM_MSM) += msm.o
|
||||
|
@ -246,7 +246,7 @@ static int a6xx_gmu_hfi_start(struct a6xx_gmu *gmu)
|
||||
}
|
||||
|
||||
struct a6xx_gmu_oob_bits {
|
||||
int set, ack, set_new, ack_new;
|
||||
int set, ack, set_new, ack_new, clear, clear_new;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
@ -260,6 +260,8 @@ static const struct a6xx_gmu_oob_bits a6xx_gmu_oob_bits[] = {
|
||||
.ack = 24,
|
||||
.set_new = 30,
|
||||
.ack_new = 31,
|
||||
.clear = 24,
|
||||
.clear_new = 31,
|
||||
},
|
||||
|
||||
[GMU_OOB_PERFCOUNTER_SET] = {
|
||||
@ -268,18 +270,22 @@ static const struct a6xx_gmu_oob_bits a6xx_gmu_oob_bits[] = {
|
||||
.ack = 25,
|
||||
.set_new = 28,
|
||||
.ack_new = 30,
|
||||
.clear = 25,
|
||||
.clear_new = 29,
|
||||
},
|
||||
|
||||
[GMU_OOB_BOOT_SLUMBER] = {
|
||||
.name = "BOOT_SLUMBER",
|
||||
.set = 22,
|
||||
.ack = 30,
|
||||
.clear = 30,
|
||||
},
|
||||
|
||||
[GMU_OOB_DCVS_SET] = {
|
||||
.name = "GPU_DCVS",
|
||||
.set = 23,
|
||||
.ack = 31,
|
||||
.clear = 31,
|
||||
},
|
||||
};
|
||||
|
||||
@ -335,9 +341,9 @@ void a6xx_gmu_clear_oob(struct a6xx_gmu *gmu, enum a6xx_gmu_oob_state state)
|
||||
return;
|
||||
|
||||
if (gmu->legacy)
|
||||
bit = a6xx_gmu_oob_bits[state].ack;
|
||||
bit = a6xx_gmu_oob_bits[state].clear;
|
||||
else
|
||||
bit = a6xx_gmu_oob_bits[state].ack_new;
|
||||
bit = a6xx_gmu_oob_bits[state].clear_new;
|
||||
|
||||
gmu_write(gmu, REG_A6XX_GMU_HOST2GMU_INTR_SET, 1 << bit);
|
||||
}
|
||||
|
@ -273,6 +273,9 @@ int adreno_get_param(struct msm_gpu *gpu, uint32_t param, uint64_t *value)
|
||||
case MSM_PARAM_FAULTS:
|
||||
*value = gpu->global_faults;
|
||||
return 0;
|
||||
case MSM_PARAM_SUSPENDS:
|
||||
*value = gpu->suspend_count;
|
||||
return 0;
|
||||
default:
|
||||
DBG("%s: invalid param: %u", gpu->name, param);
|
||||
return -EINVAL;
|
||||
|
@ -58,8 +58,8 @@ int dpu_core_irq_idx_lookup(struct dpu_kms *dpu_kms,
|
||||
if (!dpu_kms->hw_intr || !dpu_kms->hw_intr->ops.irq_idx_lookup)
|
||||
return -EINVAL;
|
||||
|
||||
return dpu_kms->hw_intr->ops.irq_idx_lookup(intr_type,
|
||||
instance_idx);
|
||||
return dpu_kms->hw_intr->ops.irq_idx_lookup(dpu_kms->hw_intr,
|
||||
intr_type, instance_idx);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -380,7 +380,6 @@ int dpu_core_perf_crtc_update(struct drm_crtc *crtc,
|
||||
} else {
|
||||
DPU_DEBUG("crtc=%d disable\n", crtc->base.id);
|
||||
memset(old, 0, sizeof(*old));
|
||||
memset(new, 0, sizeof(*new));
|
||||
update_bus = true;
|
||||
update_clk = true;
|
||||
}
|
||||
|
@ -66,6 +66,83 @@ static void dpu_crtc_destroy(struct drm_crtc *crtc)
|
||||
kfree(dpu_crtc);
|
||||
}
|
||||
|
||||
static struct drm_encoder *get_encoder_from_crtc(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
drm_for_each_encoder(encoder, dev)
|
||||
if (encoder->crtc == crtc)
|
||||
return encoder;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static u32 dpu_crtc_get_vblank_counter(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
encoder = get_encoder_from_crtc(crtc);
|
||||
if (!encoder) {
|
||||
DRM_ERROR("no encoder found for crtc %d\n", crtc->index);
|
||||
return false;
|
||||
}
|
||||
|
||||
return dpu_encoder_get_frame_count(encoder);
|
||||
}
|
||||
|
||||
static bool dpu_crtc_get_scanout_position(struct drm_crtc *crtc,
|
||||
bool in_vblank_irq,
|
||||
int *vpos, int *hpos,
|
||||
ktime_t *stime, ktime_t *etime,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
unsigned int pipe = crtc->index;
|
||||
struct drm_encoder *encoder;
|
||||
int line, vsw, vbp, vactive_start, vactive_end, vfp_end;
|
||||
|
||||
encoder = get_encoder_from_crtc(crtc);
|
||||
if (!encoder) {
|
||||
DRM_ERROR("no encoder found for crtc %d\n", pipe);
|
||||
return false;
|
||||
}
|
||||
|
||||
vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
|
||||
vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
|
||||
|
||||
/*
|
||||
* the line counter is 1 at the start of the VSYNC pulse and VTOTAL at
|
||||
* the end of VFP. Translate the porch values relative to the line
|
||||
* counter positions.
|
||||
*/
|
||||
|
||||
vactive_start = vsw + vbp + 1;
|
||||
vactive_end = vactive_start + mode->crtc_vdisplay;
|
||||
|
||||
/* last scan line before VSYNC */
|
||||
vfp_end = mode->crtc_vtotal;
|
||||
|
||||
if (stime)
|
||||
*stime = ktime_get();
|
||||
|
||||
line = dpu_encoder_get_linecount(encoder);
|
||||
|
||||
if (line < vactive_start)
|
||||
line -= vactive_start;
|
||||
else if (line > vactive_end)
|
||||
line = line - vfp_end - vactive_start;
|
||||
else
|
||||
line -= vactive_start;
|
||||
|
||||
*vpos = line;
|
||||
*hpos = 0;
|
||||
|
||||
if (etime)
|
||||
*etime = ktime_get();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _dpu_crtc_setup_blend_cfg(struct dpu_crtc_mixer *mixer,
|
||||
struct dpu_plane_state *pstate, struct dpu_format *format)
|
||||
{
|
||||
@ -130,7 +207,9 @@ static void _dpu_crtc_blend_setup_mixer(struct drm_crtc *crtc,
|
||||
uint32_t stage_idx, lm_idx;
|
||||
int zpos_cnt[DPU_STAGE_MAX + 1] = { 0 };
|
||||
bool bg_alpha_enable = false;
|
||||
DECLARE_BITMAP(fetch_active, SSPP_MAX);
|
||||
|
||||
memset(fetch_active, 0, sizeof(fetch_active));
|
||||
drm_atomic_crtc_for_each_plane(plane, crtc) {
|
||||
state = plane->state;
|
||||
if (!state)
|
||||
@ -140,7 +219,7 @@ static void _dpu_crtc_blend_setup_mixer(struct drm_crtc *crtc,
|
||||
fb = state->fb;
|
||||
|
||||
dpu_plane_get_ctl_flush(plane, ctl, &flush_mask);
|
||||
|
||||
set_bit(dpu_plane_pipe(plane), fetch_active);
|
||||
DPU_DEBUG("crtc %d stage:%d - plane %d sspp %d fb %d\n",
|
||||
crtc->base.id,
|
||||
pstate->stage,
|
||||
@ -180,6 +259,9 @@ static void _dpu_crtc_blend_setup_mixer(struct drm_crtc *crtc,
|
||||
}
|
||||
}
|
||||
|
||||
if (ctl->ops.set_active_pipes)
|
||||
ctl->ops.set_active_pipes(ctl, fetch_active);
|
||||
|
||||
_dpu_crtc_program_lm_output_roi(crtc);
|
||||
}
|
||||
|
||||
@ -839,6 +921,7 @@ static int dpu_crtc_atomic_check(struct drm_crtc *crtc,
|
||||
DPU_DEBUG("crtc%d -> enable %d, active %d, skip atomic_check\n",
|
||||
crtc->base.id, crtc_state->enable,
|
||||
crtc_state->active);
|
||||
memset(&cstate->new_perf, 0, sizeof(cstate->new_perf));
|
||||
goto end;
|
||||
}
|
||||
|
||||
@ -1247,6 +1330,8 @@ static const struct drm_crtc_funcs dpu_crtc_funcs = {
|
||||
.early_unregister = dpu_crtc_early_unregister,
|
||||
.enable_vblank = msm_crtc_enable_vblank,
|
||||
.disable_vblank = msm_crtc_disable_vblank,
|
||||
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
|
||||
.get_vblank_counter = dpu_crtc_get_vblank_counter,
|
||||
};
|
||||
|
||||
static const struct drm_crtc_helper_funcs dpu_crtc_helper_funcs = {
|
||||
@ -1255,6 +1340,7 @@ static const struct drm_crtc_helper_funcs dpu_crtc_helper_funcs = {
|
||||
.atomic_check = dpu_crtc_atomic_check,
|
||||
.atomic_begin = dpu_crtc_atomic_begin,
|
||||
.atomic_flush = dpu_crtc_atomic_flush,
|
||||
.get_scanout_position = dpu_crtc_get_scanout_position,
|
||||
};
|
||||
|
||||
/* initialize crtc */
|
||||
|
@ -426,6 +426,36 @@ int dpu_encoder_helper_unregister_irq(struct dpu_encoder_phys *phys_enc,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dpu_encoder_get_frame_count(struct drm_encoder *drm_enc)
|
||||
{
|
||||
struct dpu_encoder_virt *dpu_enc;
|
||||
struct dpu_encoder_phys *phys;
|
||||
int framecount = 0;
|
||||
|
||||
dpu_enc = to_dpu_encoder_virt(drm_enc);
|
||||
phys = dpu_enc ? dpu_enc->cur_master : NULL;
|
||||
|
||||
if (phys && phys->ops.get_frame_count)
|
||||
framecount = phys->ops.get_frame_count(phys);
|
||||
|
||||
return framecount;
|
||||
}
|
||||
|
||||
int dpu_encoder_get_linecount(struct drm_encoder *drm_enc)
|
||||
{
|
||||
struct dpu_encoder_virt *dpu_enc;
|
||||
struct dpu_encoder_phys *phys;
|
||||
int linecount = 0;
|
||||
|
||||
dpu_enc = to_dpu_encoder_virt(drm_enc);
|
||||
phys = dpu_enc ? dpu_enc->cur_master : NULL;
|
||||
|
||||
if (phys && phys->ops.get_line_count)
|
||||
linecount = phys->ops.get_line_count(phys);
|
||||
|
||||
return linecount;
|
||||
}
|
||||
|
||||
void dpu_encoder_get_hw_resources(struct drm_encoder *drm_enc,
|
||||
struct dpu_encoder_hw_resources *hw_res)
|
||||
{
|
||||
|
@ -156,5 +156,16 @@ void dpu_encoder_prepare_commit(struct drm_encoder *drm_enc);
|
||||
*/
|
||||
void dpu_encoder_set_idle_timeout(struct drm_encoder *drm_enc,
|
||||
u32 idle_timeout);
|
||||
/**
|
||||
* dpu_encoder_get_linecount - get interface line count for the encoder.
|
||||
* @drm_enc: Pointer to previously created drm encoder structure
|
||||
*/
|
||||
int dpu_encoder_get_linecount(struct drm_encoder *drm_enc);
|
||||
|
||||
/**
|
||||
* dpu_encoder_get_frame_count - get interface frame count for the encoder.
|
||||
* @drm_enc: Pointer to previously created drm encoder structure
|
||||
*/
|
||||
int dpu_encoder_get_frame_count(struct drm_encoder *drm_enc);
|
||||
|
||||
#endif /* __DPU_ENCODER_H__ */
|
||||
|
@ -143,6 +143,7 @@ struct dpu_encoder_phys_ops {
|
||||
void (*prepare_idle_pc)(struct dpu_encoder_phys *phys_enc);
|
||||
void (*restore)(struct dpu_encoder_phys *phys);
|
||||
int (*get_line_count)(struct dpu_encoder_phys *phys);
|
||||
int (*get_frame_count)(struct dpu_encoder_phys *phys);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -658,6 +658,31 @@ static int dpu_encoder_phys_vid_get_line_count(
|
||||
return phys_enc->hw_intf->ops.get_line_count(phys_enc->hw_intf);
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_vid_get_frame_count(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct intf_status s = {0};
|
||||
u32 fetch_start = 0;
|
||||
struct drm_display_mode mode = phys_enc->cached_mode;
|
||||
|
||||
if (!dpu_encoder_phys_vid_is_master(phys_enc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!phys_enc->hw_intf || !phys_enc->hw_intf->ops.get_status)
|
||||
return -EINVAL;
|
||||
|
||||
phys_enc->hw_intf->ops.get_status(phys_enc->hw_intf, &s);
|
||||
|
||||
if (s.is_prog_fetch_en && s.is_en) {
|
||||
fetch_start = mode.vtotal - (mode.vsync_start - mode.vdisplay);
|
||||
if ((s.line_count > fetch_start) &&
|
||||
(s.line_count <= mode.vtotal))
|
||||
return s.frame_count + 1;
|
||||
}
|
||||
|
||||
return s.frame_count;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_init_ops(struct dpu_encoder_phys_ops *ops)
|
||||
{
|
||||
ops->is_master = dpu_encoder_phys_vid_is_master;
|
||||
@ -676,6 +701,7 @@ static void dpu_encoder_phys_vid_init_ops(struct dpu_encoder_phys_ops *ops)
|
||||
ops->handle_post_kickoff = dpu_encoder_phys_vid_handle_post_kickoff;
|
||||
ops->needs_single_flush = dpu_encoder_phys_vid_needs_single_flush;
|
||||
ops->get_line_count = dpu_encoder_phys_vid_get_line_count;
|
||||
ops->get_frame_count = dpu_encoder_phys_vid_get_frame_count;
|
||||
}
|
||||
|
||||
struct dpu_encoder_phys *dpu_encoder_phys_vid_init(
|
||||
|
@ -22,7 +22,7 @@
|
||||
(VIG_MASK | BIT(DPU_SSPP_QOS_8LVL) | BIT(DPU_SSPP_SCALER_QSEED4))
|
||||
|
||||
#define VIG_SM8250_MASK \
|
||||
(VIG_MASK | BIT(DPU_SSPP_SCALER_QSEED3LITE))
|
||||
(VIG_MASK | BIT(DPU_SSPP_QOS_8LVL) | BIT(DPU_SSPP_SCALER_QSEED3LITE))
|
||||
|
||||
#define DMA_SDM845_MASK \
|
||||
(BIT(DPU_SSPP_SRC) | BIT(DPU_SSPP_QOS) | BIT(DPU_SSPP_QOS_8LVL) |\
|
||||
@ -43,6 +43,9 @@
|
||||
#define PINGPONG_SDM845_SPLIT_MASK \
|
||||
(PINGPONG_SDM845_MASK | BIT(DPU_PINGPONG_TE2))
|
||||
|
||||
#define CTL_SC7280_MASK \
|
||||
(BIT(DPU_CTL_ACTIVE_CFG) | BIT(DPU_CTL_FETCH_ACTIVE))
|
||||
|
||||
#define MERGE_3D_SM8150_MASK (0)
|
||||
|
||||
#define DSPP_SC7180_MASK BIT(DPU_DSPP_PCC)
|
||||
@ -51,6 +54,15 @@
|
||||
|
||||
#define INTF_SC7180_MASK BIT(DPU_INTF_INPUT_CTRL) | BIT(DPU_INTF_TE)
|
||||
|
||||
#define INTF_SC7280_MASK INTF_SC7180_MASK | BIT(DPU_DATA_HCTL_EN)
|
||||
|
||||
#define INTR_SC7180_MASK \
|
||||
(BIT(DPU_IRQ_TYPE_PING_PONG_RD_PTR) |\
|
||||
BIT(DPU_IRQ_TYPE_PING_PONG_WR_PTR) |\
|
||||
BIT(DPU_IRQ_TYPE_PING_PONG_AUTO_REF) |\
|
||||
BIT(DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK) |\
|
||||
BIT(DPU_IRQ_TYPE_PING_PONG_TE_CHECK))
|
||||
|
||||
#define DEFAULT_PIXEL_RAM_SIZE (50 * 1024)
|
||||
#define DEFAULT_DPU_LINE_WIDTH 2048
|
||||
#define DEFAULT_DPU_OUTPUT_LINE_WIDTH 2560
|
||||
@ -199,6 +211,18 @@ static const struct dpu_caps sm8250_dpu_caps = {
|
||||
.pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE,
|
||||
};
|
||||
|
||||
static const struct dpu_caps sc7280_dpu_caps = {
|
||||
.max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH,
|
||||
.max_mixer_blendstages = 0x7,
|
||||
.qseed_type = DPU_SSPP_SCALER_QSEED4,
|
||||
.smart_dma_rev = DPU_SSPP_SMART_DMA_V2,
|
||||
.ubwc_version = DPU_HW_UBWC_VER_30,
|
||||
.has_dim_layer = true,
|
||||
.has_idle_pc = true,
|
||||
.max_linewidth = 2400,
|
||||
.pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE,
|
||||
};
|
||||
|
||||
static const struct dpu_mdp_cfg sdm845_mdp[] = {
|
||||
{
|
||||
.name = "top_0", .id = MDP_TOP,
|
||||
@ -268,6 +292,22 @@ static const struct dpu_mdp_cfg sm8250_mdp[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static const struct dpu_mdp_cfg sc7280_mdp[] = {
|
||||
{
|
||||
.name = "top_0", .id = MDP_TOP,
|
||||
.base = 0x0, .len = 0x2014,
|
||||
.highest_bank_bit = 0x1,
|
||||
.clk_ctrls[DPU_CLK_CTRL_VIG0] = {
|
||||
.reg_off = 0x2AC, .bit_off = 0},
|
||||
.clk_ctrls[DPU_CLK_CTRL_DMA0] = {
|
||||
.reg_off = 0x2AC, .bit_off = 8},
|
||||
.clk_ctrls[DPU_CLK_CTRL_CURSOR0] = {
|
||||
.reg_off = 0x2B4, .bit_off = 8},
|
||||
.clk_ctrls[DPU_CLK_CTRL_CURSOR1] = {
|
||||
.reg_off = 0x2C4, .bit_off = 8},
|
||||
},
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* CTL sub blocks config
|
||||
*************************************************************/
|
||||
@ -350,6 +390,29 @@ static const struct dpu_ctl_cfg sm8150_ctl[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static const struct dpu_ctl_cfg sc7280_ctl[] = {
|
||||
{
|
||||
.name = "ctl_0", .id = CTL_0,
|
||||
.base = 0x15000, .len = 0x1E8,
|
||||
.features = CTL_SC7280_MASK
|
||||
},
|
||||
{
|
||||
.name = "ctl_1", .id = CTL_1,
|
||||
.base = 0x16000, .len = 0x1E8,
|
||||
.features = CTL_SC7280_MASK
|
||||
},
|
||||
{
|
||||
.name = "ctl_2", .id = CTL_2,
|
||||
.base = 0x17000, .len = 0x1E8,
|
||||
.features = CTL_SC7280_MASK
|
||||
},
|
||||
{
|
||||
.name = "ctl_3", .id = CTL_3,
|
||||
.base = 0x18000, .len = 0x1E8,
|
||||
.features = CTL_SC7280_MASK
|
||||
},
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* SSPP sub blocks config
|
||||
*************************************************************/
|
||||
@ -475,6 +538,17 @@ static const struct dpu_sspp_cfg sm8250_sspp[] = {
|
||||
sdm845_dma_sblk_3, 13, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR1),
|
||||
};
|
||||
|
||||
static const struct dpu_sspp_cfg sc7280_sspp[] = {
|
||||
SSPP_BLK("sspp_0", SSPP_VIG0, 0x4000, VIG_SC7180_MASK,
|
||||
sc7180_vig_sblk_0, 0, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG0),
|
||||
SSPP_BLK("sspp_8", SSPP_DMA0, 0x24000, DMA_SDM845_MASK,
|
||||
sdm845_dma_sblk_0, 1, SSPP_TYPE_DMA, DPU_CLK_CTRL_DMA0),
|
||||
SSPP_BLK("sspp_9", SSPP_DMA1, 0x26000, DMA_CURSOR_SDM845_MASK,
|
||||
sdm845_dma_sblk_1, 5, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR0),
|
||||
SSPP_BLK("sspp_10", SSPP_DMA2, 0x28000, DMA_CURSOR_SDM845_MASK,
|
||||
sdm845_dma_sblk_2, 9, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR1),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* MIXER sub blocks config
|
||||
*************************************************************/
|
||||
@ -550,6 +624,15 @@ static const struct dpu_lm_cfg sm8150_lm[] = {
|
||||
&sdm845_lm_sblk, PINGPONG_5, LM_4, 0),
|
||||
};
|
||||
|
||||
static const struct dpu_lm_cfg sc7280_lm[] = {
|
||||
LM_BLK("lm_0", LM_0, 0x44000, MIXER_SC7180_MASK,
|
||||
&sc7180_lm_sblk, PINGPONG_0, 0, 0),
|
||||
LM_BLK("lm_2", LM_2, 0x46000, MIXER_SC7180_MASK,
|
||||
&sc7180_lm_sblk, PINGPONG_2, LM_3, 0),
|
||||
LM_BLK("lm_3", LM_3, 0x47000, MIXER_SC7180_MASK,
|
||||
&sc7180_lm_sblk, PINGPONG_3, LM_2, 0),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* DSPP sub blocks config
|
||||
*************************************************************/
|
||||
@ -602,42 +685,47 @@ static const struct dpu_pingpong_sub_blks sdm845_pp_sblk = {
|
||||
.len = 0x20, .version = 0x10000},
|
||||
};
|
||||
|
||||
#define PP_BLK_TE(_name, _id, _base, _merge_3d) \
|
||||
static const struct dpu_pingpong_sub_blks sc7280_pp_sblk = {
|
||||
.dither = {.id = DPU_PINGPONG_DITHER, .base = 0xe0,
|
||||
.len = 0x20, .version = 0x20000},
|
||||
};
|
||||
|
||||
#define PP_BLK_TE(_name, _id, _base, _merge_3d, _sblk) \
|
||||
{\
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0xd4, \
|
||||
.features = PINGPONG_SDM845_SPLIT_MASK, \
|
||||
.merge_3d = _merge_3d, \
|
||||
.sblk = &sdm845_pp_sblk_te \
|
||||
.sblk = &_sblk \
|
||||
}
|
||||
#define PP_BLK(_name, _id, _base, _merge_3d) \
|
||||
#define PP_BLK(_name, _id, _base, _merge_3d, _sblk) \
|
||||
{\
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0xd4, \
|
||||
.features = PINGPONG_SDM845_MASK, \
|
||||
.merge_3d = _merge_3d, \
|
||||
.sblk = &sdm845_pp_sblk \
|
||||
.sblk = &_sblk \
|
||||
}
|
||||
|
||||
static const struct dpu_pingpong_cfg sdm845_pp[] = {
|
||||
PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000, 0),
|
||||
PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800, 0),
|
||||
PP_BLK("pingpong_2", PINGPONG_2, 0x71000, 0),
|
||||
PP_BLK("pingpong_3", PINGPONG_3, 0x71800, 0),
|
||||
PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000, 0, sdm845_pp_sblk_te),
|
||||
PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800, 0, sdm845_pp_sblk_te),
|
||||
PP_BLK("pingpong_2", PINGPONG_2, 0x71000, 0, sdm845_pp_sblk),
|
||||
PP_BLK("pingpong_3", PINGPONG_3, 0x71800, 0, sdm845_pp_sblk),
|
||||
};
|
||||
|
||||
static struct dpu_pingpong_cfg sc7180_pp[] = {
|
||||
PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000, 0),
|
||||
PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800, 0),
|
||||
PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000, 0, sdm845_pp_sblk_te),
|
||||
PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800, 0, sdm845_pp_sblk_te),
|
||||
};
|
||||
|
||||
static const struct dpu_pingpong_cfg sm8150_pp[] = {
|
||||
PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000, MERGE_3D_0),
|
||||
PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800, MERGE_3D_0),
|
||||
PP_BLK("pingpong_2", PINGPONG_2, 0x71000, MERGE_3D_1),
|
||||
PP_BLK("pingpong_3", PINGPONG_3, 0x71800, MERGE_3D_1),
|
||||
PP_BLK("pingpong_4", PINGPONG_4, 0x72000, MERGE_3D_2),
|
||||
PP_BLK("pingpong_5", PINGPONG_5, 0x72800, MERGE_3D_2),
|
||||
PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000, MERGE_3D_0, sdm845_pp_sblk_te),
|
||||
PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800, MERGE_3D_0, sdm845_pp_sblk_te),
|
||||
PP_BLK("pingpong_2", PINGPONG_2, 0x71000, MERGE_3D_1, sdm845_pp_sblk),
|
||||
PP_BLK("pingpong_3", PINGPONG_3, 0x71800, MERGE_3D_1, sdm845_pp_sblk),
|
||||
PP_BLK("pingpong_4", PINGPONG_4, 0x72000, MERGE_3D_2, sdm845_pp_sblk),
|
||||
PP_BLK("pingpong_5", PINGPONG_5, 0x72800, MERGE_3D_2, sdm845_pp_sblk),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
@ -657,6 +745,12 @@ static const struct dpu_merge_3d_cfg sm8150_merge_3d[] = {
|
||||
MERGE_3D_BLK("merge_3d_2", MERGE_3D_2, 0x83200),
|
||||
};
|
||||
|
||||
static const struct dpu_pingpong_cfg sc7280_pp[] = {
|
||||
PP_BLK("pingpong_0", PINGPONG_0, 0x59000, 0, sc7280_pp_sblk),
|
||||
PP_BLK("pingpong_1", PINGPONG_1, 0x6a000, 0, sc7280_pp_sblk),
|
||||
PP_BLK("pingpong_2", PINGPONG_2, 0x6b000, 0, sc7280_pp_sblk),
|
||||
PP_BLK("pingpong_3", PINGPONG_3, 0x6c000, 0, sc7280_pp_sblk),
|
||||
};
|
||||
/*************************************************************
|
||||
* INTF sub blocks config
|
||||
*************************************************************/
|
||||
@ -689,6 +783,12 @@ static const struct dpu_intf_cfg sm8150_intf[] = {
|
||||
INTF_BLK("intf_3", INTF_3, 0x6B800, INTF_DP, 1, 24, INTF_SC7180_MASK),
|
||||
};
|
||||
|
||||
static const struct dpu_intf_cfg sc7280_intf[] = {
|
||||
INTF_BLK("intf_0", INTF_0, 0x34000, INTF_DP, 0, 24, INTF_SC7280_MASK),
|
||||
INTF_BLK("intf_1", INTF_1, 0x35000, INTF_DSI, 0, 24, INTF_SC7280_MASK),
|
||||
INTF_BLK("intf_5", INTF_5, 0x39000, INTF_EDP, 0, 24, INTF_SC7280_MASK),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* VBIF sub blocks config
|
||||
*************************************************************/
|
||||
@ -817,6 +917,8 @@ static const struct dpu_perf_cfg sdm845_perf_data = {
|
||||
{.rd_enable = 1, .wr_enable = 1},
|
||||
{.rd_enable = 1, .wr_enable = 0}
|
||||
},
|
||||
.clk_inefficiency_factor = 105,
|
||||
.bw_inefficiency_factor = 120,
|
||||
};
|
||||
|
||||
static const struct dpu_perf_cfg sc7180_perf_data = {
|
||||
@ -852,6 +954,7 @@ static const struct dpu_perf_cfg sm8150_perf_data = {
|
||||
.min_core_ib = 2400000,
|
||||
.min_llcc_ib = 800000,
|
||||
.min_dram_ib = 800000,
|
||||
.min_prefill_lines = 24,
|
||||
.danger_lut_tbl = {0xf, 0xffff, 0x0},
|
||||
.qos_lut_tbl = {
|
||||
{.nentry = ARRAY_SIZE(sm8150_qos_linear),
|
||||
@ -869,6 +972,8 @@ static const struct dpu_perf_cfg sm8150_perf_data = {
|
||||
{.rd_enable = 1, .wr_enable = 1},
|
||||
{.rd_enable = 1, .wr_enable = 0}
|
||||
},
|
||||
.clk_inefficiency_factor = 105,
|
||||
.bw_inefficiency_factor = 120,
|
||||
};
|
||||
|
||||
static const struct dpu_perf_cfg sm8250_perf_data = {
|
||||
@ -877,6 +982,7 @@ static const struct dpu_perf_cfg sm8250_perf_data = {
|
||||
.min_core_ib = 4800000,
|
||||
.min_llcc_ib = 0,
|
||||
.min_dram_ib = 800000,
|
||||
.min_prefill_lines = 35,
|
||||
.danger_lut_tbl = {0xf, 0xffff, 0x0},
|
||||
.qos_lut_tbl = {
|
||||
{.nentry = ARRAY_SIZE(sc7180_qos_linear),
|
||||
@ -894,6 +1000,35 @@ static const struct dpu_perf_cfg sm8250_perf_data = {
|
||||
{.rd_enable = 1, .wr_enable = 1},
|
||||
{.rd_enable = 1, .wr_enable = 0}
|
||||
},
|
||||
.clk_inefficiency_factor = 105,
|
||||
.bw_inefficiency_factor = 120,
|
||||
};
|
||||
|
||||
static const struct dpu_perf_cfg sc7280_perf_data = {
|
||||
.max_bw_low = 4700000,
|
||||
.max_bw_high = 8800000,
|
||||
.min_core_ib = 2500000,
|
||||
.min_llcc_ib = 0,
|
||||
.min_dram_ib = 1600000,
|
||||
.min_prefill_lines = 24,
|
||||
.danger_lut_tbl = {0xffff, 0xffff, 0x0},
|
||||
.qos_lut_tbl = {
|
||||
{.nentry = ARRAY_SIZE(sc7180_qos_macrotile),
|
||||
.entries = sc7180_qos_macrotile
|
||||
},
|
||||
{.nentry = ARRAY_SIZE(sc7180_qos_macrotile),
|
||||
.entries = sc7180_qos_macrotile
|
||||
},
|
||||
{.nentry = ARRAY_SIZE(sc7180_qos_nrt),
|
||||
.entries = sc7180_qos_nrt
|
||||
},
|
||||
},
|
||||
.cdp_cfg = {
|
||||
{.rd_enable = 1, .wr_enable = 1},
|
||||
{.rd_enable = 1, .wr_enable = 0}
|
||||
},
|
||||
.clk_inefficiency_factor = 105,
|
||||
.bw_inefficiency_factor = 120,
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
@ -957,6 +1092,7 @@ static void sc7180_cfg_init(struct dpu_mdss_cfg *dpu_cfg)
|
||||
.dma_cfg = sdm845_regdma,
|
||||
.perf = sc7180_perf_data,
|
||||
.mdss_irqs = 0x3f,
|
||||
.obsolete_irq = INTR_SC7180_MASK,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1026,6 +1162,30 @@ static void sm8250_cfg_init(struct dpu_mdss_cfg *dpu_cfg)
|
||||
};
|
||||
}
|
||||
|
||||
static void sc7280_cfg_init(struct dpu_mdss_cfg *dpu_cfg)
|
||||
{
|
||||
*dpu_cfg = (struct dpu_mdss_cfg){
|
||||
.caps = &sc7280_dpu_caps,
|
||||
.mdp_count = ARRAY_SIZE(sc7280_mdp),
|
||||
.mdp = sc7280_mdp,
|
||||
.ctl_count = ARRAY_SIZE(sc7280_ctl),
|
||||
.ctl = sc7280_ctl,
|
||||
.sspp_count = ARRAY_SIZE(sc7280_sspp),
|
||||
.sspp = sc7280_sspp,
|
||||
.mixer_count = ARRAY_SIZE(sc7280_lm),
|
||||
.mixer = sc7280_lm,
|
||||
.pingpong_count = ARRAY_SIZE(sc7280_pp),
|
||||
.pingpong = sc7280_pp,
|
||||
.intf_count = ARRAY_SIZE(sc7280_intf),
|
||||
.intf = sc7280_intf,
|
||||
.vbif_count = ARRAY_SIZE(sdm845_vbif),
|
||||
.vbif = sdm845_vbif,
|
||||
.perf = sc7280_perf_data,
|
||||
.mdss_irqs = 0x1c07,
|
||||
.obsolete_irq = INTR_SC7180_MASK,
|
||||
};
|
||||
}
|
||||
|
||||
static const struct dpu_mdss_hw_cfg_handler cfg_handler[] = {
|
||||
{ .hw_rev = DPU_HW_VER_400, .cfg_init = sdm845_cfg_init},
|
||||
{ .hw_rev = DPU_HW_VER_401, .cfg_init = sdm845_cfg_init},
|
||||
@ -1033,6 +1193,7 @@ static const struct dpu_mdss_hw_cfg_handler cfg_handler[] = {
|
||||
{ .hw_rev = DPU_HW_VER_501, .cfg_init = sm8150_cfg_init},
|
||||
{ .hw_rev = DPU_HW_VER_600, .cfg_init = sm8250_cfg_init},
|
||||
{ .hw_rev = DPU_HW_VER_620, .cfg_init = sc7180_cfg_init},
|
||||
{ .hw_rev = DPU_HW_VER_720, .cfg_init = sc7280_cfg_init},
|
||||
};
|
||||
|
||||
void dpu_hw_catalog_deinit(struct dpu_mdss_cfg *dpu_cfg)
|
||||
|
@ -41,7 +41,7 @@
|
||||
#define DPU_HW_VER_501 DPU_HW_VER(5, 0, 1) /* sm8150 v2.0 */
|
||||
#define DPU_HW_VER_600 DPU_HW_VER(6, 0, 0) /* sm8250 */
|
||||
#define DPU_HW_VER_620 DPU_HW_VER(6, 2, 0) /* sc7180 v1.0 */
|
||||
|
||||
#define DPU_HW_VER_720 DPU_HW_VER(7, 2, 0) /* sc7280 */
|
||||
|
||||
#define IS_MSM8996_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_170)
|
||||
#define IS_MSM8998_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_300)
|
||||
@ -49,7 +49,7 @@
|
||||
#define IS_SDM670_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_410)
|
||||
#define IS_SDM855_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_500)
|
||||
#define IS_SC7180_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_620)
|
||||
|
||||
#define IS_SC7280_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_720)
|
||||
|
||||
#define DPU_HW_BLK_NAME_LEN 16
|
||||
|
||||
@ -185,6 +185,7 @@ enum {
|
||||
enum {
|
||||
DPU_CTL_SPLIT_DISPLAY = 0x1,
|
||||
DPU_CTL_ACTIVE_CFG,
|
||||
DPU_CTL_FETCH_ACTIVE,
|
||||
DPU_CTL_MAX
|
||||
};
|
||||
|
||||
@ -193,11 +194,14 @@ enum {
|
||||
* @DPU_INTF_INPUT_CTRL Supports the setting of pp block from which
|
||||
* pixel data arrives to this INTF
|
||||
* @DPU_INTF_TE INTF block has TE configuration support
|
||||
* @DPU_DATA_HCTL_EN Allows data to be transferred at different rate
|
||||
than video timing
|
||||
* @DPU_INTF_MAX
|
||||
*/
|
||||
enum {
|
||||
DPU_INTF_INPUT_CTRL = 0x1,
|
||||
DPU_INTF_TE,
|
||||
DPU_DATA_HCTL_EN,
|
||||
DPU_INTF_MAX
|
||||
};
|
||||
|
||||
@ -719,6 +723,7 @@ struct dpu_perf_cfg {
|
||||
* @cursor_formats Supported formats for cursor pipe
|
||||
* @vig_formats Supported formats for vig pipe
|
||||
* @mdss_irqs: Bitmap with the irqs supported by the target
|
||||
* @obsolete_irq: Irq types that are obsolete for a particular target
|
||||
*/
|
||||
struct dpu_mdss_cfg {
|
||||
u32 hwversion;
|
||||
@ -765,6 +770,7 @@ struct dpu_mdss_cfg {
|
||||
const struct dpu_format_extended *vig_formats;
|
||||
|
||||
unsigned long mdss_irqs;
|
||||
unsigned long obsolete_irq;
|
||||
};
|
||||
|
||||
struct dpu_mdss_hw_cfg_handler {
|
||||
|
@ -27,6 +27,7 @@
|
||||
#define CTL_MERGE_3D_FLUSH 0x100
|
||||
#define CTL_INTF_FLUSH 0x110
|
||||
#define CTL_INTF_MASTER 0x134
|
||||
#define CTL_FETCH_PIPE_ACTIVE 0x0FC
|
||||
|
||||
#define CTL_MIXER_BORDER_OUT BIT(24)
|
||||
#define CTL_FLUSH_MASK_CTL BIT(17)
|
||||
@ -34,6 +35,11 @@
|
||||
#define DPU_REG_RESET_TIMEOUT_US 2000
|
||||
#define MERGE_3D_IDX 23
|
||||
#define INTF_IDX 31
|
||||
#define CTL_INVALID_BIT 0xffff
|
||||
|
||||
static const u32 fetch_tbl[SSPP_MAX] = {CTL_INVALID_BIT, 16, 17, 18, 19,
|
||||
CTL_INVALID_BIT, CTL_INVALID_BIT, CTL_INVALID_BIT, CTL_INVALID_BIT, 0,
|
||||
1, 2, 3, CTL_INVALID_BIT, CTL_INVALID_BIT};
|
||||
|
||||
static const struct dpu_ctl_cfg *_ctl_offset(enum dpu_ctl ctl,
|
||||
const struct dpu_mdss_cfg *m,
|
||||
@ -344,6 +350,8 @@ static void dpu_hw_ctl_clear_all_blendstages(struct dpu_hw_ctl *ctx)
|
||||
DPU_REG_WRITE(c, CTL_LAYER_EXT2(LM_0 + i), 0);
|
||||
DPU_REG_WRITE(c, CTL_LAYER_EXT3(LM_0 + i), 0);
|
||||
}
|
||||
|
||||
DPU_REG_WRITE(c, CTL_FETCH_PIPE_ACTIVE, 0);
|
||||
}
|
||||
|
||||
static void dpu_hw_ctl_setup_blendstage(struct dpu_hw_ctl *ctx,
|
||||
@ -531,6 +539,23 @@ static void dpu_hw_ctl_intf_cfg(struct dpu_hw_ctl *ctx,
|
||||
DPU_REG_WRITE(c, CTL_TOP, intf_cfg);
|
||||
}
|
||||
|
||||
static void dpu_hw_ctl_set_fetch_pipe_active(struct dpu_hw_ctl *ctx,
|
||||
unsigned long *fetch_active)
|
||||
{
|
||||
int i;
|
||||
u32 val = 0;
|
||||
|
||||
if (fetch_active) {
|
||||
for (i = 0; i < SSPP_MAX; i++) {
|
||||
if (test_bit(i, fetch_active) &&
|
||||
fetch_tbl[i] != CTL_INVALID_BIT)
|
||||
val |= BIT(fetch_tbl[i]);
|
||||
}
|
||||
}
|
||||
|
||||
DPU_REG_WRITE(&ctx->hw, CTL_FETCH_PIPE_ACTIVE, val);
|
||||
}
|
||||
|
||||
static void _setup_ctl_ops(struct dpu_hw_ctl_ops *ops,
|
||||
unsigned long cap)
|
||||
{
|
||||
@ -560,6 +585,8 @@ static void _setup_ctl_ops(struct dpu_hw_ctl_ops *ops,
|
||||
ops->get_bitmask_sspp = dpu_hw_ctl_get_bitmask_sspp;
|
||||
ops->get_bitmask_mixer = dpu_hw_ctl_get_bitmask_mixer;
|
||||
ops->get_bitmask_dspp = dpu_hw_ctl_get_bitmask_dspp;
|
||||
if (cap & BIT(DPU_CTL_FETCH_ACTIVE))
|
||||
ops->set_active_pipes = dpu_hw_ctl_set_fetch_pipe_active;
|
||||
};
|
||||
|
||||
static struct dpu_hw_blk_ops dpu_hw_ops;
|
||||
|
@ -167,6 +167,9 @@ struct dpu_hw_ctl_ops {
|
||||
*/
|
||||
void (*setup_blendstage)(struct dpu_hw_ctl *ctx,
|
||||
enum dpu_lm lm, struct dpu_hw_stage_cfg *cfg);
|
||||
|
||||
void (*set_active_pipes)(struct dpu_hw_ctl *ctx,
|
||||
unsigned long *fetch_active);
|
||||
};
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -83,11 +83,12 @@ struct dpu_hw_intr_ops {
|
||||
/**
|
||||
* irq_idx_lookup - Lookup IRQ index on the HW interrupt type
|
||||
* Used for all irq related ops
|
||||
* @intr: HW interrupt handle
|
||||
* @intr_type: Interrupt type defined in dpu_intr_type
|
||||
* @instance_idx: HW interrupt block instance
|
||||
* @return: irq_idx or -EINVAL for lookup fail
|
||||
*/
|
||||
int (*irq_idx_lookup)(
|
||||
int (*irq_idx_lookup)(struct dpu_hw_intr *intr,
|
||||
enum dpu_intr_type intr_type,
|
||||
u32 instance_idx);
|
||||
|
||||
@ -179,6 +180,7 @@ struct dpu_hw_intr_ops {
|
||||
* @save_irq_status: array of IRQ status reg storage created during init
|
||||
* @irq_idx_tbl_size: total number of irq_idx mapped in the hw_interrupts
|
||||
* @irq_lock: spinlock for accessing IRQ resources
|
||||
* @obsolete_irq: irq types that are obsolete for a particular target
|
||||
*/
|
||||
struct dpu_hw_intr {
|
||||
struct dpu_hw_blk_reg_map hw;
|
||||
@ -188,6 +190,7 @@ struct dpu_hw_intr {
|
||||
u32 irq_idx_tbl_size;
|
||||
spinlock_t irq_lock;
|
||||
unsigned long irq_mask;
|
||||
unsigned long obsolete_irq;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -31,6 +31,8 @@
|
||||
#define INTF_TEST_CTL 0x054
|
||||
#define INTF_TP_COLOR0 0x058
|
||||
#define INTF_TP_COLOR1 0x05C
|
||||
#define INTF_CONFIG2 0x060
|
||||
#define INTF_DISPLAY_DATA_HCTL 0x064
|
||||
#define INTF_FRAME_LINE_COUNT_EN 0x0A8
|
||||
#define INTF_FRAME_COUNT 0x0AC
|
||||
#define INTF_LINE_COUNT 0x0B0
|
||||
@ -93,7 +95,7 @@ static void dpu_hw_intf_setup_timing_engine(struct dpu_hw_intf *ctx,
|
||||
u32 active_hctl, display_hctl, hsync_ctl;
|
||||
u32 polarity_ctl, den_polarity, hsync_polarity, vsync_polarity;
|
||||
u32 panel_format;
|
||||
u32 intf_cfg;
|
||||
u32 intf_cfg, intf_cfg2 = 0, display_data_hctl = 0;
|
||||
|
||||
/* read interface_cfg */
|
||||
intf_cfg = DPU_REG_READ(c, INTF_CONFIG);
|
||||
@ -178,6 +180,13 @@ static void dpu_hw_intf_setup_timing_engine(struct dpu_hw_intf *ctx,
|
||||
(COLOR_8BIT << 4) |
|
||||
(0x21 << 8));
|
||||
|
||||
if (ctx->cap->features & BIT(DPU_DATA_HCTL_EN)) {
|
||||
intf_cfg2 |= BIT(4);
|
||||
display_data_hctl = display_hctl;
|
||||
DPU_REG_WRITE(c, INTF_CONFIG2, intf_cfg2);
|
||||
DPU_REG_WRITE(c, INTF_DISPLAY_DATA_HCTL, display_data_hctl);
|
||||
}
|
||||
|
||||
DPU_REG_WRITE(c, INTF_HSYNC_CTL, hsync_ctl);
|
||||
DPU_REG_WRITE(c, INTF_VSYNC_PERIOD_F0, vsync_period * hsync_period);
|
||||
DPU_REG_WRITE(c, INTF_VSYNC_PULSE_WIDTH_F0,
|
||||
@ -256,6 +265,7 @@ static void dpu_hw_intf_get_status(
|
||||
struct dpu_hw_blk_reg_map *c = &intf->hw;
|
||||
|
||||
s->is_en = DPU_REG_READ(c, INTF_TIMING_ENGINE_EN);
|
||||
s->is_prog_fetch_en = !!(DPU_REG_READ(c, INTF_CONFIG) & BIT(31));
|
||||
if (s->is_en) {
|
||||
s->frame_count = DPU_REG_READ(c, INTF_FRAME_COUNT);
|
||||
s->line_count = DPU_REG_READ(c, INTF_LINE_COUNT);
|
||||
|
@ -40,6 +40,7 @@ struct intf_prog_fetch {
|
||||
|
||||
struct intf_status {
|
||||
u8 is_en; /* interface timing engine is enabled or not */
|
||||
u8 is_prog_fetch_en; /* interface prog fetch counter is enabled or not */
|
||||
u32 frame_count; /* frame count since timing engine enabled */
|
||||
u32 line_count; /* current line count including blanking */
|
||||
};
|
||||
|
@ -30,7 +30,7 @@ struct traffic_shaper_cfg {
|
||||
|
||||
/**
|
||||
* struct split_pipe_cfg - pipe configuration for dual display panels
|
||||
* @en : Enable/disable dual pipe confguration
|
||||
* @en : Enable/disable dual pipe configuration
|
||||
* @mode : Panel interface mode
|
||||
* @intf : Interface id for main control path
|
||||
* @split_flush_en: Allows both the paths to be flushed when master path is
|
||||
@ -76,7 +76,7 @@ struct dpu_vsync_source_cfg {
|
||||
* @setup_traffic_shaper : programs traffic shaper control
|
||||
*/
|
||||
struct dpu_hw_mdp_ops {
|
||||
/** setup_split_pipe() : Regsiters are not double buffered, thisk
|
||||
/** setup_split_pipe() : Registers are not double buffered, thisk
|
||||
* function should be called before timing control enable
|
||||
* @mdp : mdp top context driver
|
||||
* @cfg : upper and lower part of pipe configuration
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_file.h>
|
||||
#include <drm/drm_vblank.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_mmu.h"
|
||||
@ -933,8 +934,7 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
|
||||
DPU_DEBUG("REG_DMA is not defined");
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(dev->dev->of_node, "qcom,sc7180-mdss"))
|
||||
dpu_kms_parse_data_bus_icc_path(dpu_kms);
|
||||
dpu_kms_parse_data_bus_icc_path(dpu_kms);
|
||||
|
||||
pm_runtime_get_sync(&dpu_kms->pdev->dev);
|
||||
|
||||
@ -1025,6 +1025,10 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
|
||||
*/
|
||||
dev->mode_config.allow_fb_modifiers = true;
|
||||
|
||||
dev->max_vblank_count = 0xffffffff;
|
||||
/* Disable vblank irqs aggressively for power-saving */
|
||||
dev->vblank_disable_immediate = true;
|
||||
|
||||
/*
|
||||
* _dpu_kms_drm_obj_init should create the DRM related objects
|
||||
* i.e. CRTCs, planes, encoders, connectors and so forth
|
||||
@ -1221,6 +1225,9 @@ static const struct dev_pm_ops dpu_pm_ops = {
|
||||
static const struct of_device_id dpu_dt_match[] = {
|
||||
{ .compatible = "qcom,sdm845-dpu", },
|
||||
{ .compatible = "qcom,sc7180-dpu", },
|
||||
{ .compatible = "qcom,sc7280-dpu", },
|
||||
{ .compatible = "qcom,sm8150-dpu", },
|
||||
{ .compatible = "qcom,sm8250-dpu", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dpu_dt_match);
|
||||
|
@ -31,40 +31,8 @@ struct dpu_mdss {
|
||||
void __iomem *mmio;
|
||||
struct dss_module_power mp;
|
||||
struct dpu_irq_controller irq_controller;
|
||||
struct icc_path *path[2];
|
||||
u32 num_paths;
|
||||
};
|
||||
|
||||
static int dpu_mdss_parse_data_bus_icc_path(struct drm_device *dev,
|
||||
struct dpu_mdss *dpu_mdss)
|
||||
{
|
||||
struct icc_path *path0 = of_icc_get(dev->dev, "mdp0-mem");
|
||||
struct icc_path *path1 = of_icc_get(dev->dev, "mdp1-mem");
|
||||
|
||||
if (IS_ERR_OR_NULL(path0))
|
||||
return PTR_ERR_OR_ZERO(path0);
|
||||
|
||||
dpu_mdss->path[0] = path0;
|
||||
dpu_mdss->num_paths = 1;
|
||||
|
||||
if (!IS_ERR_OR_NULL(path1)) {
|
||||
dpu_mdss->path[1] = path1;
|
||||
dpu_mdss->num_paths++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dpu_mdss_icc_request_bw(struct msm_mdss *mdss)
|
||||
{
|
||||
struct dpu_mdss *dpu_mdss = to_dpu_mdss(mdss);
|
||||
int i;
|
||||
u64 avg_bw = dpu_mdss->num_paths ? MAX_BW / dpu_mdss->num_paths : 0;
|
||||
|
||||
for (i = 0; i < dpu_mdss->num_paths; i++)
|
||||
icc_set_bw(dpu_mdss->path[i], avg_bw, kBps_to_icc(MAX_BW));
|
||||
}
|
||||
|
||||
static void dpu_mdss_irq(struct irq_desc *desc)
|
||||
{
|
||||
struct dpu_mdss *dpu_mdss = irq_desc_get_handler_data(desc);
|
||||
@ -178,8 +146,6 @@ static int dpu_mdss_enable(struct msm_mdss *mdss)
|
||||
struct dss_module_power *mp = &dpu_mdss->mp;
|
||||
int ret;
|
||||
|
||||
dpu_mdss_icc_request_bw(mdss);
|
||||
|
||||
ret = msm_dss_enable_clk(mp->clk_config, mp->num_clk, true);
|
||||
if (ret) {
|
||||
DPU_ERROR("clock enable failed, ret:%d\n", ret);
|
||||
@ -204,6 +170,9 @@ static int dpu_mdss_enable(struct msm_mdss *mdss)
|
||||
case DPU_HW_VER_620:
|
||||
writel_relaxed(0x1e, dpu_mdss->mmio + UBWC_STATIC);
|
||||
break;
|
||||
case DPU_HW_VER_720:
|
||||
writel_relaxed(0x101e, dpu_mdss->mmio + UBWC_STATIC);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -213,15 +182,12 @@ static int dpu_mdss_disable(struct msm_mdss *mdss)
|
||||
{
|
||||
struct dpu_mdss *dpu_mdss = to_dpu_mdss(mdss);
|
||||
struct dss_module_power *mp = &dpu_mdss->mp;
|
||||
int ret, i;
|
||||
int ret;
|
||||
|
||||
ret = msm_dss_enable_clk(mp->clk_config, mp->num_clk, false);
|
||||
if (ret)
|
||||
DPU_ERROR("clock disable failed, ret:%d\n", ret);
|
||||
|
||||
for (i = 0; i < dpu_mdss->num_paths; i++)
|
||||
icc_set_bw(dpu_mdss->path[i], 0, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -232,7 +198,6 @@ static void dpu_mdss_destroy(struct drm_device *dev)
|
||||
struct dpu_mdss *dpu_mdss = to_dpu_mdss(priv->mdss);
|
||||
struct dss_module_power *mp = &dpu_mdss->mp;
|
||||
int irq;
|
||||
int i;
|
||||
|
||||
pm_runtime_suspend(dev->dev);
|
||||
pm_runtime_disable(dev->dev);
|
||||
@ -242,9 +207,6 @@ static void dpu_mdss_destroy(struct drm_device *dev)
|
||||
msm_dss_put_clk(mp->clk_config, mp->num_clk);
|
||||
devm_kfree(&pdev->dev, mp->clk_config);
|
||||
|
||||
for (i = 0; i < dpu_mdss->num_paths; i++)
|
||||
icc_put(dpu_mdss->path[i]);
|
||||
|
||||
if (dpu_mdss->mmio)
|
||||
devm_iounmap(&pdev->dev, dpu_mdss->mmio);
|
||||
dpu_mdss->mmio = NULL;
|
||||
@ -276,12 +238,6 @@ int dpu_mdss_init(struct drm_device *dev)
|
||||
|
||||
DRM_DEBUG("mapped mdss address space @%pK\n", dpu_mdss->mmio);
|
||||
|
||||
if (!of_device_is_compatible(dev->dev->of_node, "qcom,sc7180-mdss")) {
|
||||
ret = dpu_mdss_parse_data_bus_icc_path(dev, dpu_mdss);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
mp = &dpu_mdss->mp;
|
||||
ret = msm_dss_parse_clock(pdev, mp);
|
||||
if (ret) {
|
||||
@ -307,8 +263,6 @@ int dpu_mdss_init(struct drm_device *dev)
|
||||
|
||||
pm_runtime_enable(dev->dev);
|
||||
|
||||
dpu_mdss_icc_request_bw(priv->mdss);
|
||||
|
||||
return ret;
|
||||
|
||||
irq_error:
|
||||
|
@ -20,7 +20,7 @@ static int pingpong_tearcheck_setup(struct drm_encoder *encoder,
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = get_kms(encoder);
|
||||
struct device *dev = encoder->dev->dev;
|
||||
u32 total_lines_x100, vclks_line, cfg;
|
||||
u32 total_lines, vclks_line, cfg;
|
||||
long vsync_clk_speed;
|
||||
struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc);
|
||||
int pp_id = mixer->pp;
|
||||
@ -30,8 +30,8 @@ static int pingpong_tearcheck_setup(struct drm_encoder *encoder,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
total_lines_x100 = mode->vtotal * drm_mode_vrefresh(mode);
|
||||
if (!total_lines_x100) {
|
||||
total_lines = mode->vtotal * drm_mode_vrefresh(mode);
|
||||
if (!total_lines) {
|
||||
DRM_DEV_ERROR(dev, "%s: vtotal(%d) or vrefresh(%d) is 0\n",
|
||||
__func__, mode->vtotal, drm_mode_vrefresh(mode));
|
||||
return -EINVAL;
|
||||
@ -43,15 +43,23 @@ static int pingpong_tearcheck_setup(struct drm_encoder *encoder,
|
||||
vsync_clk_speed);
|
||||
return -EINVAL;
|
||||
}
|
||||
vclks_line = vsync_clk_speed * 100 / total_lines_x100;
|
||||
vclks_line = vsync_clk_speed / total_lines;
|
||||
|
||||
cfg = MDP5_PP_SYNC_CONFIG_VSYNC_COUNTER_EN
|
||||
| MDP5_PP_SYNC_CONFIG_VSYNC_IN_EN;
|
||||
cfg |= MDP5_PP_SYNC_CONFIG_VSYNC_COUNT(vclks_line);
|
||||
|
||||
/*
|
||||
* Tearcheck emits a blanking signal every vclks_line * vtotal * 2 ticks on
|
||||
* the vsync_clk equating to roughly half the desired panel refresh rate.
|
||||
* This is only necessary as stability fallback if interrupts from the
|
||||
* panel arrive too late or not at all, but is currently used by default
|
||||
* because these panel interrupts are not wired up yet.
|
||||
*/
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PP_SYNC_CONFIG_VSYNC(pp_id), cfg);
|
||||
mdp5_write(mdp5_kms,
|
||||
REG_MDP5_PP_SYNC_CONFIG_HEIGHT(pp_id), 0xfff0);
|
||||
REG_MDP5_PP_SYNC_CONFIG_HEIGHT(pp_id), (2 * mode->vtotal));
|
||||
|
||||
mdp5_write(mdp5_kms,
|
||||
REG_MDP5_PP_VSYNC_INIT_VAL(pp_id), mode->vdisplay);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PP_RD_PTR_IRQ(pp_id), mode->vdisplay + 1);
|
||||
@ -59,6 +67,7 @@ static int pingpong_tearcheck_setup(struct drm_encoder *encoder,
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PP_SYNC_THRESH(pp_id),
|
||||
MDP5_PP_SYNC_THRESH_START(4) |
|
||||
MDP5_PP_SYNC_THRESH_CONTINUE(4));
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PP_AUTOREFRESH_CONFIG(pp_id), 0x0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ static int dp_test_data_show(struct seq_file *m, void *data)
|
||||
debug->link->test_video.test_h_width);
|
||||
seq_printf(m, "vdisplay: %d\n",
|
||||
debug->link->test_video.test_v_height);
|
||||
seq_printf(m, "bpc: %u\n",
|
||||
seq_printf(m, "bpc: %u\n",
|
||||
dp_link_bit_depth_to_bpc(bpc));
|
||||
} else
|
||||
seq_puts(m, "0");
|
||||
@ -368,44 +368,21 @@ static int dp_debug_init(struct dp_debug *dp_debug, struct drm_minor *minor)
|
||||
int rc = 0;
|
||||
struct dp_debug_private *debug = container_of(dp_debug,
|
||||
struct dp_debug_private, dp_debug);
|
||||
struct dentry *file;
|
||||
struct dentry *test_active;
|
||||
struct dentry *test_data, *test_type;
|
||||
|
||||
file = debugfs_create_file("dp_debug", 0444, minor->debugfs_root,
|
||||
debugfs_create_file("dp_debug", 0444, minor->debugfs_root,
|
||||
debug, &dp_debug_fops);
|
||||
if (IS_ERR_OR_NULL(file)) {
|
||||
rc = PTR_ERR(file);
|
||||
DRM_ERROR("[%s] debugfs create file failed, rc=%d\n",
|
||||
DEBUG_NAME, rc);
|
||||
}
|
||||
|
||||
test_active = debugfs_create_file("msm_dp_test_active", 0444,
|
||||
debugfs_create_file("msm_dp_test_active", 0444,
|
||||
minor->debugfs_root,
|
||||
debug, &test_active_fops);
|
||||
if (IS_ERR_OR_NULL(test_active)) {
|
||||
rc = PTR_ERR(test_active);
|
||||
DRM_ERROR("[%s] debugfs test_active failed, rc=%d\n",
|
||||
DEBUG_NAME, rc);
|
||||
}
|
||||
|
||||
test_data = debugfs_create_file("msm_dp_test_data", 0444,
|
||||
debugfs_create_file("msm_dp_test_data", 0444,
|
||||
minor->debugfs_root,
|
||||
debug, &dp_test_data_fops);
|
||||
if (IS_ERR_OR_NULL(test_data)) {
|
||||
rc = PTR_ERR(test_data);
|
||||
DRM_ERROR("[%s] debugfs test_data failed, rc=%d\n",
|
||||
DEBUG_NAME, rc);
|
||||
}
|
||||
|
||||
test_type = debugfs_create_file("msm_dp_test_type", 0444,
|
||||
debugfs_create_file("msm_dp_test_type", 0444,
|
||||
minor->debugfs_root,
|
||||
debug, &dp_test_type_fops);
|
||||
if (IS_ERR_OR_NULL(test_type)) {
|
||||
rc = PTR_ERR(test_type);
|
||||
DRM_ERROR("[%s] debugfs test_type failed, rc=%d\n",
|
||||
DEBUG_NAME, rc);
|
||||
}
|
||||
|
||||
debug->root = minor->debugfs_root;
|
||||
|
||||
|
@ -34,8 +34,8 @@ int dp_hpd_connect(struct dp_usbpd *dp_usbpd, bool hpd)
|
||||
|
||||
dp_usbpd->hpd_high = hpd;
|
||||
|
||||
if (!hpd_priv->dp_cb && !hpd_priv->dp_cb->configure
|
||||
&& !hpd_priv->dp_cb->disconnect) {
|
||||
if (!hpd_priv->dp_cb || !hpd_priv->dp_cb->configure
|
||||
|| !hpd_priv->dp_cb->disconnect) {
|
||||
pr_err("hpd dp_cb not initialized\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -269,7 +269,7 @@ int dp_power_clk_enable(struct dp_power *dp_power,
|
||||
DRM_ERROR("failed to '%s' clks for: %s. err=%d\n",
|
||||
enable ? "enable" : "disable",
|
||||
dp_parser_pm_name(pm_type), rc);
|
||||
return rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (pm_type == DP_CORE_PM)
|
||||
|
@ -23,18 +23,6 @@
|
||||
struct msm_dsi_phy_shared_timings;
|
||||
struct msm_dsi_phy_clk_request;
|
||||
|
||||
enum msm_dsi_phy_type {
|
||||
MSM_DSI_PHY_28NM_HPM,
|
||||
MSM_DSI_PHY_28NM_LP,
|
||||
MSM_DSI_PHY_20NM,
|
||||
MSM_DSI_PHY_28NM_8960,
|
||||
MSM_DSI_PHY_14NM,
|
||||
MSM_DSI_PHY_10NM,
|
||||
MSM_DSI_PHY_7NM,
|
||||
MSM_DSI_PHY_7NM_V4_1,
|
||||
MSM_DSI_PHY_MAX
|
||||
};
|
||||
|
||||
enum msm_dsi_phy_usecase {
|
||||
MSM_DSI_PHY_STANDALONE,
|
||||
MSM_DSI_PHY_MASTER,
|
||||
@ -104,45 +92,6 @@ static inline bool msm_dsi_device_connected(struct msm_dsi *msm_dsi)
|
||||
|
||||
struct drm_encoder *msm_dsi_get_encoder(struct msm_dsi *msm_dsi);
|
||||
|
||||
/* dsi pll */
|
||||
struct msm_dsi_pll;
|
||||
#ifdef CONFIG_DRM_MSM_DSI_PLL
|
||||
struct msm_dsi_pll *msm_dsi_pll_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int dsi_id);
|
||||
void msm_dsi_pll_destroy(struct msm_dsi_pll *pll);
|
||||
int msm_dsi_pll_get_clk_provider(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider, struct clk **pixel_clk_provider);
|
||||
void msm_dsi_pll_save_state(struct msm_dsi_pll *pll);
|
||||
int msm_dsi_pll_restore_state(struct msm_dsi_pll *pll);
|
||||
int msm_dsi_pll_set_usecase(struct msm_dsi_pll *pll,
|
||||
enum msm_dsi_phy_usecase uc);
|
||||
#else
|
||||
static inline struct msm_dsi_pll *msm_dsi_pll_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id) {
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
static inline void msm_dsi_pll_destroy(struct msm_dsi_pll *pll)
|
||||
{
|
||||
}
|
||||
static inline int msm_dsi_pll_get_clk_provider(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider, struct clk **pixel_clk_provider)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
static inline void msm_dsi_pll_save_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
}
|
||||
static inline int msm_dsi_pll_restore_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline int msm_dsi_pll_set_usecase(struct msm_dsi_pll *pll,
|
||||
enum msm_dsi_phy_usecase uc)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* dsi host */
|
||||
struct msm_dsi_host;
|
||||
int msm_dsi_host_xfer_prepare(struct mipi_dsi_host *host,
|
||||
@ -169,7 +118,7 @@ struct drm_bridge *msm_dsi_host_get_bridge(struct mipi_dsi_host *host);
|
||||
int msm_dsi_host_register(struct mipi_dsi_host *host, bool check_defer);
|
||||
void msm_dsi_host_unregister(struct mipi_dsi_host *host);
|
||||
int msm_dsi_host_set_src_pll(struct mipi_dsi_host *host,
|
||||
struct msm_dsi_pll *src_pll);
|
||||
struct msm_dsi_phy *src_phy);
|
||||
void msm_dsi_host_reset_phy(struct mipi_dsi_host *host);
|
||||
void msm_dsi_host_get_phy_clk_req(struct mipi_dsi_host *host,
|
||||
struct msm_dsi_phy_clk_request *clk_req,
|
||||
@ -213,14 +162,17 @@ struct msm_dsi_phy_clk_request {
|
||||
|
||||
void msm_dsi_phy_driver_register(void);
|
||||
void msm_dsi_phy_driver_unregister(void);
|
||||
int msm_dsi_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
int msm_dsi_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req);
|
||||
void msm_dsi_phy_disable(struct msm_dsi_phy *phy);
|
||||
void msm_dsi_phy_get_shared_timings(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_shared_timings *shared_timing);
|
||||
struct msm_dsi_pll *msm_dsi_phy_get_pll(struct msm_dsi_phy *phy);
|
||||
void msm_dsi_phy_set_usecase(struct msm_dsi_phy *phy,
|
||||
enum msm_dsi_phy_usecase uc);
|
||||
int msm_dsi_phy_get_clk_provider(struct msm_dsi_phy *phy,
|
||||
struct clk **byte_clk_provider, struct clk **pixel_clk_provider);
|
||||
void msm_dsi_phy_pll_save_state(struct msm_dsi_phy *phy);
|
||||
int msm_dsi_phy_pll_restore_state(struct msm_dsi_phy *phy);
|
||||
|
||||
#endif /* __DSI_CONNECTOR_H__ */
|
||||
|
||||
|
@ -106,12 +106,8 @@ static const struct msm_dsi_config msm8994_dsi_cfg = {
|
||||
.num_dsi = 2,
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO: core_mmss_clk fails to enable for some reason, but things work fine
|
||||
* without it too. Figure out why it doesn't enable and uncomment below
|
||||
*/
|
||||
static const char * const dsi_8996_bus_clk_names[] = {
|
||||
"mdp_core", "iface", "bus", /* "core_mmss", */
|
||||
"mdp_core", "iface", "bus", "core_mmss",
|
||||
};
|
||||
|
||||
static const struct msm_dsi_config msm8996_dsi_cfg = {
|
||||
|
@ -1826,8 +1826,6 @@ int msm_dsi_host_init(struct msm_dsi *msm_dsi)
|
||||
|
||||
msm_host = devm_kzalloc(&pdev->dev, sizeof(*msm_host), GFP_KERNEL);
|
||||
if (!msm_host) {
|
||||
pr_err("%s: FAILED: cannot alloc dsi host\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
@ -2226,13 +2224,13 @@ void msm_dsi_host_cmd_xfer_commit(struct mipi_dsi_host *host, u32 dma_base,
|
||||
}
|
||||
|
||||
int msm_dsi_host_set_src_pll(struct mipi_dsi_host *host,
|
||||
struct msm_dsi_pll *src_pll)
|
||||
struct msm_dsi_phy *src_phy)
|
||||
{
|
||||
struct msm_dsi_host *msm_host = to_msm_dsi_host(host);
|
||||
struct clk *byte_clk_provider, *pixel_clk_provider;
|
||||
int ret;
|
||||
|
||||
ret = msm_dsi_pll_get_clk_provider(src_pll,
|
||||
ret = msm_dsi_phy_get_clk_provider(src_phy,
|
||||
&byte_clk_provider, &pixel_clk_provider);
|
||||
if (ret) {
|
||||
pr_info("%s: can't get provider from pll, don't set parent\n",
|
||||
|
@ -70,7 +70,6 @@ static int dsi_mgr_setup_components(int id)
|
||||
struct msm_dsi *other_dsi = dsi_mgr_get_other_dsi(id);
|
||||
struct msm_dsi *clk_master_dsi = dsi_mgr_get_dsi(DSI_CLOCK_MASTER);
|
||||
struct msm_dsi *clk_slave_dsi = dsi_mgr_get_dsi(DSI_CLOCK_SLAVE);
|
||||
struct msm_dsi_pll *src_pll;
|
||||
int ret;
|
||||
|
||||
if (!IS_DUAL_DSI()) {
|
||||
@ -79,10 +78,7 @@ static int dsi_mgr_setup_components(int id)
|
||||
return ret;
|
||||
|
||||
msm_dsi_phy_set_usecase(msm_dsi->phy, MSM_DSI_PHY_STANDALONE);
|
||||
src_pll = msm_dsi_phy_get_pll(msm_dsi->phy);
|
||||
if (IS_ERR(src_pll))
|
||||
return PTR_ERR(src_pll);
|
||||
ret = msm_dsi_host_set_src_pll(msm_dsi->host, src_pll);
|
||||
ret = msm_dsi_host_set_src_pll(msm_dsi->host, msm_dsi->phy);
|
||||
} else if (!other_dsi) {
|
||||
ret = 0;
|
||||
} else {
|
||||
@ -109,19 +105,16 @@ static int dsi_mgr_setup_components(int id)
|
||||
MSM_DSI_PHY_MASTER);
|
||||
msm_dsi_phy_set_usecase(clk_slave_dsi->phy,
|
||||
MSM_DSI_PHY_SLAVE);
|
||||
src_pll = msm_dsi_phy_get_pll(clk_master_dsi->phy);
|
||||
if (IS_ERR(src_pll))
|
||||
return PTR_ERR(src_pll);
|
||||
ret = msm_dsi_host_set_src_pll(msm_dsi->host, src_pll);
|
||||
ret = msm_dsi_host_set_src_pll(msm_dsi->host, clk_master_dsi->phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = msm_dsi_host_set_src_pll(other_dsi->host, src_pll);
|
||||
ret = msm_dsi_host_set_src_pll(other_dsi->host, clk_master_dsi->phy);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int enable_phy(struct msm_dsi *msm_dsi, int src_pll_id,
|
||||
static int enable_phy(struct msm_dsi *msm_dsi,
|
||||
struct msm_dsi_phy_shared_timings *shared_timings)
|
||||
{
|
||||
struct msm_dsi_phy_clk_request clk_req;
|
||||
@ -130,7 +123,7 @@ static int enable_phy(struct msm_dsi *msm_dsi, int src_pll_id,
|
||||
|
||||
msm_dsi_host_get_phy_clk_req(msm_dsi->host, &clk_req, is_dual_dsi);
|
||||
|
||||
ret = msm_dsi_phy_enable(msm_dsi->phy, src_pll_id, &clk_req);
|
||||
ret = msm_dsi_phy_enable(msm_dsi->phy, &clk_req);
|
||||
msm_dsi_phy_get_shared_timings(msm_dsi->phy, shared_timings);
|
||||
|
||||
return ret;
|
||||
@ -143,7 +136,6 @@ dsi_mgr_phy_enable(int id,
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *mdsi = dsi_mgr_get_dsi(DSI_CLOCK_MASTER);
|
||||
struct msm_dsi *sdsi = dsi_mgr_get_dsi(DSI_CLOCK_SLAVE);
|
||||
int src_pll_id = IS_DUAL_DSI() ? DSI_CLOCK_MASTER : id;
|
||||
int ret;
|
||||
|
||||
/* In case of dual DSI, some registers in PHY1 have been programmed
|
||||
@ -156,11 +148,11 @@ dsi_mgr_phy_enable(int id,
|
||||
msm_dsi_host_reset_phy(mdsi->host);
|
||||
msm_dsi_host_reset_phy(sdsi->host);
|
||||
|
||||
ret = enable_phy(mdsi, src_pll_id,
|
||||
ret = enable_phy(mdsi,
|
||||
&shared_timings[DSI_CLOCK_MASTER]);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = enable_phy(sdsi, src_pll_id,
|
||||
ret = enable_phy(sdsi,
|
||||
&shared_timings[DSI_CLOCK_SLAVE]);
|
||||
if (ret) {
|
||||
msm_dsi_phy_disable(mdsi->phy);
|
||||
@ -169,7 +161,7 @@ dsi_mgr_phy_enable(int id,
|
||||
}
|
||||
} else {
|
||||
msm_dsi_host_reset_phy(msm_dsi->host);
|
||||
ret = enable_phy(msm_dsi, src_pll_id, &shared_timings[id]);
|
||||
ret = enable_phy(msm_dsi, &shared_timings[id]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -505,7 +497,6 @@ static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge)
|
||||
struct msm_dsi *msm_dsi1 = dsi_mgr_get_dsi(DSI_1);
|
||||
struct mipi_dsi_host *host = msm_dsi->host;
|
||||
struct drm_panel *panel = msm_dsi->panel;
|
||||
struct msm_dsi_pll *src_pll;
|
||||
bool is_dual_dsi = IS_DUAL_DSI();
|
||||
int ret;
|
||||
|
||||
@ -539,9 +530,8 @@ static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge)
|
||||
id, ret);
|
||||
}
|
||||
|
||||
/* Save PLL status if it is a clock source */
|
||||
src_pll = msm_dsi_phy_get_pll(msm_dsi->phy);
|
||||
msm_dsi_pll_save_state(src_pll);
|
||||
/* Save PHY status if it is a clock source */
|
||||
msm_dsi_phy_pll_save_state(msm_dsi->phy);
|
||||
|
||||
ret = msm_dsi_host_power_off(host);
|
||||
if (ret)
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "dsi_phy.h"
|
||||
@ -460,23 +461,6 @@ int msm_dsi_dphy_timing_calc_v4(struct msm_dsi_dphy_timing *timing,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msm_dsi_phy_set_src_pll(struct msm_dsi_phy *phy, int pll_id, u32 reg,
|
||||
u32 bit_mask)
|
||||
{
|
||||
int phy_id = phy->id;
|
||||
u32 val;
|
||||
|
||||
if ((phy_id >= DSI_MAX) || (pll_id >= DSI_MAX))
|
||||
return;
|
||||
|
||||
val = dsi_phy_read(phy->base + reg);
|
||||
|
||||
if (phy->cfg->src_pll_truthtable[phy_id][pll_id])
|
||||
dsi_phy_write(phy->base + reg, val | bit_mask);
|
||||
else
|
||||
dsi_phy_write(phy->base + reg, val & (~bit_mask));
|
||||
}
|
||||
|
||||
static int dsi_phy_regulator_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct regulator_bulk_data *s = phy->supplies;
|
||||
@ -637,24 +621,6 @@ static int dsi_phy_get_id(struct msm_dsi_phy *phy)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int msm_dsi_phy_init_common(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
int ret = 0;
|
||||
|
||||
phy->reg_base = msm_ioremap(pdev, "dsi_phy_regulator",
|
||||
"DSI_PHY_REG");
|
||||
if (IS_ERR(phy->reg_base)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map phy regulator base\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsi_phy_driver_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_dsi_phy *phy;
|
||||
@ -670,6 +636,14 @@ static int dsi_phy_driver_probe(struct platform_device *pdev)
|
||||
if (!match)
|
||||
return -ENODEV;
|
||||
|
||||
phy->provided_clocks = devm_kzalloc(dev,
|
||||
struct_size(phy->provided_clocks, hws, NUM_PROVIDED_CLKS),
|
||||
GFP_KERNEL);
|
||||
if (!phy->provided_clocks)
|
||||
return -ENOMEM;
|
||||
|
||||
phy->provided_clocks->num = NUM_PROVIDED_CLKS;
|
||||
|
||||
phy->cfg = match->data;
|
||||
phy->pdev = pdev;
|
||||
|
||||
@ -691,6 +665,31 @@ static int dsi_phy_driver_probe(struct platform_device *pdev)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
phy->pll_base = msm_ioremap(pdev, "dsi_pll", "DSI_PLL");
|
||||
if (IS_ERR(phy->pll_base)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map pll base\n", __func__);
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (phy->cfg->has_phy_lane) {
|
||||
phy->lane_base = msm_ioremap(pdev, "dsi_phy_lane", "DSI_PHY_LANE");
|
||||
if (IS_ERR(phy->lane_base)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map phy lane base\n", __func__);
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (phy->cfg->has_phy_regulator) {
|
||||
phy->reg_base = msm_ioremap(pdev, "dsi_phy_regulator", "DSI_PHY_REG");
|
||||
if (IS_ERR(phy->reg_base)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map phy regulator base\n", __func__);
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = dsi_phy_regulator_init(phy);
|
||||
if (ret)
|
||||
goto fail;
|
||||
@ -702,12 +701,6 @@ static int dsi_phy_driver_probe(struct platform_device *pdev)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (phy->cfg->ops.init) {
|
||||
ret = phy->cfg->ops.init(phy);
|
||||
if (ret)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* PLL init will call into clk_register which requires
|
||||
* register access, so we need to enable power and ahb clock.
|
||||
*/
|
||||
@ -715,12 +708,21 @@ static int dsi_phy_driver_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
phy->pll = msm_dsi_pll_init(pdev, phy->cfg->type, phy->id);
|
||||
if (IS_ERR_OR_NULL(phy->pll)) {
|
||||
DRM_DEV_INFO(dev,
|
||||
"%s: pll init failed: %ld, need separate pll clk driver\n",
|
||||
__func__, PTR_ERR(phy->pll));
|
||||
phy->pll = NULL;
|
||||
if (phy->cfg->ops.pll_init) {
|
||||
ret = phy->cfg->ops.pll_init(phy);
|
||||
if (ret) {
|
||||
DRM_DEV_INFO(dev,
|
||||
"%s: pll init failed: %d, need separate pll clk driver\n",
|
||||
__func__, ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
|
||||
phy->provided_clocks);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "%s: failed to register clk provider: %d\n", __func__, ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dsi_phy_disable_resource(phy);
|
||||
@ -733,23 +735,8 @@ fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsi_phy_driver_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_dsi_phy *phy = platform_get_drvdata(pdev);
|
||||
|
||||
if (phy && phy->pll) {
|
||||
msm_dsi_pll_destroy(phy->pll);
|
||||
phy->pll = NULL;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver dsi_phy_platform_driver = {
|
||||
.probe = dsi_phy_driver_probe,
|
||||
.remove = dsi_phy_driver_remove,
|
||||
.driver = {
|
||||
.name = "msm_dsi_phy",
|
||||
.of_match_table = dsi_phy_dt_match,
|
||||
@ -766,7 +753,7 @@ void __exit msm_dsi_phy_driver_unregister(void)
|
||||
platform_driver_unregister(&dsi_phy_platform_driver);
|
||||
}
|
||||
|
||||
int msm_dsi_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
int msm_dsi_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req)
|
||||
{
|
||||
struct device *dev = &phy->pdev->dev;
|
||||
@ -789,7 +776,7 @@ int msm_dsi_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
goto reg_en_fail;
|
||||
}
|
||||
|
||||
ret = phy->cfg->ops.enable(phy, src_pll_id, clk_req);
|
||||
ret = phy->cfg->ops.enable(phy, clk_req);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "%s: phy enable failed, %d\n", __func__, ret);
|
||||
goto phy_en_fail;
|
||||
@ -802,9 +789,9 @@ int msm_dsi_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
* source.
|
||||
*/
|
||||
if (phy->usecase != MSM_DSI_PHY_SLAVE) {
|
||||
ret = msm_dsi_pll_restore_state(phy->pll);
|
||||
ret = msm_dsi_phy_pll_restore_state(phy);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "%s: failed to restore pll state, %d\n",
|
||||
DRM_DEV_ERROR(dev, "%s: failed to restore phy state, %d\n",
|
||||
__func__, ret);
|
||||
goto pll_restor_fail;
|
||||
}
|
||||
@ -841,17 +828,43 @@ void msm_dsi_phy_get_shared_timings(struct msm_dsi_phy *phy,
|
||||
sizeof(*shared_timings));
|
||||
}
|
||||
|
||||
struct msm_dsi_pll *msm_dsi_phy_get_pll(struct msm_dsi_phy *phy)
|
||||
{
|
||||
if (!phy)
|
||||
return NULL;
|
||||
|
||||
return phy->pll;
|
||||
}
|
||||
|
||||
void msm_dsi_phy_set_usecase(struct msm_dsi_phy *phy,
|
||||
enum msm_dsi_phy_usecase uc)
|
||||
{
|
||||
if (phy)
|
||||
phy->usecase = uc;
|
||||
}
|
||||
|
||||
int msm_dsi_phy_get_clk_provider(struct msm_dsi_phy *phy,
|
||||
struct clk **byte_clk_provider, struct clk **pixel_clk_provider)
|
||||
{
|
||||
if (byte_clk_provider)
|
||||
*byte_clk_provider = phy->provided_clocks->hws[DSI_BYTE_PLL_CLK]->clk;
|
||||
if (pixel_clk_provider)
|
||||
*pixel_clk_provider = phy->provided_clocks->hws[DSI_PIXEL_PLL_CLK]->clk;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void msm_dsi_phy_pll_save_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
if (phy->cfg->ops.save_pll_state) {
|
||||
phy->cfg->ops.save_pll_state(phy);
|
||||
phy->state_saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
int msm_dsi_phy_pll_restore_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (phy->cfg->ops.restore_pll_state && phy->state_saved) {
|
||||
ret = phy->cfg->ops.restore_pll_state(phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
phy->state_saved = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -6,37 +6,38 @@
|
||||
#ifndef __DSI_PHY_H__
|
||||
#define __DSI_PHY_H__
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include "dsi.h"
|
||||
|
||||
#define dsi_phy_read(offset) msm_readl((offset))
|
||||
#define dsi_phy_write(offset, data) msm_writel((data), (offset))
|
||||
|
||||
/* v3.0.0 10nm implementation that requires the old timings settings */
|
||||
#define V3_0_0_10NM_OLD_TIMINGS_QUIRK BIT(0)
|
||||
#define dsi_phy_write_udelay(offset, data, delay_us) { msm_writel((data), (offset)); udelay(delay_us); }
|
||||
#define dsi_phy_write_ndelay(offset, data, delay_ns) { msm_writel((data), (offset)); ndelay(delay_ns); }
|
||||
|
||||
struct msm_dsi_phy_ops {
|
||||
int (*init) (struct msm_dsi_phy *phy);
|
||||
int (*enable)(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
int (*pll_init)(struct msm_dsi_phy *phy);
|
||||
int (*enable)(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req);
|
||||
void (*disable)(struct msm_dsi_phy *phy);
|
||||
void (*save_pll_state)(struct msm_dsi_phy *phy);
|
||||
int (*restore_pll_state)(struct msm_dsi_phy *phy);
|
||||
};
|
||||
|
||||
struct msm_dsi_phy_cfg {
|
||||
enum msm_dsi_phy_type type;
|
||||
struct dsi_reg_config reg_cfg;
|
||||
struct msm_dsi_phy_ops ops;
|
||||
|
||||
/*
|
||||
* Each cell {phy_id, pll_id} of the truth table indicates
|
||||
* if the source PLL selection bit should be set for each PHY.
|
||||
* Fill default H/W values in illegal cells, eg. cell {0, 1}.
|
||||
*/
|
||||
bool src_pll_truthtable[DSI_MAX][DSI_MAX];
|
||||
unsigned long min_pll_rate;
|
||||
unsigned long max_pll_rate;
|
||||
|
||||
const resource_size_t io_start[DSI_MAX];
|
||||
const int num_dsi_phy;
|
||||
const int quirks;
|
||||
bool has_phy_regulator;
|
||||
bool has_phy_lane;
|
||||
};
|
||||
|
||||
extern const struct msm_dsi_phy_cfg dsi_phy_28nm_hpm_cfgs;
|
||||
@ -74,9 +75,14 @@ struct msm_dsi_dphy_timing {
|
||||
u8 hs_halfbyte_en_ckln;
|
||||
};
|
||||
|
||||
#define DSI_BYTE_PLL_CLK 0
|
||||
#define DSI_PIXEL_PLL_CLK 1
|
||||
#define NUM_PROVIDED_CLKS 2
|
||||
|
||||
struct msm_dsi_phy {
|
||||
struct platform_device *pdev;
|
||||
void __iomem *base;
|
||||
void __iomem *pll_base;
|
||||
void __iomem *reg_base;
|
||||
void __iomem *lane_base;
|
||||
int id;
|
||||
@ -90,7 +96,12 @@ struct msm_dsi_phy {
|
||||
enum msm_dsi_phy_usecase usecase;
|
||||
bool regulator_ldo_mode;
|
||||
|
||||
struct msm_dsi_pll *pll;
|
||||
struct clk_hw *vco_hw;
|
||||
bool pll_on;
|
||||
|
||||
struct clk_hw_onecell_data *provided_clocks;
|
||||
|
||||
bool state_saved;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -104,9 +115,5 @@ int msm_dsi_dphy_timing_calc_v3(struct msm_dsi_dphy_timing *timing,
|
||||
struct msm_dsi_phy_clk_request *clk_req);
|
||||
int msm_dsi_dphy_timing_calc_v4(struct msm_dsi_dphy_timing *timing,
|
||||
struct msm_dsi_phy_clk_request *clk_req);
|
||||
void msm_dsi_phy_set_src_pll(struct msm_dsi_phy *phy, int pll_id, u32 reg,
|
||||
u32 bit_mask);
|
||||
int msm_dsi_phy_init_common(struct msm_dsi_phy *phy);
|
||||
|
||||
#endif /* __DSI_PHY_H__ */
|
||||
|
||||
|
@ -3,11 +3,715 @@
|
||||
* Copyright (c) 2018, The Linux Foundation
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "dsi_phy.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 10nm - clock diagram (eg: DSI0):
|
||||
*
|
||||
* dsi0_pll_out_div_clk dsi0_pll_bit_clk
|
||||
* | |
|
||||
* | |
|
||||
* +---------+ | +----------+ | +----+
|
||||
* dsi0vco_clk ---| out_div |--o--| divl_3_0 |--o--| /8 |-- dsi0_phy_pll_out_byteclk
|
||||
* +---------+ | +----------+ | +----+
|
||||
* | |
|
||||
* | | dsi0_pll_by_2_bit_clk
|
||||
* | | |
|
||||
* | | +----+ | |\ dsi0_pclk_mux
|
||||
* | |--| /2 |--o--| \ |
|
||||
* | | +----+ | \ | +---------+
|
||||
* | --------------| |--o--| div_7_4 |-- dsi0_phy_pll_out_dsiclk
|
||||
* |------------------------------| / +---------+
|
||||
* | +-----+ | /
|
||||
* -----------| /4? |--o----------|/
|
||||
* +-----+ | |
|
||||
* | |dsiclk_sel
|
||||
* |
|
||||
* dsi0_pll_post_out_div_clk
|
||||
*/
|
||||
|
||||
#define VCO_REF_CLK_RATE 19200000
|
||||
#define FRAC_BITS 18
|
||||
|
||||
/* v3.0.0 10nm implementation that requires the old timings settings */
|
||||
#define DSI_PHY_10NM_QUIRK_OLD_TIMINGS BIT(0)
|
||||
|
||||
struct dsi_pll_config {
|
||||
bool enable_ssc;
|
||||
bool ssc_center;
|
||||
u32 ssc_freq;
|
||||
u32 ssc_offset;
|
||||
u32 ssc_adj_per;
|
||||
|
||||
/* out */
|
||||
u32 pll_prop_gain_rate;
|
||||
u32 decimal_div_start;
|
||||
u32 frac_div_start;
|
||||
u32 pll_clock_inverters;
|
||||
u32 ssc_stepsize;
|
||||
u32 ssc_div_per;
|
||||
};
|
||||
|
||||
struct pll_10nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 bit_clk_div;
|
||||
u8 pix_clk_div;
|
||||
u8 pll_out_div;
|
||||
u8 pll_mux;
|
||||
};
|
||||
|
||||
struct dsi_pll_10nm {
|
||||
struct clk_hw clk_hw;
|
||||
|
||||
struct msm_dsi_phy *phy;
|
||||
|
||||
u64 vco_current_rate;
|
||||
|
||||
/* protects REG_DSI_10nm_PHY_CMN_CLK_CFG0 register */
|
||||
spinlock_t postdiv_lock;
|
||||
|
||||
struct pll_10nm_cached_state cached_state;
|
||||
|
||||
struct dsi_pll_10nm *slave;
|
||||
};
|
||||
|
||||
#define to_pll_10nm(x) container_of(x, struct dsi_pll_10nm, clk_hw)
|
||||
|
||||
/*
|
||||
* Global list of private DSI PLL struct pointers. We need this for Dual DSI
|
||||
* mode, where the master PLL's clk_ops needs access the slave's private data
|
||||
*/
|
||||
static struct dsi_pll_10nm *pll_10nm_list[DSI_MAX];
|
||||
|
||||
static void dsi_pll_setup_config(struct dsi_pll_config *config)
|
||||
{
|
||||
config->ssc_freq = 31500;
|
||||
config->ssc_offset = 5000;
|
||||
config->ssc_adj_per = 2;
|
||||
|
||||
config->enable_ssc = false;
|
||||
config->ssc_center = false;
|
||||
}
|
||||
|
||||
static void dsi_pll_calc_dec_frac(struct dsi_pll_10nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
u64 fref = VCO_REF_CLK_RATE;
|
||||
u64 pll_freq;
|
||||
u64 divider;
|
||||
u64 dec, dec_multiple;
|
||||
u32 frac;
|
||||
u64 multiplier;
|
||||
|
||||
pll_freq = pll->vco_current_rate;
|
||||
|
||||
divider = fref * 2;
|
||||
|
||||
multiplier = 1 << FRAC_BITS;
|
||||
dec_multiple = div_u64(pll_freq * multiplier, divider);
|
||||
dec = div_u64_rem(dec_multiple, multiplier, &frac);
|
||||
|
||||
if (pll_freq <= 1900000000UL)
|
||||
config->pll_prop_gain_rate = 8;
|
||||
else if (pll_freq <= 3000000000UL)
|
||||
config->pll_prop_gain_rate = 10;
|
||||
else
|
||||
config->pll_prop_gain_rate = 12;
|
||||
if (pll_freq < 1100000000UL)
|
||||
config->pll_clock_inverters = 8;
|
||||
else
|
||||
config->pll_clock_inverters = 0;
|
||||
|
||||
config->decimal_div_start = dec;
|
||||
config->frac_div_start = frac;
|
||||
}
|
||||
|
||||
#define SSC_CENTER BIT(0)
|
||||
#define SSC_EN BIT(1)
|
||||
|
||||
static void dsi_pll_calc_ssc(struct dsi_pll_10nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
u32 ssc_per;
|
||||
u32 ssc_mod;
|
||||
u64 ssc_step_size;
|
||||
u64 frac;
|
||||
|
||||
if (!config->enable_ssc) {
|
||||
DBG("SSC not enabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ssc_per = DIV_ROUND_CLOSEST(VCO_REF_CLK_RATE, config->ssc_freq) / 2 - 1;
|
||||
ssc_mod = (ssc_per + 1) % (config->ssc_adj_per + 1);
|
||||
ssc_per -= ssc_mod;
|
||||
|
||||
frac = config->frac_div_start;
|
||||
ssc_step_size = config->decimal_div_start;
|
||||
ssc_step_size *= (1 << FRAC_BITS);
|
||||
ssc_step_size += frac;
|
||||
ssc_step_size *= config->ssc_offset;
|
||||
ssc_step_size *= (config->ssc_adj_per + 1);
|
||||
ssc_step_size = div_u64(ssc_step_size, (ssc_per + 1));
|
||||
ssc_step_size = DIV_ROUND_CLOSEST_ULL(ssc_step_size, 1000000);
|
||||
|
||||
config->ssc_div_per = ssc_per;
|
||||
config->ssc_stepsize = ssc_step_size;
|
||||
|
||||
pr_debug("SCC: Dec:%d, frac:%llu, frac_bits:%d\n",
|
||||
config->decimal_div_start, frac, FRAC_BITS);
|
||||
pr_debug("SSC: div_per:0x%X, stepsize:0x%X, adjper:0x%X\n",
|
||||
ssc_per, (u32)ssc_step_size, config->ssc_adj_per);
|
||||
}
|
||||
|
||||
static void dsi_pll_ssc_commit(struct dsi_pll_10nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
|
||||
if (config->enable_ssc) {
|
||||
pr_debug("SSC is enabled\n");
|
||||
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_SSC_STEPSIZE_LOW_1,
|
||||
config->ssc_stepsize & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_SSC_STEPSIZE_HIGH_1,
|
||||
config->ssc_stepsize >> 8);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_PER_LOW_1,
|
||||
config->ssc_div_per & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_PER_HIGH_1,
|
||||
config->ssc_div_per >> 8);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_ADJPER_LOW_1,
|
||||
config->ssc_adj_per & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_ADJPER_HIGH_1,
|
||||
config->ssc_adj_per >> 8);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_SSC_CONTROL,
|
||||
SSC_EN | (config->ssc_center ? SSC_CENTER : 0));
|
||||
}
|
||||
}
|
||||
|
||||
static void dsi_pll_config_hzindep_reg(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_ONE, 0x80);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_TWO, 0x03);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_THREE, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_DSM_DIVIDER, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_FEEDBACK_DIVIDER, 0x4e);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_CALIBRATION_SETTINGS, 0x40);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_BAND_SEL_CAL_SETTINGS_THREE,
|
||||
0xba);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_FREQ_DETECT_SETTINGS_ONE, 0x0c);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_OUTDIV, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_CORE_OVERRIDE, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_DIGITAL_TIMERS_TWO, 0x08);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_PROP_GAIN_RATE_1, 0x08);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_BAND_SET_RATE_1, 0xc0);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_INT_GAIN_IFILT_BAND_1, 0xfa);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_FL_INT_GAIN_PFILT_BAND_1,
|
||||
0x4c);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_LOCK_OVERRIDE, 0x80);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PFILT, 0x29);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_IFILT, 0x3f);
|
||||
}
|
||||
|
||||
static void dsi_pll_commit(struct dsi_pll_10nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_CORE_INPUT_OVERRIDE, 0x12);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_DECIMAL_DIV_START_1,
|
||||
config->decimal_div_start);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_LOW_1,
|
||||
config->frac_div_start & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_MID_1,
|
||||
(config->frac_div_start & 0xff00) >> 8);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_HIGH_1,
|
||||
(config->frac_div_start & 0x30000) >> 16);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_LOCKDET_RATE_1, 64);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_PLL_LOCK_DELAY, 0x06);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_CMODE, 0x10);
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_PLL_CLOCK_INVERTERS,
|
||||
config->pll_clock_inverters);
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_vco_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(hw);
|
||||
struct dsi_pll_config config;
|
||||
|
||||
DBG("DSI PLL%d rate=%lu, parent's=%lu", pll_10nm->phy->id, rate,
|
||||
parent_rate);
|
||||
|
||||
pll_10nm->vco_current_rate = rate;
|
||||
|
||||
dsi_pll_setup_config(&config);
|
||||
|
||||
dsi_pll_calc_dec_frac(pll_10nm, &config);
|
||||
|
||||
dsi_pll_calc_ssc(pll_10nm, &config);
|
||||
|
||||
dsi_pll_commit(pll_10nm, &config);
|
||||
|
||||
dsi_pll_config_hzindep_reg(pll_10nm);
|
||||
|
||||
dsi_pll_ssc_commit(pll_10nm, &config);
|
||||
|
||||
/* flush, ensure all register writes are done*/
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_lock_status(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
struct device *dev = &pll->phy->pdev->dev;
|
||||
int rc;
|
||||
u32 status = 0;
|
||||
u32 const delay_us = 100;
|
||||
u32 const timeout_us = 5000;
|
||||
|
||||
rc = readl_poll_timeout_atomic(pll->phy->pll_base +
|
||||
REG_DSI_10nm_PHY_PLL_COMMON_STATUS_ONE,
|
||||
status,
|
||||
((status & BIT(0)) > 0),
|
||||
delay_us,
|
||||
timeout_us);
|
||||
if (rc)
|
||||
DRM_DEV_ERROR(dev, "DSI PLL(%d) lock failed, status=0x%08x\n",
|
||||
pll->phy->id, status);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_pll_bias(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data = dsi_phy_read(pll->phy->base + REG_DSI_10nm_PHY_CMN_CTRL_0);
|
||||
|
||||
dsi_phy_write(pll->phy->pll_base + REG_DSI_10nm_PHY_PLL_SYSTEM_MUXES, 0);
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_10nm_PHY_CMN_CTRL_0,
|
||||
data & ~BIT(5));
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_pll_bias(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data = dsi_phy_read(pll->phy->base + REG_DSI_10nm_PHY_CMN_CTRL_0);
|
||||
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_10nm_PHY_CMN_CTRL_0,
|
||||
data | BIT(5));
|
||||
dsi_phy_write(pll->phy->pll_base + REG_DSI_10nm_PHY_PLL_SYSTEM_MUXES, 0xc0);
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_global_clk(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
data = dsi_phy_read(pll->phy->base + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_10nm_PHY_CMN_CLK_CFG1,
|
||||
data & ~BIT(5));
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_global_clk(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
data = dsi_phy_read(pll->phy->base + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_10nm_PHY_CMN_CLK_CFG1,
|
||||
data | BIT(5));
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_vco_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(hw);
|
||||
struct device *dev = &pll_10nm->phy->pdev->dev;
|
||||
int rc;
|
||||
|
||||
dsi_pll_enable_pll_bias(pll_10nm);
|
||||
if (pll_10nm->slave)
|
||||
dsi_pll_enable_pll_bias(pll_10nm->slave);
|
||||
|
||||
rc = dsi_pll_10nm_vco_set_rate(hw,pll_10nm->vco_current_rate, 0);
|
||||
if (rc) {
|
||||
DRM_DEV_ERROR(dev, "vco_set_rate failed, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Start PLL */
|
||||
dsi_phy_write(pll_10nm->phy->base + REG_DSI_10nm_PHY_CMN_PLL_CNTRL,
|
||||
0x01);
|
||||
|
||||
/*
|
||||
* ensure all PLL configurations are written prior to checking
|
||||
* for PLL lock.
|
||||
*/
|
||||
wmb();
|
||||
|
||||
/* Check for PLL lock */
|
||||
rc = dsi_pll_10nm_lock_status(pll_10nm);
|
||||
if (rc) {
|
||||
DRM_DEV_ERROR(dev, "PLL(%d) lock failed\n", pll_10nm->phy->id);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pll_10nm->phy->pll_on = true;
|
||||
|
||||
dsi_pll_enable_global_clk(pll_10nm);
|
||||
if (pll_10nm->slave)
|
||||
dsi_pll_enable_global_clk(pll_10nm->slave);
|
||||
|
||||
dsi_phy_write(pll_10nm->phy->base + REG_DSI_10nm_PHY_CMN_RBUF_CTRL,
|
||||
0x01);
|
||||
if (pll_10nm->slave)
|
||||
dsi_phy_write(pll_10nm->slave->phy->base +
|
||||
REG_DSI_10nm_PHY_CMN_RBUF_CTRL, 0x01);
|
||||
|
||||
error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_sub(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_10nm_PHY_CMN_RBUF_CTRL, 0);
|
||||
dsi_pll_disable_pll_bias(pll);
|
||||
}
|
||||
|
||||
static void dsi_pll_10nm_vco_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(hw);
|
||||
|
||||
/*
|
||||
* To avoid any stray glitches while abruptly powering down the PLL
|
||||
* make sure to gate the clock using the clock enable bit before
|
||||
* powering down the PLL
|
||||
*/
|
||||
dsi_pll_disable_global_clk(pll_10nm);
|
||||
dsi_phy_write(pll_10nm->phy->base + REG_DSI_10nm_PHY_CMN_PLL_CNTRL, 0);
|
||||
dsi_pll_disable_sub(pll_10nm);
|
||||
if (pll_10nm->slave) {
|
||||
dsi_pll_disable_global_clk(pll_10nm->slave);
|
||||
dsi_pll_disable_sub(pll_10nm->slave);
|
||||
}
|
||||
/* flush, ensure all register writes are done */
|
||||
wmb();
|
||||
pll_10nm->phy->pll_on = false;
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_10nm_vco_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(hw);
|
||||
void __iomem *base = pll_10nm->phy->pll_base;
|
||||
u64 ref_clk = VCO_REF_CLK_RATE;
|
||||
u64 vco_rate = 0x0;
|
||||
u64 multiplier;
|
||||
u32 frac;
|
||||
u32 dec;
|
||||
u64 pll_freq, tmp64;
|
||||
|
||||
dec = dsi_phy_read(base + REG_DSI_10nm_PHY_PLL_DECIMAL_DIV_START_1);
|
||||
dec &= 0xff;
|
||||
|
||||
frac = dsi_phy_read(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_LOW_1);
|
||||
frac |= ((dsi_phy_read(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_MID_1) &
|
||||
0xff) << 8);
|
||||
frac |= ((dsi_phy_read(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_HIGH_1) &
|
||||
0x3) << 16);
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* 1. Assumes prescaler is disabled
|
||||
*/
|
||||
multiplier = 1 << FRAC_BITS;
|
||||
pll_freq = dec * (ref_clk * 2);
|
||||
tmp64 = (ref_clk * 2 * frac);
|
||||
pll_freq += div_u64(tmp64, multiplier);
|
||||
|
||||
vco_rate = pll_freq;
|
||||
|
||||
DBG("DSI PLL%d returning vco rate = %lu, dec = %x, frac = %x",
|
||||
pll_10nm->phy->id, (unsigned long)vco_rate, dec, frac);
|
||||
|
||||
return (unsigned long)vco_rate;
|
||||
}
|
||||
|
||||
static long dsi_pll_10nm_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(hw);
|
||||
|
||||
if (rate < pll_10nm->phy->cfg->min_pll_rate)
|
||||
return pll_10nm->phy->cfg->min_pll_rate;
|
||||
else if (rate > pll_10nm->phy->cfg->max_pll_rate)
|
||||
return pll_10nm->phy->cfg->max_pll_rate;
|
||||
else
|
||||
return rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_10nm_vco = {
|
||||
.round_rate = dsi_pll_10nm_clk_round_rate,
|
||||
.set_rate = dsi_pll_10nm_vco_set_rate,
|
||||
.recalc_rate = dsi_pll_10nm_vco_recalc_rate,
|
||||
.prepare = dsi_pll_10nm_vco_prepare,
|
||||
.unprepare = dsi_pll_10nm_vco_unprepare,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
|
||||
static void dsi_10nm_pll_save_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(phy->vco_hw);
|
||||
struct pll_10nm_cached_state *cached = &pll_10nm->cached_state;
|
||||
void __iomem *phy_base = pll_10nm->phy->base;
|
||||
u32 cmn_clk_cfg0, cmn_clk_cfg1;
|
||||
|
||||
cached->pll_out_div = dsi_phy_read(pll_10nm->phy->pll_base +
|
||||
REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
cached->pll_out_div &= 0x3;
|
||||
|
||||
cmn_clk_cfg0 = dsi_phy_read(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG0);
|
||||
cached->bit_clk_div = cmn_clk_cfg0 & 0xf;
|
||||
cached->pix_clk_div = (cmn_clk_cfg0 & 0xf0) >> 4;
|
||||
|
||||
cmn_clk_cfg1 = dsi_phy_read(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
cached->pll_mux = cmn_clk_cfg1 & 0x3;
|
||||
|
||||
DBG("DSI PLL%d outdiv %x bit_clk_div %x pix_clk_div %x pll_mux %x",
|
||||
pll_10nm->phy->id, cached->pll_out_div, cached->bit_clk_div,
|
||||
cached->pix_clk_div, cached->pll_mux);
|
||||
}
|
||||
|
||||
static int dsi_10nm_pll_restore_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(phy->vco_hw);
|
||||
struct pll_10nm_cached_state *cached = &pll_10nm->cached_state;
|
||||
void __iomem *phy_base = pll_10nm->phy->base;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = dsi_phy_read(pll_10nm->phy->pll_base + REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_out_div;
|
||||
dsi_phy_write(pll_10nm->phy->pll_base + REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE, val);
|
||||
|
||||
dsi_phy_write(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG0,
|
||||
cached->bit_clk_div | (cached->pix_clk_div << 4));
|
||||
|
||||
val = dsi_phy_read(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_mux;
|
||||
dsi_phy_write(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG1, val);
|
||||
|
||||
ret = dsi_pll_10nm_vco_set_rate(phy->vco_hw,
|
||||
pll_10nm->vco_current_rate,
|
||||
VCO_REF_CLK_RATE);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_10nm->phy->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DBG("DSI PLL%d", pll_10nm->phy->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_10nm_set_usecase(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(phy->vco_hw);
|
||||
void __iomem *base = phy->base;
|
||||
u32 data = 0x0; /* internal PLL */
|
||||
|
||||
DBG("DSI PLL%d", pll_10nm->phy->id);
|
||||
|
||||
switch (phy->usecase) {
|
||||
case MSM_DSI_PHY_STANDALONE:
|
||||
break;
|
||||
case MSM_DSI_PHY_MASTER:
|
||||
pll_10nm->slave = pll_10nm_list[(pll_10nm->phy->id + 1) % DSI_MAX];
|
||||
break;
|
||||
case MSM_DSI_PHY_SLAVE:
|
||||
data = 0x1; /* external PLL */
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set PLL src */
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_CMN_CLK_CFG1, (data << 2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The post dividers and mux clocks are created using the standard divider and
|
||||
* mux API. Unlike the 14nm PHY, the slave PLL doesn't need its dividers/mux
|
||||
* state to follow the master PLL's divider/mux state. Therefore, we don't
|
||||
* require special clock ops that also configure the slave PLL registers
|
||||
*/
|
||||
static int pll_10nm_register(struct dsi_pll_10nm *pll_10nm, struct clk_hw **provided_clocks)
|
||||
{
|
||||
char clk_name[32], parent[32], vco_name[32];
|
||||
char parent2[32], parent3[32], parent4[32];
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "xo" },
|
||||
.num_parents = 1,
|
||||
.name = vco_name,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_10nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_10nm->phy->pdev->dev;
|
||||
struct clk_hw *hw;
|
||||
int ret;
|
||||
|
||||
DBG("DSI%d", pll_10nm->phy->id);
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_10nm->phy->id);
|
||||
pll_10nm->clk_hw.init = &vco_init;
|
||||
|
||||
ret = devm_clk_hw_register(dev, &pll_10nm->clk_hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_out_div_clk", pll_10nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%dvco_clk", pll_10nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name,
|
||||
parent, CLK_SET_RATE_PARENT,
|
||||
pll_10nm->phy->pll_base +
|
||||
REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE,
|
||||
0, 2, CLK_DIVIDER_POWER_OF_TWO, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_bit_clk", pll_10nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_10nm->phy->id);
|
||||
|
||||
/* BIT CLK: DIV_CTRL_3_0 */
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT,
|
||||
pll_10nm->phy->base +
|
||||
REG_DSI_10nm_PHY_CMN_CLK_CFG0,
|
||||
0, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_10nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_byteclk", pll_10nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_10nm->phy->id);
|
||||
|
||||
/* DSI Byte clock = VCO_CLK / OUT_DIV / BIT_DIV / 8 */
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT, 1, 8);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
provided_clocks[DSI_BYTE_PLL_CLK] = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_by_2_bit_clk", pll_10nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_10nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 2);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_post_out_div_clk", pll_10nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_10nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 4);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pclk_mux", pll_10nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_10nm->phy->id);
|
||||
snprintf(parent2, 32, "dsi%d_pll_by_2_bit_clk", pll_10nm->phy->id);
|
||||
snprintf(parent3, 32, "dsi%d_pll_out_div_clk", pll_10nm->phy->id);
|
||||
snprintf(parent4, 32, "dsi%d_pll_post_out_div_clk", pll_10nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_mux(dev, clk_name,
|
||||
((const char *[]){
|
||||
parent, parent2, parent3, parent4
|
||||
}), 4, 0, pll_10nm->phy->base +
|
||||
REG_DSI_10nm_PHY_CMN_CLK_CFG1,
|
||||
0, 2, 0, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_dsiclk", pll_10nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pclk_mux", pll_10nm->phy->id);
|
||||
|
||||
/* PIX CLK DIV : DIV_CTRL_7_4*/
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name, parent,
|
||||
0, pll_10nm->phy->base +
|
||||
REG_DSI_10nm_PHY_CMN_CLK_CFG0,
|
||||
4, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_10nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
provided_clocks[DSI_PIXEL_PLL_CLK] = hw;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
struct dsi_pll_10nm *pll_10nm;
|
||||
int ret;
|
||||
|
||||
pll_10nm = devm_kzalloc(&pdev->dev, sizeof(*pll_10nm), GFP_KERNEL);
|
||||
if (!pll_10nm)
|
||||
return -ENOMEM;
|
||||
|
||||
DBG("DSI PLL%d", phy->id);
|
||||
|
||||
pll_10nm_list[phy->id] = pll_10nm;
|
||||
|
||||
spin_lock_init(&pll_10nm->postdiv_lock);
|
||||
|
||||
pll_10nm->phy = phy;
|
||||
|
||||
ret = pll_10nm_register(pll_10nm, phy->provided_clocks->hws);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
phy->vco_hw = &pll_10nm->clk_hw;
|
||||
|
||||
/* TODO: Remove this when we have proper display handover support */
|
||||
msm_dsi_phy_pll_save_state(phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_phy_hw_v3_0_is_pll_on(struct msm_dsi_phy *phy)
|
||||
{
|
||||
void __iomem *base = phy->base;
|
||||
@ -42,7 +746,7 @@ static void dsi_phy_hw_v3_0_lane_settings(struct msm_dsi_phy *phy)
|
||||
u8 tx_dctrl[] = { 0x00, 0x00, 0x00, 0x04, 0x01 };
|
||||
void __iomem *lane_base = phy->lane_base;
|
||||
|
||||
if (phy->cfg->quirks & V3_0_0_10NM_OLD_TIMINGS_QUIRK)
|
||||
if (phy->cfg->quirks & DSI_PHY_10NM_QUIRK_OLD_TIMINGS)
|
||||
tx_dctrl[3] = 0x02;
|
||||
|
||||
/* Strength ctrl settings */
|
||||
@ -77,14 +781,14 @@ static void dsi_phy_hw_v3_0_lane_settings(struct msm_dsi_phy *phy)
|
||||
tx_dctrl[i]);
|
||||
}
|
||||
|
||||
if (!(phy->cfg->quirks & V3_0_0_10NM_OLD_TIMINGS_QUIRK)) {
|
||||
if (!(phy->cfg->quirks & DSI_PHY_10NM_QUIRK_OLD_TIMINGS)) {
|
||||
/* Toggle BIT 0 to release freeze I/0 */
|
||||
dsi_phy_write(lane_base + REG_DSI_10nm_PHY_LN_TX_DCTRL(3), 0x05);
|
||||
dsi_phy_write(lane_base + REG_DSI_10nm_PHY_LN_TX_DCTRL(3), 0x04);
|
||||
}
|
||||
}
|
||||
|
||||
static int dsi_10nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
static int dsi_10nm_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req)
|
||||
{
|
||||
int ret;
|
||||
@ -175,7 +879,7 @@ static int dsi_10nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
/* Select full-rate mode */
|
||||
dsi_phy_write(base + REG_DSI_10nm_PHY_CMN_CTRL_2, 0x40);
|
||||
|
||||
ret = msm_dsi_pll_set_usecase(phy->pll, phy->usecase);
|
||||
ret = dsi_10nm_set_usecase(phy);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&phy->pdev->dev, "%s: set pll usecase failed, %d\n",
|
||||
__func__, ret);
|
||||
@ -216,24 +920,8 @@ static void dsi_10nm_phy_disable(struct msm_dsi_phy *phy)
|
||||
DBG("DSI%d PHY disabled", phy->id);
|
||||
}
|
||||
|
||||
static int dsi_10nm_phy_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
|
||||
phy->lane_base = msm_ioremap(pdev, "dsi_phy_lane",
|
||||
"DSI_PHY_LANE");
|
||||
if (IS_ERR(phy->lane_base)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map phy lane base\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_10nm_cfgs = {
|
||||
.type = MSM_DSI_PHY_10NM,
|
||||
.src_pll_truthtable = { {false, false}, {true, false} },
|
||||
.has_phy_lane = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -243,15 +931,18 @@ const struct msm_dsi_phy_cfg dsi_phy_10nm_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_10nm_phy_enable,
|
||||
.disable = dsi_10nm_phy_disable,
|
||||
.init = dsi_10nm_phy_init,
|
||||
.pll_init = dsi_pll_10nm_init,
|
||||
.save_pll_state = dsi_10nm_pll_save_state,
|
||||
.restore_pll_state = dsi_10nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = 1000000000UL,
|
||||
.max_pll_rate = 3500000000UL,
|
||||
.io_start = { 0xae94400, 0xae96400 },
|
||||
.num_dsi_phy = 2,
|
||||
};
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_10nm_8998_cfgs = {
|
||||
.type = MSM_DSI_PHY_10NM,
|
||||
.src_pll_truthtable = { {false, false}, {true, false} },
|
||||
.has_phy_lane = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -261,9 +952,13 @@ const struct msm_dsi_phy_cfg dsi_phy_10nm_8998_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_10nm_phy_enable,
|
||||
.disable = dsi_10nm_phy_disable,
|
||||
.init = dsi_10nm_phy_init,
|
||||
.pll_init = dsi_pll_10nm_init,
|
||||
.save_pll_state = dsi_10nm_pll_save_state,
|
||||
.restore_pll_state = dsi_10nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = 1000000000UL,
|
||||
.max_pll_rate = 3500000000UL,
|
||||
.io_start = { 0xc994400, 0xc996400 },
|
||||
.num_dsi_phy = 2,
|
||||
.quirks = V3_0_0_10NM_OLD_TIMINGS_QUIRK,
|
||||
.quirks = DSI_PHY_10NM_QUIRK_OLD_TIMINGS,
|
||||
};
|
||||
|
@ -3,6 +3,8 @@
|
||||
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "dsi_phy.h"
|
||||
@ -10,6 +12,895 @@
|
||||
|
||||
#define PHY_14NM_CKLN_IDX 4
|
||||
|
||||
/*
|
||||
* DSI PLL 14nm - clock diagram (eg: DSI0):
|
||||
*
|
||||
* dsi0n1_postdiv_clk
|
||||
* |
|
||||
* |
|
||||
* +----+ | +----+
|
||||
* dsi0vco_clk ---| n1 |--o--| /8 |-- dsi0pllbyte
|
||||
* +----+ | +----+
|
||||
* | dsi0n1_postdivby2_clk
|
||||
* | +----+ |
|
||||
* o---| /2 |--o--|\
|
||||
* | +----+ | \ +----+
|
||||
* | | |--| n2 |-- dsi0pll
|
||||
* o--------------| / +----+
|
||||
* |/
|
||||
*/
|
||||
|
||||
#define POLL_MAX_READS 15
|
||||
#define POLL_TIMEOUT_US 1000
|
||||
|
||||
#define VCO_REF_CLK_RATE 19200000
|
||||
#define VCO_MIN_RATE 1300000000UL
|
||||
#define VCO_MAX_RATE 2600000000UL
|
||||
|
||||
struct dsi_pll_config {
|
||||
u64 vco_current_rate;
|
||||
|
||||
u32 ssc_en; /* SSC enable/disable */
|
||||
|
||||
/* fixed params */
|
||||
u32 plllock_cnt;
|
||||
u32 ssc_center;
|
||||
u32 ssc_adj_period;
|
||||
u32 ssc_spread;
|
||||
u32 ssc_freq;
|
||||
|
||||
/* calculated */
|
||||
u32 dec_start;
|
||||
u32 div_frac_start;
|
||||
u32 ssc_period;
|
||||
u32 ssc_step_size;
|
||||
u32 plllock_cmp;
|
||||
u32 pll_vco_div_ref;
|
||||
u32 pll_vco_count;
|
||||
u32 pll_kvco_div_ref;
|
||||
u32 pll_kvco_count;
|
||||
};
|
||||
|
||||
struct pll_14nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 n2postdiv;
|
||||
u8 n1postdiv;
|
||||
};
|
||||
|
||||
struct dsi_pll_14nm {
|
||||
struct clk_hw clk_hw;
|
||||
|
||||
struct msm_dsi_phy *phy;
|
||||
|
||||
/* protects REG_DSI_14nm_PHY_CMN_CLK_CFG0 register */
|
||||
spinlock_t postdiv_lock;
|
||||
|
||||
struct pll_14nm_cached_state cached_state;
|
||||
|
||||
struct dsi_pll_14nm *slave;
|
||||
};
|
||||
|
||||
#define to_pll_14nm(x) container_of(x, struct dsi_pll_14nm, clk_hw)
|
||||
|
||||
/*
|
||||
* Private struct for N1/N2 post-divider clocks. These clocks are similar to
|
||||
* the generic clk_divider class of clocks. The only difference is that it
|
||||
* also sets the slave DSI PLL's post-dividers if in Dual DSI mode
|
||||
*/
|
||||
struct dsi_pll_14nm_postdiv {
|
||||
struct clk_hw hw;
|
||||
|
||||
/* divider params */
|
||||
u8 shift;
|
||||
u8 width;
|
||||
u8 flags; /* same flags as used by clk_divider struct */
|
||||
|
||||
struct dsi_pll_14nm *pll;
|
||||
};
|
||||
|
||||
#define to_pll_14nm_postdiv(_hw) container_of(_hw, struct dsi_pll_14nm_postdiv, hw)
|
||||
|
||||
/*
|
||||
* Global list of private DSI PLL struct pointers. We need this for Dual DSI
|
||||
* mode, where the master PLL's clk_ops needs access the slave's private data
|
||||
*/
|
||||
static struct dsi_pll_14nm *pll_14nm_list[DSI_MAX];
|
||||
|
||||
static bool pll_14nm_poll_for_ready(struct dsi_pll_14nm *pll_14nm,
|
||||
u32 nb_tries, u32 timeout_us)
|
||||
{
|
||||
bool pll_locked = false;
|
||||
void __iomem *base = pll_14nm->phy->pll_base;
|
||||
u32 tries, val;
|
||||
|
||||
tries = nb_tries;
|
||||
while (tries--) {
|
||||
val = dsi_phy_read(base +
|
||||
REG_DSI_14nm_PHY_PLL_RESET_SM_READY_STATUS);
|
||||
pll_locked = !!(val & BIT(5));
|
||||
|
||||
if (pll_locked)
|
||||
break;
|
||||
|
||||
udelay(timeout_us);
|
||||
}
|
||||
|
||||
if (!pll_locked) {
|
||||
tries = nb_tries;
|
||||
while (tries--) {
|
||||
val = dsi_phy_read(base +
|
||||
REG_DSI_14nm_PHY_PLL_RESET_SM_READY_STATUS);
|
||||
pll_locked = !!(val & BIT(0));
|
||||
|
||||
if (pll_locked)
|
||||
break;
|
||||
|
||||
udelay(timeout_us);
|
||||
}
|
||||
}
|
||||
|
||||
DBG("DSI PLL is %slocked", pll_locked ? "" : "*not* ");
|
||||
|
||||
return pll_locked;
|
||||
}
|
||||
|
||||
static void dsi_pll_14nm_config_init(struct dsi_pll_config *pconf)
|
||||
{
|
||||
/* fixed input */
|
||||
pconf->plllock_cnt = 1;
|
||||
|
||||
/*
|
||||
* SSC is enabled by default. We might need DT props for configuring
|
||||
* some SSC params like PPM and center/down spread etc.
|
||||
*/
|
||||
pconf->ssc_en = 1;
|
||||
pconf->ssc_center = 0; /* down spread by default */
|
||||
pconf->ssc_spread = 5; /* PPM / 1000 */
|
||||
pconf->ssc_freq = 31500; /* default recommended */
|
||||
pconf->ssc_adj_period = 37;
|
||||
}
|
||||
|
||||
#define CEIL(x, y) (((x) + ((y) - 1)) / (y))
|
||||
|
||||
static void pll_14nm_ssc_calc(struct dsi_pll_14nm *pll, struct dsi_pll_config *pconf)
|
||||
{
|
||||
u32 period, ssc_period;
|
||||
u32 ref, rem;
|
||||
u64 step_size;
|
||||
|
||||
DBG("vco=%lld ref=%d", pconf->vco_current_rate, VCO_REF_CLK_RATE);
|
||||
|
||||
ssc_period = pconf->ssc_freq / 500;
|
||||
period = (u32)VCO_REF_CLK_RATE / 1000;
|
||||
ssc_period = CEIL(period, ssc_period);
|
||||
ssc_period -= 1;
|
||||
pconf->ssc_period = ssc_period;
|
||||
|
||||
DBG("ssc freq=%d spread=%d period=%d", pconf->ssc_freq,
|
||||
pconf->ssc_spread, pconf->ssc_period);
|
||||
|
||||
step_size = (u32)pconf->vco_current_rate;
|
||||
ref = VCO_REF_CLK_RATE;
|
||||
ref /= 1000;
|
||||
step_size = div_u64(step_size, ref);
|
||||
step_size <<= 20;
|
||||
step_size = div_u64(step_size, 1000);
|
||||
step_size *= pconf->ssc_spread;
|
||||
step_size = div_u64(step_size, 1000);
|
||||
step_size *= (pconf->ssc_adj_period + 1);
|
||||
|
||||
rem = 0;
|
||||
step_size = div_u64_rem(step_size, ssc_period + 1, &rem);
|
||||
if (rem)
|
||||
step_size++;
|
||||
|
||||
DBG("step_size=%lld", step_size);
|
||||
|
||||
step_size &= 0x0ffff; /* take lower 16 bits */
|
||||
|
||||
pconf->ssc_step_size = step_size;
|
||||
}
|
||||
|
||||
static void pll_14nm_dec_frac_calc(struct dsi_pll_14nm *pll, struct dsi_pll_config *pconf)
|
||||
{
|
||||
u64 multiplier = BIT(20);
|
||||
u64 dec_start_multiple, dec_start, pll_comp_val;
|
||||
u32 duration, div_frac_start;
|
||||
u64 vco_clk_rate = pconf->vco_current_rate;
|
||||
u64 fref = VCO_REF_CLK_RATE;
|
||||
|
||||
DBG("vco_clk_rate=%lld ref_clk_rate=%lld", vco_clk_rate, fref);
|
||||
|
||||
dec_start_multiple = div_u64(vco_clk_rate * multiplier, fref);
|
||||
div_u64_rem(dec_start_multiple, multiplier, &div_frac_start);
|
||||
|
||||
dec_start = div_u64(dec_start_multiple, multiplier);
|
||||
|
||||
pconf->dec_start = (u32)dec_start;
|
||||
pconf->div_frac_start = div_frac_start;
|
||||
|
||||
if (pconf->plllock_cnt == 0)
|
||||
duration = 1024;
|
||||
else if (pconf->plllock_cnt == 1)
|
||||
duration = 256;
|
||||
else if (pconf->plllock_cnt == 2)
|
||||
duration = 128;
|
||||
else
|
||||
duration = 32;
|
||||
|
||||
pll_comp_val = duration * dec_start_multiple;
|
||||
pll_comp_val = div_u64(pll_comp_val, multiplier);
|
||||
do_div(pll_comp_val, 10);
|
||||
|
||||
pconf->plllock_cmp = (u32)pll_comp_val;
|
||||
}
|
||||
|
||||
static u32 pll_14nm_kvco_slop(u32 vrate)
|
||||
{
|
||||
u32 slop = 0;
|
||||
|
||||
if (vrate > VCO_MIN_RATE && vrate <= 1800000000UL)
|
||||
slop = 600;
|
||||
else if (vrate > 1800000000UL && vrate < 2300000000UL)
|
||||
slop = 400;
|
||||
else if (vrate > 2300000000UL && vrate < VCO_MAX_RATE)
|
||||
slop = 280;
|
||||
|
||||
return slop;
|
||||
}
|
||||
|
||||
static void pll_14nm_calc_vco_count(struct dsi_pll_14nm *pll, struct dsi_pll_config *pconf)
|
||||
{
|
||||
u64 vco_clk_rate = pconf->vco_current_rate;
|
||||
u64 fref = VCO_REF_CLK_RATE;
|
||||
u32 vco_measure_time = 5;
|
||||
u32 kvco_measure_time = 5;
|
||||
u64 data;
|
||||
u32 cnt;
|
||||
|
||||
data = fref * vco_measure_time;
|
||||
do_div(data, 1000000);
|
||||
data &= 0x03ff; /* 10 bits */
|
||||
data -= 2;
|
||||
pconf->pll_vco_div_ref = data;
|
||||
|
||||
data = div_u64(vco_clk_rate, 1000000); /* unit is Mhz */
|
||||
data *= vco_measure_time;
|
||||
do_div(data, 10);
|
||||
pconf->pll_vco_count = data;
|
||||
|
||||
data = fref * kvco_measure_time;
|
||||
do_div(data, 1000000);
|
||||
data &= 0x03ff; /* 10 bits */
|
||||
data -= 1;
|
||||
pconf->pll_kvco_div_ref = data;
|
||||
|
||||
cnt = pll_14nm_kvco_slop(vco_clk_rate);
|
||||
cnt *= 2;
|
||||
cnt /= 100;
|
||||
cnt *= kvco_measure_time;
|
||||
pconf->pll_kvco_count = cnt;
|
||||
}
|
||||
|
||||
static void pll_db_commit_ssc(struct dsi_pll_14nm *pll, struct dsi_pll_config *pconf)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
u8 data;
|
||||
|
||||
data = pconf->ssc_adj_period;
|
||||
data &= 0x0ff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SSC_ADJ_PER1, data);
|
||||
data = (pconf->ssc_adj_period >> 8);
|
||||
data &= 0x03;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SSC_ADJ_PER2, data);
|
||||
|
||||
data = pconf->ssc_period;
|
||||
data &= 0x0ff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SSC_PER1, data);
|
||||
data = (pconf->ssc_period >> 8);
|
||||
data &= 0x0ff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SSC_PER2, data);
|
||||
|
||||
data = pconf->ssc_step_size;
|
||||
data &= 0x0ff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SSC_STEP_SIZE1, data);
|
||||
data = (pconf->ssc_step_size >> 8);
|
||||
data &= 0x0ff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SSC_STEP_SIZE2, data);
|
||||
|
||||
data = (pconf->ssc_center & 0x01);
|
||||
data <<= 1;
|
||||
data |= 0x01; /* enable */
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SSC_EN_CENTER, data);
|
||||
|
||||
wmb(); /* make sure register committed */
|
||||
}
|
||||
|
||||
static void pll_db_commit_common(struct dsi_pll_14nm *pll,
|
||||
struct dsi_pll_config *pconf)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
u8 data;
|
||||
|
||||
/* confgiure the non frequency dependent pll registers */
|
||||
data = 0;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_SYSCLK_EN_RESET, data);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_TXCLK_EN, 1);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_RESETSM_CNTRL, 48);
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_RESETSM_CNTRL2, 4 << 3); /* bandgap_timer */
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_RESETSM_CNTRL5, 5); /* pll_wakeup_timer */
|
||||
|
||||
data = pconf->pll_vco_div_ref & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_VCO_DIV_REF1, data);
|
||||
data = (pconf->pll_vco_div_ref >> 8) & 0x3;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_VCO_DIV_REF2, data);
|
||||
|
||||
data = pconf->pll_kvco_div_ref & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_KVCO_DIV_REF1, data);
|
||||
data = (pconf->pll_kvco_div_ref >> 8) & 0x3;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_KVCO_DIV_REF2, data);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_MISC1, 16);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_IE_TRIM, 4);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_IP_TRIM, 4);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_CP_SET_CUR, 1 << 3 | 1);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_ICPCSET, 0 << 3 | 0);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_ICPMSET, 0 << 3 | 0);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_ICP_SET, 4 << 3 | 4);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_LPF1, 1 << 4 | 11);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_IPTAT_TRIM, 7);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_CRCTRL, 1 << 4 | 2);
|
||||
}
|
||||
|
||||
static void pll_14nm_software_reset(struct dsi_pll_14nm *pll_14nm)
|
||||
{
|
||||
void __iomem *cmn_base = pll_14nm->phy->base;
|
||||
|
||||
/* de assert pll start and apply pll sw reset */
|
||||
|
||||
/* stop pll */
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_PLL_CNTRL, 0);
|
||||
|
||||
/* pll sw reset */
|
||||
dsi_phy_write_udelay(cmn_base + REG_DSI_14nm_PHY_CMN_CTRL_1, 0x20, 10);
|
||||
wmb(); /* make sure register committed */
|
||||
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_CTRL_1, 0);
|
||||
wmb(); /* make sure register committed */
|
||||
}
|
||||
|
||||
static void pll_db_commit_14nm(struct dsi_pll_14nm *pll,
|
||||
struct dsi_pll_config *pconf)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
void __iomem *cmn_base = pll->phy->base;
|
||||
u8 data;
|
||||
|
||||
DBG("DSI%d PLL", pll->phy->id);
|
||||
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_LDO_CNTRL, 0x3c);
|
||||
|
||||
pll_db_commit_common(pll, pconf);
|
||||
|
||||
pll_14nm_software_reset(pll);
|
||||
|
||||
/* Use the /2 path in Mux */
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_CLK_CFG1, 1);
|
||||
|
||||
data = 0xff; /* data, clk, pll normal operation */
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_CTRL_0, data);
|
||||
|
||||
/* configure the frequency dependent pll registers */
|
||||
data = pconf->dec_start;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_DEC_START, data);
|
||||
|
||||
data = pconf->div_frac_start & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_DIV_FRAC_START1, data);
|
||||
data = (pconf->div_frac_start >> 8) & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_DIV_FRAC_START2, data);
|
||||
data = (pconf->div_frac_start >> 16) & 0xf;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_DIV_FRAC_START3, data);
|
||||
|
||||
data = pconf->plllock_cmp & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLLLOCK_CMP1, data);
|
||||
|
||||
data = (pconf->plllock_cmp >> 8) & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLLLOCK_CMP2, data);
|
||||
|
||||
data = (pconf->plllock_cmp >> 16) & 0x3;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLLLOCK_CMP3, data);
|
||||
|
||||
data = pconf->plllock_cnt << 1 | 0 << 3; /* plllock_rng */
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLLLOCK_CMP_EN, data);
|
||||
|
||||
data = pconf->pll_vco_count & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_VCO_COUNT1, data);
|
||||
data = (pconf->pll_vco_count >> 8) & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_VCO_COUNT2, data);
|
||||
|
||||
data = pconf->pll_kvco_count & 0xff;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_KVCO_COUNT1, data);
|
||||
data = (pconf->pll_kvco_count >> 8) & 0x3;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_KVCO_COUNT2, data);
|
||||
|
||||
/*
|
||||
* High nibble configures the post divider internal to the VCO. It's
|
||||
* fixed to divide by 1 for now.
|
||||
*
|
||||
* 0: divided by 1
|
||||
* 1: divided by 2
|
||||
* 2: divided by 4
|
||||
* 3: divided by 8
|
||||
*/
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_LPF2_POSTDIV, 0 << 4 | 3);
|
||||
|
||||
if (pconf->ssc_en)
|
||||
pll_db_commit_ssc(pll, pconf);
|
||||
|
||||
wmb(); /* make sure register committed */
|
||||
}
|
||||
|
||||
/*
|
||||
* VCO clock Callbacks
|
||||
*/
|
||||
static int dsi_pll_14nm_vco_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(hw);
|
||||
struct dsi_pll_config conf;
|
||||
|
||||
DBG("DSI PLL%d rate=%lu, parent's=%lu", pll_14nm->phy->id, rate,
|
||||
parent_rate);
|
||||
|
||||
dsi_pll_14nm_config_init(&conf);
|
||||
conf.vco_current_rate = rate;
|
||||
|
||||
pll_14nm_dec_frac_calc(pll_14nm, &conf);
|
||||
|
||||
if (conf.ssc_en)
|
||||
pll_14nm_ssc_calc(pll_14nm, &conf);
|
||||
|
||||
pll_14nm_calc_vco_count(pll_14nm, &conf);
|
||||
|
||||
/* commit the slave DSI PLL registers if we're master. Note that we
|
||||
* don't lock the slave PLL. We just ensure that the PLL/PHY registers
|
||||
* of the master and slave are identical
|
||||
*/
|
||||
if (pll_14nm->phy->usecase == MSM_DSI_PHY_MASTER) {
|
||||
struct dsi_pll_14nm *pll_14nm_slave = pll_14nm->slave;
|
||||
|
||||
pll_db_commit_14nm(pll_14nm_slave, &conf);
|
||||
}
|
||||
|
||||
pll_db_commit_14nm(pll_14nm, &conf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_14nm_vco_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(hw);
|
||||
void __iomem *base = pll_14nm->phy->pll_base;
|
||||
u64 vco_rate, multiplier = BIT(20);
|
||||
u32 div_frac_start;
|
||||
u32 dec_start;
|
||||
u64 ref_clk = parent_rate;
|
||||
|
||||
dec_start = dsi_phy_read(base + REG_DSI_14nm_PHY_PLL_DEC_START);
|
||||
dec_start &= 0x0ff;
|
||||
|
||||
DBG("dec_start = %x", dec_start);
|
||||
|
||||
div_frac_start = (dsi_phy_read(base + REG_DSI_14nm_PHY_PLL_DIV_FRAC_START3)
|
||||
& 0xf) << 16;
|
||||
div_frac_start |= (dsi_phy_read(base + REG_DSI_14nm_PHY_PLL_DIV_FRAC_START2)
|
||||
& 0xff) << 8;
|
||||
div_frac_start |= dsi_phy_read(base + REG_DSI_14nm_PHY_PLL_DIV_FRAC_START1)
|
||||
& 0xff;
|
||||
|
||||
DBG("div_frac_start = %x", div_frac_start);
|
||||
|
||||
vco_rate = ref_clk * dec_start;
|
||||
|
||||
vco_rate += ((ref_clk * div_frac_start) / multiplier);
|
||||
|
||||
/*
|
||||
* Recalculating the rate from dec_start and frac_start doesn't end up
|
||||
* the rate we originally set. Convert the freq to KHz, round it up and
|
||||
* convert it back to MHz.
|
||||
*/
|
||||
vco_rate = DIV_ROUND_UP_ULL(vco_rate, 1000) * 1000;
|
||||
|
||||
DBG("returning vco rate = %lu", (unsigned long)vco_rate);
|
||||
|
||||
return (unsigned long)vco_rate;
|
||||
}
|
||||
|
||||
static int dsi_pll_14nm_vco_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(hw);
|
||||
void __iomem *base = pll_14nm->phy->pll_base;
|
||||
void __iomem *cmn_base = pll_14nm->phy->base;
|
||||
bool locked;
|
||||
|
||||
DBG("");
|
||||
|
||||
if (unlikely(pll_14nm->phy->pll_on))
|
||||
return 0;
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_VREF_CFG1, 0x10);
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_PLL_CNTRL, 1);
|
||||
|
||||
locked = pll_14nm_poll_for_ready(pll_14nm, POLL_MAX_READS,
|
||||
POLL_TIMEOUT_US);
|
||||
|
||||
if (unlikely(!locked)) {
|
||||
DRM_DEV_ERROR(&pll_14nm->phy->pdev->dev, "DSI PLL lock failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
DBG("DSI PLL lock success");
|
||||
pll_14nm->phy->pll_on = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_14nm_vco_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(hw);
|
||||
void __iomem *cmn_base = pll_14nm->phy->base;
|
||||
|
||||
DBG("");
|
||||
|
||||
if (unlikely(!pll_14nm->phy->pll_on))
|
||||
return;
|
||||
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_PLL_CNTRL, 0);
|
||||
|
||||
pll_14nm->phy->pll_on = false;
|
||||
}
|
||||
|
||||
static long dsi_pll_14nm_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(hw);
|
||||
|
||||
if (rate < pll_14nm->phy->cfg->min_pll_rate)
|
||||
return pll_14nm->phy->cfg->min_pll_rate;
|
||||
else if (rate > pll_14nm->phy->cfg->max_pll_rate)
|
||||
return pll_14nm->phy->cfg->max_pll_rate;
|
||||
else
|
||||
return rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_14nm_vco = {
|
||||
.round_rate = dsi_pll_14nm_clk_round_rate,
|
||||
.set_rate = dsi_pll_14nm_vco_set_rate,
|
||||
.recalc_rate = dsi_pll_14nm_vco_recalc_rate,
|
||||
.prepare = dsi_pll_14nm_vco_prepare,
|
||||
.unprepare = dsi_pll_14nm_vco_unprepare,
|
||||
};
|
||||
|
||||
/*
|
||||
* N1 and N2 post-divider clock callbacks
|
||||
*/
|
||||
#define div_mask(width) ((1 << (width)) - 1)
|
||||
static unsigned long dsi_pll_14nm_postdiv_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_14nm_postdiv *postdiv = to_pll_14nm_postdiv(hw);
|
||||
struct dsi_pll_14nm *pll_14nm = postdiv->pll;
|
||||
void __iomem *base = pll_14nm->phy->base;
|
||||
u8 shift = postdiv->shift;
|
||||
u8 width = postdiv->width;
|
||||
u32 val;
|
||||
|
||||
DBG("DSI%d PLL parent rate=%lu", pll_14nm->phy->id, parent_rate);
|
||||
|
||||
val = dsi_phy_read(base + REG_DSI_14nm_PHY_CMN_CLK_CFG0) >> shift;
|
||||
val &= div_mask(width);
|
||||
|
||||
return divider_recalc_rate(hw, parent_rate, val, NULL,
|
||||
postdiv->flags, width);
|
||||
}
|
||||
|
||||
static long dsi_pll_14nm_postdiv_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate,
|
||||
unsigned long *prate)
|
||||
{
|
||||
struct dsi_pll_14nm_postdiv *postdiv = to_pll_14nm_postdiv(hw);
|
||||
struct dsi_pll_14nm *pll_14nm = postdiv->pll;
|
||||
|
||||
DBG("DSI%d PLL parent rate=%lu", pll_14nm->phy->id, rate);
|
||||
|
||||
return divider_round_rate(hw, rate, prate, NULL,
|
||||
postdiv->width,
|
||||
postdiv->flags);
|
||||
}
|
||||
|
||||
static int dsi_pll_14nm_postdiv_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_14nm_postdiv *postdiv = to_pll_14nm_postdiv(hw);
|
||||
struct dsi_pll_14nm *pll_14nm = postdiv->pll;
|
||||
void __iomem *base = pll_14nm->phy->base;
|
||||
spinlock_t *lock = &pll_14nm->postdiv_lock;
|
||||
u8 shift = postdiv->shift;
|
||||
u8 width = postdiv->width;
|
||||
unsigned int value;
|
||||
unsigned long flags = 0;
|
||||
u32 val;
|
||||
|
||||
DBG("DSI%d PLL parent rate=%lu parent rate %lu", pll_14nm->phy->id, rate,
|
||||
parent_rate);
|
||||
|
||||
value = divider_get_val(rate, parent_rate, NULL, postdiv->width,
|
||||
postdiv->flags);
|
||||
|
||||
spin_lock_irqsave(lock, flags);
|
||||
|
||||
val = dsi_phy_read(base + REG_DSI_14nm_PHY_CMN_CLK_CFG0);
|
||||
val &= ~(div_mask(width) << shift);
|
||||
|
||||
val |= value << shift;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_CMN_CLK_CFG0, val);
|
||||
|
||||
/* If we're master in dual DSI mode, then the slave PLL's post-dividers
|
||||
* follow the master's post dividers
|
||||
*/
|
||||
if (pll_14nm->phy->usecase == MSM_DSI_PHY_MASTER) {
|
||||
struct dsi_pll_14nm *pll_14nm_slave = pll_14nm->slave;
|
||||
void __iomem *slave_base = pll_14nm_slave->phy->base;
|
||||
|
||||
dsi_phy_write(slave_base + REG_DSI_14nm_PHY_CMN_CLK_CFG0, val);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_14nm_postdiv = {
|
||||
.recalc_rate = dsi_pll_14nm_postdiv_recalc_rate,
|
||||
.round_rate = dsi_pll_14nm_postdiv_round_rate,
|
||||
.set_rate = dsi_pll_14nm_postdiv_set_rate,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
|
||||
static void dsi_14nm_pll_save_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(phy->vco_hw);
|
||||
struct pll_14nm_cached_state *cached_state = &pll_14nm->cached_state;
|
||||
void __iomem *cmn_base = pll_14nm->phy->base;
|
||||
u32 data;
|
||||
|
||||
data = dsi_phy_read(cmn_base + REG_DSI_14nm_PHY_CMN_CLK_CFG0);
|
||||
|
||||
cached_state->n1postdiv = data & 0xf;
|
||||
cached_state->n2postdiv = (data >> 4) & 0xf;
|
||||
|
||||
DBG("DSI%d PLL save state %x %x", pll_14nm->phy->id,
|
||||
cached_state->n1postdiv, cached_state->n2postdiv);
|
||||
|
||||
cached_state->vco_rate = clk_hw_get_rate(phy->vco_hw);
|
||||
}
|
||||
|
||||
static int dsi_14nm_pll_restore_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(phy->vco_hw);
|
||||
struct pll_14nm_cached_state *cached_state = &pll_14nm->cached_state;
|
||||
void __iomem *cmn_base = pll_14nm->phy->base;
|
||||
u32 data;
|
||||
int ret;
|
||||
|
||||
ret = dsi_pll_14nm_vco_set_rate(phy->vco_hw,
|
||||
cached_state->vco_rate, 0);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_14nm->phy->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
data = cached_state->n1postdiv | (cached_state->n2postdiv << 4);
|
||||
|
||||
DBG("DSI%d PLL restore state %x %x", pll_14nm->phy->id,
|
||||
cached_state->n1postdiv, cached_state->n2postdiv);
|
||||
|
||||
dsi_phy_write(cmn_base + REG_DSI_14nm_PHY_CMN_CLK_CFG0, data);
|
||||
|
||||
/* also restore post-dividers for slave DSI PLL */
|
||||
if (phy->usecase == MSM_DSI_PHY_MASTER) {
|
||||
struct dsi_pll_14nm *pll_14nm_slave = pll_14nm->slave;
|
||||
void __iomem *slave_base = pll_14nm_slave->phy->base;
|
||||
|
||||
dsi_phy_write(slave_base + REG_DSI_14nm_PHY_CMN_CLK_CFG0, data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_14nm_set_usecase(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_14nm *pll_14nm = to_pll_14nm(phy->vco_hw);
|
||||
void __iomem *base = phy->pll_base;
|
||||
u32 clkbuflr_en, bandgap = 0;
|
||||
|
||||
switch (phy->usecase) {
|
||||
case MSM_DSI_PHY_STANDALONE:
|
||||
clkbuflr_en = 0x1;
|
||||
break;
|
||||
case MSM_DSI_PHY_MASTER:
|
||||
clkbuflr_en = 0x3;
|
||||
pll_14nm->slave = pll_14nm_list[(pll_14nm->phy->id + 1) % DSI_MAX];
|
||||
break;
|
||||
case MSM_DSI_PHY_SLAVE:
|
||||
clkbuflr_en = 0x0;
|
||||
bandgap = 0x3;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_CLKBUFLR_EN, clkbuflr_en);
|
||||
if (bandgap)
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_PLL_PLL_BANDGAP, bandgap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct clk_hw *pll_14nm_postdiv_register(struct dsi_pll_14nm *pll_14nm,
|
||||
const char *name,
|
||||
const char *parent_name,
|
||||
unsigned long flags,
|
||||
u8 shift)
|
||||
{
|
||||
struct dsi_pll_14nm_postdiv *pll_postdiv;
|
||||
struct device *dev = &pll_14nm->phy->pdev->dev;
|
||||
struct clk_init_data postdiv_init = {
|
||||
.parent_names = (const char *[]) { parent_name },
|
||||
.num_parents = 1,
|
||||
.name = name,
|
||||
.flags = flags,
|
||||
.ops = &clk_ops_dsi_pll_14nm_postdiv,
|
||||
};
|
||||
int ret;
|
||||
|
||||
pll_postdiv = devm_kzalloc(dev, sizeof(*pll_postdiv), GFP_KERNEL);
|
||||
if (!pll_postdiv)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pll_postdiv->pll = pll_14nm;
|
||||
pll_postdiv->shift = shift;
|
||||
/* both N1 and N2 postdividers are 4 bits wide */
|
||||
pll_postdiv->width = 4;
|
||||
/* range of each divider is from 1 to 15 */
|
||||
pll_postdiv->flags = CLK_DIVIDER_ONE_BASED;
|
||||
pll_postdiv->hw.init = &postdiv_init;
|
||||
|
||||
ret = devm_clk_hw_register(dev, &pll_postdiv->hw);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return &pll_postdiv->hw;
|
||||
}
|
||||
|
||||
static int pll_14nm_register(struct dsi_pll_14nm *pll_14nm, struct clk_hw **provided_clocks)
|
||||
{
|
||||
char clk_name[32], parent[32], vco_name[32];
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "xo" },
|
||||
.num_parents = 1,
|
||||
.name = vco_name,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_14nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_14nm->phy->pdev->dev;
|
||||
struct clk_hw *hw;
|
||||
int ret;
|
||||
|
||||
DBG("DSI%d", pll_14nm->phy->id);
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_14nm->phy->id);
|
||||
pll_14nm->clk_hw.init = &vco_init;
|
||||
|
||||
ret = devm_clk_hw_register(dev, &pll_14nm->clk_hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dn1_postdiv_clk", pll_14nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%dvco_clk", pll_14nm->phy->id);
|
||||
|
||||
/* N1 postdiv, bits 0-3 in REG_DSI_14nm_PHY_CMN_CLK_CFG0 */
|
||||
hw = pll_14nm_postdiv_register(pll_14nm, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT, 0);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpllbyte", pll_14nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%dn1_postdiv_clk", pll_14nm->phy->id);
|
||||
|
||||
/* DSI Byte clock = VCO_CLK / N1 / 8 */
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT, 1, 8);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
|
||||
provided_clocks[DSI_BYTE_PLL_CLK] = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dn1_postdivby2_clk", pll_14nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%dn1_postdiv_clk", pll_14nm->phy->id);
|
||||
|
||||
/*
|
||||
* Skip the mux for now, force DSICLK_SEL to 1, Add a /2 divider
|
||||
* on the way. Don't let it set parent.
|
||||
*/
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent, 0, 1, 2);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpll", pll_14nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%dn1_postdivby2_clk", pll_14nm->phy->id);
|
||||
|
||||
/* DSI pixel clock = VCO_CLK / N1 / 2 / N2
|
||||
* This is the output of N2 post-divider, bits 4-7 in
|
||||
* REG_DSI_14nm_PHY_CMN_CLK_CFG0. Don't let it set parent.
|
||||
*/
|
||||
hw = pll_14nm_postdiv_register(pll_14nm, clk_name, parent, 0, 4);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
|
||||
provided_clocks[DSI_PIXEL_PLL_CLK] = hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_14nm_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
struct dsi_pll_14nm *pll_14nm;
|
||||
int ret;
|
||||
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
pll_14nm = devm_kzalloc(&pdev->dev, sizeof(*pll_14nm), GFP_KERNEL);
|
||||
if (!pll_14nm)
|
||||
return -ENOMEM;
|
||||
|
||||
DBG("PLL%d", phy->id);
|
||||
|
||||
pll_14nm_list[phy->id] = pll_14nm;
|
||||
|
||||
spin_lock_init(&pll_14nm->postdiv_lock);
|
||||
|
||||
pll_14nm->phy = phy;
|
||||
|
||||
ret = pll_14nm_register(pll_14nm, phy->provided_clocks->hws);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
phy->vco_hw = &pll_14nm->clk_hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_14nm_dphy_set_timing(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_dphy_timing *timing,
|
||||
int lane_idx)
|
||||
@ -47,7 +938,7 @@ static void dsi_14nm_dphy_set_timing(struct msm_dsi_phy *phy,
|
||||
DSI_14nm_PHY_LN_TIMING_CTRL_11_TRIG3_CMD(0xa0));
|
||||
}
|
||||
|
||||
static int dsi_14nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
static int dsi_14nm_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req)
|
||||
{
|
||||
struct msm_dsi_dphy_timing *timing = &phy->timing;
|
||||
@ -56,6 +947,7 @@ static int dsi_14nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
int ret;
|
||||
void __iomem *base = phy->base;
|
||||
void __iomem *lane_base = phy->lane_base;
|
||||
u32 glbl_test_ctrl;
|
||||
|
||||
if (msm_dsi_dphy_timing_calc_v2(timing, clk_req)) {
|
||||
DRM_DEV_ERROR(&phy->pdev->dev,
|
||||
@ -103,11 +995,13 @@ static int dsi_14nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
udelay(100);
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_CMN_CTRL_1, 0x00);
|
||||
|
||||
msm_dsi_phy_set_src_pll(phy, src_pll_id,
|
||||
REG_DSI_14nm_PHY_CMN_GLBL_TEST_CTRL,
|
||||
DSI_14nm_PHY_CMN_GLBL_TEST_CTRL_BITCLK_HS_SEL);
|
||||
|
||||
ret = msm_dsi_pll_set_usecase(phy->pll, phy->usecase);
|
||||
glbl_test_ctrl = dsi_phy_read(base + REG_DSI_14nm_PHY_CMN_GLBL_TEST_CTRL);
|
||||
if (phy->id == DSI_1 && phy->usecase == MSM_DSI_PHY_SLAVE)
|
||||
glbl_test_ctrl |= DSI_14nm_PHY_CMN_GLBL_TEST_CTRL_BITCLK_HS_SEL;
|
||||
else
|
||||
glbl_test_ctrl &= ~DSI_14nm_PHY_CMN_GLBL_TEST_CTRL_BITCLK_HS_SEL;
|
||||
dsi_phy_write(base + REG_DSI_14nm_PHY_CMN_GLBL_TEST_CTRL, glbl_test_ctrl);
|
||||
ret = dsi_14nm_set_usecase(phy);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&phy->pdev->dev, "%s: set pll usecase failed, %d\n",
|
||||
__func__, ret);
|
||||
@ -129,24 +1023,8 @@ static void dsi_14nm_phy_disable(struct msm_dsi_phy *phy)
|
||||
wmb();
|
||||
}
|
||||
|
||||
static int dsi_14nm_phy_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
|
||||
phy->lane_base = msm_ioremap(pdev, "dsi_phy_lane",
|
||||
"DSI_PHY_LANE");
|
||||
if (IS_ERR(phy->lane_base)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map phy lane base\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_14nm_cfgs = {
|
||||
.type = MSM_DSI_PHY_14NM,
|
||||
.src_pll_truthtable = { {false, false}, {true, false} },
|
||||
.has_phy_lane = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -156,15 +1034,18 @@ const struct msm_dsi_phy_cfg dsi_phy_14nm_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_14nm_phy_enable,
|
||||
.disable = dsi_14nm_phy_disable,
|
||||
.init = dsi_14nm_phy_init,
|
||||
.pll_init = dsi_pll_14nm_init,
|
||||
.save_pll_state = dsi_14nm_pll_save_state,
|
||||
.restore_pll_state = dsi_14nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = VCO_MIN_RATE,
|
||||
.max_pll_rate = VCO_MAX_RATE,
|
||||
.io_start = { 0x994400, 0x996400 },
|
||||
.num_dsi_phy = 2,
|
||||
};
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_14nm_660_cfgs = {
|
||||
.type = MSM_DSI_PHY_14NM,
|
||||
.src_pll_truthtable = { {false, false}, {true, false} },
|
||||
.has_phy_lane = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -174,8 +1055,12 @@ const struct msm_dsi_phy_cfg dsi_phy_14nm_660_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_14nm_phy_enable,
|
||||
.disable = dsi_14nm_phy_disable,
|
||||
.init = dsi_14nm_phy_init,
|
||||
.pll_init = dsi_pll_14nm_init,
|
||||
.save_pll_state = dsi_14nm_pll_save_state,
|
||||
.restore_pll_state = dsi_14nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = VCO_MIN_RATE,
|
||||
.max_pll_rate = VCO_MAX_RATE,
|
||||
.io_start = { 0xc994400, 0xc996000 },
|
||||
.num_dsi_phy = 2,
|
||||
};
|
||||
|
@ -63,13 +63,14 @@ static void dsi_20nm_phy_regulator_ctrl(struct msm_dsi_phy *phy, bool enable)
|
||||
dsi_phy_write(base + REG_DSI_20nm_PHY_REGULATOR_CTRL_0, 0x03);
|
||||
}
|
||||
|
||||
static int dsi_20nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
static int dsi_20nm_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req)
|
||||
{
|
||||
struct msm_dsi_dphy_timing *timing = &phy->timing;
|
||||
int i;
|
||||
void __iomem *base = phy->base;
|
||||
u32 cfg_4[4] = {0x20, 0x40, 0x20, 0x00};
|
||||
u32 val;
|
||||
|
||||
DBG("");
|
||||
|
||||
@ -83,9 +84,12 @@ static int dsi_20nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
|
||||
dsi_phy_write(base + REG_DSI_20nm_PHY_STRENGTH_0, 0xff);
|
||||
|
||||
msm_dsi_phy_set_src_pll(phy, src_pll_id,
|
||||
REG_DSI_20nm_PHY_GLBL_TEST_CTRL,
|
||||
DSI_20nm_PHY_GLBL_TEST_CTRL_BITCLK_HS_SEL);
|
||||
val = dsi_phy_read(base + REG_DSI_20nm_PHY_GLBL_TEST_CTRL);
|
||||
if (phy->id == DSI_1 && phy->usecase == MSM_DSI_PHY_STANDALONE)
|
||||
val |= DSI_20nm_PHY_GLBL_TEST_CTRL_BITCLK_HS_SEL;
|
||||
else
|
||||
val &= ~DSI_20nm_PHY_GLBL_TEST_CTRL_BITCLK_HS_SEL;
|
||||
dsi_phy_write(base + REG_DSI_20nm_PHY_GLBL_TEST_CTRL, val);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
dsi_phy_write(base + REG_DSI_20nm_PHY_LN_CFG_3(i),
|
||||
@ -125,8 +129,7 @@ static void dsi_20nm_phy_disable(struct msm_dsi_phy *phy)
|
||||
}
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_20nm_cfgs = {
|
||||
.type = MSM_DSI_PHY_20NM,
|
||||
.src_pll_truthtable = { {false, true}, {false, true} },
|
||||
.has_phy_regulator = true,
|
||||
.reg_cfg = {
|
||||
.num = 2,
|
||||
.regs = {
|
||||
@ -137,7 +140,6 @@ const struct msm_dsi_phy_cfg dsi_phy_20nm_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_20nm_phy_enable,
|
||||
.disable = dsi_20nm_phy_disable,
|
||||
.init = msm_dsi_phy_init_common,
|
||||
},
|
||||
.io_start = { 0xfd998500, 0xfd9a0500 },
|
||||
.num_dsi_phy = 2,
|
||||
|
@ -3,9 +3,621 @@
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "dsi_phy.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 28nm - clock diagram (eg: DSI0):
|
||||
*
|
||||
* dsi0analog_postdiv_clk
|
||||
* | dsi0indirect_path_div2_clk
|
||||
* | |
|
||||
* +------+ | +----+ | |\ dsi0byte_mux
|
||||
* dsi0vco_clk --o--| DIV1 |--o--| /2 |--o--| \ |
|
||||
* | +------+ +----+ | m| | +----+
|
||||
* | | u|--o--| /4 |-- dsi0pllbyte
|
||||
* | | x| +----+
|
||||
* o--------------------------| /
|
||||
* | |/
|
||||
* | +------+
|
||||
* o----------| DIV3 |------------------------- dsi0pll
|
||||
* +------+
|
||||
*/
|
||||
|
||||
#define POLL_MAX_READS 10
|
||||
#define POLL_TIMEOUT_US 50
|
||||
|
||||
#define VCO_REF_CLK_RATE 19200000
|
||||
#define VCO_MIN_RATE 350000000
|
||||
#define VCO_MAX_RATE 750000000
|
||||
|
||||
/* v2.0.0 28nm LP implementation */
|
||||
#define DSI_PHY_28NM_QUIRK_PHY_LP BIT(0)
|
||||
|
||||
#define LPFR_LUT_SIZE 10
|
||||
struct lpfr_cfg {
|
||||
unsigned long vco_rate;
|
||||
u32 resistance;
|
||||
};
|
||||
|
||||
/* Loop filter resistance: */
|
||||
static const struct lpfr_cfg lpfr_lut[LPFR_LUT_SIZE] = {
|
||||
{ 479500000, 8 },
|
||||
{ 480000000, 11 },
|
||||
{ 575500000, 8 },
|
||||
{ 576000000, 12 },
|
||||
{ 610500000, 8 },
|
||||
{ 659500000, 9 },
|
||||
{ 671500000, 10 },
|
||||
{ 672000000, 14 },
|
||||
{ 708500000, 10 },
|
||||
{ 750000000, 11 },
|
||||
};
|
||||
|
||||
struct pll_28nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 postdiv3;
|
||||
u8 postdiv1;
|
||||
u8 byte_mux;
|
||||
};
|
||||
|
||||
struct dsi_pll_28nm {
|
||||
struct clk_hw clk_hw;
|
||||
|
||||
struct msm_dsi_phy *phy;
|
||||
|
||||
struct pll_28nm_cached_state cached_state;
|
||||
};
|
||||
|
||||
#define to_pll_28nm(x) container_of(x, struct dsi_pll_28nm, clk_hw)
|
||||
|
||||
static bool pll_28nm_poll_for_ready(struct dsi_pll_28nm *pll_28nm,
|
||||
u32 nb_tries, u32 timeout_us)
|
||||
{
|
||||
bool pll_locked = false;
|
||||
u32 val;
|
||||
|
||||
while (nb_tries--) {
|
||||
val = dsi_phy_read(pll_28nm->phy->pll_base + REG_DSI_28nm_PHY_PLL_STATUS);
|
||||
pll_locked = !!(val & DSI_28nm_PHY_PLL_STATUS_PLL_RDY);
|
||||
|
||||
if (pll_locked)
|
||||
break;
|
||||
|
||||
udelay(timeout_us);
|
||||
}
|
||||
DBG("DSI PLL is %slocked", pll_locked ? "" : "*not* ");
|
||||
|
||||
return pll_locked;
|
||||
}
|
||||
|
||||
static void pll_28nm_software_reset(struct dsi_pll_28nm *pll_28nm)
|
||||
{
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
|
||||
/*
|
||||
* Add HW recommended delays after toggling the software
|
||||
* reset bit off and back on.
|
||||
*/
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_TEST_CFG,
|
||||
DSI_28nm_PHY_PLL_TEST_CFG_PLL_SW_RESET, 1);
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_TEST_CFG, 0x00, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clock Callbacks
|
||||
*/
|
||||
static int dsi_pll_28nm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
struct device *dev = &pll_28nm->phy->pdev->dev;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
unsigned long div_fbx1000, gen_vco_clk;
|
||||
u32 refclk_cfg, frac_n_mode, frac_n_value;
|
||||
u32 sdm_cfg0, sdm_cfg1, sdm_cfg2, sdm_cfg3;
|
||||
u32 cal_cfg10, cal_cfg11;
|
||||
u32 rem;
|
||||
int i;
|
||||
|
||||
VERB("rate=%lu, parent's=%lu", rate, parent_rate);
|
||||
|
||||
/* Force postdiv2 to be div-4 */
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_POSTDIV2_CFG, 3);
|
||||
|
||||
/* Configure the Loop filter resistance */
|
||||
for (i = 0; i < LPFR_LUT_SIZE; i++)
|
||||
if (rate <= lpfr_lut[i].vco_rate)
|
||||
break;
|
||||
if (i == LPFR_LUT_SIZE) {
|
||||
DRM_DEV_ERROR(dev, "unable to get loop filter resistance. vco=%lu\n",
|
||||
rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_LPFR_CFG, lpfr_lut[i].resistance);
|
||||
|
||||
/* Loop filter capacitance values : c1 and c2 */
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_LPFC1_CFG, 0x70);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_LPFC2_CFG, 0x15);
|
||||
|
||||
rem = rate % VCO_REF_CLK_RATE;
|
||||
if (rem) {
|
||||
refclk_cfg = DSI_28nm_PHY_PLL_REFCLK_CFG_DBLR;
|
||||
frac_n_mode = 1;
|
||||
div_fbx1000 = rate / (VCO_REF_CLK_RATE / 500);
|
||||
gen_vco_clk = div_fbx1000 * (VCO_REF_CLK_RATE / 500);
|
||||
} else {
|
||||
refclk_cfg = 0x0;
|
||||
frac_n_mode = 0;
|
||||
div_fbx1000 = rate / (VCO_REF_CLK_RATE / 1000);
|
||||
gen_vco_clk = div_fbx1000 * (VCO_REF_CLK_RATE / 1000);
|
||||
}
|
||||
|
||||
DBG("refclk_cfg = %d", refclk_cfg);
|
||||
|
||||
rem = div_fbx1000 % 1000;
|
||||
frac_n_value = (rem << 16) / 1000;
|
||||
|
||||
DBG("div_fb = %lu", div_fbx1000);
|
||||
DBG("frac_n_value = %d", frac_n_value);
|
||||
|
||||
DBG("Generated VCO Clock: %lu", gen_vco_clk);
|
||||
rem = 0;
|
||||
sdm_cfg1 = dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG1);
|
||||
sdm_cfg1 &= ~DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET__MASK;
|
||||
if (frac_n_mode) {
|
||||
sdm_cfg0 = 0x0;
|
||||
sdm_cfg0 |= DSI_28nm_PHY_PLL_SDM_CFG0_BYP_DIV(0);
|
||||
sdm_cfg1 |= DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET(
|
||||
(u32)(((div_fbx1000 / 1000) & 0x3f) - 1));
|
||||
sdm_cfg3 = frac_n_value >> 8;
|
||||
sdm_cfg2 = frac_n_value & 0xff;
|
||||
} else {
|
||||
sdm_cfg0 = DSI_28nm_PHY_PLL_SDM_CFG0_BYP;
|
||||
sdm_cfg0 |= DSI_28nm_PHY_PLL_SDM_CFG0_BYP_DIV(
|
||||
(u32)(((div_fbx1000 / 1000) & 0x3f) - 1));
|
||||
sdm_cfg1 |= DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET(0);
|
||||
sdm_cfg2 = 0;
|
||||
sdm_cfg3 = 0;
|
||||
}
|
||||
|
||||
DBG("sdm_cfg0=%d", sdm_cfg0);
|
||||
DBG("sdm_cfg1=%d", sdm_cfg1);
|
||||
DBG("sdm_cfg2=%d", sdm_cfg2);
|
||||
DBG("sdm_cfg3=%d", sdm_cfg3);
|
||||
|
||||
cal_cfg11 = (u32)(gen_vco_clk / (256 * 1000000));
|
||||
cal_cfg10 = (u32)((gen_vco_clk % (256 * 1000000)) / 1000000);
|
||||
DBG("cal_cfg10=%d, cal_cfg11=%d", cal_cfg10, cal_cfg11);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CHGPUMP_CFG, 0x02);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG3, 0x2b);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG4, 0x06);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x0d);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG1, sdm_cfg1);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG2,
|
||||
DSI_28nm_PHY_PLL_SDM_CFG2_FREQ_SEED_7_0(sdm_cfg2));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG3,
|
||||
DSI_28nm_PHY_PLL_SDM_CFG3_FREQ_SEED_15_8(sdm_cfg3));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG4, 0x00);
|
||||
|
||||
/* Add hardware recommended delay for correct PLL configuration */
|
||||
if (pll_28nm->phy->cfg->quirks & DSI_PHY_28NM_QUIRK_PHY_LP)
|
||||
udelay(1000);
|
||||
else
|
||||
udelay(1);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_REFCLK_CFG, refclk_cfg);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_PWRGEN_CFG, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_VCOLPF_CFG, 0x31);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG0, sdm_cfg0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG0, 0x12);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG6, 0x30);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG7, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG8, 0x60);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG9, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG10, cal_cfg10 & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG11, cal_cfg11 & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_EFUSE_CFG, 0x20);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_clk_is_enabled(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
|
||||
return pll_28nm_poll_for_ready(pll_28nm, POLL_MAX_READS,
|
||||
POLL_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_28nm_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
u32 sdm0, doubler, sdm_byp_div;
|
||||
u32 sdm_dc_off, sdm_freq_seed, sdm2, sdm3;
|
||||
u32 ref_clk = VCO_REF_CLK_RATE;
|
||||
unsigned long vco_rate;
|
||||
|
||||
VERB("parent_rate=%lu", parent_rate);
|
||||
|
||||
/* Check to see if the ref clk doubler is enabled */
|
||||
doubler = dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_REFCLK_CFG) &
|
||||
DSI_28nm_PHY_PLL_REFCLK_CFG_DBLR;
|
||||
ref_clk += (doubler * VCO_REF_CLK_RATE);
|
||||
|
||||
/* see if it is integer mode or sdm mode */
|
||||
sdm0 = dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG0);
|
||||
if (sdm0 & DSI_28nm_PHY_PLL_SDM_CFG0_BYP) {
|
||||
/* integer mode */
|
||||
sdm_byp_div = FIELD(
|
||||
dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG0),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG0_BYP_DIV) + 1;
|
||||
vco_rate = ref_clk * sdm_byp_div;
|
||||
} else {
|
||||
/* sdm mode */
|
||||
sdm_dc_off = FIELD(
|
||||
dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG1),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET);
|
||||
DBG("sdm_dc_off = %d", sdm_dc_off);
|
||||
sdm2 = FIELD(dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG2),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG2_FREQ_SEED_7_0);
|
||||
sdm3 = FIELD(dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG3),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG3_FREQ_SEED_15_8);
|
||||
sdm_freq_seed = (sdm3 << 8) | sdm2;
|
||||
DBG("sdm_freq_seed = %d", sdm_freq_seed);
|
||||
|
||||
vco_rate = (ref_clk * (sdm_dc_off + 1)) +
|
||||
mult_frac(ref_clk, sdm_freq_seed, BIT(16));
|
||||
DBG("vco rate = %lu", vco_rate);
|
||||
}
|
||||
|
||||
DBG("returning vco rate = %lu", vco_rate);
|
||||
|
||||
return vco_rate;
|
||||
}
|
||||
|
||||
static int _dsi_pll_28nm_vco_prepare_hpm(struct dsi_pll_28nm *pll_28nm)
|
||||
{
|
||||
struct device *dev = &pll_28nm->phy->pdev->dev;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
u32 max_reads = 5, timeout_us = 100;
|
||||
bool locked;
|
||||
u32 val;
|
||||
int i;
|
||||
|
||||
DBG("id=%d", pll_28nm->phy->id);
|
||||
|
||||
pll_28nm_software_reset(pll_28nm);
|
||||
|
||||
/*
|
||||
* PLL power up sequence.
|
||||
* Add necessary delays recommended by hardware.
|
||||
*/
|
||||
val = DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 1);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRGEN_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 200);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_ENABLE;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 600);
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
/* DSI Uniphy lock detect setting */
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2,
|
||||
0x0c, 100);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x0d);
|
||||
|
||||
/* poll for PLL ready status */
|
||||
locked = pll_28nm_poll_for_ready(pll_28nm,
|
||||
max_reads, timeout_us);
|
||||
if (locked)
|
||||
break;
|
||||
|
||||
pll_28nm_software_reset(pll_28nm);
|
||||
|
||||
/*
|
||||
* PLL power up sequence.
|
||||
* Add necessary delays recommended by hardware.
|
||||
*/
|
||||
val = DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 1);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRGEN_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 200);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 250);
|
||||
|
||||
val &= ~DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 200);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_ENABLE;
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 600);
|
||||
}
|
||||
|
||||
if (unlikely(!locked))
|
||||
DRM_DEV_ERROR(dev, "DSI PLL lock failed\n");
|
||||
else
|
||||
DBG("DSI PLL Lock success");
|
||||
|
||||
return locked ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_vco_prepare_hpm(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
int i, ret;
|
||||
|
||||
if (unlikely(pll_28nm->phy->pll_on))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
ret = _dsi_pll_28nm_vco_prepare_hpm(pll_28nm);
|
||||
if (!ret) {
|
||||
pll_28nm->phy->pll_on = true;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_vco_prepare_lp(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
struct device *dev = &pll_28nm->phy->pdev->dev;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
bool locked;
|
||||
u32 max_reads = 10, timeout_us = 50;
|
||||
u32 val;
|
||||
|
||||
DBG("id=%d", pll_28nm->phy->id);
|
||||
|
||||
if (unlikely(pll_28nm->phy->pll_on))
|
||||
return 0;
|
||||
|
||||
pll_28nm_software_reset(pll_28nm);
|
||||
|
||||
/*
|
||||
* PLL power up sequence.
|
||||
* Add necessary delays recommended by hardware.
|
||||
*/
|
||||
dsi_phy_write_ndelay(base + REG_DSI_28nm_PHY_PLL_CAL_CFG1, 0x34, 500);
|
||||
|
||||
val = DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRDN_B;
|
||||
dsi_phy_write_ndelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRGEN_PWRDN_B;
|
||||
dsi_phy_write_ndelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B |
|
||||
DSI_28nm_PHY_PLL_GLB_CFG_PLL_ENABLE;
|
||||
dsi_phy_write_ndelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
/* DSI PLL toggle lock detect setting */
|
||||
dsi_phy_write_ndelay(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x04, 500);
|
||||
dsi_phy_write_udelay(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x05, 512);
|
||||
|
||||
locked = pll_28nm_poll_for_ready(pll_28nm, max_reads, timeout_us);
|
||||
|
||||
if (unlikely(!locked)) {
|
||||
DRM_DEV_ERROR(dev, "DSI PLL lock failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
DBG("DSI PLL lock success");
|
||||
pll_28nm->phy->pll_on = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_vco_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
|
||||
DBG("id=%d", pll_28nm->phy->id);
|
||||
|
||||
if (unlikely(!pll_28nm->phy->pll_on))
|
||||
return;
|
||||
|
||||
dsi_phy_write(pll_28nm->phy->pll_base + REG_DSI_28nm_PHY_PLL_GLB_CFG, 0x00);
|
||||
|
||||
pll_28nm->phy->pll_on = false;
|
||||
}
|
||||
|
||||
static long dsi_pll_28nm_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
|
||||
if (rate < pll_28nm->phy->cfg->min_pll_rate)
|
||||
return pll_28nm->phy->cfg->min_pll_rate;
|
||||
else if (rate > pll_28nm->phy->cfg->max_pll_rate)
|
||||
return pll_28nm->phy->cfg->max_pll_rate;
|
||||
else
|
||||
return rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_28nm_vco_hpm = {
|
||||
.round_rate = dsi_pll_28nm_clk_round_rate,
|
||||
.set_rate = dsi_pll_28nm_clk_set_rate,
|
||||
.recalc_rate = dsi_pll_28nm_clk_recalc_rate,
|
||||
.prepare = dsi_pll_28nm_vco_prepare_hpm,
|
||||
.unprepare = dsi_pll_28nm_vco_unprepare,
|
||||
.is_enabled = dsi_pll_28nm_clk_is_enabled,
|
||||
};
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_28nm_vco_lp = {
|
||||
.round_rate = dsi_pll_28nm_clk_round_rate,
|
||||
.set_rate = dsi_pll_28nm_clk_set_rate,
|
||||
.recalc_rate = dsi_pll_28nm_clk_recalc_rate,
|
||||
.prepare = dsi_pll_28nm_vco_prepare_lp,
|
||||
.unprepare = dsi_pll_28nm_vco_unprepare,
|
||||
.is_enabled = dsi_pll_28nm_clk_is_enabled,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
|
||||
static void dsi_28nm_pll_save_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(phy->vco_hw);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
|
||||
cached_state->postdiv3 =
|
||||
dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_POSTDIV3_CFG);
|
||||
cached_state->postdiv1 =
|
||||
dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_POSTDIV1_CFG);
|
||||
cached_state->byte_mux = dsi_phy_read(base + REG_DSI_28nm_PHY_PLL_VREG_CFG);
|
||||
if (dsi_pll_28nm_clk_is_enabled(phy->vco_hw))
|
||||
cached_state->vco_rate = clk_hw_get_rate(phy->vco_hw);
|
||||
else
|
||||
cached_state->vco_rate = 0;
|
||||
}
|
||||
|
||||
static int dsi_28nm_pll_restore_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(phy->vco_hw);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
int ret;
|
||||
|
||||
ret = dsi_pll_28nm_clk_set_rate(phy->vco_hw,
|
||||
cached_state->vco_rate, 0);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_28nm->phy->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_POSTDIV3_CFG,
|
||||
cached_state->postdiv3);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_POSTDIV1_CFG,
|
||||
cached_state->postdiv1);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_PLL_VREG_CFG,
|
||||
cached_state->byte_mux);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pll_28nm_register(struct dsi_pll_28nm *pll_28nm, struct clk_hw **provided_clocks)
|
||||
{
|
||||
char clk_name[32], parent1[32], parent2[32], vco_name[32];
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "xo" },
|
||||
.num_parents = 1,
|
||||
.name = vco_name,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
};
|
||||
struct device *dev = &pll_28nm->phy->pdev->dev;
|
||||
struct clk_hw *hw;
|
||||
int ret;
|
||||
|
||||
DBG("%d", pll_28nm->phy->id);
|
||||
|
||||
if (pll_28nm->phy->cfg->quirks & DSI_PHY_28NM_QUIRK_PHY_LP)
|
||||
vco_init.ops = &clk_ops_dsi_pll_28nm_vco_lp;
|
||||
else
|
||||
vco_init.ops = &clk_ops_dsi_pll_28nm_vco_hpm;
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_28nm->phy->id);
|
||||
pll_28nm->clk_hw.init = &vco_init;
|
||||
ret = devm_clk_hw_register(dev, &pll_28nm->clk_hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%danalog_postdiv_clk", pll_28nm->phy->id);
|
||||
snprintf(parent1, 32, "dsi%dvco_clk", pll_28nm->phy->id);
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name,
|
||||
parent1, CLK_SET_RATE_PARENT,
|
||||
pll_28nm->phy->pll_base +
|
||||
REG_DSI_28nm_PHY_PLL_POSTDIV1_CFG,
|
||||
0, 4, 0, NULL);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dindirect_path_div2_clk", pll_28nm->phy->id);
|
||||
snprintf(parent1, 32, "dsi%danalog_postdiv_clk", pll_28nm->phy->id);
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name,
|
||||
parent1, CLK_SET_RATE_PARENT,
|
||||
1, 2);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpll", pll_28nm->phy->id);
|
||||
snprintf(parent1, 32, "dsi%dvco_clk", pll_28nm->phy->id);
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name,
|
||||
parent1, 0, pll_28nm->phy->pll_base +
|
||||
REG_DSI_28nm_PHY_PLL_POSTDIV3_CFG,
|
||||
0, 8, 0, NULL);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
provided_clocks[DSI_PIXEL_PLL_CLK] = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dbyte_mux", pll_28nm->phy->id);
|
||||
snprintf(parent1, 32, "dsi%dvco_clk", pll_28nm->phy->id);
|
||||
snprintf(parent2, 32, "dsi%dindirect_path_div2_clk", pll_28nm->phy->id);
|
||||
hw = devm_clk_hw_register_mux(dev, clk_name,
|
||||
((const char *[]){
|
||||
parent1, parent2
|
||||
}), 2, CLK_SET_RATE_PARENT, pll_28nm->phy->pll_base +
|
||||
REG_DSI_28nm_PHY_PLL_VREG_CFG, 1, 1, 0, NULL);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpllbyte", pll_28nm->phy->id);
|
||||
snprintf(parent1, 32, "dsi%dbyte_mux", pll_28nm->phy->id);
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name,
|
||||
parent1, CLK_SET_RATE_PARENT, 1, 4);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
provided_clocks[DSI_BYTE_PLL_CLK] = hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
struct dsi_pll_28nm *pll_28nm;
|
||||
int ret;
|
||||
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
pll_28nm = devm_kzalloc(&pdev->dev, sizeof(*pll_28nm), GFP_KERNEL);
|
||||
if (!pll_28nm)
|
||||
return -ENOMEM;
|
||||
|
||||
pll_28nm->phy = phy;
|
||||
|
||||
ret = pll_28nm_register(pll_28nm, phy->provided_clocks->hws);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
phy->vco_hw = &pll_28nm->clk_hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_28nm_dphy_set_timing(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_dphy_timing *timing)
|
||||
{
|
||||
@ -66,7 +678,7 @@ static void dsi_28nm_phy_regulator_enable_ldo(struct msm_dsi_phy *phy)
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_1, 0x1);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_4, 0x20);
|
||||
|
||||
if (phy->cfg->type == MSM_DSI_PHY_28NM_LP)
|
||||
if (phy->cfg->quirks & DSI_PHY_28NM_QUIRK_PHY_LP)
|
||||
dsi_phy_write(phy->base + REG_DSI_28nm_PHY_LDO_CNTRL, 0x05);
|
||||
else
|
||||
dsi_phy_write(phy->base + REG_DSI_28nm_PHY_LDO_CNTRL, 0x0d);
|
||||
@ -86,12 +698,13 @@ static void dsi_28nm_phy_regulator_ctrl(struct msm_dsi_phy *phy, bool enable)
|
||||
dsi_28nm_phy_regulator_enable_dcdc(phy);
|
||||
}
|
||||
|
||||
static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req)
|
||||
{
|
||||
struct msm_dsi_dphy_timing *timing = &phy->timing;
|
||||
int i;
|
||||
void __iomem *base = phy->base;
|
||||
u32 val;
|
||||
|
||||
DBG("");
|
||||
|
||||
@ -131,9 +744,12 @@ static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_0, 0x5f);
|
||||
|
||||
msm_dsi_phy_set_src_pll(phy, src_pll_id,
|
||||
REG_DSI_28nm_PHY_GLBL_TEST_CTRL,
|
||||
DSI_28nm_PHY_GLBL_TEST_CTRL_BITCLK_HS_SEL);
|
||||
val = dsi_phy_read(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL);
|
||||
if (phy->id == DSI_1 && phy->usecase == MSM_DSI_PHY_SLAVE)
|
||||
val &= ~DSI_28nm_PHY_GLBL_TEST_CTRL_BITCLK_HS_SEL;
|
||||
else
|
||||
val |= DSI_28nm_PHY_GLBL_TEST_CTRL_BITCLK_HS_SEL;
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -151,8 +767,7 @@ static void dsi_28nm_phy_disable(struct msm_dsi_phy *phy)
|
||||
}
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_28nm_hpm_cfgs = {
|
||||
.type = MSM_DSI_PHY_28NM_HPM,
|
||||
.src_pll_truthtable = { {true, true}, {false, true} },
|
||||
.has_phy_regulator = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -162,15 +777,18 @@ const struct msm_dsi_phy_cfg dsi_phy_28nm_hpm_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_28nm_phy_enable,
|
||||
.disable = dsi_28nm_phy_disable,
|
||||
.init = msm_dsi_phy_init_common,
|
||||
.pll_init = dsi_pll_28nm_init,
|
||||
.save_pll_state = dsi_28nm_pll_save_state,
|
||||
.restore_pll_state = dsi_28nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = VCO_MIN_RATE,
|
||||
.max_pll_rate = VCO_MAX_RATE,
|
||||
.io_start = { 0xfd922b00, 0xfd923100 },
|
||||
.num_dsi_phy = 2,
|
||||
};
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_28nm_hpm_famb_cfgs = {
|
||||
.type = MSM_DSI_PHY_28NM_HPM,
|
||||
.src_pll_truthtable = { {true, true}, {false, true} },
|
||||
.has_phy_regulator = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -180,15 +798,18 @@ const struct msm_dsi_phy_cfg dsi_phy_28nm_hpm_famb_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_28nm_phy_enable,
|
||||
.disable = dsi_28nm_phy_disable,
|
||||
.init = msm_dsi_phy_init_common,
|
||||
.pll_init = dsi_pll_28nm_init,
|
||||
.save_pll_state = dsi_28nm_pll_save_state,
|
||||
.restore_pll_state = dsi_28nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = VCO_MIN_RATE,
|
||||
.max_pll_rate = VCO_MAX_RATE,
|
||||
.io_start = { 0x1a94400, 0x1a96400 },
|
||||
.num_dsi_phy = 2,
|
||||
};
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_28nm_lp_cfgs = {
|
||||
.type = MSM_DSI_PHY_28NM_LP,
|
||||
.src_pll_truthtable = { {true, true}, {true, true} },
|
||||
.has_phy_regulator = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -198,9 +819,14 @@ const struct msm_dsi_phy_cfg dsi_phy_28nm_lp_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_28nm_phy_enable,
|
||||
.disable = dsi_28nm_phy_disable,
|
||||
.init = msm_dsi_phy_init_common,
|
||||
.pll_init = dsi_pll_28nm_init,
|
||||
.save_pll_state = dsi_28nm_pll_save_state,
|
||||
.restore_pll_state = dsi_28nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = VCO_MIN_RATE,
|
||||
.max_pll_rate = VCO_MAX_RATE,
|
||||
.io_start = { 0x1a98500 },
|
||||
.num_dsi_phy = 1,
|
||||
.quirks = DSI_PHY_28NM_QUIRK_PHY_LP,
|
||||
};
|
||||
|
||||
|
@ -3,11 +3,479 @@
|
||||
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "dsi_phy.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 28nm (8960/A family) - clock diagram (eg: DSI1):
|
||||
*
|
||||
*
|
||||
* +------+
|
||||
* dsi1vco_clk ----o-----| DIV1 |---dsi1pllbit (not exposed as clock)
|
||||
* F * byte_clk | +------+
|
||||
* | bit clock divider (F / 8)
|
||||
* |
|
||||
* | +------+
|
||||
* o-----| DIV2 |---dsi0pllbyte---o---> To byte RCG
|
||||
* | +------+ | (sets parent rate)
|
||||
* | byte clock divider (F) |
|
||||
* | |
|
||||
* | o---> To esc RCG
|
||||
* | (doesn't set parent rate)
|
||||
* |
|
||||
* | +------+
|
||||
* o-----| DIV3 |----dsi0pll------o---> To dsi RCG
|
||||
* +------+ | (sets parent rate)
|
||||
* dsi clock divider (F * magic) |
|
||||
* |
|
||||
* o---> To pixel rcg
|
||||
* (doesn't set parent rate)
|
||||
*/
|
||||
|
||||
#define POLL_MAX_READS 8000
|
||||
#define POLL_TIMEOUT_US 1
|
||||
|
||||
#define VCO_REF_CLK_RATE 27000000
|
||||
#define VCO_MIN_RATE 600000000
|
||||
#define VCO_MAX_RATE 1200000000
|
||||
|
||||
#define VCO_PREF_DIV_RATIO 27
|
||||
|
||||
struct pll_28nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 postdiv3;
|
||||
u8 postdiv2;
|
||||
u8 postdiv1;
|
||||
};
|
||||
|
||||
struct clk_bytediv {
|
||||
struct clk_hw hw;
|
||||
void __iomem *reg;
|
||||
};
|
||||
|
||||
struct dsi_pll_28nm {
|
||||
struct clk_hw clk_hw;
|
||||
|
||||
struct msm_dsi_phy *phy;
|
||||
|
||||
struct pll_28nm_cached_state cached_state;
|
||||
};
|
||||
|
||||
#define to_pll_28nm(x) container_of(x, struct dsi_pll_28nm, clk_hw)
|
||||
|
||||
static bool pll_28nm_poll_for_ready(struct dsi_pll_28nm *pll_28nm,
|
||||
int nb_tries, int timeout_us)
|
||||
{
|
||||
bool pll_locked = false;
|
||||
u32 val;
|
||||
|
||||
while (nb_tries--) {
|
||||
val = dsi_phy_read(pll_28nm->phy->pll_base + REG_DSI_28nm_8960_PHY_PLL_RDY);
|
||||
pll_locked = !!(val & DSI_28nm_8960_PHY_PLL_RDY_PLL_RDY);
|
||||
|
||||
if (pll_locked)
|
||||
break;
|
||||
|
||||
udelay(timeout_us);
|
||||
}
|
||||
DBG("DSI PLL is %slocked", pll_locked ? "" : "*not* ");
|
||||
|
||||
return pll_locked;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clock Callbacks
|
||||
*/
|
||||
static int dsi_pll_28nm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
u32 val, temp, fb_divider;
|
||||
|
||||
DBG("rate=%lu, parent's=%lu", rate, parent_rate);
|
||||
|
||||
temp = rate / 10;
|
||||
val = VCO_REF_CLK_RATE / 10;
|
||||
fb_divider = (temp * VCO_PREF_DIV_RATIO) / val;
|
||||
fb_divider = fb_divider / 2 - 1;
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_1,
|
||||
fb_divider & 0xff);
|
||||
|
||||
val = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_2);
|
||||
|
||||
val |= (fb_divider >> 8) & 0x07;
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_2,
|
||||
val);
|
||||
|
||||
val = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_3);
|
||||
|
||||
val |= (VCO_PREF_DIV_RATIO - 1) & 0x3f;
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_3,
|
||||
val);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_6,
|
||||
0xf);
|
||||
|
||||
val = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8);
|
||||
val |= 0x7 << 4;
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8,
|
||||
val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_clk_is_enabled(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
|
||||
return pll_28nm_poll_for_ready(pll_28nm, POLL_MAX_READS,
|
||||
POLL_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_28nm_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
unsigned long vco_rate;
|
||||
u32 status, fb_divider, temp, ref_divider;
|
||||
|
||||
VERB("parent_rate=%lu", parent_rate);
|
||||
|
||||
status = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_0);
|
||||
|
||||
if (status & DSI_28nm_8960_PHY_PLL_CTRL_0_ENABLE) {
|
||||
fb_divider = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_1);
|
||||
fb_divider &= 0xff;
|
||||
temp = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_2) & 0x07;
|
||||
fb_divider = (temp << 8) | fb_divider;
|
||||
fb_divider += 1;
|
||||
|
||||
ref_divider = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_3);
|
||||
ref_divider &= 0x3f;
|
||||
ref_divider += 1;
|
||||
|
||||
/* multiply by 2 */
|
||||
vco_rate = (parent_rate / ref_divider) * fb_divider * 2;
|
||||
} else {
|
||||
vco_rate = 0;
|
||||
}
|
||||
|
||||
DBG("returning vco rate = %lu", vco_rate);
|
||||
|
||||
return vco_rate;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_vco_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
struct device *dev = &pll_28nm->phy->pdev->dev;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
bool locked;
|
||||
unsigned int bit_div, byte_div;
|
||||
int max_reads = 1000, timeout_us = 100;
|
||||
u32 val;
|
||||
|
||||
DBG("id=%d", pll_28nm->phy->id);
|
||||
|
||||
if (unlikely(pll_28nm->phy->pll_on))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* before enabling the PLL, configure the bit clock divider since we
|
||||
* don't expose it as a clock to the outside world
|
||||
* 1: read back the byte clock divider that should already be set
|
||||
* 2: divide by 8 to get bit clock divider
|
||||
* 3: write it to POSTDIV1
|
||||
*/
|
||||
val = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_9);
|
||||
byte_div = val + 1;
|
||||
bit_div = byte_div / 8;
|
||||
|
||||
val = dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8);
|
||||
val &= ~0xf;
|
||||
val |= (bit_div - 1);
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8, val);
|
||||
|
||||
/* enable the PLL */
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_0,
|
||||
DSI_28nm_8960_PHY_PLL_CTRL_0_ENABLE);
|
||||
|
||||
locked = pll_28nm_poll_for_ready(pll_28nm, max_reads, timeout_us);
|
||||
|
||||
if (unlikely(!locked)) {
|
||||
DRM_DEV_ERROR(dev, "DSI PLL lock failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
DBG("DSI PLL lock success");
|
||||
pll_28nm->phy->pll_on = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_vco_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
|
||||
DBG("id=%d", pll_28nm->phy->id);
|
||||
|
||||
if (unlikely(!pll_28nm->phy->pll_on))
|
||||
return;
|
||||
|
||||
dsi_phy_write(pll_28nm->phy->pll_base + REG_DSI_28nm_8960_PHY_PLL_CTRL_0, 0x00);
|
||||
|
||||
pll_28nm->phy->pll_on = false;
|
||||
}
|
||||
|
||||
static long dsi_pll_28nm_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(hw);
|
||||
|
||||
if (rate < pll_28nm->phy->cfg->min_pll_rate)
|
||||
return pll_28nm->phy->cfg->min_pll_rate;
|
||||
else if (rate > pll_28nm->phy->cfg->max_pll_rate)
|
||||
return pll_28nm->phy->cfg->max_pll_rate;
|
||||
else
|
||||
return rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_28nm_vco = {
|
||||
.round_rate = dsi_pll_28nm_clk_round_rate,
|
||||
.set_rate = dsi_pll_28nm_clk_set_rate,
|
||||
.recalc_rate = dsi_pll_28nm_clk_recalc_rate,
|
||||
.prepare = dsi_pll_28nm_vco_prepare,
|
||||
.unprepare = dsi_pll_28nm_vco_unprepare,
|
||||
.is_enabled = dsi_pll_28nm_clk_is_enabled,
|
||||
};
|
||||
|
||||
/*
|
||||
* Custom byte clock divier clk_ops
|
||||
*
|
||||
* This clock is the entry point to configuring the PLL. The user (dsi host)
|
||||
* will set this clock's rate to the desired byte clock rate. The VCO lock
|
||||
* frequency is a multiple of the byte clock rate. The multiplication factor
|
||||
* (shown as F in the diagram above) is a function of the byte clock rate.
|
||||
*
|
||||
* This custom divider clock ensures that its parent (VCO) is set to the
|
||||
* desired rate, and that the byte clock postdivider (POSTDIV2) is configured
|
||||
* accordingly
|
||||
*/
|
||||
#define to_clk_bytediv(_hw) container_of(_hw, struct clk_bytediv, hw)
|
||||
|
||||
static unsigned long clk_bytediv_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct clk_bytediv *bytediv = to_clk_bytediv(hw);
|
||||
unsigned int div;
|
||||
|
||||
div = dsi_phy_read(bytediv->reg) & 0xff;
|
||||
|
||||
return parent_rate / (div + 1);
|
||||
}
|
||||
|
||||
/* find multiplication factor(wrt byte clock) at which the VCO should be set */
|
||||
static unsigned int get_vco_mul_factor(unsigned long byte_clk_rate)
|
||||
{
|
||||
unsigned long bit_mhz;
|
||||
|
||||
/* convert to bit clock in Mhz */
|
||||
bit_mhz = (byte_clk_rate * 8) / 1000000;
|
||||
|
||||
if (bit_mhz < 125)
|
||||
return 64;
|
||||
else if (bit_mhz < 250)
|
||||
return 32;
|
||||
else if (bit_mhz < 600)
|
||||
return 16;
|
||||
else
|
||||
return 8;
|
||||
}
|
||||
|
||||
static long clk_bytediv_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *prate)
|
||||
{
|
||||
unsigned long best_parent;
|
||||
unsigned int factor;
|
||||
|
||||
factor = get_vco_mul_factor(rate);
|
||||
|
||||
best_parent = rate * factor;
|
||||
*prate = clk_hw_round_rate(clk_hw_get_parent(hw), best_parent);
|
||||
|
||||
return *prate / factor;
|
||||
}
|
||||
|
||||
static int clk_bytediv_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct clk_bytediv *bytediv = to_clk_bytediv(hw);
|
||||
u32 val;
|
||||
unsigned int factor;
|
||||
|
||||
factor = get_vco_mul_factor(rate);
|
||||
|
||||
val = dsi_phy_read(bytediv->reg);
|
||||
val |= (factor - 1) & 0xff;
|
||||
dsi_phy_write(bytediv->reg, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Our special byte clock divider ops */
|
||||
static const struct clk_ops clk_bytediv_ops = {
|
||||
.round_rate = clk_bytediv_round_rate,
|
||||
.set_rate = clk_bytediv_set_rate,
|
||||
.recalc_rate = clk_bytediv_recalc_rate,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
static void dsi_28nm_pll_save_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(phy->vco_hw);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
|
||||
cached_state->postdiv3 =
|
||||
dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_10);
|
||||
cached_state->postdiv2 =
|
||||
dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_9);
|
||||
cached_state->postdiv1 =
|
||||
dsi_phy_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8);
|
||||
|
||||
cached_state->vco_rate = clk_hw_get_rate(phy->vco_hw);
|
||||
}
|
||||
|
||||
static int dsi_28nm_pll_restore_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(phy->vco_hw);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->phy->pll_base;
|
||||
int ret;
|
||||
|
||||
ret = dsi_pll_28nm_clk_set_rate(phy->vco_hw,
|
||||
cached_state->vco_rate, 0);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_28nm->phy->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_10,
|
||||
cached_state->postdiv3);
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_9,
|
||||
cached_state->postdiv2);
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8,
|
||||
cached_state->postdiv1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pll_28nm_register(struct dsi_pll_28nm *pll_28nm, struct clk_hw **provided_clocks)
|
||||
{
|
||||
char *clk_name, *parent_name, *vco_name;
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "pxo" },
|
||||
.num_parents = 1,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_28nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_28nm->phy->pdev->dev;
|
||||
struct clk_hw *hw;
|
||||
struct clk_bytediv *bytediv;
|
||||
struct clk_init_data bytediv_init = { };
|
||||
int ret;
|
||||
|
||||
DBG("%d", pll_28nm->phy->id);
|
||||
|
||||
bytediv = devm_kzalloc(dev, sizeof(*bytediv), GFP_KERNEL);
|
||||
if (!bytediv)
|
||||
return -ENOMEM;
|
||||
|
||||
vco_name = devm_kzalloc(dev, 32, GFP_KERNEL);
|
||||
if (!vco_name)
|
||||
return -ENOMEM;
|
||||
|
||||
clk_name = devm_kzalloc(dev, 32, GFP_KERNEL);
|
||||
if (!clk_name)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_28nm->phy->id);
|
||||
vco_init.name = vco_name;
|
||||
|
||||
pll_28nm->clk_hw.init = &vco_init;
|
||||
|
||||
ret = devm_clk_hw_register(dev, &pll_28nm->clk_hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* prepare and register bytediv */
|
||||
bytediv->hw.init = &bytediv_init;
|
||||
bytediv->reg = pll_28nm->phy->pll_base + REG_DSI_28nm_8960_PHY_PLL_CTRL_9;
|
||||
|
||||
snprintf(parent_name, 32, "dsi%dvco_clk", pll_28nm->phy->id);
|
||||
snprintf(clk_name, 32, "dsi%dpllbyte", pll_28nm->phy->id);
|
||||
|
||||
bytediv_init.name = clk_name;
|
||||
bytediv_init.ops = &clk_bytediv_ops;
|
||||
bytediv_init.flags = CLK_SET_RATE_PARENT;
|
||||
bytediv_init.parent_names = (const char * const *) &parent_name;
|
||||
bytediv_init.num_parents = 1;
|
||||
|
||||
/* DIV2 */
|
||||
ret = devm_clk_hw_register(dev, &bytediv->hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
provided_clocks[DSI_BYTE_PLL_CLK] = &bytediv->hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpll", pll_28nm->phy->id);
|
||||
/* DIV3 */
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name,
|
||||
parent_name, 0, pll_28nm->phy->pll_base +
|
||||
REG_DSI_28nm_8960_PHY_PLL_CTRL_10,
|
||||
0, 8, 0, NULL);
|
||||
if (IS_ERR(hw))
|
||||
return PTR_ERR(hw);
|
||||
provided_clocks[DSI_PIXEL_PLL_CLK] = hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_8960_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
struct dsi_pll_28nm *pll_28nm;
|
||||
int ret;
|
||||
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
pll_28nm = devm_kzalloc(&pdev->dev, sizeof(*pll_28nm), GFP_KERNEL);
|
||||
if (!pll_28nm)
|
||||
return -ENOMEM;
|
||||
|
||||
pll_28nm->phy = phy;
|
||||
|
||||
ret = pll_28nm_register(pll_28nm, phy->provided_clocks->hws);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
phy->vco_hw = &pll_28nm->clk_hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_28nm_dphy_set_timing(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_dphy_timing *timing)
|
||||
{
|
||||
@ -117,7 +585,7 @@ static void dsi_28nm_phy_lane_config(struct msm_dsi_phy *phy)
|
||||
dsi_phy_write(base + REG_DSI_28nm_8960_PHY_LNCK_TEST_STR1, 0x88);
|
||||
}
|
||||
|
||||
static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req)
|
||||
{
|
||||
struct msm_dsi_dphy_timing *timing = &phy->timing;
|
||||
@ -174,8 +642,7 @@ static void dsi_28nm_phy_disable(struct msm_dsi_phy *phy)
|
||||
}
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_28nm_8960_cfgs = {
|
||||
.type = MSM_DSI_PHY_28NM_8960,
|
||||
.src_pll_truthtable = { {true, true}, {false, true} },
|
||||
.has_phy_regulator = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -185,8 +652,12 @@ const struct msm_dsi_phy_cfg dsi_phy_28nm_8960_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_28nm_phy_enable,
|
||||
.disable = dsi_28nm_phy_disable,
|
||||
.init = msm_dsi_phy_init_common,
|
||||
.pll_init = dsi_pll_28nm_8960_init,
|
||||
.save_pll_state = dsi_28nm_pll_save_state,
|
||||
.restore_pll_state = dsi_28nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = VCO_MIN_RATE,
|
||||
.max_pll_rate = VCO_MAX_RATE,
|
||||
.io_start = { 0x4700300, 0x5800300 },
|
||||
.num_dsi_phy = 2,
|
||||
};
|
||||
|
@ -3,11 +3,743 @@
|
||||
* Copyright (c) 2018, The Linux Foundation
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "dsi_phy.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 7nm - clock diagram (eg: DSI0): TODO: updated CPHY diagram
|
||||
*
|
||||
* dsi0_pll_out_div_clk dsi0_pll_bit_clk
|
||||
* | |
|
||||
* | |
|
||||
* +---------+ | +----------+ | +----+
|
||||
* dsi0vco_clk ---| out_div |--o--| divl_3_0 |--o--| /8 |-- dsi0_phy_pll_out_byteclk
|
||||
* +---------+ | +----------+ | +----+
|
||||
* | |
|
||||
* | | dsi0_pll_by_2_bit_clk
|
||||
* | | |
|
||||
* | | +----+ | |\ dsi0_pclk_mux
|
||||
* | |--| /2 |--o--| \ |
|
||||
* | | +----+ | \ | +---------+
|
||||
* | --------------| |--o--| div_7_4 |-- dsi0_phy_pll_out_dsiclk
|
||||
* |------------------------------| / +---------+
|
||||
* | +-----+ | /
|
||||
* -----------| /4? |--o----------|/
|
||||
* +-----+ | |
|
||||
* | |dsiclk_sel
|
||||
* |
|
||||
* dsi0_pll_post_out_div_clk
|
||||
*/
|
||||
|
||||
#define VCO_REF_CLK_RATE 19200000
|
||||
#define FRAC_BITS 18
|
||||
|
||||
/* Hardware is V4.1 */
|
||||
#define DSI_PHY_7NM_QUIRK_V4_1 BIT(0)
|
||||
|
||||
struct dsi_pll_config {
|
||||
bool enable_ssc;
|
||||
bool ssc_center;
|
||||
u32 ssc_freq;
|
||||
u32 ssc_offset;
|
||||
u32 ssc_adj_per;
|
||||
|
||||
/* out */
|
||||
u32 decimal_div_start;
|
||||
u32 frac_div_start;
|
||||
u32 pll_clock_inverters;
|
||||
u32 ssc_stepsize;
|
||||
u32 ssc_div_per;
|
||||
};
|
||||
|
||||
struct pll_7nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 bit_clk_div;
|
||||
u8 pix_clk_div;
|
||||
u8 pll_out_div;
|
||||
u8 pll_mux;
|
||||
};
|
||||
|
||||
struct dsi_pll_7nm {
|
||||
struct clk_hw clk_hw;
|
||||
|
||||
struct msm_dsi_phy *phy;
|
||||
|
||||
u64 vco_current_rate;
|
||||
|
||||
/* protects REG_DSI_7nm_PHY_CMN_CLK_CFG0 register */
|
||||
spinlock_t postdiv_lock;
|
||||
|
||||
struct pll_7nm_cached_state cached_state;
|
||||
|
||||
struct dsi_pll_7nm *slave;
|
||||
};
|
||||
|
||||
#define to_pll_7nm(x) container_of(x, struct dsi_pll_7nm, clk_hw)
|
||||
|
||||
/*
|
||||
* Global list of private DSI PLL struct pointers. We need this for Dual DSI
|
||||
* mode, where the master PLL's clk_ops needs access the slave's private data
|
||||
*/
|
||||
static struct dsi_pll_7nm *pll_7nm_list[DSI_MAX];
|
||||
|
||||
static void dsi_pll_setup_config(struct dsi_pll_config *config)
|
||||
{
|
||||
config->ssc_freq = 31500;
|
||||
config->ssc_offset = 4800;
|
||||
config->ssc_adj_per = 2;
|
||||
|
||||
/* TODO: ssc enable */
|
||||
config->enable_ssc = false;
|
||||
config->ssc_center = 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_calc_dec_frac(struct dsi_pll_7nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
u64 fref = VCO_REF_CLK_RATE;
|
||||
u64 pll_freq;
|
||||
u64 divider;
|
||||
u64 dec, dec_multiple;
|
||||
u32 frac;
|
||||
u64 multiplier;
|
||||
|
||||
pll_freq = pll->vco_current_rate;
|
||||
|
||||
divider = fref * 2;
|
||||
|
||||
multiplier = 1 << FRAC_BITS;
|
||||
dec_multiple = div_u64(pll_freq * multiplier, divider);
|
||||
div_u64_rem(dec_multiple, multiplier, &frac);
|
||||
|
||||
dec = div_u64(dec_multiple, multiplier);
|
||||
|
||||
if (!(pll->phy->cfg->quirks & DSI_PHY_7NM_QUIRK_V4_1))
|
||||
config->pll_clock_inverters = 0x28;
|
||||
else if (pll_freq <= 1000000000ULL)
|
||||
config->pll_clock_inverters = 0xa0;
|
||||
else if (pll_freq <= 2500000000ULL)
|
||||
config->pll_clock_inverters = 0x20;
|
||||
else if (pll_freq <= 3020000000ULL)
|
||||
config->pll_clock_inverters = 0x00;
|
||||
else
|
||||
config->pll_clock_inverters = 0x40;
|
||||
|
||||
config->decimal_div_start = dec;
|
||||
config->frac_div_start = frac;
|
||||
}
|
||||
|
||||
#define SSC_CENTER BIT(0)
|
||||
#define SSC_EN BIT(1)
|
||||
|
||||
static void dsi_pll_calc_ssc(struct dsi_pll_7nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
u32 ssc_per;
|
||||
u32 ssc_mod;
|
||||
u64 ssc_step_size;
|
||||
u64 frac;
|
||||
|
||||
if (!config->enable_ssc) {
|
||||
DBG("SSC not enabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ssc_per = DIV_ROUND_CLOSEST(VCO_REF_CLK_RATE, config->ssc_freq) / 2 - 1;
|
||||
ssc_mod = (ssc_per + 1) % (config->ssc_adj_per + 1);
|
||||
ssc_per -= ssc_mod;
|
||||
|
||||
frac = config->frac_div_start;
|
||||
ssc_step_size = config->decimal_div_start;
|
||||
ssc_step_size *= (1 << FRAC_BITS);
|
||||
ssc_step_size += frac;
|
||||
ssc_step_size *= config->ssc_offset;
|
||||
ssc_step_size *= (config->ssc_adj_per + 1);
|
||||
ssc_step_size = div_u64(ssc_step_size, (ssc_per + 1));
|
||||
ssc_step_size = DIV_ROUND_CLOSEST_ULL(ssc_step_size, 1000000);
|
||||
|
||||
config->ssc_div_per = ssc_per;
|
||||
config->ssc_stepsize = ssc_step_size;
|
||||
|
||||
pr_debug("SCC: Dec:%d, frac:%llu, frac_bits:%d\n",
|
||||
config->decimal_div_start, frac, FRAC_BITS);
|
||||
pr_debug("SSC: div_per:0x%X, stepsize:0x%X, adjper:0x%X\n",
|
||||
ssc_per, (u32)ssc_step_size, config->ssc_adj_per);
|
||||
}
|
||||
|
||||
static void dsi_pll_ssc_commit(struct dsi_pll_7nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
|
||||
if (config->enable_ssc) {
|
||||
pr_debug("SSC is enabled\n");
|
||||
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_SSC_STEPSIZE_LOW_1,
|
||||
config->ssc_stepsize & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_SSC_STEPSIZE_HIGH_1,
|
||||
config->ssc_stepsize >> 8);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_SSC_DIV_PER_LOW_1,
|
||||
config->ssc_div_per & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_SSC_DIV_PER_HIGH_1,
|
||||
config->ssc_div_per >> 8);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_SSC_ADJPER_LOW_1,
|
||||
config->ssc_adj_per & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_SSC_ADJPER_HIGH_1,
|
||||
config->ssc_adj_per >> 8);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_SSC_CONTROL,
|
||||
SSC_EN | (config->ssc_center ? SSC_CENTER : 0));
|
||||
}
|
||||
}
|
||||
|
||||
static void dsi_pll_config_hzindep_reg(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
u8 analog_controls_five_1 = 0x01, vco_config_1 = 0x00;
|
||||
|
||||
if (pll->phy->cfg->quirks & DSI_PHY_7NM_QUIRK_V4_1) {
|
||||
if (pll->vco_current_rate >= 3100000000ULL)
|
||||
analog_controls_five_1 = 0x03;
|
||||
|
||||
if (pll->vco_current_rate < 1520000000ULL)
|
||||
vco_config_1 = 0x08;
|
||||
else if (pll->vco_current_rate < 2990000000ULL)
|
||||
vco_config_1 = 0x01;
|
||||
}
|
||||
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_FIVE_1,
|
||||
analog_controls_five_1);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_VCO_CONFIG_1, vco_config_1);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_FIVE, 0x01);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_TWO, 0x03);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_THREE, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_DSM_DIVIDER, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_FEEDBACK_DIVIDER, 0x4e);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_CALIBRATION_SETTINGS, 0x40);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_BAND_SEL_CAL_SETTINGS_THREE, 0xba);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_FREQ_DETECT_SETTINGS_ONE, 0x0c);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_OUTDIV, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_CORE_OVERRIDE, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_DIGITAL_TIMERS_TWO, 0x08);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_PROP_GAIN_RATE_1, 0x0a);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_BAND_SEL_RATE_1, 0xc0);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_INT_GAIN_IFILT_BAND_1, 0x84);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_INT_GAIN_IFILT_BAND_1, 0x82);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_FL_INT_GAIN_PFILT_BAND_1, 0x4c);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_LOCK_OVERRIDE, 0x80);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PFILT, 0x29);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PFILT, 0x2f);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_IFILT, 0x2a);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_IFILT,
|
||||
pll->phy->cfg->quirks & DSI_PHY_7NM_QUIRK_V4_1 ? 0x3f : 0x22);
|
||||
|
||||
if (pll->phy->cfg->quirks & DSI_PHY_7NM_QUIRK_V4_1) {
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PERF_OPTIMIZE, 0x22);
|
||||
if (pll->slave)
|
||||
dsi_phy_write(pll->slave->phy->pll_base + REG_DSI_7nm_PHY_PLL_PERF_OPTIMIZE, 0x22);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsi_pll_commit(struct dsi_pll_7nm *pll, struct dsi_pll_config *config)
|
||||
{
|
||||
void __iomem *base = pll->phy->pll_base;
|
||||
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_CORE_INPUT_OVERRIDE, 0x12);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_DECIMAL_DIV_START_1, config->decimal_div_start);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_LOW_1,
|
||||
config->frac_div_start & 0xff);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_MID_1,
|
||||
(config->frac_div_start & 0xff00) >> 8);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_HIGH_1,
|
||||
(config->frac_div_start & 0x30000) >> 16);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_LOCKDET_RATE_1, 0x40);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_PLL_LOCK_DELAY, 0x06);
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_CMODE_1, 0x10); /* TODO: 0x00 for CPHY */
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_PLL_CLOCK_INVERTERS, config->pll_clock_inverters);
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_vco_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(hw);
|
||||
struct dsi_pll_config config;
|
||||
|
||||
DBG("DSI PLL%d rate=%lu, parent's=%lu", pll_7nm->phy->id, rate,
|
||||
parent_rate);
|
||||
|
||||
pll_7nm->vco_current_rate = rate;
|
||||
|
||||
dsi_pll_setup_config(&config);
|
||||
|
||||
dsi_pll_calc_dec_frac(pll_7nm, &config);
|
||||
|
||||
dsi_pll_calc_ssc(pll_7nm, &config);
|
||||
|
||||
dsi_pll_commit(pll_7nm, &config);
|
||||
|
||||
dsi_pll_config_hzindep_reg(pll_7nm);
|
||||
|
||||
dsi_pll_ssc_commit(pll_7nm, &config);
|
||||
|
||||
/* flush, ensure all register writes are done*/
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_lock_status(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
int rc;
|
||||
u32 status = 0;
|
||||
u32 const delay_us = 100;
|
||||
u32 const timeout_us = 5000;
|
||||
|
||||
rc = readl_poll_timeout_atomic(pll->phy->pll_base +
|
||||
REG_DSI_7nm_PHY_PLL_COMMON_STATUS_ONE,
|
||||
status,
|
||||
((status & BIT(0)) > 0),
|
||||
delay_us,
|
||||
timeout_us);
|
||||
if (rc)
|
||||
pr_err("DSI PLL(%d) lock failed, status=0x%08x\n",
|
||||
pll->phy->id, status);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_pll_bias(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data = dsi_phy_read(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_0);
|
||||
|
||||
dsi_phy_write(pll->phy->pll_base + REG_DSI_7nm_PHY_PLL_SYSTEM_MUXES, 0);
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_0, data & ~BIT(5));
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_pll_bias(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data = dsi_phy_read(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_0);
|
||||
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_0, data | BIT(5));
|
||||
dsi_phy_write(pll->phy->pll_base + REG_DSI_7nm_PHY_PLL_SYSTEM_MUXES, 0xc0);
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_global_clk(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
data = dsi_phy_read(pll->phy->base + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_CLK_CFG1, data & ~BIT(5));
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_global_clk(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_CTRL_3, 0x04);
|
||||
|
||||
data = dsi_phy_read(pll->phy->base + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_CLK_CFG1,
|
||||
data | BIT(5) | BIT(4));
|
||||
}
|
||||
|
||||
static void dsi_pll_phy_dig_reset(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
/*
|
||||
* Reset the PHY digital domain. This would be needed when
|
||||
* coming out of a CX or analog rail power collapse while
|
||||
* ensuring that the pads maintain LP00 or LP11 state
|
||||
*/
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_GLBL_DIGTOP_SPARE4, BIT(0));
|
||||
wmb(); /* Ensure that the reset is deasserted */
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_GLBL_DIGTOP_SPARE4, 0x0);
|
||||
wmb(); /* Ensure that the reset is deasserted */
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_vco_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(hw);
|
||||
int rc;
|
||||
|
||||
dsi_pll_enable_pll_bias(pll_7nm);
|
||||
if (pll_7nm->slave)
|
||||
dsi_pll_enable_pll_bias(pll_7nm->slave);
|
||||
|
||||
/* Start PLL */
|
||||
dsi_phy_write(pll_7nm->phy->base + REG_DSI_7nm_PHY_CMN_PLL_CNTRL, 0x01);
|
||||
|
||||
/*
|
||||
* ensure all PLL configurations are written prior to checking
|
||||
* for PLL lock.
|
||||
*/
|
||||
wmb();
|
||||
|
||||
/* Check for PLL lock */
|
||||
rc = dsi_pll_7nm_lock_status(pll_7nm);
|
||||
if (rc) {
|
||||
pr_err("PLL(%d) lock failed\n", pll_7nm->phy->id);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pll_7nm->phy->pll_on = true;
|
||||
|
||||
/*
|
||||
* assert power on reset for PHY digital in case the PLL is
|
||||
* enabled after CX of analog domain power collapse. This needs
|
||||
* to be done before enabling the global clk.
|
||||
*/
|
||||
dsi_pll_phy_dig_reset(pll_7nm);
|
||||
if (pll_7nm->slave)
|
||||
dsi_pll_phy_dig_reset(pll_7nm->slave);
|
||||
|
||||
dsi_pll_enable_global_clk(pll_7nm);
|
||||
if (pll_7nm->slave)
|
||||
dsi_pll_enable_global_clk(pll_7nm->slave);
|
||||
|
||||
error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_sub(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
dsi_phy_write(pll->phy->base + REG_DSI_7nm_PHY_CMN_RBUF_CTRL, 0);
|
||||
dsi_pll_disable_pll_bias(pll);
|
||||
}
|
||||
|
||||
static void dsi_pll_7nm_vco_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(hw);
|
||||
|
||||
/*
|
||||
* To avoid any stray glitches while abruptly powering down the PLL
|
||||
* make sure to gate the clock using the clock enable bit before
|
||||
* powering down the PLL
|
||||
*/
|
||||
dsi_pll_disable_global_clk(pll_7nm);
|
||||
dsi_phy_write(pll_7nm->phy->base + REG_DSI_7nm_PHY_CMN_PLL_CNTRL, 0);
|
||||
dsi_pll_disable_sub(pll_7nm);
|
||||
if (pll_7nm->slave) {
|
||||
dsi_pll_disable_global_clk(pll_7nm->slave);
|
||||
dsi_pll_disable_sub(pll_7nm->slave);
|
||||
}
|
||||
/* flush, ensure all register writes are done */
|
||||
wmb();
|
||||
pll_7nm->phy->pll_on = false;
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_7nm_vco_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(hw);
|
||||
void __iomem *base = pll_7nm->phy->pll_base;
|
||||
u64 ref_clk = VCO_REF_CLK_RATE;
|
||||
u64 vco_rate = 0x0;
|
||||
u64 multiplier;
|
||||
u32 frac;
|
||||
u32 dec;
|
||||
u64 pll_freq, tmp64;
|
||||
|
||||
dec = dsi_phy_read(base + REG_DSI_7nm_PHY_PLL_DECIMAL_DIV_START_1);
|
||||
dec &= 0xff;
|
||||
|
||||
frac = dsi_phy_read(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_LOW_1);
|
||||
frac |= ((dsi_phy_read(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_MID_1) &
|
||||
0xff) << 8);
|
||||
frac |= ((dsi_phy_read(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_HIGH_1) &
|
||||
0x3) << 16);
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* 1. Assumes prescaler is disabled
|
||||
*/
|
||||
multiplier = 1 << FRAC_BITS;
|
||||
pll_freq = dec * (ref_clk * 2);
|
||||
tmp64 = (ref_clk * 2 * frac);
|
||||
pll_freq += div_u64(tmp64, multiplier);
|
||||
|
||||
vco_rate = pll_freq;
|
||||
|
||||
DBG("DSI PLL%d returning vco rate = %lu, dec = %x, frac = %x",
|
||||
pll_7nm->phy->id, (unsigned long)vco_rate, dec, frac);
|
||||
|
||||
return (unsigned long)vco_rate;
|
||||
}
|
||||
|
||||
static long dsi_pll_7nm_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(hw);
|
||||
|
||||
if (rate < pll_7nm->phy->cfg->min_pll_rate)
|
||||
return pll_7nm->phy->cfg->min_pll_rate;
|
||||
else if (rate > pll_7nm->phy->cfg->max_pll_rate)
|
||||
return pll_7nm->phy->cfg->max_pll_rate;
|
||||
else
|
||||
return rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_7nm_vco = {
|
||||
.round_rate = dsi_pll_7nm_clk_round_rate,
|
||||
.set_rate = dsi_pll_7nm_vco_set_rate,
|
||||
.recalc_rate = dsi_pll_7nm_vco_recalc_rate,
|
||||
.prepare = dsi_pll_7nm_vco_prepare,
|
||||
.unprepare = dsi_pll_7nm_vco_unprepare,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
|
||||
static void dsi_7nm_pll_save_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(phy->vco_hw);
|
||||
struct pll_7nm_cached_state *cached = &pll_7nm->cached_state;
|
||||
void __iomem *phy_base = pll_7nm->phy->base;
|
||||
u32 cmn_clk_cfg0, cmn_clk_cfg1;
|
||||
|
||||
cached->pll_out_div = dsi_phy_read(pll_7nm->phy->pll_base +
|
||||
REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
cached->pll_out_div &= 0x3;
|
||||
|
||||
cmn_clk_cfg0 = dsi_phy_read(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG0);
|
||||
cached->bit_clk_div = cmn_clk_cfg0 & 0xf;
|
||||
cached->pix_clk_div = (cmn_clk_cfg0 & 0xf0) >> 4;
|
||||
|
||||
cmn_clk_cfg1 = dsi_phy_read(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
cached->pll_mux = cmn_clk_cfg1 & 0x3;
|
||||
|
||||
DBG("DSI PLL%d outdiv %x bit_clk_div %x pix_clk_div %x pll_mux %x",
|
||||
pll_7nm->phy->id, cached->pll_out_div, cached->bit_clk_div,
|
||||
cached->pix_clk_div, cached->pll_mux);
|
||||
}
|
||||
|
||||
static int dsi_7nm_pll_restore_state(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(phy->vco_hw);
|
||||
struct pll_7nm_cached_state *cached = &pll_7nm->cached_state;
|
||||
void __iomem *phy_base = pll_7nm->phy->base;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = dsi_phy_read(pll_7nm->phy->pll_base + REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_out_div;
|
||||
dsi_phy_write(pll_7nm->phy->pll_base + REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE, val);
|
||||
|
||||
dsi_phy_write(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG0,
|
||||
cached->bit_clk_div | (cached->pix_clk_div << 4));
|
||||
|
||||
val = dsi_phy_read(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_mux;
|
||||
dsi_phy_write(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG1, val);
|
||||
|
||||
ret = dsi_pll_7nm_vco_set_rate(phy->vco_hw,
|
||||
pll_7nm->vco_current_rate,
|
||||
VCO_REF_CLK_RATE);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_7nm->phy->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DBG("DSI PLL%d", pll_7nm->phy->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_7nm_set_usecase(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(phy->vco_hw);
|
||||
void __iomem *base = phy->base;
|
||||
u32 data = 0x0; /* internal PLL */
|
||||
|
||||
DBG("DSI PLL%d", pll_7nm->phy->id);
|
||||
|
||||
switch (phy->usecase) {
|
||||
case MSM_DSI_PHY_STANDALONE:
|
||||
break;
|
||||
case MSM_DSI_PHY_MASTER:
|
||||
pll_7nm->slave = pll_7nm_list[(pll_7nm->phy->id + 1) % DSI_MAX];
|
||||
break;
|
||||
case MSM_DSI_PHY_SLAVE:
|
||||
data = 0x1; /* external PLL */
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set PLL src */
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_CMN_CLK_CFG1, (data << 2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The post dividers and mux clocks are created using the standard divider and
|
||||
* mux API. Unlike the 14nm PHY, the slave PLL doesn't need its dividers/mux
|
||||
* state to follow the master PLL's divider/mux state. Therefore, we don't
|
||||
* require special clock ops that also configure the slave PLL registers
|
||||
*/
|
||||
static int pll_7nm_register(struct dsi_pll_7nm *pll_7nm, struct clk_hw **provided_clocks)
|
||||
{
|
||||
char clk_name[32], parent[32], vco_name[32];
|
||||
char parent2[32], parent3[32], parent4[32];
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "bi_tcxo" },
|
||||
.num_parents = 1,
|
||||
.name = vco_name,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_7nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_7nm->phy->pdev->dev;
|
||||
struct clk_hw *hw;
|
||||
int ret;
|
||||
|
||||
DBG("DSI%d", pll_7nm->phy->id);
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_7nm->phy->id);
|
||||
pll_7nm->clk_hw.init = &vco_init;
|
||||
|
||||
ret = devm_clk_hw_register(dev, &pll_7nm->clk_hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_out_div_clk", pll_7nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%dvco_clk", pll_7nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name,
|
||||
parent, CLK_SET_RATE_PARENT,
|
||||
pll_7nm->phy->pll_base +
|
||||
REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE,
|
||||
0, 2, CLK_DIVIDER_POWER_OF_TWO, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_bit_clk", pll_7nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_7nm->phy->id);
|
||||
|
||||
/* BIT CLK: DIV_CTRL_3_0 */
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT,
|
||||
pll_7nm->phy->base +
|
||||
REG_DSI_7nm_PHY_CMN_CLK_CFG0,
|
||||
0, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_7nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_byteclk", pll_7nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_7nm->phy->id);
|
||||
|
||||
/* DSI Byte clock = VCO_CLK / OUT_DIV / BIT_DIV / 8 */
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT, 1, 8);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
provided_clocks[DSI_BYTE_PLL_CLK] = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_by_2_bit_clk", pll_7nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_7nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 2);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_post_out_div_clk", pll_7nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_7nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 4);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pclk_mux", pll_7nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_7nm->phy->id);
|
||||
snprintf(parent2, 32, "dsi%d_pll_by_2_bit_clk", pll_7nm->phy->id);
|
||||
snprintf(parent3, 32, "dsi%d_pll_out_div_clk", pll_7nm->phy->id);
|
||||
snprintf(parent4, 32, "dsi%d_pll_post_out_div_clk", pll_7nm->phy->id);
|
||||
|
||||
hw = devm_clk_hw_register_mux(dev, clk_name,
|
||||
((const char *[]){
|
||||
parent, parent2, parent3, parent4
|
||||
}), 4, 0, pll_7nm->phy->base +
|
||||
REG_DSI_7nm_PHY_CMN_CLK_CFG1,
|
||||
0, 2, 0, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_dsiclk", pll_7nm->phy->id);
|
||||
snprintf(parent, 32, "dsi%d_pclk_mux", pll_7nm->phy->id);
|
||||
|
||||
/* PIX CLK DIV : DIV_CTRL_7_4*/
|
||||
hw = devm_clk_hw_register_divider(dev, clk_name, parent,
|
||||
0, pll_7nm->phy->base +
|
||||
REG_DSI_7nm_PHY_CMN_CLK_CFG0,
|
||||
4, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_7nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
provided_clocks[DSI_PIXEL_PLL_CLK] = hw;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
struct dsi_pll_7nm *pll_7nm;
|
||||
int ret;
|
||||
|
||||
pll_7nm = devm_kzalloc(&pdev->dev, sizeof(*pll_7nm), GFP_KERNEL);
|
||||
if (!pll_7nm)
|
||||
return -ENOMEM;
|
||||
|
||||
DBG("DSI PLL%d", phy->id);
|
||||
|
||||
pll_7nm_list[phy->id] = pll_7nm;
|
||||
|
||||
spin_lock_init(&pll_7nm->postdiv_lock);
|
||||
|
||||
pll_7nm->phy = phy;
|
||||
|
||||
ret = pll_7nm_register(pll_7nm, phy->provided_clocks->hws);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
phy->vco_hw = &pll_7nm->clk_hw;
|
||||
|
||||
/* TODO: Remove this when we have proper display handover support */
|
||||
msm_dsi_phy_pll_save_state(phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_phy_hw_v4_0_is_pll_on(struct msm_dsi_phy *phy)
|
||||
{
|
||||
void __iomem *base = phy->base;
|
||||
@ -44,7 +776,7 @@ static void dsi_phy_hw_v4_0_lane_settings(struct msm_dsi_phy *phy)
|
||||
const u8 *tx_dctrl = tx_dctrl_0;
|
||||
void __iomem *lane_base = phy->lane_base;
|
||||
|
||||
if (phy->cfg->type == MSM_DSI_PHY_7NM_V4_1)
|
||||
if (phy->cfg->quirks & DSI_PHY_7NM_QUIRK_V4_1)
|
||||
tx_dctrl = tx_dctrl_1;
|
||||
|
||||
/* Strength ctrl settings */
|
||||
@ -69,7 +801,7 @@ static void dsi_phy_hw_v4_0_lane_settings(struct msm_dsi_phy *phy)
|
||||
}
|
||||
}
|
||||
|
||||
static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy,
|
||||
struct msm_dsi_phy_clk_request *clk_req)
|
||||
{
|
||||
int ret;
|
||||
@ -108,7 +840,7 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
/* Alter PHY configurations if data rate less than 1.5GHZ*/
|
||||
less_than_1500_mhz = (clk_req->bitclk_rate <= 1500000000);
|
||||
|
||||
if (phy->cfg->type == MSM_DSI_PHY_7NM_V4_1) {
|
||||
if (phy->cfg->quirks & DSI_PHY_7NM_QUIRK_V4_1) {
|
||||
vreg_ctrl_0 = less_than_1500_mhz ? 0x53 : 0x52;
|
||||
glbl_rescode_top_ctrl = less_than_1500_mhz ? 0x3d : 0x00;
|
||||
glbl_rescode_bot_ctrl = less_than_1500_mhz ? 0x39 : 0x3c;
|
||||
@ -165,7 +897,7 @@ static int dsi_7nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id,
|
||||
/* Select full-rate mode */
|
||||
dsi_phy_write(base + REG_DSI_7nm_PHY_CMN_CTRL_2, 0x40);
|
||||
|
||||
ret = msm_dsi_pll_set_usecase(phy->pll, phy->usecase);
|
||||
ret = dsi_7nm_set_usecase(phy);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&phy->pdev->dev, "%s: set pll usecase failed, %d\n",
|
||||
__func__, ret);
|
||||
@ -224,24 +956,8 @@ static void dsi_7nm_phy_disable(struct msm_dsi_phy *phy)
|
||||
DBG("DSI%d PHY disabled", phy->id);
|
||||
}
|
||||
|
||||
static int dsi_7nm_phy_init(struct msm_dsi_phy *phy)
|
||||
{
|
||||
struct platform_device *pdev = phy->pdev;
|
||||
|
||||
phy->lane_base = msm_ioremap(pdev, "dsi_phy_lane",
|
||||
"DSI_PHY_LANE");
|
||||
if (IS_ERR(phy->lane_base)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map phy lane base\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_7nm_cfgs = {
|
||||
.type = MSM_DSI_PHY_7NM_V4_1,
|
||||
.src_pll_truthtable = { {false, false}, {true, false} },
|
||||
.has_phy_lane = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -251,15 +967,19 @@ const struct msm_dsi_phy_cfg dsi_phy_7nm_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_7nm_phy_enable,
|
||||
.disable = dsi_7nm_phy_disable,
|
||||
.init = dsi_7nm_phy_init,
|
||||
.pll_init = dsi_pll_7nm_init,
|
||||
.save_pll_state = dsi_7nm_pll_save_state,
|
||||
.restore_pll_state = dsi_7nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = 600000000UL,
|
||||
.max_pll_rate = (5000000000ULL < ULONG_MAX) ? 5000000000ULL : ULONG_MAX,
|
||||
.io_start = { 0xae94400, 0xae96400 },
|
||||
.num_dsi_phy = 2,
|
||||
.quirks = DSI_PHY_7NM_QUIRK_V4_1,
|
||||
};
|
||||
|
||||
const struct msm_dsi_phy_cfg dsi_phy_7nm_8150_cfgs = {
|
||||
.type = MSM_DSI_PHY_7NM,
|
||||
.src_pll_truthtable = { {false, false}, {true, false} },
|
||||
.has_phy_lane = true,
|
||||
.reg_cfg = {
|
||||
.num = 1,
|
||||
.regs = {
|
||||
@ -269,8 +989,12 @@ const struct msm_dsi_phy_cfg dsi_phy_7nm_8150_cfgs = {
|
||||
.ops = {
|
||||
.enable = dsi_7nm_phy_enable,
|
||||
.disable = dsi_7nm_phy_disable,
|
||||
.init = dsi_7nm_phy_init,
|
||||
.pll_init = dsi_pll_7nm_init,
|
||||
.save_pll_state = dsi_7nm_pll_save_state,
|
||||
.restore_pll_state = dsi_7nm_pll_restore_state,
|
||||
},
|
||||
.min_pll_rate = 1000000000UL,
|
||||
.max_pll_rate = 3500000000UL,
|
||||
.io_start = { 0xae94400, 0xae96400 },
|
||||
.num_dsi_phy = 2,
|
||||
};
|
||||
|
@ -1,184 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "dsi_pll.h"
|
||||
|
||||
static int dsi_pll_enable(struct msm_dsi_pll *pll)
|
||||
{
|
||||
int i, ret = 0;
|
||||
|
||||
/*
|
||||
* Certain PLLs do not allow VCO rate update when it is on.
|
||||
* Keep track of their status to turn on/off after set rate success.
|
||||
*/
|
||||
if (unlikely(pll->pll_on))
|
||||
return 0;
|
||||
|
||||
/* Try all enable sequences until one succeeds */
|
||||
for (i = 0; i < pll->en_seq_cnt; i++) {
|
||||
ret = pll->enable_seqs[i](pll);
|
||||
DBG("DSI PLL %s after sequence #%d",
|
||||
ret ? "unlocked" : "locked", i + 1);
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
DRM_ERROR("DSI PLL failed to lock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pll->pll_on = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable(struct msm_dsi_pll *pll)
|
||||
{
|
||||
if (unlikely(!pll->pll_on))
|
||||
return;
|
||||
|
||||
pll->disable_seq(pll);
|
||||
|
||||
pll->pll_on = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* DSI PLL Helper functions
|
||||
*/
|
||||
long msm_dsi_pll_helper_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
|
||||
if (rate < pll->min_rate)
|
||||
return pll->min_rate;
|
||||
else if (rate > pll->max_rate)
|
||||
return pll->max_rate;
|
||||
else
|
||||
return rate;
|
||||
}
|
||||
|
||||
int msm_dsi_pll_helper_clk_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
|
||||
return dsi_pll_enable(pll);
|
||||
}
|
||||
|
||||
void msm_dsi_pll_helper_clk_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
|
||||
dsi_pll_disable(pll);
|
||||
}
|
||||
|
||||
void msm_dsi_pll_helper_unregister_clks(struct platform_device *pdev,
|
||||
struct clk **clks, u32 num_clks)
|
||||
{
|
||||
of_clk_del_provider(pdev->dev.of_node);
|
||||
|
||||
if (!num_clks || !clks)
|
||||
return;
|
||||
|
||||
do {
|
||||
clk_unregister(clks[--num_clks]);
|
||||
clks[num_clks] = NULL;
|
||||
} while (num_clks);
|
||||
}
|
||||
|
||||
/*
|
||||
* DSI PLL API
|
||||
*/
|
||||
int msm_dsi_pll_get_clk_provider(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider, struct clk **pixel_clk_provider)
|
||||
{
|
||||
if (pll->get_provider)
|
||||
return pll->get_provider(pll,
|
||||
byte_clk_provider,
|
||||
pixel_clk_provider);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void msm_dsi_pll_destroy(struct msm_dsi_pll *pll)
|
||||
{
|
||||
if (pll->destroy)
|
||||
pll->destroy(pll);
|
||||
}
|
||||
|
||||
void msm_dsi_pll_save_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
if (pll->save_state) {
|
||||
pll->save_state(pll);
|
||||
pll->state_saved = true;
|
||||
}
|
||||
}
|
||||
|
||||
int msm_dsi_pll_restore_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (pll->restore_state && pll->state_saved) {
|
||||
ret = pll->restore_state(pll);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pll->state_saved = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_dsi_pll_set_usecase(struct msm_dsi_pll *pll,
|
||||
enum msm_dsi_phy_usecase uc)
|
||||
{
|
||||
if (pll->set_usecase)
|
||||
return pll->set_usecase(pll, uc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct msm_dsi_pll *msm_dsi_pll_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct msm_dsi_pll *pll;
|
||||
|
||||
switch (type) {
|
||||
case MSM_DSI_PHY_28NM_HPM:
|
||||
case MSM_DSI_PHY_28NM_LP:
|
||||
pll = msm_dsi_pll_28nm_init(pdev, type, id);
|
||||
break;
|
||||
case MSM_DSI_PHY_28NM_8960:
|
||||
pll = msm_dsi_pll_28nm_8960_init(pdev, id);
|
||||
break;
|
||||
case MSM_DSI_PHY_14NM:
|
||||
pll = msm_dsi_pll_14nm_init(pdev, id);
|
||||
break;
|
||||
case MSM_DSI_PHY_10NM:
|
||||
pll = msm_dsi_pll_10nm_init(pdev, id);
|
||||
break;
|
||||
case MSM_DSI_PHY_7NM:
|
||||
case MSM_DSI_PHY_7NM_V4_1:
|
||||
pll = msm_dsi_pll_7nm_init(pdev, type, id);
|
||||
break;
|
||||
default:
|
||||
pll = ERR_PTR(-ENXIO);
|
||||
break;
|
||||
}
|
||||
|
||||
if (IS_ERR(pll)) {
|
||||
DRM_DEV_ERROR(dev, "%s: failed to init DSI PLL\n", __func__);
|
||||
return pll;
|
||||
}
|
||||
|
||||
pll->type = type;
|
||||
|
||||
DBG("DSI:%d PLL registered", id);
|
||||
|
||||
return pll;
|
||||
}
|
||||
|
@ -1,132 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __DSI_PLL_H__
|
||||
#define __DSI_PLL_H__
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "dsi.h"
|
||||
|
||||
#define NUM_DSI_CLOCKS_MAX 6
|
||||
#define MAX_DSI_PLL_EN_SEQS 10
|
||||
|
||||
struct msm_dsi_pll {
|
||||
enum msm_dsi_phy_type type;
|
||||
|
||||
struct clk_hw clk_hw;
|
||||
bool pll_on;
|
||||
bool state_saved;
|
||||
|
||||
unsigned long min_rate;
|
||||
unsigned long max_rate;
|
||||
u32 en_seq_cnt;
|
||||
|
||||
int (*enable_seqs[MAX_DSI_PLL_EN_SEQS])(struct msm_dsi_pll *pll);
|
||||
void (*disable_seq)(struct msm_dsi_pll *pll);
|
||||
int (*get_provider)(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider,
|
||||
struct clk **pixel_clk_provider);
|
||||
void (*destroy)(struct msm_dsi_pll *pll);
|
||||
void (*save_state)(struct msm_dsi_pll *pll);
|
||||
int (*restore_state)(struct msm_dsi_pll *pll);
|
||||
int (*set_usecase)(struct msm_dsi_pll *pll,
|
||||
enum msm_dsi_phy_usecase uc);
|
||||
};
|
||||
|
||||
#define hw_clk_to_pll(x) container_of(x, struct msm_dsi_pll, clk_hw)
|
||||
|
||||
static inline void pll_write(void __iomem *reg, u32 data)
|
||||
{
|
||||
msm_writel(data, reg);
|
||||
}
|
||||
|
||||
static inline u32 pll_read(const void __iomem *reg)
|
||||
{
|
||||
return msm_readl(reg);
|
||||
}
|
||||
|
||||
static inline void pll_write_udelay(void __iomem *reg, u32 data, u32 delay_us)
|
||||
{
|
||||
pll_write(reg, data);
|
||||
udelay(delay_us);
|
||||
}
|
||||
|
||||
static inline void pll_write_ndelay(void __iomem *reg, u32 data, u32 delay_ns)
|
||||
{
|
||||
pll_write((reg), data);
|
||||
ndelay(delay_ns);
|
||||
}
|
||||
|
||||
/*
|
||||
* DSI PLL Helper functions
|
||||
*/
|
||||
|
||||
/* clock callbacks */
|
||||
long msm_dsi_pll_helper_clk_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate);
|
||||
int msm_dsi_pll_helper_clk_prepare(struct clk_hw *hw);
|
||||
void msm_dsi_pll_helper_clk_unprepare(struct clk_hw *hw);
|
||||
/* misc */
|
||||
void msm_dsi_pll_helper_unregister_clks(struct platform_device *pdev,
|
||||
struct clk **clks, u32 num_clks);
|
||||
|
||||
/*
|
||||
* Initialization for Each PLL Type
|
||||
*/
|
||||
#ifdef CONFIG_DRM_MSM_DSI_28NM_PHY
|
||||
struct msm_dsi_pll *msm_dsi_pll_28nm_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id);
|
||||
#else
|
||||
static inline struct msm_dsi_pll *msm_dsi_pll_28nm_init(
|
||||
struct platform_device *pdev, enum msm_dsi_phy_type type, int id)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_DRM_MSM_DSI_28NM_8960_PHY
|
||||
struct msm_dsi_pll *msm_dsi_pll_28nm_8960_init(struct platform_device *pdev,
|
||||
int id);
|
||||
#else
|
||||
static inline struct msm_dsi_pll *msm_dsi_pll_28nm_8960_init(
|
||||
struct platform_device *pdev, int id)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_MSM_DSI_14NM_PHY
|
||||
struct msm_dsi_pll *msm_dsi_pll_14nm_init(struct platform_device *pdev, int id);
|
||||
#else
|
||||
static inline struct msm_dsi_pll *
|
||||
msm_dsi_pll_14nm_init(struct platform_device *pdev, int id)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_DRM_MSM_DSI_10NM_PHY
|
||||
struct msm_dsi_pll *msm_dsi_pll_10nm_init(struct platform_device *pdev, int id);
|
||||
#else
|
||||
static inline struct msm_dsi_pll *
|
||||
msm_dsi_pll_10nm_init(struct platform_device *pdev, int id)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_DRM_MSM_DSI_7NM_PHY
|
||||
struct msm_dsi_pll *msm_dsi_pll_7nm_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id);
|
||||
#else
|
||||
static inline struct msm_dsi_pll *
|
||||
msm_dsi_pll_7nm_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __DSI_PLL_H__ */
|
||||
|
@ -1,881 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
* Copyright (c) 2018, The Linux Foundation
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "dsi_pll.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 10nm - clock diagram (eg: DSI0):
|
||||
*
|
||||
* dsi0_pll_out_div_clk dsi0_pll_bit_clk
|
||||
* | |
|
||||
* | |
|
||||
* +---------+ | +----------+ | +----+
|
||||
* dsi0vco_clk ---| out_div |--o--| divl_3_0 |--o--| /8 |-- dsi0_phy_pll_out_byteclk
|
||||
* +---------+ | +----------+ | +----+
|
||||
* | |
|
||||
* | | dsi0_pll_by_2_bit_clk
|
||||
* | | |
|
||||
* | | +----+ | |\ dsi0_pclk_mux
|
||||
* | |--| /2 |--o--| \ |
|
||||
* | | +----+ | \ | +---------+
|
||||
* | --------------| |--o--| div_7_4 |-- dsi0_phy_pll_out_dsiclk
|
||||
* |------------------------------| / +---------+
|
||||
* | +-----+ | /
|
||||
* -----------| /4? |--o----------|/
|
||||
* +-----+ | |
|
||||
* | |dsiclk_sel
|
||||
* |
|
||||
* dsi0_pll_post_out_div_clk
|
||||
*/
|
||||
|
||||
#define DSI_BYTE_PLL_CLK 0
|
||||
#define DSI_PIXEL_PLL_CLK 1
|
||||
#define NUM_PROVIDED_CLKS 2
|
||||
|
||||
#define VCO_REF_CLK_RATE 19200000
|
||||
|
||||
struct dsi_pll_regs {
|
||||
u32 pll_prop_gain_rate;
|
||||
u32 pll_lockdet_rate;
|
||||
u32 decimal_div_start;
|
||||
u32 frac_div_start_low;
|
||||
u32 frac_div_start_mid;
|
||||
u32 frac_div_start_high;
|
||||
u32 pll_clock_inverters;
|
||||
u32 ssc_stepsize_low;
|
||||
u32 ssc_stepsize_high;
|
||||
u32 ssc_div_per_low;
|
||||
u32 ssc_div_per_high;
|
||||
u32 ssc_adjper_low;
|
||||
u32 ssc_adjper_high;
|
||||
u32 ssc_control;
|
||||
};
|
||||
|
||||
struct dsi_pll_config {
|
||||
u32 ref_freq;
|
||||
bool div_override;
|
||||
u32 output_div;
|
||||
bool ignore_frac;
|
||||
bool disable_prescaler;
|
||||
bool enable_ssc;
|
||||
bool ssc_center;
|
||||
u32 dec_bits;
|
||||
u32 frac_bits;
|
||||
u32 lock_timer;
|
||||
u32 ssc_freq;
|
||||
u32 ssc_offset;
|
||||
u32 ssc_adj_per;
|
||||
u32 thresh_cycles;
|
||||
u32 refclk_cycles;
|
||||
};
|
||||
|
||||
struct pll_10nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 bit_clk_div;
|
||||
u8 pix_clk_div;
|
||||
u8 pll_out_div;
|
||||
u8 pll_mux;
|
||||
};
|
||||
|
||||
struct dsi_pll_10nm {
|
||||
struct msm_dsi_pll base;
|
||||
|
||||
int id;
|
||||
struct platform_device *pdev;
|
||||
|
||||
void __iomem *phy_cmn_mmio;
|
||||
void __iomem *mmio;
|
||||
|
||||
u64 vco_ref_clk_rate;
|
||||
u64 vco_current_rate;
|
||||
|
||||
/* protects REG_DSI_10nm_PHY_CMN_CLK_CFG0 register */
|
||||
spinlock_t postdiv_lock;
|
||||
|
||||
int vco_delay;
|
||||
struct dsi_pll_config pll_configuration;
|
||||
struct dsi_pll_regs reg_setup;
|
||||
|
||||
/* private clocks: */
|
||||
struct clk_hw *out_div_clk_hw;
|
||||
struct clk_hw *bit_clk_hw;
|
||||
struct clk_hw *byte_clk_hw;
|
||||
struct clk_hw *by_2_bit_clk_hw;
|
||||
struct clk_hw *post_out_div_clk_hw;
|
||||
struct clk_hw *pclk_mux_hw;
|
||||
struct clk_hw *out_dsiclk_hw;
|
||||
|
||||
/* clock-provider: */
|
||||
struct clk_hw_onecell_data *hw_data;
|
||||
|
||||
struct pll_10nm_cached_state cached_state;
|
||||
|
||||
enum msm_dsi_phy_usecase uc;
|
||||
struct dsi_pll_10nm *slave;
|
||||
};
|
||||
|
||||
#define to_pll_10nm(x) container_of(x, struct dsi_pll_10nm, base)
|
||||
|
||||
/*
|
||||
* Global list of private DSI PLL struct pointers. We need this for Dual DSI
|
||||
* mode, where the master PLL's clk_ops needs access the slave's private data
|
||||
*/
|
||||
static struct dsi_pll_10nm *pll_10nm_list[DSI_MAX];
|
||||
|
||||
static void dsi_pll_setup_config(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
struct dsi_pll_config *config = &pll->pll_configuration;
|
||||
|
||||
config->ref_freq = pll->vco_ref_clk_rate;
|
||||
config->output_div = 1;
|
||||
config->dec_bits = 8;
|
||||
config->frac_bits = 18;
|
||||
config->lock_timer = 64;
|
||||
config->ssc_freq = 31500;
|
||||
config->ssc_offset = 5000;
|
||||
config->ssc_adj_per = 2;
|
||||
config->thresh_cycles = 32;
|
||||
config->refclk_cycles = 256;
|
||||
|
||||
config->div_override = false;
|
||||
config->ignore_frac = false;
|
||||
config->disable_prescaler = false;
|
||||
|
||||
config->enable_ssc = false;
|
||||
config->ssc_center = 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_calc_dec_frac(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
struct dsi_pll_config *config = &pll->pll_configuration;
|
||||
struct dsi_pll_regs *regs = &pll->reg_setup;
|
||||
u64 fref = pll->vco_ref_clk_rate;
|
||||
u64 pll_freq;
|
||||
u64 divider;
|
||||
u64 dec, dec_multiple;
|
||||
u32 frac;
|
||||
u64 multiplier;
|
||||
|
||||
pll_freq = pll->vco_current_rate;
|
||||
|
||||
if (config->disable_prescaler)
|
||||
divider = fref;
|
||||
else
|
||||
divider = fref * 2;
|
||||
|
||||
multiplier = 1 << config->frac_bits;
|
||||
dec_multiple = div_u64(pll_freq * multiplier, divider);
|
||||
dec = div_u64_rem(dec_multiple, multiplier, &frac);
|
||||
|
||||
if (pll_freq <= 1900000000UL)
|
||||
regs->pll_prop_gain_rate = 8;
|
||||
else if (pll_freq <= 3000000000UL)
|
||||
regs->pll_prop_gain_rate = 10;
|
||||
else
|
||||
regs->pll_prop_gain_rate = 12;
|
||||
if (pll_freq < 1100000000UL)
|
||||
regs->pll_clock_inverters = 8;
|
||||
else
|
||||
regs->pll_clock_inverters = 0;
|
||||
|
||||
regs->pll_lockdet_rate = config->lock_timer;
|
||||
regs->decimal_div_start = dec;
|
||||
regs->frac_div_start_low = (frac & 0xff);
|
||||
regs->frac_div_start_mid = (frac & 0xff00) >> 8;
|
||||
regs->frac_div_start_high = (frac & 0x30000) >> 16;
|
||||
}
|
||||
|
||||
#define SSC_CENTER BIT(0)
|
||||
#define SSC_EN BIT(1)
|
||||
|
||||
static void dsi_pll_calc_ssc(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
struct dsi_pll_config *config = &pll->pll_configuration;
|
||||
struct dsi_pll_regs *regs = &pll->reg_setup;
|
||||
u32 ssc_per;
|
||||
u32 ssc_mod;
|
||||
u64 ssc_step_size;
|
||||
u64 frac;
|
||||
|
||||
if (!config->enable_ssc) {
|
||||
DBG("SSC not enabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ssc_per = DIV_ROUND_CLOSEST(config->ref_freq, config->ssc_freq) / 2 - 1;
|
||||
ssc_mod = (ssc_per + 1) % (config->ssc_adj_per + 1);
|
||||
ssc_per -= ssc_mod;
|
||||
|
||||
frac = regs->frac_div_start_low |
|
||||
(regs->frac_div_start_mid << 8) |
|
||||
(regs->frac_div_start_high << 16);
|
||||
ssc_step_size = regs->decimal_div_start;
|
||||
ssc_step_size *= (1 << config->frac_bits);
|
||||
ssc_step_size += frac;
|
||||
ssc_step_size *= config->ssc_offset;
|
||||
ssc_step_size *= (config->ssc_adj_per + 1);
|
||||
ssc_step_size = div_u64(ssc_step_size, (ssc_per + 1));
|
||||
ssc_step_size = DIV_ROUND_CLOSEST_ULL(ssc_step_size, 1000000);
|
||||
|
||||
regs->ssc_div_per_low = ssc_per & 0xFF;
|
||||
regs->ssc_div_per_high = (ssc_per & 0xFF00) >> 8;
|
||||
regs->ssc_stepsize_low = (u32)(ssc_step_size & 0xFF);
|
||||
regs->ssc_stepsize_high = (u32)((ssc_step_size & 0xFF00) >> 8);
|
||||
regs->ssc_adjper_low = config->ssc_adj_per & 0xFF;
|
||||
regs->ssc_adjper_high = (config->ssc_adj_per & 0xFF00) >> 8;
|
||||
|
||||
regs->ssc_control = config->ssc_center ? SSC_CENTER : 0;
|
||||
|
||||
pr_debug("SCC: Dec:%d, frac:%llu, frac_bits:%d\n",
|
||||
regs->decimal_div_start, frac, config->frac_bits);
|
||||
pr_debug("SSC: div_per:0x%X, stepsize:0x%X, adjper:0x%X\n",
|
||||
ssc_per, (u32)ssc_step_size, config->ssc_adj_per);
|
||||
}
|
||||
|
||||
static void dsi_pll_ssc_commit(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->mmio;
|
||||
struct dsi_pll_regs *regs = &pll->reg_setup;
|
||||
|
||||
if (pll->pll_configuration.enable_ssc) {
|
||||
pr_debug("SSC is enabled\n");
|
||||
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_SSC_STEPSIZE_LOW_1,
|
||||
regs->ssc_stepsize_low);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_SSC_STEPSIZE_HIGH_1,
|
||||
regs->ssc_stepsize_high);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_PER_LOW_1,
|
||||
regs->ssc_div_per_low);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_PER_HIGH_1,
|
||||
regs->ssc_div_per_high);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_ADJPER_LOW_1,
|
||||
regs->ssc_adjper_low);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_SSC_DIV_ADJPER_HIGH_1,
|
||||
regs->ssc_adjper_high);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_SSC_CONTROL,
|
||||
SSC_EN | regs->ssc_control);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsi_pll_config_hzindep_reg(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->mmio;
|
||||
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_ONE, 0x80);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_TWO, 0x03);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_ANALOG_CONTROLS_THREE, 0x00);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_DSM_DIVIDER, 0x00);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_FEEDBACK_DIVIDER, 0x4e);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_CALIBRATION_SETTINGS, 0x40);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_BAND_SEL_CAL_SETTINGS_THREE,
|
||||
0xba);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_FREQ_DETECT_SETTINGS_ONE, 0x0c);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_OUTDIV, 0x00);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_CORE_OVERRIDE, 0x00);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_DIGITAL_TIMERS_TWO, 0x08);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_PROP_GAIN_RATE_1, 0x08);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_BAND_SET_RATE_1, 0xc0);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_INT_GAIN_IFILT_BAND_1, 0xfa);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_FL_INT_GAIN_PFILT_BAND_1,
|
||||
0x4c);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_LOCK_OVERRIDE, 0x80);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PFILT, 0x29);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_IFILT, 0x3f);
|
||||
}
|
||||
|
||||
static void dsi_pll_commit(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->mmio;
|
||||
struct dsi_pll_regs *reg = &pll->reg_setup;
|
||||
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_CORE_INPUT_OVERRIDE, 0x12);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_DECIMAL_DIV_START_1,
|
||||
reg->decimal_div_start);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_LOW_1,
|
||||
reg->frac_div_start_low);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_MID_1,
|
||||
reg->frac_div_start_mid);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_HIGH_1,
|
||||
reg->frac_div_start_high);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_LOCKDET_RATE_1,
|
||||
reg->pll_lockdet_rate);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_PLL_LOCK_DELAY, 0x06);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_CMODE, 0x10);
|
||||
pll_write(base + REG_DSI_10nm_PHY_PLL_CLOCK_INVERTERS,
|
||||
reg->pll_clock_inverters);
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_vco_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
|
||||
DBG("DSI PLL%d rate=%lu, parent's=%lu", pll_10nm->id, rate,
|
||||
parent_rate);
|
||||
|
||||
pll_10nm->vco_current_rate = rate;
|
||||
pll_10nm->vco_ref_clk_rate = VCO_REF_CLK_RATE;
|
||||
|
||||
dsi_pll_setup_config(pll_10nm);
|
||||
|
||||
dsi_pll_calc_dec_frac(pll_10nm);
|
||||
|
||||
dsi_pll_calc_ssc(pll_10nm);
|
||||
|
||||
dsi_pll_commit(pll_10nm);
|
||||
|
||||
dsi_pll_config_hzindep_reg(pll_10nm);
|
||||
|
||||
dsi_pll_ssc_commit(pll_10nm);
|
||||
|
||||
/* flush, ensure all register writes are done*/
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_lock_status(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
struct device *dev = &pll->pdev->dev;
|
||||
int rc;
|
||||
u32 status = 0;
|
||||
u32 const delay_us = 100;
|
||||
u32 const timeout_us = 5000;
|
||||
|
||||
rc = readl_poll_timeout_atomic(pll->mmio +
|
||||
REG_DSI_10nm_PHY_PLL_COMMON_STATUS_ONE,
|
||||
status,
|
||||
((status & BIT(0)) > 0),
|
||||
delay_us,
|
||||
timeout_us);
|
||||
if (rc)
|
||||
DRM_DEV_ERROR(dev, "DSI PLL(%d) lock failed, status=0x%08x\n",
|
||||
pll->id, status);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_pll_bias(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data = pll_read(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CTRL_0);
|
||||
|
||||
pll_write(pll->mmio + REG_DSI_10nm_PHY_PLL_SYSTEM_MUXES, 0);
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CTRL_0,
|
||||
data & ~BIT(5));
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_pll_bias(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data = pll_read(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CTRL_0);
|
||||
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CTRL_0,
|
||||
data | BIT(5));
|
||||
pll_write(pll->mmio + REG_DSI_10nm_PHY_PLL_SYSTEM_MUXES, 0xc0);
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_global_clk(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
data = pll_read(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CLK_CFG1,
|
||||
data & ~BIT(5));
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_global_clk(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
data = pll_read(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_CLK_CFG1,
|
||||
data | BIT(5));
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_vco_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
struct device *dev = &pll_10nm->pdev->dev;
|
||||
int rc;
|
||||
|
||||
dsi_pll_enable_pll_bias(pll_10nm);
|
||||
if (pll_10nm->slave)
|
||||
dsi_pll_enable_pll_bias(pll_10nm->slave);
|
||||
|
||||
rc = dsi_pll_10nm_vco_set_rate(hw,pll_10nm->vco_current_rate, 0);
|
||||
if (rc) {
|
||||
DRM_DEV_ERROR(dev, "vco_set_rate failed, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Start PLL */
|
||||
pll_write(pll_10nm->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_PLL_CNTRL,
|
||||
0x01);
|
||||
|
||||
/*
|
||||
* ensure all PLL configurations are written prior to checking
|
||||
* for PLL lock.
|
||||
*/
|
||||
wmb();
|
||||
|
||||
/* Check for PLL lock */
|
||||
rc = dsi_pll_10nm_lock_status(pll_10nm);
|
||||
if (rc) {
|
||||
DRM_DEV_ERROR(dev, "PLL(%d) lock failed\n", pll_10nm->id);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pll->pll_on = true;
|
||||
|
||||
dsi_pll_enable_global_clk(pll_10nm);
|
||||
if (pll_10nm->slave)
|
||||
dsi_pll_enable_global_clk(pll_10nm->slave);
|
||||
|
||||
pll_write(pll_10nm->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_RBUF_CTRL,
|
||||
0x01);
|
||||
if (pll_10nm->slave)
|
||||
pll_write(pll_10nm->slave->phy_cmn_mmio +
|
||||
REG_DSI_10nm_PHY_CMN_RBUF_CTRL, 0x01);
|
||||
|
||||
error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_sub(struct dsi_pll_10nm *pll)
|
||||
{
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_RBUF_CTRL, 0);
|
||||
dsi_pll_disable_pll_bias(pll);
|
||||
}
|
||||
|
||||
static void dsi_pll_10nm_vco_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
|
||||
/*
|
||||
* To avoid any stray glitches while abruptly powering down the PLL
|
||||
* make sure to gate the clock using the clock enable bit before
|
||||
* powering down the PLL
|
||||
*/
|
||||
dsi_pll_disable_global_clk(pll_10nm);
|
||||
pll_write(pll_10nm->phy_cmn_mmio + REG_DSI_10nm_PHY_CMN_PLL_CNTRL, 0);
|
||||
dsi_pll_disable_sub(pll_10nm);
|
||||
if (pll_10nm->slave) {
|
||||
dsi_pll_disable_global_clk(pll_10nm->slave);
|
||||
dsi_pll_disable_sub(pll_10nm->slave);
|
||||
}
|
||||
/* flush, ensure all register writes are done */
|
||||
wmb();
|
||||
pll->pll_on = false;
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_10nm_vco_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
struct dsi_pll_config *config = &pll_10nm->pll_configuration;
|
||||
void __iomem *base = pll_10nm->mmio;
|
||||
u64 ref_clk = pll_10nm->vco_ref_clk_rate;
|
||||
u64 vco_rate = 0x0;
|
||||
u64 multiplier;
|
||||
u32 frac;
|
||||
u32 dec;
|
||||
u64 pll_freq, tmp64;
|
||||
|
||||
dec = pll_read(base + REG_DSI_10nm_PHY_PLL_DECIMAL_DIV_START_1);
|
||||
dec &= 0xff;
|
||||
|
||||
frac = pll_read(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_LOW_1);
|
||||
frac |= ((pll_read(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_MID_1) &
|
||||
0xff) << 8);
|
||||
frac |= ((pll_read(base + REG_DSI_10nm_PHY_PLL_FRAC_DIV_START_HIGH_1) &
|
||||
0x3) << 16);
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* 1. Assumes prescaler is disabled
|
||||
*/
|
||||
multiplier = 1 << config->frac_bits;
|
||||
pll_freq = dec * (ref_clk * 2);
|
||||
tmp64 = (ref_clk * 2 * frac);
|
||||
pll_freq += div_u64(tmp64, multiplier);
|
||||
|
||||
vco_rate = pll_freq;
|
||||
|
||||
DBG("DSI PLL%d returning vco rate = %lu, dec = %x, frac = %x",
|
||||
pll_10nm->id, (unsigned long)vco_rate, dec, frac);
|
||||
|
||||
return (unsigned long)vco_rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_10nm_vco = {
|
||||
.round_rate = msm_dsi_pll_helper_clk_round_rate,
|
||||
.set_rate = dsi_pll_10nm_vco_set_rate,
|
||||
.recalc_rate = dsi_pll_10nm_vco_recalc_rate,
|
||||
.prepare = dsi_pll_10nm_vco_prepare,
|
||||
.unprepare = dsi_pll_10nm_vco_unprepare,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
|
||||
static void dsi_pll_10nm_save_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
struct pll_10nm_cached_state *cached = &pll_10nm->cached_state;
|
||||
void __iomem *phy_base = pll_10nm->phy_cmn_mmio;
|
||||
u32 cmn_clk_cfg0, cmn_clk_cfg1;
|
||||
|
||||
cached->pll_out_div = pll_read(pll_10nm->mmio +
|
||||
REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
cached->pll_out_div &= 0x3;
|
||||
|
||||
cmn_clk_cfg0 = pll_read(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG0);
|
||||
cached->bit_clk_div = cmn_clk_cfg0 & 0xf;
|
||||
cached->pix_clk_div = (cmn_clk_cfg0 & 0xf0) >> 4;
|
||||
|
||||
cmn_clk_cfg1 = pll_read(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
cached->pll_mux = cmn_clk_cfg1 & 0x3;
|
||||
|
||||
DBG("DSI PLL%d outdiv %x bit_clk_div %x pix_clk_div %x pll_mux %x",
|
||||
pll_10nm->id, cached->pll_out_div, cached->bit_clk_div,
|
||||
cached->pix_clk_div, cached->pll_mux);
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_restore_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
struct pll_10nm_cached_state *cached = &pll_10nm->cached_state;
|
||||
void __iomem *phy_base = pll_10nm->phy_cmn_mmio;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = pll_read(pll_10nm->mmio + REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_out_div;
|
||||
pll_write(pll_10nm->mmio + REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE, val);
|
||||
|
||||
pll_write(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG0,
|
||||
cached->bit_clk_div | (cached->pix_clk_div << 4));
|
||||
|
||||
val = pll_read(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG1);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_mux;
|
||||
pll_write(phy_base + REG_DSI_10nm_PHY_CMN_CLK_CFG1, val);
|
||||
|
||||
ret = dsi_pll_10nm_vco_set_rate(&pll->clk_hw, pll_10nm->vco_current_rate, pll_10nm->vco_ref_clk_rate);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_10nm->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DBG("DSI PLL%d", pll_10nm->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_set_usecase(struct msm_dsi_pll *pll,
|
||||
enum msm_dsi_phy_usecase uc)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
void __iomem *base = pll_10nm->phy_cmn_mmio;
|
||||
u32 data = 0x0; /* internal PLL */
|
||||
|
||||
DBG("DSI PLL%d", pll_10nm->id);
|
||||
|
||||
switch (uc) {
|
||||
case MSM_DSI_PHY_STANDALONE:
|
||||
break;
|
||||
case MSM_DSI_PHY_MASTER:
|
||||
pll_10nm->slave = pll_10nm_list[(pll_10nm->id + 1) % DSI_MAX];
|
||||
break;
|
||||
case MSM_DSI_PHY_SLAVE:
|
||||
data = 0x1; /* external PLL */
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set PLL src */
|
||||
pll_write(base + REG_DSI_10nm_PHY_CMN_CLK_CFG1, (data << 2));
|
||||
|
||||
pll_10nm->uc = uc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_10nm_get_provider(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider,
|
||||
struct clk **pixel_clk_provider)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
struct clk_hw_onecell_data *hw_data = pll_10nm->hw_data;
|
||||
|
||||
DBG("DSI PLL%d", pll_10nm->id);
|
||||
|
||||
if (byte_clk_provider)
|
||||
*byte_clk_provider = hw_data->hws[DSI_BYTE_PLL_CLK]->clk;
|
||||
if (pixel_clk_provider)
|
||||
*pixel_clk_provider = hw_data->hws[DSI_PIXEL_PLL_CLK]->clk;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_10nm_destroy(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm = to_pll_10nm(pll);
|
||||
struct device *dev = &pll_10nm->pdev->dev;
|
||||
|
||||
DBG("DSI PLL%d", pll_10nm->id);
|
||||
of_clk_del_provider(dev->of_node);
|
||||
|
||||
clk_hw_unregister_divider(pll_10nm->out_dsiclk_hw);
|
||||
clk_hw_unregister_mux(pll_10nm->pclk_mux_hw);
|
||||
clk_hw_unregister_fixed_factor(pll_10nm->post_out_div_clk_hw);
|
||||
clk_hw_unregister_fixed_factor(pll_10nm->by_2_bit_clk_hw);
|
||||
clk_hw_unregister_fixed_factor(pll_10nm->byte_clk_hw);
|
||||
clk_hw_unregister_divider(pll_10nm->bit_clk_hw);
|
||||
clk_hw_unregister_divider(pll_10nm->out_div_clk_hw);
|
||||
clk_hw_unregister(&pll_10nm->base.clk_hw);
|
||||
}
|
||||
|
||||
/*
|
||||
* The post dividers and mux clocks are created using the standard divider and
|
||||
* mux API. Unlike the 14nm PHY, the slave PLL doesn't need its dividers/mux
|
||||
* state to follow the master PLL's divider/mux state. Therefore, we don't
|
||||
* require special clock ops that also configure the slave PLL registers
|
||||
*/
|
||||
static int pll_10nm_register(struct dsi_pll_10nm *pll_10nm)
|
||||
{
|
||||
char clk_name[32], parent[32], vco_name[32];
|
||||
char parent2[32], parent3[32], parent4[32];
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "xo" },
|
||||
.num_parents = 1,
|
||||
.name = vco_name,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_10nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_10nm->pdev->dev;
|
||||
struct clk_hw_onecell_data *hw_data;
|
||||
struct clk_hw *hw;
|
||||
int ret;
|
||||
|
||||
DBG("DSI%d", pll_10nm->id);
|
||||
|
||||
hw_data = devm_kzalloc(dev, sizeof(*hw_data) +
|
||||
NUM_PROVIDED_CLKS * sizeof(struct clk_hw *),
|
||||
GFP_KERNEL);
|
||||
if (!hw_data)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_10nm->id);
|
||||
pll_10nm->base.clk_hw.init = &vco_init;
|
||||
|
||||
ret = clk_hw_register(dev, &pll_10nm->base.clk_hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_out_div_clk", pll_10nm->id);
|
||||
snprintf(parent, 32, "dsi%dvco_clk", pll_10nm->id);
|
||||
|
||||
hw = clk_hw_register_divider(dev, clk_name,
|
||||
parent, CLK_SET_RATE_PARENT,
|
||||
pll_10nm->mmio +
|
||||
REG_DSI_10nm_PHY_PLL_PLL_OUTDIV_RATE,
|
||||
0, 2, CLK_DIVIDER_POWER_OF_TWO, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_base_clk_hw;
|
||||
}
|
||||
|
||||
pll_10nm->out_div_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_bit_clk", pll_10nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_10nm->id);
|
||||
|
||||
/* BIT CLK: DIV_CTRL_3_0 */
|
||||
hw = clk_hw_register_divider(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT,
|
||||
pll_10nm->phy_cmn_mmio +
|
||||
REG_DSI_10nm_PHY_CMN_CLK_CFG0,
|
||||
0, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_10nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_out_div_clk_hw;
|
||||
}
|
||||
|
||||
pll_10nm->bit_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_byteclk", pll_10nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_10nm->id);
|
||||
|
||||
/* DSI Byte clock = VCO_CLK / OUT_DIV / BIT_DIV / 8 */
|
||||
hw = clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT, 1, 8);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_bit_clk_hw;
|
||||
}
|
||||
|
||||
pll_10nm->byte_clk_hw = hw;
|
||||
hw_data->hws[DSI_BYTE_PLL_CLK] = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_by_2_bit_clk", pll_10nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_10nm->id);
|
||||
|
||||
hw = clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 2);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_byte_clk_hw;
|
||||
}
|
||||
|
||||
pll_10nm->by_2_bit_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_post_out_div_clk", pll_10nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_10nm->id);
|
||||
|
||||
hw = clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 4);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_by_2_bit_clk_hw;
|
||||
}
|
||||
|
||||
pll_10nm->post_out_div_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pclk_mux", pll_10nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_10nm->id);
|
||||
snprintf(parent2, 32, "dsi%d_pll_by_2_bit_clk", pll_10nm->id);
|
||||
snprintf(parent3, 32, "dsi%d_pll_out_div_clk", pll_10nm->id);
|
||||
snprintf(parent4, 32, "dsi%d_pll_post_out_div_clk", pll_10nm->id);
|
||||
|
||||
hw = clk_hw_register_mux(dev, clk_name,
|
||||
((const char *[]){
|
||||
parent, parent2, parent3, parent4
|
||||
}), 4, 0, pll_10nm->phy_cmn_mmio +
|
||||
REG_DSI_10nm_PHY_CMN_CLK_CFG1,
|
||||
0, 2, 0, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_post_out_div_clk_hw;
|
||||
}
|
||||
|
||||
pll_10nm->pclk_mux_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_dsiclk", pll_10nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pclk_mux", pll_10nm->id);
|
||||
|
||||
/* PIX CLK DIV : DIV_CTRL_7_4*/
|
||||
hw = clk_hw_register_divider(dev, clk_name, parent,
|
||||
0, pll_10nm->phy_cmn_mmio +
|
||||
REG_DSI_10nm_PHY_CMN_CLK_CFG0,
|
||||
4, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_10nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_pclk_mux_hw;
|
||||
}
|
||||
|
||||
pll_10nm->out_dsiclk_hw = hw;
|
||||
hw_data->hws[DSI_PIXEL_PLL_CLK] = hw;
|
||||
|
||||
hw_data->num = NUM_PROVIDED_CLKS;
|
||||
pll_10nm->hw_data = hw_data;
|
||||
|
||||
ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
|
||||
pll_10nm->hw_data);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "failed to register clk provider: %d\n", ret);
|
||||
goto err_dsiclk_hw;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_dsiclk_hw:
|
||||
clk_hw_unregister_divider(pll_10nm->out_dsiclk_hw);
|
||||
err_pclk_mux_hw:
|
||||
clk_hw_unregister_mux(pll_10nm->pclk_mux_hw);
|
||||
err_post_out_div_clk_hw:
|
||||
clk_hw_unregister_fixed_factor(pll_10nm->post_out_div_clk_hw);
|
||||
err_by_2_bit_clk_hw:
|
||||
clk_hw_unregister_fixed_factor(pll_10nm->by_2_bit_clk_hw);
|
||||
err_byte_clk_hw:
|
||||
clk_hw_unregister_fixed_factor(pll_10nm->byte_clk_hw);
|
||||
err_bit_clk_hw:
|
||||
clk_hw_unregister_divider(pll_10nm->bit_clk_hw);
|
||||
err_out_div_clk_hw:
|
||||
clk_hw_unregister_divider(pll_10nm->out_div_clk_hw);
|
||||
err_base_clk_hw:
|
||||
clk_hw_unregister(&pll_10nm->base.clk_hw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct msm_dsi_pll *msm_dsi_pll_10nm_init(struct platform_device *pdev, int id)
|
||||
{
|
||||
struct dsi_pll_10nm *pll_10nm;
|
||||
struct msm_dsi_pll *pll;
|
||||
int ret;
|
||||
|
||||
pll_10nm = devm_kzalloc(&pdev->dev, sizeof(*pll_10nm), GFP_KERNEL);
|
||||
if (!pll_10nm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
DBG("DSI PLL%d", id);
|
||||
|
||||
pll_10nm->pdev = pdev;
|
||||
pll_10nm->id = id;
|
||||
pll_10nm_list[id] = pll_10nm;
|
||||
|
||||
pll_10nm->phy_cmn_mmio = msm_ioremap(pdev, "dsi_phy", "DSI_PHY");
|
||||
if (IS_ERR_OR_NULL(pll_10nm->phy_cmn_mmio)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to map CMN PHY base\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
pll_10nm->mmio = msm_ioremap(pdev, "dsi_pll", "DSI_PLL");
|
||||
if (IS_ERR_OR_NULL(pll_10nm->mmio)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to map PLL base\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
spin_lock_init(&pll_10nm->postdiv_lock);
|
||||
|
||||
pll = &pll_10nm->base;
|
||||
pll->min_rate = 1000000000UL;
|
||||
pll->max_rate = 3500000000UL;
|
||||
pll->get_provider = dsi_pll_10nm_get_provider;
|
||||
pll->destroy = dsi_pll_10nm_destroy;
|
||||
pll->save_state = dsi_pll_10nm_save_state;
|
||||
pll->restore_state = dsi_pll_10nm_restore_state;
|
||||
pll->set_usecase = dsi_pll_10nm_set_usecase;
|
||||
|
||||
pll_10nm->vco_delay = 1;
|
||||
|
||||
ret = pll_10nm_register(pll_10nm);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/* TODO: Remove this when we have proper display handover support */
|
||||
msm_dsi_pll_save_state(pll);
|
||||
|
||||
return pll;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,643 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "dsi_pll.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 28nm - clock diagram (eg: DSI0):
|
||||
*
|
||||
* dsi0analog_postdiv_clk
|
||||
* | dsi0indirect_path_div2_clk
|
||||
* | |
|
||||
* +------+ | +----+ | |\ dsi0byte_mux
|
||||
* dsi0vco_clk --o--| DIV1 |--o--| /2 |--o--| \ |
|
||||
* | +------+ +----+ | m| | +----+
|
||||
* | | u|--o--| /4 |-- dsi0pllbyte
|
||||
* | | x| +----+
|
||||
* o--------------------------| /
|
||||
* | |/
|
||||
* | +------+
|
||||
* o----------| DIV3 |------------------------- dsi0pll
|
||||
* +------+
|
||||
*/
|
||||
|
||||
#define POLL_MAX_READS 10
|
||||
#define POLL_TIMEOUT_US 50
|
||||
|
||||
#define NUM_PROVIDED_CLKS 2
|
||||
|
||||
#define VCO_REF_CLK_RATE 19200000
|
||||
#define VCO_MIN_RATE 350000000
|
||||
#define VCO_MAX_RATE 750000000
|
||||
|
||||
#define DSI_BYTE_PLL_CLK 0
|
||||
#define DSI_PIXEL_PLL_CLK 1
|
||||
|
||||
#define LPFR_LUT_SIZE 10
|
||||
struct lpfr_cfg {
|
||||
unsigned long vco_rate;
|
||||
u32 resistance;
|
||||
};
|
||||
|
||||
/* Loop filter resistance: */
|
||||
static const struct lpfr_cfg lpfr_lut[LPFR_LUT_SIZE] = {
|
||||
{ 479500000, 8 },
|
||||
{ 480000000, 11 },
|
||||
{ 575500000, 8 },
|
||||
{ 576000000, 12 },
|
||||
{ 610500000, 8 },
|
||||
{ 659500000, 9 },
|
||||
{ 671500000, 10 },
|
||||
{ 672000000, 14 },
|
||||
{ 708500000, 10 },
|
||||
{ 750000000, 11 },
|
||||
};
|
||||
|
||||
struct pll_28nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 postdiv3;
|
||||
u8 postdiv1;
|
||||
u8 byte_mux;
|
||||
};
|
||||
|
||||
struct dsi_pll_28nm {
|
||||
struct msm_dsi_pll base;
|
||||
|
||||
int id;
|
||||
struct platform_device *pdev;
|
||||
void __iomem *mmio;
|
||||
|
||||
int vco_delay;
|
||||
|
||||
/* private clocks: */
|
||||
struct clk *clks[NUM_DSI_CLOCKS_MAX];
|
||||
u32 num_clks;
|
||||
|
||||
/* clock-provider: */
|
||||
struct clk *provided_clks[NUM_PROVIDED_CLKS];
|
||||
struct clk_onecell_data clk_data;
|
||||
|
||||
struct pll_28nm_cached_state cached_state;
|
||||
};
|
||||
|
||||
#define to_pll_28nm(x) container_of(x, struct dsi_pll_28nm, base)
|
||||
|
||||
static bool pll_28nm_poll_for_ready(struct dsi_pll_28nm *pll_28nm,
|
||||
u32 nb_tries, u32 timeout_us)
|
||||
{
|
||||
bool pll_locked = false;
|
||||
u32 val;
|
||||
|
||||
while (nb_tries--) {
|
||||
val = pll_read(pll_28nm->mmio + REG_DSI_28nm_PHY_PLL_STATUS);
|
||||
pll_locked = !!(val & DSI_28nm_PHY_PLL_STATUS_PLL_RDY);
|
||||
|
||||
if (pll_locked)
|
||||
break;
|
||||
|
||||
udelay(timeout_us);
|
||||
}
|
||||
DBG("DSI PLL is %slocked", pll_locked ? "" : "*not* ");
|
||||
|
||||
return pll_locked;
|
||||
}
|
||||
|
||||
static void pll_28nm_software_reset(struct dsi_pll_28nm *pll_28nm)
|
||||
{
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
|
||||
/*
|
||||
* Add HW recommended delays after toggling the software
|
||||
* reset bit off and back on.
|
||||
*/
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_TEST_CFG,
|
||||
DSI_28nm_PHY_PLL_TEST_CFG_PLL_SW_RESET, 1);
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_TEST_CFG, 0x00, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clock Callbacks
|
||||
*/
|
||||
static int dsi_pll_28nm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct device *dev = &pll_28nm->pdev->dev;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
unsigned long div_fbx1000, gen_vco_clk;
|
||||
u32 refclk_cfg, frac_n_mode, frac_n_value;
|
||||
u32 sdm_cfg0, sdm_cfg1, sdm_cfg2, sdm_cfg3;
|
||||
u32 cal_cfg10, cal_cfg11;
|
||||
u32 rem;
|
||||
int i;
|
||||
|
||||
VERB("rate=%lu, parent's=%lu", rate, parent_rate);
|
||||
|
||||
/* Force postdiv2 to be div-4 */
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_POSTDIV2_CFG, 3);
|
||||
|
||||
/* Configure the Loop filter resistance */
|
||||
for (i = 0; i < LPFR_LUT_SIZE; i++)
|
||||
if (rate <= lpfr_lut[i].vco_rate)
|
||||
break;
|
||||
if (i == LPFR_LUT_SIZE) {
|
||||
DRM_DEV_ERROR(dev, "unable to get loop filter resistance. vco=%lu\n",
|
||||
rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_LPFR_CFG, lpfr_lut[i].resistance);
|
||||
|
||||
/* Loop filter capacitance values : c1 and c2 */
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_LPFC1_CFG, 0x70);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_LPFC2_CFG, 0x15);
|
||||
|
||||
rem = rate % VCO_REF_CLK_RATE;
|
||||
if (rem) {
|
||||
refclk_cfg = DSI_28nm_PHY_PLL_REFCLK_CFG_DBLR;
|
||||
frac_n_mode = 1;
|
||||
div_fbx1000 = rate / (VCO_REF_CLK_RATE / 500);
|
||||
gen_vco_clk = div_fbx1000 * (VCO_REF_CLK_RATE / 500);
|
||||
} else {
|
||||
refclk_cfg = 0x0;
|
||||
frac_n_mode = 0;
|
||||
div_fbx1000 = rate / (VCO_REF_CLK_RATE / 1000);
|
||||
gen_vco_clk = div_fbx1000 * (VCO_REF_CLK_RATE / 1000);
|
||||
}
|
||||
|
||||
DBG("refclk_cfg = %d", refclk_cfg);
|
||||
|
||||
rem = div_fbx1000 % 1000;
|
||||
frac_n_value = (rem << 16) / 1000;
|
||||
|
||||
DBG("div_fb = %lu", div_fbx1000);
|
||||
DBG("frac_n_value = %d", frac_n_value);
|
||||
|
||||
DBG("Generated VCO Clock: %lu", gen_vco_clk);
|
||||
rem = 0;
|
||||
sdm_cfg1 = pll_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG1);
|
||||
sdm_cfg1 &= ~DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET__MASK;
|
||||
if (frac_n_mode) {
|
||||
sdm_cfg0 = 0x0;
|
||||
sdm_cfg0 |= DSI_28nm_PHY_PLL_SDM_CFG0_BYP_DIV(0);
|
||||
sdm_cfg1 |= DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET(
|
||||
(u32)(((div_fbx1000 / 1000) & 0x3f) - 1));
|
||||
sdm_cfg3 = frac_n_value >> 8;
|
||||
sdm_cfg2 = frac_n_value & 0xff;
|
||||
} else {
|
||||
sdm_cfg0 = DSI_28nm_PHY_PLL_SDM_CFG0_BYP;
|
||||
sdm_cfg0 |= DSI_28nm_PHY_PLL_SDM_CFG0_BYP_DIV(
|
||||
(u32)(((div_fbx1000 / 1000) & 0x3f) - 1));
|
||||
sdm_cfg1 |= DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET(0);
|
||||
sdm_cfg2 = 0;
|
||||
sdm_cfg3 = 0;
|
||||
}
|
||||
|
||||
DBG("sdm_cfg0=%d", sdm_cfg0);
|
||||
DBG("sdm_cfg1=%d", sdm_cfg1);
|
||||
DBG("sdm_cfg2=%d", sdm_cfg2);
|
||||
DBG("sdm_cfg3=%d", sdm_cfg3);
|
||||
|
||||
cal_cfg11 = (u32)(gen_vco_clk / (256 * 1000000));
|
||||
cal_cfg10 = (u32)((gen_vco_clk % (256 * 1000000)) / 1000000);
|
||||
DBG("cal_cfg10=%d, cal_cfg11=%d", cal_cfg10, cal_cfg11);
|
||||
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CHGPUMP_CFG, 0x02);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG3, 0x2b);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG4, 0x06);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x0d);
|
||||
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG1, sdm_cfg1);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG2,
|
||||
DSI_28nm_PHY_PLL_SDM_CFG2_FREQ_SEED_7_0(sdm_cfg2));
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG3,
|
||||
DSI_28nm_PHY_PLL_SDM_CFG3_FREQ_SEED_15_8(sdm_cfg3));
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG4, 0x00);
|
||||
|
||||
/* Add hardware recommended delay for correct PLL configuration */
|
||||
if (pll_28nm->vco_delay)
|
||||
udelay(pll_28nm->vco_delay);
|
||||
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_REFCLK_CFG, refclk_cfg);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_PWRGEN_CFG, 0x00);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_VCOLPF_CFG, 0x31);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_SDM_CFG0, sdm_cfg0);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG0, 0x12);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG6, 0x30);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG7, 0x00);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG8, 0x60);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG9, 0x00);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG10, cal_cfg10 & 0xff);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_CAL_CFG11, cal_cfg11 & 0xff);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_EFUSE_CFG, 0x20);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_clk_is_enabled(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
|
||||
return pll_28nm_poll_for_ready(pll_28nm, POLL_MAX_READS,
|
||||
POLL_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_28nm_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
u32 sdm0, doubler, sdm_byp_div;
|
||||
u32 sdm_dc_off, sdm_freq_seed, sdm2, sdm3;
|
||||
u32 ref_clk = VCO_REF_CLK_RATE;
|
||||
unsigned long vco_rate;
|
||||
|
||||
VERB("parent_rate=%lu", parent_rate);
|
||||
|
||||
/* Check to see if the ref clk doubler is enabled */
|
||||
doubler = pll_read(base + REG_DSI_28nm_PHY_PLL_REFCLK_CFG) &
|
||||
DSI_28nm_PHY_PLL_REFCLK_CFG_DBLR;
|
||||
ref_clk += (doubler * VCO_REF_CLK_RATE);
|
||||
|
||||
/* see if it is integer mode or sdm mode */
|
||||
sdm0 = pll_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG0);
|
||||
if (sdm0 & DSI_28nm_PHY_PLL_SDM_CFG0_BYP) {
|
||||
/* integer mode */
|
||||
sdm_byp_div = FIELD(
|
||||
pll_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG0),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG0_BYP_DIV) + 1;
|
||||
vco_rate = ref_clk * sdm_byp_div;
|
||||
} else {
|
||||
/* sdm mode */
|
||||
sdm_dc_off = FIELD(
|
||||
pll_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG1),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG1_DC_OFFSET);
|
||||
DBG("sdm_dc_off = %d", sdm_dc_off);
|
||||
sdm2 = FIELD(pll_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG2),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG2_FREQ_SEED_7_0);
|
||||
sdm3 = FIELD(pll_read(base + REG_DSI_28nm_PHY_PLL_SDM_CFG3),
|
||||
DSI_28nm_PHY_PLL_SDM_CFG3_FREQ_SEED_15_8);
|
||||
sdm_freq_seed = (sdm3 << 8) | sdm2;
|
||||
DBG("sdm_freq_seed = %d", sdm_freq_seed);
|
||||
|
||||
vco_rate = (ref_clk * (sdm_dc_off + 1)) +
|
||||
mult_frac(ref_clk, sdm_freq_seed, BIT(16));
|
||||
DBG("vco rate = %lu", vco_rate);
|
||||
}
|
||||
|
||||
DBG("returning vco rate = %lu", vco_rate);
|
||||
|
||||
return vco_rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_28nm_vco = {
|
||||
.round_rate = msm_dsi_pll_helper_clk_round_rate,
|
||||
.set_rate = dsi_pll_28nm_clk_set_rate,
|
||||
.recalc_rate = dsi_pll_28nm_clk_recalc_rate,
|
||||
.prepare = msm_dsi_pll_helper_clk_prepare,
|
||||
.unprepare = msm_dsi_pll_helper_clk_unprepare,
|
||||
.is_enabled = dsi_pll_28nm_clk_is_enabled,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
static int dsi_pll_28nm_enable_seq_hpm(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct device *dev = &pll_28nm->pdev->dev;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
u32 max_reads = 5, timeout_us = 100;
|
||||
bool locked;
|
||||
u32 val;
|
||||
int i;
|
||||
|
||||
DBG("id=%d", pll_28nm->id);
|
||||
|
||||
pll_28nm_software_reset(pll_28nm);
|
||||
|
||||
/*
|
||||
* PLL power up sequence.
|
||||
* Add necessary delays recommended by hardware.
|
||||
*/
|
||||
val = DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 1);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRGEN_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 200);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_ENABLE;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 600);
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
/* DSI Uniphy lock detect setting */
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2,
|
||||
0x0c, 100);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x0d);
|
||||
|
||||
/* poll for PLL ready status */
|
||||
locked = pll_28nm_poll_for_ready(pll_28nm,
|
||||
max_reads, timeout_us);
|
||||
if (locked)
|
||||
break;
|
||||
|
||||
pll_28nm_software_reset(pll_28nm);
|
||||
|
||||
/*
|
||||
* PLL power up sequence.
|
||||
* Add necessary delays recommended by hardware.
|
||||
*/
|
||||
val = DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 1);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRGEN_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 200);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 250);
|
||||
|
||||
val &= ~DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 200);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_ENABLE;
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 600);
|
||||
}
|
||||
|
||||
if (unlikely(!locked))
|
||||
DRM_DEV_ERROR(dev, "DSI PLL lock failed\n");
|
||||
else
|
||||
DBG("DSI PLL Lock success");
|
||||
|
||||
return locked ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_enable_seq_lp(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct device *dev = &pll_28nm->pdev->dev;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
bool locked;
|
||||
u32 max_reads = 10, timeout_us = 50;
|
||||
u32 val;
|
||||
|
||||
DBG("id=%d", pll_28nm->id);
|
||||
|
||||
pll_28nm_software_reset(pll_28nm);
|
||||
|
||||
/*
|
||||
* PLL power up sequence.
|
||||
* Add necessary delays recommended by hardware.
|
||||
*/
|
||||
pll_write_ndelay(base + REG_DSI_28nm_PHY_PLL_CAL_CFG1, 0x34, 500);
|
||||
|
||||
val = DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRDN_B;
|
||||
pll_write_ndelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_PWRGEN_PWRDN_B;
|
||||
pll_write_ndelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
val |= DSI_28nm_PHY_PLL_GLB_CFG_PLL_LDO_PWRDN_B |
|
||||
DSI_28nm_PHY_PLL_GLB_CFG_PLL_ENABLE;
|
||||
pll_write_ndelay(base + REG_DSI_28nm_PHY_PLL_GLB_CFG, val, 500);
|
||||
|
||||
/* DSI PLL toggle lock detect setting */
|
||||
pll_write_ndelay(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x04, 500);
|
||||
pll_write_udelay(base + REG_DSI_28nm_PHY_PLL_LKDET_CFG2, 0x05, 512);
|
||||
|
||||
locked = pll_28nm_poll_for_ready(pll_28nm, max_reads, timeout_us);
|
||||
|
||||
if (unlikely(!locked))
|
||||
DRM_DEV_ERROR(dev, "DSI PLL lock failed\n");
|
||||
else
|
||||
DBG("DSI PLL lock success");
|
||||
|
||||
return locked ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_disable_seq(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
|
||||
DBG("id=%d", pll_28nm->id);
|
||||
pll_write(pll_28nm->mmio + REG_DSI_28nm_PHY_PLL_GLB_CFG, 0x00);
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_save_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
|
||||
cached_state->postdiv3 =
|
||||
pll_read(base + REG_DSI_28nm_PHY_PLL_POSTDIV3_CFG);
|
||||
cached_state->postdiv1 =
|
||||
pll_read(base + REG_DSI_28nm_PHY_PLL_POSTDIV1_CFG);
|
||||
cached_state->byte_mux = pll_read(base + REG_DSI_28nm_PHY_PLL_VREG_CFG);
|
||||
if (dsi_pll_28nm_clk_is_enabled(&pll->clk_hw))
|
||||
cached_state->vco_rate = clk_hw_get_rate(&pll->clk_hw);
|
||||
else
|
||||
cached_state->vco_rate = 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_restore_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
int ret;
|
||||
|
||||
ret = dsi_pll_28nm_clk_set_rate(&pll->clk_hw,
|
||||
cached_state->vco_rate, 0);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_28nm->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_POSTDIV3_CFG,
|
||||
cached_state->postdiv3);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_POSTDIV1_CFG,
|
||||
cached_state->postdiv1);
|
||||
pll_write(base + REG_DSI_28nm_PHY_PLL_VREG_CFG,
|
||||
cached_state->byte_mux);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_get_provider(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider,
|
||||
struct clk **pixel_clk_provider)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
|
||||
if (byte_clk_provider)
|
||||
*byte_clk_provider = pll_28nm->provided_clks[DSI_BYTE_PLL_CLK];
|
||||
if (pixel_clk_provider)
|
||||
*pixel_clk_provider =
|
||||
pll_28nm->provided_clks[DSI_PIXEL_PLL_CLK];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_destroy(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
int i;
|
||||
|
||||
msm_dsi_pll_helper_unregister_clks(pll_28nm->pdev,
|
||||
pll_28nm->clks, pll_28nm->num_clks);
|
||||
|
||||
for (i = 0; i < NUM_PROVIDED_CLKS; i++)
|
||||
pll_28nm->provided_clks[i] = NULL;
|
||||
|
||||
pll_28nm->num_clks = 0;
|
||||
pll_28nm->clk_data.clks = NULL;
|
||||
pll_28nm->clk_data.clk_num = 0;
|
||||
}
|
||||
|
||||
static int pll_28nm_register(struct dsi_pll_28nm *pll_28nm)
|
||||
{
|
||||
char clk_name[32], parent1[32], parent2[32], vco_name[32];
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "xo" },
|
||||
.num_parents = 1,
|
||||
.name = vco_name,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_28nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_28nm->pdev->dev;
|
||||
struct clk **clks = pll_28nm->clks;
|
||||
struct clk **provided_clks = pll_28nm->provided_clks;
|
||||
int num = 0;
|
||||
int ret;
|
||||
|
||||
DBG("%d", pll_28nm->id);
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_28nm->id);
|
||||
pll_28nm->base.clk_hw.init = &vco_init;
|
||||
clks[num++] = clk_register(dev, &pll_28nm->base.clk_hw);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%danalog_postdiv_clk", pll_28nm->id);
|
||||
snprintf(parent1, 32, "dsi%dvco_clk", pll_28nm->id);
|
||||
clks[num++] = clk_register_divider(dev, clk_name,
|
||||
parent1, CLK_SET_RATE_PARENT,
|
||||
pll_28nm->mmio +
|
||||
REG_DSI_28nm_PHY_PLL_POSTDIV1_CFG,
|
||||
0, 4, 0, NULL);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dindirect_path_div2_clk", pll_28nm->id);
|
||||
snprintf(parent1, 32, "dsi%danalog_postdiv_clk", pll_28nm->id);
|
||||
clks[num++] = clk_register_fixed_factor(dev, clk_name,
|
||||
parent1, CLK_SET_RATE_PARENT,
|
||||
1, 2);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpll", pll_28nm->id);
|
||||
snprintf(parent1, 32, "dsi%dvco_clk", pll_28nm->id);
|
||||
clks[num++] = provided_clks[DSI_PIXEL_PLL_CLK] =
|
||||
clk_register_divider(dev, clk_name,
|
||||
parent1, 0, pll_28nm->mmio +
|
||||
REG_DSI_28nm_PHY_PLL_POSTDIV3_CFG,
|
||||
0, 8, 0, NULL);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dbyte_mux", pll_28nm->id);
|
||||
snprintf(parent1, 32, "dsi%dvco_clk", pll_28nm->id);
|
||||
snprintf(parent2, 32, "dsi%dindirect_path_div2_clk", pll_28nm->id);
|
||||
clks[num++] = clk_register_mux(dev, clk_name,
|
||||
((const char *[]){
|
||||
parent1, parent2
|
||||
}), 2, CLK_SET_RATE_PARENT, pll_28nm->mmio +
|
||||
REG_DSI_28nm_PHY_PLL_VREG_CFG, 1, 1, 0, NULL);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpllbyte", pll_28nm->id);
|
||||
snprintf(parent1, 32, "dsi%dbyte_mux", pll_28nm->id);
|
||||
clks[num++] = provided_clks[DSI_BYTE_PLL_CLK] =
|
||||
clk_register_fixed_factor(dev, clk_name,
|
||||
parent1, CLK_SET_RATE_PARENT, 1, 4);
|
||||
|
||||
pll_28nm->num_clks = num;
|
||||
|
||||
pll_28nm->clk_data.clk_num = NUM_PROVIDED_CLKS;
|
||||
pll_28nm->clk_data.clks = provided_clks;
|
||||
|
||||
ret = of_clk_add_provider(dev->of_node,
|
||||
of_clk_src_onecell_get, &pll_28nm->clk_data);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "failed to register clk provider: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct msm_dsi_pll *msm_dsi_pll_28nm_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm;
|
||||
struct msm_dsi_pll *pll;
|
||||
int ret;
|
||||
|
||||
if (!pdev)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
pll_28nm = devm_kzalloc(&pdev->dev, sizeof(*pll_28nm), GFP_KERNEL);
|
||||
if (!pll_28nm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pll_28nm->pdev = pdev;
|
||||
pll_28nm->id = id;
|
||||
|
||||
pll_28nm->mmio = msm_ioremap(pdev, "dsi_pll", "DSI_PLL");
|
||||
if (IS_ERR_OR_NULL(pll_28nm->mmio)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map pll base\n", __func__);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
pll = &pll_28nm->base;
|
||||
pll->min_rate = VCO_MIN_RATE;
|
||||
pll->max_rate = VCO_MAX_RATE;
|
||||
pll->get_provider = dsi_pll_28nm_get_provider;
|
||||
pll->destroy = dsi_pll_28nm_destroy;
|
||||
pll->disable_seq = dsi_pll_28nm_disable_seq;
|
||||
pll->save_state = dsi_pll_28nm_save_state;
|
||||
pll->restore_state = dsi_pll_28nm_restore_state;
|
||||
|
||||
if (type == MSM_DSI_PHY_28NM_HPM) {
|
||||
pll_28nm->vco_delay = 1;
|
||||
|
||||
pll->en_seq_cnt = 3;
|
||||
pll->enable_seqs[0] = dsi_pll_28nm_enable_seq_hpm;
|
||||
pll->enable_seqs[1] = dsi_pll_28nm_enable_seq_hpm;
|
||||
pll->enable_seqs[2] = dsi_pll_28nm_enable_seq_hpm;
|
||||
} else if (type == MSM_DSI_PHY_28NM_LP) {
|
||||
pll_28nm->vco_delay = 1000;
|
||||
|
||||
pll->en_seq_cnt = 1;
|
||||
pll->enable_seqs[0] = dsi_pll_28nm_enable_seq_lp;
|
||||
} else {
|
||||
DRM_DEV_ERROR(&pdev->dev, "phy type (%d) is not 28nm\n", type);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
ret = pll_28nm_register(pll_28nm);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return pll;
|
||||
}
|
||||
|
@ -1,526 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "dsi_pll.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 28nm (8960/A family) - clock diagram (eg: DSI1):
|
||||
*
|
||||
*
|
||||
* +------+
|
||||
* dsi1vco_clk ----o-----| DIV1 |---dsi1pllbit (not exposed as clock)
|
||||
* F * byte_clk | +------+
|
||||
* | bit clock divider (F / 8)
|
||||
* |
|
||||
* | +------+
|
||||
* o-----| DIV2 |---dsi0pllbyte---o---> To byte RCG
|
||||
* | +------+ | (sets parent rate)
|
||||
* | byte clock divider (F) |
|
||||
* | |
|
||||
* | o---> To esc RCG
|
||||
* | (doesn't set parent rate)
|
||||
* |
|
||||
* | +------+
|
||||
* o-----| DIV3 |----dsi0pll------o---> To dsi RCG
|
||||
* +------+ | (sets parent rate)
|
||||
* dsi clock divider (F * magic) |
|
||||
* |
|
||||
* o---> To pixel rcg
|
||||
* (doesn't set parent rate)
|
||||
*/
|
||||
|
||||
#define POLL_MAX_READS 8000
|
||||
#define POLL_TIMEOUT_US 1
|
||||
|
||||
#define NUM_PROVIDED_CLKS 2
|
||||
|
||||
#define VCO_REF_CLK_RATE 27000000
|
||||
#define VCO_MIN_RATE 600000000
|
||||
#define VCO_MAX_RATE 1200000000
|
||||
|
||||
#define DSI_BYTE_PLL_CLK 0
|
||||
#define DSI_PIXEL_PLL_CLK 1
|
||||
|
||||
#define VCO_PREF_DIV_RATIO 27
|
||||
|
||||
struct pll_28nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 postdiv3;
|
||||
u8 postdiv2;
|
||||
u8 postdiv1;
|
||||
};
|
||||
|
||||
struct clk_bytediv {
|
||||
struct clk_hw hw;
|
||||
void __iomem *reg;
|
||||
};
|
||||
|
||||
struct dsi_pll_28nm {
|
||||
struct msm_dsi_pll base;
|
||||
|
||||
int id;
|
||||
struct platform_device *pdev;
|
||||
void __iomem *mmio;
|
||||
|
||||
/* custom byte clock divider */
|
||||
struct clk_bytediv *bytediv;
|
||||
|
||||
/* private clocks: */
|
||||
struct clk *clks[NUM_DSI_CLOCKS_MAX];
|
||||
u32 num_clks;
|
||||
|
||||
/* clock-provider: */
|
||||
struct clk *provided_clks[NUM_PROVIDED_CLKS];
|
||||
struct clk_onecell_data clk_data;
|
||||
|
||||
struct pll_28nm_cached_state cached_state;
|
||||
};
|
||||
|
||||
#define to_pll_28nm(x) container_of(x, struct dsi_pll_28nm, base)
|
||||
|
||||
static bool pll_28nm_poll_for_ready(struct dsi_pll_28nm *pll_28nm,
|
||||
int nb_tries, int timeout_us)
|
||||
{
|
||||
bool pll_locked = false;
|
||||
u32 val;
|
||||
|
||||
while (nb_tries--) {
|
||||
val = pll_read(pll_28nm->mmio + REG_DSI_28nm_8960_PHY_PLL_RDY);
|
||||
pll_locked = !!(val & DSI_28nm_8960_PHY_PLL_RDY_PLL_RDY);
|
||||
|
||||
if (pll_locked)
|
||||
break;
|
||||
|
||||
udelay(timeout_us);
|
||||
}
|
||||
DBG("DSI PLL is %slocked", pll_locked ? "" : "*not* ");
|
||||
|
||||
return pll_locked;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clock Callbacks
|
||||
*/
|
||||
static int dsi_pll_28nm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
u32 val, temp, fb_divider;
|
||||
|
||||
DBG("rate=%lu, parent's=%lu", rate, parent_rate);
|
||||
|
||||
temp = rate / 10;
|
||||
val = VCO_REF_CLK_RATE / 10;
|
||||
fb_divider = (temp * VCO_PREF_DIV_RATIO) / val;
|
||||
fb_divider = fb_divider / 2 - 1;
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_1,
|
||||
fb_divider & 0xff);
|
||||
|
||||
val = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_2);
|
||||
|
||||
val |= (fb_divider >> 8) & 0x07;
|
||||
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_2,
|
||||
val);
|
||||
|
||||
val = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_3);
|
||||
|
||||
val |= (VCO_PREF_DIV_RATIO - 1) & 0x3f;
|
||||
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_3,
|
||||
val);
|
||||
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_6,
|
||||
0xf);
|
||||
|
||||
val = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8);
|
||||
val |= 0x7 << 4;
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8,
|
||||
val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_clk_is_enabled(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
|
||||
return pll_28nm_poll_for_ready(pll_28nm, POLL_MAX_READS,
|
||||
POLL_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_28nm_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
unsigned long vco_rate;
|
||||
u32 status, fb_divider, temp, ref_divider;
|
||||
|
||||
VERB("parent_rate=%lu", parent_rate);
|
||||
|
||||
status = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_0);
|
||||
|
||||
if (status & DSI_28nm_8960_PHY_PLL_CTRL_0_ENABLE) {
|
||||
fb_divider = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_1);
|
||||
fb_divider &= 0xff;
|
||||
temp = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_2) & 0x07;
|
||||
fb_divider = (temp << 8) | fb_divider;
|
||||
fb_divider += 1;
|
||||
|
||||
ref_divider = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_3);
|
||||
ref_divider &= 0x3f;
|
||||
ref_divider += 1;
|
||||
|
||||
/* multiply by 2 */
|
||||
vco_rate = (parent_rate / ref_divider) * fb_divider * 2;
|
||||
} else {
|
||||
vco_rate = 0;
|
||||
}
|
||||
|
||||
DBG("returning vco rate = %lu", vco_rate);
|
||||
|
||||
return vco_rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_28nm_vco = {
|
||||
.round_rate = msm_dsi_pll_helper_clk_round_rate,
|
||||
.set_rate = dsi_pll_28nm_clk_set_rate,
|
||||
.recalc_rate = dsi_pll_28nm_clk_recalc_rate,
|
||||
.prepare = msm_dsi_pll_helper_clk_prepare,
|
||||
.unprepare = msm_dsi_pll_helper_clk_unprepare,
|
||||
.is_enabled = dsi_pll_28nm_clk_is_enabled,
|
||||
};
|
||||
|
||||
/*
|
||||
* Custom byte clock divier clk_ops
|
||||
*
|
||||
* This clock is the entry point to configuring the PLL. The user (dsi host)
|
||||
* will set this clock's rate to the desired byte clock rate. The VCO lock
|
||||
* frequency is a multiple of the byte clock rate. The multiplication factor
|
||||
* (shown as F in the diagram above) is a function of the byte clock rate.
|
||||
*
|
||||
* This custom divider clock ensures that its parent (VCO) is set to the
|
||||
* desired rate, and that the byte clock postdivider (POSTDIV2) is configured
|
||||
* accordingly
|
||||
*/
|
||||
#define to_clk_bytediv(_hw) container_of(_hw, struct clk_bytediv, hw)
|
||||
|
||||
static unsigned long clk_bytediv_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct clk_bytediv *bytediv = to_clk_bytediv(hw);
|
||||
unsigned int div;
|
||||
|
||||
div = pll_read(bytediv->reg) & 0xff;
|
||||
|
||||
return parent_rate / (div + 1);
|
||||
}
|
||||
|
||||
/* find multiplication factor(wrt byte clock) at which the VCO should be set */
|
||||
static unsigned int get_vco_mul_factor(unsigned long byte_clk_rate)
|
||||
{
|
||||
unsigned long bit_mhz;
|
||||
|
||||
/* convert to bit clock in Mhz */
|
||||
bit_mhz = (byte_clk_rate * 8) / 1000000;
|
||||
|
||||
if (bit_mhz < 125)
|
||||
return 64;
|
||||
else if (bit_mhz < 250)
|
||||
return 32;
|
||||
else if (bit_mhz < 600)
|
||||
return 16;
|
||||
else
|
||||
return 8;
|
||||
}
|
||||
|
||||
static long clk_bytediv_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *prate)
|
||||
{
|
||||
unsigned long best_parent;
|
||||
unsigned int factor;
|
||||
|
||||
factor = get_vco_mul_factor(rate);
|
||||
|
||||
best_parent = rate * factor;
|
||||
*prate = clk_hw_round_rate(clk_hw_get_parent(hw), best_parent);
|
||||
|
||||
return *prate / factor;
|
||||
}
|
||||
|
||||
static int clk_bytediv_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct clk_bytediv *bytediv = to_clk_bytediv(hw);
|
||||
u32 val;
|
||||
unsigned int factor;
|
||||
|
||||
factor = get_vco_mul_factor(rate);
|
||||
|
||||
val = pll_read(bytediv->reg);
|
||||
val |= (factor - 1) & 0xff;
|
||||
pll_write(bytediv->reg, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Our special byte clock divider ops */
|
||||
static const struct clk_ops clk_bytediv_ops = {
|
||||
.round_rate = clk_bytediv_round_rate,
|
||||
.set_rate = clk_bytediv_set_rate,
|
||||
.recalc_rate = clk_bytediv_recalc_rate,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
static int dsi_pll_28nm_enable_seq(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct device *dev = &pll_28nm->pdev->dev;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
bool locked;
|
||||
unsigned int bit_div, byte_div;
|
||||
int max_reads = 1000, timeout_us = 100;
|
||||
u32 val;
|
||||
|
||||
DBG("id=%d", pll_28nm->id);
|
||||
|
||||
/*
|
||||
* before enabling the PLL, configure the bit clock divider since we
|
||||
* don't expose it as a clock to the outside world
|
||||
* 1: read back the byte clock divider that should already be set
|
||||
* 2: divide by 8 to get bit clock divider
|
||||
* 3: write it to POSTDIV1
|
||||
*/
|
||||
val = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_9);
|
||||
byte_div = val + 1;
|
||||
bit_div = byte_div / 8;
|
||||
|
||||
val = pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8);
|
||||
val &= ~0xf;
|
||||
val |= (bit_div - 1);
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8, val);
|
||||
|
||||
/* enable the PLL */
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_0,
|
||||
DSI_28nm_8960_PHY_PLL_CTRL_0_ENABLE);
|
||||
|
||||
locked = pll_28nm_poll_for_ready(pll_28nm, max_reads, timeout_us);
|
||||
|
||||
if (unlikely(!locked))
|
||||
DRM_DEV_ERROR(dev, "DSI PLL lock failed\n");
|
||||
else
|
||||
DBG("DSI PLL lock success");
|
||||
|
||||
return locked ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_disable_seq(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
|
||||
DBG("id=%d", pll_28nm->id);
|
||||
pll_write(pll_28nm->mmio + REG_DSI_28nm_8960_PHY_PLL_CTRL_0, 0x00);
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_save_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
|
||||
cached_state->postdiv3 =
|
||||
pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_10);
|
||||
cached_state->postdiv2 =
|
||||
pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_9);
|
||||
cached_state->postdiv1 =
|
||||
pll_read(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8);
|
||||
|
||||
cached_state->vco_rate = clk_hw_get_rate(&pll->clk_hw);
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_restore_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
struct pll_28nm_cached_state *cached_state = &pll_28nm->cached_state;
|
||||
void __iomem *base = pll_28nm->mmio;
|
||||
int ret;
|
||||
|
||||
ret = dsi_pll_28nm_clk_set_rate(&pll->clk_hw,
|
||||
cached_state->vco_rate, 0);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_28nm->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_10,
|
||||
cached_state->postdiv3);
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_9,
|
||||
cached_state->postdiv2);
|
||||
pll_write(base + REG_DSI_28nm_8960_PHY_PLL_CTRL_8,
|
||||
cached_state->postdiv1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_28nm_get_provider(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider,
|
||||
struct clk **pixel_clk_provider)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
|
||||
if (byte_clk_provider)
|
||||
*byte_clk_provider = pll_28nm->provided_clks[DSI_BYTE_PLL_CLK];
|
||||
if (pixel_clk_provider)
|
||||
*pixel_clk_provider =
|
||||
pll_28nm->provided_clks[DSI_PIXEL_PLL_CLK];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_28nm_destroy(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm = to_pll_28nm(pll);
|
||||
|
||||
msm_dsi_pll_helper_unregister_clks(pll_28nm->pdev,
|
||||
pll_28nm->clks, pll_28nm->num_clks);
|
||||
}
|
||||
|
||||
static int pll_28nm_register(struct dsi_pll_28nm *pll_28nm)
|
||||
{
|
||||
char *clk_name, *parent_name, *vco_name;
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "pxo" },
|
||||
.num_parents = 1,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_28nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_28nm->pdev->dev;
|
||||
struct clk **clks = pll_28nm->clks;
|
||||
struct clk **provided_clks = pll_28nm->provided_clks;
|
||||
struct clk_bytediv *bytediv;
|
||||
struct clk_init_data bytediv_init = { };
|
||||
int ret, num = 0;
|
||||
|
||||
DBG("%d", pll_28nm->id);
|
||||
|
||||
bytediv = devm_kzalloc(dev, sizeof(*bytediv), GFP_KERNEL);
|
||||
if (!bytediv)
|
||||
return -ENOMEM;
|
||||
|
||||
vco_name = devm_kzalloc(dev, 32, GFP_KERNEL);
|
||||
if (!vco_name)
|
||||
return -ENOMEM;
|
||||
|
||||
parent_name = devm_kzalloc(dev, 32, GFP_KERNEL);
|
||||
if (!parent_name)
|
||||
return -ENOMEM;
|
||||
|
||||
clk_name = devm_kzalloc(dev, 32, GFP_KERNEL);
|
||||
if (!clk_name)
|
||||
return -ENOMEM;
|
||||
|
||||
pll_28nm->bytediv = bytediv;
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_28nm->id);
|
||||
vco_init.name = vco_name;
|
||||
|
||||
pll_28nm->base.clk_hw.init = &vco_init;
|
||||
|
||||
clks[num++] = clk_register(dev, &pll_28nm->base.clk_hw);
|
||||
|
||||
/* prepare and register bytediv */
|
||||
bytediv->hw.init = &bytediv_init;
|
||||
bytediv->reg = pll_28nm->mmio + REG_DSI_28nm_8960_PHY_PLL_CTRL_9;
|
||||
|
||||
snprintf(parent_name, 32, "dsi%dvco_clk", pll_28nm->id);
|
||||
snprintf(clk_name, 32, "dsi%dpllbyte", pll_28nm->id);
|
||||
|
||||
bytediv_init.name = clk_name;
|
||||
bytediv_init.ops = &clk_bytediv_ops;
|
||||
bytediv_init.flags = CLK_SET_RATE_PARENT;
|
||||
bytediv_init.parent_names = (const char * const *) &parent_name;
|
||||
bytediv_init.num_parents = 1;
|
||||
|
||||
/* DIV2 */
|
||||
clks[num++] = provided_clks[DSI_BYTE_PLL_CLK] =
|
||||
clk_register(dev, &bytediv->hw);
|
||||
|
||||
snprintf(clk_name, 32, "dsi%dpll", pll_28nm->id);
|
||||
/* DIV3 */
|
||||
clks[num++] = provided_clks[DSI_PIXEL_PLL_CLK] =
|
||||
clk_register_divider(dev, clk_name,
|
||||
parent_name, 0, pll_28nm->mmio +
|
||||
REG_DSI_28nm_8960_PHY_PLL_CTRL_10,
|
||||
0, 8, 0, NULL);
|
||||
|
||||
pll_28nm->num_clks = num;
|
||||
|
||||
pll_28nm->clk_data.clk_num = NUM_PROVIDED_CLKS;
|
||||
pll_28nm->clk_data.clks = provided_clks;
|
||||
|
||||
ret = of_clk_add_provider(dev->of_node,
|
||||
of_clk_src_onecell_get, &pll_28nm->clk_data);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "failed to register clk provider: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct msm_dsi_pll *msm_dsi_pll_28nm_8960_init(struct platform_device *pdev,
|
||||
int id)
|
||||
{
|
||||
struct dsi_pll_28nm *pll_28nm;
|
||||
struct msm_dsi_pll *pll;
|
||||
int ret;
|
||||
|
||||
if (!pdev)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
pll_28nm = devm_kzalloc(&pdev->dev, sizeof(*pll_28nm), GFP_KERNEL);
|
||||
if (!pll_28nm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pll_28nm->pdev = pdev;
|
||||
pll_28nm->id = id + 1;
|
||||
|
||||
pll_28nm->mmio = msm_ioremap(pdev, "dsi_pll", "DSI_PLL");
|
||||
if (IS_ERR_OR_NULL(pll_28nm->mmio)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "%s: failed to map pll base\n", __func__);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
pll = &pll_28nm->base;
|
||||
pll->min_rate = VCO_MIN_RATE;
|
||||
pll->max_rate = VCO_MAX_RATE;
|
||||
pll->get_provider = dsi_pll_28nm_get_provider;
|
||||
pll->destroy = dsi_pll_28nm_destroy;
|
||||
pll->disable_seq = dsi_pll_28nm_disable_seq;
|
||||
pll->save_state = dsi_pll_28nm_save_state;
|
||||
pll->restore_state = dsi_pll_28nm_restore_state;
|
||||
|
||||
pll->en_seq_cnt = 1;
|
||||
pll->enable_seqs[0] = dsi_pll_28nm_enable_seq;
|
||||
|
||||
ret = pll_28nm_register(pll_28nm);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return pll;
|
||||
}
|
@ -1,913 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
* Copyright (c) 2018, The Linux Foundation
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "dsi_pll.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
/*
|
||||
* DSI PLL 7nm - clock diagram (eg: DSI0): TODO: updated CPHY diagram
|
||||
*
|
||||
* dsi0_pll_out_div_clk dsi0_pll_bit_clk
|
||||
* | |
|
||||
* | |
|
||||
* +---------+ | +----------+ | +----+
|
||||
* dsi0vco_clk ---| out_div |--o--| divl_3_0 |--o--| /8 |-- dsi0_phy_pll_out_byteclk
|
||||
* +---------+ | +----------+ | +----+
|
||||
* | |
|
||||
* | | dsi0_pll_by_2_bit_clk
|
||||
* | | |
|
||||
* | | +----+ | |\ dsi0_pclk_mux
|
||||
* | |--| /2 |--o--| \ |
|
||||
* | | +----+ | \ | +---------+
|
||||
* | --------------| |--o--| div_7_4 |-- dsi0_phy_pll_out_dsiclk
|
||||
* |------------------------------| / +---------+
|
||||
* | +-----+ | /
|
||||
* -----------| /4? |--o----------|/
|
||||
* +-----+ | |
|
||||
* | |dsiclk_sel
|
||||
* |
|
||||
* dsi0_pll_post_out_div_clk
|
||||
*/
|
||||
|
||||
#define DSI_BYTE_PLL_CLK 0
|
||||
#define DSI_PIXEL_PLL_CLK 1
|
||||
#define NUM_PROVIDED_CLKS 2
|
||||
|
||||
#define VCO_REF_CLK_RATE 19200000
|
||||
|
||||
struct dsi_pll_regs {
|
||||
u32 pll_prop_gain_rate;
|
||||
u32 pll_lockdet_rate;
|
||||
u32 decimal_div_start;
|
||||
u32 frac_div_start_low;
|
||||
u32 frac_div_start_mid;
|
||||
u32 frac_div_start_high;
|
||||
u32 pll_clock_inverters;
|
||||
u32 ssc_stepsize_low;
|
||||
u32 ssc_stepsize_high;
|
||||
u32 ssc_div_per_low;
|
||||
u32 ssc_div_per_high;
|
||||
u32 ssc_adjper_low;
|
||||
u32 ssc_adjper_high;
|
||||
u32 ssc_control;
|
||||
};
|
||||
|
||||
struct dsi_pll_config {
|
||||
u32 ref_freq;
|
||||
bool div_override;
|
||||
u32 output_div;
|
||||
bool ignore_frac;
|
||||
bool disable_prescaler;
|
||||
bool enable_ssc;
|
||||
bool ssc_center;
|
||||
u32 dec_bits;
|
||||
u32 frac_bits;
|
||||
u32 lock_timer;
|
||||
u32 ssc_freq;
|
||||
u32 ssc_offset;
|
||||
u32 ssc_adj_per;
|
||||
u32 thresh_cycles;
|
||||
u32 refclk_cycles;
|
||||
};
|
||||
|
||||
struct pll_7nm_cached_state {
|
||||
unsigned long vco_rate;
|
||||
u8 bit_clk_div;
|
||||
u8 pix_clk_div;
|
||||
u8 pll_out_div;
|
||||
u8 pll_mux;
|
||||
};
|
||||
|
||||
struct dsi_pll_7nm {
|
||||
struct msm_dsi_pll base;
|
||||
|
||||
int id;
|
||||
struct platform_device *pdev;
|
||||
|
||||
void __iomem *phy_cmn_mmio;
|
||||
void __iomem *mmio;
|
||||
|
||||
u64 vco_ref_clk_rate;
|
||||
u64 vco_current_rate;
|
||||
|
||||
/* protects REG_DSI_7nm_PHY_CMN_CLK_CFG0 register */
|
||||
spinlock_t postdiv_lock;
|
||||
|
||||
int vco_delay;
|
||||
struct dsi_pll_config pll_configuration;
|
||||
struct dsi_pll_regs reg_setup;
|
||||
|
||||
/* private clocks: */
|
||||
struct clk_hw *out_div_clk_hw;
|
||||
struct clk_hw *bit_clk_hw;
|
||||
struct clk_hw *byte_clk_hw;
|
||||
struct clk_hw *by_2_bit_clk_hw;
|
||||
struct clk_hw *post_out_div_clk_hw;
|
||||
struct clk_hw *pclk_mux_hw;
|
||||
struct clk_hw *out_dsiclk_hw;
|
||||
|
||||
/* clock-provider: */
|
||||
struct clk_hw_onecell_data *hw_data;
|
||||
|
||||
struct pll_7nm_cached_state cached_state;
|
||||
|
||||
enum msm_dsi_phy_usecase uc;
|
||||
struct dsi_pll_7nm *slave;
|
||||
};
|
||||
|
||||
#define to_pll_7nm(x) container_of(x, struct dsi_pll_7nm, base)
|
||||
|
||||
/*
|
||||
* Global list of private DSI PLL struct pointers. We need this for Dual DSI
|
||||
* mode, where the master PLL's clk_ops needs access the slave's private data
|
||||
*/
|
||||
static struct dsi_pll_7nm *pll_7nm_list[DSI_MAX];
|
||||
|
||||
static void dsi_pll_setup_config(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
struct dsi_pll_config *config = &pll->pll_configuration;
|
||||
|
||||
config->ref_freq = pll->vco_ref_clk_rate;
|
||||
config->output_div = 1;
|
||||
config->dec_bits = 8;
|
||||
config->frac_bits = 18;
|
||||
config->lock_timer = 64;
|
||||
config->ssc_freq = 31500;
|
||||
config->ssc_offset = 4800;
|
||||
config->ssc_adj_per = 2;
|
||||
config->thresh_cycles = 32;
|
||||
config->refclk_cycles = 256;
|
||||
|
||||
config->div_override = false;
|
||||
config->ignore_frac = false;
|
||||
config->disable_prescaler = false;
|
||||
|
||||
/* TODO: ssc enable */
|
||||
config->enable_ssc = false;
|
||||
config->ssc_center = 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_calc_dec_frac(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
struct dsi_pll_config *config = &pll->pll_configuration;
|
||||
struct dsi_pll_regs *regs = &pll->reg_setup;
|
||||
u64 fref = pll->vco_ref_clk_rate;
|
||||
u64 pll_freq;
|
||||
u64 divider;
|
||||
u64 dec, dec_multiple;
|
||||
u32 frac;
|
||||
u64 multiplier;
|
||||
|
||||
pll_freq = pll->vco_current_rate;
|
||||
|
||||
if (config->disable_prescaler)
|
||||
divider = fref;
|
||||
else
|
||||
divider = fref * 2;
|
||||
|
||||
multiplier = 1 << config->frac_bits;
|
||||
dec_multiple = div_u64(pll_freq * multiplier, divider);
|
||||
div_u64_rem(dec_multiple, multiplier, &frac);
|
||||
|
||||
dec = div_u64(dec_multiple, multiplier);
|
||||
|
||||
if (pll->base.type != MSM_DSI_PHY_7NM_V4_1)
|
||||
regs->pll_clock_inverters = 0x28;
|
||||
else if (pll_freq <= 1000000000ULL)
|
||||
regs->pll_clock_inverters = 0xa0;
|
||||
else if (pll_freq <= 2500000000ULL)
|
||||
regs->pll_clock_inverters = 0x20;
|
||||
else if (pll_freq <= 3020000000ULL)
|
||||
regs->pll_clock_inverters = 0x00;
|
||||
else
|
||||
regs->pll_clock_inverters = 0x40;
|
||||
|
||||
regs->pll_lockdet_rate = config->lock_timer;
|
||||
regs->decimal_div_start = dec;
|
||||
regs->frac_div_start_low = (frac & 0xff);
|
||||
regs->frac_div_start_mid = (frac & 0xff00) >> 8;
|
||||
regs->frac_div_start_high = (frac & 0x30000) >> 16;
|
||||
}
|
||||
|
||||
#define SSC_CENTER BIT(0)
|
||||
#define SSC_EN BIT(1)
|
||||
|
||||
static void dsi_pll_calc_ssc(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
struct dsi_pll_config *config = &pll->pll_configuration;
|
||||
struct dsi_pll_regs *regs = &pll->reg_setup;
|
||||
u32 ssc_per;
|
||||
u32 ssc_mod;
|
||||
u64 ssc_step_size;
|
||||
u64 frac;
|
||||
|
||||
if (!config->enable_ssc) {
|
||||
DBG("SSC not enabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ssc_per = DIV_ROUND_CLOSEST(config->ref_freq, config->ssc_freq) / 2 - 1;
|
||||
ssc_mod = (ssc_per + 1) % (config->ssc_adj_per + 1);
|
||||
ssc_per -= ssc_mod;
|
||||
|
||||
frac = regs->frac_div_start_low |
|
||||
(regs->frac_div_start_mid << 8) |
|
||||
(regs->frac_div_start_high << 16);
|
||||
ssc_step_size = regs->decimal_div_start;
|
||||
ssc_step_size *= (1 << config->frac_bits);
|
||||
ssc_step_size += frac;
|
||||
ssc_step_size *= config->ssc_offset;
|
||||
ssc_step_size *= (config->ssc_adj_per + 1);
|
||||
ssc_step_size = div_u64(ssc_step_size, (ssc_per + 1));
|
||||
ssc_step_size = DIV_ROUND_CLOSEST_ULL(ssc_step_size, 1000000);
|
||||
|
||||
regs->ssc_div_per_low = ssc_per & 0xFF;
|
||||
regs->ssc_div_per_high = (ssc_per & 0xFF00) >> 8;
|
||||
regs->ssc_stepsize_low = (u32)(ssc_step_size & 0xFF);
|
||||
regs->ssc_stepsize_high = (u32)((ssc_step_size & 0xFF00) >> 8);
|
||||
regs->ssc_adjper_low = config->ssc_adj_per & 0xFF;
|
||||
regs->ssc_adjper_high = (config->ssc_adj_per & 0xFF00) >> 8;
|
||||
|
||||
regs->ssc_control = config->ssc_center ? SSC_CENTER : 0;
|
||||
|
||||
pr_debug("SCC: Dec:%d, frac:%llu, frac_bits:%d\n",
|
||||
regs->decimal_div_start, frac, config->frac_bits);
|
||||
pr_debug("SSC: div_per:0x%X, stepsize:0x%X, adjper:0x%X\n",
|
||||
ssc_per, (u32)ssc_step_size, config->ssc_adj_per);
|
||||
}
|
||||
|
||||
static void dsi_pll_ssc_commit(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->mmio;
|
||||
struct dsi_pll_regs *regs = &pll->reg_setup;
|
||||
|
||||
if (pll->pll_configuration.enable_ssc) {
|
||||
pr_debug("SSC is enabled\n");
|
||||
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_SSC_STEPSIZE_LOW_1,
|
||||
regs->ssc_stepsize_low);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_SSC_STEPSIZE_HIGH_1,
|
||||
regs->ssc_stepsize_high);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_SSC_DIV_PER_LOW_1,
|
||||
regs->ssc_div_per_low);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_SSC_DIV_PER_HIGH_1,
|
||||
regs->ssc_div_per_high);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_SSC_ADJPER_LOW_1,
|
||||
regs->ssc_adjper_low);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_SSC_ADJPER_HIGH_1,
|
||||
regs->ssc_adjper_high);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_SSC_CONTROL,
|
||||
SSC_EN | regs->ssc_control);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsi_pll_config_hzindep_reg(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->mmio;
|
||||
u8 analog_controls_five_1 = 0x01, vco_config_1 = 0x00;
|
||||
|
||||
if (pll->base.type == MSM_DSI_PHY_7NM_V4_1) {
|
||||
if (pll->vco_current_rate >= 3100000000ULL)
|
||||
analog_controls_five_1 = 0x03;
|
||||
|
||||
if (pll->vco_current_rate < 1520000000ULL)
|
||||
vco_config_1 = 0x08;
|
||||
else if (pll->vco_current_rate < 2990000000ULL)
|
||||
vco_config_1 = 0x01;
|
||||
}
|
||||
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_FIVE_1,
|
||||
analog_controls_five_1);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_VCO_CONFIG_1, vco_config_1);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_FIVE, 0x01);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_TWO, 0x03);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_ANALOG_CONTROLS_THREE, 0x00);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_DSM_DIVIDER, 0x00);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_FEEDBACK_DIVIDER, 0x4e);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_CALIBRATION_SETTINGS, 0x40);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_BAND_SEL_CAL_SETTINGS_THREE, 0xba);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_FREQ_DETECT_SETTINGS_ONE, 0x0c);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_OUTDIV, 0x00);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_CORE_OVERRIDE, 0x00);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_DIGITAL_TIMERS_TWO, 0x08);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_PROP_GAIN_RATE_1, 0x0a);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_BAND_SEL_RATE_1, 0xc0);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_INT_GAIN_IFILT_BAND_1, 0x84);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_INT_GAIN_IFILT_BAND_1, 0x82);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_FL_INT_GAIN_PFILT_BAND_1, 0x4c);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_LOCK_OVERRIDE, 0x80);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PFILT, 0x29);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PFILT, 0x2f);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_IFILT, 0x2a);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_IFILT,
|
||||
pll->base.type == MSM_DSI_PHY_7NM_V4_1 ? 0x3f : 0x22);
|
||||
|
||||
if (pll->base.type == MSM_DSI_PHY_7NM_V4_1) {
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PERF_OPTIMIZE, 0x22);
|
||||
if (pll->slave)
|
||||
pll_write(pll->slave->mmio + REG_DSI_7nm_PHY_PLL_PERF_OPTIMIZE, 0x22);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsi_pll_commit(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
void __iomem *base = pll->mmio;
|
||||
struct dsi_pll_regs *reg = &pll->reg_setup;
|
||||
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_CORE_INPUT_OVERRIDE, 0x12);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_DECIMAL_DIV_START_1, reg->decimal_div_start);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_LOW_1, reg->frac_div_start_low);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_MID_1, reg->frac_div_start_mid);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_HIGH_1, reg->frac_div_start_high);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_LOCKDET_RATE_1, reg->pll_lockdet_rate);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_PLL_LOCK_DELAY, 0x06);
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_CMODE_1, 0x10); /* TODO: 0x00 for CPHY */
|
||||
pll_write(base + REG_DSI_7nm_PHY_PLL_CLOCK_INVERTERS, reg->pll_clock_inverters);
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_vco_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
|
||||
DBG("DSI PLL%d rate=%lu, parent's=%lu", pll_7nm->id, rate,
|
||||
parent_rate);
|
||||
|
||||
pll_7nm->vco_current_rate = rate;
|
||||
pll_7nm->vco_ref_clk_rate = VCO_REF_CLK_RATE;
|
||||
|
||||
dsi_pll_setup_config(pll_7nm);
|
||||
|
||||
dsi_pll_calc_dec_frac(pll_7nm);
|
||||
|
||||
dsi_pll_calc_ssc(pll_7nm);
|
||||
|
||||
dsi_pll_commit(pll_7nm);
|
||||
|
||||
dsi_pll_config_hzindep_reg(pll_7nm);
|
||||
|
||||
dsi_pll_ssc_commit(pll_7nm);
|
||||
|
||||
/* flush, ensure all register writes are done*/
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_lock_status(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
int rc;
|
||||
u32 status = 0;
|
||||
u32 const delay_us = 100;
|
||||
u32 const timeout_us = 5000;
|
||||
|
||||
rc = readl_poll_timeout_atomic(pll->mmio +
|
||||
REG_DSI_7nm_PHY_PLL_COMMON_STATUS_ONE,
|
||||
status,
|
||||
((status & BIT(0)) > 0),
|
||||
delay_us,
|
||||
timeout_us);
|
||||
if (rc)
|
||||
pr_err("DSI PLL(%d) lock failed, status=0x%08x\n",
|
||||
pll->id, status);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_pll_bias(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data = pll_read(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CTRL_0);
|
||||
|
||||
pll_write(pll->mmio + REG_DSI_7nm_PHY_PLL_SYSTEM_MUXES, 0);
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CTRL_0, data & ~BIT(5));
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_pll_bias(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data = pll_read(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CTRL_0);
|
||||
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CTRL_0, data | BIT(5));
|
||||
pll_write(pll->mmio + REG_DSI_7nm_PHY_PLL_SYSTEM_MUXES, 0xc0);
|
||||
ndelay(250);
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_global_clk(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
data = pll_read(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CLK_CFG1, data & ~BIT(5));
|
||||
}
|
||||
|
||||
static void dsi_pll_enable_global_clk(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CTRL_3, 0x04);
|
||||
|
||||
data = pll_read(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_CLK_CFG1,
|
||||
data | BIT(5) | BIT(4));
|
||||
}
|
||||
|
||||
static void dsi_pll_phy_dig_reset(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
/*
|
||||
* Reset the PHY digital domain. This would be needed when
|
||||
* coming out of a CX or analog rail power collapse while
|
||||
* ensuring that the pads maintain LP00 or LP11 state
|
||||
*/
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_GLBL_DIGTOP_SPARE4, BIT(0));
|
||||
wmb(); /* Ensure that the reset is deasserted */
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_GLBL_DIGTOP_SPARE4, 0x0);
|
||||
wmb(); /* Ensure that the reset is deasserted */
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_vco_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
int rc;
|
||||
|
||||
dsi_pll_enable_pll_bias(pll_7nm);
|
||||
if (pll_7nm->slave)
|
||||
dsi_pll_enable_pll_bias(pll_7nm->slave);
|
||||
|
||||
/* Start PLL */
|
||||
pll_write(pll_7nm->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_PLL_CNTRL, 0x01);
|
||||
|
||||
/*
|
||||
* ensure all PLL configurations are written prior to checking
|
||||
* for PLL lock.
|
||||
*/
|
||||
wmb();
|
||||
|
||||
/* Check for PLL lock */
|
||||
rc = dsi_pll_7nm_lock_status(pll_7nm);
|
||||
if (rc) {
|
||||
pr_err("PLL(%d) lock failed\n", pll_7nm->id);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pll->pll_on = true;
|
||||
|
||||
/*
|
||||
* assert power on reset for PHY digital in case the PLL is
|
||||
* enabled after CX of analog domain power collapse. This needs
|
||||
* to be done before enabling the global clk.
|
||||
*/
|
||||
dsi_pll_phy_dig_reset(pll_7nm);
|
||||
if (pll_7nm->slave)
|
||||
dsi_pll_phy_dig_reset(pll_7nm->slave);
|
||||
|
||||
dsi_pll_enable_global_clk(pll_7nm);
|
||||
if (pll_7nm->slave)
|
||||
dsi_pll_enable_global_clk(pll_7nm->slave);
|
||||
|
||||
error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dsi_pll_disable_sub(struct dsi_pll_7nm *pll)
|
||||
{
|
||||
pll_write(pll->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_RBUF_CTRL, 0);
|
||||
dsi_pll_disable_pll_bias(pll);
|
||||
}
|
||||
|
||||
static void dsi_pll_7nm_vco_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
|
||||
/*
|
||||
* To avoid any stray glitches while abruptly powering down the PLL
|
||||
* make sure to gate the clock using the clock enable bit before
|
||||
* powering down the PLL
|
||||
*/
|
||||
dsi_pll_disable_global_clk(pll_7nm);
|
||||
pll_write(pll_7nm->phy_cmn_mmio + REG_DSI_7nm_PHY_CMN_PLL_CNTRL, 0);
|
||||
dsi_pll_disable_sub(pll_7nm);
|
||||
if (pll_7nm->slave) {
|
||||
dsi_pll_disable_global_clk(pll_7nm->slave);
|
||||
dsi_pll_disable_sub(pll_7nm->slave);
|
||||
}
|
||||
/* flush, ensure all register writes are done */
|
||||
wmb();
|
||||
pll->pll_on = false;
|
||||
}
|
||||
|
||||
static unsigned long dsi_pll_7nm_vco_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct msm_dsi_pll *pll = hw_clk_to_pll(hw);
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
struct dsi_pll_config *config = &pll_7nm->pll_configuration;
|
||||
void __iomem *base = pll_7nm->mmio;
|
||||
u64 ref_clk = pll_7nm->vco_ref_clk_rate;
|
||||
u64 vco_rate = 0x0;
|
||||
u64 multiplier;
|
||||
u32 frac;
|
||||
u32 dec;
|
||||
u64 pll_freq, tmp64;
|
||||
|
||||
dec = pll_read(base + REG_DSI_7nm_PHY_PLL_DECIMAL_DIV_START_1);
|
||||
dec &= 0xff;
|
||||
|
||||
frac = pll_read(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_LOW_1);
|
||||
frac |= ((pll_read(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_MID_1) &
|
||||
0xff) << 8);
|
||||
frac |= ((pll_read(base + REG_DSI_7nm_PHY_PLL_FRAC_DIV_START_HIGH_1) &
|
||||
0x3) << 16);
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* 1. Assumes prescaler is disabled
|
||||
*/
|
||||
multiplier = 1 << config->frac_bits;
|
||||
pll_freq = dec * (ref_clk * 2);
|
||||
tmp64 = (ref_clk * 2 * frac);
|
||||
pll_freq += div_u64(tmp64, multiplier);
|
||||
|
||||
vco_rate = pll_freq;
|
||||
|
||||
DBG("DSI PLL%d returning vco rate = %lu, dec = %x, frac = %x",
|
||||
pll_7nm->id, (unsigned long)vco_rate, dec, frac);
|
||||
|
||||
return (unsigned long)vco_rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops clk_ops_dsi_pll_7nm_vco = {
|
||||
.round_rate = msm_dsi_pll_helper_clk_round_rate,
|
||||
.set_rate = dsi_pll_7nm_vco_set_rate,
|
||||
.recalc_rate = dsi_pll_7nm_vco_recalc_rate,
|
||||
.prepare = dsi_pll_7nm_vco_prepare,
|
||||
.unprepare = dsi_pll_7nm_vco_unprepare,
|
||||
};
|
||||
|
||||
/*
|
||||
* PLL Callbacks
|
||||
*/
|
||||
|
||||
static void dsi_pll_7nm_save_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
struct pll_7nm_cached_state *cached = &pll_7nm->cached_state;
|
||||
void __iomem *phy_base = pll_7nm->phy_cmn_mmio;
|
||||
u32 cmn_clk_cfg0, cmn_clk_cfg1;
|
||||
|
||||
cached->pll_out_div = pll_read(pll_7nm->mmio +
|
||||
REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
cached->pll_out_div &= 0x3;
|
||||
|
||||
cmn_clk_cfg0 = pll_read(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG0);
|
||||
cached->bit_clk_div = cmn_clk_cfg0 & 0xf;
|
||||
cached->pix_clk_div = (cmn_clk_cfg0 & 0xf0) >> 4;
|
||||
|
||||
cmn_clk_cfg1 = pll_read(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
cached->pll_mux = cmn_clk_cfg1 & 0x3;
|
||||
|
||||
DBG("DSI PLL%d outdiv %x bit_clk_div %x pix_clk_div %x pll_mux %x",
|
||||
pll_7nm->id, cached->pll_out_div, cached->bit_clk_div,
|
||||
cached->pix_clk_div, cached->pll_mux);
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_restore_state(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
struct pll_7nm_cached_state *cached = &pll_7nm->cached_state;
|
||||
void __iomem *phy_base = pll_7nm->phy_cmn_mmio;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = pll_read(pll_7nm->mmio + REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_out_div;
|
||||
pll_write(pll_7nm->mmio + REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE, val);
|
||||
|
||||
pll_write(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG0,
|
||||
cached->bit_clk_div | (cached->pix_clk_div << 4));
|
||||
|
||||
val = pll_read(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG1);
|
||||
val &= ~0x3;
|
||||
val |= cached->pll_mux;
|
||||
pll_write(phy_base + REG_DSI_7nm_PHY_CMN_CLK_CFG1, val);
|
||||
|
||||
ret = dsi_pll_7nm_vco_set_rate(&pll->clk_hw, pll_7nm->vco_current_rate, pll_7nm->vco_ref_clk_rate);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pll_7nm->pdev->dev,
|
||||
"restore vco rate failed. ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DBG("DSI PLL%d", pll_7nm->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_set_usecase(struct msm_dsi_pll *pll,
|
||||
enum msm_dsi_phy_usecase uc)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
void __iomem *base = pll_7nm->phy_cmn_mmio;
|
||||
u32 data = 0x0; /* internal PLL */
|
||||
|
||||
DBG("DSI PLL%d", pll_7nm->id);
|
||||
|
||||
switch (uc) {
|
||||
case MSM_DSI_PHY_STANDALONE:
|
||||
break;
|
||||
case MSM_DSI_PHY_MASTER:
|
||||
pll_7nm->slave = pll_7nm_list[(pll_7nm->id + 1) % DSI_MAX];
|
||||
break;
|
||||
case MSM_DSI_PHY_SLAVE:
|
||||
data = 0x1; /* external PLL */
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set PLL src */
|
||||
pll_write(base + REG_DSI_7nm_PHY_CMN_CLK_CFG1, (data << 2));
|
||||
|
||||
pll_7nm->uc = uc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_pll_7nm_get_provider(struct msm_dsi_pll *pll,
|
||||
struct clk **byte_clk_provider,
|
||||
struct clk **pixel_clk_provider)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
struct clk_hw_onecell_data *hw_data = pll_7nm->hw_data;
|
||||
|
||||
DBG("DSI PLL%d", pll_7nm->id);
|
||||
|
||||
if (byte_clk_provider)
|
||||
*byte_clk_provider = hw_data->hws[DSI_BYTE_PLL_CLK]->clk;
|
||||
if (pixel_clk_provider)
|
||||
*pixel_clk_provider = hw_data->hws[DSI_PIXEL_PLL_CLK]->clk;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_pll_7nm_destroy(struct msm_dsi_pll *pll)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm = to_pll_7nm(pll);
|
||||
struct device *dev = &pll_7nm->pdev->dev;
|
||||
|
||||
DBG("DSI PLL%d", pll_7nm->id);
|
||||
of_clk_del_provider(dev->of_node);
|
||||
|
||||
clk_hw_unregister_divider(pll_7nm->out_dsiclk_hw);
|
||||
clk_hw_unregister_mux(pll_7nm->pclk_mux_hw);
|
||||
clk_hw_unregister_fixed_factor(pll_7nm->post_out_div_clk_hw);
|
||||
clk_hw_unregister_fixed_factor(pll_7nm->by_2_bit_clk_hw);
|
||||
clk_hw_unregister_fixed_factor(pll_7nm->byte_clk_hw);
|
||||
clk_hw_unregister_divider(pll_7nm->bit_clk_hw);
|
||||
clk_hw_unregister_divider(pll_7nm->out_div_clk_hw);
|
||||
clk_hw_unregister(&pll_7nm->base.clk_hw);
|
||||
}
|
||||
|
||||
/*
|
||||
* The post dividers and mux clocks are created using the standard divider and
|
||||
* mux API. Unlike the 14nm PHY, the slave PLL doesn't need its dividers/mux
|
||||
* state to follow the master PLL's divider/mux state. Therefore, we don't
|
||||
* require special clock ops that also configure the slave PLL registers
|
||||
*/
|
||||
static int pll_7nm_register(struct dsi_pll_7nm *pll_7nm)
|
||||
{
|
||||
char clk_name[32], parent[32], vco_name[32];
|
||||
char parent2[32], parent3[32], parent4[32];
|
||||
struct clk_init_data vco_init = {
|
||||
.parent_names = (const char *[]){ "bi_tcxo" },
|
||||
.num_parents = 1,
|
||||
.name = vco_name,
|
||||
.flags = CLK_IGNORE_UNUSED,
|
||||
.ops = &clk_ops_dsi_pll_7nm_vco,
|
||||
};
|
||||
struct device *dev = &pll_7nm->pdev->dev;
|
||||
struct clk_hw_onecell_data *hw_data;
|
||||
struct clk_hw *hw;
|
||||
int ret;
|
||||
|
||||
DBG("DSI%d", pll_7nm->id);
|
||||
|
||||
hw_data = devm_kzalloc(dev, sizeof(*hw_data) +
|
||||
NUM_PROVIDED_CLKS * sizeof(struct clk_hw *),
|
||||
GFP_KERNEL);
|
||||
if (!hw_data)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(vco_name, 32, "dsi%dvco_clk", pll_7nm->id);
|
||||
pll_7nm->base.clk_hw.init = &vco_init;
|
||||
|
||||
ret = clk_hw_register(dev, &pll_7nm->base.clk_hw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_out_div_clk", pll_7nm->id);
|
||||
snprintf(parent, 32, "dsi%dvco_clk", pll_7nm->id);
|
||||
|
||||
hw = clk_hw_register_divider(dev, clk_name,
|
||||
parent, CLK_SET_RATE_PARENT,
|
||||
pll_7nm->mmio +
|
||||
REG_DSI_7nm_PHY_PLL_PLL_OUTDIV_RATE,
|
||||
0, 2, CLK_DIVIDER_POWER_OF_TWO, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_base_clk_hw;
|
||||
}
|
||||
|
||||
pll_7nm->out_div_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_bit_clk", pll_7nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_7nm->id);
|
||||
|
||||
/* BIT CLK: DIV_CTRL_3_0 */
|
||||
hw = clk_hw_register_divider(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT,
|
||||
pll_7nm->phy_cmn_mmio +
|
||||
REG_DSI_7nm_PHY_CMN_CLK_CFG0,
|
||||
0, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_7nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_out_div_clk_hw;
|
||||
}
|
||||
|
||||
pll_7nm->bit_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_byteclk", pll_7nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_7nm->id);
|
||||
|
||||
/* DSI Byte clock = VCO_CLK / OUT_DIV / BIT_DIV / 8 */
|
||||
hw = clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
CLK_SET_RATE_PARENT, 1, 8);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_bit_clk_hw;
|
||||
}
|
||||
|
||||
pll_7nm->byte_clk_hw = hw;
|
||||
hw_data->hws[DSI_BYTE_PLL_CLK] = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_by_2_bit_clk", pll_7nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_7nm->id);
|
||||
|
||||
hw = clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 2);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_byte_clk_hw;
|
||||
}
|
||||
|
||||
pll_7nm->by_2_bit_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pll_post_out_div_clk", pll_7nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_out_div_clk", pll_7nm->id);
|
||||
|
||||
hw = clk_hw_register_fixed_factor(dev, clk_name, parent,
|
||||
0, 1, 4);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_by_2_bit_clk_hw;
|
||||
}
|
||||
|
||||
pll_7nm->post_out_div_clk_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_pclk_mux", pll_7nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pll_bit_clk", pll_7nm->id);
|
||||
snprintf(parent2, 32, "dsi%d_pll_by_2_bit_clk", pll_7nm->id);
|
||||
snprintf(parent3, 32, "dsi%d_pll_out_div_clk", pll_7nm->id);
|
||||
snprintf(parent4, 32, "dsi%d_pll_post_out_div_clk", pll_7nm->id);
|
||||
|
||||
hw = clk_hw_register_mux(dev, clk_name,
|
||||
((const char *[]){
|
||||
parent, parent2, parent3, parent4
|
||||
}), 4, 0, pll_7nm->phy_cmn_mmio +
|
||||
REG_DSI_7nm_PHY_CMN_CLK_CFG1,
|
||||
0, 2, 0, NULL);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_post_out_div_clk_hw;
|
||||
}
|
||||
|
||||
pll_7nm->pclk_mux_hw = hw;
|
||||
|
||||
snprintf(clk_name, 32, "dsi%d_phy_pll_out_dsiclk", pll_7nm->id);
|
||||
snprintf(parent, 32, "dsi%d_pclk_mux", pll_7nm->id);
|
||||
|
||||
/* PIX CLK DIV : DIV_CTRL_7_4*/
|
||||
hw = clk_hw_register_divider(dev, clk_name, parent,
|
||||
0, pll_7nm->phy_cmn_mmio +
|
||||
REG_DSI_7nm_PHY_CMN_CLK_CFG0,
|
||||
4, 4, CLK_DIVIDER_ONE_BASED,
|
||||
&pll_7nm->postdiv_lock);
|
||||
if (IS_ERR(hw)) {
|
||||
ret = PTR_ERR(hw);
|
||||
goto err_pclk_mux_hw;
|
||||
}
|
||||
|
||||
pll_7nm->out_dsiclk_hw = hw;
|
||||
hw_data->hws[DSI_PIXEL_PLL_CLK] = hw;
|
||||
|
||||
hw_data->num = NUM_PROVIDED_CLKS;
|
||||
pll_7nm->hw_data = hw_data;
|
||||
|
||||
ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
|
||||
pll_7nm->hw_data);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "failed to register clk provider: %d\n", ret);
|
||||
goto err_dsiclk_hw;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_dsiclk_hw:
|
||||
clk_hw_unregister_divider(pll_7nm->out_dsiclk_hw);
|
||||
err_pclk_mux_hw:
|
||||
clk_hw_unregister_mux(pll_7nm->pclk_mux_hw);
|
||||
err_post_out_div_clk_hw:
|
||||
clk_hw_unregister_fixed_factor(pll_7nm->post_out_div_clk_hw);
|
||||
err_by_2_bit_clk_hw:
|
||||
clk_hw_unregister_fixed_factor(pll_7nm->by_2_bit_clk_hw);
|
||||
err_byte_clk_hw:
|
||||
clk_hw_unregister_fixed_factor(pll_7nm->byte_clk_hw);
|
||||
err_bit_clk_hw:
|
||||
clk_hw_unregister_divider(pll_7nm->bit_clk_hw);
|
||||
err_out_div_clk_hw:
|
||||
clk_hw_unregister_divider(pll_7nm->out_div_clk_hw);
|
||||
err_base_clk_hw:
|
||||
clk_hw_unregister(&pll_7nm->base.clk_hw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct msm_dsi_pll *msm_dsi_pll_7nm_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id)
|
||||
{
|
||||
struct dsi_pll_7nm *pll_7nm;
|
||||
struct msm_dsi_pll *pll;
|
||||
int ret;
|
||||
|
||||
pll_7nm = devm_kzalloc(&pdev->dev, sizeof(*pll_7nm), GFP_KERNEL);
|
||||
if (!pll_7nm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
DBG("DSI PLL%d", id);
|
||||
|
||||
pll_7nm->pdev = pdev;
|
||||
pll_7nm->id = id;
|
||||
pll_7nm_list[id] = pll_7nm;
|
||||
|
||||
pll_7nm->phy_cmn_mmio = msm_ioremap(pdev, "dsi_phy", "DSI_PHY");
|
||||
if (IS_ERR_OR_NULL(pll_7nm->phy_cmn_mmio)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to map CMN PHY base\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
pll_7nm->mmio = msm_ioremap(pdev, "dsi_pll", "DSI_PLL");
|
||||
if (IS_ERR_OR_NULL(pll_7nm->mmio)) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to map PLL base\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
spin_lock_init(&pll_7nm->postdiv_lock);
|
||||
|
||||
pll = &pll_7nm->base;
|
||||
pll->min_rate = 1000000000UL;
|
||||
pll->max_rate = 3500000000UL;
|
||||
if (type == MSM_DSI_PHY_7NM_V4_1) {
|
||||
pll->min_rate = 600000000UL;
|
||||
pll->max_rate = (unsigned long)5000000000ULL;
|
||||
/* workaround for max rate overflowing on 32-bit builds: */
|
||||
pll->max_rate = max(pll->max_rate, 0xffffffffUL);
|
||||
}
|
||||
pll->get_provider = dsi_pll_7nm_get_provider;
|
||||
pll->destroy = dsi_pll_7nm_destroy;
|
||||
pll->save_state = dsi_pll_7nm_save_state;
|
||||
pll->restore_state = dsi_pll_7nm_restore_state;
|
||||
pll->set_usecase = dsi_pll_7nm_set_usecase;
|
||||
|
||||
pll_7nm->vco_delay = 1;
|
||||
|
||||
ret = pll_7nm_register(pll_7nm);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(&pdev->dev, "failed to register PLL: %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/* TODO: Remove this when we have proper display handover support */
|
||||
msm_dsi_pll_save_state(pll);
|
||||
|
||||
return pll;
|
||||
}
|
@ -111,23 +111,15 @@ static const struct file_operations msm_gpu_fops = {
|
||||
static int msm_gem_show(struct drm_device *dev, struct seq_file *m)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_gpu *gpu = priv->gpu;
|
||||
int ret;
|
||||
|
||||
ret = mutex_lock_interruptible(&priv->mm_lock);
|
||||
ret = mutex_lock_interruptible(&priv->obj_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (gpu) {
|
||||
seq_printf(m, "Active Objects (%s):\n", gpu->name);
|
||||
msm_gem_describe_objects(&gpu->active_list, m);
|
||||
}
|
||||
msm_gem_describe_objects(&priv->objects, m);
|
||||
|
||||
seq_printf(m, "Inactive Objects:\n");
|
||||
msm_gem_describe_objects(&priv->inactive_dontneed, m);
|
||||
msm_gem_describe_objects(&priv->inactive_willneed, m);
|
||||
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
mutex_unlock(&priv->obj_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
* GEM object's debug name
|
||||
* - 1.5.0 - Add SUBMITQUERY_QUERY ioctl
|
||||
* - 1.6.0 - Syncobj support
|
||||
* - 1.7.0 - Add MSM_PARAM_SUSPENDS to access suspend count
|
||||
*/
|
||||
#define MSM_VERSION_MAJOR 1
|
||||
#define MSM_VERSION_MINOR 6
|
||||
@ -446,8 +447,12 @@ static int msm_drm_init(struct device *dev, const struct drm_driver *drv)
|
||||
|
||||
priv->wq = alloc_ordered_workqueue("msm", 0);
|
||||
|
||||
INIT_LIST_HEAD(&priv->objects);
|
||||
mutex_init(&priv->obj_lock);
|
||||
|
||||
INIT_LIST_HEAD(&priv->inactive_willneed);
|
||||
INIT_LIST_HEAD(&priv->inactive_dontneed);
|
||||
INIT_LIST_HEAD(&priv->inactive_unpinned);
|
||||
mutex_init(&priv->mm_lock);
|
||||
|
||||
/* Teach lockdep about lock ordering wrt. shrinker: */
|
||||
@ -1182,10 +1187,11 @@ static int compare_name_mdp(struct device *dev, void *data)
|
||||
return (strstr(dev_name(dev), "mdp") != NULL);
|
||||
}
|
||||
|
||||
static int add_display_components(struct device *dev,
|
||||
static int add_display_components(struct platform_device *pdev,
|
||||
struct component_match **matchptr)
|
||||
{
|
||||
struct device *mdp_dev;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
@ -1194,9 +1200,9 @@ static int add_display_components(struct device *dev,
|
||||
* Populate the children devices, find the MDP5/DPU node, and then add
|
||||
* the interfaces to our components list.
|
||||
*/
|
||||
if (of_device_is_compatible(dev->of_node, "qcom,mdss") ||
|
||||
of_device_is_compatible(dev->of_node, "qcom,sdm845-mdss") ||
|
||||
of_device_is_compatible(dev->of_node, "qcom,sc7180-mdss")) {
|
||||
switch (get_mdp_ver(pdev)) {
|
||||
case KMS_MDP5:
|
||||
case KMS_DPU:
|
||||
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "failed to populate children devices\n");
|
||||
@ -1215,9 +1221,11 @@ static int add_display_components(struct device *dev,
|
||||
/* add the MDP component itself */
|
||||
drm_of_component_match_add(dev, matchptr, compare_of,
|
||||
mdp_dev->of_node);
|
||||
} else {
|
||||
break;
|
||||
case KMS_MDP4:
|
||||
/* MDP4 */
|
||||
mdp_dev = dev;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = add_components_mdp(mdp_dev, matchptr);
|
||||
@ -1282,7 +1290,7 @@ static int msm_pdev_probe(struct platform_device *pdev)
|
||||
int ret;
|
||||
|
||||
if (get_mdp_ver(pdev)) {
|
||||
ret = add_display_components(&pdev->dev, &match);
|
||||
ret = add_display_components(pdev, &match);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -1333,6 +1341,9 @@ static const struct of_device_id dt_match[] = {
|
||||
{ .compatible = "qcom,mdss", .data = (void *)KMS_MDP5 },
|
||||
{ .compatible = "qcom,sdm845-mdss", .data = (void *)KMS_DPU },
|
||||
{ .compatible = "qcom,sc7180-mdss", .data = (void *)KMS_DPU },
|
||||
{ .compatible = "qcom,sc7280-mdss", .data = (void *)KMS_DPU },
|
||||
{ .compatible = "qcom,sm8150-mdss", .data = (void *)KMS_DPU },
|
||||
{ .compatible = "qcom,sm8250-mdss", .data = (void *)KMS_DPU },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dt_match);
|
||||
|
@ -174,20 +174,35 @@ struct msm_drm_private {
|
||||
struct msm_rd_state *hangrd; /* debugfs to dump hanging submits */
|
||||
struct msm_perf_state *perf;
|
||||
|
||||
/*
|
||||
* Lists of inactive GEM objects. Every bo is either in one of the
|
||||
/**
|
||||
* List of all GEM objects (mainly for debugfs, protected by obj_lock
|
||||
* (acquire before per GEM object lock)
|
||||
*/
|
||||
struct list_head objects;
|
||||
struct mutex obj_lock;
|
||||
|
||||
/**
|
||||
* LRUs of inactive GEM objects. Every bo is either in one of the
|
||||
* inactive lists (depending on whether or not it is shrinkable) or
|
||||
* gpu->active_list (for the gpu it is active on[1])
|
||||
* gpu->active_list (for the gpu it is active on[1]), or transiently
|
||||
* on a temporary list as the shrinker is running.
|
||||
*
|
||||
* These lists are protected by mm_lock. If struct_mutex is involved, it
|
||||
* should be aquired prior to mm_lock. One should *not* hold mm_lock in
|
||||
* Note that inactive_willneed also contains pinned and vmap'd bos,
|
||||
* but the number of pinned-but-not-active objects is small (scanout
|
||||
* buffers, ringbuffer, etc).
|
||||
*
|
||||
* These lists are protected by mm_lock (which should be acquired
|
||||
* before per GEM object lock). One should *not* hold mm_lock in
|
||||
* get_pages()/vmap()/etc paths, as they can trigger the shrinker.
|
||||
*
|
||||
* [1] if someone ever added support for the old 2d cores, there could be
|
||||
* more than one gpu object
|
||||
*/
|
||||
struct list_head inactive_willneed; /* inactive + !shrinkable */
|
||||
struct list_head inactive_dontneed; /* inactive + shrinkable */
|
||||
struct list_head inactive_willneed; /* inactive + potentially unpin/evictable */
|
||||
struct list_head inactive_dontneed; /* inactive + shrinkable */
|
||||
struct list_head inactive_unpinned; /* inactive + purged or unpinned */
|
||||
long shrinkable_count; /* write access under mm_lock */
|
||||
long evictable_count; /* write access under mm_lock */
|
||||
struct mutex mm_lock;
|
||||
|
||||
struct workqueue_struct *wq;
|
||||
|
@ -33,6 +33,7 @@ static const struct drm_framebuffer_funcs msm_framebuffer_funcs = {
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m)
|
||||
{
|
||||
struct msm_gem_stats stats = {};
|
||||
int i, n = fb->format->num_planes;
|
||||
|
||||
seq_printf(m, "fb: %dx%d@%4.4s (%2d, ID:%d)\n",
|
||||
@ -42,7 +43,7 @@ void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m)
|
||||
for (i = 0; i < n; i++) {
|
||||
seq_printf(m, " %d: offset=%d pitch=%d, obj: ",
|
||||
i, fb->offsets[i], fb->pitches[i]);
|
||||
msm_gem_describe(fb->obj[i], m);
|
||||
msm_gem_describe(fb->obj[i], m, &stats);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -96,7 +96,7 @@ static struct page **get_pages(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
if (!msm_obj->pages) {
|
||||
struct drm_device *dev = obj->dev;
|
||||
@ -130,6 +130,9 @@ static struct page **get_pages(struct drm_gem_object *obj)
|
||||
*/
|
||||
if (msm_obj->flags & (MSM_BO_WC|MSM_BO_UNCACHED))
|
||||
sync_for_device(msm_obj);
|
||||
|
||||
GEM_WARN_ON(msm_obj->active_count);
|
||||
update_inactive(msm_obj);
|
||||
}
|
||||
|
||||
return msm_obj->pages;
|
||||
@ -162,6 +165,7 @@ static void put_pages(struct drm_gem_object *obj)
|
||||
|
||||
sg_free_table(msm_obj->sgt);
|
||||
kfree(msm_obj->sgt);
|
||||
msm_obj->sgt = NULL;
|
||||
}
|
||||
|
||||
if (use_pages(obj))
|
||||
@ -180,7 +184,7 @@ struct page **msm_gem_get_pages(struct drm_gem_object *obj)
|
||||
|
||||
msm_gem_lock(obj);
|
||||
|
||||
if (WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED)) {
|
||||
if (GEM_WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED)) {
|
||||
msm_gem_unlock(obj);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
@ -256,7 +260,7 @@ static vm_fault_t msm_gem_fault(struct vm_fault *vmf)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED)) {
|
||||
if (GEM_WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED)) {
|
||||
msm_gem_unlock(obj);
|
||||
return VM_FAULT_SIGBUS;
|
||||
}
|
||||
@ -289,7 +293,7 @@ static uint64_t mmap_offset(struct drm_gem_object *obj)
|
||||
struct drm_device *dev = obj->dev;
|
||||
int ret;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
/* Make it mmapable */
|
||||
ret = drm_gem_create_mmap_offset(obj);
|
||||
@ -318,7 +322,7 @@ static struct msm_gem_vma *add_vma(struct drm_gem_object *obj,
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_gem_vma *vma;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
vma = kzalloc(sizeof(*vma), GFP_KERNEL);
|
||||
if (!vma)
|
||||
@ -337,7 +341,7 @@ static struct msm_gem_vma *lookup_vma(struct drm_gem_object *obj,
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_gem_vma *vma;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
list_for_each_entry(vma, &msm_obj->vmas, list) {
|
||||
if (vma->aspace == aspace)
|
||||
@ -356,19 +360,25 @@ static void del_vma(struct msm_gem_vma *vma)
|
||||
kfree(vma);
|
||||
}
|
||||
|
||||
/* Called with msm_obj locked */
|
||||
/**
|
||||
* If close is true, this also closes the VMA (releasing the allocated
|
||||
* iova range) in addition to removing the iommu mapping. In the eviction
|
||||
* case (!close), we keep the iova allocated, but only remove the iommu
|
||||
* mapping.
|
||||
*/
|
||||
static void
|
||||
put_iova_spaces(struct drm_gem_object *obj)
|
||||
put_iova_spaces(struct drm_gem_object *obj, bool close)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_gem_vma *vma;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
list_for_each_entry(vma, &msm_obj->vmas, list) {
|
||||
if (vma->aspace) {
|
||||
msm_gem_purge_vma(vma->aspace, vma);
|
||||
msm_gem_close_vma(vma->aspace, vma);
|
||||
if (close)
|
||||
msm_gem_close_vma(vma->aspace, vma);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -380,7 +390,7 @@ put_iova_vmas(struct drm_gem_object *obj)
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_gem_vma *vma, *tmp;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
list_for_each_entry_safe(vma, tmp, &msm_obj->vmas, list) {
|
||||
del_vma(vma);
|
||||
@ -394,7 +404,7 @@ static int get_iova_locked(struct drm_gem_object *obj,
|
||||
struct msm_gem_vma *vma;
|
||||
int ret = 0;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
vma = lookup_vma(obj, aspace);
|
||||
|
||||
@ -421,7 +431,7 @@ static int msm_gem_pin_iova(struct drm_gem_object *obj,
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_gem_vma *vma;
|
||||
struct page **pages;
|
||||
int prot = IOMMU_READ;
|
||||
int ret, prot = IOMMU_READ;
|
||||
|
||||
if (!(msm_obj->flags & MSM_BO_GPU_READONLY))
|
||||
prot |= IOMMU_WRITE;
|
||||
@ -429,21 +439,26 @@ static int msm_gem_pin_iova(struct drm_gem_object *obj,
|
||||
if (msm_obj->flags & MSM_BO_MAP_PRIV)
|
||||
prot |= IOMMU_PRIV;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
if (WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED))
|
||||
if (GEM_WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED))
|
||||
return -EBUSY;
|
||||
|
||||
vma = lookup_vma(obj, aspace);
|
||||
if (WARN_ON(!vma))
|
||||
if (GEM_WARN_ON(!vma))
|
||||
return -EINVAL;
|
||||
|
||||
pages = get_pages(obj);
|
||||
if (IS_ERR(pages))
|
||||
return PTR_ERR(pages);
|
||||
|
||||
return msm_gem_map_vma(aspace, vma, prot,
|
||||
ret = msm_gem_map_vma(aspace, vma, prot,
|
||||
msm_obj->sgt, obj->size >> PAGE_SHIFT);
|
||||
|
||||
if (!ret)
|
||||
msm_obj->pin_count++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_and_pin_iova_range_locked(struct drm_gem_object *obj,
|
||||
@ -453,7 +468,7 @@ static int get_and_pin_iova_range_locked(struct drm_gem_object *obj,
|
||||
u64 local;
|
||||
int ret;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
ret = get_iova_locked(obj, aspace, &local,
|
||||
range_start, range_end);
|
||||
@ -524,7 +539,7 @@ uint64_t msm_gem_iova(struct drm_gem_object *obj,
|
||||
msm_gem_lock(obj);
|
||||
vma = lookup_vma(obj, aspace);
|
||||
msm_gem_unlock(obj);
|
||||
WARN_ON(!vma);
|
||||
GEM_WARN_ON(!vma);
|
||||
|
||||
return vma ? vma->iova : 0;
|
||||
}
|
||||
@ -535,14 +550,21 @@ uint64_t msm_gem_iova(struct drm_gem_object *obj,
|
||||
void msm_gem_unpin_iova_locked(struct drm_gem_object *obj,
|
||||
struct msm_gem_address_space *aspace)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_gem_vma *vma;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
vma = lookup_vma(obj, aspace);
|
||||
|
||||
if (!WARN_ON(!vma))
|
||||
if (!GEM_WARN_ON(!vma)) {
|
||||
msm_gem_unmap_vma(aspace, vma);
|
||||
|
||||
msm_obj->pin_count--;
|
||||
GEM_WARN_ON(msm_obj->pin_count < 0);
|
||||
|
||||
update_inactive(msm_obj);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -593,12 +615,12 @@ static void *get_vaddr(struct drm_gem_object *obj, unsigned madv)
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
int ret = 0;
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
if (obj->import_attach)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
if (WARN_ON(msm_obj->madv > madv)) {
|
||||
if (GEM_WARN_ON(msm_obj->madv > madv)) {
|
||||
DRM_DEV_ERROR(obj->dev->dev, "Invalid madv state: %u vs %u\n",
|
||||
msm_obj->madv, madv);
|
||||
return ERR_PTR(-EBUSY);
|
||||
@ -664,8 +686,8 @@ void msm_gem_put_vaddr_locked(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
WARN_ON(msm_obj->vmap_count < 1);
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(msm_obj->vmap_count < 1);
|
||||
|
||||
msm_obj->vmap_count--;
|
||||
}
|
||||
@ -707,20 +729,23 @@ void msm_gem_purge(struct drm_gem_object *obj)
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
WARN_ON(!is_purgeable(msm_obj));
|
||||
WARN_ON(obj->import_attach);
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!is_purgeable(msm_obj));
|
||||
|
||||
put_iova_spaces(obj);
|
||||
/* Get rid of any iommu mapping(s): */
|
||||
put_iova_spaces(obj, true);
|
||||
|
||||
msm_gem_vunmap(obj);
|
||||
|
||||
drm_vma_node_unmap(&obj->vma_node, dev->anon_inode->i_mapping);
|
||||
|
||||
put_pages(obj);
|
||||
|
||||
put_iova_vmas(obj);
|
||||
|
||||
msm_obj->madv = __MSM_MADV_PURGED;
|
||||
update_inactive(msm_obj);
|
||||
|
||||
drm_vma_node_unmap(&obj->vma_node, dev->anon_inode->i_mapping);
|
||||
drm_gem_free_mmap_offset(obj);
|
||||
|
||||
/* Our goal here is to return as much of the memory as
|
||||
@ -734,13 +759,36 @@ void msm_gem_purge(struct drm_gem_object *obj)
|
||||
0, (loff_t)-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpin the backing pages and make them available to be swapped out.
|
||||
*/
|
||||
void msm_gem_evict(struct drm_gem_object *obj)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(is_unevictable(msm_obj));
|
||||
GEM_WARN_ON(!msm_obj->evictable);
|
||||
GEM_WARN_ON(msm_obj->active_count);
|
||||
|
||||
/* Get rid of any iommu mapping(s): */
|
||||
put_iova_spaces(obj, false);
|
||||
|
||||
drm_vma_node_unmap(&obj->vma_node, dev->anon_inode->i_mapping);
|
||||
|
||||
put_pages(obj);
|
||||
|
||||
update_inactive(msm_obj);
|
||||
}
|
||||
|
||||
void msm_gem_vunmap(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
if (!msm_obj->vaddr || WARN_ON(!is_vunmapable(msm_obj)))
|
||||
if (!msm_obj->vaddr || GEM_WARN_ON(!is_vunmapable(msm_obj)))
|
||||
return;
|
||||
|
||||
vunmap(msm_obj->vaddr);
|
||||
@ -788,12 +836,16 @@ void msm_gem_active_get(struct drm_gem_object *obj, struct msm_gpu *gpu)
|
||||
struct msm_drm_private *priv = obj->dev->dev_private;
|
||||
|
||||
might_sleep();
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED);
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(msm_obj->madv != MSM_MADV_WILLNEED);
|
||||
GEM_WARN_ON(msm_obj->dontneed);
|
||||
GEM_WARN_ON(!msm_obj->sgt);
|
||||
|
||||
if (msm_obj->active_count++ == 0) {
|
||||
mutex_lock(&priv->mm_lock);
|
||||
list_del_init(&msm_obj->mm_list);
|
||||
if (msm_obj->evictable)
|
||||
mark_unevictable(msm_obj);
|
||||
list_del(&msm_obj->mm_list);
|
||||
list_add_tail(&msm_obj->mm_list, &gpu->active_list);
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
}
|
||||
@ -804,7 +856,7 @@ void msm_gem_active_put(struct drm_gem_object *obj)
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
might_sleep();
|
||||
WARN_ON(!msm_gem_is_locked(obj));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(obj));
|
||||
|
||||
if (--msm_obj->active_count == 0) {
|
||||
update_inactive(msm_obj);
|
||||
@ -815,14 +867,29 @@ static void update_inactive(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
struct msm_drm_private *priv = msm_obj->base.dev->dev_private;
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
WARN_ON(msm_obj->active_count != 0);
|
||||
GEM_WARN_ON(!msm_gem_is_locked(&msm_obj->base));
|
||||
|
||||
list_del_init(&msm_obj->mm_list);
|
||||
if (msm_obj->madv == MSM_MADV_WILLNEED)
|
||||
if (msm_obj->active_count != 0)
|
||||
return;
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
|
||||
if (msm_obj->dontneed)
|
||||
mark_unpurgeable(msm_obj);
|
||||
if (msm_obj->evictable)
|
||||
mark_unevictable(msm_obj);
|
||||
|
||||
list_del(&msm_obj->mm_list);
|
||||
if ((msm_obj->madv == MSM_MADV_WILLNEED) && msm_obj->sgt) {
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_willneed);
|
||||
else
|
||||
mark_evictable(msm_obj);
|
||||
} else if (msm_obj->madv == MSM_MADV_DONTNEED) {
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_dontneed);
|
||||
mark_purgeable(msm_obj);
|
||||
} else {
|
||||
GEM_WARN_ON((msm_obj->madv != __MSM_MADV_PURGED) && msm_obj->sgt);
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_unpinned);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
}
|
||||
@ -863,7 +930,8 @@ static void describe_fence(struct dma_fence *fence, const char *type,
|
||||
fence->seqno);
|
||||
}
|
||||
|
||||
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m)
|
||||
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m,
|
||||
struct msm_gem_stats *stats)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct dma_resv *robj = obj->resv;
|
||||
@ -875,11 +943,28 @@ void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m)
|
||||
|
||||
msm_gem_lock(obj);
|
||||
|
||||
stats->all.count++;
|
||||
stats->all.size += obj->size;
|
||||
|
||||
if (is_active(msm_obj)) {
|
||||
stats->active.count++;
|
||||
stats->active.size += obj->size;
|
||||
}
|
||||
|
||||
if (msm_obj->pages) {
|
||||
stats->resident.count++;
|
||||
stats->resident.size += obj->size;
|
||||
}
|
||||
|
||||
switch (msm_obj->madv) {
|
||||
case __MSM_MADV_PURGED:
|
||||
stats->purged.count++;
|
||||
stats->purged.size += obj->size;
|
||||
madv = " purged";
|
||||
break;
|
||||
case MSM_MADV_DONTNEED:
|
||||
stats->purgeable.count++;
|
||||
stats->purgeable.size += obj->size;
|
||||
madv = " purgeable";
|
||||
break;
|
||||
case MSM_MADV_WILLNEED:
|
||||
@ -946,20 +1031,26 @@ void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m)
|
||||
|
||||
void msm_gem_describe_objects(struct list_head *list, struct seq_file *m)
|
||||
{
|
||||
struct msm_gem_stats stats = {};
|
||||
struct msm_gem_object *msm_obj;
|
||||
int count = 0;
|
||||
size_t size = 0;
|
||||
|
||||
seq_puts(m, " flags id ref offset kaddr size madv name\n");
|
||||
list_for_each_entry(msm_obj, list, mm_list) {
|
||||
list_for_each_entry(msm_obj, list, node) {
|
||||
struct drm_gem_object *obj = &msm_obj->base;
|
||||
seq_puts(m, " ");
|
||||
msm_gem_describe(obj, m);
|
||||
count++;
|
||||
size += obj->size;
|
||||
msm_gem_describe(obj, m, &stats);
|
||||
}
|
||||
|
||||
seq_printf(m, "Total %d objects, %zu bytes\n", count, size);
|
||||
seq_printf(m, "Total: %4d objects, %9zu bytes\n",
|
||||
stats.all.count, stats.all.size);
|
||||
seq_printf(m, "Active: %4d objects, %9zu bytes\n",
|
||||
stats.active.count, stats.active.size);
|
||||
seq_printf(m, "Resident: %4d objects, %9zu bytes\n",
|
||||
stats.resident.count, stats.resident.size);
|
||||
seq_printf(m, "Purgeable: %4d objects, %9zu bytes\n",
|
||||
stats.purgeable.count, stats.purgeable.size);
|
||||
seq_printf(m, "Purged: %4d objects, %9zu bytes\n",
|
||||
stats.purged.count, stats.purged.size);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -970,19 +1061,25 @@ void msm_gem_free_object(struct drm_gem_object *obj)
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
|
||||
mutex_lock(&priv->obj_lock);
|
||||
list_del(&msm_obj->node);
|
||||
mutex_unlock(&priv->obj_lock);
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
if (msm_obj->dontneed)
|
||||
mark_unpurgeable(msm_obj);
|
||||
list_del(&msm_obj->mm_list);
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
|
||||
msm_gem_lock(obj);
|
||||
|
||||
/* object should not be on active list: */
|
||||
WARN_ON(is_active(msm_obj));
|
||||
GEM_WARN_ON(is_active(msm_obj));
|
||||
|
||||
put_iova_spaces(obj);
|
||||
put_iova_spaces(obj, true);
|
||||
|
||||
if (obj->import_attach) {
|
||||
WARN_ON(msm_obj->vaddr);
|
||||
GEM_WARN_ON(msm_obj->vaddr);
|
||||
|
||||
/* Don't drop the pages for imported dmabuf, as they are not
|
||||
* ours, just free the array we allocated:
|
||||
@ -1098,7 +1195,7 @@ static struct drm_gem_object *_msm_gem_new(struct drm_device *dev,
|
||||
else if ((flags & (MSM_BO_STOLEN | MSM_BO_SCANOUT)) && priv->vram.size)
|
||||
use_vram = true;
|
||||
|
||||
if (WARN_ON(use_vram && !priv->vram.size))
|
||||
if (GEM_WARN_ON(use_vram && !priv->vram.size))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/* Disallow zero sized objects as they make the underlying
|
||||
@ -1153,10 +1250,13 @@ static struct drm_gem_object *_msm_gem_new(struct drm_device *dev,
|
||||
}
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
/* Initially obj is idle, obj->madv == WILLNEED: */
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_willneed);
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_unpinned);
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
|
||||
mutex_lock(&priv->obj_lock);
|
||||
list_add_tail(&msm_obj->node, &priv->objects);
|
||||
mutex_unlock(&priv->obj_lock);
|
||||
|
||||
return obj;
|
||||
|
||||
fail:
|
||||
@ -1224,9 +1324,13 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev,
|
||||
msm_gem_unlock(obj);
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_willneed);
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_unpinned);
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
|
||||
mutex_lock(&priv->obj_lock);
|
||||
list_add_tail(&msm_obj->node, &priv->objects);
|
||||
mutex_unlock(&priv->obj_lock);
|
||||
|
||||
return obj;
|
||||
|
||||
fail:
|
||||
|
@ -11,6 +11,11 @@
|
||||
#include <linux/dma-resv.h>
|
||||
#include "msm_drv.h"
|
||||
|
||||
/* Make all GEM related WARN_ON()s ratelimited.. when things go wrong they
|
||||
* tend to go wrong 1000s of times in a short timespan.
|
||||
*/
|
||||
#define GEM_WARN_ON(x) WARN_RATELIMIT(x, "%s", __stringify(x))
|
||||
|
||||
/* Additional internal-use only BO flags: */
|
||||
#define MSM_BO_STOLEN 0x10000000 /* try to use stolen/splash memory */
|
||||
#define MSM_BO_MAP_PRIV 0x20000000 /* use IOMMU_PRIV when mapping */
|
||||
@ -50,18 +55,35 @@ struct msm_gem_object {
|
||||
*/
|
||||
uint8_t madv;
|
||||
|
||||
/**
|
||||
* Is object on inactive_dontneed list (ie. counted in priv->shrinkable_count)?
|
||||
*/
|
||||
bool dontneed : 1;
|
||||
|
||||
/**
|
||||
* Is object evictable (ie. counted in priv->evictable_count)?
|
||||
*/
|
||||
bool evictable : 1;
|
||||
|
||||
/**
|
||||
* count of active vmap'ing
|
||||
*/
|
||||
uint8_t vmap_count;
|
||||
|
||||
/* And object is either:
|
||||
* inactive - on priv->inactive_list
|
||||
/**
|
||||
* Node in list of all objects (mainly for debugfs, protected by
|
||||
* priv->obj_lock
|
||||
*/
|
||||
struct list_head node;
|
||||
|
||||
/**
|
||||
* An object is either:
|
||||
* inactive - on priv->inactive_dontneed or priv->inactive_willneed
|
||||
* (depending on purgeability status)
|
||||
* active - on one one of the gpu's active_list.. well, at
|
||||
* least for now we don't have (I don't think) hw sync between
|
||||
* 2d and 3d one devices which have both, meaning we need to
|
||||
* block on submit if a bo is already on other ring
|
||||
*
|
||||
*/
|
||||
struct list_head mm_list;
|
||||
|
||||
@ -78,8 +100,6 @@ struct msm_gem_object {
|
||||
|
||||
struct list_head vmas; /* list of msm_gem_vma */
|
||||
|
||||
struct llist_node freed;
|
||||
|
||||
/* For physically contiguous buffers. Used when we don't have
|
||||
* an IOMMU. Also used for stolen/splashscreen buffer.
|
||||
*/
|
||||
@ -88,6 +108,7 @@ struct msm_gem_object {
|
||||
char name[32]; /* Identifier to print for the debugfs files */
|
||||
|
||||
int active_count;
|
||||
int pin_count;
|
||||
};
|
||||
#define to_msm_bo(x) container_of(x, struct msm_gem_object, base)
|
||||
|
||||
@ -147,8 +168,17 @@ struct drm_gem_object *msm_gem_import(struct drm_device *dev,
|
||||
struct dma_buf *dmabuf, struct sg_table *sgt);
|
||||
__printf(2, 3)
|
||||
void msm_gem_object_set_name(struct drm_gem_object *bo, const char *fmt, ...);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
|
||||
struct msm_gem_stats {
|
||||
struct {
|
||||
unsigned count;
|
||||
size_t size;
|
||||
} all, active, resident, purgeable, purged;
|
||||
};
|
||||
|
||||
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m,
|
||||
struct msm_gem_stats *stats);
|
||||
void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
|
||||
#endif
|
||||
|
||||
@ -184,23 +214,101 @@ msm_gem_is_locked(struct drm_gem_object *obj)
|
||||
|
||||
static inline bool is_active(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
WARN_ON(!msm_gem_is_locked(&msm_obj->base));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(&msm_obj->base));
|
||||
return msm_obj->active_count;
|
||||
}
|
||||
|
||||
/* imported/exported objects are not purgeable: */
|
||||
static inline bool is_unpurgeable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
return msm_obj->base.dma_buf && msm_obj->base.import_attach;
|
||||
}
|
||||
|
||||
static inline bool is_purgeable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
return (msm_obj->madv == MSM_MADV_DONTNEED) && msm_obj->sgt &&
|
||||
!msm_obj->base.dma_buf && !msm_obj->base.import_attach;
|
||||
!is_unpurgeable(msm_obj);
|
||||
}
|
||||
|
||||
static inline bool is_vunmapable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
WARN_ON(!msm_gem_is_locked(&msm_obj->base));
|
||||
GEM_WARN_ON(!msm_gem_is_locked(&msm_obj->base));
|
||||
return (msm_obj->vmap_count == 0) && msm_obj->vaddr;
|
||||
}
|
||||
|
||||
static inline void mark_purgeable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
struct msm_drm_private *priv = msm_obj->base.dev->dev_private;
|
||||
|
||||
GEM_WARN_ON(!mutex_is_locked(&priv->mm_lock));
|
||||
|
||||
if (is_unpurgeable(msm_obj))
|
||||
return;
|
||||
|
||||
if (GEM_WARN_ON(msm_obj->dontneed))
|
||||
return;
|
||||
|
||||
priv->shrinkable_count += msm_obj->base.size >> PAGE_SHIFT;
|
||||
msm_obj->dontneed = true;
|
||||
}
|
||||
|
||||
static inline void mark_unpurgeable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
struct msm_drm_private *priv = msm_obj->base.dev->dev_private;
|
||||
|
||||
GEM_WARN_ON(!mutex_is_locked(&priv->mm_lock));
|
||||
|
||||
if (is_unpurgeable(msm_obj))
|
||||
return;
|
||||
|
||||
if (GEM_WARN_ON(!msm_obj->dontneed))
|
||||
return;
|
||||
|
||||
priv->shrinkable_count -= msm_obj->base.size >> PAGE_SHIFT;
|
||||
GEM_WARN_ON(priv->shrinkable_count < 0);
|
||||
msm_obj->dontneed = false;
|
||||
}
|
||||
|
||||
static inline bool is_unevictable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
return is_unpurgeable(msm_obj) || msm_obj->pin_count || msm_obj->vaddr;
|
||||
}
|
||||
|
||||
static inline void mark_evictable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
struct msm_drm_private *priv = msm_obj->base.dev->dev_private;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&priv->mm_lock));
|
||||
|
||||
if (is_unevictable(msm_obj))
|
||||
return;
|
||||
|
||||
if (WARN_ON(msm_obj->evictable))
|
||||
return;
|
||||
|
||||
priv->evictable_count += msm_obj->base.size >> PAGE_SHIFT;
|
||||
msm_obj->evictable = true;
|
||||
}
|
||||
|
||||
static inline void mark_unevictable(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
struct msm_drm_private *priv = msm_obj->base.dev->dev_private;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&priv->mm_lock));
|
||||
|
||||
if (is_unevictable(msm_obj))
|
||||
return;
|
||||
|
||||
if (WARN_ON(!msm_obj->evictable))
|
||||
return;
|
||||
|
||||
priv->evictable_count -= msm_obj->base.size >> PAGE_SHIFT;
|
||||
WARN_ON(priv->evictable_count < 0);
|
||||
msm_obj->evictable = false;
|
||||
}
|
||||
|
||||
void msm_gem_purge(struct drm_gem_object *obj);
|
||||
void msm_gem_evict(struct drm_gem_object *obj);
|
||||
void msm_gem_vunmap(struct drm_gem_object *obj);
|
||||
|
||||
/* Created per submit-ioctl, to track bo's and cmdstream bufs, etc,
|
||||
|
@ -9,27 +9,115 @@
|
||||
#include "msm_gpu.h"
|
||||
#include "msm_gpu_trace.h"
|
||||
|
||||
/* Default disabled for now until it has some more testing on the different
|
||||
* iommu combinations that can be paired with the driver:
|
||||
*/
|
||||
bool enable_eviction = false;
|
||||
MODULE_PARM_DESC(enable_eviction, "Enable swappable GEM buffers");
|
||||
module_param(enable_eviction, bool, 0600);
|
||||
|
||||
static bool can_swap(void)
|
||||
{
|
||||
return enable_eviction && get_nr_swap_pages() > 0;
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
|
||||
{
|
||||
struct msm_drm_private *priv =
|
||||
container_of(shrinker, struct msm_drm_private, shrinker);
|
||||
struct msm_gem_object *msm_obj;
|
||||
unsigned long count = 0;
|
||||
unsigned count = priv->shrinkable_count;
|
||||
|
||||
if (can_swap())
|
||||
count += priv->evictable_count;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool
|
||||
purge(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
if (!is_purgeable(msm_obj))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* This will move the obj out of still_in_list to
|
||||
* the purged list
|
||||
*/
|
||||
msm_gem_purge(&msm_obj->base);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
evict(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
if (is_unevictable(msm_obj))
|
||||
return false;
|
||||
|
||||
msm_gem_evict(&msm_obj->base);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
scan(struct msm_drm_private *priv, unsigned nr_to_scan, struct list_head *list,
|
||||
bool (*shrink)(struct msm_gem_object *msm_obj))
|
||||
{
|
||||
unsigned freed = 0;
|
||||
struct list_head still_in_list;
|
||||
|
||||
INIT_LIST_HEAD(&still_in_list);
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
|
||||
list_for_each_entry(msm_obj, &priv->inactive_dontneed, mm_list) {
|
||||
if (!msm_gem_trylock(&msm_obj->base))
|
||||
while (freed < nr_to_scan) {
|
||||
struct msm_gem_object *msm_obj = list_first_entry_or_null(
|
||||
list, typeof(*msm_obj), mm_list);
|
||||
|
||||
if (!msm_obj)
|
||||
break;
|
||||
|
||||
list_move_tail(&msm_obj->mm_list, &still_in_list);
|
||||
|
||||
/*
|
||||
* If it is in the process of being freed, msm_gem_free_object
|
||||
* can be blocked on mm_lock waiting to remove it. So just
|
||||
* skip it.
|
||||
*/
|
||||
if (!kref_get_unless_zero(&msm_obj->base.refcount))
|
||||
continue;
|
||||
if (is_purgeable(msm_obj))
|
||||
count += msm_obj->base.size >> PAGE_SHIFT;
|
||||
|
||||
/*
|
||||
* Now that we own a reference, we can drop mm_lock for the
|
||||
* rest of the loop body, to reduce contention with the
|
||||
* retire_submit path (which could make more objects purgeable)
|
||||
*/
|
||||
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
|
||||
/*
|
||||
* Note that this still needs to be trylock, since we can
|
||||
* hit shrinker in response to trying to get backing pages
|
||||
* for this obj (ie. while it's lock is already held)
|
||||
*/
|
||||
if (!msm_gem_trylock(&msm_obj->base))
|
||||
goto tail;
|
||||
|
||||
if (shrink(msm_obj))
|
||||
freed += msm_obj->base.size >> PAGE_SHIFT;
|
||||
|
||||
msm_gem_unlock(&msm_obj->base);
|
||||
|
||||
tail:
|
||||
drm_gem_object_put(&msm_obj->base);
|
||||
mutex_lock(&priv->mm_lock);
|
||||
}
|
||||
|
||||
list_splice_tail(&still_in_list, list);
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
|
||||
return count;
|
||||
return freed;
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
@ -37,29 +125,24 @@ msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
|
||||
{
|
||||
struct msm_drm_private *priv =
|
||||
container_of(shrinker, struct msm_drm_private, shrinker);
|
||||
struct msm_gem_object *msm_obj;
|
||||
unsigned long freed = 0;
|
||||
unsigned long freed;
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
|
||||
list_for_each_entry(msm_obj, &priv->inactive_dontneed, mm_list) {
|
||||
if (freed >= sc->nr_to_scan)
|
||||
break;
|
||||
if (!msm_gem_trylock(&msm_obj->base))
|
||||
continue;
|
||||
if (is_purgeable(msm_obj)) {
|
||||
msm_gem_purge(&msm_obj->base);
|
||||
freed += msm_obj->base.size >> PAGE_SHIFT;
|
||||
}
|
||||
msm_gem_unlock(&msm_obj->base);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
freed = scan(priv, sc->nr_to_scan, &priv->inactive_dontneed, purge);
|
||||
|
||||
if (freed > 0)
|
||||
trace_msm_gem_purge(freed << PAGE_SHIFT);
|
||||
|
||||
return freed;
|
||||
if (can_swap() && freed < sc->nr_to_scan) {
|
||||
int evicted = scan(priv, sc->nr_to_scan - freed,
|
||||
&priv->inactive_willneed, evict);
|
||||
|
||||
if (evicted > 0)
|
||||
trace_msm_gem_evict(evicted << PAGE_SHIFT);
|
||||
|
||||
freed += evicted;
|
||||
}
|
||||
|
||||
return (freed > 0) ? freed : SHRINK_STOP;
|
||||
}
|
||||
|
||||
/* since we don't know any better, lets bail after a few
|
||||
@ -68,26 +151,15 @@ msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
|
||||
*/
|
||||
static const int vmap_shrink_limit = 15;
|
||||
|
||||
static unsigned
|
||||
vmap_shrink(struct list_head *mm_list)
|
||||
static bool
|
||||
vmap_shrink(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj;
|
||||
unsigned unmapped = 0;
|
||||
if (!is_vunmapable(msm_obj))
|
||||
return false;
|
||||
|
||||
list_for_each_entry(msm_obj, mm_list, mm_list) {
|
||||
if (!msm_gem_trylock(&msm_obj->base))
|
||||
continue;
|
||||
if (is_vunmapable(msm_obj)) {
|
||||
msm_gem_vunmap(&msm_obj->base);
|
||||
unmapped++;
|
||||
}
|
||||
msm_gem_unlock(&msm_obj->base);
|
||||
msm_gem_vunmap(&msm_obj->base);
|
||||
|
||||
if (++unmapped >= vmap_shrink_limit)
|
||||
break;
|
||||
}
|
||||
|
||||
return unmapped;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -103,17 +175,11 @@ msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr)
|
||||
};
|
||||
unsigned idx, unmapped = 0;
|
||||
|
||||
mutex_lock(&priv->mm_lock);
|
||||
|
||||
for (idx = 0; mm_lists[idx]; idx++) {
|
||||
unmapped += vmap_shrink(mm_lists[idx]);
|
||||
|
||||
if (unmapped >= vmap_shrink_limit)
|
||||
break;
|
||||
for (idx = 0; mm_lists[idx] && unmapped < vmap_shrink_limit; idx++) {
|
||||
unmapped += scan(priv, vmap_shrink_limit - unmapped,
|
||||
mm_lists[idx], vmap_shrink);
|
||||
}
|
||||
|
||||
mutex_unlock(&priv->mm_lock);
|
||||
|
||||
*(unsigned long *)ptr += unmapped;
|
||||
|
||||
if (unmapped > 0)
|
||||
|
@ -251,6 +251,8 @@ int msm_gpu_pm_suspend(struct msm_gpu *gpu)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
gpu->suspend_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,8 @@ struct msm_gpu {
|
||||
ktime_t time;
|
||||
} devfreq;
|
||||
|
||||
uint32_t suspend_count;
|
||||
|
||||
struct msm_gpu_state *crashstate;
|
||||
/* True if the hardware supports expanded apriv (a650 and newer) */
|
||||
bool hw_apriv;
|
||||
|
@ -128,6 +128,19 @@ TRACE_EVENT(msm_gem_purge,
|
||||
);
|
||||
|
||||
|
||||
TRACE_EVENT(msm_gem_evict,
|
||||
TP_PROTO(u32 bytes),
|
||||
TP_ARGS(bytes),
|
||||
TP_STRUCT__entry(
|
||||
__field(u32, bytes)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->bytes = bytes;
|
||||
),
|
||||
TP_printk("Evicting %u bytes", __entry->bytes)
|
||||
);
|
||||
|
||||
|
||||
TRACE_EVENT(msm_gem_purge_vmaps,
|
||||
TP_PROTO(u32 unmapped),
|
||||
TP_ARGS(unmapped),
|
||||
|
@ -785,6 +785,23 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name,
|
||||
(parent_data), (flags), (reg), (shift), \
|
||||
(width), (clk_divider_flags), (table), \
|
||||
(lock))
|
||||
/**
|
||||
* devm_clk_hw_register_divider - register a divider clock with the clock framework
|
||||
* @dev: device registering this clock
|
||||
* @name: name of this clock
|
||||
* @parent_name: name of clock's parent
|
||||
* @flags: framework-specific flags
|
||||
* @reg: register address to adjust divider
|
||||
* @shift: number of bits to shift the bitfield
|
||||
* @width: width of the bitfield
|
||||
* @clk_divider_flags: divider-specific flags for this clock
|
||||
* @lock: shared register lock for this clock
|
||||
*/
|
||||
#define devm_clk_hw_register_divider(dev, name, parent_name, flags, reg, shift, \
|
||||
width, clk_divider_flags, lock) \
|
||||
__devm_clk_hw_register_divider((dev), NULL, (name), (parent_name), NULL, \
|
||||
NULL, (flags), (reg), (shift), (width), \
|
||||
(clk_divider_flags), NULL, (lock))
|
||||
/**
|
||||
* devm_clk_hw_register_divider_table - register a table based divider clock
|
||||
* with the clock framework (devres variant)
|
||||
@ -868,6 +885,13 @@ struct clk_hw *__clk_hw_register_mux(struct device *dev, struct device_node *np,
|
||||
const struct clk_parent_data *parent_data,
|
||||
unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
|
||||
u8 clk_mux_flags, u32 *table, spinlock_t *lock);
|
||||
struct clk_hw *__devm_clk_hw_register_mux(struct device *dev, struct device_node *np,
|
||||
const char *name, u8 num_parents,
|
||||
const char * const *parent_names,
|
||||
const struct clk_hw **parent_hws,
|
||||
const struct clk_parent_data *parent_data,
|
||||
unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
|
||||
u8 clk_mux_flags, u32 *table, spinlock_t *lock);
|
||||
struct clk *clk_register_mux_table(struct device *dev, const char *name,
|
||||
const char * const *parent_names, u8 num_parents,
|
||||
unsigned long flags, void __iomem *reg, u8 shift, u32 mask,
|
||||
@ -902,6 +926,12 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name,
|
||||
__clk_hw_register_mux((dev), NULL, (name), (num_parents), NULL, NULL, \
|
||||
(parent_data), (flags), (reg), (shift), \
|
||||
BIT((width)) - 1, (clk_mux_flags), NULL, (lock))
|
||||
#define devm_clk_hw_register_mux(dev, name, parent_names, num_parents, flags, reg, \
|
||||
shift, width, clk_mux_flags, lock) \
|
||||
__devm_clk_hw_register_mux((dev), NULL, (name), (num_parents), \
|
||||
(parent_names), NULL, NULL, (flags), (reg), \
|
||||
(shift), BIT((width)) - 1, (clk_mux_flags), \
|
||||
NULL, (lock))
|
||||
|
||||
int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags,
|
||||
unsigned int val);
|
||||
|
@ -76,6 +76,7 @@ struct drm_msm_timespec {
|
||||
#define MSM_PARAM_NR_RINGS 0x07
|
||||
#define MSM_PARAM_PP_PGTABLE 0x08 /* => 1 for per-process pagetables, else 0 */
|
||||
#define MSM_PARAM_FAULTS 0x09
|
||||
#define MSM_PARAM_SUSPENDS 0x0a
|
||||
|
||||
struct drm_msm_param {
|
||||
__u32 pipe; /* in, MSM_PIPE_x */
|
||||
|
Loading…
Reference in New Issue
Block a user