diff options
Diffstat (limited to 'drivers/gpu/drm/pl111')
-rw-r--r-- | drivers/gpu/drm/pl111/Kconfig | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_debugfs.c | 55 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_display.c | 164 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_drm.h | 11 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_drv.c | 21 |
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 */ |