summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/pl111
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/pl111')
-rw-r--r--drivers/gpu/drm/pl111/Kconfig1
-rw-r--r--drivers/gpu/drm/pl111/Makefile2
-rw-r--r--drivers/gpu/drm/pl111/pl111_debugfs.c55
-rw-r--r--drivers/gpu/drm/pl111/pl111_display.c164
-rw-r--r--drivers/gpu/drm/pl111/pl111_drm.h11
-rw-r--r--drivers/gpu/drm/pl111/pl111_drv.c21
6 files changed, 226 insertions, 28 deletions
diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig
index ede49efd531f..309f4fd52de7 100644
--- a/drivers/gpu/drm/pl111/Kconfig
+++ b/drivers/gpu/drm/pl111/Kconfig
@@ -2,6 +2,7 @@ config DRM_PL111
tristate "DRM Support for PL111 CLCD Controller"
depends on DRM
depends on ARM || ARM64 || COMPILE_TEST
+ depends on COMMON_CLK
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER
diff --git a/drivers/gpu/drm/pl111/Makefile b/drivers/gpu/drm/pl111/Makefile
index 01caee727c13..59483d610ef5 100644
--- a/drivers/gpu/drm/pl111/Makefile
+++ b/drivers/gpu/drm/pl111/Makefile
@@ -2,4 +2,6 @@ pl111_drm-y += pl111_connector.o \
pl111_display.o \
pl111_drv.o
+pl111_drm-$(CONFIG_DEBUG_FS) += pl111_debugfs.o
+
obj-$(CONFIG_DRM_PL111) += pl111_drm.o
diff --git a/drivers/gpu/drm/pl111/pl111_debugfs.c b/drivers/gpu/drm/pl111/pl111_debugfs.c
new file mode 100644
index 000000000000..0d9dee199b2c
--- /dev/null
+++ b/drivers/gpu/drm/pl111/pl111_debugfs.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2017 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/amba/clcd-regs.h>
+#include <linux/seq_file.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drmP.h>
+#include "pl111_drm.h"
+
+#define REGDEF(reg) { reg, #reg }
+static const struct {
+ u32 reg;
+ const char *name;
+} pl111_reg_defs[] = {
+ REGDEF(CLCD_TIM0),
+ REGDEF(CLCD_TIM1),
+ REGDEF(CLCD_TIM2),
+ REGDEF(CLCD_TIM3),
+ REGDEF(CLCD_UBAS),
+ REGDEF(CLCD_PL111_CNTL),
+ REGDEF(CLCD_PL111_IENB),
+};
+
+int pl111_debugfs_regs(struct seq_file *m, void *unused)
+{
+ struct drm_info_node *node = (struct drm_info_node *)m->private;
+ struct drm_device *dev = node->minor->dev;
+ struct pl111_drm_dev_private *priv = dev->dev_private;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pl111_reg_defs); i++) {
+ seq_printf(m, "%s (0x%04x): 0x%08x\n",
+ pl111_reg_defs[i].name, pl111_reg_defs[i].reg,
+ readl(priv->regs + pl111_reg_defs[i].reg));
+ }
+
+ return 0;
+}
+
+static const struct drm_info_list pl111_debugfs_list[] = {
+ {"regs", pl111_debugfs_regs, 0},
+};
+
+int
+pl111_debugfs_init(struct drm_minor *minor)
+{
+ return drm_debugfs_create_files(pl111_debugfs_list,
+ ARRAY_SIZE(pl111_debugfs_list),
+ minor->debugfs_root, minor);
+}
diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c
index 39a5c33bce7d..3e0a4fa73ddb 100644
--- a/drivers/gpu/drm/pl111/pl111_display.c
+++ b/drivers/gpu/drm/pl111/pl111_display.c
@@ -108,7 +108,7 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
u32 cntl;
u32 ppl, hsw, hfp, hbp;
u32 lpp, vsw, vfp, vbp;
- u32 cpl;
+ u32 cpl, tim2;
int ret;
ret = clk_set_rate(priv->clk, mode->clock * 1000);
@@ -142,20 +142,28 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
(vfp << 16) |
(vbp << 24),
priv->regs + CLCD_TIM1);
- /* XXX: We currently always use CLCDCLK with no divisor. We
- * could probably reduce power consumption by using HCLK
- * (apb_pclk) with a divisor when it gets us near our target
- * pixel clock.
- */
- writel(((mode->flags & DRM_MODE_FLAG_NHSYNC) ? TIM2_IHS : 0) |
- ((mode->flags & DRM_MODE_FLAG_NVSYNC) ? TIM2_IVS : 0) |
- ((connector->display_info.bus_flags &
- DRM_BUS_FLAG_DE_LOW) ? TIM2_IOE : 0) |
- ((connector->display_info.bus_flags &
- DRM_BUS_FLAG_PIXDATA_NEGEDGE) ? TIM2_IPC : 0) |
- TIM2_BCD |
- (cpl << 16),
- priv->regs + CLCD_TIM2);
+
+ spin_lock(&priv->tim2_lock);
+
+ tim2 = readl(priv->regs + CLCD_TIM2);
+ tim2 &= (TIM2_BCD | TIM2_PCD_LO_MASK | TIM2_PCD_HI_MASK);
+
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ tim2 |= TIM2_IHS;
+
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ tim2 |= TIM2_IVS;
+
+ if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW)
+ tim2 |= TIM2_IOE;
+
+ if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
+ tim2 |= TIM2_IPC;
+
+ tim2 |= cpl << 16;
+ writel(tim2, priv->regs + CLCD_TIM2);
+ spin_unlock(&priv->tim2_lock);
+
writel(0, priv->regs + CLCD_TIM3);
drm_panel_prepare(priv->connector.panel);
@@ -280,7 +288,7 @@ static int pl111_display_prepare_fb(struct drm_simple_display_pipe *pipe,
return drm_fb_cma_prepare_fb(&pipe->plane, plane_state);
}
-const struct drm_simple_display_pipe_funcs pl111_display_funcs = {
+static const struct drm_simple_display_pipe_funcs pl111_display_funcs = {
.check = pl111_display_check,
.enable = pl111_display_enable,
.disable = pl111_display_disable,
@@ -288,6 +296,126 @@ const struct drm_simple_display_pipe_funcs pl111_display_funcs = {
.prepare_fb = pl111_display_prepare_fb,
};
+static int pl111_clk_div_choose_div(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate, bool set_parent)
+{
+ int best_div = 1, div;
+ struct clk_hw *parent = clk_hw_get_parent(hw);
+ unsigned long best_prate = 0;
+ unsigned long best_diff = ~0ul;
+ int max_div = (1 << (TIM2_PCD_LO_BITS + TIM2_PCD_HI_BITS)) - 1;
+
+ for (div = 1; div < max_div; div++) {
+ unsigned long this_prate, div_rate, diff;
+
+ if (set_parent)
+ this_prate = clk_hw_round_rate(parent, rate * div);
+ else
+ this_prate = *prate;
+ div_rate = DIV_ROUND_UP_ULL(this_prate, div);
+ diff = abs(rate - div_rate);
+
+ if (diff < best_diff) {
+ best_div = div;
+ best_diff = diff;
+ best_prate = this_prate;
+ }
+ }
+
+ *prate = best_prate;
+ return best_div;
+}
+
+static long pl111_clk_div_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ int div = pl111_clk_div_choose_div(hw, rate, prate, true);
+
+ return DIV_ROUND_UP_ULL(*prate, div);
+}
+
+static unsigned long pl111_clk_div_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct pl111_drm_dev_private *priv =
+ container_of(hw, struct pl111_drm_dev_private, clk_div);
+ u32 tim2 = readl(priv->regs + CLCD_TIM2);
+ int div;
+
+ if (tim2 & TIM2_BCD)
+ return prate;
+
+ div = tim2 & TIM2_PCD_LO_MASK;
+ div |= (tim2 & TIM2_PCD_HI_MASK) >>
+ (TIM2_PCD_HI_SHIFT - TIM2_PCD_LO_BITS);
+ div += 2;
+
+ return DIV_ROUND_UP_ULL(prate, div);
+}
+
+static int pl111_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct pl111_drm_dev_private *priv =
+ container_of(hw, struct pl111_drm_dev_private, clk_div);
+ int div = pl111_clk_div_choose_div(hw, rate, &prate, false);
+ u32 tim2;
+
+ spin_lock(&priv->tim2_lock);
+ tim2 = readl(priv->regs + CLCD_TIM2);
+ tim2 &= ~(TIM2_BCD | TIM2_PCD_LO_MASK | TIM2_PCD_HI_MASK);
+
+ if (div == 1) {
+ tim2 |= TIM2_BCD;
+ } else {
+ div -= 2;
+ tim2 |= div & TIM2_PCD_LO_MASK;
+ tim2 |= (div >> TIM2_PCD_LO_BITS) << TIM2_PCD_HI_SHIFT;
+ }
+
+ writel(tim2, priv->regs + CLCD_TIM2);
+ spin_unlock(&priv->tim2_lock);
+
+ return 0;
+}
+
+static const struct clk_ops pl111_clk_div_ops = {
+ .recalc_rate = pl111_clk_div_recalc_rate,
+ .round_rate = pl111_clk_div_round_rate,
+ .set_rate = pl111_clk_div_set_rate,
+};
+
+static int
+pl111_init_clock_divider(struct drm_device *drm)
+{
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ struct clk *parent = devm_clk_get(drm->dev, "clcdclk");
+ struct clk_hw *div = &priv->clk_div;
+ const char *parent_name;
+ struct clk_init_data init = {
+ .name = "pl111_div",
+ .ops = &pl111_clk_div_ops,
+ .parent_names = &parent_name,
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ };
+ int ret;
+
+ if (IS_ERR(parent)) {
+ dev_err(drm->dev, "CLCD: unable to get clcdclk.\n");
+ return PTR_ERR(parent);
+ }
+ parent_name = __clk_get_name(parent);
+
+ spin_lock_init(&priv->tim2_lock);
+ div->init = &init;
+
+ ret = devm_clk_hw_register(drm->dev, div);
+
+ priv->clk = div->clk;
+ return ret;
+}
+
int pl111_display_init(struct drm_device *drm)
{
struct pl111_drm_dev_private *priv = drm->dev_private;
@@ -333,6 +461,10 @@ int pl111_display_init(struct drm_device *drm)
return -EINVAL;
}
+ ret = pl111_init_clock_divider(drm);
+ if (ret)
+ return ret;
+
ret = drm_simple_display_pipe_init(drm, &priv->pipe,
&pl111_display_funcs,
formats, ARRAY_SIZE(formats),
diff --git a/drivers/gpu/drm/pl111/pl111_drm.h b/drivers/gpu/drm/pl111/pl111_drm.h
index f381593921b7..5c685bfc8fdc 100644
--- a/drivers/gpu/drm/pl111/pl111_drm.h
+++ b/drivers/gpu/drm/pl111/pl111_drm.h
@@ -21,9 +21,12 @@
#include <drm/drm_gem.h>
#include <drm/drm_simple_kms_helper.h>
+#include <linux/clk-provider.h>
#define CLCD_IRQ_NEXTBASE_UPDATE BIT(2)
+struct drm_minor;
+
struct pl111_drm_connector {
struct drm_connector connector;
struct drm_panel *panel;
@@ -37,7 +40,14 @@ struct pl111_drm_dev_private {
struct drm_fbdev_cma *fbdev;
void *regs;
+ /* The pixel clock (a reference to our clock divider off of CLCDCLK). */
struct clk *clk;
+ /* pl111's internal clock divider. */
+ struct clk_hw clk_div;
+ /* Lock to sync access to CLCD_TIM2 between the common clock
+ * subsystem and pl111_display_enable().
+ */
+ spinlock_t tim2_lock;
};
#define to_pl111_connector(x) \
@@ -52,5 +62,6 @@ int pl111_encoder_init(struct drm_device *dev);
int pl111_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args);
+int pl111_debugfs_init(struct drm_minor *minor);
#endif /* _PL111_DRM_H_ */
diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c
index 936403f65508..e96efad37d27 100644
--- a/drivers/gpu/drm/pl111/pl111_drv.c
+++ b/drivers/gpu/drm/pl111/pl111_drv.c
@@ -50,8 +50,8 @@
* - Read back hardware state at boot to skip reprogramming the
* hardware when doing a no-op modeset.
*
- * - Use the internal clock divisor to reduce power consumption by
- * using HCLK (apb_pclk) when appropriate.
+ * - Use the CLKSEL bit to support switching between the two external
+ * clock parents.
*/
#include <linux/amba/bus.h>
@@ -72,7 +72,7 @@
#define DRIVER_DESC "DRM module for PL111"
-struct drm_mode_config_funcs mode_config_funcs = {
+static struct drm_mode_config_funcs mode_config_funcs = {
.fb_create = drm_fb_cma_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
@@ -173,6 +173,10 @@ static struct drm_driver pl111_drm_driver = {
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
.gem_prime_export = drm_gem_prime_export,
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+
+#if defined(CONFIG_DEBUG_FS)
+ .debugfs_init = pl111_debugfs_init,
+#endif
};
#ifdef CONFIG_ARM_AMBA
@@ -195,17 +199,10 @@ static int pl111_amba_probe(struct amba_device *amba_dev,
priv->drm = drm;
drm->dev_private = priv;
- priv->clk = devm_clk_get(dev, "clcdclk");
- if (IS_ERR(priv->clk)) {
- dev_err(dev, "CLCD: unable to get clk.\n");
- ret = PTR_ERR(priv->clk);
- goto dev_unref;
- }
-
priv->regs = devm_ioremap_resource(dev, &amba_dev->res);
- if (!priv->regs) {
+ if (IS_ERR(priv->regs)) {
dev_err(dev, "%s failed mmio\n", __func__);
- return -EINVAL;
+ return PTR_ERR(priv->regs);
}
/* turn off interrupts before requesting the irq */
OpenPOWER on IntegriCloud