diff options
Diffstat (limited to 'drivers/gpu/drm/armada/armada_crtc.c')
| -rw-r--r-- | drivers/gpu/drm/armada/armada_crtc.c | 297 |
1 files changed, 240 insertions, 57 deletions
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index da9360688b55..033b19b31f63 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -1,20 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2012 Russell King * Rewritten from the dovefb driver, and Armada510 manuals. - * - * 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/clk.h> #include <linux/component.h> -#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/of.h> #include <linux/platform_device.h> -#include <drm/drmP.h> +#include <linux/property.h> + #include <drm/drm_atomic.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_plane_helper.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + #include "armada_crtc.h" #include "armada_drm.h" #include "armada_fb.h" @@ -130,6 +132,70 @@ static void armada_drm_crtc_queue_state_event(struct drm_crtc *crtc) } } +static void armada_drm_update_gamma(struct drm_crtc *crtc) +{ + struct drm_property_blob *blob = crtc->state->gamma_lut; + void __iomem *base = drm_to_armada_crtc(crtc)->base; + int i; + + if (blob) { + struct drm_color_lut *lut = blob->data; + + armada_updatel(CFG_CSB_256x8, CFG_CSB_256x8 | CFG_PDWN256x8, + base + LCD_SPU_SRAM_PARA1); + + for (i = 0; i < 256; i++) { + writel_relaxed(drm_color_lut_extract(lut[i].red, 8), + base + LCD_SPU_SRAM_WRDAT); + writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_YR, + base + LCD_SPU_SRAM_CTRL); + readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN); + writel_relaxed(drm_color_lut_extract(lut[i].green, 8), + base + LCD_SPU_SRAM_WRDAT); + writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_UG, + base + LCD_SPU_SRAM_CTRL); + readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN); + writel_relaxed(drm_color_lut_extract(lut[i].blue, 8), + base + LCD_SPU_SRAM_WRDAT); + writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_VB, + base + LCD_SPU_SRAM_CTRL); + readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN); + } + armada_updatel(CFG_GAMMA_ENA, CFG_GAMMA_ENA, + base + LCD_SPU_DMA_CTRL0); + } else { + armada_updatel(0, CFG_GAMMA_ENA, base + LCD_SPU_DMA_CTRL0); + armada_updatel(CFG_PDWN256x8, CFG_CSB_256x8 | CFG_PDWN256x8, + base + LCD_SPU_SRAM_PARA1); + } +} + +static enum drm_mode_status armada_drm_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); + + if (mode->vscan > 1) + return MODE_NO_VSCAN; + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + if (mode->flags & DRM_MODE_FLAG_HSKEW) + return MODE_H_ILLEGAL; + + /* We can't do interlaced modes if we don't have the SPU_ADV_REG */ + if (!dcrtc->variant->has_spu_adv_reg && + mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + if (mode->flags & (DRM_MODE_FLAG_BCAST | DRM_MODE_FLAG_PIXMUX | + DRM_MODE_FLAG_CLKDIV2)) + return MODE_BAD; + + return MODE_OK; +} + /* The mode_config.mutex will be held for this call */ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adj) @@ -137,9 +203,18 @@ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); int ret; - /* We can't do interlaced modes if we don't have the SPU_ADV_REG */ - if (!dcrtc->variant->has_spu_adv_reg && - adj->flags & DRM_MODE_FLAG_INTERLACE) + /* + * Set CRTC modesetting parameters for the adjusted mode. This is + * applied after the connectors, bridges, and encoders have fixed up + * this mode, as described above drm_atomic_helper_check_modeset(). + */ + drm_mode_set_crtcinfo(adj, CRTC_INTERLACE_HALVE_V); + + /* + * Validate the adjusted mode in case an encoder/bridge has set + * something we don't support. + */ + if (armada_drm_crtc_mode_valid(crtc, adj) != MODE_OK) return false; /* Check whether the display mode is possible */ @@ -270,13 +345,7 @@ static void armada_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) tm = adj->crtc_vtotal - adj->crtc_vsync_end; DRM_DEBUG_KMS("[CRTC:%d:%s] mode " DRM_MODE_FMT "\n", - crtc->base.id, crtc->name, - adj->base.id, adj->name, adj->vrefresh, adj->clock, - adj->crtc_hdisplay, adj->crtc_hsync_start, - adj->crtc_hsync_end, adj->crtc_htotal, - adj->crtc_vdisplay, adj->crtc_vsync_start, - adj->crtc_vsync_end, adj->crtc_vtotal, - adj->type, adj->flags); + crtc->base.id, crtc->name, DRM_MODE_ARG(adj)); DRM_DEBUG_KMS("lm %d rm %d tm %d bm %d\n", lm, rm, tm, bm); /* Now compute the divider for real */ @@ -284,16 +353,9 @@ static void armada_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) armada_reg_queue_set(regs, i, sclk, LCD_CFG_SCLK_DIV); - if (interlaced ^ dcrtc->interlaced) { - if (adj->flags & DRM_MODE_FLAG_INTERLACE) - drm_crtc_vblank_get(&dcrtc->crtc); - else - drm_crtc_vblank_put(&dcrtc->crtc); - dcrtc->interlaced = interlaced; - } - spin_lock_irqsave(&dcrtc->irq_lock, flags); + dcrtc->interlaced = interlaced; /* Even interlaced/progressive frame */ dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 | adj->crtc_htotal; @@ -351,20 +413,43 @@ static void armada_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) spin_unlock_irqrestore(&dcrtc->irq_lock, flags); } +static int armada_drm_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); + + if (crtc_state->gamma_lut && drm_color_lut_size(crtc_state->gamma_lut) != 256) + return -EINVAL; + + if (crtc_state->color_mgmt_changed) + crtc_state->planes_changed = true; + + return 0; +} + static void armada_drm_crtc_atomic_begin(struct drm_crtc *crtc, - struct drm_crtc_state *old_crtc_state) + struct drm_atomic_state *state) { + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); + if (crtc_state->color_mgmt_changed) + armada_drm_update_gamma(crtc); + dcrtc->regs_idx = 0; dcrtc->regs = dcrtc->atomic_regs; } static void armada_drm_crtc_atomic_flush(struct drm_crtc *crtc, - struct drm_crtc_state *old_crtc_state) + struct drm_atomic_state *state) { + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); @@ -375,7 +460,7 @@ static void armada_drm_crtc_atomic_flush(struct drm_crtc *crtc, * If we aren't doing a full modeset, then we need to queue * the event here. */ - if (!drm_atomic_crtc_needs_modeset(crtc->state)) { + if (!drm_atomic_crtc_needs_modeset(crtc_state)) { dcrtc->update_pending = true; armada_drm_crtc_queue_state_event(crtc); spin_lock_irq(&dcrtc->irq_lock); @@ -389,13 +474,18 @@ static void armada_drm_crtc_atomic_flush(struct drm_crtc *crtc, } static void armada_drm_crtc_atomic_disable(struct drm_crtc *crtc, - struct drm_crtc_state *old_state) + struct drm_atomic_state *state) { + struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, + crtc); struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); struct drm_pending_vblank_event *event; DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); + if (old_state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + drm_crtc_vblank_put(crtc); + drm_crtc_vblank_off(crtc); armada_drm_crtc_update(dcrtc, false); @@ -422,8 +512,10 @@ static void armada_drm_crtc_atomic_disable(struct drm_crtc *crtc, } static void armada_drm_crtc_atomic_enable(struct drm_crtc *crtc, - struct drm_crtc_state *old_state) + struct drm_atomic_state *state) { + struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, + crtc); struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); @@ -440,12 +532,17 @@ static void armada_drm_crtc_atomic_enable(struct drm_crtc *crtc, armada_drm_crtc_update(dcrtc, true); drm_crtc_vblank_on(crtc); + if (crtc->state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + WARN_ON(drm_crtc_vblank_get(crtc)); + armada_drm_crtc_queue_state_event(crtc); } static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = { + .mode_valid = armada_drm_crtc_mode_valid, .mode_fixup = armada_drm_crtc_mode_fixup, .mode_set_nofb = armada_drm_crtc_mode_set_nofb, + .atomic_check = armada_drm_crtc_atomic_check, .atomic_begin = armada_drm_crtc_atomic_begin, .atomic_flush = armada_drm_crtc_atomic_flush, .atomic_disable = armada_drm_crtc_atomic_disable, @@ -466,6 +563,13 @@ static void armada_load_cursor_argb(void __iomem *base, uint32_t *pix, for (x = 0; x < width; x++, p++) { uint32_t val = *p; + /* + * In "ARGB888" (HWC32) mode, writing to the SRAM + * requires these bits to contain: + * 31:24 = alpha 23:16 = blue 15:8 = green 7:0 = red + * So, it's actually ABGR8888. This is independent + * of the SWAPRB bits in DMA control register 0. + */ val = (val & 0xff00ff00) | (val & 0x000000ff) << 16 | (val & 0x00ff0000) >> 16; @@ -617,13 +721,13 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc, /* Must be a kernel-mapped object */ if (!obj->addr) { - drm_gem_object_put_unlocked(&obj->obj); + drm_gem_object_put(&obj->obj); return -EINVAL; } if (obj->obj.size < w * h * 4) { DRM_ERROR("buffer is too small\n"); - drm_gem_object_put_unlocked(&obj->obj); + drm_gem_object_put(&obj->obj); return -ENOMEM; } } @@ -631,7 +735,7 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc, if (dcrtc->cursor_obj) { dcrtc->cursor_obj->update = NULL; dcrtc->cursor_obj->update_data = NULL; - drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj); + drm_gem_object_put(&dcrtc->cursor_obj->obj); } dcrtc->cursor_obj = obj; dcrtc->cursor_w = w; @@ -664,10 +768,10 @@ static int armada_drm_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) static void armada_drm_crtc_destroy(struct drm_crtc *crtc) { struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); - struct armada_private *priv = crtc->dev->dev_private; + struct armada_private *priv = drm_to_armada_dev(crtc->dev); if (dcrtc->cursor_obj) - drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj); + drm_gem_object_put(&dcrtc->cursor_obj->obj); priv->dcrtc[dcrtc->num] = NULL; drm_crtc_cleanup(&dcrtc->crtc); @@ -682,6 +786,14 @@ static void armada_drm_crtc_destroy(struct drm_crtc *crtc) kfree(dcrtc); } +static int armada_drm_crtc_late_register(struct drm_crtc *crtc) +{ + if (IS_ENABLED(CONFIG_DEBUG_FS)) + armada_drm_crtc_debugfs_init(drm_to_armada_crtc(crtc)); + + return 0; +} + /* These are called under the vbl_lock. */ static int armada_drm_crtc_enable_vblank(struct drm_crtc *crtc) { @@ -713,15 +825,93 @@ static const struct drm_crtc_funcs armada_crtc_funcs = { .page_flip = drm_atomic_helper_page_flip, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .late_register = armada_drm_crtc_late_register, .enable_vblank = armada_drm_crtc_enable_vblank, .disable_vblank = armada_drm_crtc_disable_vblank, }; +int armada_crtc_select_clock(struct armada_crtc *dcrtc, + struct armada_clk_result *res, + const struct armada_clocking_params *params, + struct clk *clks[], size_t num_clks, + unsigned long desired_khz) +{ + unsigned long desired_hz = desired_khz * 1000; + unsigned long desired_clk_hz; // requested clk input + unsigned long real_clk_hz; // actual clk input + unsigned long real_hz; // actual pixel clk + unsigned long permillage; + struct clk *clk; + u32 div; + int i; + + DRM_DEBUG_KMS("[CRTC:%u:%s] desired clock=%luHz\n", + dcrtc->crtc.base.id, dcrtc->crtc.name, desired_hz); + + for (i = 0; i < num_clks; i++) { + clk = clks[i]; + if (!clk) + continue; + + if (params->settable & BIT(i)) { + real_clk_hz = clk_round_rate(clk, desired_hz); + desired_clk_hz = desired_hz; + } else { + real_clk_hz = clk_get_rate(clk); + desired_clk_hz = real_clk_hz; + } + + /* If the clock can do exactly the desired rate, we're done */ + if (real_clk_hz == desired_hz) { + real_hz = real_clk_hz; + div = 1; + goto found; + } + + /* Calculate the divider - if invalid, we can't do this rate */ + div = DIV_ROUND_CLOSEST(real_clk_hz, desired_hz); + if (div == 0 || div > params->div_max) + continue; + + /* Calculate the actual rate - HDMI requires -0.6%..+0.5% */ + real_hz = DIV_ROUND_CLOSEST(real_clk_hz, div); + + DRM_DEBUG_KMS("[CRTC:%u:%s] clk=%u %luHz div=%u real=%luHz\n", + dcrtc->crtc.base.id, dcrtc->crtc.name, + i, real_clk_hz, div, real_hz); + + /* Avoid repeated division */ + if (real_hz < desired_hz) { + permillage = real_hz / desired_khz; + if (permillage < params->permillage_min) + continue; + } else { + permillage = DIV_ROUND_UP(real_hz, desired_khz); + if (permillage > params->permillage_max) + continue; + } + goto found; + } + + return -ERANGE; + +found: + DRM_DEBUG_KMS("[CRTC:%u:%s] selected clk=%u %luHz div=%u real=%luHz\n", + dcrtc->crtc.base.id, dcrtc->crtc.name, + i, real_clk_hz, div, real_hz); + + res->desired_clk_hz = desired_clk_hz; + res->clk = clk; + res->div = div; + + return i; +} + static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, struct resource *res, int irq, const struct armada_variant *variant, struct device_node *port) { - struct armada_private *priv = drm->dev_private; + struct armada_private *priv = drm_to_armada_dev(drm); struct armada_crtc *dcrtc; struct drm_plane *primary; void __iomem *base; @@ -743,7 +933,6 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, dcrtc->variant = variant; dcrtc->base = base; dcrtc->num = drm->mode_config.num_crtc; - dcrtc->clk = ERR_PTR(-EINVAL); dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0; dcrtc->spu_iopad_ctrl = CFG_VSCALE_LN_EN | CFG_IOPAD_DUMB24; spin_lock_init(&dcrtc->irq_lock); @@ -800,6 +989,12 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs); + ret = drm_mode_crtc_set_gamma_size(&dcrtc->crtc, 256); + if (ret) + return ret; + + drm_crtc_enable_color_mgmt(&dcrtc->crtc, 0, false, 256); + return armada_overlay_plane_create(drm, 1 << dcrtc->num); err_crtc_init: @@ -819,26 +1014,17 @@ armada_lcd_bind(struct device *dev, struct device *master, void *data) int irq = platform_get_irq(pdev, 0); const struct armada_variant *variant; struct device_node *port = NULL; + struct device_node *np, *parent = dev->of_node; if (irq < 0) return irq; - if (!dev->of_node) { - const struct platform_device_id *id; - - id = platform_get_device_id(pdev); - if (!id) - return -ENXIO; - - variant = (const struct armada_variant *)id->driver_data; - } else { - const struct of_device_id *match; - struct device_node *np, *parent = dev->of_node; - match = of_match_device(dev->driver->of_match_table, dev); - if (!match) - return -ENXIO; + variant = device_get_match_data(dev); + if (!variant) + return -ENXIO; + if (parent) { np = of_get_child_by_name(parent, "ports"); if (np) parent = np; @@ -848,8 +1034,6 @@ armada_lcd_bind(struct device *dev, struct device *master, void *data) dev_err(dev, "no port node found in %pOF\n", parent); return -ENXIO; } - - variant = match->data; } return armada_drm_crtc_create(drm, dev, res, irq, variant, port); @@ -873,10 +1057,9 @@ static int armada_lcd_probe(struct platform_device *pdev) return component_add(&pdev->dev, &armada_lcd_ops); } -static int armada_lcd_remove(struct platform_device *pdev) +static void armada_lcd_remove(struct platform_device *pdev) { component_del(&pdev->dev, &armada_lcd_ops); - return 0; } static const struct of_device_id armada_lcd_of_match[] = { @@ -902,7 +1085,7 @@ MODULE_DEVICE_TABLE(platform, armada_lcd_platform_ids); struct platform_driver armada_lcd_platform_driver = { .probe = armada_lcd_probe, - .remove = armada_lcd_remove, + .remove = armada_lcd_remove, .driver = { .name = "armada-lcd", .owner = THIS_MODULE, |
