diff options
Diffstat (limited to 'drivers/gpu/drm/sun4i')
25 files changed, 2483 insertions, 124 deletions
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 882d85db9053..eee6bc0eaf97 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -1,6 +1,6 @@ config DRM_SUN4I tristate "DRM Support for Allwinner A10 Display Engine" - depends on DRM && ARM && COMMON_CLK + depends on DRM && (ARM || ARM64) && COMMON_CLK depends on ARCH_SUNXI || COMPILE_TEST select DRM_GEM_CMA_HELPER select DRM_KMS_HELPER @@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND do some alpha blending and feed graphics to TCON. If M is selected the module will be called sun4i-backend. +config DRM_SUN8I_DW_HDMI + tristate "Support for Allwinner version of DesignWare HDMI" + depends on DRM_SUN4I + select DRM_DW_HDMI + help + Choose this option if you have an Allwinner SoC with the + DesignWare HDMI controller with custom HDMI PHY. If M is + selected the module will be called sun8i_dw_hdmi. + config DRM_SUN8I_MIXER tristate "Support for Allwinner Display Engine 2.0 Mixer" default MACH_SUN8I diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 2b37a6abbb1d..330843ce4280 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 sun4i-backend-y += sun4i_backend.o sun4i_layer.o +sun4i-frontend-y += sun4i_frontend.o sun4i-drm-y += sun4i_drv.o sun4i-drm-y += sun4i_framebuffer.o @@ -9,6 +10,10 @@ sun4i-drm-hdmi-y += sun4i_hdmi_enc.o sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o +sun8i-drm-hdmi-y += sun8i_dw_hdmi.o +sun8i-drm-hdmi-y += sun8i_hdmi_phy.o +sun8i-drm-hdmi-y += sun8i_hdmi_phy_clk.o + sun8i-mixer-y += sun8i_mixer.o sun8i_ui_layer.o \ sun8i_vi_layer.o sun8i_ui_scaler.o \ sun8i_vi_scaler.o sun8i_csc.o @@ -24,6 +29,7 @@ obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o -obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o +obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o sun4i-frontend.o obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o +obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index 847eecbe4d14..9bad54f3de38 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -11,6 +11,7 @@ */ #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> @@ -26,6 +27,7 @@ #include "sun4i_backend.h" #include "sun4i_drv.h" +#include "sun4i_frontend.h" #include "sun4i_layer.h" #include "sunxi_engine.h" @@ -40,6 +42,56 @@ static const u32 sunxi_rgb2yuv_coef[12] = { 0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808 }; +/* + * These coefficients are taken from the A33 BSP from Allwinner. + * + * The formula is for each component, each coefficient being multiplied by + * 1024 and each constant being multiplied by 16: + * G = 1.164 * Y - 0.391 * U - 0.813 * V + 135 + * R = 1.164 * Y + 1.596 * V - 222 + * B = 1.164 * Y + 2.018 * U + 276 + * + * This seems to be a conversion from Y[16:235] UV[16:240] to RGB[0:255], + * following the BT601 spec. + */ +static const u32 sunxi_bt601_yuv2rgb_coef[12] = { + 0x000004a7, 0x00001e6f, 0x00001cbf, 0x00000877, + 0x000004a7, 0x00000000, 0x00000662, 0x00003211, + 0x000004a7, 0x00000812, 0x00000000, 0x00002eb1, +}; + +static inline bool sun4i_backend_format_is_planar_yuv(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV444: + return true; + default: + return false; + } +} + +static inline bool sun4i_backend_format_is_packed_yuv422(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + return true; + + default: + return false; + } +} + +static inline bool sun4i_backend_format_is_yuv(uint32_t format) +{ + return sun4i_backend_format_is_planar_yuv(format) || + sun4i_backend_format_is_packed_yuv422(format); +} + static void sun4i_backend_apply_color_correction(struct sunxi_engine *engine) { int i; @@ -90,13 +142,8 @@ void sun4i_backend_layer_enable(struct sun4i_backend *backend, SUN4I_BACKEND_MODCTL_LAY_EN(layer), val); } -static int sun4i_backend_drm_format_to_layer(struct drm_plane *plane, - u32 format, u32 *mode) +static int sun4i_backend_drm_format_to_layer(u32 format, u32 *mode) { - if ((plane->type == DRM_PLANE_TYPE_PRIMARY) && - (format == DRM_FORMAT_ARGB8888)) - format = DRM_FORMAT_XRGB8888; - switch (format) { case DRM_FORMAT_ARGB8888: *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB8888; @@ -141,7 +188,6 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, int layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - struct drm_framebuffer *fb = state->fb; DRM_DEBUG_DRIVER("Updating layer %d\n", layer); @@ -153,12 +199,6 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, state->crtc_h)); } - /* Set the line width */ - DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); - regmap_write(backend->engine.regs, - SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), - fb->pitches[0] * 8); - /* Set height and width */ DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", state->crtc_w, state->crtc_h); @@ -176,6 +216,61 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, return 0; } +static int sun4i_backend_update_yuv_format(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + uint32_t format = fb->format->format; + u32 val = SUN4I_BACKEND_IYUVCTL_EN; + int i; + + for (i = 0; i < ARRAY_SIZE(sunxi_bt601_yuv2rgb_coef); i++) + regmap_write(backend->engine.regs, + SUN4I_BACKEND_YGCOEF_REG(i), + sunxi_bt601_yuv2rgb_coef[i]); + + /* + * We should do that only for a single plane, but the + * framebuffer's atomic_check has our back on this. + */ + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_ATTCTL_REG0(layer), + SUN4I_BACKEND_ATTCTL_REG0_LAY_YUVEN, + SUN4I_BACKEND_ATTCTL_REG0_LAY_YUVEN); + + /* TODO: Add support for the multi-planar YUV formats */ + if (sun4i_backend_format_is_packed_yuv422(format)) + val |= SUN4I_BACKEND_IYUVCTL_FBFMT_PACKED_YUV422; + else + DRM_DEBUG_DRIVER("Unsupported YUV format (0x%x)\n", format); + + /* + * Allwinner seems to list the pixel sequence from right to left, while + * DRM lists it from left to right. + */ + switch (format) { + case DRM_FORMAT_YUYV: + val |= SUN4I_BACKEND_IYUVCTL_FBPS_VYUY; + break; + case DRM_FORMAT_YVYU: + val |= SUN4I_BACKEND_IYUVCTL_FBPS_UYVY; + break; + case DRM_FORMAT_UYVY: + val |= SUN4I_BACKEND_IYUVCTL_FBPS_YVYU; + break; + case DRM_FORMAT_VYUY: + val |= SUN4I_BACKEND_IYUVCTL_FBPS_YUYV; + break; + default: + DRM_DEBUG_DRIVER("Unsupported YUV pixel sequence (0x%x)\n", + format); + } + + regmap_write(backend->engine.regs, SUN4I_BACKEND_IYUVCTL_REG, val); + + return 0; +} + int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, int layer, struct drm_plane *plane) { @@ -185,6 +280,10 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, u32 val; int ret; + /* Clear the YUV mode */ + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_ATTCTL_REG0(layer), + SUN4I_BACKEND_ATTCTL_REG0_LAY_YUVEN, 0); + if (plane->state->crtc) interlaced = plane->state->crtc->state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE; @@ -196,8 +295,10 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, DRM_DEBUG_DRIVER("Switching display backend interlaced mode %s\n", interlaced ? "on" : "off"); - ret = sun4i_backend_drm_format_to_layer(plane, fb->format->format, - &val); + if (sun4i_backend_format_is_yuv(fb->format->format)) + return sun4i_backend_update_yuv_format(backend, layer, plane); + + ret = sun4i_backend_drm_format_to_layer(fb->format->format, &val); if (ret) { DRM_DEBUG_DRIVER("Invalid format\n"); return ret; @@ -210,6 +311,45 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, return 0; } +int sun4i_backend_update_layer_frontend(struct sun4i_backend *backend, + int layer, uint32_t fmt) +{ + u32 val; + int ret; + + ret = sun4i_backend_drm_format_to_layer(fmt, &val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid format\n"); + return ret; + } + + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_ATTCTL_REG0(layer), + SUN4I_BACKEND_ATTCTL_REG0_LAY_VDOEN, + SUN4I_BACKEND_ATTCTL_REG0_LAY_VDOEN); + + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_ATTCTL_REG1(layer), + SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); + + return 0; +} + +static int sun4i_backend_update_yuv_buffer(struct sun4i_backend *backend, + struct drm_framebuffer *fb, + dma_addr_t paddr) +{ + /* TODO: Add support for the multi-planar YUV formats */ + DRM_DEBUG_DRIVER("Setting packed YUV buffer address to %pad\n", &paddr); + regmap_write(backend->engine.regs, SUN4I_BACKEND_IYUVADD_REG(0), paddr); + + DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); + regmap_write(backend->engine.regs, SUN4I_BACKEND_IYUVLINEWIDTH_REG(0), + fb->pitches[0] * 8); + + return 0; +} + int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, int layer, struct drm_plane *plane) { @@ -218,6 +358,12 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, u32 lo_paddr, hi_paddr; dma_addr_t paddr; + /* Set the line width */ + DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); + regmap_write(backend->engine.regs, + SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), + fb->pitches[0] * 8); + /* Get the start of the displayed memory */ paddr = drm_fb_cma_get_gem_addr(fb, state, 0); DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); @@ -229,6 +375,9 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, */ paddr -= PHYS_OFFSET; + if (sun4i_backend_format_is_yuv(fb->format->format)) + return sun4i_backend_update_yuv_buffer(backend, fb, paddr); + /* Write the 32 lower bits of the address (in bits) */ lo_paddr = paddr << 3; DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); @@ -246,6 +395,225 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, return 0; } +int sun4i_backend_update_layer_zpos(struct sun4i_backend *backend, int layer, + struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct sun4i_layer_state *p_state = state_to_sun4i_layer_state(state); + unsigned int priority = state->normalized_zpos; + unsigned int pipe = p_state->pipe; + + DRM_DEBUG_DRIVER("Setting layer %d's priority to %d and pipe %d\n", + layer, priority, pipe); + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_ATTCTL_REG0(layer), + SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK | + SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK, + SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(p_state->pipe) | + SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(priority)); + + return 0; +} + +static bool sun4i_backend_plane_uses_scaler(struct drm_plane_state *state) +{ + u16 src_h = state->src_h >> 16; + u16 src_w = state->src_w >> 16; + + DRM_DEBUG_DRIVER("Input size %dx%d, output size %dx%d\n", + src_w, src_h, state->crtc_w, state->crtc_h); + + if ((state->crtc_h != src_h) || (state->crtc_w != src_w)) + return true; + + return false; +} + +static bool sun4i_backend_plane_uses_frontend(struct drm_plane_state *state) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(state->plane); + struct sun4i_backend *backend = layer->backend; + + if (IS_ERR(backend->frontend)) + return false; + + return sun4i_backend_plane_uses_scaler(state); +} + +static void sun4i_backend_atomic_begin(struct sunxi_engine *engine, + struct drm_crtc_state *old_state) +{ + u32 val; + + WARN_ON(regmap_read_poll_timeout(engine->regs, + SUN4I_BACKEND_REGBUFFCTL_REG, + val, !(val & SUN4I_BACKEND_REGBUFFCTL_LOADCTL), + 100, 50000)); +} + +static int sun4i_backend_atomic_check(struct sunxi_engine *engine, + struct drm_crtc_state *crtc_state) +{ + struct drm_plane_state *plane_states[SUN4I_BACKEND_NUM_LAYERS] = { 0 }; + struct drm_atomic_state *state = crtc_state->state; + struct drm_device *drm = state->dev; + struct drm_plane *plane; + unsigned int num_planes = 0; + unsigned int num_alpha_planes = 0; + unsigned int num_frontend_planes = 0; + unsigned int num_yuv_planes = 0; + unsigned int current_pipe = 0; + unsigned int i; + + DRM_DEBUG_DRIVER("Starting checking our planes\n"); + + if (!crtc_state->planes_changed) + return 0; + + drm_for_each_plane_mask(plane, drm, crtc_state->plane_mask) { + struct drm_plane_state *plane_state = + drm_atomic_get_plane_state(state, plane); + struct sun4i_layer_state *layer_state = + state_to_sun4i_layer_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_format_name_buf format_name; + + if (sun4i_backend_plane_uses_frontend(plane_state)) { + DRM_DEBUG_DRIVER("Using the frontend for plane %d\n", + plane->index); + + layer_state->uses_frontend = true; + num_frontend_planes++; + } else { + layer_state->uses_frontend = false; + } + + DRM_DEBUG_DRIVER("Plane FB format is %s\n", + drm_get_format_name(fb->format->format, + &format_name)); + if (fb->format->has_alpha) + num_alpha_planes++; + + if (sun4i_backend_format_is_yuv(fb->format->format)) { + DRM_DEBUG_DRIVER("Plane FB format is YUV\n"); + num_yuv_planes++; + } + + DRM_DEBUG_DRIVER("Plane zpos is %d\n", + plane_state->normalized_zpos); + + /* Sort our planes by Zpos */ + plane_states[plane_state->normalized_zpos] = plane_state; + + num_planes++; + } + + /* All our planes were disabled, bail out */ + if (!num_planes) + return 0; + + /* + * The hardware is a bit unusual here. + * + * Even though it supports 4 layers, it does the composition + * in two separate steps. + * + * The first one is assigning a layer to one of its two + * pipes. If more that 1 layer is assigned to the same pipe, + * and if pixels overlaps, the pipe will take the pixel from + * the layer with the highest priority. + * + * The second step is the actual alpha blending, that takes + * the two pipes as input, and uses the eventual alpha + * component to do the transparency between the two. + * + * This two steps scenario makes us unable to guarantee a + * robust alpha blending between the 4 layers in all + * situations, since this means that we need to have one layer + * with alpha at the lowest position of our two pipes. + * + * However, we cannot even do that, since the hardware has a + * bug where the lowest plane of the lowest pipe (pipe 0, + * priority 0), if it has any alpha, will discard the pixel + * entirely and just display the pixels in the background + * color (black by default). + * + * This means that we effectively have only three valid + * configurations with alpha, all of them with the alpha being + * on pipe1 with the lowest position, which can be 1, 2 or 3 + * depending on the number of planes and their zpos. + */ + if (num_alpha_planes > SUN4I_BACKEND_NUM_ALPHA_LAYERS) { + DRM_DEBUG_DRIVER("Too many planes with alpha, rejecting...\n"); + return -EINVAL; + } + + /* We can't have an alpha plane at the lowest position */ + if (plane_states[0]->fb->format->has_alpha) + return -EINVAL; + + for (i = 1; i < num_planes; i++) { + struct drm_plane_state *p_state = plane_states[i]; + struct drm_framebuffer *fb = p_state->fb; + struct sun4i_layer_state *s_state = state_to_sun4i_layer_state(p_state); + + /* + * The only alpha position is the lowest plane of the + * second pipe. + */ + if (fb->format->has_alpha) + current_pipe++; + + s_state->pipe = current_pipe; + } + + /* We can only have a single YUV plane at a time */ + if (num_yuv_planes > SUN4I_BACKEND_NUM_YUV_PLANES) { + DRM_DEBUG_DRIVER("Too many planes with YUV, rejecting...\n"); + return -EINVAL; + } + + if (num_frontend_planes > SUN4I_BACKEND_NUM_FRONTEND_LAYERS) { + DRM_DEBUG_DRIVER("Too many planes going through the frontend, rejecting\n"); + return -EINVAL; + } + + DRM_DEBUG_DRIVER("State valid with %u planes, %u alpha, %u video, %u YUV\n", + num_planes, num_alpha_planes, num_frontend_planes, + num_yuv_planes); + + return 0; +} + +static void sun4i_backend_vblank_quirk(struct sunxi_engine *engine) +{ + struct sun4i_backend *backend = engine_to_sun4i_backend(engine); + struct sun4i_frontend *frontend = backend->frontend; + + if (!frontend) + return; + + /* + * In a teardown scenario with the frontend involved, we have + * to keep the frontend enabled until the next vblank, and + * only then disable it. + * + * This is due to the fact that the backend will not take into + * account the new configuration (with the plane that used to + * be fed by the frontend now disabled) until we write to the + * commit bit and the hardware fetches the new configuration + * during the next vblank. + * + * So we keep the frontend around in order to prevent any + * visual artifacts. + */ + spin_lock(&backend->frontend_lock); + if (backend->frontend_teardown) { + sun4i_frontend_exit(frontend); + backend->frontend_teardown = false; + } + spin_unlock(&backend->frontend_lock); +}; + static int sun4i_backend_init_sat(struct device *dev) { struct sun4i_backend *backend = dev_get_drvdata(dev); int ret; @@ -330,11 +698,43 @@ static int sun4i_backend_of_get_id(struct device_node *node) return ret; } +/* TODO: This needs to take multiple pipelines into account */ +static struct sun4i_frontend *sun4i_backend_find_frontend(struct sun4i_drv *drv, + struct device_node *node) +{ + struct device_node *port, *ep, *remote; + struct sun4i_frontend *frontend; + + port = of_graph_get_port_by_id(node, 0); + if (!port) + return ERR_PTR(-EINVAL); + + for_each_available_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote) + continue; + + /* does this node match any registered engines? */ + list_for_each_entry(frontend, &drv->frontend_list, list) { + if (remote == frontend->node) { + of_node_put(remote); + of_node_put(port); + return frontend; + } + } + } + + return ERR_PTR(-EINVAL); +} + static const struct sunxi_engine_ops sun4i_backend_engine_ops = { + .atomic_begin = sun4i_backend_atomic_begin, + .atomic_check = sun4i_backend_atomic_check, .commit = sun4i_backend_commit, .layers_init = sun4i_layers_init, .apply_color_correction = sun4i_backend_apply_color_correction, .disable_color_correction = sun4i_backend_disable_color_correction, + .vblank_quirk = sun4i_backend_vblank_quirk, }; static struct regmap_config sun4i_backend_regmap_config = { @@ -360,6 +760,7 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, if (!backend) return -ENOMEM; dev_set_drvdata(dev, backend); + spin_lock_init(&backend->frontend_lock); backend->engine.node = dev->of_node; backend->engine.ops = &sun4i_backend_engine_ops; @@ -367,6 +768,10 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, if (backend->engine.id < 0) return backend->engine.id; + backend->frontend = sun4i_backend_find_frontend(drv, dev->of_node); + if (IS_ERR(backend->frontend)) + dev_warn(dev, "Couldn't find matching frontend, frontend features disabled\n"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(dev, res); if (IS_ERR(regs)) @@ -531,6 +936,9 @@ static const struct sun4i_backend_quirks sun7i_backend_quirks = { static const struct sun4i_backend_quirks sun8i_a33_backend_quirks = { }; +static const struct sun4i_backend_quirks sun9i_backend_quirks = { +}; + static const struct of_device_id sun4i_backend_of_table[] = { { .compatible = "allwinner,sun4i-a10-display-backend", @@ -552,6 +960,10 @@ static const struct of_device_id sun4i_backend_of_table[] = { .compatible = "allwinner,sun8i-a33-display-backend", .data = &sun8i_a33_backend_quirks, }, + { + .compatible = "allwinner,sun9i-a80-display-backend", + .data = &sun9i_backend_quirks, + }, { } }; MODULE_DEVICE_TABLE(of, sun4i_backend_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index ac3cc029f5cd..316f2179e9e1 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -72,6 +72,8 @@ #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(x) ((x) << 15) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK GENMASK(11, 10) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(x) ((x) << 10) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_YUVEN BIT(2) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_VDOEN BIT(1) #define SUN4I_BACKEND_ATTCTL_REG1(l) (0x8a0 + (0x4 * (l))) #define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT GENMASK(15, 14) @@ -109,9 +111,27 @@ #define SUN4I_BACKEND_SPREN_REG 0x900 #define SUN4I_BACKEND_SPRFMTCTL_REG 0x908 #define SUN4I_BACKEND_SPRALPHACTL_REG 0x90c + #define SUN4I_BACKEND_IYUVCTL_REG 0x920 +#define SUN4I_BACKEND_IYUVCTL_FBFMT_MASK GENMASK(14, 12) +#define SUN4I_BACKEND_IYUVCTL_FBFMT_PACKED_YUV444 (4 << 12) +#define SUN4I_BACKEND_IYUVCTL_FBFMT_PACKED_YUV422 (3 << 12) +#define SUN4I_BACKEND_IYUVCTL_FBFMT_PLANAR_YUV444 (2 << 12) +#define SUN4I_BACKEND_IYUVCTL_FBFMT_PLANAR_YUV222 (1 << 12) +#define SUN4I_BACKEND_IYUVCTL_FBFMT_PLANAR_YUV111 (0 << 12) +#define SUN4I_BACKEND_IYUVCTL_FBPS_MASK GENMASK(9, 8) +#define SUN4I_BACKEND_IYUVCTL_FBPS_YVYU (3 << 8) +#define SUN4I_BACKEND_IYUVCTL_FBPS_VYUY (2 << 8) +#define SUN4I_BACKEND_IYUVCTL_FBPS_YUYV (1 << 8) +#define SUN4I_BACKEND_IYUVCTL_FBPS_UYVY (0 << 8) +#define SUN4I_BACKEND_IYUVCTL_FBPS_VUYA (1 << 8) +#define SUN4I_BACKEND_IYUVCTL_FBPS_AYUV (0 << 8) +#define SUN4I_BACKEND_IYUVCTL_EN BIT(0) + #define SUN4I_BACKEND_IYUVADD_REG(c) (0x930 + (0x4 * (c))) -#define SUN4I_BACKEND_IYUVLINEWITDTH_REG(c) (0x940 + (0x4 * (c))) + +#define SUN4I_BACKEND_IYUVLINEWIDTH_REG(c) (0x940 + (0x4 * (c))) + #define SUN4I_BACKEND_YGCOEF_REG(c) (0x950 + (0x4 * (c))) #define SUN4I_BACKEND_YGCONS_REG 0x95c #define SUN4I_BACKEND_URCOEF_REG(c) (0x960 + (0x4 * (c))) @@ -143,8 +163,14 @@ #define SUN4I_BACKEND_HWCCOLORTAB_OFF 0x4c00 #define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p))) +#define SUN4I_BACKEND_NUM_LAYERS 4 +#define SUN4I_BACKEND_NUM_ALPHA_LAYERS 1 +#define SUN4I_BACKEND_NUM_FRONTEND_LAYERS 1 +#define SUN4I_BACKEND_NUM_YUV_PLANES 1 + struct sun4i_backend { struct sunxi_engine engine; + struct sun4i_frontend *frontend; struct reset_control *reset; @@ -154,6 +180,10 @@ struct sun4i_backend { struct clk *sat_clk; struct reset_control *sat_reset; + + /* Protects against races in the frontend teardown */ + spinlock_t frontend_lock; + bool frontend_teardown; }; static inline struct sun4i_backend * @@ -170,5 +200,9 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, int layer, struct drm_plane *plane); int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, int layer, struct drm_plane *plane); +int sun4i_backend_update_layer_frontend(struct sun4i_backend *backend, + int layer, uint32_t in_fmt); +int sun4i_backend_update_layer_zpos(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); #endif /* _SUN4I_BACKEND_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index 78cbc3145e44..2d7c57406715 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -25,6 +25,7 @@ #include <video/videomode.h> +#include "sun4i_backend.h" #include "sun4i_crtc.h" #include "sun4i_drv.h" #include "sunxi_engine.h" @@ -46,11 +47,25 @@ static struct drm_encoder *sun4i_crtc_get_encoder(struct drm_crtc *crtc) return NULL; } +static int sun4i_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sunxi_engine *engine = scrtc->engine; + int ret = 0; + + if (engine && engine->ops && engine->ops->atomic_check) + ret = engine->ops->atomic_check(engine, state); + + return ret; +} + static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); struct drm_device *dev = crtc->dev; + struct sunxi_engine *engine = scrtc->engine; unsigned long flags; if (crtc->state->event) { @@ -60,7 +75,10 @@ static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, scrtc->event = crtc->state->event; spin_unlock_irqrestore(&dev->event_lock, flags); crtc->state->event = NULL; - } + } + + if (engine->ops->atomic_begin) + engine->ops->atomic_begin(engine, old_state); } static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, @@ -129,6 +147,7 @@ static void sun4i_crtc_mode_set_nofb(struct drm_crtc *crtc) } static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { + .atomic_check = sun4i_crtc_atomic_check, .atomic_begin = sun4i_crtc_atomic_begin, .atomic_flush = sun4i_crtc_atomic_flush, .atomic_enable = sun4i_crtc_atomic_enable, diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index d9a71f361b14..50d19605c38f 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -23,6 +23,7 @@ #include <drm/drm_of.h> #include "sun4i_drv.h" +#include "sun4i_frontend.h" #include "sun4i_framebuffer.h" #include "sun4i_tcon.h" @@ -91,6 +92,7 @@ static int sun4i_drv_bind(struct device *dev) goto free_drm; } drm->dev_private = drv; + INIT_LIST_HEAD(&drv->frontend_list); INIT_LIST_HEAD(&drv->engine_list); INIT_LIST_HEAD(&drv->tcon_list); @@ -173,7 +175,21 @@ static bool sun4i_drv_node_is_frontend(struct device_node *node) of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") || of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") || - of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); + of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend") || + of_device_is_compatible(node, "allwinner,sun9i-a80-display-frontend"); +} + +static bool sun4i_drv_node_is_deu(struct device_node *node) +{ + return of_device_is_compatible(node, "allwinner,sun9i-a80-deu"); +} + +static bool sun4i_drv_node_is_supported_frontend(struct device_node *node) +{ + if (IS_ENABLED(CONFIG_DRM_SUN4I_BACKEND)) + return !!of_match_node(sun4i_frontend_of_table, node); + + return false; } static bool sun4i_drv_node_is_tcon(struct device_node *node) @@ -224,9 +240,11 @@ static int sun4i_drv_add_endpoints(struct device *dev, int count = 0; /* - * We don't support the frontend for now, so we will never - * have a device bound. Just skip over it, but we still want - * the rest our pipeline to be added. + * The frontend has been disabled in some of our old device + * trees. If we find a node that is the frontend and is + * disabled, we should just follow through and parse its + * child, but without adding it to the component list. + * Otherwise, we obviously want to add it to the list. */ if (!sun4i_drv_node_is_frontend(node) && !of_device_is_available(node)) @@ -239,7 +257,15 @@ static int sun4i_drv_add_endpoints(struct device *dev, if (sun4i_drv_node_is_connector(node)) return 0; - if (!sun4i_drv_node_is_frontend(node)) { + /* + * If the device is either just a regular device, or an + * enabled frontend supported by the driver, we add it to our + * component list. + */ + if (!(sun4i_drv_node_is_frontend(node) || + sun4i_drv_node_is_deu(node)) || + (sun4i_drv_node_is_supported_frontend(node) && + of_device_is_available(node))) { /* Add current component */ DRM_DEBUG_DRIVER("Adding component %pOF\n", node); drm_of_component_match_add(dev, match, compare_of, node); @@ -339,7 +365,9 @@ static const struct of_device_id sun4i_drv_of_table[] = { { .compatible = "allwinner,sun7i-a20-display-engine" }, { .compatible = "allwinner,sun8i-a33-display-engine" }, { .compatible = "allwinner,sun8i-a83t-display-engine" }, + { .compatible = "allwinner,sun8i-h3-display-engine" }, { .compatible = "allwinner,sun8i-v3s-display-engine" }, + { .compatible = "allwinner,sun9i-a80-display-engine" }, { } }; MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h index 2825f140da54..5750b8ce8b31 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.h +++ b/drivers/gpu/drm/sun4i/sun4i_drv.h @@ -19,6 +19,7 @@ struct sun4i_drv { struct list_head engine_list; + struct list_head frontend_list; struct list_head tcon_list; }; diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c index 38a36c0dfa2f..5f29850ef8ac 100644 --- a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -10,6 +10,7 @@ * the License, or (at your option) any later version. */ +#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_fb_helper.h> #include <drm/drm_fb_cma_helper.h> @@ -19,13 +20,33 @@ #include "sun4i_drv.h" #include "sun4i_framebuffer.h" +static int sun4i_de_atomic_check(struct drm_device *dev, + struct drm_atomic_state *state) +{ + int ret; + + ret = drm_atomic_helper_check_modeset(dev, state); + if (ret) + return ret; + + ret = drm_atomic_normalize_zpos(dev, state); + if (ret) + return ret; + + return drm_atomic_helper_check_planes(dev, state); +} + static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = { .output_poll_changed = drm_fb_helper_output_poll_changed, - .atomic_check = drm_atomic_helper_check, + .atomic_check = sun4i_de_atomic_check, .atomic_commit = drm_atomic_helper_commit, .fb_create = drm_gem_fb_create, }; +static struct drm_mode_config_helper_funcs sun4i_de_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + int sun4i_framebuffer_init(struct drm_device *drm) { drm_mode_config_reset(drm); @@ -34,6 +55,7 @@ int sun4i_framebuffer_init(struct drm_device *drm) drm->mode_config.max_height = 8192; drm->mode_config.funcs = &sun4i_de_mode_config_funcs; + drm->mode_config.helper_private = &sun4i_de_mode_config_helpers; return drm_fb_cma_fbdev_init(drm, 32, 0); } diff --git a/drivers/gpu/drm/sun4i/sun4i_frontend.c b/drivers/gpu/drm/sun4i/sun4i_frontend.c new file mode 100644 index 000000000000..ddf6cfa6dd23 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_frontend.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Free Electrons + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ +#include <drm/drmP.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sun4i_drv.h" +#include "sun4i_frontend.h" + +static const u32 sun4i_frontend_vert_coef[32] = { + 0x00004000, 0x000140ff, 0x00033ffe, 0x00043ffd, + 0x00063efc, 0xff083dfc, 0x000a3bfb, 0xff0d39fb, + 0xff0f37fb, 0xff1136fa, 0xfe1433fb, 0xfe1631fb, + 0xfd192ffb, 0xfd1c2cfb, 0xfd1f29fb, 0xfc2127fc, + 0xfc2424fc, 0xfc2721fc, 0xfb291ffd, 0xfb2c1cfd, + 0xfb2f19fd, 0xfb3116fe, 0xfb3314fe, 0xfa3611ff, + 0xfb370fff, 0xfb390dff, 0xfb3b0a00, 0xfc3d08ff, + 0xfc3e0600, 0xfd3f0400, 0xfe3f0300, 0xff400100, +}; + +static const u32 sun4i_frontend_horz_coef[64] = { + 0x40000000, 0x00000000, 0x40fe0000, 0x0000ff03, + 0x3ffd0000, 0x0000ff05, 0x3ffc0000, 0x0000ff06, + 0x3efb0000, 0x0000ff08, 0x3dfb0000, 0x0000ff09, + 0x3bfa0000, 0x0000fe0d, 0x39fa0000, 0x0000fe0f, + 0x38fa0000, 0x0000fe10, 0x36fa0000, 0x0000fe12, + 0x33fa0000, 0x0000fd16, 0x31fa0000, 0x0000fd18, + 0x2ffa0000, 0x0000fd1a, 0x2cfa0000, 0x0000fc1e, + 0x29fa0000, 0x0000fc21, 0x27fb0000, 0x0000fb23, + 0x24fb0000, 0x0000fb26, 0x21fb0000, 0x0000fb29, + 0x1ffc0000, 0x0000fa2b, 0x1cfc0000, 0x0000fa2e, + 0x19fd0000, 0x0000fa30, 0x16fd0000, 0x0000fa33, + 0x14fd0000, 0x0000fa35, 0x11fe0000, 0x0000fa37, + 0x0ffe0000, 0x0000fa39, 0x0dfe0000, 0x0000fa3b, + 0x0afe0000, 0x0000fa3e, 0x08ff0000, 0x0000fb3e, + 0x06ff0000, 0x0000fb40, 0x05ff0000, 0x0000fc40, + 0x03ff0000, 0x0000fd41, 0x01ff0000, 0x0000fe42, +}; + +static void sun4i_frontend_scaler_init(struct sun4i_frontend *frontend) +{ + int i; + + for (i = 0; i < 32; i++) { + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZCOEF0_REG(i), + sun4i_frontend_horz_coef[2 * i]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZCOEF0_REG(i), + sun4i_frontend_horz_coef[2 * i]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZCOEF1_REG(i), + sun4i_frontend_horz_coef[2 * i + 1]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZCOEF1_REG(i), + sun4i_frontend_horz_coef[2 * i + 1]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTCOEF_REG(i), + sun4i_frontend_vert_coef[i]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTCOEF_REG(i), + sun4i_frontend_vert_coef[i]); + } + + regmap_update_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, + SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL, + SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL); +} + +int sun4i_frontend_init(struct sun4i_frontend *frontend) +{ + return pm_runtime_get_sync(frontend->dev); +} +EXPORT_SYMBOL(sun4i_frontend_init); + +void sun4i_frontend_exit(struct sun4i_frontend *frontend) +{ + pm_runtime_put(frontend->dev); +} +EXPORT_SYMBOL(sun4i_frontend_exit); + +void sun4i_frontend_update_buffer(struct sun4i_frontend *frontend, + struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + dma_addr_t paddr; + + /* Set the line width */ + DRM_DEBUG_DRIVER("Frontend stride: %d bytes\n", fb->pitches[0]); + regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD0_REG, + fb->pitches[0]); + + /* Set the physical address of the buffer in memory */ + paddr = drm_fb_cma_get_gem_addr(fb, state, 0); + paddr -= PHYS_OFFSET; + DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); + regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR0_REG, paddr); +} +EXPORT_SYMBOL(sun4i_frontend_update_buffer); + +static int sun4i_frontend_drm_format_to_input_fmt(uint32_t fmt, u32 *val) +{ + switch (fmt) { + case DRM_FORMAT_ARGB8888: + *val = 5; + return 0; + + default: + return -EINVAL; + } +} + +static int sun4i_frontend_drm_format_to_output_fmt(uint32_t fmt, u32 *val) +{ + switch (fmt) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + *val = 2; + return 0; + + default: + return -EINVAL; + } +} + +int sun4i_frontend_update_formats(struct sun4i_frontend *frontend, + struct drm_plane *plane, uint32_t out_fmt) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + u32 out_fmt_val; + u32 in_fmt_val; + int ret; + + ret = sun4i_frontend_drm_format_to_input_fmt(fb->format->format, + &in_fmt_val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid input format\n"); + return ret; + } + + ret = sun4i_frontend_drm_format_to_output_fmt(out_fmt, &out_fmt_val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid output format\n"); + return ret; + } + + /* + * I have no idea what this does exactly, but it seems to be + * related to the scaler FIR filter phase parameters. + */ + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZPHASE_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZPHASE_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE0_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE0_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE1_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE1_REG, 0x400); + + regmap_write(frontend->regs, SUN4I_FRONTEND_INPUT_FMT_REG, + SUN4I_FRONTEND_INPUT_FMT_DATA_MOD(1) | + SUN4I_FRONTEND_INPUT_FMT_DATA_FMT(in_fmt_val) | + SUN4I_FRONTEND_INPUT_FMT_PS(1)); + + /* + * TODO: It look like the A31 and A80 at least will need the + * bit 7 (ALPHA_EN) enabled when using a format with alpha (so + * ARGB8888). + */ + regmap_write(frontend->regs, SUN4I_FRONTEND_OUTPUT_FMT_REG, + SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT(out_fmt_val)); + + return 0; +} +EXPORT_SYMBOL(sun4i_frontend_update_formats); + +void sun4i_frontend_update_coord(struct sun4i_frontend *frontend, + struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + + /* Set height and width */ + DRM_DEBUG_DRIVER("Frontend size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_INSIZE_REG, + SUN4I_FRONTEND_INSIZE(state->src_h >> 16, + state->src_w >> 16)); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_INSIZE_REG, + SUN4I_FRONTEND_INSIZE(state->src_h >> 16, + state->src_w >> 16)); + + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_OUTSIZE_REG, + SUN4I_FRONTEND_OUTSIZE(state->crtc_h, state->crtc_w)); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_OUTSIZE_REG, + SUN4I_FRONTEND_OUTSIZE(state->crtc_h, state->crtc_w)); + + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZFACT_REG, + state->src_w / state->crtc_w); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZFACT_REG, + state->src_w / state->crtc_w); + + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTFACT_REG, + state->src_h / state->crtc_h); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTFACT_REG, + state->src_h / state->crtc_h); + + regmap_write_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, + SUN4I_FRONTEND_FRM_CTRL_REG_RDY, + SUN4I_FRONTEND_FRM_CTRL_REG_RDY); +} +EXPORT_SYMBOL(sun4i_frontend_update_coord); + +int sun4i_frontend_enable(struct sun4i_frontend *frontend) +{ + regmap_write_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, + SUN4I_FRONTEND_FRM_CTRL_FRM_START, + SUN4I_FRONTEND_FRM_CTRL_FRM_START); + + return 0; +} +EXPORT_SYMBOL(sun4i_frontend_enable); + +static struct regmap_config sun4i_frontend_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x0a14, +}; + +static int sun4i_frontend_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sun4i_frontend *frontend; + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct resource *res; + void __iomem *regs; + + frontend = devm_kzalloc(dev, sizeof(*frontend), GFP_KERNEL); + if (!frontend) + return -ENOMEM; + + dev_set_drvdata(dev, frontend); + frontend->dev = dev; + frontend->node = dev->of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + frontend->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_frontend_regmap_config); + if (IS_ERR(frontend->regs)) { + dev_err(dev, "Couldn't create the frontend regmap\n"); + return PTR_ERR(frontend->regs); + } + + frontend->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(frontend->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(frontend->reset); + } + + frontend->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(frontend->bus_clk)) { + dev_err(dev, "Couldn't get our bus clock\n"); + return PTR_ERR(frontend->bus_clk); + } + + frontend->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(frontend->mod_clk)) { + dev_err(dev, "Couldn't get our mod clock\n"); + return PTR_ERR(frontend->mod_clk); + } + + frontend->ram_clk = devm_clk_get(dev, "ram"); + if (IS_ERR(frontend->ram_clk)) { + dev_err(dev, "Couldn't get our ram clock\n"); + return PTR_ERR(frontend->ram_clk); + } + + list_add_tail(&frontend->list, &drv->frontend_list); + pm_runtime_enable(dev); + + return 0; +} + +static void sun4i_frontend_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_frontend *frontend = dev_get_drvdata(dev); + + list_del(&frontend->list); + pm_runtime_force_suspend(dev); +} + +static const struct component_ops sun4i_frontend_ops = { + .bind = sun4i_frontend_bind, + .unbind = sun4i_frontend_unbind, +}; + +static int sun4i_frontend_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_frontend_ops); +} + +static int sun4i_frontend_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_frontend_ops); + + return 0; +} + +static int sun4i_frontend_runtime_resume(struct device *dev) +{ + struct sun4i_frontend *frontend = dev_get_drvdata(dev); + int ret; + + clk_set_rate(frontend->mod_clk, 300000000); + + clk_prepare_enable(frontend->bus_clk); + clk_prepare_enable(frontend->mod_clk); + clk_prepare_enable(frontend->ram_clk); + + ret = reset_control_reset(frontend->reset); + if (ret) { + dev_err(dev, "Couldn't reset our device\n"); + return ret; + } + + regmap_update_bits(frontend->regs, SUN4I_FRONTEND_EN_REG, + SUN4I_FRONTEND_EN_EN, + SUN4I_FRONTEND_EN_EN); + + regmap_update_bits(frontend->regs, SUN4I_FRONTEND_BYPASS_REG, + SUN4I_FRONTEND_BYPASS_CSC_EN, + SUN4I_FRONTEND_BYPASS_CSC_EN); + + sun4i_frontend_scaler_init(frontend); + + return 0; +} + +static int sun4i_frontend_runtime_suspend(struct device *dev) +{ + struct sun4i_frontend *frontend = dev_get_drvdata(dev); + + clk_disable_unprepare(frontend->ram_clk); + clk_disable_unprepare(frontend->mod_clk); + clk_disable_unprepare(frontend->bus_clk); + + reset_control_assert(frontend->reset); + + return 0; +} + +static const struct dev_pm_ops sun4i_frontend_pm_ops = { + .runtime_resume = sun4i_frontend_runtime_resume, + .runtime_suspend = sun4i_frontend_runtime_suspend, +}; + +const struct of_device_id sun4i_frontend_of_table[] = { + { .compatible = "allwinner,sun8i-a33-display-frontend" }, + { } +}; +EXPORT_SYMBOL(sun4i_frontend_of_table); +MODULE_DEVICE_TABLE(of, sun4i_frontend_of_table); + +static struct platform_driver sun4i_frontend_driver = { + .probe = sun4i_frontend_probe, + .remove = sun4i_frontend_remove, + .driver = { + .name = "sun4i-frontend", + .of_match_table = sun4i_frontend_of_table, + .pm = &sun4i_frontend_pm_ops, + }, +}; +module_platform_driver(sun4i_frontend_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Display Engine Frontend Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_frontend.h b/drivers/gpu/drm/sun4i/sun4i_frontend.h new file mode 100644 index 000000000000..02661ce81f3e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_frontend.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Free Electrons + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#ifndef _SUN4I_FRONTEND_H_ +#define _SUN4I_FRONTEND_H_ + +#include <linux/list.h> + +#define SUN4I_FRONTEND_EN_REG 0x000 +#define SUN4I_FRONTEND_EN_EN BIT(0) + +#define SUN4I_FRONTEND_FRM_CTRL_REG 0x004 +#define SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL BIT(23) +#define SUN4I_FRONTEND_FRM_CTRL_FRM_START BIT(16) +#define SUN4I_FRONTEND_FRM_CTRL_COEF_RDY BIT(1) +#define SUN4I_FRONTEND_FRM_CTRL_REG_RDY BIT(0) + +#define SUN4I_FRONTEND_BYPASS_REG 0x008 +#define SUN4I_FRONTEND_BYPASS_CSC_EN BIT(1) + +#define SUN4I_FRONTEND_BUF_ADDR0_REG 0x020 + +#define SUN4I_FRONTEND_LINESTRD0_REG 0x040 + +#define SUN4I_FRONTEND_INPUT_FMT_REG 0x04c +#define SUN4I_FRONTEND_INPUT_FMT_DATA_MOD(mod) ((mod) << 8) +#define SUN4I_FRONTEND_INPUT_FMT_DATA_FMT(fmt) ((fmt) << 4) +#define SUN4I_FRONTEND_INPUT_FMT_PS(ps) (ps) + +#define SUN4I_FRONTEND_OUTPUT_FMT_REG 0x05c +#define SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT(fmt) (fmt) + +#define SUN4I_FRONTEND_CH0_INSIZE_REG 0x100 +#define SUN4I_FRONTEND_INSIZE(h, w) ((((h) - 1) << 16) | (((w) - 1))) + +#define SUN4I_FRONTEND_CH0_OUTSIZE_REG 0x104 +#define SUN4I_FRONTEND_OUTSIZE(h, w) ((((h) - 1) << 16) | (((w) - 1))) + +#define SUN4I_FRONTEND_CH0_HORZFACT_REG 0x108 +#define SUN4I_FRONTEND_HORZFACT(i, f) (((i) << 16) | (f)) + +#define SUN4I_FRONTEND_CH0_VERTFACT_REG 0x10c +#define SUN4I_FRONTEND_VERTFACT(i, f) (((i) << 16) | (f)) + +#define SUN4I_FRONTEND_CH0_HORZPHASE_REG 0x110 +#define SUN4I_FRONTEND_CH0_VERTPHASE0_REG 0x114 +#define SUN4I_FRONTEND_CH0_VERTPHASE1_REG 0x118 + +#define SUN4I_FRONTEND_CH1_INSIZE_REG 0x200 +#define SUN4I_FRONTEND_CH1_OUTSIZE_REG 0x204 +#define SUN4I_FRONTEND_CH1_HORZFACT_REG 0x208 +#define SUN4I_FRONTEND_CH1_VERTFACT_REG 0x20c + +#define SUN4I_FRONTEND_CH1_HORZPHASE_REG 0x210 +#define SUN4I_FRONTEND_CH1_VERTPHASE0_REG 0x214 +#define SUN4I_FRONTEND_CH1_VERTPHASE1_REG 0x218 + +#define SUN4I_FRONTEND_CH0_HORZCOEF0_REG(i) (0x400 + i * 4) +#define SUN4I_FRONTEND_CH0_HORZCOEF1_REG(i) (0x480 + i * 4) +#define SUN4I_FRONTEND_CH0_VERTCOEF_REG(i) (0x500 + i * 4) +#define SUN4I_FRONTEND_CH1_HORZCOEF0_REG(i) (0x600 + i * 4) +#define SUN4I_FRONTEND_CH1_HORZCOEF1_REG(i) (0x680 + i * 4) +#define SUN4I_FRONTEND_CH1_VERTCOEF_REG(i) (0x700 + i * 4) + +struct clk; +struct device_node; +struct drm_plane; +struct regmap; +struct reset_control; + +struct sun4i_frontend { + struct list_head list; + struct device *dev; + struct device_node *node; + + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *ram_clk; + struct regmap *regs; + struct reset_control *reset; +}; + +extern const struct of_device_id sun4i_frontend_of_table[]; + +int sun4i_frontend_init(struct sun4i_frontend *frontend); +void sun4i_frontend_exit(struct sun4i_frontend *frontend); +int sun4i_frontend_enable(struct sun4i_frontend *frontend); + +void sun4i_frontend_update_buffer(struct sun4i_frontend *frontend, + struct drm_plane *plane); +void sun4i_frontend_update_coord(struct sun4i_frontend *frontend, + struct drm_plane *plane); +int sun4i_frontend_update_formats(struct sun4i_frontend *frontend, + struct drm_plane *plane, uint32_t out_fmt); + +#endif /* _SUN4I_FRONTEND_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c index 7bddf12548d3..2949a3c912c1 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.c +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -15,34 +15,100 @@ #include <drm/drmP.h> #include "sun4i_backend.h" +#include "sun4i_frontend.h" #include "sun4i_layer.h" #include "sunxi_engine.h" -struct sun4i_plane_desc { - enum drm_plane_type type; - u8 pipe; - const uint32_t *formats; - uint32_t nformats; -}; +static void sun4i_backend_layer_reset(struct drm_plane *plane) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(plane); + struct sun4i_layer_state *state; + + if (plane->state) { + state = state_to_sun4i_layer_state(plane->state); + + __drm_atomic_helper_plane_destroy_state(&state->state); + + kfree(state); + plane->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + plane->state = &state->state; + plane->state->plane = plane; + plane->state->zpos = layer->id; + } +} + +static struct drm_plane_state * +sun4i_backend_layer_duplicate_state(struct drm_plane *plane) +{ + struct sun4i_layer_state *orig = state_to_sun4i_layer_state(plane->state); + struct sun4i_layer_state *copy; + + copy = kzalloc(sizeof(*copy), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->state); + copy->uses_frontend = orig->uses_frontend; + + return ©->state; +} + +static void sun4i_backend_layer_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct sun4i_layer_state *s_state = state_to_sun4i_layer_state(state); + + __drm_atomic_helper_plane_destroy_state(state); + + kfree(s_state); +} static void sun4i_backend_layer_atomic_disable(struct drm_plane *plane, struct drm_plane_state *old_state) { + struct sun4i_layer_state *layer_state = state_to_sun4i_layer_state(old_state); struct sun4i_layer *layer = plane_to_sun4i_layer(plane); struct sun4i_backend *backend = layer->backend; sun4i_backend_layer_enable(backend, layer->id, false); + + if (layer_state->uses_frontend) { + unsigned long flags; + + spin_lock_irqsave(&backend->frontend_lock, flags); + backend->frontend_teardown = true; + spin_unlock_irqrestore(&backend->frontend_lock, flags); + } } static void sun4i_backend_layer_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state) { + struct sun4i_layer_state *layer_state = state_to_sun4i_layer_state(plane->state); struct sun4i_layer *layer = plane_to_sun4i_layer(plane); struct sun4i_backend *backend = layer->backend; + struct sun4i_frontend *frontend = backend->frontend; + + if (layer_state->uses_frontend) { + sun4i_frontend_init(frontend); + sun4i_frontend_update_coord(frontend, plane); + sun4i_frontend_update_buffer(frontend, plane); + sun4i_frontend_update_formats(frontend, plane, + DRM_FORMAT_ARGB8888); + sun4i_backend_update_layer_frontend(backend, layer->id, + DRM_FORMAT_ARGB8888); + sun4i_frontend_enable(frontend); + } else { + sun4i_backend_update_layer_formats(backend, layer->id, plane); + sun4i_backend_update_layer_buffer(backend, layer->id, plane); + } sun4i_backend_update_layer_coord(backend, layer->id, plane); - sun4i_backend_update_layer_formats(backend, layer->id, plane); - sun4i_backend_update_layer_buffer(backend, layer->id, plane); + sun4i_backend_update_layer_zpos(backend, layer->id, plane); sun4i_backend_layer_enable(backend, layer->id, true); } @@ -52,22 +118,15 @@ static const struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { }; static const struct drm_plane_funcs sun4i_backend_layer_funcs = { - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = sun4i_backend_layer_destroy_state, + .atomic_duplicate_state = sun4i_backend_layer_duplicate_state, .destroy = drm_plane_cleanup, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .reset = sun4i_backend_layer_reset, .update_plane = drm_atomic_helper_update_plane, }; -static const uint32_t sun4i_backend_layer_formats_primary[] = { - DRM_FORMAT_ARGB8888, - DRM_FORMAT_RGB888, - DRM_FORMAT_RGB565, - DRM_FORMAT_XRGB8888, -}; - -static const uint32_t sun4i_backend_layer_formats_overlay[] = { +static const uint32_t sun4i_backend_layer_formats[] = { DRM_FORMAT_ARGB8888, DRM_FORMAT_ARGB4444, DRM_FORMAT_ARGB1555, @@ -75,27 +134,16 @@ static const uint32_t sun4i_backend_layer_formats_overlay[] = { DRM_FORMAT_RGBA4444, DRM_FORMAT_RGB888, DRM_FORMAT_RGB565, + DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY, DRM_FORMAT_XRGB8888, -}; - -static const struct sun4i_plane_desc sun4i_backend_planes[] = { - { - .type = DRM_PLANE_TYPE_PRIMARY, - .pipe = 0, - .formats = sun4i_backend_layer_formats_primary, - .nformats = ARRAY_SIZE(sun4i_backend_layer_formats_primary), - }, - { - .type = DRM_PLANE_TYPE_OVERLAY, - .pipe = 1, - .formats = sun4i_backend_layer_formats_overlay, - .nformats = ARRAY_SIZE(sun4i_backend_layer_formats_overlay), - }, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, }; static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, struct sun4i_backend *backend, - const struct sun4i_plane_desc *plane) + enum drm_plane_type type) { struct sun4i_layer *layer; int ret; @@ -107,8 +155,9 @@ static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, /* possible crtcs are set later */ ret = drm_universal_plane_init(drm, &layer->plane, 0, &sun4i_backend_layer_funcs, - plane->formats, plane->nformats, - NULL, plane->type, NULL); + sun4i_backend_layer_formats, + ARRAY_SIZE(sun4i_backend_layer_formats), + NULL, type, NULL); if (ret) { dev_err(drm->dev, "Couldn't initialize layer\n"); return ERR_PTR(ret); @@ -118,6 +167,9 @@ static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, &sun4i_backend_layer_helper_funcs); layer->backend = backend; + drm_plane_create_zpos_property(&layer->plane, 0, 0, + SUN4I_BACKEND_NUM_LAYERS - 1); + return layer; } @@ -128,49 +180,23 @@ struct drm_plane **sun4i_layers_init(struct drm_device *drm, struct sun4i_backend *backend = engine_to_sun4i_backend(engine); int i; - planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1, + /* We need to have a sentinel at the need, hence the overallocation */ + planes = devm_kcalloc(drm->dev, SUN4I_BACKEND_NUM_LAYERS + 1, sizeof(*planes), GFP_KERNEL); if (!planes) return ERR_PTR(-ENOMEM); - /* - * The hardware is a bit unusual here. - * - * Even though it supports 4 layers, it does the composition - * in two separate steps. - * - * The first one is assigning a layer to one of its two - * pipes. If more that 1 layer is assigned to the same pipe, - * and if pixels overlaps, the pipe will take the pixel from - * the layer with the highest priority. - * - * The second step is the actual alpha blending, that takes - * the two pipes as input, and uses the eventual alpha - * component to do the transparency between the two. - * - * This two steps scenario makes us unable to guarantee a - * robust alpha blending between the 4 layers in all - * situations. So we just expose two layers, one per pipe. On - * SoCs that support it, sprites could fill the need for more - * layers. - */ - for (i = 0; i < ARRAY_SIZE(sun4i_backend_planes); i++) { - const struct sun4i_plane_desc *plane = &sun4i_backend_planes[i]; + for (i = 0; i < SUN4I_BACKEND_NUM_LAYERS; i++) { + enum drm_plane_type type = i ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; struct sun4i_layer *layer; - layer = sun4i_layer_init_one(drm, backend, plane); + layer = sun4i_layer_init_one(drm, backend, type); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", i ? "overlay" : "primary"); return ERR_CAST(layer); }; - DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", - i ? "overlay" : "primary", plane->pipe); - regmap_update_bits(engine->regs, SUN4I_BACKEND_ATTCTL_REG0(i), - SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK, - SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(plane->pipe)); - layer->id = i; planes[i] = &layer->plane; }; diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h index 4e84f438b346..36b20265bd31 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.h +++ b/drivers/gpu/drm/sun4i/sun4i_layer.h @@ -22,12 +22,24 @@ struct sun4i_layer { int id; }; +struct sun4i_layer_state { + struct drm_plane_state state; + unsigned int pipe; + bool uses_frontend; +}; + static inline struct sun4i_layer * plane_to_sun4i_layer(struct drm_plane *plane) { return container_of(plane, struct sun4i_layer, plane); } +static inline struct sun4i_layer_state * +state_to_sun4i_layer_state(struct drm_plane_state *state) +{ + return container_of(state, struct sun4i_layer_state, state); +} + struct drm_plane **sun4i_layers_init(struct drm_device *drm, struct sunxi_engine *engine); diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c index be3f14d7746d..bffff4c9fbf5 100644 --- a/drivers/gpu/drm/sun4i/sun4i_lvds.c +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c @@ -94,9 +94,64 @@ static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) } } +static enum drm_mode_status sun4i_lvds_encoder_mode_valid(struct drm_encoder *crtc, + const struct drm_display_mode *mode) +{ + struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(crtc); + struct sun4i_tcon *tcon = lvds->tcon; + u32 hsync = mode->hsync_end - mode->hsync_start; + u32 vsync = mode->vsync_end - mode->vsync_start; + unsigned long rate = mode->clock * 1000; + long rounded_rate; + + DRM_DEBUG_DRIVER("Validating modes...\n"); + + if (hsync < 1) + return MODE_HSYNC_NARROW; + + if (hsync > 0x3ff) + return MODE_HSYNC_WIDE; + + if ((mode->hdisplay < 1) || (mode->htotal < 1)) + return MODE_H_ILLEGAL; + + if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff)) + return MODE_BAD_HVALUE; + + DRM_DEBUG_DRIVER("Horizontal parameters OK\n"); + + if (vsync < 1) + return MODE_VSYNC_NARROW; + + if (vsync > 0x3ff) + return MODE_VSYNC_WIDE; + + if ((mode->vdisplay < 1) || (mode->vtotal < 1)) + return MODE_V_ILLEGAL; + + if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff)) + return MODE_BAD_VVALUE; + + DRM_DEBUG_DRIVER("Vertical parameters OK\n"); + + tcon->dclk_min_div = 7; + tcon->dclk_max_div = 7; + rounded_rate = clk_round_rate(tcon->dclk, rate); + if (rounded_rate < rate) + return MODE_CLOCK_LOW; + + if (rounded_rate > rate) + return MODE_CLOCK_HIGH; + + DRM_DEBUG_DRIVER("Clock rate OK\n"); + + return MODE_OK; +} + static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { .disable = sun4i_lvds_encoder_disable, .enable = sun4i_lvds_encoder_enable, + .mode_valid = sun4i_lvds_encoder_mode_valid, }; static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = { diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index b8da5a50a61d..f2fa1f210509 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -52,10 +52,10 @@ static int sun4i_rgb_get_modes(struct drm_connector *connector) return drm_panel_get_modes(tcon->panel); } -static int sun4i_rgb_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) +static enum drm_mode_status sun4i_rgb_mode_valid(struct drm_encoder *crtc, + const struct drm_display_mode *mode) { - struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(crtc); struct sun4i_tcon *tcon = rgb->tcon; u32 hsync = mode->hsync_end - mode->hsync_start; u32 vsync = mode->vsync_end - mode->vsync_start; @@ -108,7 +108,6 @@ static int sun4i_rgb_mode_valid(struct drm_connector *connector, static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { .get_modes = sun4i_rgb_get_modes, - .mode_valid = sun4i_rgb_mode_valid, }; static void @@ -158,6 +157,7 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { .disable = sun4i_rgb_encoder_disable, .enable = sun4i_rgb_encoder_enable, + .mode_valid = sun4i_rgb_mode_valid, }; static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index a818ca491605..c3d92d537240 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -17,6 +17,7 @@ #include <drm/drm_encoder.h> #include <drm/drm_modes.h> #include <drm/drm_of.h> +#include <drm/drm_panel.h> #include <uapi/drm/drm_mode.h> @@ -84,6 +85,7 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, switch (channel) { case 0: + WARN_ON(!tcon->quirks->has_channel_0); regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_TCON_ENABLE, enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0); @@ -279,6 +281,8 @@ static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, u8 clk_delay; u32 reg, val = 0; + WARN_ON(!tcon->quirks->has_channel_0); + tcon->dclk_min_div = 7; tcon->dclk_max_div = 7; sun4i_tcon0_mode_set_common(tcon, mode); @@ -346,10 +350,15 @@ static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, const struct drm_display_mode *mode) { + struct drm_panel *panel = tcon->panel; + struct drm_connector *connector = panel->connector; + struct drm_display_info display_info = connector->display_info; unsigned int bp, hsync, vsync; u8 clk_delay; u32 val = 0; + WARN_ON(!tcon->quirks->has_channel_0); + tcon->dclk_min_div = 6; tcon->dclk_max_div = 127; sun4i_tcon0_mode_set_common(tcon, mode); @@ -395,12 +404,33 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, SUN4I_TCON0_BASIC3_H_SYNC(hsync)); /* Setup the polarity of the various signals */ - if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + if (mode->flags & DRM_MODE_FLAG_PHSYNC) val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; - if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + if (mode->flags & DRM_MODE_FLAG_PVSYNC) val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; + /* + * On A20 and similar SoCs, the only way to achieve Positive Edge + * (Rising Edge), is setting dclk clock phase to 2/3(240°). + * By default TCON works in Negative Edge(Falling Edge), + * this is why phase is set to 0 in that case. + * Unfortunately there's no way to logically invert dclk through + * IO_POL register. + * The only acceptable way to work, triple checked with scope, + * is using clock phase set to 0° for Negative Edge and set to 240° + * for Positive Edge. + * On A33 and similar SoCs there would be a 90° phase option, + * but it divides also dclk by 2. + * Following code is a way to avoid quirks all around TCON + * and DOTCLOCK drivers. + */ + if (display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_POSEDGE) + clk_set_phase(tcon->dclk, 240); + + if (display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE) + clk_set_phase(tcon->dclk, 0); + regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, val); @@ -546,6 +576,7 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) struct sun4i_tcon *tcon = private; struct drm_device *drm = tcon->drm; struct sun4i_crtc *scrtc = tcon->crtc; + struct sunxi_engine *engine = scrtc->engine; unsigned int status; regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); @@ -563,6 +594,9 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) SUN4I_TCON_GINT0_VBLANK_INT(1), 0); + if (engine->ops->vblank_quirk) + engine->ops->vblank_quirk(engine); + return IRQ_HANDLED; } @@ -576,10 +610,12 @@ static int sun4i_tcon_init_clocks(struct device *dev, } clk_prepare_enable(tcon->clk); - tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); - if (IS_ERR(tcon->sclk0)) { - dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); - return PTR_ERR(tcon->sclk0); + if (tcon->quirks->has_channel_0) { + tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); + if (IS_ERR(tcon->sclk0)) { + dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); + return PTR_ERR(tcon->sclk0); + } } if (tcon->quirks->has_channel_1) { @@ -845,6 +881,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, struct sunxi_engine *engine; struct device_node *remote; struct sun4i_tcon *tcon; + struct reset_control *edp_rstc; bool has_lvds_rst, has_lvds_alt, can_lvds; int ret; @@ -869,6 +906,20 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, return PTR_ERR(tcon->lcd_rst); } + if (tcon->quirks->needs_edp_reset) { + edp_rstc = devm_reset_control_get_shared(dev, "edp"); + if (IS_ERR(edp_rstc)) { + dev_err(dev, "Couldn't get edp reset line\n"); + return PTR_ERR(edp_rstc); + } + + ret = reset_control_deassert(edp_rstc); + if (ret) { + dev_err(dev, "Couldn't deassert edp reset line\n"); + return ret; + } + } + /* Make sure our TCON is reset */ ret = reset_control_reset(tcon->lcd_rst); if (ret) { @@ -940,10 +991,12 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, goto err_free_clocks; } - ret = sun4i_dclk_create(dev, tcon); - if (ret) { - dev_err(dev, "Couldn't create our TCON dot clock\n"); - goto err_free_clocks; + if (tcon->quirks->has_channel_0) { + ret = sun4i_dclk_create(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't create our TCON dot clock\n"); + goto err_free_clocks; + } } ret = sun4i_tcon_init_irq(dev, tcon); @@ -1001,7 +1054,8 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, return 0; err_free_dotclock: - sun4i_dclk_free(tcon); + if (tcon->quirks->has_channel_0) + sun4i_dclk_free(tcon); err_free_clocks: sun4i_tcon_free_clocks(tcon); err_assert_reset: @@ -1015,7 +1069,8 @@ static void sun4i_tcon_unbind(struct device *dev, struct device *master, struct sun4i_tcon *tcon = dev_get_drvdata(dev); list_del(&tcon->list); - sun4i_dclk_free(tcon); + if (tcon->quirks->has_channel_0) + sun4i_dclk_free(tcon); sun4i_tcon_free_clocks(tcon); } @@ -1112,16 +1167,19 @@ static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, } static const struct sun4i_tcon_quirks sun4i_a10_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .set_mux = sun4i_a10_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun5i_a13_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .set_mux = sun5i_a13_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun6i_a31_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .has_lvds_alt = true, .needs_de_be_mux = true, @@ -1129,26 +1187,44 @@ static const struct sun4i_tcon_quirks sun6i_a31_quirks = { }; static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { + .has_channel_0 = true, .has_channel_1 = true, .needs_de_be_mux = true, }; static const struct sun4i_tcon_quirks sun7i_a20_quirks = { + .has_channel_0 = true, .has_channel_1 = true, /* Same display pipeline structure as A10 */ .set_mux = sun4i_a10_tcon_set_mux, }; static const struct sun4i_tcon_quirks sun8i_a33_quirks = { + .has_channel_0 = true, .has_lvds_alt = true, }; static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = { .supports_lvds = true, + .has_channel_0 = true, +}; + +static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = { + .has_channel_1 = true, }; static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { - /* nothing is supported */ + .has_channel_0 = true, +}; + +static const struct sun4i_tcon_quirks sun9i_a80_tcon_lcd_quirks = { + .has_channel_0 = true, + .needs_edp_reset = true, +}; + +static const struct sun4i_tcon_quirks sun9i_a80_tcon_tv_quirks = { + .has_channel_1 = true, + .needs_edp_reset = true, }; /* sun4i_drv uses this list to check if a device node is a TCON */ @@ -1160,7 +1236,10 @@ const struct of_device_id sun4i_tcon_of_table[] = { { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, { .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks }, + { .compatible = "allwinner,sun8i-a83t-tcon-tv", .data = &sun8i_a83t_tv_quirks }, { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, + { .compatible = "allwinner,sun9i-a80-tcon-lcd", .data = &sun9i_a80_tcon_lcd_quirks }, + { .compatible = "allwinner,sun9i-a80-tcon-tv", .data = &sun9i_a80_tcon_tv_quirks }, { } }; MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 278700c7bf9f..161e09427124 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -172,9 +172,11 @@ struct sun4i_tcon; struct sun4i_tcon_quirks { + bool has_channel_0; /* a83t does not have channel 0 on second TCON */ bool has_channel_1; /* a33 does not have channel 1 */ bool has_lvds_alt; /* Does the LVDS clock have a parent other than the TCON clock? */ bool needs_de_be_mux; /* sun6i needs mux to select backend */ + bool needs_edp_reset; /* a80 edp reset needed for tcon0 access */ bool supports_lvds; /* Does the TCON support an LVDS output? */ /* callback to handle tcon muxing options */ diff --git a/drivers/gpu/drm/sun4i/sun6i_drc.c b/drivers/gpu/drm/sun4i/sun6i_drc.c index 09bba853e2a4..b5e071a49045 100644 --- a/drivers/gpu/drm/sun4i/sun6i_drc.c +++ b/drivers/gpu/drm/sun4i/sun6i_drc.c @@ -101,6 +101,7 @@ static const struct of_device_id sun6i_drc_of_table[] = { { .compatible = "allwinner,sun6i-a31-drc" }, { .compatible = "allwinner,sun6i-a31s-drc" }, { .compatible = "allwinner,sun8i-a33-drc" }, + { .compatible = "allwinner,sun9i-a80-drc" }, { } }; MODULE_DEVICE_TABLE(of, sun6i_drc_of_table); diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c new file mode 100644 index 000000000000..9f40a44b456b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <drm/drm_of.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "sun8i_dw_hdmi.h" + +static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct sun8i_dw_hdmi *hdmi = encoder_to_sun8i_dw_hdmi(encoder); + + clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000); +} + +static const struct drm_encoder_helper_funcs +sun8i_dw_hdmi_encoder_helper_funcs = { + .mode_set = sun8i_dw_hdmi_encoder_mode_set, +}; + +static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static enum drm_mode_status +sun8i_dw_hdmi_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + if (mode->clock > 297000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_hdmi_plat_data *plat_data; + struct drm_device *drm = data; + struct device_node *phy_node; + struct drm_encoder *encoder; + struct sun8i_dw_hdmi *hdmi; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + plat_data = &hdmi->plat_data; + hdmi->dev = &pdev->dev; + encoder = &hdmi->encoder; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + hdmi->rst_ctrl = devm_reset_control_get(dev, "ctrl"); + if (IS_ERR(hdmi->rst_ctrl)) { + dev_err(dev, "Could not get ctrl reset control\n"); + return PTR_ERR(hdmi->rst_ctrl); + } + + hdmi->clk_tmds = devm_clk_get(dev, "tmds"); + if (IS_ERR(hdmi->clk_tmds)) { + dev_err(dev, "Couldn't get the tmds clock\n"); + return PTR_ERR(hdmi->clk_tmds); + } + + ret = reset_control_deassert(hdmi->rst_ctrl); + if (ret) { + dev_err(dev, "Could not deassert ctrl reset control\n"); + return ret; + } + + ret = clk_prepare_enable(hdmi->clk_tmds); + if (ret) { + dev_err(dev, "Could not enable tmds clock\n"); + goto err_assert_ctrl_reset; + } + + phy_node = of_parse_phandle(dev->of_node, "phys", 0); + if (!phy_node) { + dev_err(dev, "Can't found PHY phandle\n"); + goto err_disable_clk_tmds; + } + + ret = sun8i_hdmi_phy_probe(hdmi, phy_node); + of_node_put(phy_node); + if (ret) { + dev_err(dev, "Couldn't get the HDMI PHY\n"); + goto err_disable_clk_tmds; + } + + drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs); + drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + + sun8i_hdmi_phy_init(hdmi->phy); + + plat_data->mode_valid = &sun8i_dw_hdmi_mode_valid; + plat_data->phy_ops = sun8i_hdmi_phy_get_ops(); + plat_data->phy_name = "sun8i_dw_hdmi_phy"; + plat_data->phy_data = hdmi->phy; + + platform_set_drvdata(pdev, hdmi); + + hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data); + + /* + * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(), + * which would have called the encoder cleanup. Do it manually. + */ + if (IS_ERR(hdmi->hdmi)) { + ret = PTR_ERR(hdmi->hdmi); + goto cleanup_encoder; + } + + return 0; + +cleanup_encoder: + drm_encoder_cleanup(encoder); + sun8i_hdmi_phy_remove(hdmi); +err_disable_clk_tmds: + clk_disable_unprepare(hdmi->clk_tmds); +err_assert_ctrl_reset: + reset_control_assert(hdmi->rst_ctrl); + + return ret; +} + +static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun8i_dw_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_unbind(hdmi->hdmi); + sun8i_hdmi_phy_remove(hdmi); + clk_disable_unprepare(hdmi->clk_tmds); + reset_control_assert(hdmi->rst_ctrl); +} + +static const struct component_ops sun8i_dw_hdmi_ops = { + .bind = sun8i_dw_hdmi_bind, + .unbind = sun8i_dw_hdmi_unbind, +}; + +static int sun8i_dw_hdmi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun8i_dw_hdmi_ops); +} + +static int sun8i_dw_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun8i_dw_hdmi_ops); + + return 0; +} + +static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = { + { .compatible = "allwinner,sun8i-a83t-dw-hdmi" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids); + +struct platform_driver sun8i_dw_hdmi_pltfm_driver = { + .probe = sun8i_dw_hdmi_probe, + .remove = sun8i_dw_hdmi_remove, + .driver = { + .name = "sun8i-dw-hdmi", + .of_match_table = sun8i_dw_hdmi_dt_ids, + }, +}; +module_platform_driver(sun8i_dw_hdmi_pltfm_driver); + +MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>"); +MODULE_DESCRIPTION("Allwinner DW HDMI bridge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h new file mode 100644 index 000000000000..79154f0f674a --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net> + */ + +#ifndef _SUN8I_DW_HDMI_H_ +#define _SUN8I_DW_HDMI_H_ + +#include <drm/bridge/dw_hdmi.h> +#include <drm/drm_encoder.h> +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#define SUN8I_HDMI_PHY_DBG_CTRL_REG 0x0000 +#define SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK BIT(0) +#define SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK GENMASK(15, 8) +#define SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC BIT(8) +#define SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC BIT(9) +#define SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK GENMASK(23, 16) +#define SUN8I_HDMI_PHY_DBG_CTRL_ADDR(addr) (addr << 16) + +#define SUN8I_HDMI_PHY_REXT_CTRL_REG 0x0004 +#define SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN BIT(31) + +#define SUN8I_HDMI_PHY_READ_EN_REG 0x0010 +#define SUN8I_HDMI_PHY_READ_EN_MAGIC 0x54524545 + +#define SUN8I_HDMI_PHY_UNSCRAMBLE_REG 0x0014 +#define SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC 0x42494E47 + +#define SUN8I_HDMI_PHY_ANA_CFG1_REG 0x0020 +#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SWI BIT(31) +#define SUN8I_HDMI_PHY_ANA_CFG1_REG_PWEND BIT(30) +#define SUN8I_HDMI_PHY_ANA_CFG1_REG_PWENC BIT(29) +#define SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW BIT(28) +#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SVRCAL(x) ((x) << 26) +#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SVBH(x) ((x) << 24) +#define SUN8I_HDMI_PHY_ANA_CFG1_AMP_OPT BIT(23) +#define SUN8I_HDMI_PHY_ANA_CFG1_EMP_OPT BIT(22) +#define SUN8I_HDMI_PHY_ANA_CFG1_AMPCK_OPT BIT(21) +#define SUN8I_HDMI_PHY_ANA_CFG1_EMPCK_OPT BIT(20) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL BIT(19) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG BIT(18) +#define SUN8I_HDMI_PHY_ANA_CFG1_REG_SCKTMDS BIT(17) +#define SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN BIT(16) +#define SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK GENMASK(15, 12) +#define SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL (0xf << 12) +#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK BIT(11) +#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 BIT(10) +#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 BIT(9) +#define SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 BIT(8) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK BIT(7) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2 BIT(6) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 BIT(5) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 BIT(4) +#define SUN8I_HDMI_PHY_ANA_CFG1_CKEN BIT(3) +#define SUN8I_HDMI_PHY_ANA_CFG1_LDOEN BIT(2) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENVBS BIT(1) +#define SUN8I_HDMI_PHY_ANA_CFG1_ENBI BIT(0) + +#define SUN8I_HDMI_PHY_ANA_CFG2_REG 0x0024 +#define SUN8I_HDMI_PHY_ANA_CFG2_M_EN BIT(31) +#define SUN8I_HDMI_PHY_ANA_CFG2_PLLDBEN BIT(30) +#define SUN8I_HDMI_PHY_ANA_CFG2_SEN BIT(29) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_HPDPD BIT(28) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_HPDEN BIT(27) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_PLRCK BIT(26) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_PLR(x) ((x) << 23) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_DENCK BIT(22) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_DEN BIT(21) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_CD(x) ((x) << 19) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_CKSS(x) ((x) << 17) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK BIT(16) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW BIT(15) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_CSMPS(x) ((x) << 13) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(x) ((x) << 10) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BOOSTCK(x) ((x) << 8) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_BOOST(x) ((x) << 6) +#define SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(x) ((x) << 0) + +#define SUN8I_HDMI_PHY_ANA_CFG3_REG 0x0028 +#define SUN8I_HDMI_PHY_ANA_CFG3_REG_SLOWCK(x) ((x) << 30) +#define SUN8I_HDMI_PHY_ANA_CFG3_REG_SLOW(x) ((x) << 28) +#define SUN8I_HDMI_PHY_ANA_CFG3_REG_WIRE(x) ((x) << 18) +#define SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(x) ((x) << 14) +#define SUN8I_HDMI_PHY_ANA_CFG3_REG_EMPCK(x) ((x) << 11) +#define SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(x) ((x) << 7) +#define SUN8I_HDMI_PHY_ANA_CFG3_REG_EMP(x) ((x) << 4) +#define SUN8I_HDMI_PHY_ANA_CFG3_SDAPD BIT(3) +#define SUN8I_HDMI_PHY_ANA_CFG3_SDAEN BIT(2) +#define SUN8I_HDMI_PHY_ANA_CFG3_SCLPD BIT(1) +#define SUN8I_HDMI_PHY_ANA_CFG3_SCLEN BIT(0) + +#define SUN8I_HDMI_PHY_PLL_CFG1_REG 0x002c +#define SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 BIT(31) +#define SUN8I_HDMI_PHY_PLL_CFG1_REG_OD BIT(30) +#define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN BIT(29) +#define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN BIT(28) +#define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 BIT(27) +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL BIT(26) +#define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN BIT(25) +#define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x) ((x) << 22) +#define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x) ((x) << 20) +#define SUN8I_HDMI_PHY_PLL_CFG1_PLLDBEN BIT(19) +#define SUN8I_HDMI_PHY_PLL_CFG1_CS BIT(18) +#define SUN8I_HDMI_PHY_PLL_CFG1_CP_S(x) ((x) << 13) +#define SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(x) ((x) << 7) +#define SUN8I_HDMI_PHY_PLL_CFG1_BWS BIT(6) +#define SUN8I_HDMI_PHY_PLL_CFG1_B_IN_MSK GENMASK(5, 0) +#define SUN8I_HDMI_PHY_PLL_CFG1_B_IN_SHIFT 0 + +#define SUN8I_HDMI_PHY_PLL_CFG2_REG 0x0030 +#define SUN8I_HDMI_PHY_PLL_CFG2_SV_H BIT(31) +#define SUN8I_HDMI_PHY_PLL_CFG2_PDCLKSEL(x) ((x) << 29) +#define SUN8I_HDMI_PHY_PLL_CFG2_CLKSTEP(x) ((x) << 27) +#define SUN8I_HDMI_PHY_PLL_CFG2_PSET(x) ((x) << 24) +#define SUN8I_HDMI_PHY_PLL_CFG2_PCLK_SEL BIT(23) +#define SUN8I_HDMI_PHY_PLL_CFG2_AUTOSYNC_DIS BIT(22) +#define SUN8I_HDMI_PHY_PLL_CFG2_VREG2_OUT_EN BIT(21) +#define SUN8I_HDMI_PHY_PLL_CFG2_VREG1_OUT_EN BIT(20) +#define SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN_EN BIT(19) +#define SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN(x) ((x) << 16) +#define SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(x) ((x) << 12) +#define SUN8I_HDMI_PHY_PLL_CFG2_VCO_RST_IN BIT(11) +#define SUN8I_HDMI_PHY_PLL_CFG2_SINT_FRAC BIT(10) +#define SUN8I_HDMI_PHY_PLL_CFG2_SDIV2 BIT(9) +#define SUN8I_HDMI_PHY_PLL_CFG2_S(x) ((x) << 6) +#define SUN8I_HDMI_PHY_PLL_CFG2_S6P25_7P5 BIT(5) +#define SUN8I_HDMI_PHY_PLL_CFG2_S5_7 BIT(4) +#define SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK GENMASK(3, 0) +#define SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_SHIFT 0 +#define SUN8I_HDMI_PHY_PLL_CFG2_PREDIV(x) (((x) - 1) << 0) + +#define SUN8I_HDMI_PHY_PLL_CFG3_REG 0x0034 +#define SUN8I_HDMI_PHY_PLL_CFG3_SOUT_DIV2 BIT(0) + +#define SUN8I_HDMI_PHY_ANA_STS_REG 0x0038 +#define SUN8I_HDMI_PHY_ANA_STS_B_OUT_SHIFT 11 +#define SUN8I_HDMI_PHY_ANA_STS_B_OUT_MSK GENMASK(16, 11) +#define SUN8I_HDMI_PHY_ANA_STS_RCALEND2D BIT(7) +#define SUN8I_HDMI_PHY_ANA_STS_RCAL_MASK GENMASK(5, 0) + +#define SUN8I_HDMI_PHY_CEC_REG 0x003c + +struct sun8i_hdmi_phy; + +struct sun8i_hdmi_phy_variant { + bool has_phy_clk; + void (*phy_init)(struct sun8i_hdmi_phy *phy); + void (*phy_disable)(struct dw_hdmi *hdmi, + struct sun8i_hdmi_phy *phy); + int (*phy_config)(struct dw_hdmi *hdmi, + struct sun8i_hdmi_phy *phy, + unsigned int clk_rate); +}; + +struct sun8i_hdmi_phy { + struct clk *clk_bus; + struct clk *clk_mod; + struct clk *clk_phy; + struct clk *clk_pll0; + unsigned int rcal; + struct regmap *regs; + struct reset_control *rst_phy; + struct sun8i_hdmi_phy_variant *variant; +}; + +struct sun8i_dw_hdmi { + struct clk *clk_tmds; + struct device *dev; + struct dw_hdmi *hdmi; + struct drm_encoder encoder; + struct sun8i_hdmi_phy *phy; + struct dw_hdmi_plat_data plat_data; + struct reset_control *rst_ctrl; +}; + +static inline struct sun8i_dw_hdmi * +encoder_to_sun8i_dw_hdmi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun8i_dw_hdmi, encoder); +} + +int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node); +void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi); + +void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy); +const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void); + +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev); + +#endif /* _SUN8I_DW_HDMI_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c new file mode 100644 index 000000000000..5a52fc489a9d --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> + */ + +#include <linux/delay.h> +#include <linux/of_address.h> + +#include "sun8i_dw_hdmi.h" + +/* + * Address can be actually any value. Here is set to same value as + * it is set in BSP driver. + */ +#define I2C_ADDR 0x69 + +static int sun8i_hdmi_phy_config_a83t(struct dw_hdmi *hdmi, + struct sun8i_hdmi_phy *phy, + unsigned int clk_rate) +{ + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, + SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, + SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN); + + /* power down */ + dw_hdmi_phy_gen2_txpwron(hdmi, 0); + dw_hdmi_phy_gen2_pddq(hdmi, 1); + + dw_hdmi_phy_reset(hdmi); + + dw_hdmi_phy_gen2_pddq(hdmi, 0); + + dw_hdmi_phy_i2c_set_addr(hdmi, I2C_ADDR); + + /* + * Values are taken from BSP HDMI driver. Although AW didn't + * release any documentation, explanation of this values can + * be found in i.MX 6Dual/6Quad Reference Manual. + */ + if (clk_rate <= 27000000) { + dw_hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06); + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); + dw_hdmi_phy_i2c_write(hdmi, 0x08da, 0x10); + dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19); + dw_hdmi_phy_i2c_write(hdmi, 0x0318, 0x0e); + dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09); + } else if (clk_rate <= 74250000) { + dw_hdmi_phy_i2c_write(hdmi, 0x0540, 0x06); + dw_hdmi_phy_i2c_write(hdmi, 0x0005, 0x15); + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10); + dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19); + dw_hdmi_phy_i2c_write(hdmi, 0x02b5, 0x0e); + dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09); + } else if (clk_rate <= 148500000) { + dw_hdmi_phy_i2c_write(hdmi, 0x04a0, 0x06); + dw_hdmi_phy_i2c_write(hdmi, 0x000a, 0x15); + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10); + dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19); + dw_hdmi_phy_i2c_write(hdmi, 0x0021, 0x0e); + dw_hdmi_phy_i2c_write(hdmi, 0x8029, 0x09); + } else { + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x06); + dw_hdmi_phy_i2c_write(hdmi, 0x000f, 0x15); + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10); + dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19); + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x0e); + dw_hdmi_phy_i2c_write(hdmi, 0x802b, 0x09); + } + + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x1e); + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); + dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x17); + + dw_hdmi_phy_gen2_txpwron(hdmi, 1); + + return 0; +} + +static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi, + struct sun8i_hdmi_phy *phy, + unsigned int clk_rate) +{ + u32 pll_cfg1_init; + u32 pll_cfg2_init; + u32 ana_cfg1_end; + u32 ana_cfg2_init; + u32 ana_cfg3_init; + u32 b_offset = 0; + u32 val; + + /* bandwidth / frequency independent settings */ + + pll_cfg1_init = SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN | + SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN | + SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(7) | + SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(1) | + SUN8I_HDMI_PHY_PLL_CFG1_PLLDBEN | + SUN8I_HDMI_PHY_PLL_CFG1_CS | + SUN8I_HDMI_PHY_PLL_CFG1_CP_S(2) | + SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63) | + SUN8I_HDMI_PHY_PLL_CFG1_BWS; + + pll_cfg2_init = SUN8I_HDMI_PHY_PLL_CFG2_SV_H | + SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN_EN | + SUN8I_HDMI_PHY_PLL_CFG2_SDIV2; + + ana_cfg1_end = SUN8I_HDMI_PHY_ANA_CFG1_REG_SVBH(1) | + SUN8I_HDMI_PHY_ANA_CFG1_AMP_OPT | + SUN8I_HDMI_PHY_ANA_CFG1_EMP_OPT | + SUN8I_HDMI_PHY_ANA_CFG1_AMPCK_OPT | + SUN8I_HDMI_PHY_ANA_CFG1_EMPCK_OPT | + SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL | + SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG | + SUN8I_HDMI_PHY_ANA_CFG1_REG_SCKTMDS | + SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN | + SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK | + SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 | + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2 | + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 | + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 | + SUN8I_HDMI_PHY_ANA_CFG1_CKEN | + SUN8I_HDMI_PHY_ANA_CFG1_LDOEN | + SUN8I_HDMI_PHY_ANA_CFG1_ENVBS | + SUN8I_HDMI_PHY_ANA_CFG1_ENBI; + + ana_cfg2_init = SUN8I_HDMI_PHY_ANA_CFG2_M_EN | + SUN8I_HDMI_PHY_ANA_CFG2_REG_DENCK | + SUN8I_HDMI_PHY_ANA_CFG2_REG_DEN | + SUN8I_HDMI_PHY_ANA_CFG2_REG_CKSS(1) | + SUN8I_HDMI_PHY_ANA_CFG2_REG_CSMPS(1); + + ana_cfg3_init = SUN8I_HDMI_PHY_ANA_CFG3_REG_WIRE(0x3e0) | + SUN8I_HDMI_PHY_ANA_CFG3_SDAEN | + SUN8I_HDMI_PHY_ANA_CFG3_SCLEN; + + /* bandwidth / frequency dependent settings */ + if (clk_rate <= 27000000) { + pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 | + SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32); + pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) | + SUN8I_HDMI_PHY_PLL_CFG2_S(4); + ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW; + ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) | + SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal); + ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(3) | + SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(5); + } else if (clk_rate <= 74250000) { + pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 | + SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32); + pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) | + SUN8I_HDMI_PHY_PLL_CFG2_S(5); + ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW; + ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) | + SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal); + ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(5) | + SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(7); + } else if (clk_rate <= 148500000) { + pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 | + SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32); + pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) | + SUN8I_HDMI_PHY_PLL_CFG2_S(6); + ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK | + SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW | + SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(2); + ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(7) | + SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(9); + } else { + b_offset = 2; + pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63); + pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(6) | + SUN8I_HDMI_PHY_PLL_CFG2_S(7); + ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK | + SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW | + SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4); + ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(9) | + SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(13); + } + + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0); + + regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, + (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK, + pll_cfg2_init); + usleep_range(10000, 15000); + regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG3_REG, + SUN8I_HDMI_PHY_PLL_CFG3_SOUT_DIV2); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, + SUN8I_HDMI_PHY_PLL_CFG1_PLLEN, + SUN8I_HDMI_PHY_PLL_CFG1_PLLEN); + msleep(100); + + /* get B value */ + regmap_read(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, &val); + val = (val & SUN8I_HDMI_PHY_ANA_STS_B_OUT_MSK) >> + SUN8I_HDMI_PHY_ANA_STS_B_OUT_SHIFT; + val = min(val + b_offset, (u32)0x3f); + + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, + SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 | + SUN8I_HDMI_PHY_PLL_CFG1_REG_OD, + SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 | + SUN8I_HDMI_PHY_PLL_CFG1_REG_OD); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, + SUN8I_HDMI_PHY_PLL_CFG1_B_IN_MSK, + val << SUN8I_HDMI_PHY_PLL_CFG1_B_IN_SHIFT); + msleep(100); + regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, ana_cfg1_end); + regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG2_REG, ana_cfg2_init); + regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG3_REG, ana_cfg3_init); + + return 0; +} + +static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data, + struct drm_display_mode *mode) +{ + struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data; + u32 val = 0; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC; + + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, + SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val); + + if (phy->variant->has_phy_clk) + clk_set_rate(phy->clk_phy, mode->crtc_clock * 1000); + + return phy->variant->phy_config(hdmi, phy, mode->crtc_clock * 1000); +}; + +static void sun8i_hdmi_phy_disable_a83t(struct dw_hdmi *hdmi, + struct sun8i_hdmi_phy *phy) +{ + dw_hdmi_phy_gen2_txpwron(hdmi, 0); + dw_hdmi_phy_gen2_pddq(hdmi, 1); + + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG, + SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0); +} + +static void sun8i_hdmi_phy_disable_h3(struct dw_hdmi *hdmi, + struct sun8i_hdmi_phy *phy) +{ + regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_LDOEN | + SUN8I_HDMI_PHY_ANA_CFG1_ENVBS | + SUN8I_HDMI_PHY_ANA_CFG1_ENBI); + regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0); +} + +static void sun8i_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) +{ + struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data; + + phy->variant->phy_disable(hdmi, phy); +} + +static const struct dw_hdmi_phy_ops sun8i_hdmi_phy_ops = { + .init = &sun8i_hdmi_phy_config, + .disable = &sun8i_hdmi_phy_disable, + .read_hpd = &dw_hdmi_phy_read_hpd, + .update_hpd = &dw_hdmi_phy_update_hpd, + .setup_hpd = &dw_hdmi_phy_setup_hpd, +}; + +static void sun8i_hdmi_phy_init_a83t(struct sun8i_hdmi_phy *phy) +{ + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, + SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK, + SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK); + + /* + * Set PHY I2C address. It must match to the address set by + * dw_hdmi_phy_set_slave_addr(). + */ + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG, + SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK, + SUN8I_HDMI_PHY_DBG_CTRL_ADDR(I2C_ADDR)); +} + +static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy) +{ + unsigned int val; + + regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, 0); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_ENBI, + SUN8I_HDMI_PHY_ANA_CFG1_ENBI); + udelay(5); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN, + SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_ENVBS, + SUN8I_HDMI_PHY_ANA_CFG1_ENVBS); + usleep_range(10, 20); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_LDOEN, + SUN8I_HDMI_PHY_ANA_CFG1_LDOEN); + udelay(5); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_CKEN, + SUN8I_HDMI_PHY_ANA_CFG1_CKEN); + usleep_range(40, 100); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL, + SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL); + usleep_range(100, 200); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG, + SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 | + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 | + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2, + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 | + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 | + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2); + + /* wait for calibration to finish */ + regmap_read_poll_timeout(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, val, + (val & SUN8I_HDMI_PHY_ANA_STS_RCALEND2D), + 100, 2000); + + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK, + SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDSCLK); + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK, + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 | + SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK); + + /* enable DDC communication */ + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG3_REG, + SUN8I_HDMI_PHY_ANA_CFG3_SCLEN | + SUN8I_HDMI_PHY_ANA_CFG3_SDAEN, + SUN8I_HDMI_PHY_ANA_CFG3_SCLEN | + SUN8I_HDMI_PHY_ANA_CFG3_SDAEN); + + /* set HW control of CEC pins */ + regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG, 0); + + /* read calibration data */ + regmap_read(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, &val); + phy->rcal = (val & SUN8I_HDMI_PHY_ANA_STS_RCAL_MASK) >> 2; +} + +void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy) +{ + /* enable read access to HDMI controller */ + regmap_write(phy->regs, SUN8I_HDMI_PHY_READ_EN_REG, + SUN8I_HDMI_PHY_READ_EN_MAGIC); + + /* unscramble register offsets */ + regmap_write(phy->regs, SUN8I_HDMI_PHY_UNSCRAMBLE_REG, + SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC); + + phy->variant->phy_init(phy); +} + +const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void) +{ + return &sun8i_hdmi_phy_ops; +} + +static struct regmap_config sun8i_hdmi_phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN8I_HDMI_PHY_CEC_REG, + .name = "phy" +}; + +static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = { + .phy_init = &sun8i_hdmi_phy_init_a83t, + .phy_disable = &sun8i_hdmi_phy_disable_a83t, + .phy_config = &sun8i_hdmi_phy_config_a83t, +}; + +static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = { + .has_phy_clk = true, + .phy_init = &sun8i_hdmi_phy_init_h3, + .phy_disable = &sun8i_hdmi_phy_disable_h3, + .phy_config = &sun8i_hdmi_phy_config_h3, +}; + +static const struct of_device_id sun8i_hdmi_phy_of_table[] = { + { + .compatible = "allwinner,sun8i-a83t-hdmi-phy", + .data = &sun8i_a83t_hdmi_phy, + }, + { + .compatible = "allwinner,sun8i-h3-hdmi-phy", + .data = &sun8i_h3_hdmi_phy, + }, + { /* sentinel */ } +}; + +int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node) +{ + const struct of_device_id *match; + struct device *dev = hdmi->dev; + struct sun8i_hdmi_phy *phy; + struct resource res; + void __iomem *regs; + int ret; + + match = of_match_node(sun8i_hdmi_phy_of_table, node); + if (!match) { + dev_err(dev, "Incompatible HDMI PHY\n"); + return -EINVAL; + } + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->variant = (struct sun8i_hdmi_phy_variant *)match->data; + + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(dev, "phy: Couldn't get our resources\n"); + return ret; + } + + regs = devm_ioremap_resource(dev, &res); + if (IS_ERR(regs)) { + dev_err(dev, "Couldn't map the HDMI PHY registers\n"); + return PTR_ERR(regs); + } + + phy->regs = devm_regmap_init_mmio(dev, regs, + &sun8i_hdmi_phy_regmap_config); + if (IS_ERR(phy->regs)) { + dev_err(dev, "Couldn't create the HDMI PHY regmap\n"); + return PTR_ERR(phy->regs); + } + + phy->clk_bus = of_clk_get_by_name(node, "bus"); + if (IS_ERR(phy->clk_bus)) { + dev_err(dev, "Could not get bus clock\n"); + return PTR_ERR(phy->clk_bus); + } + + phy->clk_mod = of_clk_get_by_name(node, "mod"); + if (IS_ERR(phy->clk_mod)) { + dev_err(dev, "Could not get mod clock\n"); + ret = PTR_ERR(phy->clk_mod); + goto err_put_clk_bus; + } + + if (phy->variant->has_phy_clk) { + phy->clk_pll0 = of_clk_get_by_name(node, "pll-0"); + if (IS_ERR(phy->clk_pll0)) { + dev_err(dev, "Could not get pll-0 clock\n"); + ret = PTR_ERR(phy->clk_pll0); + goto err_put_clk_mod; + } + + ret = sun8i_phy_clk_create(phy, dev); + if (ret) { + dev_err(dev, "Couldn't create the PHY clock\n"); + goto err_put_clk_pll0; + } + } + + phy->rst_phy = of_reset_control_get_shared(node, "phy"); + if (IS_ERR(phy->rst_phy)) { + dev_err(dev, "Could not get phy reset control\n"); + ret = PTR_ERR(phy->rst_phy); + goto err_put_clk_pll0; + } + + ret = reset_control_deassert(phy->rst_phy); + if (ret) { + dev_err(dev, "Cannot deassert phy reset control: %d\n", ret); + goto err_put_rst_phy; + } + + ret = clk_prepare_enable(phy->clk_bus); + if (ret) { + dev_err(dev, "Cannot enable bus clock: %d\n", ret); + goto err_deassert_rst_phy; + } + + ret = clk_prepare_enable(phy->clk_mod); + if (ret) { + dev_err(dev, "Cannot enable mod clock: %d\n", ret); + goto err_disable_clk_bus; + } + + hdmi->phy = phy; + + return 0; + +err_disable_clk_bus: + clk_disable_unprepare(phy->clk_bus); +err_deassert_rst_phy: + reset_control_assert(phy->rst_phy); +err_put_rst_phy: + reset_control_put(phy->rst_phy); +err_put_clk_pll0: + if (phy->variant->has_phy_clk) + clk_put(phy->clk_pll0); +err_put_clk_mod: + clk_put(phy->clk_mod); +err_put_clk_bus: + clk_put(phy->clk_bus); + + return ret; +} + +void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi) +{ + struct sun8i_hdmi_phy *phy = hdmi->phy; + + clk_disable_unprepare(phy->clk_mod); + clk_disable_unprepare(phy->clk_bus); + + reset_control_assert(phy->rst_phy); + + reset_control_put(phy->rst_phy); + + if (phy->variant->has_phy_clk) + clk_put(phy->clk_pll0); + clk_put(phy->clk_mod); + clk_put(phy->clk_bus); +} diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c new file mode 100644 index 000000000000..faea449812f8 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net> + */ + +#include <linux/clk-provider.h> + +#include "sun8i_dw_hdmi.h" + +struct sun8i_phy_clk { + struct clk_hw hw; + struct sun8i_hdmi_phy *phy; +}; + +static inline struct sun8i_phy_clk *hw_to_phy_clk(struct clk_hw *hw) +{ + return container_of(hw, struct sun8i_phy_clk, hw); +} + +static int sun8i_phy_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned long rate = req->rate; + unsigned long best_rate = 0; + struct clk_hw *parent; + int best_div = 1; + int i; + + parent = clk_hw_get_parent(hw); + + for (i = 1; i <= 16; i++) { + unsigned long ideal = rate * i; + unsigned long rounded; + + rounded = clk_hw_round_rate(parent, ideal); + + if (rounded == ideal) { + best_rate = rounded; + best_div = i; + break; + } + + if (!best_rate || + abs(rate - rounded / i) < + abs(rate - best_rate / best_div)) { + best_rate = rounded; + best_div = i; + } + } + + req->rate = best_rate / best_div; + req->best_parent_rate = best_rate; + req->best_parent_hw = parent; + + return 0; +} + +static unsigned long sun8i_phy_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw); + u32 reg; + + regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, ®); + reg = ((reg >> SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_SHIFT) & + SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK) + 1; + + return parent_rate / reg; +} + +static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw); + unsigned long best_rate = 0; + u8 best_m = 0, m; + + for (m = 1; m <= 16; m++) { + unsigned long tmp_rate = parent_rate / m; + + if (tmp_rate > rate) + continue; + + if (!best_rate || + (rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_m = m; + } + } + + regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, + SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK, + SUN8I_HDMI_PHY_PLL_CFG2_PREDIV(best_m)); + + return 0; +} + +static const struct clk_ops sun8i_phy_clk_ops = { + .determine_rate = sun8i_phy_clk_determine_rate, + .recalc_rate = sun8i_phy_clk_recalc_rate, + .set_rate = sun8i_phy_clk_set_rate, +}; + +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev) +{ + struct clk_init_data init; + struct sun8i_phy_clk *priv; + const char *parents[1]; + + parents[0] = __clk_get_name(phy->clk_pll0); + if (!parents[0]) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + init.name = "hdmi-phy-clk"; + init.ops = &sun8i_phy_clk_ops; + init.parent_names = parents; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + priv->phy = phy; + priv->hw.init = &init; + + phy->clk_phy = devm_clk_register(dev, &priv->hw); + if (IS_ERR(phy->clk_phy)) + return PTR_ERR(phy->clk_phy); + + return 0; +} diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 2cbb2de6d39c..126899d6f0d3 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -485,6 +485,21 @@ static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = { .vi_num = 1, }; +static const struct sun8i_mixer_cfg sun8i_a83t_mixer1_cfg = { + .ccsc = 1, + .scaler_mask = 0x3, + .ui_num = 1, + .vi_num = 1, +}; + +static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = { + .ccsc = 0, + .mod_rate = 432000000, + .scaler_mask = 0xf, + .ui_num = 3, + .vi_num = 1, +}; + static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { .vi_num = 2, .ui_num = 1, @@ -499,6 +514,14 @@ static const struct of_device_id sun8i_mixer_of_table[] = { .data = &sun8i_a83t_mixer0_cfg, }, { + .compatible = "allwinner,sun8i-a83t-de2-mixer-1", + .data = &sun8i_a83t_mixer1_cfg, + }, + { + .compatible = "allwinner,sun8i-h3-de2-mixer-0", + .data = &sun8i_h3_mixer0_cfg, + }, + { .compatible = "allwinner,sun8i-v3s-de2-mixer", .data = &sun8i_v3s_mixer_cfg, }, diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 28d7c48d50fe..9a540330cb79 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -211,7 +211,6 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, struct drm_crtc *crtc = state->crtc; struct drm_crtc_state *crtc_state; int min_scale, max_scale; - struct drm_rect clip; if (!crtc) return 0; @@ -220,11 +219,6 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, if (WARN_ON(!crtc_state)) return -EINVAL; - clip.x1 = 0; - clip.y1 = 0; - clip.x2 = crtc_state->adjusted_mode.hdisplay; - clip.y2 = crtc_state->adjusted_mode.vdisplay; - min_scale = DRM_PLANE_HELPER_NO_SCALING; max_scale = DRM_PLANE_HELPER_NO_SCALING; @@ -233,7 +227,7 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, max_scale = SUN8I_UI_SCALER_SCALE_MAX; } - return drm_atomic_helper_check_plane_state(state, crtc_state, &clip, + return drm_atomic_helper_check_plane_state(state, crtc_state, min_scale, max_scale, true, true); } diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 40c3b303068a..5877f8ef5895 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -239,7 +239,6 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, struct drm_crtc *crtc = state->crtc; struct drm_crtc_state *crtc_state; int min_scale, max_scale; - struct drm_rect clip; if (!crtc) return 0; @@ -248,11 +247,6 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, if (WARN_ON(!crtc_state)) return -EINVAL; - clip.x1 = 0; - clip.y1 = 0; - clip.x2 = crtc_state->adjusted_mode.hdisplay; - clip.y2 = crtc_state->adjusted_mode.vdisplay; - min_scale = DRM_PLANE_HELPER_NO_SCALING; max_scale = DRM_PLANE_HELPER_NO_SCALING; @@ -261,7 +255,7 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, max_scale = SUN8I_VI_SCALER_SCALE_MAX; } - return drm_atomic_helper_check_plane_state(state, crtc_state, &clip, + return drm_atomic_helper_check_plane_state(state, crtc_state, min_scale, max_scale, true, true); } diff --git a/drivers/gpu/drm/sun4i/sunxi_engine.h b/drivers/gpu/drm/sun4i/sunxi_engine.h index 4cb70ae65c79..d317ea04b8aa 100644 --- a/drivers/gpu/drm/sun4i/sunxi_engine.h +++ b/drivers/gpu/drm/sun4i/sunxi_engine.h @@ -12,16 +12,106 @@ struct drm_plane; struct drm_device; +struct drm_crtc_state; struct sunxi_engine; +/** + * struct sunxi_engine_ops - helper operations for sunXi engines + * + * These hooks are used by the common part of the DRM driver to + * implement the proper behaviour. + */ struct sunxi_engine_ops { + /** + * @atomic_begin: + * + * This callback allows to prepare our engine for an atomic + * update. This is mirroring the + * &drm_crtc_helper_funcs.atomic_begin callback, so any + * documentation there applies. + * + * This function is optional. + */ + void (*atomic_begin)(struct sunxi_engine *engine, + struct drm_crtc_state *old_state); + + /** + * @atomic_check: + * + * This callback allows to validate plane-update related CRTC + * constraints specific to engines. This is mirroring the + * &drm_crtc_helper_funcs.atomic_check callback, so any + * documentation there applies. + * + * This function is optional. + * + * RETURNS: + * + * 0 on success or a negative error code. + */ + int (*atomic_check)(struct sunxi_engine *engine, + struct drm_crtc_state *state); + + /** + * @commit: + * + * This callback will trigger the hardware switch to commit + * the new configuration that has been setup during the next + * vblank period. + * + * This function is optional. + */ void (*commit)(struct sunxi_engine *engine); + + /** + * @layers_init: + * + * This callback is used to allocate, initialize and register + * the layers supported by that engine. + * + * This function is mandatory. + * + * RETURNS: + * + * The array of struct drm_plane backing the layers, or an + * error pointer on failure. + */ struct drm_plane **(*layers_init)(struct drm_device *drm, struct sunxi_engine *engine); + /** + * @apply_color_correction: + * + * This callback will enable the color correction in the + * engine. This is useful only for the composite output. + * + * This function is optional. + */ void (*apply_color_correction)(struct sunxi_engine *engine); + + /** + * @disable_color_correction: + * + * This callback will stop the color correction in the + * engine. This is useful only for the composite output. + * + * This function is optional. + */ void (*disable_color_correction)(struct sunxi_engine *engine); + + /** + * @vblank_quirk: + * + * This callback is used to implement engine-specific + * behaviour part of the VBLANK event. It is run with all the + * constraints of an interrupt (can't sleep, all local + * interrupts disabled) and therefore should be as fast as + * possible. + * + * This function is optional. + */ + void (*vblank_quirk)(struct sunxi_engine *engine); }; /** |