struct {
const char *id;
struct clk **clk;
+ bool optional;
} clks[] = {
- {"apb", &dcss->apb_clk},
- {"axi", &dcss->axi_clk},
- {"pix_div", &dcss->pdiv_clk},
- {"pix_out", &dcss->pout_clk},
- {"rtrm", &dcss->rtrm_clk},
- {"dtrc", &dcss->dtrc_clk},
- {"pll_src", &dcss->pll_src_clk},
- {"pll_phy_ref", &dcss->pll_phy_ref_clk},
+ {"apb", &dcss->apb_clk, false},
+ {"axi", &dcss->axi_clk, false},
+ {"pix", &dcss->pix_clk, false},
+ {"rtrm", &dcss->rtrm_clk, false},
+ {"dtrc", &dcss->dtrc_clk, false},
+ {"pll", &dcss->pll_clk, true},
+ {"pll_src1", &dcss->src_clk[0], true},
+ {"pll_src2", &dcss->src_clk[1], true},
+ {"pll_src3", &dcss->src_clk[2], true},
};
for (i = 0; i < ARRAY_SIZE(clks); i++) {
*clks[i].clk = devm_clk_get(dcss->dev, clks[i].id);
- if (IS_ERR(*clks[i].clk)) {
+ if (IS_ERR(*clks[i].clk) && !clks[i].optional) {
dev_err(dcss->dev, "failed to get %s clock\n",
clks[i].id);
ret = PTR_ERR(*clks[i].clk);
goto err;
}
- clk_prepare_enable(*clks[i].clk);
+ if (!clks[i].optional)
+ clk_prepare_enable(*clks[i].clk);
}
dcss->clks_on = true;
static void dcss_clocks_enable(struct dcss_soc *dcss, bool en)
{
if (en && !dcss->clks_on) {
- clk_prepare_enable(dcss->pll_phy_ref_clk);
- clk_prepare_enable(dcss->pll_src_clk);
clk_prepare_enable(dcss->axi_clk);
clk_prepare_enable(dcss->apb_clk);
clk_prepare_enable(dcss->rtrm_clk);
clk_prepare_enable(dcss->dtrc_clk);
- clk_prepare_enable(dcss->pdiv_clk);
- clk_prepare_enable(dcss->pout_clk);
+ clk_prepare_enable(dcss->pix_clk);
}
if (!en && dcss->clks_on) {
- clk_disable_unprepare(dcss->pout_clk);
- clk_disable_unprepare(dcss->pdiv_clk);
+ clk_disable_unprepare(dcss->pix_clk);
clk_disable_unprepare(dcss->dtrc_clk);
clk_disable_unprepare(dcss->rtrm_clk);
clk_disable_unprepare(dcss->apb_clk);
clk_disable_unprepare(dcss->axi_clk);
- clk_disable_unprepare(dcss->pll_src_clk);
- clk_disable_unprepare(dcss->pll_phy_ref_clk);
}
dcss->clks_on = en;
#define DCSS_DTG_DBY_BL 0x78
#define DCSS_DTG_DBY_EL 0x7C
+/* Maximum Video PLL frequency */
+#define MAX_PLL_FREQ 1200000000
+/* Mininum pixel clock in kHz */
+#define MIN_PIX_CLK 74250
+
static struct dcss_debug_reg dtg_debug_reg[] = {
DCSS_DBG_REG(DCSS_DTG_TC_CONTROL_STATUS),
DCSS_DBG_REG(DCSS_DTG_TC_DTG),
DCSS_DBG_REG(DCSS_DTG_DBY_EL),
};
+struct mode_config {
+ struct clk *clk_src;
+ unsigned long out_rate;
+ int clock;
+ int mode_clock;
+ struct list_head list;
+};
+
struct dcss_dtg_priv {
struct dcss_soc *dcss;
void __iomem *base_reg;
int ctxld_kick_irq;
bool ctxld_kick_irq_en;
+ struct list_head valid_modes;
/*
* This will be passed on by DRM CRTC so that we can signal when DTG has
dtg->control_status |= OVL_DATA_MODE | BLENDER_VIDEO_ALPHA_SEL |
((dtg->alpha << DEFAULT_FG_ALPHA_POS) & DEFAULT_FG_ALPHA_MASK);
+ INIT_LIST_HEAD(&dtg->valid_modes);
+
return dcss_dtg_irq_config(dtg);
}
void dcss_dtg_exit(struct dcss_soc *dcss)
{
struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+ struct mode_config *config;
+ struct list_head *pos, *tmp;
/* stop DTG */
dcss_writel(DTG_START, dtg->base_reg + DCSS_DTG_TC_CONTROL_STATUS);
+
+ list_for_each_safe(pos, tmp, &dtg->valid_modes) {
+ config = list_entry(pos, struct mode_config, list);
+ list_del(pos);
+ devm_kfree(dcss->dev, config);
+ }
+}
+
+static struct clk *dcss_dtg_find_src_clk(struct dcss_soc *dcss, int crtc_clock,
+ unsigned long *out_rate)
+{
+ struct clk *src = NULL;
+ struct clk *p = dcss->pix_clk;
+ struct clk *src_clk[MAX_CLK_SRC];
+ int num_src_clk = ARRAY_SIZE(dcss->src_clk);
+ unsigned long src_rate;
+ int i;
+
+ for (i = 0; i < num_src_clk; i++)
+ src_clk[i] = dcss->src_clk[i];
+
+ /*
+ * First, check the current clock source and find the clock
+ * selector
+ */
+ while (p) {
+ struct clk *pp = clk_get_parent(p);
+
+ for (i = 0; i < num_src_clk; i++)
+ if (src_clk[i] && clk_is_match(pp, src_clk[i])) {
+ src = pp;
+ dcss->sel_clk = p;
+ src_clk[i] = NULL;
+ break;
+ }
+
+ if (src)
+ break;
+
+ p = pp;
+ }
+
+ while (!IS_ERR_OR_NULL(src)) {
+ /* Check if current rate satisfies our needs */
+ src_rate = clk_get_rate(src);
+ *out_rate = clk_get_rate(dcss->pll_clk);
+ if (!(*out_rate % crtc_clock))
+ break;
+
+ /* Find the highest rate that fits our needs */
+ *out_rate = crtc_clock * (MAX_PLL_FREQ / crtc_clock);
+ if (!(*out_rate % src_rate))
+ break;
+
+ /* Get the next clock source available */
+ src = NULL;
+ for (i = 0; i < num_src_clk; i++) {
+ if (IS_ERR_OR_NULL(src_clk[i]))
+ continue;
+ src = src_clk[i];
+ src_clk[i] = NULL;
+ break;
+ }
+ }
+
+ return src;
+}
+
+int dcss_dtg_mode_valid(struct dcss_soc *dcss, int clock, int crtc_clock)
+{
+ struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+ struct clk *src = NULL;
+ unsigned long out_rate;
+ struct mode_config *config;
+
+ /*
+ * In order to verify possible clock sources we need to have at least
+ * two of them. Also, do not check the clock if the output is hdmi.
+ */
+ if (dtg->hdmi_output || !dcss->src_clk[0] || !dcss->src_clk[1])
+ return 0;
+
+ /*
+ * TODO: Currently, only modes with pixel clock higher or equal to
+ * 74250kHz are working. Limit to these modes until we figure out how
+ * to handle the rest of the display modes.
+ */
+ if (clock < MIN_PIX_CLK)
+ return 1;
+
+ /* Transform clocks in Hz */
+ clock *= 1000;
+ crtc_clock *= 1000;
+
+ if (!crtc_clock)
+ crtc_clock = clock;
+
+ /* Skip saving the config again */
+ list_for_each_entry(config, &dtg->valid_modes, list)
+ if (config->clock == clock)
+ return 0;
+
+ src = dcss_dtg_find_src_clk(dcss, crtc_clock, &out_rate);
+
+ if (IS_ERR_OR_NULL(src))
+ return 1;
+
+ clk_set_rate(dcss->pll_clk, out_rate);
+
+ /* Save this configuration for later use */
+ config = devm_kzalloc(dcss->dev,
+ sizeof(struct mode_config), GFP_KERNEL);
+ list_add(&config->list, &dtg->valid_modes);
+ config->clk_src = src;
+ config->out_rate = out_rate;
+ config->clock = clock;
+ config->mode_clock = crtc_clock;
+
+ return 0;
+}
+EXPORT_SYMBOL(dcss_dtg_mode_valid);
+
+int dcss_dtg_mode_fixup(struct dcss_soc *dcss, int clock)
+{
+ struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+ struct mode_config *config;
+ struct clk *src;
+
+ /* Make sure that current mode can get the required clock */
+ list_for_each_entry(config, &dtg->valid_modes, list)
+ if (config->clock == clock * 1000) {
+ if (dcss->clks_on)
+ clk_disable_unprepare(dcss->pix_clk);
+ src = clk_get_parent(dcss->sel_clk);
+ if (!clk_is_match(src, config->clk_src))
+ clk_set_parent(dcss->sel_clk, config->clk_src);
+ if (clk_get_rate(dcss->pll_clk) != config->out_rate)
+ clk_set_rate(dcss->pll_clk, config->out_rate);
+ dev_dbg(dcss->dev, "pll rate: %ld (actual %ld)\n",
+ config->out_rate, clk_get_rate(dcss->pll_clk));
+ if (dcss->clks_on)
+ clk_prepare_enable(dcss->pix_clk);
+ break;
+ }
+
+ return 0;
+
+}
+EXPORT_SYMBOL(dcss_dtg_mode_fixup);
+
+static void dcss_dtg_set_clock(struct dcss_soc *dcss, unsigned long clock)
+{
+ struct dcss_dtg_priv *dtg = dcss->dtg_priv;
+ struct mode_config *config;
+
+ /*
+ * Before setting the clock rate, we need to be sure that the clock
+ * has the right source to output the required rate.
+ */
+ list_for_each_entry(config, &dtg->valid_modes, list) {
+ if (config->clock == clock) {
+ struct clk *src;
+
+ src = clk_get_parent(dcss->sel_clk);
+ if (!clk_is_match(src, config->clk_src))
+ clk_set_parent(dcss->sel_clk, config->clk_src);
+ if (clk_get_rate(dcss->pll_clk) != config->out_rate)
+ clk_set_rate(dcss->pll_clk, config->out_rate);
+ dev_dbg(dcss->dev, "pll rate: %ld (actual %ld)\n",
+ config->out_rate, clk_get_rate(dcss->pll_clk));
+ clock = config->mode_clock;
+ break;
+ }
+ }
+
+ clk_set_rate(dcss->pix_clk, clock);
}
void dcss_dtg_sync_set(struct dcss_soc *dcss, struct videomode *vm)
u16 dis_ulc_x, dis_ulc_y;
u16 dis_lrc_x, dis_lrc_y;
u32 sb_ctxld_trig, db_ctxld_trig;
+ u32 pixclock = vm->pixelclock;
u32 actual_clk;
dev_dbg(dcss->dev, "hfront_porch = %d\n", vm->hfront_porch);
dis_lrc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch +
vm->vactive - 1;
+ clk_disable_unprepare(dcss->pix_clk);
if (dtg->hdmi_output) {
int err;
- clk_disable_unprepare(dcss->pout_clk);
- clk_disable_unprepare(dcss->pll_src_clk);
- err = clk_set_parent(dcss->pll_src_clk, dcss->pll_phy_ref_clk);
+ /*
+ * At this point, since pix_clk is disabled, the pll_clk
+ * should also be disabled, so re-parenting should be safe
+ */
+ err = clk_set_parent(dcss->pll_clk, dcss->src_clk[0]);
if (err < 0)
dev_warn(dcss->dev, "clk_set_parent() returned %d",
err);
- clk_set_rate(dcss->pout_clk, vm->pixelclock);
- clk_prepare_enable(dcss->pll_src_clk);
- clk_prepare_enable(dcss->pout_clk);
+ clk_set_rate(dcss->pix_clk, vm->pixelclock);
} else {
- clk_disable_unprepare(dcss->pout_clk);
- clk_disable_unprepare(dcss->pdiv_clk);
- clk_set_rate(dcss->pdiv_clk, vm->pixelclock);
- actual_clk = clk_get_rate(dcss->pdiv_clk);
- clk_prepare_enable(dcss->pdiv_clk);
- clk_prepare_enable(dcss->pout_clk);
+ dcss_dtg_set_clock(dcss, pixclock);
+ }
+ clk_prepare_enable(dcss->pix_clk);
+
+ actual_clk = clk_get_rate(dcss->pix_clk);
+ if (pixclock != actual_clk) {
+ dev_info(dcss->dev,
+ "Pixel clock set to %u kHz instead of %u kHz, "
+ "error is %d Hz\n",
+ (actual_clk / 1000), (pixclock / 1000),
+ (int)(actual_clk - pixclock));
}
msleep(50);