| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2019-2022 Bootlin |
| * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> |
| */ |
| |
| #include <linux/of.h> |
| #include <linux/of_graph.h> |
| #include <linux/types.h> |
| #include <linux/workqueue.h> |
| |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_crtc.h> |
| #include <drm/drm_drv.h> |
| #include <drm/drm_gem_dma_helper.h> |
| #include <drm/drm_print.h> |
| #include <drm/drm_vblank.h> |
| |
| #include "logicvc_crtc.h" |
| #include "logicvc_drm.h" |
| #include "logicvc_interface.h" |
| #include "logicvc_layer.h" |
| #include "logicvc_regs.h" |
| |
| #define logicvc_crtc(c) \ |
| container_of(c, struct logicvc_crtc, drm_crtc) |
| |
| static enum drm_mode_status |
| logicvc_crtc_mode_valid(struct drm_crtc *drm_crtc, |
| const struct drm_display_mode *mode) |
| { |
| if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static void logicvc_crtc_atomic_begin(struct drm_crtc *drm_crtc, |
| struct drm_atomic_state *state) |
| { |
| struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc); |
| struct drm_crtc_state *old_state = |
| drm_atomic_get_old_crtc_state(state, drm_crtc); |
| struct drm_device *drm_dev = drm_crtc->dev; |
| unsigned long flags; |
| |
| /* |
| * We need to grab the pending event here if vblank was already enabled |
| * since we won't get a call to atomic_enable to grab it. |
| */ |
| if (drm_crtc->state->event && old_state->active) { |
| spin_lock_irqsave(&drm_dev->event_lock, flags); |
| WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0); |
| |
| crtc->event = drm_crtc->state->event; |
| drm_crtc->state->event = NULL; |
| |
| spin_unlock_irqrestore(&drm_dev->event_lock, flags); |
| } |
| } |
| |
| static void logicvc_crtc_atomic_enable(struct drm_crtc *drm_crtc, |
| struct drm_atomic_state *state) |
| { |
| struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc); |
| struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); |
| struct drm_crtc_state *old_state = |
| drm_atomic_get_old_crtc_state(state, drm_crtc); |
| struct drm_crtc_state *new_state = |
| drm_atomic_get_new_crtc_state(state, drm_crtc); |
| struct drm_display_mode *mode = &new_state->adjusted_mode; |
| |
| struct drm_device *drm_dev = drm_crtc->dev; |
| unsigned int hact, hfp, hsl, hbp; |
| unsigned int vact, vfp, vsl, vbp; |
| unsigned long flags; |
| u32 ctrl; |
| |
| /* Timings */ |
| |
| hact = mode->hdisplay; |
| hfp = mode->hsync_start - mode->hdisplay; |
| hsl = mode->hsync_end - mode->hsync_start; |
| hbp = mode->htotal - mode->hsync_end; |
| |
| vact = mode->vdisplay; |
| vfp = mode->vsync_start - mode->vdisplay; |
| vsl = mode->vsync_end - mode->vsync_start; |
| vbp = mode->vtotal - mode->vsync_end; |
| |
| regmap_write(logicvc->regmap, LOGICVC_HSYNC_FRONT_PORCH_REG, hfp - 1); |
| regmap_write(logicvc->regmap, LOGICVC_HSYNC_REG, hsl - 1); |
| regmap_write(logicvc->regmap, LOGICVC_HSYNC_BACK_PORCH_REG, hbp - 1); |
| regmap_write(logicvc->regmap, LOGICVC_HRES_REG, hact - 1); |
| |
| regmap_write(logicvc->regmap, LOGICVC_VSYNC_FRONT_PORCH_REG, vfp - 1); |
| regmap_write(logicvc->regmap, LOGICVC_VSYNC_REG, vsl - 1); |
| regmap_write(logicvc->regmap, LOGICVC_VSYNC_BACK_PORCH_REG, vbp - 1); |
| regmap_write(logicvc->regmap, LOGICVC_VRES_REG, vact - 1); |
| |
| /* Signals */ |
| |
| ctrl = LOGICVC_CTRL_HSYNC_ENABLE | LOGICVC_CTRL_VSYNC_ENABLE | |
| LOGICVC_CTRL_DE_ENABLE; |
| |
| if (mode->flags & DRM_MODE_FLAG_NHSYNC) |
| ctrl |= LOGICVC_CTRL_HSYNC_INVERT; |
| |
| if (mode->flags & DRM_MODE_FLAG_NVSYNC) |
| ctrl |= LOGICVC_CTRL_VSYNC_INVERT; |
| |
| if (logicvc->interface) { |
| struct drm_connector *connector = |
| &logicvc->interface->drm_connector; |
| struct drm_display_info *display_info = |
| &connector->display_info; |
| |
| if (display_info->bus_flags & DRM_BUS_FLAG_DE_LOW) |
| ctrl |= LOGICVC_CTRL_DE_INVERT; |
| |
| if (display_info->bus_flags & |
| DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) |
| ctrl |= LOGICVC_CTRL_CLOCK_INVERT; |
| } |
| |
| regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG, |
| LOGICVC_CTRL_HSYNC_ENABLE | |
| LOGICVC_CTRL_HSYNC_INVERT | |
| LOGICVC_CTRL_VSYNC_ENABLE | |
| LOGICVC_CTRL_VSYNC_INVERT | |
| LOGICVC_CTRL_DE_ENABLE | |
| LOGICVC_CTRL_DE_INVERT | |
| LOGICVC_CTRL_PIXEL_INVERT | |
| LOGICVC_CTRL_CLOCK_INVERT, ctrl); |
| |
| /* Generate internal state reset. */ |
| regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0); |
| |
| drm_crtc_vblank_on(drm_crtc); |
| |
| /* Register our event after vblank is enabled. */ |
| if (drm_crtc->state->event && !old_state->active) { |
| spin_lock_irqsave(&drm_dev->event_lock, flags); |
| WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0); |
| |
| crtc->event = drm_crtc->state->event; |
| drm_crtc->state->event = NULL; |
| spin_unlock_irqrestore(&drm_dev->event_lock, flags); |
| } |
| } |
| |
| static void logicvc_crtc_atomic_disable(struct drm_crtc *drm_crtc, |
| struct drm_atomic_state *state) |
| { |
| struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); |
| struct drm_device *drm_dev = drm_crtc->dev; |
| |
| drm_crtc_vblank_off(drm_crtc); |
| |
| /* Disable and clear CRTC bits. */ |
| regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG, |
| LOGICVC_CTRL_HSYNC_ENABLE | |
| LOGICVC_CTRL_HSYNC_INVERT | |
| LOGICVC_CTRL_VSYNC_ENABLE | |
| LOGICVC_CTRL_VSYNC_INVERT | |
| LOGICVC_CTRL_DE_ENABLE | |
| LOGICVC_CTRL_DE_INVERT | |
| LOGICVC_CTRL_PIXEL_INVERT | |
| LOGICVC_CTRL_CLOCK_INVERT, 0); |
| |
| /* Generate internal state reset. */ |
| regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0); |
| |
| /* Consume any leftover event since vblank is now disabled. */ |
| if (drm_crtc->state->event && !drm_crtc->state->active) { |
| spin_lock_irq(&drm_dev->event_lock); |
| |
| drm_crtc_send_vblank_event(drm_crtc, drm_crtc->state->event); |
| drm_crtc->state->event = NULL; |
| spin_unlock_irq(&drm_dev->event_lock); |
| } |
| } |
| |
| static const struct drm_crtc_helper_funcs logicvc_crtc_helper_funcs = { |
| .mode_valid = logicvc_crtc_mode_valid, |
| .atomic_begin = logicvc_crtc_atomic_begin, |
| .atomic_enable = logicvc_crtc_atomic_enable, |
| .atomic_disable = logicvc_crtc_atomic_disable, |
| }; |
| |
| static int logicvc_crtc_enable_vblank(struct drm_crtc *drm_crtc) |
| { |
| struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); |
| |
| /* Clear any pending V_SYNC interrupt. */ |
| regmap_write_bits(logicvc->regmap, LOGICVC_INT_STAT_REG, |
| LOGICVC_INT_STAT_V_SYNC, LOGICVC_INT_STAT_V_SYNC); |
| |
| /* Unmask V_SYNC interrupt. */ |
| regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG, |
| LOGICVC_INT_MASK_V_SYNC, 0); |
| |
| return 0; |
| } |
| |
| static void logicvc_crtc_disable_vblank(struct drm_crtc *drm_crtc) |
| { |
| struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev); |
| |
| /* Mask V_SYNC interrupt. */ |
| regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG, |
| LOGICVC_INT_MASK_V_SYNC, LOGICVC_INT_MASK_V_SYNC); |
| } |
| |
| static const struct drm_crtc_funcs logicvc_crtc_funcs = { |
| .reset = drm_atomic_helper_crtc_reset, |
| .destroy = drm_crtc_cleanup, |
| .set_config = drm_atomic_helper_set_config, |
| .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, |
| .enable_vblank = logicvc_crtc_enable_vblank, |
| .disable_vblank = logicvc_crtc_disable_vblank, |
| }; |
| |
| void logicvc_crtc_vblank_handler(struct logicvc_drm *logicvc) |
| { |
| struct drm_device *drm_dev = &logicvc->drm_dev; |
| struct logicvc_crtc *crtc = logicvc->crtc; |
| unsigned long flags; |
| |
| if (!crtc) |
| return; |
| |
| drm_crtc_handle_vblank(&crtc->drm_crtc); |
| |
| if (crtc->event) { |
| spin_lock_irqsave(&drm_dev->event_lock, flags); |
| drm_crtc_send_vblank_event(&crtc->drm_crtc, crtc->event); |
| drm_crtc_vblank_put(&crtc->drm_crtc); |
| crtc->event = NULL; |
| spin_unlock_irqrestore(&drm_dev->event_lock, flags); |
| } |
| } |
| |
| int logicvc_crtc_init(struct logicvc_drm *logicvc) |
| { |
| struct drm_device *drm_dev = &logicvc->drm_dev; |
| struct device *dev = drm_dev->dev; |
| struct device_node *of_node = dev->of_node; |
| struct logicvc_crtc *crtc; |
| struct logicvc_layer *layer_primary; |
| int ret; |
| |
| crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); |
| if (!crtc) |
| return -ENOMEM; |
| |
| layer_primary = logicvc_layer_get_primary(logicvc); |
| if (!layer_primary) { |
| drm_err(drm_dev, "Failed to get primary layer\n"); |
| return -EINVAL; |
| } |
| |
| ret = drm_crtc_init_with_planes(drm_dev, &crtc->drm_crtc, |
| &layer_primary->drm_plane, NULL, |
| &logicvc_crtc_funcs, NULL); |
| if (ret) { |
| drm_err(drm_dev, "Failed to initialize CRTC\n"); |
| return ret; |
| } |
| |
| drm_crtc_helper_add(&crtc->drm_crtc, &logicvc_crtc_helper_funcs); |
| |
| crtc->drm_crtc.port = of_graph_get_port_by_id(of_node, 1); |
| |
| logicvc->crtc = crtc; |
| |
| return 0; |
| } |