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:
Daniel Vetter 2021-04-13 23:33:41 +02:00
commit af8352f1ff
58 changed files with 5289 additions and 5026 deletions

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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 */

View File

@ -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)
{

View File

@ -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__ */

View File

@ -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);
};
/**

View File

@ -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(

View File

@ -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)

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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;
};
/**

View File

@ -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);

View File

@ -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 */
};

View File

@ -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

View File

@ -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,7 +934,6 @@ 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);
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);

View File

@ -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:

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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__ */

View File

@ -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 = {

View File

@ -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",

View File

@ -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)

View File

@ -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)) {
if (phy->cfg->ops.pll_init) {
ret = phy->cfg->ops.pll_init(phy);
if (ret) {
DRM_DEV_INFO(dev,
"%s: pll init failed: %ld, need separate pll clk driver\n",
__func__, PTR_ERR(phy->pll));
phy->pll = NULL;
"%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;
}

View File

@ -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__ */

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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,

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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;
}

View File

@ -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__ */

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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_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;

View File

@ -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

View File

@ -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,18 +360,24 @@ 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);
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:

View File

@ -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,

View File

@ -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;
msm_gem_unlock(&msm_obj->base);
}
/*
* 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);
return count;
/*
* 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 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);
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)

View File

@ -251,6 +251,8 @@ int msm_gpu_pm_suspend(struct msm_gpu *gpu)
if (ret)
return ret;
gpu->suspend_count++;
return 0;
}

View File

@ -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;

View File

@ -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),

View File

@ -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);

View File

@ -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 */