diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/gpu/drm/sun4i | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/sun4i')
51 files changed, 13984 insertions, 0 deletions
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig new file mode 100644 index 000000000..c2c042287 --- /dev/null +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -0,0 +1,78 @@ +config DRM_SUN4I + tristate "DRM Support for Allwinner A10 Display Engine" + depends on DRM && (ARM || ARM64) && COMMON_CLK + depends on ARCH_SUNXI || COMPILE_TEST + select DRM_GEM_CMA_HELPER + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_PANEL + select REGMAP_MMIO + select VIDEOMODE_HELPERS + help + Choose this option if you have an Allwinner SoC with a + Display Engine. If M is selected the module will be called + sun4i-drm. + +if DRM_SUN4I + +config DRM_SUN4I_HDMI + tristate "Allwinner A10 HDMI Controller Support" + default DRM_SUN4I + help + Choose this option if you have an Allwinner SoC with an HDMI + controller. + +config DRM_SUN4I_HDMI_CEC + bool "Allwinner A10 HDMI CEC Support" + depends on DRM_SUN4I_HDMI + select CEC_CORE + select CEC_PIN + help + Choose this option if you have an Allwinner SoC with an HDMI + controller and want to use CEC. + +config DRM_SUN4I_BACKEND + tristate "Support for Allwinner A10 Display Engine Backend" + default DRM_SUN4I + help + Choose this option if you have an Allwinner SoC with the + original Allwinner Display Engine, which has a backend to + do some alpha blending and feed graphics to TCON. If M is + selected the module will be called sun4i-backend. + +config DRM_SUN6I_DSI + tristate "Allwinner A31 MIPI-DSI Controller Support" + default MACH_SUN8I + select CRC_CCITT + select DRM_MIPI_DSI + help + Choose this option if you want have an Allwinner SoC with + MIPI-DSI support. If M is selected the module will be called + sun6i-dsi + +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 + help + Choose this option if you have an Allwinner SoC with the + Allwinner Display Engine 2.0, which has a mixer to do some + graphics mixture and feed graphics to TCON, If M is + selected the module will be called sun8i-mixer. + +config DRM_SUN8I_TCON_TOP + tristate + default DRM_SUN4I if DRM_SUN8I_MIXER!=n + help + TCON TOP is responsible for configuring display pipeline for + HTMI, TVE and LCD. + +endif diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile new file mode 100644 index 000000000..0eb38ac8e --- /dev/null +++ b/drivers/gpu/drm/sun4i/Makefile @@ -0,0 +1,43 @@ +# 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 + +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o +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 + +sun4i-tcon-y += sun4i_crtc.o +sun4i-tcon-y += sun4i_dotclock.o +sun4i-tcon-y += sun4i_lvds.o +sun4i-tcon-y += sun4i_tcon.o +sun4i-tcon-y += sun4i_rgb.o + +sun6i-dsi-y += sun6i_mipi_dphy.o +sun6i-dsi-y += sun6i_mipi_dsi.o + +obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o +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 +ifdef CONFIG_DRM_SUN4I_BACKEND +obj-$(CONFIG_DRM_SUN4I) += sun4i-frontend.o +endif +obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o +obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i-dsi.o +obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o +obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o +obj-$(CONFIG_DRM_SUN8I_TCON_TOP) += sun8i_tcon_top.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c new file mode 100644 index 000000000..e30b1f5b9 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -0,0 +1,988 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#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> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> + +#include <linux/component.h> +#include <linux/list.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/reset.h> + +#include "sun4i_backend.h" +#include "sun4i_drv.h" +#include "sun4i_frontend.h" +#include "sun4i_layer.h" +#include "sunxi_engine.h" + +struct sun4i_backend_quirks { + /* backend <-> TCON muxing selection done in backend */ + bool needs_output_muxing; +}; + +static const u32 sunxi_rgb2yuv_coef[12] = { + 0x00000107, 0x00000204, 0x00000064, 0x00000108, + 0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808, + 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 void sun4i_backend_apply_color_correction(struct sunxi_engine *engine) +{ + int i; + + DRM_DEBUG_DRIVER("Applying RGB to YUV color correction\n"); + + /* Set color correction */ + regmap_write(engine->regs, SUN4I_BACKEND_OCCTL_REG, + SUN4I_BACKEND_OCCTL_ENABLE); + + for (i = 0; i < 12; i++) + regmap_write(engine->regs, SUN4I_BACKEND_OCRCOEF_REG(i), + sunxi_rgb2yuv_coef[i]); +} + +static void sun4i_backend_disable_color_correction(struct sunxi_engine *engine) +{ + DRM_DEBUG_DRIVER("Disabling color correction\n"); + + /* Disable color correction */ + regmap_update_bits(engine->regs, SUN4I_BACKEND_OCCTL_REG, + SUN4I_BACKEND_OCCTL_ENABLE, 0); +} + +static void sun4i_backend_commit(struct sunxi_engine *engine) +{ + DRM_DEBUG_DRIVER("Committing changes\n"); + + regmap_write(engine->regs, SUN4I_BACKEND_REGBUFFCTL_REG, + SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS | + SUN4I_BACKEND_REGBUFFCTL_LOADCTL); +} + +void sun4i_backend_layer_enable(struct sun4i_backend *backend, + int layer, bool enable) +{ + u32 val; + + DRM_DEBUG_DRIVER("%sabling layer %d\n", enable ? "En" : "Dis", + layer); + + if (enable) + val = SUN4I_BACKEND_MODCTL_LAY_EN(layer); + else + val = 0; + + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_LAY_EN(layer), val); +} + +static int sun4i_backend_drm_format_to_layer(u32 format, u32 *mode) +{ + switch (format) { + case DRM_FORMAT_ARGB8888: + *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB8888; + break; + + case DRM_FORMAT_ARGB4444: + *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB4444; + break; + + case DRM_FORMAT_ARGB1555: + *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB1555; + break; + + case DRM_FORMAT_RGBA5551: + *mode = SUN4I_BACKEND_LAY_FBFMT_RGBA5551; + break; + + case DRM_FORMAT_RGBA4444: + *mode = SUN4I_BACKEND_LAY_FBFMT_RGBA4444; + break; + + case DRM_FORMAT_XRGB8888: + *mode = SUN4I_BACKEND_LAY_FBFMT_XRGB8888; + break; + + case DRM_FORMAT_RGB888: + *mode = SUN4I_BACKEND_LAY_FBFMT_RGB888; + break; + + case DRM_FORMAT_RGB565: + *mode = SUN4I_BACKEND_LAY_FBFMT_RGB565; + break; + + default: + return -EINVAL; + } + + return 0; +} + +int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + + DRM_DEBUG_DRIVER("Updating layer %d\n", layer); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(backend->engine.regs, SUN4I_BACKEND_DISSIZE_REG, + SUN4I_BACKEND_DISSIZE(state->crtc_w, + state->crtc_h)); + } + + /* Set height and width */ + DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYSIZE_REG(layer), + SUN4I_BACKEND_LAYSIZE(state->crtc_w, + state->crtc_h)); + + /* Set base coordinates */ + DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", + state->crtc_x, state->crtc_y); + regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYCOOR_REG(layer), + SUN4I_BACKEND_LAYCOOR(state->crtc_x, + state->crtc_y)); + + 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) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + bool interlaced = false; + 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; + + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_ITLMOD_EN, + interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0); + + DRM_DEBUG_DRIVER("Switching display backend interlaced mode %s\n", + interlaced ? "on" : "off"); + + val = SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA(state->alpha >> 8); + if (state->alpha != DRM_BLEND_ALPHA_OPAQUE) + val |= SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA_EN; + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_ATTCTL_REG0(layer), + SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA_MASK | + SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA_EN, + val); + + if (fb->format->is_yuv) + 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; + } + + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_ATTCTL_REG1(layer), + SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); + + 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) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + 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); + + /* + * backend DMA accesses DRAM directly, bypassing the system + * bus. As such, the address range is different and the buffer + * address needs to be corrected. + */ + paddr -= PHYS_OFFSET; + + if (fb->format->is_yuv) + 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); + regmap_write(backend->engine.regs, + SUN4I_BACKEND_LAYFB_L32ADD_REG(layer), + lo_paddr); + + /* And the upper bits */ + hi_paddr = paddr >> 29; + DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr); + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_LAYFB_H4ADD_REG, + SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer), + SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr)); + + 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 || (plane_state->alpha != DRM_BLEND_ALPHA_OPAQUE)) + num_alpha_planes++; + + if (fb->format->is_yuv) { + 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 || + (plane_states[0]->alpha != DRM_BLEND_ALPHA_OPAQUE)) + 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 || (p_state->alpha != DRM_BLEND_ALPHA_OPAQUE)) + 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; + + backend->sat_reset = devm_reset_control_get(dev, "sat"); + if (IS_ERR(backend->sat_reset)) { + dev_err(dev, "Couldn't get the SAT reset line\n"); + return PTR_ERR(backend->sat_reset); + } + + ret = reset_control_deassert(backend->sat_reset); + if (ret) { + dev_err(dev, "Couldn't deassert the SAT reset line\n"); + return ret; + } + + backend->sat_clk = devm_clk_get(dev, "sat"); + if (IS_ERR(backend->sat_clk)) { + dev_err(dev, "Couldn't get our SAT clock\n"); + ret = PTR_ERR(backend->sat_clk); + goto err_assert_reset; + } + + ret = clk_prepare_enable(backend->sat_clk); + if (ret) { + dev_err(dev, "Couldn't enable the SAT clock\n"); + return ret; + } + + return 0; + +err_assert_reset: + reset_control_assert(backend->sat_reset); + return ret; +} + +static int sun4i_backend_free_sat(struct device *dev) { + struct sun4i_backend *backend = dev_get_drvdata(dev); + + clk_disable_unprepare(backend->sat_clk); + reset_control_assert(backend->sat_reset); + + return 0; +} + +/* + * The display backend can take video output from the display frontend, or + * the display enhancement unit on the A80, as input for one it its layers. + * This relationship within the display pipeline is encoded in the device + * tree with of_graph, and we use it here to figure out which backend, if + * there are 2 or more, we are currently probing. The number would be in + * the "reg" property of the upstream output port endpoint. + */ +static int sun4i_backend_of_get_id(struct device_node *node) +{ + struct device_node *port, *ep; + int ret = -EINVAL; + + /* input is port 0 */ + port = of_graph_get_port_by_id(node, 0); + if (!port) + return -EINVAL; + + /* try finding an upstream endpoint */ + for_each_available_child_of_node(port, ep) { + struct device_node *remote; + u32 reg; + + remote = of_graph_get_remote_endpoint(ep); + if (!remote) + continue; + + ret = of_property_read_u32(remote, "reg", ®); + if (ret) + continue; + + ret = reg; + } + + of_node_put(port); + + 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; + of_node_put(remote); + + /* does this node match any registered engines? */ + list_for_each_entry(frontend, &drv->frontend_list, list) { + if (remote == frontend->node) { + of_node_put(port); + of_node_put(ep); + return frontend; + } + } + } + of_node_put(port); + 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 = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x5800, +}; + +static int sun4i_backend_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_backend *backend; + const struct sun4i_backend_quirks *quirks; + struct resource *res; + void __iomem *regs; + int i, ret; + + backend = devm_kzalloc(dev, sizeof(*backend), GFP_KERNEL); + 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; + backend->engine.id = sun4i_backend_of_get_id(dev->of_node); + 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)) + return PTR_ERR(regs); + + backend->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(backend->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(backend->reset); + } + + ret = reset_control_deassert(backend->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + backend->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(backend->bus_clk)) { + dev_err(dev, "Couldn't get the backend bus clock\n"); + ret = PTR_ERR(backend->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(backend->bus_clk); + + backend->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(backend->mod_clk)) { + dev_err(dev, "Couldn't get the backend module clock\n"); + ret = PTR_ERR(backend->mod_clk); + goto err_disable_bus_clk; + } + clk_prepare_enable(backend->mod_clk); + + backend->ram_clk = devm_clk_get(dev, "ram"); + if (IS_ERR(backend->ram_clk)) { + dev_err(dev, "Couldn't get the backend RAM clock\n"); + ret = PTR_ERR(backend->ram_clk); + goto err_disable_mod_clk; + } + clk_prepare_enable(backend->ram_clk); + + if (of_device_is_compatible(dev->of_node, + "allwinner,sun8i-a33-display-backend")) { + ret = sun4i_backend_init_sat(dev); + if (ret) { + dev_err(dev, "Couldn't init SAT resources\n"); + goto err_disable_ram_clk; + } + } + + backend->engine.regs = devm_regmap_init_mmio(dev, regs, + &sun4i_backend_regmap_config); + if (IS_ERR(backend->engine.regs)) { + dev_err(dev, "Couldn't create the backend regmap\n"); + return PTR_ERR(backend->engine.regs); + } + + list_add_tail(&backend->engine.list, &drv->engine_list); + + /* + * Many of the backend's layer configuration registers have + * undefined default values. This poses a risk as we use + * regmap_update_bits in some places, and don't overwrite + * the whole register. + * + * Clear the registers here to have something predictable. + */ + for (i = 0x800; i < 0x1000; i += 4) + regmap_write(backend->engine.regs, i, 0); + + /* Disable registers autoloading */ + regmap_write(backend->engine.regs, SUN4I_BACKEND_REGBUFFCTL_REG, + SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS); + + /* Enable the backend */ + regmap_write(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_DEBE_EN | + SUN4I_BACKEND_MODCTL_START_CTL); + + /* Set output selection if needed */ + quirks = of_device_get_match_data(dev); + if (quirks->needs_output_muxing) { + /* + * We assume there is no dynamic muxing of backends + * and TCONs, so we select the backend with same ID. + * + * While dynamic selection might be interesting, since + * the CRTC is tied to the TCON, while the layers are + * tied to the backends, this means, we will need to + * switch between groups of layers. There might not be + * a way to represent this constraint in DRM. + */ + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_OUT_SEL, + (backend->engine.id + ? SUN4I_BACKEND_MODCTL_OUT_LCD1 + : SUN4I_BACKEND_MODCTL_OUT_LCD0)); + } + + return 0; + +err_disable_ram_clk: + clk_disable_unprepare(backend->ram_clk); +err_disable_mod_clk: + clk_disable_unprepare(backend->mod_clk); +err_disable_bus_clk: + clk_disable_unprepare(backend->bus_clk); +err_assert_reset: + reset_control_assert(backend->reset); + return ret; +} + +static void sun4i_backend_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_backend *backend = dev_get_drvdata(dev); + + list_del(&backend->engine.list); + + if (of_device_is_compatible(dev->of_node, + "allwinner,sun8i-a33-display-backend")) + sun4i_backend_free_sat(dev); + + clk_disable_unprepare(backend->ram_clk); + clk_disable_unprepare(backend->mod_clk); + clk_disable_unprepare(backend->bus_clk); + reset_control_assert(backend->reset); +} + +static const struct component_ops sun4i_backend_ops = { + .bind = sun4i_backend_bind, + .unbind = sun4i_backend_unbind, +}; + +static int sun4i_backend_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_backend_ops); +} + +static int sun4i_backend_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_backend_ops); + + return 0; +} + +static const struct sun4i_backend_quirks sun4i_backend_quirks = { + .needs_output_muxing = true, +}; + +static const struct sun4i_backend_quirks sun5i_backend_quirks = { +}; + +static const struct sun4i_backend_quirks sun6i_backend_quirks = { +}; + +static const struct sun4i_backend_quirks sun7i_backend_quirks = { + .needs_output_muxing = true, +}; + +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", + .data = &sun4i_backend_quirks, + }, + { + .compatible = "allwinner,sun5i-a13-display-backend", + .data = &sun5i_backend_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-display-backend", + .data = &sun6i_backend_quirks, + }, + { + .compatible = "allwinner,sun7i-a20-display-backend", + .data = &sun7i_backend_quirks, + }, + { + .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); + +static struct platform_driver sun4i_backend_platform_driver = { + .probe = sun4i_backend_probe, + .remove = sun4i_backend_remove, + .driver = { + .name = "sun4i-backend", + .of_match_table = sun4i_backend_of_table, + }, +}; +module_platform_driver(sun4i_backend_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Display Backend Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h new file mode 100644 index 000000000..4caee0392 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_BACKEND_H_ +#define _SUN4I_BACKEND_H_ + +#include <linux/clk.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sunxi_engine.h" + +#define SUN4I_BACKEND_MODCTL_REG 0x800 +#define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) +#define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) +#define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK(22, 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD0 (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD1 (1 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20) +#define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16) +#define SUN4I_BACKEND_MODCTL_LAY_EN(l) BIT(8 + l) +#define SUN4I_BACKEND_MODCTL_OCSC_EN BIT(5) +#define SUN4I_BACKEND_MODCTL_DFLK_EN BIT(4) +#define SUN4I_BACKEND_MODCTL_DLP_START_CTL BIT(2) +#define SUN4I_BACKEND_MODCTL_START_CTL BIT(1) +#define SUN4I_BACKEND_MODCTL_DEBE_EN BIT(0) + +#define SUN4I_BACKEND_BACKCOLOR_REG 0x804 +#define SUN4I_BACKEND_BACKCOLOR(r, g, b) (((r) << 16) | ((g) << 8) | (b)) + +#define SUN4I_BACKEND_DISSIZE_REG 0x808 +#define SUN4I_BACKEND_DISSIZE(w, h) (((((h) - 1) & 0xffff) << 16) | \ + (((w) - 1) & 0xffff)) + +#define SUN4I_BACKEND_LAYSIZE_REG(l) (0x810 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYSIZE(w, h) (((((h) - 1) & 0x1fff) << 16) | \ + (((w) - 1) & 0x1fff)) + +#define SUN4I_BACKEND_LAYCOOR_REG(l) (0x820 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYCOOR(x, y) ((((u32)(y) & 0xffff) << 16) | \ + ((u32)(x) & 0xffff)) + +#define SUN4I_BACKEND_LAYLINEWIDTH_REG(l) (0x840 + (0x4 * (l))) + +#define SUN4I_BACKEND_LAYFB_L32ADD_REG(l) (0x850 + (0x4 * (l))) + +#define SUN4I_BACKEND_LAYFB_H4ADD_REG 0x860 +#define SUN4I_BACKEND_LAYFB_H4ADD_MSK(l) GENMASK(3 + ((l) * 8), (l) * 8) +#define SUN4I_BACKEND_LAYFB_H4ADD(l, val) ((val) << ((l) * 8)) + +#define SUN4I_BACKEND_REGBUFFCTL_REG 0x870 +#define SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS BIT(1) +#define SUN4I_BACKEND_REGBUFFCTL_LOADCTL BIT(0) + +#define SUN4I_BACKEND_CKMAX_REG 0x880 +#define SUN4I_BACKEND_CKMIN_REG 0x884 +#define SUN4I_BACKEND_CKCFG_REG 0x888 +#define SUN4I_BACKEND_ATTCTL_REG0(l) (0x890 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA_MASK GENMASK(31, 24) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA(x) ((x) << 24) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK BIT(15) +#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_REG0_LAY_GLBALPHA_EN BIT(0) + +#define SUN4I_BACKEND_ATTCTL_REG1(l) (0x8a0 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT GENMASK(15, 14) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_WSCAFCT GENMASK(13, 12) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT GENMASK(11, 8) +#define SUN4I_BACKEND_LAY_FBFMT_1BPP (0 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_2BPP (1 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_4BPP (2 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_8BPP (3 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB655 (4 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB565 (5 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB556 (6 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB1555 (7 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA5551 (8 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_XRGB8888 (9 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB8888 (10 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB888 (11 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB4444 (12 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA4444 (13 << 8) + +#define SUN4I_BACKEND_DLCDPCTL_REG 0x8b0 +#define SUN4I_BACKEND_DLCDPFRMBUF_ADDRCTL_REG 0x8b4 +#define SUN4I_BACKEND_DLCDPCOOR_REG0 0x8b8 +#define SUN4I_BACKEND_DLCDPCOOR_REG1 0x8bc + +#define SUN4I_BACKEND_INT_EN_REG 0x8c0 +#define SUN4I_BACKEND_INT_FLAG_REG 0x8c4 +#define SUN4I_BACKEND_REG_LOAD_FINISHED BIT(1) + +#define SUN4I_BACKEND_HWCCTL_REG 0x8d8 +#define SUN4I_BACKEND_HWCFBCTL_REG 0x8e0 +#define SUN4I_BACKEND_WBCTL_REG 0x8f0 +#define SUN4I_BACKEND_WBADD_REG 0x8f4 +#define SUN4I_BACKEND_WBLINEWIDTH_REG 0x8f8 +#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_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))) +#define SUN4I_BACKEND_URCONS_REG 0x96c +#define SUN4I_BACKEND_VBCOEF_REG(c) (0x970 + (0x4 * (c))) +#define SUN4I_BACKEND_VBCONS_REG 0x97c +#define SUN4I_BACKEND_KSCTL_REG 0x980 +#define SUN4I_BACKEND_KSBKCOLOR_REG 0x984 +#define SUN4I_BACKEND_KSFSTLINEWIDTH_REG 0x988 +#define SUN4I_BACKEND_KSVSCAFCT_REG 0x98c +#define SUN4I_BACKEND_KSHSCACOEF_REG(x) (0x9a0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCCTL_REG 0x9c0 +#define SUN4I_BACKEND_OCCTL_ENABLE BIT(0) + +#define SUN4I_BACKEND_OCRCOEF_REG(x) (0x9d0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCRCONS_REG 0x9dc +#define SUN4I_BACKEND_OCGCOEF_REG(x) (0x9e0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCGCONS_REG 0x9ec +#define SUN4I_BACKEND_OCBCOEF_REG(x) (0x9f0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCBCONS_REG 0x9fc +#define SUN4I_BACKEND_SPRCOORCTL_REG(s) (0xa00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRATTCTL_REG(s) (0xb00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRADD_REG(s) (0xc00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRLINEWIDTH_REG(s) (0xd00 + (0x4 * (s))) + +#define SUN4I_BACKEND_SPRPALTAB_OFF 0x4000 +#define SUN4I_BACKEND_GAMMATAB_OFF 0x4400 +#define SUN4I_BACKEND_HWCPATTERN_OFF 0x4800 +#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; + + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *ram_clk; + + 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 * +engine_to_sun4i_backend(struct sunxi_engine *engine) +{ + return container_of(engine, struct sun4i_backend, engine); +} + +void sun4i_backend_layer_enable(struct sun4i_backend *backend, + int layer, bool enable); +int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); +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 new file mode 100644 index 000000000..3eedf335a --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_modes.h> + +#include <linux/clk-provider.h> +#include <linux/ioport.h> +#include <linux/of_address.h> +#include <linux/of_graph.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> + +#include <video/videomode.h> + +#include "sun4i_backend.h" +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sunxi_engine.h" +#include "sun4i_tcon.h" + +/* + * While this isn't really working in the DRM theory, in practice we + * can only ever have one encoder per TCON since we have a mux in our + * TCON. + */ +static struct drm_encoder *sun4i_crtc_get_encoder(struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + + drm_for_each_encoder(encoder, crtc->dev) + if (encoder->crtc == crtc) + return encoder; + + 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) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + 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, + struct drm_crtc_state *old_state) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct drm_pending_vblank_event *event = crtc->state->event; + + DRM_DEBUG_DRIVER("Committing plane changes\n"); + + sunxi_engine_commit(scrtc->engine); + + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + + DRM_DEBUG_DRIVER("Disabling the CRTC\n"); + + drm_crtc_vblank_off(crtc); + + sun4i_tcon_set_status(scrtc->tcon, encoder, false); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + +static void sun4i_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + + DRM_DEBUG_DRIVER("Enabling the CRTC\n"); + + sun4i_tcon_set_status(scrtc->tcon, encoder, true); + + drm_crtc_vblank_on(crtc); +} + +static void sun4i_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + + sun4i_tcon_mode_set(scrtc->tcon, encoder, mode); +} + +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, + .atomic_disable = sun4i_crtc_atomic_disable, + .mode_set_nofb = sun4i_crtc_mode_set_nofb, +}; + +static int sun4i_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + + DRM_DEBUG_DRIVER("Enabling VBLANK on crtc %p\n", crtc); + + sun4i_tcon_enable_vblank(scrtc->tcon, true); + + return 0; +} + +static void sun4i_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + + DRM_DEBUG_DRIVER("Disabling VBLANK on crtc %p\n", crtc); + + sun4i_tcon_enable_vblank(scrtc->tcon, false); +} + +static const struct drm_crtc_funcs sun4i_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .enable_vblank = sun4i_crtc_enable_vblank, + .disable_vblank = sun4i_crtc_disable_vblank, +}; + +struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, + struct sunxi_engine *engine, + struct sun4i_tcon *tcon) +{ + struct sun4i_crtc *scrtc; + struct drm_plane **planes; + struct drm_plane *primary = NULL, *cursor = NULL; + int ret, i; + + scrtc = devm_kzalloc(drm->dev, sizeof(*scrtc), GFP_KERNEL); + if (!scrtc) + return ERR_PTR(-ENOMEM); + scrtc->engine = engine; + scrtc->tcon = tcon; + + /* Create our layers */ + planes = sunxi_engine_layers_init(drm, engine); + if (IS_ERR(planes)) { + dev_err(drm->dev, "Couldn't create the planes\n"); + return NULL; + } + + /* find primary and cursor planes for drm_crtc_init_with_planes */ + for (i = 0; planes[i]; i++) { + struct drm_plane *plane = planes[i]; + + switch (plane->type) { + case DRM_PLANE_TYPE_PRIMARY: + primary = plane; + break; + case DRM_PLANE_TYPE_CURSOR: + cursor = plane; + break; + default: + break; + } + } + + ret = drm_crtc_init_with_planes(drm, &scrtc->crtc, + primary, + cursor, + &sun4i_crtc_funcs, + NULL); + if (ret) { + dev_err(drm->dev, "Couldn't init DRM CRTC\n"); + return ERR_PTR(ret); + } + + drm_crtc_helper_add(&scrtc->crtc, &sun4i_crtc_helper_funcs); + + /* Set crtc.port to output port node of the tcon */ + scrtc->crtc.port = of_graph_get_port_by_id(scrtc->tcon->dev->of_node, + 1); + + /* Set possible_crtcs to this crtc for overlay planes */ + for (i = 0; planes[i]; i++) { + uint32_t possible_crtcs = drm_crtc_mask(&scrtc->crtc); + struct drm_plane *plane = planes[i]; + + if (plane->type == DRM_PLANE_TYPE_OVERLAY) + plane->possible_crtcs = possible_crtcs; + } + + return scrtc; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.h b/drivers/gpu/drm/sun4i/sun4i_crtc.h new file mode 100644 index 000000000..bf0ce36eb --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_CRTC_H_ +#define _SUN4I_CRTC_H_ + +struct sun4i_crtc { + struct drm_crtc crtc; + struct drm_pending_vblank_event *event; + + struct sunxi_engine *engine; + struct sun4i_tcon *tcon; +}; + +static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct sun4i_crtc, crtc); +} + +struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, + struct sunxi_engine *engine, + struct sun4i_tcon *tcon); + +#endif /* _SUN4I_CRTC_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.c b/drivers/gpu/drm/sun4i/sun4i_dotclock.c new file mode 100644 index 000000000..2a15f2f92 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk-provider.h> +#include <linux/regmap.h> + +#include "sun4i_tcon.h" +#include "sun4i_dotclock.h" + +struct sun4i_dclk { + struct clk_hw hw; + struct regmap *regmap; + struct sun4i_tcon *tcon; +}; + +static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw) +{ + return container_of(hw, struct sun4i_dclk, hw); +} + +static void sun4i_dclk_disable(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + + regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, + BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0); +} + +static int sun4i_dclk_enable(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + + return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, + BIT(SUN4I_TCON0_DCLK_GATE_BIT), + BIT(SUN4I_TCON0_DCLK_GATE_BIT)); +} + +static int sun4i_dclk_is_enabled(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u32 val; + + regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); + + return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT); +} + +static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u32 val; + + regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); + + val >>= SUN4I_TCON0_DCLK_DIV_SHIFT; + val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1; + + if (!val) + val = 1; + + return parent_rate / val; +} + +static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + struct sun4i_tcon *tcon = dclk->tcon; + unsigned long best_parent = 0; + u8 best_div = 1; + int i; + + for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) { + u64 ideal = (u64)rate * i; + unsigned long rounded; + + /* + * ideal has overflowed the max value that can be stored in an + * unsigned long, and every clk operation we might do on a + * truncated u64 value will give us incorrect results. + * Let's just stop there since bigger dividers will result in + * the same overflow issue. + */ + if (ideal > ULONG_MAX) + goto out; + + rounded = clk_hw_round_rate(clk_hw_get_parent(hw), + ideal); + + if (rounded == ideal) { + best_parent = rounded; + best_div = i; + goto out; + } + + if (abs(rate - rounded / i) < + abs(rate - best_parent / best_div)) { + best_parent = rounded; + best_div = i; + } + } + +out: + *parent_rate = best_parent; + + return best_parent / best_div; +} + +static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u8 div = parent_rate / rate; + + return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, + GENMASK(6, 0), div); +} + +static int sun4i_dclk_get_phase(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u32 val; + + regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val); + + val >>= 28; + val &= 3; + + return val * 120; +} + +static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u32 val = degrees / 120; + + val <<= 28; + + regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG, + GENMASK(29, 28), + val); + + return 0; +} + +static const struct clk_ops sun4i_dclk_ops = { + .disable = sun4i_dclk_disable, + .enable = sun4i_dclk_enable, + .is_enabled = sun4i_dclk_is_enabled, + + .recalc_rate = sun4i_dclk_recalc_rate, + .round_rate = sun4i_dclk_round_rate, + .set_rate = sun4i_dclk_set_rate, + + .get_phase = sun4i_dclk_get_phase, + .set_phase = sun4i_dclk_set_phase, +}; + +int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon) +{ + const char *clk_name, *parent_name; + struct clk_init_data init; + struct sun4i_dclk *dclk; + int ret; + + parent_name = __clk_get_name(tcon->sclk0); + ret = of_property_read_string_index(dev->of_node, + "clock-output-names", 0, + &clk_name); + if (ret) + return ret; + + dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL); + if (!dclk) + return -ENOMEM; + dclk->tcon = tcon; + + init.name = clk_name; + init.ops = &sun4i_dclk_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + dclk->regmap = tcon->regs; + dclk->hw.init = &init; + + tcon->dclk = clk_register(dev, &dclk->hw); + if (IS_ERR(tcon->dclk)) + return PTR_ERR(tcon->dclk); + + return 0; +} +EXPORT_SYMBOL(sun4i_dclk_create); + +int sun4i_dclk_free(struct sun4i_tcon *tcon) +{ + clk_unregister(tcon->dclk); + return 0; +} +EXPORT_SYMBOL(sun4i_dclk_free); diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.h b/drivers/gpu/drm/sun4i/sun4i_dotclock.h new file mode 100644 index 000000000..d5e25fa9e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_DOTCLOCK_H_ +#define _SUN4I_DOTCLOCK_H_ + +struct sun4i_tcon; + +int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon); +int sun4i_dclk_free(struct sun4i_tcon *tcon); + +#endif /* _SUN4I_DOTCLOCK_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c new file mode 100644 index 000000000..57f61ec4b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/component.h> +#include <linux/kfifo.h> +#include <linux/of_graph.h> +#include <linux/of_reserved_mem.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_of.h> + +#include "sun4i_drv.h" +#include "sun4i_frontend.h" +#include "sun4i_framebuffer.h" +#include "sun4i_tcon.h" +#include "sun8i_tcon_top.h" + +DEFINE_DRM_GEM_CMA_FOPS(sun4i_drv_fops); + +static struct drm_driver sun4i_drv_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, + + /* Generic Operations */ + .lastclose = drm_fb_helper_lastclose, + .fops = &sun4i_drv_fops, + .name = "sun4i-drm", + .desc = "Allwinner sun4i Display Engine", + .date = "20150629", + .major = 1, + .minor = 0, + + /* GEM Operations */ + .dumb_create = drm_gem_cma_dumb_create, + .gem_free_object_unlocked = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + + /* PRIME Operations */ + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + + /* Frame Buffer Operations */ +}; + +static void sun4i_remove_framebuffers(void) +{ + struct apertures_struct *ap; + + ap = alloc_apertures(1); + if (!ap) + return; + + /* The framebuffer can be located anywhere in RAM */ + ap->ranges[0].base = 0; + ap->ranges[0].size = ~0; + + drm_fb_helper_remove_conflicting_framebuffers(ap, "sun4i-drm-fb", false); + kfree(ap); +} + +static int sun4i_drv_bind(struct device *dev) +{ + struct drm_device *drm; + struct sun4i_drv *drv; + int ret; + + drm = drm_dev_alloc(&sun4i_drv_driver, dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) { + ret = -ENOMEM; + goto free_drm; + } + + dev_set_drvdata(dev, drm); + drm->dev_private = drv; + INIT_LIST_HEAD(&drv->frontend_list); + INIT_LIST_HEAD(&drv->engine_list); + INIT_LIST_HEAD(&drv->tcon_list); + + ret = of_reserved_mem_device_init(dev); + if (ret && ret != -ENODEV) { + dev_err(drm->dev, "Couldn't claim our memory region\n"); + goto free_drm; + } + + drm_mode_config_init(drm); + + ret = component_bind_all(drm->dev, drm); + if (ret) { + dev_err(drm->dev, "Couldn't bind all pipelines components\n"); + goto cleanup_mode_config; + } + + /* drm_vblank_init calls kcalloc, which can fail */ + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (ret) + goto cleanup_mode_config; + + drm->irq_enabled = true; + + /* Remove early framebuffers (ie. simplefb) */ + sun4i_remove_framebuffers(); + + /* Create our framebuffer */ + ret = sun4i_framebuffer_init(drm); + if (ret) { + dev_err(drm->dev, "Couldn't create our framebuffer\n"); + goto cleanup_mode_config; + } + + /* Enable connectors polling */ + drm_kms_helper_poll_init(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto finish_poll; + + return 0; + +finish_poll: + drm_kms_helper_poll_fini(drm); + sun4i_framebuffer_free(drm); +cleanup_mode_config: + drm_mode_config_cleanup(drm); + of_reserved_mem_device_release(dev); +free_drm: + drm_dev_put(drm); + return ret; +} + +static void sun4i_drv_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_dev_unregister(drm); + drm_kms_helper_poll_fini(drm); + sun4i_framebuffer_free(drm); + drm_mode_config_cleanup(drm); + + component_unbind_all(dev, NULL); + of_reserved_mem_device_release(dev); + + drm_dev_put(drm); +} + +static const struct component_master_ops sun4i_drv_master_ops = { + .bind = sun4i_drv_bind, + .unbind = sun4i_drv_unbind, +}; + +static bool sun4i_drv_node_is_connector(struct device_node *node) +{ + return of_device_is_compatible(node, "hdmi-connector"); +} + +static bool sun4i_drv_node_is_frontend(struct device_node *node) +{ + return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") || + 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,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) +{ + return !!of_match_node(sun4i_tcon_of_table, node); +} + +static bool sun4i_drv_node_is_tcon_with_ch0(struct device_node *node) +{ + const struct of_device_id *match; + + match = of_match_node(sun4i_tcon_of_table, node); + if (match) { + struct sun4i_tcon_quirks *quirks; + + quirks = (struct sun4i_tcon_quirks *)match->data; + + return quirks->has_channel_0; + } + + return false; +} + +static bool sun4i_drv_node_is_tcon_top(struct device_node *node) +{ + return IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && + !!of_match_node(sun8i_tcon_top_of_table, node); +} + +static int compare_of(struct device *dev, void *data) +{ + DRM_DEBUG_DRIVER("Comparing of node %pOF with %pOF\n", + dev->of_node, + data); + + return dev->of_node == data; +} + +/* + * The encoder drivers use drm_of_find_possible_crtcs to get upstream + * crtcs from the device tree using of_graph. For the results to be + * correct, encoders must be probed/bound after _all_ crtcs have been + * created. The existing code uses a depth first recursive traversal + * of the of_graph, which means the encoders downstream of the TCON + * get add right after the first TCON. The second TCON or CRTC will + * never be properly associated with encoders connected to it. + * + * Also, in a dual display pipeline setup, both frontends can feed + * either backend, and both backends can feed either TCON, we want + * all components of the same type to be added before the next type + * in the pipeline. Fortunately, the pipelines are perfectly symmetric, + * i.e. components of the same type are at the same depth when counted + * from the frontend. The only exception is the third pipeline in + * the A80 SoC, which we do not support anyway. + * + * Hence we can use a breadth first search traversal order to add + * components. We do not need to check for duplicates. The component + * matching system handles this for us. + */ +struct endpoint_list { + DECLARE_KFIFO(fifo, struct device_node *, 16); +}; + +static void sun4i_drv_traverse_endpoints(struct endpoint_list *list, + struct device_node *node, + int port_id) +{ + struct device_node *ep, *remote, *port; + + port = of_graph_get_port_by_id(node, port_id); + if (!port) { + DRM_DEBUG_DRIVER("No output to bind on port %d\n", port_id); + return; + } + + for_each_available_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + DRM_DEBUG_DRIVER("Error retrieving the output node\n"); + continue; + } + + if (sun4i_drv_node_is_tcon(node)) { + /* + * TCON TOP is always probed before TCON. However, TCON + * points back to TCON TOP when it is source for HDMI. + * We have to skip it here to prevent infinite looping + * between TCON TOP and TCON. + */ + if (sun4i_drv_node_is_tcon_top(remote)) { + DRM_DEBUG_DRIVER("TCON output endpoint is TCON TOP... skipping\n"); + of_node_put(remote); + continue; + } + + /* + * If the node is our TCON with channel 0, the first + * port is used for panel or bridges, and will not be + * part of the component framework. + */ + if (sun4i_drv_node_is_tcon_with_ch0(node)) { + struct of_endpoint endpoint; + + if (of_graph_parse_endpoint(ep, &endpoint)) { + DRM_DEBUG_DRIVER("Couldn't parse endpoint\n"); + of_node_put(remote); + continue; + } + + if (!endpoint.id) { + DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n"); + of_node_put(remote); + continue; + } + } + } + + kfifo_put(&list->fifo, remote); + } +} + +static int sun4i_drv_add_endpoints(struct device *dev, + struct endpoint_list *list, + struct component_match **match, + struct device_node *node) +{ + int count = 0; + + /* + * 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)) + return 0; + + /* + * The connectors will be the last nodes in our pipeline, we + * can just bail out. + */ + if (sun4i_drv_node_is_connector(node)) + return 0; + + /* + * 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); + count++; + } + + /* each node has at least one output */ + sun4i_drv_traverse_endpoints(list, node, 1); + + /* TCON TOP has second and third output */ + if (sun4i_drv_node_is_tcon_top(node)) { + sun4i_drv_traverse_endpoints(list, node, 3); + sun4i_drv_traverse_endpoints(list, node, 5); + } + + return count; +} + +static int sun4i_drv_probe(struct platform_device *pdev) +{ + struct component_match *match = NULL; + struct device_node *np = pdev->dev.of_node, *endpoint; + struct endpoint_list list; + int i, ret, count = 0; + + INIT_KFIFO(list.fifo); + + for (i = 0;; i++) { + struct device_node *pipeline = of_parse_phandle(np, + "allwinner,pipelines", + i); + if (!pipeline) + break; + + kfifo_put(&list.fifo, pipeline); + } + + while (kfifo_get(&list.fifo, &endpoint)) { + /* process this endpoint */ + ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match, + endpoint); + + /* sun4i_drv_add_endpoints can fail to allocate memory */ + if (ret < 0) + return ret; + + count += ret; + } + + if (count) + return component_master_add_with_match(&pdev->dev, + &sun4i_drv_master_ops, + match); + else + return 0; +} + +static int sun4i_drv_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &sun4i_drv_master_ops); + + return 0; +} + +static const struct of_device_id sun4i_drv_of_table[] = { + { .compatible = "allwinner,sun4i-a10-display-engine" }, + { .compatible = "allwinner,sun5i-a10s-display-engine" }, + { .compatible = "allwinner,sun5i-a13-display-engine" }, + { .compatible = "allwinner,sun6i-a31-display-engine" }, + { .compatible = "allwinner,sun6i-a31s-display-engine" }, + { .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); + +static struct platform_driver sun4i_drv_platform_driver = { + .probe = sun4i_drv_probe, + .remove = sun4i_drv_remove, + .driver = { + .name = "sun4i-drm", + .of_match_table = sun4i_drv_of_table, + }, +}; +module_platform_driver(sun4i_drv_platform_driver); + +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h new file mode 100644 index 000000000..5750b8ce8 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_drv.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_DRV_H_ +#define _SUN4I_DRV_H_ + +#include <linux/clk.h> +#include <linux/list.h> +#include <linux/regmap.h> + +struct sun4i_drv { + struct list_head engine_list; + struct list_head frontend_list; + struct list_head tcon_list; +}; + +#endif /* _SUN4I_DRV_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c new file mode 100644 index 000000000..5f29850ef --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * 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> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drmP.h> + +#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 = 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); + + drm->mode_config.max_width = 8192; + 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); +} + +void sun4i_framebuffer_free(struct drm_device *drm) +{ + drm_fb_cma_fbdev_fini(drm); +} diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.h b/drivers/gpu/drm/sun4i/sun4i_framebuffer.h new file mode 100644 index 000000000..7ef0aed83 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_FRAMEBUFFER_H_ +#define _SUN4I_FRAMEBUFFER_H_ + +int sun4i_framebuffer_init(struct drm_device *drm); +void sun4i_framebuffer_free(struct drm_device *drm); + +#endif /* _SUN4I_FRAMEBUFFER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_frontend.c b/drivers/gpu/drm/sun4i/sun4i_frontend.c new file mode 100644 index 000000000..ddf6cfa6d --- /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 000000000..02661ce81 --- /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_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h new file mode 100644 index 000000000..a13e14dd7 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_HDMI_H_ +#define _SUN4I_HDMI_H_ + +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <linux/regmap.h> + +#include <media/cec-pin.h> + +#define SUN4I_HDMI_CTRL_REG 0x004 +#define SUN4I_HDMI_CTRL_ENABLE BIT(31) + +#define SUN4I_HDMI_IRQ_REG 0x008 +#define SUN4I_HDMI_IRQ_STA_MASK 0x73 +#define SUN4I_HDMI_IRQ_STA_FIFO_OF BIT(1) +#define SUN4I_HDMI_IRQ_STA_FIFO_UF BIT(0) + +#define SUN4I_HDMI_HPD_REG 0x00c +#define SUN4I_HDMI_HPD_HIGH BIT(0) + +#define SUN4I_HDMI_VID_CTRL_REG 0x010 +#define SUN4I_HDMI_VID_CTRL_ENABLE BIT(31) +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE BIT(30) + +#define SUN4I_HDMI_VID_TIMING_ACT_REG 0x014 +#define SUN4I_HDMI_VID_TIMING_BP_REG 0x018 +#define SUN4I_HDMI_VID_TIMING_FP_REG 0x01c +#define SUN4I_HDMI_VID_TIMING_SPW_REG 0x020 + +#define SUN4I_HDMI_VID_TIMING_X(x) ((((x) - 1) & GENMASK(11, 0))) +#define SUN4I_HDMI_VID_TIMING_Y(y) ((((y) - 1) & GENMASK(11, 0)) << 16) + +#define SUN4I_HDMI_VID_TIMING_POL_REG 0x024 +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK (0x3e0 << 16) +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC BIT(1) +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC BIT(0) + +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n) (0x080 + (n)) + +#define SUN4I_HDMI_PAD_CTRL0_REG 0x200 +#define SUN4I_HDMI_PAD_CTRL0_BIASEN BIT(31) +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN BIT(30) +#define SUN4I_HDMI_PAD_CTRL0_LDODEN BIT(29) +#define SUN4I_HDMI_PAD_CTRL0_PWENC BIT(28) +#define SUN4I_HDMI_PAD_CTRL0_PWEND BIT(27) +#define SUN4I_HDMI_PAD_CTRL0_PWENG BIT(26) +#define SUN4I_HDMI_PAD_CTRL0_CKEN BIT(25) +#define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23) + +#define SUN4I_HDMI_PAD_CTRL1_REG 0x204 +#define SUN4I_HDMI_PAD_CTRL1_UNKNOWN BIT(24) /* set on A31 */ +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT BIT(23) +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT BIT(22) +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT BIT(20) +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT BIT(19) +#define SUN4I_HDMI_PAD_CTRL1_PWSCK BIT(18) +#define SUN4I_HDMI_PAD_CTRL1_PWSDT BIT(17) +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15) +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14) +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10) +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK BIT(6) +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n) (((n) & 7) << 3) + +/* These bits seem to invert the TMDS data channels */ +#define SUN4I_HDMI_PAD_CTRL1_INVERT_R BIT(2) +#define SUN4I_HDMI_PAD_CTRL1_INVERT_G BIT(1) +#define SUN4I_HDMI_PAD_CTRL1_INVERT_B BIT(0) + +#define SUN4I_HDMI_PLL_CTRL_REG 0x208 +#define SUN4I_HDMI_PLL_CTRL_PLL_EN BIT(31) +#define SUN4I_HDMI_PLL_CTRL_BWS BIT(30) +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33 BIT(29) +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN BIT(28) +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN BIT(27) +#define SUN4I_HDMI_PLL_CTRL_SDIV2 BIT(25) +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n) (((n) & 7) << 20) +#define SUN4I_HDMI_PLL_CTRL_S(n) (((n) & 7) << 17) +#define SUN4I_HDMI_PLL_CTRL_CP_S(n) (((n) & 0x1f) << 12) +#define SUN4I_HDMI_PLL_CTRL_CS(n) (((n) & 0xf) << 8) +#define SUN4I_HDMI_PLL_CTRL_DIV(n) (((n) & 0xf) << 4) +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK GENMASK(7, 4) +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n) ((n) & 0xf) + +#define SUN4I_HDMI_PLL_DBG0_REG 0x20c +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n) (((n) & 1) << 21) +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21) +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21 + +#define SUN4I_HDMI_CEC 0x214 +#define SUN4I_HDMI_CEC_ENABLE BIT(11) +#define SUN4I_HDMI_CEC_TX BIT(9) +#define SUN4I_HDMI_CEC_RX BIT(8) + +#define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n))) +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4)) + +#define SUN4I_HDMI_UNKNOWN_REG 0x300 +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC BIT(27) + +#define SUN4I_HDMI_DDC_CTRL_REG 0x500 +#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31) +#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE (1 << 8) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) +#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) + +#define SUN4I_HDMI_DDC_ADDR_REG 0x504 +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24) +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16) +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff) + +#define SUN4I_HDMI_DDC_INT_STATUS_REG 0x50c +#define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW BIT(6) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW BIT(5) +#define SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST BIT(4) +#define SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR BIT(3) +#define SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR BIT(2) +#define SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR BIT(1) +#define SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE BIT(0) + +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) (((n) & 0xf) << 4) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) ((n) & 0xf) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1) + +#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 + +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c +#define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1) + +#define SUN4I_HDMI_DDC_CMD_REG 0x520 +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 5 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 3 + +#define SUN4I_HDMI_DDC_CLK_REG 0x528 +#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0xf) << 3) +#define SUN4I_HDMI_DDC_CLK_N(n) ((n) & 0x7) + +#define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540 +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9) +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8) + +#define SUN4I_HDMI_DDC_FIFO_SIZE 16 + +/* A31 specific */ +#define SUN6I_HDMI_DDC_CTRL_REG 0x500 +#define SUN6I_HDMI_DDC_CTRL_RESET BIT(31) +#define SUN6I_HDMI_DDC_CTRL_START_CMD BIT(27) +#define SUN6I_HDMI_DDC_CTRL_SDA_ENABLE BIT(6) +#define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4) +#define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0) + +#define SUN6I_HDMI_DDC_CMD_REG 0x508 +#define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16) +/* command types in lower 3 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_ADDR_REG 0x50c +#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24) +#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16) +#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) +#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) & 0xff) << 1) + +#define SUN6I_HDMI_DDC_INT_STATUS_REG 0x514 +#define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8) +/* lower 8 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_FIFO_CTRL_REG 0x518 +#define SUN6I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(15) +/* lower 9 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_CLK_REG 0x520 +/* DDC CLK bit fields are the same, but the formula is not */ + +#define SUN6I_HDMI_DDC_FIFO_DATA_REG 0x580 + +enum sun4i_hdmi_pkt_type { + SUN4I_HDMI_PKT_AVI = 2, + SUN4I_HDMI_PKT_END = 15, +}; + +struct sun4i_hdmi_variant { + bool has_ddc_parent_clk; + bool has_reset_control; + + u32 pad_ctrl0_init_val; + u32 pad_ctrl1_init_val; + u32 pll_ctrl_init_val; + + struct reg_field ddc_clk_reg; + u8 ddc_clk_pre_divider; + u8 ddc_clk_m_offset; + + u8 tmds_clk_div_offset; + + /* Register fields for I2C adapter */ + struct reg_field field_ddc_en; + struct reg_field field_ddc_start; + struct reg_field field_ddc_reset; + struct reg_field field_ddc_addr_reg; + struct reg_field field_ddc_slave_addr; + struct reg_field field_ddc_int_mask; + struct reg_field field_ddc_int_status; + struct reg_field field_ddc_fifo_clear; + struct reg_field field_ddc_fifo_rx_thres; + struct reg_field field_ddc_fifo_tx_thres; + struct reg_field field_ddc_byte_count; + struct reg_field field_ddc_cmd; + struct reg_field field_ddc_sda_en; + struct reg_field field_ddc_sck_en; + + /* DDC FIFO register offset */ + u32 ddc_fifo_reg; + + /* + * DDC FIFO threshold boundary conditions + * + * This is used to cope with the threshold boundary condition + * being slightly different on sun5i and sun6i. + * + * On sun5i the threshold is exclusive, i.e. does not include, + * the value of the threshold. ( > for RX; < for TX ) + * On sun6i the threshold is inclusive, i.e. includes, the + * value of the threshold. ( >= for RX; <= for TX ) + */ + bool ddc_fifo_thres_incl; + + bool ddc_fifo_has_dir; +}; + +struct sun4i_hdmi { + struct drm_connector connector; + struct drm_encoder encoder; + struct device *dev; + + void __iomem *base; + struct regmap *regmap; + + /* Reset control */ + struct reset_control *reset; + + /* Parent clocks */ + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *ddc_parent_clk; + struct clk *pll0_clk; + struct clk *pll1_clk; + + /* And the clocks we create */ + struct clk *ddc_clk; + struct clk *tmds_clk; + + struct i2c_adapter *i2c; + + /* Regmap fields for I2C adapter */ + struct regmap_field *field_ddc_en; + struct regmap_field *field_ddc_start; + struct regmap_field *field_ddc_reset; + struct regmap_field *field_ddc_addr_reg; + struct regmap_field *field_ddc_slave_addr; + struct regmap_field *field_ddc_int_mask; + struct regmap_field *field_ddc_int_status; + struct regmap_field *field_ddc_fifo_clear; + struct regmap_field *field_ddc_fifo_rx_thres; + struct regmap_field *field_ddc_fifo_tx_thres; + struct regmap_field *field_ddc_byte_count; + struct regmap_field *field_ddc_cmd; + struct regmap_field *field_ddc_sda_en; + struct regmap_field *field_ddc_sck_en; + + struct sun4i_drv *drv; + + bool hdmi_monitor; + struct cec_adapter *cec_adap; + + const struct sun4i_hdmi_variant *variant; +}; + +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); +int sun4i_tmds_create(struct sun4i_hdmi *hdmi); +int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi); + +#endif /* _SUN4I_HDMI_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c new file mode 100644 index 000000000..14b676256 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk-provider.h> +#include <linux/regmap.h> + +#include "sun4i_hdmi.h" + +struct sun4i_ddc { + struct clk_hw hw; + struct sun4i_hdmi *hdmi; + struct regmap_field *reg; + u8 pre_div; + u8 m_offset; +}; + +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw) +{ + return container_of(hw, struct sun4i_ddc, hw); +} + +static unsigned long sun4i_ddc_calc_divider(unsigned long rate, + unsigned long parent_rate, + const u8 pre_div, + const u8 m_offset, + u8 *m, u8 *n) +{ + unsigned long best_rate = 0; + u8 best_m = 0, best_n = 0, _m, _n; + + for (_m = 0; _m < 16; _m++) { + for (_n = 0; _n < 8; _n++) { + unsigned long tmp_rate; + + tmp_rate = (((parent_rate / pre_div) / 10) >> _n) / + (_m + m_offset); + + if (tmp_rate > rate) + continue; + + if (abs(rate - tmp_rate) < abs(rate - best_rate)) { + best_rate = tmp_rate; + best_m = _m; + best_n = _n; + } + } + } + + if (m && n) { + *m = best_m; + *n = best_n; + } + + return best_rate; +} + +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct sun4i_ddc *ddc = hw_to_ddc(hw); + + return sun4i_ddc_calc_divider(rate, *prate, ddc->pre_div, + ddc->m_offset, NULL, NULL); +} + +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sun4i_ddc *ddc = hw_to_ddc(hw); + unsigned int reg; + u8 m, n; + + regmap_field_read(ddc->reg, ®); + m = (reg >> 3) & 0xf; + n = reg & 0x7; + + return (((parent_rate / ddc->pre_div) / 10) >> n) / + (m + ddc->m_offset); +} + +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sun4i_ddc *ddc = hw_to_ddc(hw); + u8 div_m, div_n; + + sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div, + ddc->m_offset, &div_m, &div_n); + + regmap_field_write(ddc->reg, + SUN4I_HDMI_DDC_CLK_M(div_m) | + SUN4I_HDMI_DDC_CLK_N(div_n)); + + return 0; +} + +static const struct clk_ops sun4i_ddc_ops = { + .recalc_rate = sun4i_ddc_recalc_rate, + .round_rate = sun4i_ddc_round_rate, + .set_rate = sun4i_ddc_set_rate, +}; + +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) +{ + struct clk_init_data init; + struct sun4i_ddc *ddc; + const char *parent_name; + + parent_name = __clk_get_name(parent); + if (!parent_name) + return -ENODEV; + + ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL); + if (!ddc) + return -ENOMEM; + + ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->ddc_clk_reg); + if (IS_ERR(ddc->reg)) + return PTR_ERR(ddc->reg); + + init.name = "hdmi-ddc"; + init.ops = &sun4i_ddc_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + + ddc->hdmi = hdmi; + ddc->hw.init = &init; + ddc->pre_div = hdmi->variant->ddc_clk_pre_divider; + ddc->m_offset = hdmi->variant->ddc_clk_m_offset; + + hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); + if (IS_ERR(hdmi->ddc_clk)) + return PTR_ERR(hdmi->ddc_clk); + + return 0; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c new file mode 100644 index 000000000..8ba19a8ca --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -0,0 +1,695 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_encoder.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/iopoll.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sun4i_backend.h" +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_hdmi.h" + +static inline struct sun4i_hdmi * +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_hdmi, + encoder); +} + +static inline struct sun4i_hdmi * +drm_connector_to_sun4i_hdmi(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_hdmi, + connector); +} + +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi, + struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u8 buffer[17]; + int i, ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, false); + if (ret < 0) { + DRM_ERROR("Failed to get infoframes from mode\n"); + return ret; + } + + ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (ret < 0) { + DRM_ERROR("Failed to pack infoframes\n"); + return ret; + } + + for (i = 0; i < sizeof(buffer); i++) + writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i)); + + return 0; +} + +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *mode = &crtc_state->mode; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return -EINVAL; + + return 0; +} + +static void sun4i_hdmi_disable(struct drm_encoder *encoder) +{ + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); + u32 val; + + DRM_DEBUG_DRIVER("Disabling the HDMI Output\n"); + + val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG); + val &= ~SUN4I_HDMI_VID_CTRL_ENABLE; + writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); + + clk_disable_unprepare(hdmi->tmds_clk); +} + +static void sun4i_hdmi_enable(struct drm_encoder *encoder) +{ + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); + u32 val = 0; + + DRM_DEBUG_DRIVER("Enabling the HDMI Output\n"); + + clk_prepare_enable(hdmi->tmds_clk); + + sun4i_hdmi_setup_avi_infoframes(hdmi, mode); + val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); + val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); + writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0)); + + val = SUN4I_HDMI_VID_CTRL_ENABLE; + if (hdmi->hdmi_monitor) + val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE; + + writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); +} + +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); + unsigned int x, y; + u32 val; + + clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000); + clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000); + + /* Set input sync enable */ + writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC, + hdmi->base + SUN4I_HDMI_UNKNOWN_REG); + + /* + * Setup output pad (?) controls + * + * This is done here instead of at probe/bind time because + * the controller seems to toggle some of the bits on its own. + * + * We can't just initialize the register there, we need to + * protect the clock bits that have already been read out and + * cached by the clock framework. + */ + val = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + val &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; + val |= hdmi->variant->pad_ctrl1_init_val; + writel(val, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + val = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + + /* Setup timing registers */ + writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) | + SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay), + hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG); + + x = mode->htotal - mode->hsync_start; + y = mode->vtotal - mode->vsync_start; + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), + hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG); + + x = mode->hsync_start - mode->hdisplay; + y = mode->vsync_start - mode->vdisplay; + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), + hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG); + + x = mode->hsync_end - mode->hsync_start; + y = mode->vsync_end - mode->vsync_start; + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), + hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG); + + val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC; + + writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG); +} + +static enum drm_mode_status sun4i_hdmi_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); + unsigned long rate = mode->clock * 1000; + unsigned long diff = rate / 200; /* +-0.5% allowed by HDMI spec */ + long rounded_rate; + + /* 165 MHz is the typical max pixelclock frequency for HDMI <= 1.2 */ + if (rate > 165000000) + return MODE_CLOCK_HIGH; + rounded_rate = clk_round_rate(hdmi->tmds_clk, rate); + if (rounded_rate > 0 && + max_t(unsigned long, rounded_rate, rate) - + min_t(unsigned long, rounded_rate, rate) < diff) + return MODE_OK; + return MODE_NOCLOCK; +} + +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = { + .atomic_check = sun4i_hdmi_atomic_check, + .disable = sun4i_hdmi_disable, + .enable = sun4i_hdmi_enable, + .mode_set = sun4i_hdmi_mode_set, + .mode_valid = sun4i_hdmi_mode_valid, +}; + +static const struct drm_encoder_funcs sun4i_hdmi_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int sun4i_hdmi_get_modes(struct drm_connector *connector) +{ + struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); + struct edid *edid; + int ret; + + edid = drm_get_edid(connector, hdmi->i2c); + if (!edid) + return 0; + + hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid); + DRM_DEBUG_DRIVER("Monitor is %s monitor\n", + hdmi->hdmi_monitor ? "an HDMI" : "a DVI"); + + drm_connector_update_edid_property(connector, edid); + cec_s_phys_addr_from_edid(hdmi->cec_adap, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = { + .get_modes = sun4i_hdmi_get_modes, +}; + +static enum drm_connector_status +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); + unsigned long reg; + + reg = readl(hdmi->base + SUN4I_HDMI_HPD_REG); + if (!(reg & SUN4I_HDMI_HPD_HIGH)) { + cec_phys_addr_invalidate(hdmi->cec_adap); + return connector_status_disconnected; + } + + return connector_status_connected; +} + +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { + .detect = sun4i_hdmi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +#ifdef CONFIG_DRM_SUN4I_HDMI_CEC +static bool sun4i_hdmi_cec_pin_read(struct cec_adapter *adap) +{ + struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); + + return readl(hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX; +} + +static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap) +{ + struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); + + /* Start driving the CEC pin low */ + writel(SUN4I_HDMI_CEC_ENABLE, hdmi->base + SUN4I_HDMI_CEC); +} + +static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap) +{ + struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); + + /* + * Stop driving the CEC pin, the pull up will take over + * unless another CEC device is driving the pin low. + */ + writel(0, hdmi->base + SUN4I_HDMI_CEC); +} + +static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { + .read = sun4i_hdmi_cec_pin_read, + .low = sun4i_hdmi_cec_pin_low, + .high = sun4i_hdmi_cec_pin_high, +}; +#endif + +#define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0)) +#define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0)) + +/* Only difference from sun5i is AMP is 4 instead of 6 */ +static const struct sun4i_hdmi_variant sun4i_variant = { + .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | + SUN4I_HDMI_PAD_CTRL0_CKEN | + SUN4I_HDMI_PAD_CTRL0_PWENG | + SUN4I_HDMI_PAD_CTRL0_PWEND | + SUN4I_HDMI_PAD_CTRL0_PWENC | + SUN4I_HDMI_PAD_CTRL0_LDODEN | + SUN4I_HDMI_PAD_CTRL0_LDOCEN | + SUN4I_HDMI_PAD_CTRL0_BIASEN, + .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(4) | + SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | + SUN4I_HDMI_PAD_CTRL1_REG_DENCK | + SUN4I_HDMI_PAD_CTRL1_REG_DEN | + SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_EMP_OPT | + SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_AMP_OPT, + .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | + SUN4I_HDMI_PLL_CTRL_CS(7) | + SUN4I_HDMI_PLL_CTRL_CP_S(15) | + SUN4I_HDMI_PLL_CTRL_S(7) | + SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | + SUN4I_HDMI_PLL_CTRL_SDIV2 | + SUN4I_HDMI_PLL_CTRL_LDO2_EN | + SUN4I_HDMI_PLL_CTRL_LDO1_EN | + SUN4I_HDMI_PLL_CTRL_HV_IS_33 | + SUN4I_HDMI_PLL_CTRL_BWS | + SUN4I_HDMI_PLL_CTRL_PLL_EN, + + .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 2, + .ddc_clk_m_offset = 1, + + .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), + .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), + .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), + .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), + .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), + .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), + + .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_has_dir = true, +}; + +static const struct sun4i_hdmi_variant sun5i_variant = { + .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | + SUN4I_HDMI_PAD_CTRL0_CKEN | + SUN4I_HDMI_PAD_CTRL0_PWENG | + SUN4I_HDMI_PAD_CTRL0_PWEND | + SUN4I_HDMI_PAD_CTRL0_PWENC | + SUN4I_HDMI_PAD_CTRL0_LDODEN | + SUN4I_HDMI_PAD_CTRL0_LDOCEN | + SUN4I_HDMI_PAD_CTRL0_BIASEN, + .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | + SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | + SUN4I_HDMI_PAD_CTRL1_REG_DENCK | + SUN4I_HDMI_PAD_CTRL1_REG_DEN | + SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_EMP_OPT | + SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_AMP_OPT, + .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | + SUN4I_HDMI_PLL_CTRL_CS(7) | + SUN4I_HDMI_PLL_CTRL_CP_S(15) | + SUN4I_HDMI_PLL_CTRL_S(7) | + SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | + SUN4I_HDMI_PLL_CTRL_SDIV2 | + SUN4I_HDMI_PLL_CTRL_LDO2_EN | + SUN4I_HDMI_PLL_CTRL_LDO1_EN | + SUN4I_HDMI_PLL_CTRL_HV_IS_33 | + SUN4I_HDMI_PLL_CTRL_BWS | + SUN4I_HDMI_PLL_CTRL_PLL_EN, + + .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 2, + .ddc_clk_m_offset = 1, + + .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), + .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), + .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), + .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), + .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), + .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), + + .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_has_dir = true, +}; + +static const struct sun4i_hdmi_variant sun6i_variant = { + .has_ddc_parent_clk = true, + .has_reset_control = true, + .pad_ctrl0_init_val = 0xff | + SUN4I_HDMI_PAD_CTRL0_TXEN | + SUN4I_HDMI_PAD_CTRL0_CKEN | + SUN4I_HDMI_PAD_CTRL0_PWENG | + SUN4I_HDMI_PAD_CTRL0_PWEND | + SUN4I_HDMI_PAD_CTRL0_PWENC | + SUN4I_HDMI_PAD_CTRL0_LDODEN | + SUN4I_HDMI_PAD_CTRL0_LDOCEN, + .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) | + SUN4I_HDMI_PAD_CTRL1_REG_EMP(4) | + SUN4I_HDMI_PAD_CTRL1_REG_DENCK | + SUN4I_HDMI_PAD_CTRL1_REG_DEN | + SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_EMP_OPT | + SUN4I_HDMI_PAD_CTRL1_PWSDT | + SUN4I_HDMI_PAD_CTRL1_PWSCK | + SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_AMP_OPT | + SUN4I_HDMI_PAD_CTRL1_UNKNOWN, + .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | + SUN4I_HDMI_PLL_CTRL_CS(3) | + SUN4I_HDMI_PLL_CTRL_CP_S(10) | + SUN4I_HDMI_PLL_CTRL_S(4) | + SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | + SUN4I_HDMI_PLL_CTRL_SDIV2 | + SUN4I_HDMI_PLL_CTRL_LDO2_EN | + SUN4I_HDMI_PLL_CTRL_LDO1_EN | + SUN4I_HDMI_PLL_CTRL_HV_IS_33 | + SUN4I_HDMI_PLL_CTRL_PLL_EN, + + .ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 1, + .ddc_clk_m_offset = 2, + + .tmds_clk_div_offset = 1, + + .field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27), + .field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31), + .field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7), + .field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25), + .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6), + .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4), + + .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_thres_incl = true, +}; + +static const struct regmap_config sun4i_hdmi_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x580, +}; + +static int sun4i_hdmi_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_hdmi *hdmi; + struct resource *res; + u32 reg; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + dev_set_drvdata(dev, hdmi); + hdmi->dev = dev; + hdmi->drv = drv; + + hdmi->variant = of_device_get_match_data(dev); + if (!hdmi->variant) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdmi->base)) { + dev_err(dev, "Couldn't map the HDMI encoder registers\n"); + return PTR_ERR(hdmi->base); + } + + if (hdmi->variant->has_reset_control) { + hdmi->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(hdmi->reset)) { + dev_err(dev, "Couldn't get the HDMI reset control\n"); + return PTR_ERR(hdmi->reset); + } + + ret = reset_control_deassert(hdmi->reset); + if (ret) { + dev_err(dev, "Couldn't deassert HDMI reset\n"); + return ret; + } + } + + hdmi->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(hdmi->bus_clk)) { + dev_err(dev, "Couldn't get the HDMI bus clock\n"); + ret = PTR_ERR(hdmi->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(hdmi->bus_clk); + + hdmi->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(hdmi->mod_clk)) { + dev_err(dev, "Couldn't get the HDMI mod clock\n"); + ret = PTR_ERR(hdmi->mod_clk); + goto err_disable_bus_clk; + } + clk_prepare_enable(hdmi->mod_clk); + + hdmi->pll0_clk = devm_clk_get(dev, "pll-0"); + if (IS_ERR(hdmi->pll0_clk)) { + dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n"); + ret = PTR_ERR(hdmi->pll0_clk); + goto err_disable_mod_clk; + } + + hdmi->pll1_clk = devm_clk_get(dev, "pll-1"); + if (IS_ERR(hdmi->pll1_clk)) { + dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n"); + ret = PTR_ERR(hdmi->pll1_clk); + goto err_disable_mod_clk; + } + + hdmi->regmap = devm_regmap_init_mmio(dev, hdmi->base, + &sun4i_hdmi_regmap_config); + if (IS_ERR(hdmi->regmap)) { + dev_err(dev, "Couldn't create HDMI encoder regmap\n"); + ret = PTR_ERR(hdmi->regmap); + goto err_disable_mod_clk; + } + + ret = sun4i_tmds_create(hdmi); + if (ret) { + dev_err(dev, "Couldn't create the TMDS clock\n"); + goto err_disable_mod_clk; + } + + if (hdmi->variant->has_ddc_parent_clk) { + hdmi->ddc_parent_clk = devm_clk_get(dev, "ddc"); + if (IS_ERR(hdmi->ddc_parent_clk)) { + dev_err(dev, "Couldn't get the HDMI DDC clock\n"); + ret = PTR_ERR(hdmi->ddc_parent_clk); + goto err_disable_mod_clk; + } + } else { + hdmi->ddc_parent_clk = hdmi->tmds_clk; + } + + writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG); + + writel(hdmi->variant->pad_ctrl0_init_val, + hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG); + + reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); + reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK; + reg |= hdmi->variant->pll_ctrl_init_val; + writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); + + ret = sun4i_hdmi_i2c_create(dev, hdmi); + if (ret) { + dev_err(dev, "Couldn't create the HDMI I2C adapter\n"); + goto err_disable_mod_clk; + } + + drm_encoder_helper_add(&hdmi->encoder, + &sun4i_hdmi_helper_funcs); + ret = drm_encoder_init(drm, + &hdmi->encoder, + &sun4i_hdmi_funcs, + DRM_MODE_ENCODER_TMDS, + NULL); + if (ret) { + dev_err(dev, "Couldn't initialise the HDMI encoder\n"); + goto err_del_i2c_adapter; + } + + hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, + dev->of_node); + if (!hdmi->encoder.possible_crtcs) { + ret = -EPROBE_DEFER; + goto err_del_i2c_adapter; + } + +#ifdef CONFIG_DRM_SUN4I_HDMI_CEC + hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops, + hdmi, "sun4i", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS | + CEC_CAP_PASSTHROUGH | CEC_CAP_RC); + ret = PTR_ERR_OR_ZERO(hdmi->cec_adap); + if (ret < 0) + goto err_cleanup_connector; + writel(readl(hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX, + hdmi->base + SUN4I_HDMI_CEC); +#endif + + drm_connector_helper_add(&hdmi->connector, + &sun4i_hdmi_connector_helper_funcs); + ret = drm_connector_init(drm, &hdmi->connector, + &sun4i_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + dev_err(dev, + "Couldn't initialise the HDMI connector\n"); + goto err_cleanup_connector; + } + + /* There is no HPD interrupt, so we need to poll the controller */ + hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + ret = cec_register_adapter(hdmi->cec_adap, dev); + if (ret < 0) + goto err_cleanup_connector; + drm_connector_attach_encoder(&hdmi->connector, &hdmi->encoder); + + return 0; + +err_cleanup_connector: + cec_delete_adapter(hdmi->cec_adap); + drm_encoder_cleanup(&hdmi->encoder); +err_del_i2c_adapter: + i2c_del_adapter(hdmi->i2c); +err_disable_mod_clk: + clk_disable_unprepare(hdmi->mod_clk); +err_disable_bus_clk: + clk_disable_unprepare(hdmi->bus_clk); +err_assert_reset: + reset_control_assert(hdmi->reset); + return ret; +} + +static void sun4i_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); + + cec_unregister_adapter(hdmi->cec_adap); + i2c_del_adapter(hdmi->i2c); + clk_disable_unprepare(hdmi->mod_clk); + clk_disable_unprepare(hdmi->bus_clk); +} + +static const struct component_ops sun4i_hdmi_ops = { + .bind = sun4i_hdmi_bind, + .unbind = sun4i_hdmi_unbind, +}; + +static int sun4i_hdmi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_hdmi_ops); +} + +static int sun4i_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_hdmi_ops); + + return 0; +} + +static const struct of_device_id sun4i_hdmi_of_table[] = { + { .compatible = "allwinner,sun4i-a10-hdmi", .data = &sun4i_variant, }, + { .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, }, + { .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_variant, }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table); + +static struct platform_driver sun4i_hdmi_driver = { + .probe = sun4i_hdmi_probe, + .remove = sun4i_hdmi_remove, + .driver = { + .name = "sun4i-hdmi", + .of_match_table = sun4i_hdmi_of_table, + }, +}; +module_platform_driver(sun4i_hdmi_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c new file mode 100644 index 000000000..58e9d37e8 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com> + * Copyright (C) 2017 Jonathan Liu <net147@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> + +#include "sun4i_hdmi.h" + +#define SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK ( \ + SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION | \ + SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW | \ + SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW | \ + SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR | \ + SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR | \ + SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR \ +) + +/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */ +#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX + +static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) +{ + /* + * 1 byte takes 9 clock cycles (8 bits + 1 ACK) = 90 us for 100 kHz + * clock. As clock rate is fixed, just round it up to 100 us. + */ + const unsigned long byte_time_ns = 100; + const u32 mask = SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | + SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; + u32 reg; + /* + * If threshold is inclusive, then the FIFO may only have + * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1. + */ + int read_len = RX_THRESHOLD + + (hdmi->variant->ddc_fifo_thres_incl ? 0 : 1); + + /* + * Limit transfer length by FIFO threshold or FIFO size. + * For TX the threshold is for an empty FIFO. + */ + len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE); + + /* Wait until error, FIFO request bit set or transfer complete */ + if (regmap_field_read_poll_timeout(hdmi->field_ddc_int_status, reg, + reg & mask, len * byte_time_ns, + 100000)) + return -ETIMEDOUT; + + if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) + return -EIO; + + if (read) + readsb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len); + else + writesb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len); + + /* Clear FIFO request bit by forcing a write to that bit */ + regmap_field_force_write(hdmi->field_ddc_int_status, + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST); + + return len; +} + +static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) +{ + int i, len; + u32 reg; + + /* Set FIFO direction */ + if (hdmi->variant->ddc_fifo_has_dir) { + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; + reg |= (msg->flags & I2C_M_RD) ? + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; + writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + } + + /* Clear address register (not cleared by soft reset) */ + regmap_field_write(hdmi->field_ddc_addr_reg, 0); + + /* Set I2C address */ + regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr); + + /* + * Set FIFO RX/TX thresholds and clear FIFO + * + * If threshold is inclusive, we can set the TX threshold to + * 0 instead of 1. + */ + regmap_field_write(hdmi->field_ddc_fifo_tx_thres, + hdmi->variant->ddc_fifo_thres_incl ? 0 : 1); + regmap_field_write(hdmi->field_ddc_fifo_rx_thres, RX_THRESHOLD); + regmap_field_write(hdmi->field_ddc_fifo_clear, 1); + if (regmap_field_read_poll_timeout(hdmi->field_ddc_fifo_clear, + reg, !reg, 100, 2000)) + return -EIO; + + /* Set transfer length */ + regmap_field_write(hdmi->field_ddc_byte_count, msg->len); + + /* Set command */ + regmap_field_write(hdmi->field_ddc_cmd, + msg->flags & I2C_M_RD ? + SUN4I_HDMI_DDC_CMD_IMPLICIT_READ : + SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE); + + /* Clear interrupt status bits by forcing a write */ + regmap_field_force_write(hdmi->field_ddc_int_status, + SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | + SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE); + + /* Start command */ + regmap_field_write(hdmi->field_ddc_start, 1); + + /* Transfer bytes */ + for (i = 0; i < msg->len; i += len) { + len = fifo_transfer(hdmi, msg->buf + i, msg->len - i, + msg->flags & I2C_M_RD); + if (len <= 0) + return len; + } + + /* Wait for command to finish */ + if (regmap_field_read_poll_timeout(hdmi->field_ddc_start, + reg, !reg, 100, 100000)) + return -EIO; + + /* Check for errors */ + regmap_field_read(hdmi->field_ddc_int_status, ®); + if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) || + !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) { + return -EIO; + } + + return 0; +} + +static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct sun4i_hdmi *hdmi = i2c_get_adapdata(adap); + u32 reg; + int err, i, ret = num; + + for (i = 0; i < num; i++) { + if (!msgs[i].len) + return -EINVAL; + if (msgs[i].len > SUN4I_HDMI_DDC_BYTE_COUNT_MAX) + return -EINVAL; + } + + /* DDC clock needs to be enabled for the module to work */ + clk_prepare_enable(hdmi->ddc_clk); + clk_set_rate(hdmi->ddc_clk, 100000); + + /* Reset I2C controller */ + regmap_field_write(hdmi->field_ddc_en, 1); + regmap_field_write(hdmi->field_ddc_reset, 1); + if (regmap_field_read_poll_timeout(hdmi->field_ddc_reset, + reg, !reg, 100, 2000)) { + clk_disable_unprepare(hdmi->ddc_clk); + return -EIO; + } + + regmap_field_write(hdmi->field_ddc_sck_en, 1); + regmap_field_write(hdmi->field_ddc_sda_en, 1); + + for (i = 0; i < num; i++) { + err = xfer_msg(hdmi, &msgs[i]); + if (err) { + ret = err; + break; + } + } + + clk_disable_unprepare(hdmi->ddc_clk); + return ret; +} + +static u32 sun4i_hdmi_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = { + .master_xfer = sun4i_hdmi_i2c_xfer, + .functionality = sun4i_hdmi_i2c_func, +}; + +static int sun4i_hdmi_init_regmap_fields(struct sun4i_hdmi *hdmi) +{ + hdmi->field_ddc_en = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_en); + if (IS_ERR(hdmi->field_ddc_en)) + return PTR_ERR(hdmi->field_ddc_en); + + hdmi->field_ddc_start = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_start); + if (IS_ERR(hdmi->field_ddc_start)) + return PTR_ERR(hdmi->field_ddc_start); + + hdmi->field_ddc_reset = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_reset); + if (IS_ERR(hdmi->field_ddc_reset)) + return PTR_ERR(hdmi->field_ddc_reset); + + hdmi->field_ddc_addr_reg = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_addr_reg); + if (IS_ERR(hdmi->field_ddc_addr_reg)) + return PTR_ERR(hdmi->field_ddc_addr_reg); + + hdmi->field_ddc_slave_addr = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_slave_addr); + if (IS_ERR(hdmi->field_ddc_slave_addr)) + return PTR_ERR(hdmi->field_ddc_slave_addr); + + hdmi->field_ddc_int_mask = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_int_mask); + if (IS_ERR(hdmi->field_ddc_int_mask)) + return PTR_ERR(hdmi->field_ddc_int_mask); + + hdmi->field_ddc_int_status = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_int_status); + if (IS_ERR(hdmi->field_ddc_int_status)) + return PTR_ERR(hdmi->field_ddc_int_status); + + hdmi->field_ddc_fifo_clear = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_fifo_clear); + if (IS_ERR(hdmi->field_ddc_fifo_clear)) + return PTR_ERR(hdmi->field_ddc_fifo_clear); + + hdmi->field_ddc_fifo_rx_thres = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_fifo_rx_thres); + if (IS_ERR(hdmi->field_ddc_fifo_rx_thres)) + return PTR_ERR(hdmi->field_ddc_fifo_rx_thres); + + hdmi->field_ddc_fifo_tx_thres = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_fifo_tx_thres); + if (IS_ERR(hdmi->field_ddc_fifo_tx_thres)) + return PTR_ERR(hdmi->field_ddc_fifo_tx_thres); + + hdmi->field_ddc_byte_count = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_byte_count); + if (IS_ERR(hdmi->field_ddc_byte_count)) + return PTR_ERR(hdmi->field_ddc_byte_count); + + hdmi->field_ddc_cmd = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_cmd); + if (IS_ERR(hdmi->field_ddc_cmd)) + return PTR_ERR(hdmi->field_ddc_cmd); + + hdmi->field_ddc_sda_en = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_sda_en); + if (IS_ERR(hdmi->field_ddc_sda_en)) + return PTR_ERR(hdmi->field_ddc_sda_en); + + hdmi->field_ddc_sck_en = + devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, + hdmi->variant->field_ddc_sck_en); + if (IS_ERR(hdmi->field_ddc_sck_en)) + return PTR_ERR(hdmi->field_ddc_sck_en); + + return 0; +} + +int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi) +{ + struct i2c_adapter *adap; + int ret = 0; + + ret = sun4i_ddc_create(hdmi, hdmi->ddc_parent_clk); + if (ret) + return ret; + + ret = sun4i_hdmi_init_regmap_fields(hdmi); + if (ret) + return ret; + + adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_DDC; + adap->algo = &sun4i_hdmi_i2c_algorithm; + strlcpy(adap->name, "sun4i_hdmi_i2c adapter", sizeof(adap->name)); + i2c_set_adapdata(adap, hdmi); + + ret = i2c_add_adapter(adap); + if (ret) + return ret; + + hdmi->i2c = adap; + + return ret; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c new file mode 100644 index 000000000..a74adec6c --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk-provider.h> + +#include "sun4i_hdmi.h" + +struct sun4i_tmds { + struct clk_hw hw; + struct sun4i_hdmi *hdmi; + + u8 div_offset; +}; + +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) +{ + return container_of(hw, struct sun4i_tmds, hw); +} + + +static unsigned long sun4i_tmds_calc_divider(unsigned long rate, + unsigned long parent_rate, + u8 div_offset, + u8 *div, + bool *half) +{ + unsigned long best_rate = 0; + u8 best_m = 0, m; + bool is_double; + + for (m = div_offset ?: 1; m < (16 + div_offset); m++) { + u8 d; + + for (d = 1; d < 3; d++) { + unsigned long tmp_rate; + + tmp_rate = parent_rate / m / d; + + if (tmp_rate > rate) + continue; + + if (!best_rate || + (rate - tmp_rate) < (rate - best_rate)) { + best_rate = tmp_rate; + best_m = m; + is_double = (d == 2) ? true : false; + } + } + } + + if (div && half) { + *div = best_m; + *half = is_double; + } + + return best_rate; +} + + +static int sun4i_tmds_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct sun4i_tmds *tmds = hw_to_tmds(hw); + struct clk_hw *parent = NULL; + unsigned long best_parent = 0; + unsigned long rate = req->rate; + int best_div = 1, best_half = 1; + int i, j, p; + + /* + * We only consider PLL3, since the TCON is very likely to be + * clocked from it, and to have the same rate than our HDMI + * clock, so we should not need to do anything. + */ + + for (p = 0; p < clk_hw_get_num_parents(hw); p++) { + parent = clk_hw_get_parent_by_index(hw, p); + if (!parent) + continue; + + for (i = 1; i < 3; i++) { + for (j = tmds->div_offset ?: 1; + j < (16 + tmds->div_offset); j++) { + unsigned long ideal = rate * i * j; + unsigned long rounded; + + rounded = clk_hw_round_rate(parent, ideal); + + if (rounded == ideal) { + best_parent = rounded; + best_half = i; + best_div = j; + goto out; + } + + if (!best_parent || + abs(rate - rounded / i / j) < + abs(rate - best_parent / best_half / + best_div)) { + best_parent = rounded; + best_half = i; + best_div = j; + } + } + } + } + + if (!parent) + return -EINVAL; + +out: + req->rate = best_parent / best_half / best_div; + req->best_parent_rate = best_parent; + req->best_parent_hw = parent; + + return 0; +} + +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sun4i_tmds *tmds = hw_to_tmds(hw); + u32 reg; + + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK) + parent_rate /= 2; + + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); + reg = ((reg >> 4) & 0xf) + tmds->div_offset; + if (!reg) + reg = 1; + + return parent_rate / reg; +} + +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sun4i_tmds *tmds = hw_to_tmds(hw); + bool half; + u32 reg; + u8 div; + + sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset, + &div, &half); + + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; + if (half) + reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; + writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); + reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK; + writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset), + tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); + + return 0; +} + +static u8 sun4i_tmds_get_parent(struct clk_hw *hw) +{ + struct sun4i_tmds *tmds = hw_to_tmds(hw); + u32 reg; + + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); + return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >> + SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT); +} + +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index) +{ + struct sun4i_tmds *tmds = hw_to_tmds(hw); + u32 reg; + + if (index > 1) + return -EINVAL; + + reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); + reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK; + writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index), + tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); + + return 0; +} + +static const struct clk_ops sun4i_tmds_ops = { + .determine_rate = sun4i_tmds_determine_rate, + .recalc_rate = sun4i_tmds_recalc_rate, + .set_rate = sun4i_tmds_set_rate, + + .get_parent = sun4i_tmds_get_parent, + .set_parent = sun4i_tmds_set_parent, +}; + +int sun4i_tmds_create(struct sun4i_hdmi *hdmi) +{ + struct clk_init_data init; + struct sun4i_tmds *tmds; + const char *parents[2]; + + parents[0] = __clk_get_name(hdmi->pll0_clk); + if (!parents[0]) + return -ENODEV; + + parents[1] = __clk_get_name(hdmi->pll1_clk); + if (!parents[1]) + return -ENODEV; + + tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL); + if (!tmds) + return -ENOMEM; + + init.name = "hdmi-tmds"; + init.ops = &sun4i_tmds_ops; + init.parent_names = parents; + init.num_parents = 2; + init.flags = CLK_SET_RATE_PARENT; + + tmds->hdmi = hdmi; + tmds->hw.init = &init; + tmds->div_offset = hdmi->variant->tmds_clk_div_offset; + + hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw); + if (IS_ERR(hdmi->tmds_clk)) + return PTR_ERR(hdmi->tmds_clk); + + return 0; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c new file mode 100644 index 000000000..750ad24de --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "sun4i_backend.h" +#include "sun4i_frontend.h" +#include "sun4i_layer.h" +#include "sunxi_engine.h" + +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->alpha = DRM_BLEND_ALPHA_OPAQUE; + 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_zpos(backend, layer->id, plane); + sun4i_backend_layer_enable(backend, layer->id, true); +} + +static const struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { + .atomic_disable = sun4i_backend_layer_atomic_disable, + .atomic_update = sun4i_backend_layer_atomic_update, +}; + +static const struct drm_plane_funcs sun4i_backend_layer_funcs = { + .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 = sun4i_backend_layer_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +static const uint32_t sun4i_backend_layer_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, + DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, +}; + +static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, + struct sun4i_backend *backend, + enum drm_plane_type type) +{ + struct sun4i_layer *layer; + int ret; + + layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); + if (!layer) + return ERR_PTR(-ENOMEM); + + /* possible crtcs are set later */ + ret = drm_universal_plane_init(drm, &layer->plane, 0, + &sun4i_backend_layer_funcs, + 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); + } + + drm_plane_helper_add(&layer->plane, + &sun4i_backend_layer_helper_funcs); + layer->backend = backend; + + drm_plane_create_alpha_property(&layer->plane); + drm_plane_create_zpos_property(&layer->plane, 0, 0, + SUN4I_BACKEND_NUM_LAYERS - 1); + + return layer; +} + +struct drm_plane **sun4i_layers_init(struct drm_device *drm, + struct sunxi_engine *engine) +{ + struct drm_plane **planes; + struct sun4i_backend *backend = engine_to_sun4i_backend(engine); + int i; + + /* 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); + + 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, type); + if (IS_ERR(layer)) { + dev_err(drm->dev, "Couldn't initialize %s plane\n", + i ? "overlay" : "primary"); + return ERR_CAST(layer); + }; + + layer->id = i; + planes[i] = &layer->plane; + }; + + return planes; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h new file mode 100644 index 000000000..36b20265b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_layer.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_LAYER_H_ +#define _SUN4I_LAYER_H_ + +struct sunxi_engine; + +struct sun4i_layer { + struct drm_plane plane; + struct sun4i_drv *drv; + struct sun4i_backend *backend; + 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); + +#endif /* _SUN4I_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c new file mode 100644 index 000000000..af7dcb6da --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Free Electrons + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#include <linux/clk.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include "sun4i_crtc.h" +#include "sun4i_tcon.h" +#include "sun4i_lvds.h" + +struct sun4i_lvds { + struct drm_connector connector; + struct drm_encoder encoder; + + struct sun4i_tcon *tcon; +}; + +static inline struct sun4i_lvds * +drm_connector_to_sun4i_lvds(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_lvds, + connector); +} + +static inline struct sun4i_lvds * +drm_encoder_to_sun4i_lvds(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_lvds, + encoder); +} + +static int sun4i_lvds_get_modes(struct drm_connector *connector) +{ + struct sun4i_lvds *lvds = + drm_connector_to_sun4i_lvds(connector); + struct sun4i_tcon *tcon = lvds->tcon; + + return drm_panel_get_modes(tcon->panel); +} + +static struct drm_connector_helper_funcs sun4i_lvds_con_helper_funcs = { + .get_modes = sun4i_lvds_get_modes, +}; + +static void +sun4i_lvds_connector_destroy(struct drm_connector *connector) +{ + struct sun4i_lvds *lvds = drm_connector_to_sun4i_lvds(connector); + struct sun4i_tcon *tcon = lvds->tcon; + + drm_panel_detach(tcon->panel); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs sun4i_lvds_con_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = sun4i_lvds_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static void sun4i_lvds_encoder_enable(struct drm_encoder *encoder) +{ + struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); + struct sun4i_tcon *tcon = lvds->tcon; + + DRM_DEBUG_DRIVER("Enabling LVDS output\n"); + + if (!IS_ERR(tcon->panel)) { + drm_panel_prepare(tcon->panel); + drm_panel_enable(tcon->panel); + } +} + +static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder) +{ + struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder); + struct sun4i_tcon *tcon = lvds->tcon; + + DRM_DEBUG_DRIVER("Disabling LVDS output\n"); + + if (!IS_ERR(tcon->panel)) { + drm_panel_disable(tcon->panel); + drm_panel_unprepare(tcon->panel); + } +} + +static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = { + .disable = sun4i_lvds_encoder_disable, + .enable = sun4i_lvds_encoder_enable, +}; + +static const struct drm_encoder_funcs sun4i_lvds_enc_funcs = { + .destroy = drm_encoder_cleanup, +}; + +int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) +{ + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct sun4i_lvds *lvds; + int ret; + + lvds = devm_kzalloc(drm->dev, sizeof(*lvds), GFP_KERNEL); + if (!lvds) + return -ENOMEM; + lvds->tcon = tcon; + encoder = &lvds->encoder; + + ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, + &tcon->panel, &bridge); + if (ret) { + dev_info(drm->dev, "No panel or bridge found... LVDS output disabled\n"); + return 0; + } + + drm_encoder_helper_add(&lvds->encoder, + &sun4i_lvds_enc_helper_funcs); + ret = drm_encoder_init(drm, + &lvds->encoder, + &sun4i_lvds_enc_funcs, + DRM_MODE_ENCODER_LVDS, + NULL); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the lvds encoder\n"); + goto err_out; + } + + /* The LVDS encoder can only work with the TCON channel 0 */ + lvds->encoder.possible_crtcs = drm_crtc_mask(&tcon->crtc->crtc); + + if (tcon->panel) { + drm_connector_helper_add(&lvds->connector, + &sun4i_lvds_con_helper_funcs); + ret = drm_connector_init(drm, &lvds->connector, + &sun4i_lvds_con_funcs, + DRM_MODE_CONNECTOR_LVDS); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the lvds connector\n"); + goto err_cleanup_connector; + } + + drm_connector_attach_encoder(&lvds->connector, + &lvds->encoder); + + ret = drm_panel_attach(tcon->panel, &lvds->connector); + if (ret) { + dev_err(drm->dev, "Couldn't attach our panel\n"); + goto err_cleanup_connector; + } + } + + if (bridge) { + ret = drm_bridge_attach(encoder, bridge, NULL); + if (ret) { + dev_err(drm->dev, "Couldn't attach our bridge\n"); + goto err_cleanup_connector; + } + } + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&lvds->encoder); +err_out: + return ret; +} +EXPORT_SYMBOL(sun4i_lvds_init); diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.h b/drivers/gpu/drm/sun4i/sun4i_lvds.h new file mode 100644 index 000000000..f3e90faa3 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Free Electrons + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#ifndef _SUN4I_LVDS_H_ +#define _SUN4I_LVDS_H_ + +int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon); + +#endif /* _SUN4I_LVDS_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c new file mode 100644 index 000000000..bf068da6b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include "sun4i_crtc.h" +#include "sun4i_tcon.h" +#include "sun4i_rgb.h" + +struct sun4i_rgb { + struct drm_connector connector; + struct drm_encoder encoder; + + struct sun4i_tcon *tcon; +}; + +static inline struct sun4i_rgb * +drm_connector_to_sun4i_rgb(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_rgb, + connector); +} + +static inline struct sun4i_rgb * +drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_rgb, + encoder); +} + +static int sun4i_rgb_get_modes(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = + drm_connector_to_sun4i_rgb(connector); + struct sun4i_tcon *tcon = rgb->tcon; + + return drm_panel_get_modes(tcon->panel); +} + +static enum drm_mode_status sun4i_rgb_mode_valid(struct drm_encoder *crtc, + const struct drm_display_mode *mode) +{ + 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; + 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 = 6; + tcon->dclk_max_div = 127; + 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 struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { + .get_modes = sun4i_rgb_get_modes, +}; + +static void +sun4i_rgb_connector_destroy(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); + struct sun4i_tcon *tcon = rgb->tcon; + + drm_panel_detach(tcon->panel); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs sun4i_rgb_con_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = sun4i_rgb_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_tcon *tcon = rgb->tcon; + + DRM_DEBUG_DRIVER("Enabling RGB output\n"); + + if (!IS_ERR(tcon->panel)) { + drm_panel_prepare(tcon->panel); + drm_panel_enable(tcon->panel); + } +} + +static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_tcon *tcon = rgb->tcon; + + DRM_DEBUG_DRIVER("Disabling RGB output\n"); + + if (!IS_ERR(tcon->panel)) { + drm_panel_disable(tcon->panel); + drm_panel_unprepare(tcon->panel); + } +} + +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) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs sun4i_rgb_enc_funcs = { + .destroy = sun4i_rgb_enc_destroy, +}; + +int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon) +{ + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct sun4i_rgb *rgb; + int ret; + + rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); + if (!rgb) + return -ENOMEM; + rgb->tcon = tcon; + encoder = &rgb->encoder; + + ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0, + &tcon->panel, &bridge); + if (ret) { + dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n"); + return 0; + } + + drm_encoder_helper_add(&rgb->encoder, + &sun4i_rgb_enc_helper_funcs); + ret = drm_encoder_init(drm, + &rgb->encoder, + &sun4i_rgb_enc_funcs, + DRM_MODE_ENCODER_NONE, + NULL); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the rgb encoder\n"); + goto err_out; + } + + /* The RGB encoder can only work with the TCON channel 0 */ + rgb->encoder.possible_crtcs = drm_crtc_mask(&tcon->crtc->crtc); + + if (tcon->panel) { + drm_connector_helper_add(&rgb->connector, + &sun4i_rgb_con_helper_funcs); + ret = drm_connector_init(drm, &rgb->connector, + &sun4i_rgb_con_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the rgb connector\n"); + goto err_cleanup_connector; + } + + drm_connector_attach_encoder(&rgb->connector, + &rgb->encoder); + + ret = drm_panel_attach(tcon->panel, &rgb->connector); + if (ret) { + dev_err(drm->dev, "Couldn't attach our panel\n"); + goto err_cleanup_connector; + } + } + + if (bridge) { + ret = drm_bridge_attach(encoder, bridge, NULL); + if (ret) { + dev_err(drm->dev, "Couldn't attach our bridge\n"); + goto err_cleanup_connector; + } + } + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&rgb->encoder); +err_out: + return ret; +} +EXPORT_SYMBOL(sun4i_rgb_init); diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.h b/drivers/gpu/drm/sun4i/sun4i_rgb.h new file mode 100644 index 000000000..40c18f4a6 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_RGB_H_ +#define _SUN4I_RGB_H_ + +int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon); + +#endif /* _SUN4I_RGB_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c new file mode 100644 index 000000000..113c032a2 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -0,0 +1,1349 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder.h> +#include <drm/drm_modes.h> +#include <drm/drm_of.h> + +#include <uapi/drm/drm_mode.h> + +#include <linux/component.h> +#include <linux/ioport.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sun4i_crtc.h" +#include "sun4i_dotclock.h" +#include "sun4i_drv.h" +#include "sun4i_lvds.h" +#include "sun4i_rgb.h" +#include "sun4i_tcon.h" +#include "sun6i_mipi_dsi.h" +#include "sunxi_engine.h" + +static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder) +{ + struct drm_connector *connector; + struct drm_connector_list_iter iter; + + drm_connector_list_iter_begin(encoder->dev, &iter); + drm_for_each_connector_iter(connector, &iter) + if (connector->encoder == encoder) { + drm_connector_list_iter_end(&iter); + return connector; + } + drm_connector_list_iter_end(&iter); + + return NULL; +} + +static int sun4i_tcon_get_pixel_depth(const struct drm_encoder *encoder) +{ + struct drm_connector *connector; + struct drm_display_info *info; + + connector = sun4i_tcon_get_connector(encoder); + if (!connector) + return -EINVAL; + + info = &connector->display_info; + if (info->num_bus_formats != 1) + return -EINVAL; + + switch (info->bus_formats[0]) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + return 18; + + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + return 24; + } + + return -EINVAL; +} + +static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, + bool enabled) +{ + struct clk *clk; + + 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); + clk = tcon->dclk; + break; + case 1: + WARN_ON(!tcon->quirks->has_channel_1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_TCON_ENABLE, + enabled ? SUN4I_TCON1_CTL_TCON_ENABLE : 0); + clk = tcon->sclk1; + break; + default: + DRM_WARN("Unknown channel... doing nothing\n"); + return; + } + + if (enabled) { + clk_prepare_enable(clk); + clk_rate_exclusive_get(clk); + } else { + clk_rate_exclusive_put(clk); + clk_disable_unprepare(clk); + } +} + +static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + bool enabled) +{ + if (enabled) { + u8 val; + + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, + SUN4I_TCON0_LVDS_IF_EN, + SUN4I_TCON0_LVDS_IF_EN); + + /* + * As their name suggest, these values only apply to the A31 + * and later SoCs. We'll have to rework this when merging + * support for the older SoCs. + */ + regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, + SUN6I_TCON0_LVDS_ANA0_C(2) | + SUN6I_TCON0_LVDS_ANA0_V(3) | + SUN6I_TCON0_LVDS_ANA0_PD(2) | + SUN6I_TCON0_LVDS_ANA0_EN_LDO); + udelay(2); + + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, + SUN6I_TCON0_LVDS_ANA0_EN_MB, + SUN6I_TCON0_LVDS_ANA0_EN_MB); + udelay(2); + + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, + SUN6I_TCON0_LVDS_ANA0_EN_DRVC, + SUN6I_TCON0_LVDS_ANA0_EN_DRVC); + + if (sun4i_tcon_get_pixel_depth(encoder) == 18) + val = 7; + else + val = 0xf; + + regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG, + SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf), + SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val)); + } else { + regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, + SUN4I_TCON0_LVDS_IF_EN, 0); + } +} + +void sun4i_tcon_set_status(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + bool enabled) +{ + bool is_lvds = false; + int channel; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_LVDS: + is_lvds = true; + /* Fallthrough */ + case DRM_MODE_ENCODER_DSI: + case DRM_MODE_ENCODER_NONE: + channel = 0; + break; + case DRM_MODE_ENCODER_TMDS: + case DRM_MODE_ENCODER_TVDAC: + channel = 1; + break; + default: + DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n"); + return; + } + + if (is_lvds && !enabled) + sun4i_tcon_lvds_set_status(tcon, encoder, false); + + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE, + enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0); + + if (is_lvds && enabled) + sun4i_tcon_lvds_set_status(tcon, encoder, true); + + sun4i_tcon_channel_set_status(tcon, channel, enabled); +} + +void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) +{ + u32 mask, val = 0; + + DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis"); + + mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | + SUN4I_TCON_GINT0_VBLANK_ENABLE(1) | + SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE; + + if (enable) + val = mask; + + regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val); +} +EXPORT_SYMBOL(sun4i_tcon_enable_vblank); + +/* + * This function is a helper for TCON output muxing. The TCON output + * muxing control register in earlier SoCs (without the TCON TOP block) + * are located in TCON0. This helper returns a pointer to TCON0's + * sun4i_tcon structure, or NULL if not found. + */ +static struct sun4i_tcon *sun4i_get_tcon0(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon; + + list_for_each_entry(tcon, &drv->tcon_list, list) + if (tcon->id == 0) + return tcon; + + dev_warn(drm->dev, + "TCON0 not found, display output muxing may not work\n"); + + return NULL; +} + +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, + const struct drm_encoder *encoder) +{ + int ret = -ENOTSUPP; + + if (tcon->quirks->set_mux) + ret = tcon->quirks->set_mux(tcon, encoder); + + DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n", + encoder->name, encoder->crtc->name, ret); +} + +static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, + int channel) +{ + int delay = mode->vtotal - mode->vdisplay; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + delay /= 2; + + if (channel == 1) + delay -= 2; + + delay = min(delay, 30); + + DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay); + + return delay; +} + +static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) +{ + /* Configure the dot clock */ + clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + + /* Set the resolution */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, + SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); +} + +static void sun4i_tcon0_mode_set_cpu(struct sun4i_tcon *tcon, + struct mipi_dsi_device *device, + const struct drm_display_mode *mode) +{ + u8 bpp = mipi_dsi_pixel_format_to_bpp(device->format); + u8 lanes = device->lanes; + u32 block_space, start_delay; + u32 tcon_div; + + tcon->dclk_min_div = 4; + tcon->dclk_max_div = 127; + + sun4i_tcon0_mode_set_common(tcon, mode); + + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_IF_MASK, + SUN4I_TCON0_CTL_IF_8080); + + regmap_write(tcon->regs, SUN4I_TCON_ECC_FIFO_REG, + SUN4I_TCON_ECC_FIFO_EN); + + regmap_write(tcon->regs, SUN4I_TCON0_CPU_IF_REG, + SUN4I_TCON0_CPU_IF_MODE_DSI | + SUN4I_TCON0_CPU_IF_TRI_FIFO_FLUSH | + SUN4I_TCON0_CPU_IF_TRI_FIFO_EN | + SUN4I_TCON0_CPU_IF_TRI_EN); + + /* + * This looks suspicious, but it works... + * + * The datasheet says that this should be set higher than 20 * + * pixel cycle, but it's not clear what a pixel cycle is. + */ + regmap_read(tcon->regs, SUN4I_TCON0_DCLK_REG, &tcon_div); + tcon_div &= GENMASK(6, 0); + block_space = mode->htotal * bpp / (tcon_div * lanes); + block_space -= mode->hdisplay + 40; + + regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI0_REG, + SUN4I_TCON0_CPU_TRI0_BLOCK_SPACE(block_space) | + SUN4I_TCON0_CPU_TRI0_BLOCK_SIZE(mode->hdisplay)); + + regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI1_REG, + SUN4I_TCON0_CPU_TRI1_BLOCK_NUM(mode->vdisplay)); + + start_delay = (mode->crtc_vtotal - mode->crtc_vdisplay - 10 - 1); + start_delay = start_delay * mode->crtc_htotal * 149; + start_delay = start_delay / (mode->crtc_clock / 1000) / 8; + regmap_write(tcon->regs, SUN4I_TCON0_CPU_TRI2_REG, + SUN4I_TCON0_CPU_TRI2_TRANS_START_SET(10) | + SUN4I_TCON0_CPU_TRI2_START_DELAY(start_delay)); + + /* + * The Allwinner BSP has a comment that the period should be + * the display clock * 15, but uses an hardcoded 3000... + */ + regmap_write(tcon->regs, SUN4I_TCON_SAFE_PERIOD_REG, + SUN4I_TCON_SAFE_PERIOD_NUM(3000) | + SUN4I_TCON_SAFE_PERIOD_MODE(3)); + + /* Enable the output on the pins */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, + 0xe0000000); +} + +static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + unsigned int bp; + 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); + + /* Adjust clock delay */ + clk_delay = sun4i_tcon_get_clk_delay(mode, 0); + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_CLK_DELAY_MASK, + SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); + + /* + * This is called a backporch in the register documentation, + * but it really is the back porch + hsync + */ + bp = mode->crtc_htotal - mode->crtc_hsync_start; + DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", + mode->crtc_htotal, bp); + + /* Set horizontal display timings */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, + SUN4I_TCON0_BASIC1_H_TOTAL(mode->htotal) | + SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); + + /* + * This is called a backporch in the register documentation, + * but it really is the back porch + hsync + */ + bp = mode->crtc_vtotal - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", + mode->crtc_vtotal, bp); + + /* Set vertical display timings */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, + SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | + SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); + + reg = SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 | + SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL | + SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL; + if (sun4i_tcon_get_pixel_depth(encoder) == 24) + reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS; + else + reg |= SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS; + + regmap_write(tcon->regs, SUN4I_TCON0_LVDS_IF_REG, reg); + + /* Setup the polarity of the various signals */ + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; + + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; + + regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, val); + + /* Map output pins to channel 0 */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_IOMAP_MASK, + SUN4I_TCON_GCTL_IOMAP_TCON0); + + /* Enable the output on the pins */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0xe0000000); +} + +static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) +{ + unsigned int bp, hsync, vsync; + u8 clk_delay; + u32 val = 0; + + WARN_ON(!tcon->quirks->has_channel_0); + + tcon->dclk_min_div = tcon->quirks->dclk_min_div; + tcon->dclk_max_div = 127; + sun4i_tcon0_mode_set_common(tcon, mode); + + /* Adjust clock delay */ + clk_delay = sun4i_tcon_get_clk_delay(mode, 0); + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_CLK_DELAY_MASK, + SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); + + /* + * This is called a backporch in the register documentation, + * but it really is the back porch + hsync + */ + bp = mode->crtc_htotal - mode->crtc_hsync_start; + DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", + mode->crtc_htotal, bp); + + /* Set horizontal display timings */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, + SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | + SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); + + /* + * This is called a backporch in the register documentation, + * but it really is the back porch + hsync + */ + bp = mode->crtc_vtotal - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", + mode->crtc_vtotal, bp); + + /* Set vertical display timings */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, + SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | + SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); + + /* Set Hsync and Vsync length */ + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); + regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG, + SUN4I_TCON0_BASIC3_V_SYNC(vsync) | + SUN4I_TCON0_BASIC3_H_SYNC(hsync)); + + /* Setup the polarity of the various signals */ + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; + + regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, + SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, + val); + + /* Map output pins to channel 0 */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_IOMAP_MASK, + SUN4I_TCON_GCTL_IOMAP_TCON0); + + /* Enable the output on the pins */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); +} + +static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) +{ + unsigned int bp, hsync, vsync, vtotal; + u8 clk_delay; + u32 val; + + WARN_ON(!tcon->quirks->has_channel_1); + + /* Configure the dot clock */ + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); + + /* Adjust clock delay */ + clk_delay = sun4i_tcon_get_clk_delay(mode, 1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_CLK_DELAY_MASK, + SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); + + /* Set interlaced mode */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; + else + val = 0; + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_INTERLACE_ENABLE, + val); + + /* Set the input resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG, + SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); + + /* Set the upscaling resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, + SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); + + /* Set the output resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, + SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); + + /* Set horizontal display timings */ + bp = mode->crtc_htotal - mode->crtc_hsync_start; + DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", + mode->htotal, bp); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, + SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | + SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); + + bp = mode->crtc_vtotal - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", + mode->crtc_vtotal, bp); + + /* + * The vertical resolution needs to be doubled in all + * cases. We could use crtc_vtotal and always multiply by two, + * but that leads to a rounding error in interlace when vtotal + * is odd. + * + * This happens with TV's PAL for example, where vtotal will + * be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be + * 624, which apparently confuses the hardware. + * + * To work around this, we will always use vtotal, and + * multiply by two only if we're not in interlace. + */ + vtotal = mode->vtotal; + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) + vtotal = vtotal * 2; + + /* Set vertical display timings */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, + SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) | + SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); + + /* Set Hsync and Vsync length */ + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, + SUN4I_TCON1_BASIC5_V_SYNC(vsync) | + SUN4I_TCON1_BASIC5_H_SYNC(hsync)); + + /* Map output pins to channel 1 */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_IOMAP_MASK, + SUN4I_TCON_GCTL_IOMAP_TCON1); +} + +void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + struct sun6i_dsi *dsi; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_DSI: + /* + * This is not really elegant, but it's the "cleaner" + * way I could think of... + */ + dsi = encoder_to_sun6i_dsi(encoder); + sun4i_tcon0_mode_set_cpu(tcon, dsi->device, mode); + break; + case DRM_MODE_ENCODER_LVDS: + sun4i_tcon0_mode_set_lvds(tcon, encoder, mode); + break; + case DRM_MODE_ENCODER_NONE: + sun4i_tcon0_mode_set_rgb(tcon, mode); + sun4i_tcon_set_mux(tcon, 0, encoder); + break; + case DRM_MODE_ENCODER_TVDAC: + case DRM_MODE_ENCODER_TMDS: + sun4i_tcon1_mode_set(tcon, mode); + sun4i_tcon_set_mux(tcon, 1, encoder); + break; + default: + DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n"); + } +} +EXPORT_SYMBOL(sun4i_tcon_mode_set); + +static void sun4i_tcon_finish_page_flip(struct drm_device *dev, + struct sun4i_crtc *scrtc) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (scrtc->event) { + drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event); + drm_crtc_vblank_put(&scrtc->crtc); + scrtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +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); + + if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | + SUN4I_TCON_GINT0_VBLANK_INT(1) | + SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT))) + return IRQ_NONE; + + drm_crtc_handle_vblank(&scrtc->crtc); + sun4i_tcon_finish_page_flip(drm, scrtc); + + /* Acknowledge the interrupt */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, + SUN4I_TCON_GINT0_VBLANK_INT(0) | + SUN4I_TCON_GINT0_VBLANK_INT(1) | + SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT, + 0); + + if (engine->ops->vblank_quirk) + engine->ops->vblank_quirk(engine); + + return IRQ_HANDLED; +} + +static int sun4i_tcon_init_clocks(struct device *dev, + struct sun4i_tcon *tcon) +{ + tcon->clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(tcon->clk)) { + dev_err(dev, "Couldn't get the TCON bus clock\n"); + return PTR_ERR(tcon->clk); + } + clk_prepare_enable(tcon->clk); + + 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); + } + } + clk_prepare_enable(tcon->sclk0); + + if (tcon->quirks->has_channel_1) { + tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); + if (IS_ERR(tcon->sclk1)) { + dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); + return PTR_ERR(tcon->sclk1); + } + } + + return 0; +} + +static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) +{ + clk_disable_unprepare(tcon->sclk0); + clk_disable_unprepare(tcon->clk); +} + +static int sun4i_tcon_init_irq(struct device *dev, + struct sun4i_tcon *tcon) +{ + struct platform_device *pdev = to_platform_device(dev); + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); + return irq; + } + + ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, + dev_name(dev), tcon); + if (ret) { + dev_err(dev, "Couldn't request the IRQ\n"); + return ret; + } + + return 0; +} + +static struct regmap_config sun4i_tcon_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x800, +}; + +static int sun4i_tcon_init_regmap(struct device *dev, + struct sun4i_tcon *tcon) +{ + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + void __iomem *regs; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + tcon->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_tcon_regmap_config); + if (IS_ERR(tcon->regs)) { + dev_err(dev, "Couldn't create the TCON regmap\n"); + return PTR_ERR(tcon->regs); + } + + /* Make sure the TCON is disabled and all IRQs are off */ + regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); + regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); + regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0); + + /* Disable IO lines and set them to tristate */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); + regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); + + return 0; +} + +/* + * On SoCs with the old display pipeline design (Display Engine 1.0), + * the TCON is always tied to just one backend. Hence we can traverse + * the of_graph upwards to find the backend our tcon is connected to, + * and take its ID as our own. + * + * We can either identify backends from their compatible strings, which + * means maintaining a large list of them. Or, since the backend is + * registered and binded before the TCON, we can just go through the + * list of registered backends and compare the device node. + * + * As the structures now store engines instead of backends, here this + * function in fact searches the corresponding engine, and the ID is + * requested via the get_id function of the engine. + */ +static struct sunxi_engine * +sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, + struct device_node *node, + u32 port_id) +{ + struct device_node *port, *ep, *remote; + struct sunxi_engine *engine = ERR_PTR(-EINVAL); + u32 reg = 0; + + port = of_graph_get_port_by_id(node, port_id); + if (!port) + return ERR_PTR(-EINVAL); + + /* + * This only works if there is only one path from the TCON + * to any display engine. Otherwise the probe order of the + * TCONs and display engines is not guaranteed. They may + * either bind to the wrong one, or worse, bind to the same + * one if additional checks are not done. + * + * Bail out if there are multiple input connections. + */ + if (of_get_available_child_count(port) != 1) + goto out_put_port; + + /* Get the first connection without specifying an ID */ + ep = of_get_next_available_child(port, NULL); + if (!ep) + goto out_put_port; + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) + goto out_put_ep; + + /* does this node match any registered engines? */ + list_for_each_entry(engine, &drv->engine_list, list) + if (remote == engine->node) + goto out_put_remote; + + /* + * According to device tree binding input ports have even id + * number and output ports have odd id. Since component with + * more than one input and one output (TCON TOP) exits, correct + * remote input id has to be calculated by subtracting 1 from + * remote output id. If this for some reason can't be done, 0 + * is used as input port id. + */ + of_node_put(port); + port = of_graph_get_remote_port(ep); + if (!of_property_read_u32(port, "reg", ®) && reg > 0) + reg -= 1; + + /* keep looking through upstream ports */ + engine = sun4i_tcon_find_engine_traverse(drv, remote, reg); + +out_put_remote: + of_node_put(remote); +out_put_ep: + of_node_put(ep); +out_put_port: + of_node_put(port); + + return engine; +} + +/* + * The device tree binding says that the remote endpoint ID of any + * connection between components, up to and including the TCON, of + * the display pipeline should be equal to the actual ID of the local + * component. Thus we can look at any one of the input connections of + * the TCONs, and use that connection's remote endpoint ID as our own. + * + * Since the user of this function already finds the input port, + * the port is passed in directly without further checks. + */ +static int sun4i_tcon_of_get_id_from_port(struct device_node *port) +{ + struct device_node *ep; + int ret = -EINVAL; + + /* try finding an upstream endpoint */ + for_each_available_child_of_node(port, ep) { + struct device_node *remote; + u32 reg; + + remote = of_graph_get_remote_endpoint(ep); + if (!remote) + continue; + + ret = of_property_read_u32(remote, "reg", ®); + if (ret) + continue; + + ret = reg; + } + + return ret; +} + +/* + * Once we know the TCON's id, we can look through the list of + * engines to find a matching one. We assume all engines have + * been probed and added to the list. + */ +static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv, + int id) +{ + struct sunxi_engine *engine; + + list_for_each_entry(engine, &drv->engine_list, list) + if (engine->id == id) + return engine; + + return ERR_PTR(-EINVAL); +} + +/* + * On SoCs with the old display pipeline design (Display Engine 1.0), + * we assumed the TCON was always tied to just one backend. However + * this proved not to be the case. On the A31, the TCON can select + * either backend as its source. On the A20 (and likely on the A10), + * the backend can choose which TCON to output to. + * + * The device tree binding says that the remote endpoint ID of any + * connection between components, up to and including the TCON, of + * the display pipeline should be equal to the actual ID of the local + * component. Thus we should be able to look at any one of the input + * connections of the TCONs, and use that connection's remote endpoint + * ID as our own. + * + * However the connections between the backend and TCON were assumed + * to be always singular, and their endpoit IDs were all incorrectly + * set to 0. This means for these old device trees, we cannot just look + * up the remote endpoint ID of a TCON input endpoint. TCON1 would be + * incorrectly identified as TCON0. + * + * This function first checks if the TCON node has 2 input endpoints. + * If so, then the device tree is a corrected version, and it will use + * sun4i_tcon_of_get_id() and sun4i_tcon_get_engine_by_id() from above + * to fetch the ID and engine directly. If not, then it is likely an + * old device trees, where the endpoint IDs were incorrect, but did not + * have endpoint connections between the backend and TCON across + * different display pipelines. It will fall back to the old method of + * traversing the of_graph to try and find a matching engine by device + * node. + * + * In the case of single display pipeline device trees, either method + * works. + */ +static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, + struct device_node *node) +{ + struct device_node *port; + struct sunxi_engine *engine; + + port = of_graph_get_port_by_id(node, 0); + if (!port) + return ERR_PTR(-EINVAL); + + /* + * Is this a corrected device tree with cross pipeline + * connections between the backend and TCON? + */ + if (of_get_child_count(port) > 1) { + /* Get our ID directly from an upstream endpoint */ + int id = sun4i_tcon_of_get_id_from_port(port); + + /* Get our engine by matching our ID */ + engine = sun4i_tcon_get_engine_by_id(drv, id); + + of_node_put(port); + return engine; + } + + /* Fallback to old method by traversing input endpoints */ + of_node_put(port); + return sun4i_tcon_find_engine_traverse(drv, node, 0); +} + +static int sun4i_tcon_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + 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; + + engine = sun4i_tcon_find_engine(drv, dev->of_node); + if (IS_ERR(engine)) { + dev_err(dev, "Couldn't find matching engine\n"); + return -EPROBE_DEFER; + } + + tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); + if (!tcon) + return -ENOMEM; + dev_set_drvdata(dev, tcon); + tcon->drm = drm; + tcon->dev = dev; + tcon->id = engine->id; + tcon->quirks = of_device_get_match_data(dev); + + tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); + if (IS_ERR(tcon->lcd_rst)) { + dev_err(dev, "Couldn't get our reset line\n"); + 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) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + if (tcon->quirks->supports_lvds) { + /* + * This can only be made optional since we've had DT + * nodes without the LVDS reset properties. + * + * If the property is missing, just disable LVDS, and + * print a warning. + */ + tcon->lvds_rst = devm_reset_control_get_optional(dev, "lvds"); + if (IS_ERR(tcon->lvds_rst)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(tcon->lvds_rst); + } else if (tcon->lvds_rst) { + has_lvds_rst = true; + reset_control_reset(tcon->lvds_rst); + } else { + has_lvds_rst = false; + } + + /* + * This can only be made optional since we've had DT + * nodes without the LVDS reset properties. + * + * If the property is missing, just disable LVDS, and + * print a warning. + */ + if (tcon->quirks->has_lvds_alt) { + tcon->lvds_pll = devm_clk_get(dev, "lvds-alt"); + if (IS_ERR(tcon->lvds_pll)) { + if (PTR_ERR(tcon->lvds_pll) == -ENOENT) { + has_lvds_alt = false; + } else { + dev_err(dev, "Couldn't get the LVDS PLL\n"); + return PTR_ERR(tcon->lvds_pll); + } + } else { + has_lvds_alt = true; + } + } + + if (!has_lvds_rst || + (tcon->quirks->has_lvds_alt && !has_lvds_alt)) { + dev_warn(dev, "Missing LVDS properties, Please upgrade your DT\n"); + dev_warn(dev, "LVDS output disabled\n"); + can_lvds = false; + } else { + can_lvds = true; + } + } else { + can_lvds = false; + } + + ret = sun4i_tcon_init_clocks(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON clocks\n"); + goto err_assert_reset; + } + + ret = sun4i_tcon_init_regmap(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON regmap\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); + if (ret) { + dev_err(dev, "Couldn't init our TCON interrupts\n"); + goto err_free_dotclock; + } + + tcon->crtc = sun4i_crtc_init(drm, engine, tcon); + if (IS_ERR(tcon->crtc)) { + dev_err(dev, "Couldn't create our CRTC\n"); + ret = PTR_ERR(tcon->crtc); + goto err_free_dotclock; + } + + if (tcon->quirks->has_channel_0) { + /* + * If we have an LVDS panel connected to the TCON, we should + * just probe the LVDS connector. Otherwise, just probe RGB as + * we used to. + */ + remote = of_graph_get_remote_node(dev->of_node, 1, 0); + if (of_device_is_compatible(remote, "panel-lvds")) + if (can_lvds) + ret = sun4i_lvds_init(drm, tcon); + else + ret = -EINVAL; + else + ret = sun4i_rgb_init(drm, tcon); + of_node_put(remote); + + if (ret < 0) + goto err_free_dotclock; + } + + if (tcon->quirks->needs_de_be_mux) { + /* + * We assume there is no dynamic muxing of backends + * and TCONs, so we select the backend with same ID. + * + * While dynamic selection might be interesting, since + * the CRTC is tied to the TCON, while the layers are + * tied to the backends, this means, we will need to + * switch between groups of layers. There might not be + * a way to represent this constraint in DRM. + */ + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_SRC_SEL_MASK, + tcon->id); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_SRC_SEL_MASK, + tcon->id); + } + + list_add_tail(&tcon->list, &drv->tcon_list); + + return 0; + +err_free_dotclock: + if (tcon->quirks->has_channel_0) + sun4i_dclk_free(tcon); +err_free_clocks: + sun4i_tcon_free_clocks(tcon); +err_assert_reset: + reset_control_assert(tcon->lcd_rst); + return ret; +} + +static void sun4i_tcon_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_tcon *tcon = dev_get_drvdata(dev); + + list_del(&tcon->list); + if (tcon->quirks->has_channel_0) + sun4i_dclk_free(tcon); + sun4i_tcon_free_clocks(tcon); +} + +static const struct component_ops sun4i_tcon_ops = { + .bind = sun4i_tcon_bind, + .unbind = sun4i_tcon_unbind, +}; + +static int sun4i_tcon_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + const struct sun4i_tcon_quirks *quirks; + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + quirks = of_device_get_match_data(&pdev->dev); + + /* panels and bridges are present only on TCONs with channel 0 */ + if (quirks->has_channel_0) { + ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); + if (ret == -EPROBE_DEFER) + return ret; + } + + return component_add(&pdev->dev, &sun4i_tcon_ops); +} + +static int sun4i_tcon_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_tcon_ops); + + return 0; +} + +/* platform specific TCON muxing callbacks */ +static int sun4i_a10_tcon_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev); + u32 shift; + + if (!tcon0) + return -EINVAL; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_TMDS: + /* HDMI */ + shift = 8; + break; + default: + return -EINVAL; + } + + regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, + 0x3 << shift, tcon->id << shift); + + return 0; +} + +static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + u32 val; + + if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) + val = 1; + else + val = 0; + + /* + * FIXME: Undocumented bits + */ + return regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); +} + +static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev); + u32 shift; + + if (!tcon0) + return -EINVAL; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_TMDS: + /* HDMI */ + shift = 8; + break; + default: + /* TODO A31 has MIPI DSI but A31s does not */ + return -EINVAL; + } + + regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, + 0x3 << shift, tcon->id << shift); + + return 0; +} + +static const struct sun4i_tcon_quirks sun4i_a10_quirks = { + .has_channel_0 = true, + .has_channel_1 = true, + .dclk_min_div = 4, + .set_mux = sun4i_a10_tcon_set_mux, +}; + +static const struct sun4i_tcon_quirks sun5i_a13_quirks = { + .has_channel_0 = true, + .has_channel_1 = true, + .dclk_min_div = 4, + .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, + .dclk_min_div = 1, + .set_mux = sun6i_tcon_set_mux, +}; + +static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { + .has_channel_0 = true, + .has_channel_1 = true, + .needs_de_be_mux = true, + .dclk_min_div = 1, +}; + +static const struct sun4i_tcon_quirks sun7i_a20_quirks = { + .has_channel_0 = true, + .has_channel_1 = true, + .dclk_min_div = 4, + /* 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, + .dclk_min_div = 1, +}; + +static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = { + .supports_lvds = true, + .has_channel_0 = true, + .dclk_min_div = 1, +}; + +static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = { + .has_channel_1 = true, +}; + +static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { + .has_channel_0 = true, + .dclk_min_div = 1, +}; + +static const struct sun4i_tcon_quirks sun9i_a80_tcon_lcd_quirks = { + .has_channel_0 = true, + .needs_edp_reset = true, + .dclk_min_div = 1, +}; + +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 */ +const struct of_device_id sun4i_tcon_of_table[] = { + { .compatible = "allwinner,sun4i-a10-tcon", .data = &sun4i_a10_quirks }, + { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, + { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, + { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, + { .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); +EXPORT_SYMBOL(sun4i_tcon_of_table); + +static struct platform_driver sun4i_tcon_platform_driver = { + .probe = sun4i_tcon_probe, + .remove = sun4i_tcon_remove, + .driver = { + .name = "sun4i-tcon", + .of_match_table = sun4i_tcon_of_table, + }, +}; +module_platform_driver(sun4i_tcon_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h new file mode 100644 index 000000000..e05db6b19 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Boris Brezillon <boris.brezillon@free-electrons.com> + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef __SUN4I_TCON_H__ +#define __SUN4I_TCON_H__ + +#include <drm/drm_crtc.h> + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/reset.h> + +#define SUN4I_TCON_GCTL_REG 0x0 +#define SUN4I_TCON_GCTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON_GCTL_IOMAP_MASK BIT(0) +#define SUN4I_TCON_GCTL_IOMAP_TCON1 (1 << 0) +#define SUN4I_TCON_GCTL_IOMAP_TCON0 (0 << 0) + +#define SUN4I_TCON_GINT0_REG 0x4 +#define SUN4I_TCON_GINT0_VBLANK_ENABLE(pipe) BIT(31 - (pipe)) +#define SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE BIT(27) +#define SUN4I_TCON_GINT0_TCON0_TRI_COUNTER_ENABLE BIT(26) +#define SUN4I_TCON_GINT0_VBLANK_INT(pipe) BIT(15 - (pipe)) +#define SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT BIT(11) +#define SUN4I_TCON_GINT0_TCON0_TRI_COUNTER_INT BIT(10) + +#define SUN4I_TCON_GINT1_REG 0x8 + +#define SUN4I_TCON_FRM_CTL_REG 0x10 +#define SUN4I_TCON_FRM_CTL_EN BIT(31) + +#define SUN4I_TCON_FRM_SEED_PR_REG 0x14 +#define SUN4I_TCON_FRM_SEED_PG_REG 0x18 +#define SUN4I_TCON_FRM_SEED_PB_REG 0x1c +#define SUN4I_TCON_FRM_SEED_LR_REG 0x20 +#define SUN4I_TCON_FRM_SEED_LG_REG 0x24 +#define SUN4I_TCON_FRM_SEED_LB_REG 0x28 +#define SUN4I_TCON_FRM_TBL0_REG 0x2c +#define SUN4I_TCON_FRM_TBL1_REG 0x30 +#define SUN4I_TCON_FRM_TBL2_REG 0x34 +#define SUN4I_TCON_FRM_TBL3_REG 0x38 + +#define SUN4I_TCON0_CTL_REG 0x40 +#define SUN4I_TCON0_CTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON0_CTL_IF_MASK GENMASK(25, 24) +#define SUN4I_TCON0_CTL_IF_8080 (1 << 24) +#define SUN4I_TCON0_CTL_CLK_DELAY_MASK GENMASK(8, 4) +#define SUN4I_TCON0_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK) +#define SUN4I_TCON0_CTL_SRC_SEL_MASK GENMASK(2, 0) + +#define SUN4I_TCON0_DCLK_REG 0x44 +#define SUN4I_TCON0_DCLK_GATE_BIT (31) +#define SUN4I_TCON0_DCLK_DIV_SHIFT (0) +#define SUN4I_TCON0_DCLK_DIV_WIDTH (7) + +#define SUN4I_TCON0_BASIC0_REG 0x48 +#define SUN4I_TCON0_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON0_BASIC0_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC1_REG 0x4c +#define SUN4I_TCON0_BASIC1_H_TOTAL(total) ((((total) - 1) & 0x1fff) << 16) +#define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC2_REG 0x50 +#define SUN4I_TCON0_BASIC2_V_TOTAL(total) (((total) & 0x1fff) << 16) +#define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC3_REG 0x54 +#define SUN4I_TCON0_BASIC3_H_SYNC(width) ((((width) - 1) & 0x7ff) << 16) +#define SUN4I_TCON0_BASIC3_V_SYNC(height) (((height) - 1) & 0x7ff) + +#define SUN4I_TCON0_HV_IF_REG 0x58 + +#define SUN4I_TCON0_CPU_IF_REG 0x60 +#define SUN4I_TCON0_CPU_IF_MODE_MASK GENMASK(31, 28) +#define SUN4I_TCON0_CPU_IF_MODE_DSI (1 << 28) +#define SUN4I_TCON0_CPU_IF_TRI_FIFO_FLUSH BIT(16) +#define SUN4I_TCON0_CPU_IF_TRI_FIFO_EN BIT(2) +#define SUN4I_TCON0_CPU_IF_TRI_EN BIT(0) + +#define SUN4I_TCON0_CPU_WR_REG 0x64 +#define SUN4I_TCON0_CPU_RD0_REG 0x68 +#define SUN4I_TCON0_CPU_RDA_REG 0x6c +#define SUN4I_TCON0_TTL0_REG 0x70 +#define SUN4I_TCON0_TTL1_REG 0x74 +#define SUN4I_TCON0_TTL2_REG 0x78 +#define SUN4I_TCON0_TTL3_REG 0x7c +#define SUN4I_TCON0_TTL4_REG 0x80 + +#define SUN4I_TCON0_LVDS_IF_REG 0x84 +#define SUN4I_TCON0_LVDS_IF_EN BIT(31) +#define SUN4I_TCON0_LVDS_IF_BITWIDTH_MASK BIT(26) +#define SUN4I_TCON0_LVDS_IF_BITWIDTH_18BITS (1 << 26) +#define SUN4I_TCON0_LVDS_IF_BITWIDTH_24BITS (0 << 26) +#define SUN4I_TCON0_LVDS_IF_CLK_SEL_MASK BIT(20) +#define SUN4I_TCON0_LVDS_IF_CLK_SEL_TCON0 (1 << 20) +#define SUN4I_TCON0_LVDS_IF_CLK_POL_MASK BIT(4) +#define SUN4I_TCON0_LVDS_IF_CLK_POL_NORMAL (1 << 4) +#define SUN4I_TCON0_LVDS_IF_CLK_POL_INV (0 << 4) +#define SUN4I_TCON0_LVDS_IF_DATA_POL_MASK GENMASK(3, 0) +#define SUN4I_TCON0_LVDS_IF_DATA_POL_NORMAL (0xf) +#define SUN4I_TCON0_LVDS_IF_DATA_POL_INV (0) + +#define SUN4I_TCON0_IO_POL_REG 0x88 +#define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase) ((phase & 3) << 28) +#define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE BIT(25) +#define SUN4I_TCON0_IO_POL_VSYNC_POSITIVE BIT(24) + +#define SUN4I_TCON0_IO_TRI_REG 0x8c +#define SUN4I_TCON0_IO_TRI_HSYNC_DISABLE BIT(25) +#define SUN4I_TCON0_IO_TRI_VSYNC_DISABLE BIT(24) +#define SUN4I_TCON0_IO_TRI_DATA_PINS_DISABLE(pins) GENMASK(pins, 0) + +#define SUN4I_TCON1_CTL_REG 0x90 +#define SUN4I_TCON1_CTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON1_CTL_INTERLACE_ENABLE BIT(20) +#define SUN4I_TCON1_CTL_CLK_DELAY_MASK GENMASK(8, 4) +#define SUN4I_TCON1_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON1_CTL_CLK_DELAY_MASK) +#define SUN4I_TCON1_CTL_SRC_SEL_MASK GENMASK(1, 0) + +#define SUN4I_TCON1_BASIC0_REG 0x94 +#define SUN4I_TCON1_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC0_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC1_REG 0x98 +#define SUN4I_TCON1_BASIC1_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC1_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC2_REG 0x9c +#define SUN4I_TCON1_BASIC2_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC2_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC3_REG 0xa0 +#define SUN4I_TCON1_BASIC3_H_TOTAL(total) ((((total) - 1) & 0x1fff) << 16) +#define SUN4I_TCON1_BASIC3_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC4_REG 0xa4 +#define SUN4I_TCON1_BASIC4_V_TOTAL(total) (((total) & 0x1fff) << 16) +#define SUN4I_TCON1_BASIC4_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC5_REG 0xa8 +#define SUN4I_TCON1_BASIC5_H_SYNC(width) ((((width) - 1) & 0x3ff) << 16) +#define SUN4I_TCON1_BASIC5_V_SYNC(height) (((height) - 1) & 0x3ff) + +#define SUN4I_TCON1_IO_POL_REG 0xf0 +#define SUN4I_TCON1_IO_TRI_REG 0xf4 + +#define SUN4I_TCON_ECC_FIFO_REG 0xf8 +#define SUN4I_TCON_ECC_FIFO_EN BIT(3) + +#define SUN4I_TCON_CEU_CTL_REG 0x100 +#define SUN4I_TCON_CEU_MUL_RR_REG 0x110 +#define SUN4I_TCON_CEU_MUL_RG_REG 0x114 +#define SUN4I_TCON_CEU_MUL_RB_REG 0x118 +#define SUN4I_TCON_CEU_ADD_RC_REG 0x11c +#define SUN4I_TCON_CEU_MUL_GR_REG 0x120 +#define SUN4I_TCON_CEU_MUL_GG_REG 0x124 +#define SUN4I_TCON_CEU_MUL_GB_REG 0x128 +#define SUN4I_TCON_CEU_ADD_GC_REG 0x12c +#define SUN4I_TCON_CEU_MUL_BR_REG 0x130 +#define SUN4I_TCON_CEU_MUL_BG_REG 0x134 +#define SUN4I_TCON_CEU_MUL_BB_REG 0x138 +#define SUN4I_TCON_CEU_ADD_BC_REG 0x13c +#define SUN4I_TCON_CEU_RANGE_R_REG 0x140 +#define SUN4I_TCON_CEU_RANGE_G_REG 0x144 +#define SUN4I_TCON_CEU_RANGE_B_REG 0x148 + +#define SUN4I_TCON0_CPU_TRI0_REG 0x160 +#define SUN4I_TCON0_CPU_TRI0_BLOCK_SPACE(space) ((((space) - 1) & 0xfff) << 16) +#define SUN4I_TCON0_CPU_TRI0_BLOCK_SIZE(size) (((size) - 1) & 0xfff) + +#define SUN4I_TCON0_CPU_TRI1_REG 0x164 +#define SUN4I_TCON0_CPU_TRI1_BLOCK_NUM(num) (((num) - 1) & 0xffff) + +#define SUN4I_TCON0_CPU_TRI2_REG 0x168 +#define SUN4I_TCON0_CPU_TRI2_START_DELAY(delay) (((delay) & 0xffff) << 16) +#define SUN4I_TCON0_CPU_TRI2_TRANS_START_SET(set) ((set) & 0xfff) + +#define SUN4I_TCON_SAFE_PERIOD_REG 0x1f0 +#define SUN4I_TCON_SAFE_PERIOD_NUM(num) (((num) & 0xfff) << 16) +#define SUN4I_TCON_SAFE_PERIOD_MODE(mode) ((mode) & 0x3) + +#define SUN4I_TCON_MUX_CTRL_REG 0x200 + +#define SUN4I_TCON0_LVDS_ANA0_REG 0x220 +#define SUN6I_TCON0_LVDS_ANA0_EN_MB BIT(31) +#define SUN6I_TCON0_LVDS_ANA0_EN_LDO BIT(30) +#define SUN6I_TCON0_LVDS_ANA0_EN_DRVC BIT(24) +#define SUN6I_TCON0_LVDS_ANA0_EN_DRVD(x) (((x) & 0xf) << 20) +#define SUN6I_TCON0_LVDS_ANA0_C(x) (((x) & 3) << 17) +#define SUN6I_TCON0_LVDS_ANA0_V(x) (((x) & 3) << 8) +#define SUN6I_TCON0_LVDS_ANA0_PD(x) (((x) & 3) << 4) + +#define SUN4I_TCON1_FILL_CTL_REG 0x300 +#define SUN4I_TCON1_FILL_BEG0_REG 0x304 +#define SUN4I_TCON1_FILL_END0_REG 0x308 +#define SUN4I_TCON1_FILL_DATA0_REG 0x30c +#define SUN4I_TCON1_FILL_BEG1_REG 0x310 +#define SUN4I_TCON1_FILL_END1_REG 0x314 +#define SUN4I_TCON1_FILL_DATA1_REG 0x318 +#define SUN4I_TCON1_FILL_BEG2_REG 0x31c +#define SUN4I_TCON1_FILL_END2_REG 0x320 +#define SUN4I_TCON1_FILL_DATA2_REG 0x324 +#define SUN4I_TCON1_GAMMA_TABLE_REG 0x400 + +#define SUN4I_TCON_MAX_CHANNELS 2 + +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? */ + u8 dclk_min_div; /* minimum divider for TCON0 DCLK */ + + /* callback to handle tcon muxing options */ + int (*set_mux)(struct sun4i_tcon *, const struct drm_encoder *); +}; + +struct sun4i_tcon { + struct device *dev; + struct drm_device *drm; + struct regmap *regs; + + /* Main bus clock */ + struct clk *clk; + + /* Clocks for the TCON channels */ + struct clk *sclk0; + struct clk *sclk1; + + /* Possible mux for the LVDS clock */ + struct clk *lvds_pll; + + /* Pixel clock */ + struct clk *dclk; + u8 dclk_max_div; + u8 dclk_min_div; + + /* Reset control */ + struct reset_control *lcd_rst; + struct reset_control *lvds_rst; + + struct drm_panel *panel; + + /* Platform adjustments */ + const struct sun4i_tcon_quirks *quirks; + + /* Associated crtc */ + struct sun4i_crtc *crtc; + + int id; + + /* TCON list management */ + struct list_head list; +}; + +struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node); +struct drm_panel *sun4i_tcon_find_panel(struct device_node *node); + +void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); +void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + const struct drm_display_mode *mode); +void sun4i_tcon_set_status(struct sun4i_tcon *crtc, + const struct drm_encoder *encoder, bool enable); + +extern const struct of_device_id sun4i_tcon_of_table[]; + +#endif /* __SUN4I_TCON_H__ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c new file mode 100644 index 000000000..1a838d208 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -0,0 +1,684 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sunxi_engine.h" + +#define SUN4I_TVE_EN_REG 0x000 +#define SUN4I_TVE_EN_DAC_MAP_MASK GENMASK(19, 4) +#define SUN4I_TVE_EN_DAC_MAP(dac, out) (((out) & 0xf) << (dac + 1) * 4) +#define SUN4I_TVE_EN_ENABLE BIT(0) + +#define SUN4I_TVE_CFG0_REG 0x004 +#define SUN4I_TVE_CFG0_DAC_CONTROL_54M BIT(26) +#define SUN4I_TVE_CFG0_CORE_DATAPATH_54M BIT(25) +#define SUN4I_TVE_CFG0_CORE_CONTROL_54M BIT(24) +#define SUN4I_TVE_CFG0_YC_EN BIT(17) +#define SUN4I_TVE_CFG0_COMP_EN BIT(16) +#define SUN4I_TVE_CFG0_RES(x) ((x) & 0xf) +#define SUN4I_TVE_CFG0_RES_480i SUN4I_TVE_CFG0_RES(0) +#define SUN4I_TVE_CFG0_RES_576i SUN4I_TVE_CFG0_RES(1) + +#define SUN4I_TVE_DAC0_REG 0x008 +#define SUN4I_TVE_DAC0_CLOCK_INVERT BIT(24) +#define SUN4I_TVE_DAC0_LUMA(x) (((x) & 3) << 20) +#define SUN4I_TVE_DAC0_LUMA_0_4 SUN4I_TVE_DAC0_LUMA(3) +#define SUN4I_TVE_DAC0_CHROMA(x) (((x) & 3) << 18) +#define SUN4I_TVE_DAC0_CHROMA_0_75 SUN4I_TVE_DAC0_CHROMA(3) +#define SUN4I_TVE_DAC0_INTERNAL_DAC(x) (((x) & 3) << 16) +#define SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS SUN4I_TVE_DAC0_INTERNAL_DAC(3) +#define SUN4I_TVE_DAC0_DAC_EN(dac) BIT(dac) + +#define SUN4I_TVE_NOTCH_REG 0x00c +#define SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(dac, x) ((4 - (x)) << (dac * 3)) + +#define SUN4I_TVE_CHROMA_FREQ_REG 0x010 + +#define SUN4I_TVE_PORCH_REG 0x014 +#define SUN4I_TVE_PORCH_BACK(x) ((x) << 16) +#define SUN4I_TVE_PORCH_FRONT(x) (x) + +#define SUN4I_TVE_LINE_REG 0x01c +#define SUN4I_TVE_LINE_FIRST(x) ((x) << 16) +#define SUN4I_TVE_LINE_NUMBER(x) (x) + +#define SUN4I_TVE_LEVEL_REG 0x020 +#define SUN4I_TVE_LEVEL_BLANK(x) ((x) << 16) +#define SUN4I_TVE_LEVEL_BLACK(x) (x) + +#define SUN4I_TVE_DAC1_REG 0x024 +#define SUN4I_TVE_DAC1_AMPLITUDE(dac, x) ((x) << (dac * 8)) + +#define SUN4I_TVE_DETECT_STA_REG 0x038 +#define SUN4I_TVE_DETECT_STA_DAC(dac) BIT((dac * 8)) +#define SUN4I_TVE_DETECT_STA_UNCONNECTED 0 +#define SUN4I_TVE_DETECT_STA_CONNECTED 1 +#define SUN4I_TVE_DETECT_STA_GROUND 2 + +#define SUN4I_TVE_CB_CR_LVL_REG 0x10c +#define SUN4I_TVE_CB_CR_LVL_CR_BURST(x) ((x) << 8) +#define SUN4I_TVE_CB_CR_LVL_CB_BURST(x) (x) + +#define SUN4I_TVE_TINT_BURST_PHASE_REG 0x110 +#define SUN4I_TVE_TINT_BURST_PHASE_CHROMA(x) (x) + +#define SUN4I_TVE_BURST_WIDTH_REG 0x114 +#define SUN4I_TVE_BURST_WIDTH_BREEZEWAY(x) ((x) << 16) +#define SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(x) ((x) << 8) +#define SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(x) (x) + +#define SUN4I_TVE_CB_CR_GAIN_REG 0x118 +#define SUN4I_TVE_CB_CR_GAIN_CR(x) ((x) << 8) +#define SUN4I_TVE_CB_CR_GAIN_CB(x) (x) + +#define SUN4I_TVE_SYNC_VBI_REG 0x11c +#define SUN4I_TVE_SYNC_VBI_SYNC(x) ((x) << 16) +#define SUN4I_TVE_SYNC_VBI_VBLANK(x) (x) + +#define SUN4I_TVE_ACTIVE_LINE_REG 0x124 +#define SUN4I_TVE_ACTIVE_LINE(x) (x) + +#define SUN4I_TVE_CHROMA_REG 0x128 +#define SUN4I_TVE_CHROMA_COMP_GAIN(x) ((x) & 3) +#define SUN4I_TVE_CHROMA_COMP_GAIN_50 SUN4I_TVE_CHROMA_COMP_GAIN(2) + +#define SUN4I_TVE_12C_REG 0x12c +#define SUN4I_TVE_12C_NOTCH_WIDTH_WIDE BIT(8) +#define SUN4I_TVE_12C_COMP_YUV_EN BIT(0) + +#define SUN4I_TVE_RESYNC_REG 0x130 +#define SUN4I_TVE_RESYNC_FIELD BIT(31) +#define SUN4I_TVE_RESYNC_LINE(x) ((x) << 16) +#define SUN4I_TVE_RESYNC_PIXEL(x) (x) + +#define SUN4I_TVE_SLAVE_REG 0x134 + +#define SUN4I_TVE_WSS_DATA2_REG 0x244 + +struct color_gains { + u16 cb; + u16 cr; +}; + +struct burst_levels { + u16 cb; + u16 cr; +}; + +struct video_levels { + u16 black; + u16 blank; +}; + +struct resync_parameters { + bool field; + u16 line; + u16 pixel; +}; + +struct tv_mode { + char *name; + + u32 mode; + u32 chroma_freq; + u16 back_porch; + u16 front_porch; + u16 line_number; + u16 vblank_level; + + u32 hdisplay; + u16 hfront_porch; + u16 hsync_len; + u16 hback_porch; + + u32 vdisplay; + u16 vfront_porch; + u16 vsync_len; + u16 vback_porch; + + bool yc_en; + bool dac3_en; + bool dac_bit25_en; + + const struct color_gains *color_gains; + const struct burst_levels *burst_levels; + const struct video_levels *video_levels; + const struct resync_parameters *resync_params; +}; + +struct sun4i_tv { + struct drm_connector connector; + struct drm_encoder encoder; + + struct clk *clk; + struct regmap *regs; + struct reset_control *reset; + + struct sun4i_drv *drv; +}; + +static const struct video_levels ntsc_video_levels = { + .black = 282, .blank = 240, +}; + +static const struct video_levels pal_video_levels = { + .black = 252, .blank = 252, +}; + +static const struct burst_levels ntsc_burst_levels = { + .cb = 79, .cr = 0, +}; + +static const struct burst_levels pal_burst_levels = { + .cb = 40, .cr = 40, +}; + +static const struct color_gains ntsc_color_gains = { + .cb = 160, .cr = 160, +}; + +static const struct color_gains pal_color_gains = { + .cb = 224, .cr = 224, +}; + +static const struct resync_parameters ntsc_resync_parameters = { + .field = false, .line = 14, .pixel = 12, +}; + +static const struct resync_parameters pal_resync_parameters = { + .field = true, .line = 13, .pixel = 12, +}; + +static const struct tv_mode tv_modes[] = { + { + .name = "NTSC", + .mode = SUN4I_TVE_CFG0_RES_480i, + .chroma_freq = 0x21f07c1f, + .yc_en = true, + .dac3_en = true, + .dac_bit25_en = true, + + .back_porch = 118, + .front_porch = 32, + .line_number = 525, + + .hdisplay = 720, + .hfront_porch = 18, + .hsync_len = 2, + .hback_porch = 118, + + .vdisplay = 480, + .vfront_porch = 26, + .vsync_len = 2, + .vback_porch = 17, + + .vblank_level = 240, + + .color_gains = &ntsc_color_gains, + .burst_levels = &ntsc_burst_levels, + .video_levels = &ntsc_video_levels, + .resync_params = &ntsc_resync_parameters, + }, + { + .name = "PAL", + .mode = SUN4I_TVE_CFG0_RES_576i, + .chroma_freq = 0x2a098acb, + + .back_porch = 138, + .front_porch = 24, + .line_number = 625, + + .hdisplay = 720, + .hfront_porch = 3, + .hsync_len = 2, + .hback_porch = 139, + + .vdisplay = 576, + .vfront_porch = 28, + .vsync_len = 2, + .vback_porch = 19, + + .vblank_level = 252, + + .color_gains = &pal_color_gains, + .burst_levels = &pal_burst_levels, + .video_levels = &pal_video_levels, + .resync_params = &pal_resync_parameters, + }, +}; + +static inline struct sun4i_tv * +drm_encoder_to_sun4i_tv(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_tv, + encoder); +} + +static inline struct sun4i_tv * +drm_connector_to_sun4i_tv(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_tv, + connector); +} + +/* + * FIXME: If only the drm_display_mode private field was usable, this + * could go away... + * + * So far, it doesn't seem to be preserved when the mode is passed by + * to mode_set for some reason. + */ +static const struct tv_mode *sun4i_tv_find_tv_by_mode(const struct drm_display_mode *mode) +{ + int i; + + /* First try to identify the mode by name */ + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + const struct tv_mode *tv_mode = &tv_modes[i]; + + DRM_DEBUG_DRIVER("Comparing mode %s vs %s", + mode->name, tv_mode->name); + + if (!strcmp(mode->name, tv_mode->name)) + return tv_mode; + } + + /* Then by number of lines */ + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + const struct tv_mode *tv_mode = &tv_modes[i]; + + DRM_DEBUG_DRIVER("Comparing mode %s vs %s (X: %d vs %d)", + mode->name, tv_mode->name, + mode->vdisplay, tv_mode->vdisplay); + + if (mode->vdisplay == tv_mode->vdisplay) + return tv_mode; + } + + return NULL; +} + +static void sun4i_tv_mode_to_drm_mode(const struct tv_mode *tv_mode, + struct drm_display_mode *mode) +{ + DRM_DEBUG_DRIVER("Creating mode %s\n", mode->name); + + mode->type = DRM_MODE_TYPE_DRIVER; + mode->clock = 13500; + mode->flags = DRM_MODE_FLAG_INTERLACE; + + mode->hdisplay = tv_mode->hdisplay; + mode->hsync_start = mode->hdisplay + tv_mode->hfront_porch; + mode->hsync_end = mode->hsync_start + tv_mode->hsync_len; + mode->htotal = mode->hsync_end + tv_mode->hback_porch; + + mode->vdisplay = tv_mode->vdisplay; + mode->vsync_start = mode->vdisplay + tv_mode->vfront_porch; + mode->vsync_end = mode->vsync_start + tv_mode->vsync_len; + mode->vtotal = mode->vsync_end + tv_mode->vback_porch; +} + +static void sun4i_tv_disable(struct drm_encoder *encoder) +{ + struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + + DRM_DEBUG_DRIVER("Disabling the TV Output\n"); + + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_ENABLE, + 0); + + sunxi_engine_disable_color_correction(crtc->engine); +} + +static void sun4i_tv_enable(struct drm_encoder *encoder) +{ + struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + + DRM_DEBUG_DRIVER("Enabling the TV Output\n"); + + sunxi_engine_apply_color_correction(crtc->engine); + + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_ENABLE, + SUN4I_TVE_EN_ENABLE); +} + +static void sun4i_tv_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); + const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); + + /* Enable and map the DAC to the output */ + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_DAC_MAP_MASK, + SUN4I_TVE_EN_DAC_MAP(0, 1) | + SUN4I_TVE_EN_DAC_MAP(1, 2) | + SUN4I_TVE_EN_DAC_MAP(2, 3) | + SUN4I_TVE_EN_DAC_MAP(3, 4)); + + /* Set PAL settings */ + regmap_write(tv->regs, SUN4I_TVE_CFG0_REG, + tv_mode->mode | + (tv_mode->yc_en ? SUN4I_TVE_CFG0_YC_EN : 0) | + SUN4I_TVE_CFG0_COMP_EN | + SUN4I_TVE_CFG0_DAC_CONTROL_54M | + SUN4I_TVE_CFG0_CORE_DATAPATH_54M | + SUN4I_TVE_CFG0_CORE_CONTROL_54M); + + /* Configure the DAC for a composite output */ + regmap_write(tv->regs, SUN4I_TVE_DAC0_REG, + SUN4I_TVE_DAC0_DAC_EN(0) | + (tv_mode->dac3_en ? SUN4I_TVE_DAC0_DAC_EN(3) : 0) | + SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS | + SUN4I_TVE_DAC0_CHROMA_0_75 | + SUN4I_TVE_DAC0_LUMA_0_4 | + SUN4I_TVE_DAC0_CLOCK_INVERT | + (tv_mode->dac_bit25_en ? BIT(25) : 0) | + BIT(30)); + + /* Configure the sample delay between DAC0 and the other DAC */ + regmap_write(tv->regs, SUN4I_TVE_NOTCH_REG, + SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(1, 0) | + SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(2, 0)); + + regmap_write(tv->regs, SUN4I_TVE_CHROMA_FREQ_REG, + tv_mode->chroma_freq); + + /* Set the front and back porch */ + regmap_write(tv->regs, SUN4I_TVE_PORCH_REG, + SUN4I_TVE_PORCH_BACK(tv_mode->back_porch) | + SUN4I_TVE_PORCH_FRONT(tv_mode->front_porch)); + + /* Set the lines setup */ + regmap_write(tv->regs, SUN4I_TVE_LINE_REG, + SUN4I_TVE_LINE_FIRST(22) | + SUN4I_TVE_LINE_NUMBER(tv_mode->line_number)); + + regmap_write(tv->regs, SUN4I_TVE_LEVEL_REG, + SUN4I_TVE_LEVEL_BLANK(tv_mode->video_levels->blank) | + SUN4I_TVE_LEVEL_BLACK(tv_mode->video_levels->black)); + + regmap_write(tv->regs, SUN4I_TVE_DAC1_REG, + SUN4I_TVE_DAC1_AMPLITUDE(0, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(1, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(2, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(3, 0x18)); + + regmap_write(tv->regs, SUN4I_TVE_CB_CR_LVL_REG, + SUN4I_TVE_CB_CR_LVL_CB_BURST(tv_mode->burst_levels->cb) | + SUN4I_TVE_CB_CR_LVL_CR_BURST(tv_mode->burst_levels->cr)); + + /* Set burst width for a composite output */ + regmap_write(tv->regs, SUN4I_TVE_BURST_WIDTH_REG, + SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(126) | + SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(68) | + SUN4I_TVE_BURST_WIDTH_BREEZEWAY(22)); + + regmap_write(tv->regs, SUN4I_TVE_CB_CR_GAIN_REG, + SUN4I_TVE_CB_CR_GAIN_CB(tv_mode->color_gains->cb) | + SUN4I_TVE_CB_CR_GAIN_CR(tv_mode->color_gains->cr)); + + regmap_write(tv->regs, SUN4I_TVE_SYNC_VBI_REG, + SUN4I_TVE_SYNC_VBI_SYNC(0x10) | + SUN4I_TVE_SYNC_VBI_VBLANK(tv_mode->vblank_level)); + + regmap_write(tv->regs, SUN4I_TVE_ACTIVE_LINE_REG, + SUN4I_TVE_ACTIVE_LINE(1440)); + + /* Set composite chroma gain to 50 % */ + regmap_write(tv->regs, SUN4I_TVE_CHROMA_REG, + SUN4I_TVE_CHROMA_COMP_GAIN_50); + + regmap_write(tv->regs, SUN4I_TVE_12C_REG, + SUN4I_TVE_12C_COMP_YUV_EN | + SUN4I_TVE_12C_NOTCH_WIDTH_WIDE); + + regmap_write(tv->regs, SUN4I_TVE_RESYNC_REG, + SUN4I_TVE_RESYNC_PIXEL(tv_mode->resync_params->pixel) | + SUN4I_TVE_RESYNC_LINE(tv_mode->resync_params->line) | + (tv_mode->resync_params->field ? + SUN4I_TVE_RESYNC_FIELD : 0)); + + regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0); +} + +static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = { + .disable = sun4i_tv_disable, + .enable = sun4i_tv_enable, + .mode_set = sun4i_tv_mode_set, +}; + +static void sun4i_tv_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs sun4i_tv_funcs = { + .destroy = sun4i_tv_destroy, +}; + +static int sun4i_tv_comp_get_modes(struct drm_connector *connector) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + struct drm_display_mode *mode; + const struct tv_mode *tv_mode = &tv_modes[i]; + + mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_ERROR("Failed to create a new display mode\n"); + return 0; + } + + strcpy(mode->name, tv_mode->name); + + sun4i_tv_mode_to_drm_mode(tv_mode, mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int sun4i_tv_comp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* TODO */ + return MODE_OK; +} + +static struct drm_connector_helper_funcs sun4i_tv_comp_connector_helper_funcs = { + .get_modes = sun4i_tv_comp_get_modes, + .mode_valid = sun4i_tv_comp_mode_valid, +}; + +static void +sun4i_tv_comp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs sun4i_tv_comp_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = sun4i_tv_comp_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct regmap_config sun4i_tv_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN4I_TVE_WSS_DATA2_REG, + .name = "tv-encoder", +}; + +static int sun4i_tv_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tv *tv; + struct resource *res; + void __iomem *regs; + int ret; + + tv = devm_kzalloc(dev, sizeof(*tv), GFP_KERNEL); + if (!tv) + return -ENOMEM; + tv->drv = drv; + dev_set_drvdata(dev, tv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) { + dev_err(dev, "Couldn't map the TV encoder registers\n"); + return PTR_ERR(regs); + } + + tv->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_tv_regmap_config); + if (IS_ERR(tv->regs)) { + dev_err(dev, "Couldn't create the TV encoder regmap\n"); + return PTR_ERR(tv->regs); + } + + tv->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(tv->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(tv->reset); + } + + ret = reset_control_deassert(tv->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + tv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(tv->clk)) { + dev_err(dev, "Couldn't get the TV encoder clock\n"); + ret = PTR_ERR(tv->clk); + goto err_assert_reset; + } + clk_prepare_enable(tv->clk); + + drm_encoder_helper_add(&tv->encoder, + &sun4i_tv_helper_funcs); + ret = drm_encoder_init(drm, + &tv->encoder, + &sun4i_tv_funcs, + DRM_MODE_ENCODER_TVDAC, + NULL); + if (ret) { + dev_err(dev, "Couldn't initialise the TV encoder\n"); + goto err_disable_clk; + } + + tv->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, + dev->of_node); + if (!tv->encoder.possible_crtcs) { + ret = -EPROBE_DEFER; + goto err_disable_clk; + } + + drm_connector_helper_add(&tv->connector, + &sun4i_tv_comp_connector_helper_funcs); + ret = drm_connector_init(drm, &tv->connector, + &sun4i_tv_comp_connector_funcs, + DRM_MODE_CONNECTOR_Composite); + if (ret) { + dev_err(dev, + "Couldn't initialise the Composite connector\n"); + goto err_cleanup_connector; + } + tv->connector.interlace_allowed = true; + + drm_connector_attach_encoder(&tv->connector, &tv->encoder); + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&tv->encoder); +err_disable_clk: + clk_disable_unprepare(tv->clk); +err_assert_reset: + reset_control_assert(tv->reset); + return ret; +} + +static void sun4i_tv_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_tv *tv = dev_get_drvdata(dev); + + drm_connector_cleanup(&tv->connector); + drm_encoder_cleanup(&tv->encoder); + clk_disable_unprepare(tv->clk); +} + +static const struct component_ops sun4i_tv_ops = { + .bind = sun4i_tv_bind, + .unbind = sun4i_tv_unbind, +}; + +static int sun4i_tv_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_tv_ops); +} + +static int sun4i_tv_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_tv_ops); + + return 0; +} + +static const struct of_device_id sun4i_tv_of_table[] = { + { .compatible = "allwinner,sun4i-a10-tv-encoder" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_tv_of_table); + +static struct platform_driver sun4i_tv_platform_driver = { + .probe = sun4i_tv_probe, + .remove = sun4i_tv_remove, + .driver = { + .name = "sun4i-tve", + .of_match_table = sun4i_tv_of_table, + }, +}; +module_platform_driver(sun4i_tv_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 TV Encoder Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun6i_drc.c b/drivers/gpu/drm/sun4i/sun6i_drc.c new file mode 100644 index 000000000..88eb268fd --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_drc.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 Free Electrons + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +struct sun6i_drc { + struct clk *bus_clk; + struct clk *mod_clk; + struct reset_control *reset; +}; + +static int sun6i_drc_bind(struct device *dev, struct device *master, + void *data) +{ + struct sun6i_drc *drc; + int ret; + + drc = devm_kzalloc(dev, sizeof(*drc), GFP_KERNEL); + if (!drc) + return -ENOMEM; + dev_set_drvdata(dev, drc); + + drc->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(drc->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(drc->reset); + } + + ret = reset_control_deassert(drc->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + drc->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(drc->bus_clk)) { + dev_err(dev, "Couldn't get our bus clock\n"); + ret = PTR_ERR(drc->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(drc->bus_clk); + + drc->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(drc->mod_clk)) { + dev_err(dev, "Couldn't get our mod clock\n"); + ret = PTR_ERR(drc->mod_clk); + goto err_disable_bus_clk; + } + clk_prepare_enable(drc->mod_clk); + + return 0; + +err_disable_bus_clk: + clk_disable_unprepare(drc->bus_clk); +err_assert_reset: + reset_control_assert(drc->reset); + return ret; +} + +static void sun6i_drc_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun6i_drc *drc = dev_get_drvdata(dev); + + clk_disable_unprepare(drc->mod_clk); + clk_disable_unprepare(drc->bus_clk); + reset_control_assert(drc->reset); +} + +static const struct component_ops sun6i_drc_ops = { + .bind = sun6i_drc_bind, + .unbind = sun6i_drc_unbind, +}; + +static int sun6i_drc_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun6i_drc_ops); +} + +static int sun6i_drc_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun6i_drc_ops); + + return 0; +} + +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); + +static struct platform_driver sun6i_drc_platform_driver = { + .probe = sun6i_drc_probe, + .remove = sun6i_drc_remove, + .driver = { + .name = "sun6i-drc", + .of_match_table = sun6i_drc_of_table, + }, +}; +module_platform_driver(sun6i_drc_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A31 Dynamic Range Control (DRC) Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c new file mode 100644 index 000000000..e4d19431f --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * Copyright (C) 2017-2018 Bootlin + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sun6i_mipi_dsi.h" + +#define SUN6I_DPHY_GCTL_REG 0x00 +#define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) +#define SUN6I_DPHY_GCTL_EN BIT(0) + +#define SUN6I_DPHY_TX_CTL_REG 0x04 +#define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28) + +#define SUN6I_DPHY_TX_TIME0_REG 0x10 +#define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24) +#define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16) +#define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME1_REG 0x14 +#define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24) +#define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16) +#define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8) +#define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME2_REG 0x18 +#define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff) + +#define SUN6I_DPHY_TX_TIME3_REG 0x1c + +#define SUN6I_DPHY_TX_TIME4_REG 0x20 +#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8) +#define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff) + +#define SUN6I_DPHY_ANA0_REG 0x4c +#define SUN6I_DPHY_ANA0_REG_PWS BIT(31) +#define SUN6I_DPHY_ANA0_REG_DMPC BIT(28) +#define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24) +#define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12) +#define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8) + +#define SUN6I_DPHY_ANA1_REG 0x50 +#define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31) +#define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28) +#define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24) + +#define SUN6I_DPHY_ANA2_REG 0x54 +#define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24) +#define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24) +#define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4) +#define SUN6I_DPHY_ANA2_REG_ENIB BIT(1) + +#define SUN6I_DPHY_ANA3_REG 0x58 +#define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28) +#define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28) +#define SUN6I_DPHY_ANA3_EN_VTTC BIT(27) +#define SUN6I_DPHY_ANA3_EN_DIV BIT(26) +#define SUN6I_DPHY_ANA3_EN_LDOC BIT(25) +#define SUN6I_DPHY_ANA3_EN_LDOD BIT(24) +#define SUN6I_DPHY_ANA3_EN_LDOR BIT(18) + +#define SUN6I_DPHY_ANA4_REG 0x5c +#define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24) +#define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20) +#define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12) +#define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10) +#define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8) +#define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6) +#define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4) +#define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2) +#define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3) + +#define SUN6I_DPHY_DBG5_REG 0xf4 + +int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes) +{ + reset_control_deassert(dphy->reset); + clk_prepare_enable(dphy->mod_clk); + clk_set_rate_exclusive(dphy->mod_clk, 150000000); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, + SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG, + SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) | + SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) | + SUN6I_DPHY_TX_TIME0_HS_TRAIL(10)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG, + SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) | + SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) | + SUN6I_DPHY_TX_TIME1_CLK_PRE(3) | + SUN6I_DPHY_TX_TIME1_CLK_POST(10)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG, + SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30)); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0); + + regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG, + SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) | + SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3)); + + regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, + SUN6I_DPHY_GCTL_LANE_NUM(lanes) | + SUN6I_DPHY_GCTL_EN); + + return 0; +} + +int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes) +{ + u8 lanes_mask = GENMASK(lanes - 1, 0); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, + SUN6I_DPHY_ANA0_REG_PWS | + SUN6I_DPHY_ANA0_REG_DMPC | + SUN6I_DPHY_ANA0_REG_SLV(7) | + SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) | + SUN6I_DPHY_ANA0_REG_DEN(lanes_mask)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_CSMPS(1) | + SUN6I_DPHY_ANA1_REG_SVTT(7)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, + SUN6I_DPHY_ANA4_REG_CKDV(1) | + SUN6I_DPHY_ANA4_REG_TMSC(1) | + SUN6I_DPHY_ANA4_REG_TMSD(1) | + SUN6I_DPHY_ANA4_REG_TXDNSC(1) | + SUN6I_DPHY_ANA4_REG_TXDNSD(1) | + SUN6I_DPHY_ANA4_REG_TXPUSC(1) | + SUN6I_DPHY_ANA4_REG_TXPUSD(1) | + SUN6I_DPHY_ANA4_REG_DMPLVC | + SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask)); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_REG_ENIB); + udelay(5); + + regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_LDOR | + SUN6I_DPHY_ANA3_EN_LDOC | + SUN6I_DPHY_ANA3_EN_LDOD); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_VTTC | + SUN6I_DPHY_ANA3_EN_VTTD_MASK, + SUN6I_DPHY_ANA3_EN_VTTC | + SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask)); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, + SUN6I_DPHY_ANA3_EN_DIV, + SUN6I_DPHY_ANA3_EN_DIV); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_EN_CK_CPU, + SUN6I_DPHY_ANA2_EN_CK_CPU); + udelay(1); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_VTTMODE, + SUN6I_DPHY_ANA1_REG_VTTMODE); + + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, + SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK, + SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask)); + + return 0; +} + +int sun6i_dphy_power_off(struct sun6i_dphy *dphy) +{ + regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, + SUN6I_DPHY_ANA1_REG_VTTMODE, 0); + + return 0; +} + +int sun6i_dphy_exit(struct sun6i_dphy *dphy) +{ + clk_rate_exclusive_put(dphy->mod_clk); + clk_disable_unprepare(dphy->mod_clk); + reset_control_assert(dphy->reset); + + return 0; +} + +static struct regmap_config sun6i_dphy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN6I_DPHY_DBG5_REG, + .name = "mipi-dphy", +}; + +static const struct of_device_id sun6i_dphy_of_table[] = { + { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, + { } +}; + +int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node) +{ + struct sun6i_dphy *dphy; + struct resource res; + void __iomem *regs; + int ret; + + if (!of_match_node(sun6i_dphy_of_table, node)) { + dev_err(dsi->dev, "Incompatible D-PHY\n"); + return -EINVAL; + } + + dphy = devm_kzalloc(dsi->dev, sizeof(*dphy), GFP_KERNEL); + if (!dphy) + return -ENOMEM; + + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(dsi->dev, "phy: Couldn't get our resources\n"); + return ret; + } + + regs = devm_ioremap_resource(dsi->dev, &res); + if (IS_ERR(regs)) { + dev_err(dsi->dev, "Couldn't map the DPHY encoder registers\n"); + return PTR_ERR(regs); + } + + dphy->regs = devm_regmap_init_mmio(dsi->dev, regs, + &sun6i_dphy_regmap_config); + if (IS_ERR(dphy->regs)) { + dev_err(dsi->dev, "Couldn't create the DPHY encoder regmap\n"); + return PTR_ERR(dphy->regs); + } + + dphy->reset = of_reset_control_get_shared(node, NULL); + if (IS_ERR(dphy->reset)) { + dev_err(dsi->dev, "Couldn't get our reset line\n"); + return PTR_ERR(dphy->reset); + } + + dphy->bus_clk = of_clk_get_by_name(node, "bus"); + if (IS_ERR(dphy->bus_clk)) { + dev_err(dsi->dev, "Couldn't get the DPHY bus clock\n"); + ret = PTR_ERR(dphy->bus_clk); + goto err_free_reset; + } + regmap_mmio_attach_clk(dphy->regs, dphy->bus_clk); + + dphy->mod_clk = of_clk_get_by_name(node, "mod"); + if (IS_ERR(dphy->mod_clk)) { + dev_err(dsi->dev, "Couldn't get the DPHY mod clock\n"); + ret = PTR_ERR(dphy->mod_clk); + goto err_free_bus; + } + + dsi->dphy = dphy; + + return 0; + +err_free_bus: + regmap_mmio_detach_clk(dphy->regs); + clk_put(dphy->bus_clk); +err_free_reset: + reset_control_put(dphy->reset); + return ret; +} + +int sun6i_dphy_remove(struct sun6i_dsi *dsi) +{ + struct sun6i_dphy *dphy = dsi->dphy; + + regmap_mmio_detach_clk(dphy->regs); + clk_put(dphy->mod_clk); + clk_put(dphy->bus_clk); + reset_control_put(dphy->reset); + + return 0; +} diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c new file mode 100644 index 000000000..9a5584efd --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -0,0 +1,1133 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * Copyright (C) 2017-2018 Bootlin + * + * Maxime Ripard <maxime.ripard@bootlin.com> + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/crc-ccitt.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/slab.h> + +#include <linux/phy/phy.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include "sun4i_drv.h" +#include "sun6i_mipi_dsi.h" + +#include <video/mipi_display.h> + +#define SUN6I_DSI_CTL_REG 0x000 +#define SUN6I_DSI_CTL_EN BIT(0) + +#define SUN6I_DSI_BASIC_CTL_REG 0x00c +#define SUN6I_DSI_BASIC_CTL_HBP_DIS BIT(2) +#define SUN6I_DSI_BASIC_CTL_HSA_HSE_DIS BIT(1) +#define SUN6I_DSI_BASIC_CTL_VIDEO_BURST BIT(0) + +#define SUN6I_DSI_BASIC_CTL0_REG 0x010 +#define SUN6I_DSI_BASIC_CTL0_HS_EOTP_EN BIT(18) +#define SUN6I_DSI_BASIC_CTL0_CRC_EN BIT(17) +#define SUN6I_DSI_BASIC_CTL0_ECC_EN BIT(16) +#define SUN6I_DSI_BASIC_CTL0_INST_ST BIT(0) + +#define SUN6I_DSI_BASIC_CTL1_REG 0x014 +#define SUN6I_DSI_BASIC_CTL1_VIDEO_ST_DELAY(n) (((n) & 0x1fff) << 4) +#define SUN6I_DSI_BASIC_CTL1_VIDEO_FILL BIT(2) +#define SUN6I_DSI_BASIC_CTL1_VIDEO_PRECISION BIT(1) +#define SUN6I_DSI_BASIC_CTL1_VIDEO_MODE BIT(0) + +#define SUN6I_DSI_BASIC_SIZE0_REG 0x018 +#define SUN6I_DSI_BASIC_SIZE0_VBP(n) (((n) & 0xfff) << 16) +#define SUN6I_DSI_BASIC_SIZE0_VSA(n) ((n) & 0xfff) + +#define SUN6I_DSI_BASIC_SIZE1_REG 0x01c +#define SUN6I_DSI_BASIC_SIZE1_VT(n) (((n) & 0xfff) << 16) +#define SUN6I_DSI_BASIC_SIZE1_VACT(n) ((n) & 0xfff) + +#define SUN6I_DSI_INST_FUNC_REG(n) (0x020 + (n) * 0x04) +#define SUN6I_DSI_INST_FUNC_INST_MODE(n) (((n) & 0xf) << 28) +#define SUN6I_DSI_INST_FUNC_ESCAPE_ENTRY(n) (((n) & 0xf) << 24) +#define SUN6I_DSI_INST_FUNC_TRANS_PACKET(n) (((n) & 0xf) << 20) +#define SUN6I_DSI_INST_FUNC_LANE_CEN BIT(4) +#define SUN6I_DSI_INST_FUNC_LANE_DEN(n) ((n) & 0xf) + +#define SUN6I_DSI_INST_LOOP_SEL_REG 0x040 + +#define SUN6I_DSI_INST_LOOP_NUM_REG(n) (0x044 + (n) * 0x10) +#define SUN6I_DSI_INST_LOOP_NUM_N1(n) (((n) & 0xfff) << 16) +#define SUN6I_DSI_INST_LOOP_NUM_N0(n) ((n) & 0xfff) + +#define SUN6I_DSI_INST_JUMP_SEL_REG 0x048 + +#define SUN6I_DSI_INST_JUMP_CFG_REG(n) (0x04c + (n) * 0x04) +#define SUN6I_DSI_INST_JUMP_CFG_TO(n) (((n) & 0xf) << 20) +#define SUN6I_DSI_INST_JUMP_CFG_POINT(n) (((n) & 0xf) << 16) +#define SUN6I_DSI_INST_JUMP_CFG_NUM(n) ((n) & 0xffff) + +#define SUN6I_DSI_TRANS_START_REG 0x060 + +#define SUN6I_DSI_TRANS_ZERO_REG 0x078 + +#define SUN6I_DSI_TCON_DRQ_REG 0x07c +#define SUN6I_DSI_TCON_DRQ_ENABLE_MODE BIT(28) +#define SUN6I_DSI_TCON_DRQ_SET(n) ((n) & 0x3ff) + +#define SUN6I_DSI_PIXEL_CTL0_REG 0x080 +#define SUN6I_DSI_PIXEL_CTL0_PD_PLUG_DISABLE BIT(16) +#define SUN6I_DSI_PIXEL_CTL0_FORMAT(n) ((n) & 0xf) + +#define SUN6I_DSI_PIXEL_CTL1_REG 0x084 + +#define SUN6I_DSI_PIXEL_PH_REG 0x090 +#define SUN6I_DSI_PIXEL_PH_ECC(n) (((n) & 0xff) << 24) +#define SUN6I_DSI_PIXEL_PH_WC(n) (((n) & 0xffff) << 8) +#define SUN6I_DSI_PIXEL_PH_VC(n) (((n) & 3) << 6) +#define SUN6I_DSI_PIXEL_PH_DT(n) ((n) & 0x3f) + +#define SUN6I_DSI_PIXEL_PF0_REG 0x098 +#define SUN6I_DSI_PIXEL_PF0_CRC_FORCE(n) ((n) & 0xffff) + +#define SUN6I_DSI_PIXEL_PF1_REG 0x09c +#define SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINEN(n) (((n) & 0xffff) << 16) +#define SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINE0(n) ((n) & 0xffff) + +#define SUN6I_DSI_SYNC_HSS_REG 0x0b0 + +#define SUN6I_DSI_SYNC_HSE_REG 0x0b4 + +#define SUN6I_DSI_SYNC_VSS_REG 0x0b8 + +#define SUN6I_DSI_SYNC_VSE_REG 0x0bc + +#define SUN6I_DSI_BLK_HSA0_REG 0x0c0 + +#define SUN6I_DSI_BLK_HSA1_REG 0x0c4 +#define SUN6I_DSI_BLK_PF(n) (((n) & 0xffff) << 16) +#define SUN6I_DSI_BLK_PD(n) ((n) & 0xff) + +#define SUN6I_DSI_BLK_HBP0_REG 0x0c8 + +#define SUN6I_DSI_BLK_HBP1_REG 0x0cc + +#define SUN6I_DSI_BLK_HFP0_REG 0x0d0 + +#define SUN6I_DSI_BLK_HFP1_REG 0x0d4 + +#define SUN6I_DSI_BLK_HBLK0_REG 0x0e0 + +#define SUN6I_DSI_BLK_HBLK1_REG 0x0e4 + +#define SUN6I_DSI_BLK_VBLK0_REG 0x0e8 + +#define SUN6I_DSI_BLK_VBLK1_REG 0x0ec + +#define SUN6I_DSI_BURST_LINE_REG 0x0f0 +#define SUN6I_DSI_BURST_LINE_SYNC_POINT(n) (((n) & 0xffff) << 16) +#define SUN6I_DSI_BURST_LINE_NUM(n) ((n) & 0xffff) + +#define SUN6I_DSI_BURST_DRQ_REG 0x0f4 +#define SUN6I_DSI_BURST_DRQ_EDGE1(n) (((n) & 0xffff) << 16) +#define SUN6I_DSI_BURST_DRQ_EDGE0(n) ((n) & 0xffff) + +#define SUN6I_DSI_CMD_CTL_REG 0x200 +#define SUN6I_DSI_CMD_CTL_RX_OVERFLOW BIT(26) +#define SUN6I_DSI_CMD_CTL_RX_FLAG BIT(25) +#define SUN6I_DSI_CMD_CTL_TX_FLAG BIT(9) + +#define SUN6I_DSI_CMD_RX_REG(n) (0x240 + (n) * 0x04) + +#define SUN6I_DSI_DEBUG_DATA_REG 0x2f8 + +#define SUN6I_DSI_CMD_TX_REG(n) (0x300 + (n) * 0x04) + +enum sun6i_dsi_start_inst { + DSI_START_LPRX, + DSI_START_LPTX, + DSI_START_HSC, + DSI_START_HSD, +}; + +enum sun6i_dsi_inst_id { + DSI_INST_ID_LP11 = 0, + DSI_INST_ID_TBA, + DSI_INST_ID_HSC, + DSI_INST_ID_HSD, + DSI_INST_ID_LPDT, + DSI_INST_ID_HSCEXIT, + DSI_INST_ID_NOP, + DSI_INST_ID_DLY, + DSI_INST_ID_END = 15, +}; + +enum sun6i_dsi_inst_mode { + DSI_INST_MODE_STOP = 0, + DSI_INST_MODE_TBA, + DSI_INST_MODE_HS, + DSI_INST_MODE_ESCAPE, + DSI_INST_MODE_HSCEXIT, + DSI_INST_MODE_NOP, +}; + +enum sun6i_dsi_inst_escape { + DSI_INST_ESCA_LPDT = 0, + DSI_INST_ESCA_ULPS, + DSI_INST_ESCA_UN1, + DSI_INST_ESCA_UN2, + DSI_INST_ESCA_RESET, + DSI_INST_ESCA_UN3, + DSI_INST_ESCA_UN4, + DSI_INST_ESCA_UN5, +}; + +enum sun6i_dsi_inst_packet { + DSI_INST_PACK_PIXEL = 0, + DSI_INST_PACK_COMMAND, +}; + +static const u32 sun6i_dsi_ecc_array[] = { + [0] = (BIT(0) | BIT(1) | BIT(2) | BIT(4) | BIT(5) | BIT(7) | BIT(10) | + BIT(11) | BIT(13) | BIT(16) | BIT(20) | BIT(21) | BIT(22) | + BIT(23)), + [1] = (BIT(0) | BIT(1) | BIT(3) | BIT(4) | BIT(6) | BIT(8) | BIT(10) | + BIT(12) | BIT(14) | BIT(17) | BIT(20) | BIT(21) | BIT(22) | + BIT(23)), + [2] = (BIT(0) | BIT(2) | BIT(3) | BIT(5) | BIT(6) | BIT(9) | BIT(11) | + BIT(12) | BIT(15) | BIT(18) | BIT(20) | BIT(21) | BIT(22)), + [3] = (BIT(1) | BIT(2) | BIT(3) | BIT(7) | BIT(8) | BIT(9) | BIT(13) | + BIT(14) | BIT(15) | BIT(19) | BIT(20) | BIT(21) | BIT(23)), + [4] = (BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9) | BIT(16) | + BIT(17) | BIT(18) | BIT(19) | BIT(20) | BIT(22) | BIT(23)), + [5] = (BIT(10) | BIT(11) | BIT(12) | BIT(13) | BIT(14) | BIT(15) | + BIT(16) | BIT(17) | BIT(18) | BIT(19) | BIT(21) | BIT(22) | + BIT(23)), +}; + +static u32 sun6i_dsi_ecc_compute(unsigned int data) +{ + int i; + u8 ecc = 0; + + for (i = 0; i < ARRAY_SIZE(sun6i_dsi_ecc_array); i++) { + u32 field = sun6i_dsi_ecc_array[i]; + bool init = false; + u8 val = 0; + int j; + + for (j = 0; j < 24; j++) { + if (!(BIT(j) & field)) + continue; + + if (!init) { + val = (BIT(j) & data) ? 1 : 0; + init = true; + } else { + val ^= (BIT(j) & data) ? 1 : 0; + } + } + + ecc |= val << i; + } + + return ecc; +} + +static u16 sun6i_dsi_crc_compute(u8 const *buffer, size_t len) +{ + return crc_ccitt(0xffff, buffer, len); +} + +static u16 sun6i_dsi_crc_repeat(u8 pd, u8 *buffer, size_t len) +{ + memset(buffer, pd, len); + + return sun6i_dsi_crc_compute(buffer, len); +} + +static u32 sun6i_dsi_build_sync_pkt(u8 dt, u8 vc, u8 d0, u8 d1) +{ + u32 val = dt & 0x3f; + + val |= (vc & 3) << 6; + val |= (d0 & 0xff) << 8; + val |= (d1 & 0xff) << 16; + val |= sun6i_dsi_ecc_compute(val) << 24; + + return val; +} + +static u32 sun6i_dsi_build_blk0_pkt(u8 vc, u16 wc) +{ + return sun6i_dsi_build_sync_pkt(MIPI_DSI_BLANKING_PACKET, vc, + wc & 0xff, wc >> 8); +} + +static u32 sun6i_dsi_build_blk1_pkt(u16 pd, u8 *buffer, size_t len) +{ + u32 val = SUN6I_DSI_BLK_PD(pd); + + return val | SUN6I_DSI_BLK_PF(sun6i_dsi_crc_repeat(pd, buffer, len)); +} + +static void sun6i_dsi_inst_abort(struct sun6i_dsi *dsi) +{ + regmap_update_bits(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG, + SUN6I_DSI_BASIC_CTL0_INST_ST, 0); +} + +static void sun6i_dsi_inst_commit(struct sun6i_dsi *dsi) +{ + regmap_update_bits(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG, + SUN6I_DSI_BASIC_CTL0_INST_ST, + SUN6I_DSI_BASIC_CTL0_INST_ST); +} + +static int sun6i_dsi_inst_wait_for_completion(struct sun6i_dsi *dsi) +{ + u32 val; + + return regmap_read_poll_timeout(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG, + val, + !(val & SUN6I_DSI_BASIC_CTL0_INST_ST), + 100, 5000); +} + +static void sun6i_dsi_inst_setup(struct sun6i_dsi *dsi, + enum sun6i_dsi_inst_id id, + enum sun6i_dsi_inst_mode mode, + bool clock, u8 data, + enum sun6i_dsi_inst_packet packet, + enum sun6i_dsi_inst_escape escape) +{ + regmap_write(dsi->regs, SUN6I_DSI_INST_FUNC_REG(id), + SUN6I_DSI_INST_FUNC_INST_MODE(mode) | + SUN6I_DSI_INST_FUNC_ESCAPE_ENTRY(escape) | + SUN6I_DSI_INST_FUNC_TRANS_PACKET(packet) | + (clock ? SUN6I_DSI_INST_FUNC_LANE_CEN : 0) | + SUN6I_DSI_INST_FUNC_LANE_DEN(data)); +} + +static void sun6i_dsi_inst_init(struct sun6i_dsi *dsi, + struct mipi_dsi_device *device) +{ + u8 lanes_mask = GENMASK(device->lanes - 1, 0); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_LP11, DSI_INST_MODE_STOP, + true, lanes_mask, 0, 0); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_TBA, DSI_INST_MODE_TBA, + false, 1, 0, 0); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_HSC, DSI_INST_MODE_HS, + true, 0, DSI_INST_PACK_PIXEL, 0); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_HSD, DSI_INST_MODE_HS, + false, lanes_mask, DSI_INST_PACK_PIXEL, 0); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_LPDT, DSI_INST_MODE_ESCAPE, + false, 1, DSI_INST_PACK_COMMAND, + DSI_INST_ESCA_LPDT); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_HSCEXIT, DSI_INST_MODE_HSCEXIT, + true, 0, 0, 0); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_NOP, DSI_INST_MODE_STOP, + false, lanes_mask, 0, 0); + + sun6i_dsi_inst_setup(dsi, DSI_INST_ID_DLY, DSI_INST_MODE_NOP, + true, lanes_mask, 0, 0); + + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_CFG_REG(0), + SUN6I_DSI_INST_JUMP_CFG_POINT(DSI_INST_ID_NOP) | + SUN6I_DSI_INST_JUMP_CFG_TO(DSI_INST_ID_HSCEXIT) | + SUN6I_DSI_INST_JUMP_CFG_NUM(1)); +}; + +static u16 sun6i_dsi_get_video_start_delay(struct sun6i_dsi *dsi, + struct drm_display_mode *mode) +{ + u16 delay = mode->vtotal - (mode->vsync_end - mode->vdisplay) + 1; + + if (delay > mode->vtotal) + delay = delay % mode->vtotal; + + return max_t(u16, delay, 1); +} + +static void sun6i_dsi_setup_burst(struct sun6i_dsi *dsi, + struct drm_display_mode *mode) +{ + struct mipi_dsi_device *device = dsi->device; + u32 val = 0; + + if ((mode->hsync_end - mode->hdisplay) > 20) { + /* Maaaaaagic */ + u16 drq = (mode->hsync_end - mode->hdisplay) - 20; + + drq *= mipi_dsi_pixel_format_to_bpp(device->format); + drq /= 32; + + val = (SUN6I_DSI_TCON_DRQ_ENABLE_MODE | + SUN6I_DSI_TCON_DRQ_SET(drq)); + } + + regmap_write(dsi->regs, SUN6I_DSI_TCON_DRQ_REG, val); +} + +static void sun6i_dsi_setup_inst_loop(struct sun6i_dsi *dsi, + struct drm_display_mode *mode) +{ + u16 delay = 50 - 1; + + regmap_write(dsi->regs, SUN6I_DSI_INST_LOOP_NUM_REG(0), + SUN6I_DSI_INST_LOOP_NUM_N0(50 - 1) | + SUN6I_DSI_INST_LOOP_NUM_N1(delay)); + regmap_write(dsi->regs, SUN6I_DSI_INST_LOOP_NUM_REG(1), + SUN6I_DSI_INST_LOOP_NUM_N0(50 - 1) | + SUN6I_DSI_INST_LOOP_NUM_N1(delay)); +} + +static void sun6i_dsi_setup_format(struct sun6i_dsi *dsi, + struct drm_display_mode *mode) +{ + struct mipi_dsi_device *device = dsi->device; + u32 val = SUN6I_DSI_PIXEL_PH_VC(device->channel); + u8 dt, fmt; + u16 wc; + + /* + * TODO: The format defines are only valid in video mode and + * change in command mode. + */ + switch (device->format) { + case MIPI_DSI_FMT_RGB888: + dt = MIPI_DSI_PACKED_PIXEL_STREAM_24; + fmt = 8; + break; + case MIPI_DSI_FMT_RGB666: + dt = MIPI_DSI_PIXEL_STREAM_3BYTE_18; + fmt = 9; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + dt = MIPI_DSI_PACKED_PIXEL_STREAM_18; + fmt = 10; + break; + case MIPI_DSI_FMT_RGB565: + dt = MIPI_DSI_PACKED_PIXEL_STREAM_16; + fmt = 11; + break; + default: + return; + } + val |= SUN6I_DSI_PIXEL_PH_DT(dt); + + wc = mode->hdisplay * mipi_dsi_pixel_format_to_bpp(device->format) / 8; + val |= SUN6I_DSI_PIXEL_PH_WC(wc); + val |= SUN6I_DSI_PIXEL_PH_ECC(sun6i_dsi_ecc_compute(val)); + + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_PH_REG, val); + + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_PF0_REG, + SUN6I_DSI_PIXEL_PF0_CRC_FORCE(0xffff)); + + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_PF1_REG, + SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINE0(0xffff) | + SUN6I_DSI_PIXEL_PF1_CRC_INIT_LINEN(0xffff)); + + regmap_write(dsi->regs, SUN6I_DSI_PIXEL_CTL0_REG, + SUN6I_DSI_PIXEL_CTL0_PD_PLUG_DISABLE | + SUN6I_DSI_PIXEL_CTL0_FORMAT(fmt)); +} + +static void sun6i_dsi_setup_timings(struct sun6i_dsi *dsi, + struct drm_display_mode *mode) +{ + struct mipi_dsi_device *device = dsi->device; + unsigned int Bpp = mipi_dsi_pixel_format_to_bpp(device->format) / 8; + u16 hbp, hfp, hsa, hblk, vblk; + size_t bytes; + u8 *buffer; + + /* Do all timing calculations up front to allocate buffer space */ + + /* + * A sync period is composed of a blanking packet (4 bytes + + * payload + 2 bytes) and a sync event packet (4 bytes). Its + * minimal size is therefore 10 bytes + */ +#define HSA_PACKET_OVERHEAD 10 + hsa = max((unsigned int)HSA_PACKET_OVERHEAD, + (mode->hsync_end - mode->hsync_start) * Bpp - HSA_PACKET_OVERHEAD); + + /* + * The backporch is set using a blanking packet (4 bytes + + * payload + 2 bytes). Its minimal size is therefore 6 bytes + */ +#define HBP_PACKET_OVERHEAD 6 + hbp = max((unsigned int)HBP_PACKET_OVERHEAD, + (mode->hsync_start - mode->hdisplay) * Bpp - HBP_PACKET_OVERHEAD); + + /* + * The frontporch is set using a blanking packet (4 bytes + + * payload + 2 bytes). Its minimal size is therefore 6 bytes + */ +#define HFP_PACKET_OVERHEAD 6 + hfp = max((unsigned int)HFP_PACKET_OVERHEAD, + (mode->htotal - mode->hsync_end) * Bpp - HFP_PACKET_OVERHEAD); + + /* + * hblk seems to be the line + porches length. + */ + hblk = mode->htotal * Bpp - hsa; + + /* + * And I'm not entirely sure what vblk is about. The driver in + * Allwinner BSP is using a rather convoluted calculation + * there only for 4 lanes. However, using 0 (the !4 lanes + * case) even with a 4 lanes screen seems to work... + */ + vblk = 0; + + /* How many bytes do we need to send all payloads? */ + bytes = max_t(size_t, max(max(hfp, hblk), max(hsa, hbp)), vblk); + buffer = kmalloc(bytes, GFP_KERNEL); + if (WARN_ON(!buffer)) + return; + + regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL_REG, 0); + + regmap_write(dsi->regs, SUN6I_DSI_SYNC_HSS_REG, + sun6i_dsi_build_sync_pkt(MIPI_DSI_H_SYNC_START, + device->channel, + 0, 0)); + + regmap_write(dsi->regs, SUN6I_DSI_SYNC_HSE_REG, + sun6i_dsi_build_sync_pkt(MIPI_DSI_H_SYNC_END, + device->channel, + 0, 0)); + + regmap_write(dsi->regs, SUN6I_DSI_SYNC_VSS_REG, + sun6i_dsi_build_sync_pkt(MIPI_DSI_V_SYNC_START, + device->channel, + 0, 0)); + + regmap_write(dsi->regs, SUN6I_DSI_SYNC_VSE_REG, + sun6i_dsi_build_sync_pkt(MIPI_DSI_V_SYNC_END, + device->channel, + 0, 0)); + + regmap_write(dsi->regs, SUN6I_DSI_BASIC_SIZE0_REG, + SUN6I_DSI_BASIC_SIZE0_VSA(mode->vsync_end - + mode->vsync_start) | + SUN6I_DSI_BASIC_SIZE0_VBP(mode->vsync_start - + mode->vdisplay)); + + regmap_write(dsi->regs, SUN6I_DSI_BASIC_SIZE1_REG, + SUN6I_DSI_BASIC_SIZE1_VACT(mode->vdisplay) | + SUN6I_DSI_BASIC_SIZE1_VT(mode->vtotal)); + + /* sync */ + regmap_write(dsi->regs, SUN6I_DSI_BLK_HSA0_REG, + sun6i_dsi_build_blk0_pkt(device->channel, hsa)); + regmap_write(dsi->regs, SUN6I_DSI_BLK_HSA1_REG, + sun6i_dsi_build_blk1_pkt(0, buffer, hsa)); + + /* backporch */ + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBP0_REG, + sun6i_dsi_build_blk0_pkt(device->channel, hbp)); + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBP1_REG, + sun6i_dsi_build_blk1_pkt(0, buffer, hbp)); + + /* frontporch */ + regmap_write(dsi->regs, SUN6I_DSI_BLK_HFP0_REG, + sun6i_dsi_build_blk0_pkt(device->channel, hfp)); + regmap_write(dsi->regs, SUN6I_DSI_BLK_HFP1_REG, + sun6i_dsi_build_blk1_pkt(0, buffer, hfp)); + + /* hblk */ + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBLK0_REG, + sun6i_dsi_build_blk0_pkt(device->channel, hblk)); + regmap_write(dsi->regs, SUN6I_DSI_BLK_HBLK1_REG, + sun6i_dsi_build_blk1_pkt(0, buffer, hblk)); + + /* vblk */ + regmap_write(dsi->regs, SUN6I_DSI_BLK_VBLK0_REG, + sun6i_dsi_build_blk0_pkt(device->channel, vblk)); + regmap_write(dsi->regs, SUN6I_DSI_BLK_VBLK1_REG, + sun6i_dsi_build_blk1_pkt(0, buffer, vblk)); + + kfree(buffer); +} + +static int sun6i_dsi_start(struct sun6i_dsi *dsi, + enum sun6i_dsi_start_inst func) +{ + switch (func) { + case DSI_START_LPTX: + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG, + DSI_INST_ID_LPDT << (4 * DSI_INST_ID_LP11) | + DSI_INST_ID_END << (4 * DSI_INST_ID_LPDT)); + break; + case DSI_START_LPRX: + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG, + DSI_INST_ID_LPDT << (4 * DSI_INST_ID_LP11) | + DSI_INST_ID_DLY << (4 * DSI_INST_ID_LPDT) | + DSI_INST_ID_TBA << (4 * DSI_INST_ID_DLY) | + DSI_INST_ID_END << (4 * DSI_INST_ID_TBA)); + break; + case DSI_START_HSC: + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG, + DSI_INST_ID_HSC << (4 * DSI_INST_ID_LP11) | + DSI_INST_ID_END << (4 * DSI_INST_ID_HSC)); + break; + case DSI_START_HSD: + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG, + DSI_INST_ID_NOP << (4 * DSI_INST_ID_LP11) | + DSI_INST_ID_HSD << (4 * DSI_INST_ID_NOP) | + DSI_INST_ID_DLY << (4 * DSI_INST_ID_HSD) | + DSI_INST_ID_NOP << (4 * DSI_INST_ID_DLY) | + DSI_INST_ID_END << (4 * DSI_INST_ID_HSCEXIT)); + break; + default: + regmap_write(dsi->regs, SUN6I_DSI_INST_JUMP_SEL_REG, + DSI_INST_ID_END << (4 * DSI_INST_ID_LP11)); + break; + } + + sun6i_dsi_inst_abort(dsi); + sun6i_dsi_inst_commit(dsi); + + if (func == DSI_START_HSC) + regmap_write_bits(dsi->regs, + SUN6I_DSI_INST_FUNC_REG(DSI_INST_ID_LP11), + SUN6I_DSI_INST_FUNC_LANE_CEN, 0); + + return 0; +} + +static void sun6i_dsi_encoder_enable(struct drm_encoder *encoder) +{ + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; + struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder); + struct mipi_dsi_device *device = dsi->device; + u16 delay; + + DRM_DEBUG_DRIVER("Enabling DSI output\n"); + + pm_runtime_get_sync(dsi->dev); + + delay = sun6i_dsi_get_video_start_delay(dsi, mode); + regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL1_REG, + SUN6I_DSI_BASIC_CTL1_VIDEO_ST_DELAY(delay) | + SUN6I_DSI_BASIC_CTL1_VIDEO_FILL | + SUN6I_DSI_BASIC_CTL1_VIDEO_PRECISION | + SUN6I_DSI_BASIC_CTL1_VIDEO_MODE); + + sun6i_dsi_setup_burst(dsi, mode); + sun6i_dsi_setup_inst_loop(dsi, mode); + sun6i_dsi_setup_format(dsi, mode); + sun6i_dsi_setup_timings(dsi, mode); + + sun6i_dphy_init(dsi->dphy, device->lanes); + sun6i_dphy_power_on(dsi->dphy, device->lanes); + + if (!IS_ERR(dsi->panel)) + drm_panel_prepare(dsi->panel); + + /* + * FIXME: This should be moved after the switch to HS mode. + * + * Unfortunately, once in HS mode, it seems like we're not + * able to send DCS commands anymore, which would prevent any + * panel to send any DCS command as part as their enable + * method, which is quite common. + * + * I haven't seen any artifact due to that sub-optimal + * ordering on the panels I've tested it with, so I guess this + * will do for now, until that IP is better understood. + */ + if (!IS_ERR(dsi->panel)) + drm_panel_enable(dsi->panel); + + sun6i_dsi_start(dsi, DSI_START_HSC); + + udelay(1000); + + sun6i_dsi_start(dsi, DSI_START_HSD); +} + +static void sun6i_dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct sun6i_dsi *dsi = encoder_to_sun6i_dsi(encoder); + + DRM_DEBUG_DRIVER("Disabling DSI output\n"); + + if (!IS_ERR(dsi->panel)) { + drm_panel_disable(dsi->panel); + drm_panel_unprepare(dsi->panel); + } + + sun6i_dphy_power_off(dsi->dphy); + sun6i_dphy_exit(dsi->dphy); + + pm_runtime_put(dsi->dev); +} + +static int sun6i_dsi_get_modes(struct drm_connector *connector) +{ + struct sun6i_dsi *dsi = connector_to_sun6i_dsi(connector); + + return drm_panel_get_modes(dsi->panel); +} + +static struct drm_connector_helper_funcs sun6i_dsi_connector_helper_funcs = { + .get_modes = sun6i_dsi_get_modes, +}; + +static enum drm_connector_status +sun6i_dsi_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs sun6i_dsi_connector_funcs = { + .detect = sun6i_dsi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_encoder_helper_funcs sun6i_dsi_enc_helper_funcs = { + .disable = sun6i_dsi_encoder_disable, + .enable = sun6i_dsi_encoder_enable, +}; + +static const struct drm_encoder_funcs sun6i_dsi_enc_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static u32 sun6i_dsi_dcs_build_pkt_hdr(struct sun6i_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + u32 pkt = msg->type; + + if (msg->type == MIPI_DSI_DCS_LONG_WRITE) { + pkt |= ((msg->tx_len + 1) & 0xffff) << 8; + pkt |= (((msg->tx_len + 1) >> 8) & 0xffff) << 16; + } else { + pkt |= (((u8 *)msg->tx_buf)[0] << 8); + if (msg->tx_len > 1) + pkt |= (((u8 *)msg->tx_buf)[1] << 16); + } + + pkt |= sun6i_dsi_ecc_compute(pkt) << 24; + + return pkt; +} + +static int sun6i_dsi_dcs_write_short(struct sun6i_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + regmap_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(0), + sun6i_dsi_dcs_build_pkt_hdr(dsi, msg)); + regmap_write_bits(dsi->regs, SUN6I_DSI_CMD_CTL_REG, + 0xff, (4 - 1)); + + sun6i_dsi_start(dsi, DSI_START_LPTX); + + return msg->tx_len; +} + +static int sun6i_dsi_dcs_write_long(struct sun6i_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + int ret, len = 0; + u8 *bounce; + u16 crc; + + regmap_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(0), + sun6i_dsi_dcs_build_pkt_hdr(dsi, msg)); + + bounce = kzalloc(ALIGN(msg->tx_len + sizeof(crc), 4), GFP_KERNEL); + if (!bounce) + return -ENOMEM; + + memcpy(bounce, msg->tx_buf, msg->tx_len); + len += msg->tx_len; + + crc = sun6i_dsi_crc_compute(bounce, msg->tx_len); + memcpy((u8 *)bounce + msg->tx_len, &crc, sizeof(crc)); + len += sizeof(crc); + + regmap_bulk_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(1), bounce, DIV_ROUND_UP(len, 4)); + regmap_write(dsi->regs, SUN6I_DSI_CMD_CTL_REG, len + 4 - 1); + kfree(bounce); + + sun6i_dsi_start(dsi, DSI_START_LPTX); + + ret = sun6i_dsi_inst_wait_for_completion(dsi); + if (ret < 0) { + sun6i_dsi_inst_abort(dsi); + return ret; + } + + /* + * TODO: There's some bits (reg 0x200, bits 8/9) that + * apparently can be used to check whether the data have been + * sent, but I couldn't get it to work reliably. + */ + return msg->tx_len; +} + +static int sun6i_dsi_dcs_read(struct sun6i_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + u32 val; + int ret; + u8 byte0; + + regmap_write(dsi->regs, SUN6I_DSI_CMD_TX_REG(0), + sun6i_dsi_dcs_build_pkt_hdr(dsi, msg)); + regmap_write(dsi->regs, SUN6I_DSI_CMD_CTL_REG, + (4 - 1)); + + sun6i_dsi_start(dsi, DSI_START_LPRX); + + ret = sun6i_dsi_inst_wait_for_completion(dsi); + if (ret < 0) { + sun6i_dsi_inst_abort(dsi); + return ret; + } + + /* + * TODO: There's some bits (reg 0x200, bits 24/25) that + * apparently can be used to check whether the data have been + * received, but I couldn't get it to work reliably. + */ + regmap_read(dsi->regs, SUN6I_DSI_CMD_CTL_REG, &val); + if (val & SUN6I_DSI_CMD_CTL_RX_OVERFLOW) + return -EIO; + + regmap_read(dsi->regs, SUN6I_DSI_CMD_RX_REG(0), &val); + byte0 = val & 0xff; + if (byte0 == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT) + return -EIO; + + ((u8 *)msg->rx_buf)[0] = (val >> 8); + + return 1; +} + +static int sun6i_dsi_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct sun6i_dsi *dsi = host_to_sun6i_dsi(host); + + dsi->device = device; + dsi->panel = of_drm_find_panel(device->dev.of_node); + if (IS_ERR(dsi->panel)) + return PTR_ERR(dsi->panel); + + dev_info(host->dev, "Attached device %s\n", device->name); + + return 0; +} + +static int sun6i_dsi_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct sun6i_dsi *dsi = host_to_sun6i_dsi(host); + + dsi->panel = NULL; + dsi->device = NULL; + + return 0; +} + +static ssize_t sun6i_dsi_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct sun6i_dsi *dsi = host_to_sun6i_dsi(host); + int ret; + + ret = sun6i_dsi_inst_wait_for_completion(dsi); + if (ret < 0) + sun6i_dsi_inst_abort(dsi); + + regmap_write(dsi->regs, SUN6I_DSI_CMD_CTL_REG, + SUN6I_DSI_CMD_CTL_RX_OVERFLOW | + SUN6I_DSI_CMD_CTL_RX_FLAG | + SUN6I_DSI_CMD_CTL_TX_FLAG); + + switch (msg->type) { + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + ret = sun6i_dsi_dcs_write_short(dsi, msg); + break; + + case MIPI_DSI_DCS_LONG_WRITE: + ret = sun6i_dsi_dcs_write_long(dsi, msg); + break; + + case MIPI_DSI_DCS_READ: + if (msg->rx_len == 1) { + ret = sun6i_dsi_dcs_read(dsi, msg); + break; + } + + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct mipi_dsi_host_ops sun6i_dsi_host_ops = { + .attach = sun6i_dsi_attach, + .detach = sun6i_dsi_detach, + .transfer = sun6i_dsi_transfer, +}; + +static const struct regmap_config sun6i_dsi_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN6I_DSI_CMD_TX_REG(255), + .name = "mipi-dsi", +}; + +static int sun6i_dsi_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun6i_dsi *dsi = dev_get_drvdata(dev); + int ret; + + if (!dsi->panel) + return -EPROBE_DEFER; + + dsi->drv = drv; + + drm_encoder_helper_add(&dsi->encoder, + &sun6i_dsi_enc_helper_funcs); + ret = drm_encoder_init(drm, + &dsi->encoder, + &sun6i_dsi_enc_funcs, + DRM_MODE_ENCODER_DSI, + NULL); + if (ret) { + dev_err(dsi->dev, "Couldn't initialise the DSI encoder\n"); + return ret; + } + dsi->encoder.possible_crtcs = BIT(0); + + drm_connector_helper_add(&dsi->connector, + &sun6i_dsi_connector_helper_funcs); + ret = drm_connector_init(drm, &dsi->connector, + &sun6i_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) { + dev_err(dsi->dev, + "Couldn't initialise the DSI connector\n"); + goto err_cleanup_connector; + } + + drm_connector_attach_encoder(&dsi->connector, &dsi->encoder); + drm_panel_attach(dsi->panel, &dsi->connector); + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&dsi->encoder); + return ret; +} + +static void sun6i_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun6i_dsi *dsi = dev_get_drvdata(dev); + + drm_panel_detach(dsi->panel); +} + +static const struct component_ops sun6i_dsi_ops = { + .bind = sun6i_dsi_bind, + .unbind = sun6i_dsi_unbind, +}; + +static int sun6i_dsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dphy_node; + struct sun6i_dsi *dsi; + struct resource *res; + void __iomem *base; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + dev_set_drvdata(dev, dsi); + dsi->dev = dev; + dsi->host.ops = &sun6i_dsi_host_ops; + dsi->host.dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) { + dev_err(dev, "Couldn't map the DSI encoder registers\n"); + return PTR_ERR(base); + } + + dsi->regs = devm_regmap_init_mmio_clk(dev, "bus", base, + &sun6i_dsi_regmap_config); + if (IS_ERR(dsi->regs)) { + dev_err(dev, "Couldn't create the DSI encoder regmap\n"); + return PTR_ERR(dsi->regs); + } + + dsi->reset = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(dsi->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(dsi->reset); + } + + dsi->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(dsi->mod_clk)) { + dev_err(dev, "Couldn't get the DSI mod clock\n"); + return PTR_ERR(dsi->mod_clk); + } + + /* + * In order to operate properly, that clock seems to be always + * set to 297MHz. + */ + clk_set_rate_exclusive(dsi->mod_clk, 297000000); + + dphy_node = of_parse_phandle(dev->of_node, "phys", 0); + ret = sun6i_dphy_probe(dsi, dphy_node); + of_node_put(dphy_node); + if (ret) { + dev_err(dev, "Couldn't get the MIPI D-PHY\n"); + goto err_unprotect_clk; + } + + pm_runtime_enable(dev); + + ret = mipi_dsi_host_register(&dsi->host); + if (ret) { + dev_err(dev, "Couldn't register MIPI-DSI host\n"); + goto err_remove_phy; + } + + ret = component_add(&pdev->dev, &sun6i_dsi_ops); + if (ret) { + dev_err(dev, "Couldn't register our component\n"); + goto err_remove_dsi_host; + } + + return 0; + +err_remove_dsi_host: + mipi_dsi_host_unregister(&dsi->host); +err_remove_phy: + pm_runtime_disable(dev); + sun6i_dphy_remove(dsi); +err_unprotect_clk: + clk_rate_exclusive_put(dsi->mod_clk); + return ret; +} + +static int sun6i_dsi_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sun6i_dsi *dsi = dev_get_drvdata(dev); + + component_del(&pdev->dev, &sun6i_dsi_ops); + mipi_dsi_host_unregister(&dsi->host); + pm_runtime_disable(dev); + sun6i_dphy_remove(dsi); + clk_rate_exclusive_put(dsi->mod_clk); + + return 0; +} + +static int __maybe_unused sun6i_dsi_runtime_resume(struct device *dev) +{ + struct sun6i_dsi *dsi = dev_get_drvdata(dev); + + reset_control_deassert(dsi->reset); + clk_prepare_enable(dsi->mod_clk); + + /* + * Enable the DSI block. + * + * Some part of it can only be done once we get a number of + * lanes, see sun6i_dsi_inst_init + */ + regmap_write(dsi->regs, SUN6I_DSI_CTL_REG, SUN6I_DSI_CTL_EN); + + regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL0_REG, + SUN6I_DSI_BASIC_CTL0_ECC_EN | SUN6I_DSI_BASIC_CTL0_CRC_EN); + + regmap_write(dsi->regs, SUN6I_DSI_TRANS_START_REG, 10); + regmap_write(dsi->regs, SUN6I_DSI_TRANS_ZERO_REG, 0); + + if (dsi->device) + sun6i_dsi_inst_init(dsi, dsi->device); + + regmap_write(dsi->regs, SUN6I_DSI_DEBUG_DATA_REG, 0xff); + + return 0; +} + +static int __maybe_unused sun6i_dsi_runtime_suspend(struct device *dev) +{ + struct sun6i_dsi *dsi = dev_get_drvdata(dev); + + clk_disable_unprepare(dsi->mod_clk); + reset_control_assert(dsi->reset); + + return 0; +} + +static const struct dev_pm_ops sun6i_dsi_pm_ops = { + SET_RUNTIME_PM_OPS(sun6i_dsi_runtime_suspend, + sun6i_dsi_runtime_resume, + NULL) +}; + +static const struct of_device_id sun6i_dsi_of_table[] = { + { .compatible = "allwinner,sun6i-a31-mipi-dsi" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun6i_dsi_of_table); + +static struct platform_driver sun6i_dsi_platform_driver = { + .probe = sun6i_dsi_probe, + .remove = sun6i_dsi_remove, + .driver = { + .name = "sun6i-mipi-dsi", + .of_match_table = sun6i_dsi_of_table, + .pm = &sun6i_dsi_pm_ops, + }, +}; +module_platform_driver(sun6i_dsi_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A31 DSI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h new file mode 100644 index 000000000..dbbc5b3ec --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * Copyright (C) 2017-2018 Bootlin + * + * Maxime Ripard <maxime.ripard@bootlin.com> + */ + +#ifndef _SUN6I_MIPI_DSI_H_ +#define _SUN6I_MIPI_DSI_H_ + +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <drm/drm_mipi_dsi.h> + +struct sun6i_dphy { + struct clk *bus_clk; + struct clk *mod_clk; + struct regmap *regs; + struct reset_control *reset; +}; + +struct sun6i_dsi { + struct drm_connector connector; + struct drm_encoder encoder; + struct mipi_dsi_host host; + + struct clk *bus_clk; + struct clk *mod_clk; + struct regmap *regs; + struct reset_control *reset; + struct sun6i_dphy *dphy; + + struct device *dev; + struct sun4i_drv *drv; + struct mipi_dsi_device *device; + struct drm_panel *panel; +}; + +static inline struct sun6i_dsi *host_to_sun6i_dsi(struct mipi_dsi_host *host) +{ + return container_of(host, struct sun6i_dsi, host); +}; + +static inline struct sun6i_dsi *connector_to_sun6i_dsi(struct drm_connector *connector) +{ + return container_of(connector, struct sun6i_dsi, connector); +}; + +static inline struct sun6i_dsi *encoder_to_sun6i_dsi(const struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun6i_dsi, encoder); +}; + +int sun6i_dphy_probe(struct sun6i_dsi *dsi, struct device_node *node); +int sun6i_dphy_remove(struct sun6i_dsi *dsi); + +int sun6i_dphy_init(struct sun6i_dphy *dphy, unsigned int lanes); +int sun6i_dphy_power_on(struct sun6i_dphy *dphy, unsigned int lanes); +int sun6i_dphy_power_off(struct sun6i_dphy *dphy); +int sun6i_dphy_exit(struct sun6i_dphy *dphy); + +#endif /* _SUN6I_MIPI_DSI_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.c b/drivers/gpu/drm/sun4i/sun8i_csc.c new file mode 100644 index 000000000..b14925b40 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_csc.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> + +#include "sun8i_csc.h" +#include "sun8i_mixer.h" + +static const u32 ccsc_base[2][2] = { + {CCSC00_OFFSET, CCSC01_OFFSET}, + {CCSC10_OFFSET, CCSC11_OFFSET}, +}; + +/* + * Factors are in two's complement format, 10 bits for fractinal part. + * First tree values in each line are multiplication factor and last + * value is constant, which is added at the end. + */ +static const u32 yuv2rgb[] = { + 0x000004A8, 0x00000000, 0x00000662, 0xFFFC845A, + 0x000004A8, 0xFFFFFE6F, 0xFFFFFCBF, 0x00021DF4, + 0x000004A8, 0x00000813, 0x00000000, 0xFFFBAC4A, +}; + +static const u32 yvu2rgb[] = { + 0x000004A8, 0x00000662, 0x00000000, 0xFFFC845A, + 0x000004A8, 0xFFFFFCBF, 0xFFFFFE6F, 0x00021DF4, + 0x000004A8, 0x00000000, 0x00000813, 0xFFFBAC4A, +}; + +static void sun8i_csc_set_coefficients(struct regmap *map, u32 base, + enum sun8i_csc_mode mode) +{ + const u32 *table; + int i, data; + + switch (mode) { + case SUN8I_CSC_MODE_YUV2RGB: + table = yuv2rgb; + break; + case SUN8I_CSC_MODE_YVU2RGB: + table = yvu2rgb; + break; + default: + DRM_WARN("Wrong CSC mode specified.\n"); + return; + } + + for (i = 0; i < 12; i++) { + data = table[i]; + /* For some reason, 0x200 must be added to constant parts */ + if (((i + 1) & 3) == 0) + data += 0x200; + regmap_write(map, SUN8I_CSC_COEFF(base, i), data); + } +} + +static void sun8i_csc_enable(struct regmap *map, u32 base, bool enable) +{ + u32 val; + + if (enable) + val = SUN8I_CSC_CTRL_EN; + else + val = 0; + + regmap_update_bits(map, SUN8I_CSC_CTRL(base), SUN8I_CSC_CTRL_EN, val); +} + +void sun8i_csc_set_ccsc_coefficients(struct sun8i_mixer *mixer, int layer, + enum sun8i_csc_mode mode) +{ + u32 base; + + base = ccsc_base[mixer->cfg->ccsc][layer]; + + sun8i_csc_set_coefficients(mixer->engine.regs, base, mode); +} + +void sun8i_csc_enable_ccsc(struct sun8i_mixer *mixer, int layer, bool enable) +{ + u32 base; + + base = ccsc_base[mixer->cfg->ccsc][layer]; + + sun8i_csc_enable(mixer->engine.regs, base, enable); +} diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.h b/drivers/gpu/drm/sun4i/sun8i_csc.h new file mode 100644 index 000000000..242752b2d --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_csc.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN8I_CSC_H_ +#define _SUN8I_CSC_H_ + +struct sun8i_mixer; + +/* VI channel CSC units offsets */ +#define CCSC00_OFFSET 0xAA050 +#define CCSC01_OFFSET 0xFA050 +#define CCSC10_OFFSET 0xA0000 +#define CCSC11_OFFSET 0xF0000 + +#define SUN8I_CSC_CTRL(base) (base + 0x0) +#define SUN8I_CSC_COEFF(base, i) (base + 0x10 + 4 * i) + +#define SUN8I_CSC_CTRL_EN BIT(0) + +enum sun8i_csc_mode { + SUN8I_CSC_MODE_OFF, + SUN8I_CSC_MODE_YUV2RGB, + SUN8I_CSC_MODE_YVU2RGB, +}; + +void sun8i_csc_set_ccsc_coefficients(struct sun8i_mixer *mixer, int layer, + enum sun8i_csc_mode mode); +void sun8i_csc_enable_ccsc(struct sun8i_mixer *mixer, int layer, bool enable); + +#endif 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 000000000..5073622cb --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c @@ -0,0 +1,237 @@ +// 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" +#include "sun8i_tcon_top.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 bool sun8i_dw_hdmi_node_is_tcon_top(struct device_node *node) +{ + return IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && + !!of_match_node(sun8i_tcon_top_of_table, node); +} + +static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm, + struct device_node *node) +{ + struct device_node *port, *ep, *remote, *remote_port; + u32 crtcs = 0; + + remote = of_graph_get_remote_node(node, 0, -1); + if (!remote) + return 0; + + if (sun8i_dw_hdmi_node_is_tcon_top(remote)) { + port = of_graph_get_port_by_id(remote, 4); + if (!port) + goto crtcs_exit; + + for_each_child_of_node(port, ep) { + remote_port = of_graph_get_remote_port(ep); + if (remote_port) { + crtcs |= drm_of_crtc_port_mask(drm, remote_port); + of_node_put(remote_port); + } + } + } else { + crtcs = drm_of_find_possible_crtcs(drm, node); + } + +crtcs_exit: + of_node_put(remote); + + return crtcs; +} + +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 = + sun8i_dw_hdmi_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"); + ret = -EINVAL; + 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); + +static 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 000000000..aadbe0a10 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h @@ -0,0 +1,197 @@ +/* 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_MSK BIT(26) +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT 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; + bool has_second_pll; + 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; + struct clk *clk_pll1; + 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, + bool second_parent); + +#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 000000000..dc9b1398a --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c @@ -0,0 +1,583 @@ +// 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) | + SUN8I_HDMI_PHY_ANA_CFG3_REG_EMP(3); + } + + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, + SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0); + + /* + * NOTE: We have to be careful not to overwrite PHY parent + * clock selection bit and clock divider. + */ + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, + (u32)~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, + 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); + + /* reset PHY PLL clock parent */ + regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, + SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, 0); + + /* 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 sun50i_a64_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 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,sun50i-a64-hdmi-phy", + .data = &sun50i_a64_hdmi_phy, + }, + { + .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; + } + + if (phy->variant->has_second_pll) { + phy->clk_pll1 = of_clk_get_by_name(node, "pll-1"); + if (IS_ERR(phy->clk_pll1)) { + dev_err(dev, "Could not get pll-1 clock\n"); + ret = PTR_ERR(phy->clk_pll1); + 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_pll1; + } + + 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; + } + + if (phy->variant->has_phy_clk) { + ret = sun8i_phy_clk_create(phy, dev, + phy->variant->has_second_pll); + if (ret) { + dev_err(dev, "Couldn't create the PHY clock\n"); + goto err_disable_clk_mod; + } + + clk_prepare_enable(phy->clk_phy); + } + + hdmi->phy = phy; + + return 0; + +err_disable_clk_mod: + clk_disable_unprepare(phy->clk_mod); +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_pll1: + clk_put(phy->clk_pll1); +err_put_clk_pll0: + 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); + clk_disable_unprepare(phy->clk_phy); + + reset_control_assert(phy->rst_phy); + + reset_control_put(phy->rst_phy); + + clk_put(phy->clk_pll0); + clk_put(phy->clk_pll1); + 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 000000000..a4d31fe3a --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c @@ -0,0 +1,178 @@ +// 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 *best_parent = NULL; + struct clk_hw *parent; + int best_div = 1; + int i, p; + + for (p = 0; p < clk_hw_get_num_parents(hw); p++) { + parent = clk_hw_get_parent_by_index(hw, p); + if (!parent) + continue; + + 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; + best_parent = parent; + break; + } + + if (!best_rate || + abs(rate - rounded / i) < + abs(rate - best_rate / best_div)) { + best_rate = rounded; + best_div = i; + best_parent = parent; + } + } + + if (best_rate / best_div == rate) + break; + } + + req->rate = best_rate / best_div; + req->best_parent_rate = best_rate; + req->best_parent_hw = best_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 u8 sun8i_phy_clk_get_parent(struct clk_hw *hw) +{ + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw); + u32 reg; + + regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, ®); + reg = (reg & SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK) >> + SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT; + + return reg; +} + +static int sun8i_phy_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct sun8i_phy_clk *priv = hw_to_phy_clk(hw); + + if (index > 1) + return -EINVAL; + + regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, + SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, + index << SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT); + + 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, + + .get_parent = sun8i_phy_clk_get_parent, + .set_parent = sun8i_phy_clk_set_parent, +}; + +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev, + bool second_parent) +{ + struct clk_init_data init; + struct sun8i_phy_clk *priv; + const char *parents[2]; + + parents[0] = __clk_get_name(phy->clk_pll0); + if (!parents[0]) + return -ENODEV; + + if (second_parent) { + parents[1] = __clk_get_name(phy->clk_pll1); + if (!parents[1]) + 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 = second_parent ? 2 : 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 new file mode 100644 index 000000000..649b57e5e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> + * + * Based on sun4i_backend.c, which is: + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> + +#include <linux/component.h> +#include <linux/dma-mapping.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/reset.h> + +#include "sun4i_drv.h" +#include "sun8i_mixer.h" +#include "sun8i_ui_layer.h" +#include "sun8i_vi_layer.h" +#include "sunxi_engine.h" + +static const struct de2_fmt_info de2_formats[] = { + { + .drm_fmt = DRM_FORMAT_ARGB8888, + .de2_fmt = SUN8I_MIXER_FBFMT_ARGB8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_ABGR8888, + .de2_fmt = SUN8I_MIXER_FBFMT_ABGR8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_RGBA8888, + .de2_fmt = SUN8I_MIXER_FBFMT_RGBA8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_BGRA8888, + .de2_fmt = SUN8I_MIXER_FBFMT_BGRA8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_XRGB8888, + .de2_fmt = SUN8I_MIXER_FBFMT_XRGB8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_XBGR8888, + .de2_fmt = SUN8I_MIXER_FBFMT_XBGR8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_RGBX8888, + .de2_fmt = SUN8I_MIXER_FBFMT_RGBX8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_BGRX8888, + .de2_fmt = SUN8I_MIXER_FBFMT_BGRX8888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_RGB888, + .de2_fmt = SUN8I_MIXER_FBFMT_RGB888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_BGR888, + .de2_fmt = SUN8I_MIXER_FBFMT_BGR888, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_RGB565, + .de2_fmt = SUN8I_MIXER_FBFMT_RGB565, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_BGR565, + .de2_fmt = SUN8I_MIXER_FBFMT_BGR565, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_ARGB4444, + .de2_fmt = SUN8I_MIXER_FBFMT_ARGB4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_XRGB4444, + .de2_fmt = SUN8I_MIXER_FBFMT_ARGB4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_ABGR4444, + .de2_fmt = SUN8I_MIXER_FBFMT_ABGR4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_XBGR4444, + .de2_fmt = SUN8I_MIXER_FBFMT_ABGR4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_RGBA4444, + .de2_fmt = SUN8I_MIXER_FBFMT_RGBA4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_RGBX4444, + .de2_fmt = SUN8I_MIXER_FBFMT_RGBA4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_BGRA4444, + .de2_fmt = SUN8I_MIXER_FBFMT_BGRA4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_BGRX4444, + .de2_fmt = SUN8I_MIXER_FBFMT_BGRA4444, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_ARGB1555, + .de2_fmt = SUN8I_MIXER_FBFMT_ARGB1555, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_XRGB1555, + .de2_fmt = SUN8I_MIXER_FBFMT_ARGB1555, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_ABGR1555, + .de2_fmt = SUN8I_MIXER_FBFMT_ABGR1555, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_XBGR1555, + .de2_fmt = SUN8I_MIXER_FBFMT_ABGR1555, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_RGBA5551, + .de2_fmt = SUN8I_MIXER_FBFMT_RGBA5551, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_RGBX5551, + .de2_fmt = SUN8I_MIXER_FBFMT_RGBA5551, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_BGRA5551, + .de2_fmt = SUN8I_MIXER_FBFMT_BGRA5551, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + /* for DE2 VI layer which ignores alpha */ + .drm_fmt = DRM_FORMAT_BGRX5551, + .de2_fmt = SUN8I_MIXER_FBFMT_BGRA5551, + .rgb = true, + .csc = SUN8I_CSC_MODE_OFF, + }, + { + .drm_fmt = DRM_FORMAT_UYVY, + .de2_fmt = SUN8I_MIXER_FBFMT_UYVY, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_VYUY, + .de2_fmt = SUN8I_MIXER_FBFMT_VYUY, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YUYV, + .de2_fmt = SUN8I_MIXER_FBFMT_YUYV, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YVYU, + .de2_fmt = SUN8I_MIXER_FBFMT_YVYU, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_NV16, + .de2_fmt = SUN8I_MIXER_FBFMT_NV16, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_NV61, + .de2_fmt = SUN8I_MIXER_FBFMT_NV61, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_NV12, + .de2_fmt = SUN8I_MIXER_FBFMT_NV12, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_NV21, + .de2_fmt = SUN8I_MIXER_FBFMT_NV21, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YUV422, + .de2_fmt = SUN8I_MIXER_FBFMT_YUV422, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YUV420, + .de2_fmt = SUN8I_MIXER_FBFMT_YUV420, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YUV411, + .de2_fmt = SUN8I_MIXER_FBFMT_YUV411, + .rgb = false, + .csc = SUN8I_CSC_MODE_YUV2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YVU422, + .de2_fmt = SUN8I_MIXER_FBFMT_YUV422, + .rgb = false, + .csc = SUN8I_CSC_MODE_YVU2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YVU420, + .de2_fmt = SUN8I_MIXER_FBFMT_YUV420, + .rgb = false, + .csc = SUN8I_CSC_MODE_YVU2RGB, + }, + { + .drm_fmt = DRM_FORMAT_YVU411, + .de2_fmt = SUN8I_MIXER_FBFMT_YUV411, + .rgb = false, + .csc = SUN8I_CSC_MODE_YVU2RGB, + }, +}; + +const struct de2_fmt_info *sun8i_mixer_format_info(u32 format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(de2_formats); ++i) + if (de2_formats[i].drm_fmt == format) + return &de2_formats[i]; + + return NULL; +} + +static void sun8i_mixer_commit(struct sunxi_engine *engine) +{ + DRM_DEBUG_DRIVER("Committing changes\n"); + + regmap_write(engine->regs, SUN8I_MIXER_GLOBAL_DBUFF, + SUN8I_MIXER_GLOBAL_DBUFF_ENABLE); +} + +static struct drm_plane **sun8i_layers_init(struct drm_device *drm, + struct sunxi_engine *engine) +{ + struct drm_plane **planes; + struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine); + int i; + + planes = devm_kcalloc(drm->dev, + mixer->cfg->vi_num + mixer->cfg->ui_num + 1, + sizeof(*planes), GFP_KERNEL); + if (!planes) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < mixer->cfg->vi_num; i++) { + struct sun8i_vi_layer *layer; + + layer = sun8i_vi_layer_init_one(drm, mixer, i); + if (IS_ERR(layer)) { + dev_err(drm->dev, + "Couldn't initialize overlay plane\n"); + return ERR_CAST(layer); + }; + + planes[i] = &layer->plane; + }; + + for (i = 0; i < mixer->cfg->ui_num; i++) { + struct sun8i_ui_layer *layer; + + layer = sun8i_ui_layer_init_one(drm, mixer, i); + if (IS_ERR(layer)) { + dev_err(drm->dev, "Couldn't initialize %s plane\n", + i ? "overlay" : "primary"); + return ERR_CAST(layer); + }; + + planes[mixer->cfg->vi_num + i] = &layer->plane; + }; + + return planes; +} + +static const struct sunxi_engine_ops sun8i_engine_ops = { + .commit = sun8i_mixer_commit, + .layers_init = sun8i_layers_init, +}; + +static struct regmap_config sun8i_mixer_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0xffffc, /* guessed */ +}; + +static int sun8i_mixer_of_get_id(struct device_node *node) +{ + struct device_node *port, *ep; + int ret = -EINVAL; + + /* output is port 1 */ + port = of_graph_get_port_by_id(node, 1); + if (!port) + return -EINVAL; + + /* try to find downstream endpoint */ + for_each_available_child_of_node(port, ep) { + struct device_node *remote; + u32 reg; + + remote = of_graph_get_remote_endpoint(ep); + if (!remote) + continue; + + ret = of_property_read_u32(remote, "reg", ®); + if (!ret) { + of_node_put(remote); + of_node_put(ep); + of_node_put(port); + + return reg; + } + + of_node_put(remote); + } + + of_node_put(port); + + return ret; +} + +static int sun8i_mixer_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun8i_mixer *mixer; + struct resource *res; + void __iomem *regs; + int plane_cnt; + int i, ret; + + /* + * The mixer uses single 32-bit register to store memory + * addresses, so that it cannot deal with 64-bit memory + * addresses. + * Restrict the DMA mask so that the mixer won't be + * allocated some memory that is too high. + */ + ret = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "Cannot do 32-bit DMA.\n"); + return ret; + } + + mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + dev_set_drvdata(dev, mixer); + mixer->engine.ops = &sun8i_engine_ops; + mixer->engine.node = dev->of_node; + + /* + * While this function can fail, we shouldn't do anything + * if this happens. Some early DE2 DT entries don't provide + * mixer id but work nevertheless because matching between + * TCON and mixer is done by comparing node pointers (old + * way) instead comparing ids. If this function fails and + * id is needed, it will fail during id matching anyway. + */ + mixer->engine.id = sun8i_mixer_of_get_id(dev->of_node); + + mixer->cfg = of_device_get_match_data(dev); + if (!mixer->cfg) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mixer->engine.regs = devm_regmap_init_mmio(dev, regs, + &sun8i_mixer_regmap_config); + if (IS_ERR(mixer->engine.regs)) { + dev_err(dev, "Couldn't create the mixer regmap\n"); + return PTR_ERR(mixer->engine.regs); + } + + mixer->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(mixer->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(mixer->reset); + } + + ret = reset_control_deassert(mixer->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + mixer->bus_clk = devm_clk_get(dev, "bus"); + if (IS_ERR(mixer->bus_clk)) { + dev_err(dev, "Couldn't get the mixer bus clock\n"); + ret = PTR_ERR(mixer->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(mixer->bus_clk); + + mixer->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(mixer->mod_clk)) { + dev_err(dev, "Couldn't get the mixer module clock\n"); + ret = PTR_ERR(mixer->mod_clk); + goto err_disable_bus_clk; + } + + /* + * It seems that we need to enforce that rate for whatever + * reason for the mixer to be functional. Make sure it's the + * case. + */ + if (mixer->cfg->mod_rate) + clk_set_rate(mixer->mod_clk, mixer->cfg->mod_rate); + + clk_prepare_enable(mixer->mod_clk); + + list_add_tail(&mixer->engine.list, &drv->engine_list); + + /* Reset the registers */ + for (i = 0x0; i < 0x20000; i += 4) + regmap_write(mixer->engine.regs, i, 0); + + /* Enable the mixer */ + regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_CTL, + SUN8I_MIXER_GLOBAL_CTL_RT_EN); + + /* Set background color to black */ + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_BKCOLOR, + SUN8I_MIXER_BLEND_COLOR_BLACK); + + /* + * Set fill color of bottom plane to black. Generally not needed + * except when VI plane is at bottom (zpos = 0) and enabled. + */ + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_PIPE_CTL, + SUN8I_MIXER_BLEND_PIPE_CTL_FC_EN(0)); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_ATTR_FCOLOR(0), + SUN8I_MIXER_BLEND_COLOR_BLACK); + + plane_cnt = mixer->cfg->vi_num + mixer->cfg->ui_num; + for (i = 0; i < plane_cnt; i++) + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_MODE(i), + SUN8I_MIXER_BLEND_MODE_DEF); + + regmap_update_bits(mixer->engine.regs, SUN8I_MIXER_BLEND_PIPE_CTL, + SUN8I_MIXER_BLEND_PIPE_CTL_EN_MSK, 0); + + return 0; + +err_disable_bus_clk: + clk_disable_unprepare(mixer->bus_clk); +err_assert_reset: + reset_control_assert(mixer->reset); + return ret; +} + +static void sun8i_mixer_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun8i_mixer *mixer = dev_get_drvdata(dev); + + list_del(&mixer->engine.list); + + clk_disable_unprepare(mixer->mod_clk); + clk_disable_unprepare(mixer->bus_clk); + reset_control_assert(mixer->reset); +} + +static const struct component_ops sun8i_mixer_ops = { + .bind = sun8i_mixer_bind, + .unbind = sun8i_mixer_unbind, +}; + +static int sun8i_mixer_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun8i_mixer_ops); +} + +static int sun8i_mixer_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun8i_mixer_ops); + + return 0; +} + +static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = { + .ccsc = 0, + .scaler_mask = 0xf, + .ui_num = 3, + .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, + .scaler_mask = 0x3, + .ccsc = 0, + .mod_rate = 150000000, +}; + +static const struct of_device_id sun8i_mixer_of_table[] = { + { + .compatible = "allwinner,sun8i-a83t-de2-mixer-0", + .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, + }, + { } +}; +MODULE_DEVICE_TABLE(of, sun8i_mixer_of_table); + +static struct platform_driver sun8i_mixer_platform_driver = { + .probe = sun8i_mixer_probe, + .remove = sun8i_mixer_remove, + .driver = { + .name = "sun8i-mixer", + .of_match_table = sun8i_mixer_of_table, + }, +}; +module_platform_driver(sun8i_mixer_platform_driver); + +MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>"); +MODULE_DESCRIPTION("Allwinner DE2 Mixer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h new file mode 100644 index 000000000..406c42e75 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN8I_MIXER_H_ +#define _SUN8I_MIXER_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sun8i_csc.h" +#include "sunxi_engine.h" + +#define SUN8I_MIXER_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1)) +#define SUN8I_MIXER_COORD(x, y) ((y) << 16 | (x)) + +#define SUN8I_MIXER_GLOBAL_CTL 0x0 +#define SUN8I_MIXER_GLOBAL_STATUS 0x4 +#define SUN8I_MIXER_GLOBAL_DBUFF 0x8 +#define SUN8I_MIXER_GLOBAL_SIZE 0xc + +#define SUN8I_MIXER_GLOBAL_CTL_RT_EN BIT(0) + +#define SUN8I_MIXER_GLOBAL_DBUFF_ENABLE BIT(0) + +#define SUN8I_MIXER_BLEND_PIPE_CTL 0x1000 +#define SUN8I_MIXER_BLEND_ATTR_FCOLOR(x) (0x1004 + 0x10 * (x) + 0x0) +#define SUN8I_MIXER_BLEND_ATTR_INSIZE(x) (0x1004 + 0x10 * (x) + 0x4) +#define SUN8I_MIXER_BLEND_ATTR_COORD(x) (0x1004 + 0x10 * (x) + 0x8) +#define SUN8I_MIXER_BLEND_ROUTE 0x1080 +#define SUN8I_MIXER_BLEND_PREMULTIPLY 0x1084 +#define SUN8I_MIXER_BLEND_BKCOLOR 0x1088 +#define SUN8I_MIXER_BLEND_OUTSIZE 0x108c +#define SUN8I_MIXER_BLEND_MODE(x) (0x1090 + 0x04 * (x)) +#define SUN8I_MIXER_BLEND_CK_CTL 0x10b0 +#define SUN8I_MIXER_BLEND_CK_CFG 0x10b4 +#define SUN8I_MIXER_BLEND_CK_MAX(x) (0x10c0 + 0x04 * (x)) +#define SUN8I_MIXER_BLEND_CK_MIN(x) (0x10e0 + 0x04 * (x)) +#define SUN8I_MIXER_BLEND_OUTCTL 0x10fc + +#define SUN8I_MIXER_BLEND_PIPE_CTL_EN_MSK GENMASK(12, 8) +#define SUN8I_MIXER_BLEND_PIPE_CTL_EN(pipe) BIT(8 + pipe) +#define SUN8I_MIXER_BLEND_PIPE_CTL_FC_EN(pipe) BIT(pipe) +/* colors are always in AARRGGBB format */ +#define SUN8I_MIXER_BLEND_COLOR_BLACK 0xff000000 +/* The following numbers are some still unknown magic numbers */ +#define SUN8I_MIXER_BLEND_MODE_DEF 0x03010301 + +#define SUN8I_MIXER_BLEND_ROUTE_PIPE_MSK(n) (0xf << ((n) << 2)) +#define SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(n) ((n) << 2) + +#define SUN8I_MIXER_BLEND_OUTCTL_INTERLACED BIT(1) + +#define SUN8I_MIXER_FBFMT_ARGB8888 0 +#define SUN8I_MIXER_FBFMT_ABGR8888 1 +#define SUN8I_MIXER_FBFMT_RGBA8888 2 +#define SUN8I_MIXER_FBFMT_BGRA8888 3 +#define SUN8I_MIXER_FBFMT_XRGB8888 4 +#define SUN8I_MIXER_FBFMT_XBGR8888 5 +#define SUN8I_MIXER_FBFMT_RGBX8888 6 +#define SUN8I_MIXER_FBFMT_BGRX8888 7 +#define SUN8I_MIXER_FBFMT_RGB888 8 +#define SUN8I_MIXER_FBFMT_BGR888 9 +#define SUN8I_MIXER_FBFMT_RGB565 10 +#define SUN8I_MIXER_FBFMT_BGR565 11 +#define SUN8I_MIXER_FBFMT_ARGB4444 12 +#define SUN8I_MIXER_FBFMT_ABGR4444 13 +#define SUN8I_MIXER_FBFMT_RGBA4444 14 +#define SUN8I_MIXER_FBFMT_BGRA4444 15 +#define SUN8I_MIXER_FBFMT_ARGB1555 16 +#define SUN8I_MIXER_FBFMT_ABGR1555 17 +#define SUN8I_MIXER_FBFMT_RGBA5551 18 +#define SUN8I_MIXER_FBFMT_BGRA5551 19 + +#define SUN8I_MIXER_FBFMT_YUYV 0 +#define SUN8I_MIXER_FBFMT_UYVY 1 +#define SUN8I_MIXER_FBFMT_YVYU 2 +#define SUN8I_MIXER_FBFMT_VYUY 3 +#define SUN8I_MIXER_FBFMT_NV16 4 +#define SUN8I_MIXER_FBFMT_NV61 5 +#define SUN8I_MIXER_FBFMT_YUV422 6 +/* format 7 doesn't exist */ +#define SUN8I_MIXER_FBFMT_NV12 8 +#define SUN8I_MIXER_FBFMT_NV21 9 +#define SUN8I_MIXER_FBFMT_YUV420 10 +/* format 11 doesn't exist */ +/* format 12 is semi-planar YUV411 UVUV */ +/* format 13 is semi-planar YUV411 VUVU */ +#define SUN8I_MIXER_FBFMT_YUV411 14 + +/* + * These sub-engines are still unknown now, the EN registers are here only to + * be used to disable these sub-engines. + */ +#define SUN8I_MIXER_FCE_EN 0xa0000 +#define SUN8I_MIXER_BWS_EN 0xa2000 +#define SUN8I_MIXER_LTI_EN 0xa4000 +#define SUN8I_MIXER_PEAK_EN 0xa6000 +#define SUN8I_MIXER_ASE_EN 0xa8000 +#define SUN8I_MIXER_FCC_EN 0xaa000 +#define SUN8I_MIXER_DCSC_EN 0xb0000 + +struct de2_fmt_info { + u32 drm_fmt; + u32 de2_fmt; + bool rgb; + enum sun8i_csc_mode csc; +}; + +/** + * struct sun8i_mixer_cfg - mixer HW configuration + * @vi_num: number of VI channels + * @ui_num: number of UI channels + * @scaler_mask: bitmask which tells which channel supports scaling + * First, scaler supports for VI channels is defined and after that, scaler + * support for UI channels. For example, if mixer has 2 VI channels without + * scaler and 2 UI channels with scaler, bitmask would be 0xC. + * @ccsc: select set of CCSC base addresses + * Set value to 0 if this is first mixer or second mixer with VEP support. + * Set value to 1 if this is second mixer without VEP support. Other values + * are invalid. + * @mod_rate: module clock rate that needs to be set in order to have + * a functional block. + */ +struct sun8i_mixer_cfg { + int vi_num; + int ui_num; + int scaler_mask; + int ccsc; + unsigned long mod_rate; +}; + +struct sun8i_mixer { + struct sunxi_engine engine; + + const struct sun8i_mixer_cfg *cfg; + + struct reset_control *reset; + + struct clk *bus_clk; + struct clk *mod_clk; +}; + +static inline struct sun8i_mixer * +engine_to_sun8i_mixer(struct sunxi_engine *engine) +{ + return container_of(engine, struct sun8i_mixer, engine); +} + +const struct de2_fmt_info *sun8i_mixer_format_info(u32 format); +#endif /* _SUN8I_MIXER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c new file mode 100644 index 000000000..dfbcd1ad8 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */ + +#include <drm/drmP.h> + +#include <dt-bindings/clock/sun8i-tcon-top.h> + +#include <linux/bitfield.h> +#include <linux/component.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> + +#include "sun8i_tcon_top.h" + +static bool sun8i_tcon_top_node_is_tcon_top(struct device_node *node) +{ + return !!of_match_node(sun8i_tcon_top_of_table, node); +} + +int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon) +{ + struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); + unsigned long flags; + u32 val; + + if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) { + dev_err(dev, "Device is not TCON TOP!\n"); + return -EINVAL; + } + + if (tcon < 2 || tcon > 3) { + dev_err(dev, "TCON index must be 2 or 3!\n"); + return -EINVAL; + } + + spin_lock_irqsave(&tcon_top->reg_lock, flags); + + val = readl(tcon_top->regs + TCON_TOP_GATE_SRC_REG); + val &= ~TCON_TOP_HDMI_SRC_MSK; + val |= FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, tcon - 1); + writel(val, tcon_top->regs + TCON_TOP_GATE_SRC_REG); + + spin_unlock_irqrestore(&tcon_top->reg_lock, flags); + + return 0; +} +EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src); + +int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon) +{ + struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); + unsigned long flags; + u32 reg; + + if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) { + dev_err(dev, "Device is not TCON TOP!\n"); + return -EINVAL; + } + + if (mixer > 1) { + dev_err(dev, "Mixer index is too high!\n"); + return -EINVAL; + } + + if (tcon > 3) { + dev_err(dev, "TCON index is too high!\n"); + return -EINVAL; + } + + spin_lock_irqsave(&tcon_top->reg_lock, flags); + + reg = readl(tcon_top->regs + TCON_TOP_PORT_SEL_REG); + if (mixer == 0) { + reg &= ~TCON_TOP_PORT_DE0_MSK; + reg |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, tcon); + } else { + reg &= ~TCON_TOP_PORT_DE1_MSK; + reg |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, tcon); + } + writel(reg, tcon_top->regs + TCON_TOP_PORT_SEL_REG); + + spin_unlock_irqrestore(&tcon_top->reg_lock, flags); + + return 0; +} +EXPORT_SYMBOL(sun8i_tcon_top_de_config); + + +static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev, + const char *parent, + void __iomem *regs, + spinlock_t *lock, + u8 bit, int name_index) +{ + const char *clk_name, *parent_name; + int ret, index; + + index = of_property_match_string(dev->of_node, "clock-names", parent); + if (index < 0) + return ERR_PTR(index); + + parent_name = of_clk_get_parent_name(dev->of_node, index); + + ret = of_property_read_string_index(dev->of_node, + "clock-output-names", name_index, + &clk_name); + if (ret) + return ERR_PTR(ret); + + return clk_hw_register_gate(dev, clk_name, parent_name, + CLK_SET_RATE_PARENT, + regs + TCON_TOP_GATE_SRC_REG, + bit, 0, lock); +}; + +static int sun8i_tcon_top_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct clk_hw_onecell_data *clk_data; + struct sun8i_tcon_top *tcon_top; + struct resource *res; + void __iomem *regs; + int ret, i; + + tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL); + if (!tcon_top) + return -ENOMEM; + + clk_data = devm_kzalloc(dev, sizeof(*clk_data) + + sizeof(*clk_data->hws) * CLK_NUM, + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + tcon_top->clk_data = clk_data; + + spin_lock_init(&tcon_top->reg_lock); + + tcon_top->rst = devm_reset_control_get(dev, NULL); + if (IS_ERR(tcon_top->rst)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(tcon_top->rst); + } + + tcon_top->bus = devm_clk_get(dev, "bus"); + if (IS_ERR(tcon_top->bus)) { + dev_err(dev, "Couldn't get the bus clock\n"); + return PTR_ERR(tcon_top->bus); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + tcon_top->regs = regs; + if (IS_ERR(regs)) + return PTR_ERR(regs); + + ret = reset_control_deassert(tcon_top->rst); + if (ret) { + dev_err(dev, "Could not deassert ctrl reset control\n"); + return ret; + } + + ret = clk_prepare_enable(tcon_top->bus); + if (ret) { + dev_err(dev, "Could not enable bus clock\n"); + goto err_assert_reset; + } + + /* + * At least on H6, some registers have some bits set by default + * which may cause issues. Clear them here. + */ + writel(0, regs + TCON_TOP_PORT_SEL_REG); + writel(0, regs + TCON_TOP_GATE_SRC_REG); + + /* + * TCON TOP has two muxes, which select parent clock for each TCON TV + * channel clock. Parent could be either TCON TV or TVE clock. For now + * we leave this fixed to TCON TV, since TVE driver for R40 is not yet + * implemented. Once it is, graph needs to be traversed to determine + * if TVE is active on each TCON TV. If it is, mux should be switched + * to TVE clock parent. + */ + clk_data->hws[CLK_TCON_TOP_TV0] = + sun8i_tcon_top_register_gate(dev, "tcon-tv0", regs, + &tcon_top->reg_lock, + TCON_TOP_TCON_TV0_GATE, 0); + + clk_data->hws[CLK_TCON_TOP_TV1] = + sun8i_tcon_top_register_gate(dev, "tcon-tv1", regs, + &tcon_top->reg_lock, + TCON_TOP_TCON_TV1_GATE, 1); + + clk_data->hws[CLK_TCON_TOP_DSI] = + sun8i_tcon_top_register_gate(dev, "dsi", regs, + &tcon_top->reg_lock, + TCON_TOP_TCON_DSI_GATE, 2); + + for (i = 0; i < CLK_NUM; i++) + if (IS_ERR(clk_data->hws[i])) { + ret = PTR_ERR(clk_data->hws[i]); + goto err_unregister_gates; + } + + clk_data->num = CLK_NUM; + + ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, + clk_data); + if (ret) + goto err_unregister_gates; + + dev_set_drvdata(dev, tcon_top); + + return 0; + +err_unregister_gates: + for (i = 0; i < CLK_NUM; i++) + if (!IS_ERR_OR_NULL(clk_data->hws[i])) + clk_hw_unregister_gate(clk_data->hws[i]); + clk_disable_unprepare(tcon_top->bus); +err_assert_reset: + reset_control_assert(tcon_top->rst); + + return ret; +} + +static void sun8i_tcon_top_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); + struct clk_hw_onecell_data *clk_data = tcon_top->clk_data; + int i; + + of_clk_del_provider(dev->of_node); + for (i = 0; i < CLK_NUM; i++) + if (clk_data->hws[i]) + clk_hw_unregister_gate(clk_data->hws[i]); + + clk_disable_unprepare(tcon_top->bus); + reset_control_assert(tcon_top->rst); +} + +static const struct component_ops sun8i_tcon_top_ops = { + .bind = sun8i_tcon_top_bind, + .unbind = sun8i_tcon_top_unbind, +}; + +static int sun8i_tcon_top_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun8i_tcon_top_ops); +} + +static int sun8i_tcon_top_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun8i_tcon_top_ops); + + return 0; +} + +/* sun4i_drv uses this list to check if a device node is a TCON TOP */ +const struct of_device_id sun8i_tcon_top_of_table[] = { + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table); +EXPORT_SYMBOL(sun8i_tcon_top_of_table); + +static struct platform_driver sun8i_tcon_top_platform_driver = { + .probe = sun8i_tcon_top_probe, + .remove = sun8i_tcon_top_remove, + .driver = { + .name = "sun8i-tcon-top", + .of_match_table = sun8i_tcon_top_of_table, + }, +}; +module_platform_driver(sun8i_tcon_top_platform_driver); + +MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>"); +MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h new file mode 100644 index 000000000..0390584a3 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */ + +#ifndef _SUN8I_TCON_TOP_H_ +#define _SUN8I_TCON_TOP_H_ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#define TCON_TOP_TCON_TV_SETUP_REG 0x00 + +#define TCON_TOP_PORT_SEL_REG 0x1C +#define TCON_TOP_PORT_DE0_MSK GENMASK(1, 0) +#define TCON_TOP_PORT_DE1_MSK GENMASK(5, 4) + +#define TCON_TOP_GATE_SRC_REG 0x20 +#define TCON_TOP_HDMI_SRC_MSK GENMASK(29, 28) +#define TCON_TOP_TCON_TV1_GATE 24 +#define TCON_TOP_TCON_TV0_GATE 20 +#define TCON_TOP_TCON_DSI_GATE 16 + +#define CLK_NUM 3 + +struct sun8i_tcon_top { + struct clk *bus; + struct clk_hw_onecell_data *clk_data; + void __iomem *regs; + struct reset_control *rst; + + /* + * spinlock is used to synchronize access to same + * register where multiple clock gates can be set. + */ + spinlock_t reg_lock; +}; + +extern const struct of_device_id sun8i_tcon_top_of_table[]; + +int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon); +int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon); + +#endif /* _SUN8I_TCON_TOP_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c new file mode 100644 index 000000000..28c15c6ef --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -0,0 +1,370 @@ +/* + * Copyright (C) Icenowy Zheng <icenowy@aosc.io> + * + * Based on sun4i_layer.h, which is: + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "sun8i_ui_layer.h" +#include "sun8i_mixer.h" +#include "sun8i_ui_scaler.h" + +static void sun8i_ui_layer_enable(struct sun8i_mixer *mixer, int channel, + int overlay, bool enable, unsigned int zpos, + unsigned int old_zpos) +{ + u32 val; + + DRM_DEBUG_DRIVER("%sabling channel %d overlay %d\n", + enable ? "En" : "Dis", channel, overlay); + + if (enable) + val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; + else + val = 0; + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(channel, overlay), + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN, val); + + if (!enable || zpos != old_zpos) { + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_PIPE_CTL, + SUN8I_MIXER_BLEND_PIPE_CTL_EN(old_zpos), + 0); + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_ROUTE, + SUN8I_MIXER_BLEND_ROUTE_PIPE_MSK(old_zpos), + 0); + } + + if (enable) { + val = SUN8I_MIXER_BLEND_PIPE_CTL_EN(zpos); + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_PIPE_CTL, val, val); + + val = channel << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos); + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_ROUTE, + SUN8I_MIXER_BLEND_ROUTE_PIPE_MSK(zpos), + val); + } +} + +static int sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane, + unsigned int zpos) +{ + struct drm_plane_state *state = plane->state; + u32 src_w, src_h, dst_w, dst_h; + u32 outsize, insize; + u32 hphase, vphase; + + DRM_DEBUG_DRIVER("Updating UI channel %d overlay %d\n", + channel, overlay); + + src_w = drm_rect_width(&state->src) >> 16; + src_h = drm_rect_height(&state->src) >> 16; + dst_w = drm_rect_width(&state->dst); + dst_h = drm_rect_height(&state->dst); + + hphase = state->src.x1 & 0xffff; + vphase = state->src.y1 & 0xffff; + + insize = SUN8I_MIXER_SIZE(src_w, src_h); + outsize = SUN8I_MIXER_SIZE(dst_w, dst_h); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + bool interlaced = false; + u32 val; + + DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", + dst_w, dst_h); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_GLOBAL_SIZE, + outsize); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_OUTSIZE, + outsize); + + if (state->crtc) + interlaced = state->crtc->state->adjusted_mode.flags + & DRM_MODE_FLAG_INTERLACE; + + if (interlaced) + val = SUN8I_MIXER_BLEND_OUTCTL_INTERLACED; + else + val = 0; + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_OUTCTL, + SUN8I_MIXER_BLEND_OUTCTL_INTERLACED, + val); + + DRM_DEBUG_DRIVER("Switching display mixer interlaced mode %s\n", + interlaced ? "on" : "off"); + } + + /* Set height and width */ + DRM_DEBUG_DRIVER("Layer source offset X: %d Y: %d\n", + state->src.x1 >> 16, state->src.y1 >> 16); + DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_SIZE(channel, overlay), + insize); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_OVL_SIZE(channel), + insize); + + if (insize != outsize || hphase || vphase) { + u32 hscale, vscale; + + DRM_DEBUG_DRIVER("HW scaling is enabled\n"); + + hscale = state->src_w / state->crtc_w; + vscale = state->src_h / state->crtc_h; + + sun8i_ui_scaler_setup(mixer, channel, src_w, src_h, dst_w, + dst_h, hscale, vscale, hphase, vphase); + sun8i_ui_scaler_enable(mixer, channel, true); + } else { + DRM_DEBUG_DRIVER("HW scaling is not needed\n"); + sun8i_ui_scaler_enable(mixer, channel, false); + } + + /* Set base coordinates */ + DRM_DEBUG_DRIVER("Layer destination coordinates X: %d Y: %d\n", + state->dst.x1, state->dst.y1); + DRM_DEBUG_DRIVER("Layer destination size W: %d H: %d\n", dst_w, dst_h); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_BLEND_ATTR_COORD(zpos), + SUN8I_MIXER_COORD(state->dst.x1, state->dst.y1)); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_BLEND_ATTR_INSIZE(zpos), + outsize); + + return 0; +} + +static int sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + const struct de2_fmt_info *fmt_info; + u32 val; + + fmt_info = sun8i_mixer_format_info(state->fb->format->format); + if (!fmt_info || !fmt_info->rgb) { + DRM_DEBUG_DRIVER("Invalid format\n"); + return -EINVAL; + } + + val = fmt_info->de2_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET; + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(channel, overlay), + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val); + + return 0; +} + +static int sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *gem; + dma_addr_t paddr; + int bpp; + + /* Get the physical address of the buffer in memory */ + gem = drm_fb_cma_get_gem_obj(fb, 0); + + DRM_DEBUG_DRIVER("Using GEM @ %pad\n", &gem->paddr); + + /* Compute the start of the displayed memory */ + bpp = fb->format->cpp[0]; + paddr = gem->paddr + fb->offsets[0]; + + /* Fixup framebuffer address for src coordinates */ + paddr += (state->src.x1 >> 16) * bpp; + paddr += (state->src.y1 >> 16) * fb->pitches[0]; + + /* Set the line width */ + DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_PITCH(channel, overlay), + fb->pitches[0]); + + DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); + + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(channel, overlay), + lower_32_bits(paddr)); + + return 0; +} + +static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct sun8i_ui_layer *layer = plane_to_sun8i_ui_layer(plane); + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + int min_scale, max_scale; + + if (!crtc) + return 0; + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + min_scale = DRM_PLANE_HELPER_NO_SCALING; + max_scale = DRM_PLANE_HELPER_NO_SCALING; + + if (layer->mixer->cfg->scaler_mask & BIT(layer->channel)) { + min_scale = SUN8I_UI_SCALER_SCALE_MIN; + max_scale = SUN8I_UI_SCALER_SCALE_MAX; + } + + return drm_atomic_helper_check_plane_state(state, crtc_state, + min_scale, max_scale, + true, true); +} + +static void sun8i_ui_layer_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun8i_ui_layer *layer = plane_to_sun8i_ui_layer(plane); + unsigned int old_zpos = old_state->normalized_zpos; + struct sun8i_mixer *mixer = layer->mixer; + + sun8i_ui_layer_enable(mixer, layer->channel, layer->overlay, false, 0, + old_zpos); +} + +static void sun8i_ui_layer_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun8i_ui_layer *layer = plane_to_sun8i_ui_layer(plane); + unsigned int zpos = plane->state->normalized_zpos; + unsigned int old_zpos = old_state->normalized_zpos; + struct sun8i_mixer *mixer = layer->mixer; + + if (!plane->state->visible) { + sun8i_ui_layer_enable(mixer, layer->channel, + layer->overlay, false, 0, old_zpos); + return; + } + + sun8i_ui_layer_update_coord(mixer, layer->channel, + layer->overlay, plane, zpos); + sun8i_ui_layer_update_formats(mixer, layer->channel, + layer->overlay, plane); + sun8i_ui_layer_update_buffer(mixer, layer->channel, + layer->overlay, plane); + sun8i_ui_layer_enable(mixer, layer->channel, layer->overlay, + true, zpos, old_zpos); +} + +static struct drm_plane_helper_funcs sun8i_ui_layer_helper_funcs = { + .atomic_check = sun8i_ui_layer_atomic_check, + .atomic_disable = sun8i_ui_layer_atomic_disable, + .atomic_update = sun8i_ui_layer_atomic_update, +}; + +static const struct drm_plane_funcs sun8i_ui_layer_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .destroy = drm_plane_cleanup, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +static const u32 sun8i_ui_layer_formats[] = { + DRM_FORMAT_ABGR1555, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_BGR565, + DRM_FORMAT_BGR888, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB8888, +}; + +struct sun8i_ui_layer *sun8i_ui_layer_init_one(struct drm_device *drm, + struct sun8i_mixer *mixer, + int index) +{ + enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY; + int channel = mixer->cfg->vi_num + index; + struct sun8i_ui_layer *layer; + unsigned int plane_cnt; + int ret; + + layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); + if (!layer) + return ERR_PTR(-ENOMEM); + + if (index == 0) + type = DRM_PLANE_TYPE_PRIMARY; + + /* possible crtcs are set later */ + ret = drm_universal_plane_init(drm, &layer->plane, 0, + &sun8i_ui_layer_funcs, + sun8i_ui_layer_formats, + ARRAY_SIZE(sun8i_ui_layer_formats), + NULL, type, NULL); + if (ret) { + dev_err(drm->dev, "Couldn't initialize layer\n"); + return ERR_PTR(ret); + } + + plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num; + + ret = drm_plane_create_zpos_property(&layer->plane, channel, + 0, plane_cnt - 1); + if (ret) { + dev_err(drm->dev, "Couldn't add zpos property\n"); + return ERR_PTR(ret); + } + + drm_plane_helper_add(&layer->plane, &sun8i_ui_layer_helper_funcs); + layer->mixer = mixer; + layer->channel = channel; + layer->overlay = 0; + + return layer; +} diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h new file mode 100644 index 000000000..123b15ea9 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) Icenowy Zheng <icenowy@aosc.io> + * + * Based on sun4i_layer.h, which is: + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN8I_UI_LAYER_H_ +#define _SUN8I_UI_LAYER_H_ + +#include <drm/drm_plane.h> + +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x0) +#define SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x4) +#define SUN8I_MIXER_CHAN_UI_LAYER_COORD(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x8) +#define SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0xc) +#define SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x10) +#define SUN8I_MIXER_CHAN_UI_LAYER_BOT_LADDR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x14) +#define SUN8I_MIXER_CHAN_UI_LAYER_FCOLOR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x18) +#define SUN8I_MIXER_CHAN_UI_TOP_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x80) +#define SUN8I_MIXER_CHAN_UI_BOT_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x84) +#define SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch) (0x2000 + 0x1000 * (ch) + 0x88) + +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN BIT(0) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK GENMASK(2, 1) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK GENMASK(12, 8) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET 8 +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK GENMASK(31, 24) + +struct sun8i_mixer; + +struct sun8i_ui_layer { + struct drm_plane plane; + struct sun8i_mixer *mixer; + int channel; + int overlay; +}; + +static inline struct sun8i_ui_layer * +plane_to_sun8i_ui_layer(struct drm_plane *plane) +{ + return container_of(plane, struct sun8i_ui_layer, plane); +} + +struct sun8i_ui_layer *sun8i_ui_layer_init_one(struct drm_device *drm, + struct sun8i_mixer *mixer, + int index); +#endif /* _SUN8I_UI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c new file mode 100644 index 000000000..6bb2aa164 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2017 Jernej Skrabec <jernej.skrabec@siol.net> + * + * Coefficients are taken from BSP driver, which is: + * Copyright (C) 2014-2015 Allwinner + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include "sun8i_ui_scaler.h" + +static const u32 lan2coefftab16[240] = { + 0x00004000, 0x00033ffe, 0x00063efc, 0x000a3bfb, + 0xff0f37fb, 0xfe1433fb, 0xfd192ffb, 0xfd1f29fb, + 0xfc2424fc, 0xfb291ffd, 0xfb2f19fd, 0xfb3314fe, + 0xfb370fff, 0xfb3b0a00, 0xfc3e0600, 0xfe3f0300, + + 0xff053804, 0xff083801, 0xff0a3700, 0xff0e34ff, + 0xff1232fd, 0xfe162ffd, 0xfd1b2cfc, 0xfd1f28fc, + 0xfd2323fd, 0xfc281ffd, 0xfc2c1bfd, 0xfd2f16fe, + 0xfd3212ff, 0xff340eff, 0x00360a00, 0x02370700, + + 0xff083207, 0xff0a3205, 0xff0d3103, 0xfe113001, + 0xfe142e00, 0xfe182bff, 0xfe1b29fe, 0xfe1f25fe, + 0xfe2222fe, 0xfe251ffe, 0xfe291bfe, 0xff2b18fe, + 0x002e14fe, 0x013010ff, 0x03310dff, 0x05310a00, + + 0xff0a2e09, 0xff0c2e07, 0xff0f2d05, 0xff122c03, + 0xfe152b02, 0xfe182901, 0xfe1b2700, 0xff1e24ff, + 0xff2121ff, 0xff241eff, 0x00261bff, 0x012818ff, + 0x022a15ff, 0x032c12ff, 0x052d0fff, 0x072d0c00, + + 0xff0c2a0b, 0xff0e2a09, 0xff102a07, 0xff132905, + 0xff162803, 0xff182702, 0xff1b2501, 0xff1e2300, + 0x00202000, 0x01221d00, 0x01251bff, 0x032618ff, + 0x042815ff, 0x052913ff, 0x072a10ff, 0x092a0d00, + + 0xff0d280c, 0xff0f280a, 0xff112808, 0xff142706, + 0xff162605, 0xff192503, 0x001b2302, 0x001d2201, + 0x011f1f01, 0x01221d00, 0x02231b00, 0x04241800, + 0x052616ff, 0x072713ff, 0x08271100, 0x0a280e00, + + 0xff0e260d, 0xff10260b, 0xff122609, 0xff142508, + 0x00152506, 0x00182305, 0x001b2203, 0x011d2002, + 0x011f1f01, 0x02201d01, 0x03221b00, 0x04231801, + 0x06241600, 0x08251300, 0x09261100, 0x0b260f00, + + 0xff0e250e, 0xff10250c, 0x0011250a, 0x00142408, + 0x00162307, 0x00182206, 0x011a2104, 0x011c2003, + 0x021e1e02, 0x03201c01, 0x04211a01, 0x05221801, + 0x07231600, 0x08241400, 0x0a241200, 0x0c241000, + + 0x000e240e, 0x0010240c, 0x0013230a, 0x00142309, + 0x00162208, 0x01182106, 0x011a2005, 0x021b1f04, + 0x031d1d03, 0x041e1c02, 0x05201a01, 0x06211801, + 0x07221601, 0x09231400, 0x0a231300, 0x0c231100, + + 0x000f220f, 0x0011220d, 0x0013220b, 0x0015210a, + 0x01162108, 0x01182007, 0x02191f06, 0x031a1e05, + 0x041c1c04, 0x051d1b03, 0x061f1902, 0x07201801, + 0x08211601, 0x0a211500, 0x0b221300, 0x0d221100, + + 0x0010210f, 0x0011210e, 0x0013210c, 0x0114200b, + 0x01161f0a, 0x02171f08, 0x03181e07, 0x031a1d06, + 0x041c1c04, 0x051d1a04, 0x071d1903, 0x081e1802, + 0x091f1602, 0x0b1f1501, 0x0c211300, 0x0e201200, + + 0x00102010, 0x0012200e, 0x0013200d, 0x01151f0b, + 0x01161f0a, 0x02171e09, 0x03191d07, 0x041a1c06, + 0x051b1b05, 0x061c1a04, 0x071d1903, 0x081e1703, + 0x0a1f1601, 0x0b1f1501, 0x0d201300, 0x0e201200, + + 0x00102010, 0x00121f0f, 0x00141f0d, 0x01141f0c, + 0x02161e0a, 0x03171d09, 0x03181d08, 0x041a1c06, + 0x051b1b05, 0x061c1a04, 0x081c1903, 0x091d1703, + 0x0a1e1602, 0x0c1e1501, 0x0d1f1400, 0x0e1f1201, + + 0x00111e11, 0x00131e0f, 0x01131e0e, 0x02151d0c, + 0x02161d0b, 0x03171c0a, 0x04181b09, 0x05191b07, + 0x061a1a06, 0x071b1905, 0x091b1804, 0x0a1c1703, + 0x0b1d1602, 0x0c1d1502, 0x0e1d1401, 0x0f1e1300, + + 0x00111e11, 0x00131d10, 0x01141d0e, 0x02151c0d, + 0x03161c0b, 0x04171b0a, 0x05171b09, 0x06181a08, + 0x07191907, 0x081a1806, 0x091a1805, 0x0a1b1704, + 0x0b1c1603, 0x0d1c1502, 0x0e1d1401, 0x0f1d1301, +}; + +static int sun8i_ui_scaler_coef_index(unsigned int step) +{ + unsigned int scale, int_part, float_part; + + scale = step >> (SUN8I_UI_SCALER_SCALE_FRAC - 3); + int_part = scale >> 3; + float_part = scale & 0x7; + + switch (int_part) { + case 0: + return 0; + case 1: + return float_part; + case 2: + return 8 + (float_part >> 1); + case 3: + return 12; + case 4: + return 13; + default: + return 14; + } +} + +void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable) +{ + int vi_cnt = mixer->cfg->vi_num; + u32 val; + + if (WARN_ON(layer < vi_cnt)) + return; + + if (enable) + val = SUN8I_SCALER_GSU_CTRL_EN | + SUN8I_SCALER_GSU_CTRL_COEFF_RDY; + else + val = 0; + + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_CTRL(vi_cnt, layer - vi_cnt), val); +} + +void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer, + u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, + u32 hscale, u32 vscale, u32 hphase, u32 vphase) +{ + int vi_cnt = mixer->cfg->vi_num; + u32 insize, outsize; + int i, offset; + + if (WARN_ON(layer < vi_cnt)) + return; + + hphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16; + vphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16; + hscale <<= SUN8I_UI_SCALER_SCALE_FRAC - 16; + vscale <<= SUN8I_UI_SCALER_SCALE_FRAC - 16; + + insize = SUN8I_UI_SCALER_SIZE(src_w, src_h); + outsize = SUN8I_UI_SCALER_SIZE(dst_w, dst_h); + + layer -= vi_cnt; + + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_OUTSIZE(vi_cnt, layer), outsize); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_INSIZE(vi_cnt, layer), insize); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_HSTEP(vi_cnt, layer), hscale); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_VSTEP(vi_cnt, layer), vscale); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_HPHASE(vi_cnt, layer), hphase); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_VPHASE(vi_cnt, layer), vphase); + offset = sun8i_ui_scaler_coef_index(hscale) * + SUN8I_UI_SCALER_COEFF_COUNT; + for (i = 0; i < SUN8I_UI_SCALER_COEFF_COUNT; i++) + regmap_write(mixer->engine.regs, + SUN8I_SCALER_GSU_HCOEFF(vi_cnt, layer, i), + lan2coefftab16[offset + i]); +} diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.h b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.h new file mode 100644 index 000000000..86295be8b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 Jernej Skrabec <jernej.skrabec@siol.net> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _SUN8I_UI_SCALER_H_ +#define _SUN8I_UI_SCALER_H_ + +#include "sun8i_mixer.h" + +/* this two macros assumes 16 fractional bits which is standard in DRM */ +#define SUN8I_UI_SCALER_SCALE_MIN 1 +#define SUN8I_UI_SCALER_SCALE_MAX ((1UL << 20) - 1) + +#define SUN8I_UI_SCALER_SCALE_FRAC 20 +#define SUN8I_UI_SCALER_PHASE_FRAC 20 +#define SUN8I_UI_SCALER_COEFF_COUNT 16 +#define SUN8I_UI_SCALER_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1)) + +#define SUN8I_SCALER_GSU_CTRL(vi_cnt, ui_idx) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x0) +#define SUN8I_SCALER_GSU_OUTSIZE(vi_cnt, ui_idx) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x40) +#define SUN8I_SCALER_GSU_INSIZE(vi_cnt, ui_idx) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x80) +#define SUN8I_SCALER_GSU_HSTEP(vi_cnt, ui_idx) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x88) +#define SUN8I_SCALER_GSU_VSTEP(vi_cnt, ui_idx) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x8c) +#define SUN8I_SCALER_GSU_HPHASE(vi_cnt, ui_idx) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x90) +#define SUN8I_SCALER_GSU_VPHASE(vi_cnt, ui_idx) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x98) +#define SUN8I_SCALER_GSU_HCOEFF(vi_cnt, ui_idx, index) \ + (0x20000 + 0x20000 * (vi_cnt) + 0x10000 * (ui_idx) + 0x200 + \ + 0x4 * (index)) + +#define SUN8I_SCALER_GSU_CTRL_EN BIT(0) +#define SUN8I_SCALER_GSU_CTRL_COEFF_RDY BIT(4) + +void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable); +void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer, + u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, + u32 hscale, u32 vscale, u32 hphase, u32 vphase); + +#endif diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c new file mode 100644 index 000000000..15fc6363c --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -0,0 +1,409 @@ +/* + * Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "sun8i_vi_layer.h" +#include "sun8i_mixer.h" +#include "sun8i_vi_scaler.h" + +static void sun8i_vi_layer_enable(struct sun8i_mixer *mixer, int channel, + int overlay, bool enable, unsigned int zpos, + unsigned int old_zpos) +{ + u32 val; + + DRM_DEBUG_DRIVER("%sabling VI channel %d overlay %d\n", + enable ? "En" : "Dis", channel, overlay); + + if (enable) + val = SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN; + else + val = 0; + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_ATTR(channel, overlay), + SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN, val); + + if (!enable || zpos != old_zpos) { + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_PIPE_CTL, + SUN8I_MIXER_BLEND_PIPE_CTL_EN(old_zpos), + 0); + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_ROUTE, + SUN8I_MIXER_BLEND_ROUTE_PIPE_MSK(old_zpos), + 0); + } + + if (enable) { + val = SUN8I_MIXER_BLEND_PIPE_CTL_EN(zpos); + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_PIPE_CTL, val, val); + + val = channel << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos); + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_BLEND_ROUTE, + SUN8I_MIXER_BLEND_ROUTE_PIPE_MSK(zpos), + val); + } +} + +static int sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane, + unsigned int zpos) +{ + struct drm_plane_state *state = plane->state; + const struct drm_format_info *format = state->fb->format; + u32 src_w, src_h, dst_w, dst_h; + u32 outsize, insize; + u32 hphase, vphase; + bool subsampled; + + DRM_DEBUG_DRIVER("Updating VI channel %d overlay %d\n", + channel, overlay); + + src_w = drm_rect_width(&state->src) >> 16; + src_h = drm_rect_height(&state->src) >> 16; + dst_w = drm_rect_width(&state->dst); + dst_h = drm_rect_height(&state->dst); + + hphase = state->src.x1 & 0xffff; + vphase = state->src.y1 & 0xffff; + + /* make coordinates dividable by subsampling factor */ + if (format->hsub > 1) { + int mask, remainder; + + mask = format->hsub - 1; + remainder = (state->src.x1 >> 16) & mask; + src_w = (src_w + remainder) & ~mask; + hphase += remainder << 16; + } + + if (format->vsub > 1) { + int mask, remainder; + + mask = format->vsub - 1; + remainder = (state->src.y1 >> 16) & mask; + src_h = (src_h + remainder) & ~mask; + vphase += remainder << 16; + } + + insize = SUN8I_MIXER_SIZE(src_w, src_h); + outsize = SUN8I_MIXER_SIZE(dst_w, dst_h); + + /* Set height and width */ + DRM_DEBUG_DRIVER("Layer source offset X: %d Y: %d\n", + (state->src.x1 >> 16) & ~(format->hsub - 1), + (state->src.y1 >> 16) & ~(format->vsub - 1)); + DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_SIZE(channel, overlay), + insize); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_OVL_SIZE(channel), + insize); + + /* + * Scaler must be enabled for subsampled formats, so it scales + * chroma to same size as luma. + */ + subsampled = format->hsub > 1 || format->vsub > 1; + + if (insize != outsize || subsampled || hphase || vphase) { + u32 hscale, vscale; + + DRM_DEBUG_DRIVER("HW scaling is enabled\n"); + + hscale = state->src_w / state->crtc_w; + vscale = state->src_h / state->crtc_h; + + sun8i_vi_scaler_setup(mixer, channel, src_w, src_h, dst_w, + dst_h, hscale, vscale, hphase, vphase, + format); + sun8i_vi_scaler_enable(mixer, channel, true); + } else { + DRM_DEBUG_DRIVER("HW scaling is not needed\n"); + sun8i_vi_scaler_enable(mixer, channel, false); + } + + /* Set base coordinates */ + DRM_DEBUG_DRIVER("Layer destination coordinates X: %d Y: %d\n", + state->dst.x1, state->dst.y1); + DRM_DEBUG_DRIVER("Layer destination size W: %d H: %d\n", dst_w, dst_h); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_BLEND_ATTR_COORD(zpos), + SUN8I_MIXER_COORD(state->dst.x1, state->dst.y1)); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_BLEND_ATTR_INSIZE(zpos), + outsize); + + return 0; +} + +static int sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + const struct de2_fmt_info *fmt_info; + u32 val; + + fmt_info = sun8i_mixer_format_info(state->fb->format->format); + if (!fmt_info) { + DRM_DEBUG_DRIVER("Invalid format\n"); + return -EINVAL; + } + + val = fmt_info->de2_fmt << SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET; + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_ATTR(channel, overlay), + SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_MASK, val); + + if (fmt_info->csc != SUN8I_CSC_MODE_OFF) { + sun8i_csc_set_ccsc_coefficients(mixer, channel, fmt_info->csc); + sun8i_csc_enable_ccsc(mixer, channel, true); + } else { + sun8i_csc_enable_ccsc(mixer, channel, false); + } + + if (fmt_info->rgb) + val = SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE; + else + val = 0; + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_ATTR(channel, overlay), + SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE, val); + + return 0; +} + +static int sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + const struct drm_format_info *format = fb->format; + struct drm_gem_cma_object *gem; + u32 dx, dy, src_x, src_y; + dma_addr_t paddr; + int i; + + /* Adjust x and y to be dividable by subsampling factor */ + src_x = (state->src.x1 >> 16) & ~(format->hsub - 1); + src_y = (state->src.y1 >> 16) & ~(format->vsub - 1); + + for (i = 0; i < format->num_planes; i++) { + /* Get the physical address of the buffer in memory */ + gem = drm_fb_cma_get_gem_obj(fb, i); + + DRM_DEBUG_DRIVER("Using GEM @ %pad\n", &gem->paddr); + + /* Compute the start of the displayed memory */ + paddr = gem->paddr + fb->offsets[i]; + + dx = src_x; + dy = src_y; + + if (i > 0) { + dx /= format->hsub; + dy /= format->vsub; + } + + /* Fixup framebuffer address for src coordinates */ + paddr += dx * format->cpp[i]; + paddr += dy * fb->pitches[i]; + + /* Set the line width */ + DRM_DEBUG_DRIVER("Layer %d. line width: %d bytes\n", + i + 1, fb->pitches[i]); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_PITCH(channel, + overlay, i), + fb->pitches[i]); + + DRM_DEBUG_DRIVER("Setting %d. buffer address to %pad\n", + i + 1, &paddr); + + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_TOP_LADDR(channel, + overlay, i), + lower_32_bits(paddr)); + } + + return 0; +} + +static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct sun8i_vi_layer *layer = plane_to_sun8i_vi_layer(plane); + struct drm_crtc *crtc = state->crtc; + struct drm_crtc_state *crtc_state; + int min_scale, max_scale; + + if (!crtc) + return 0; + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + min_scale = DRM_PLANE_HELPER_NO_SCALING; + max_scale = DRM_PLANE_HELPER_NO_SCALING; + + if (layer->mixer->cfg->scaler_mask & BIT(layer->channel)) { + min_scale = SUN8I_VI_SCALER_SCALE_MIN; + max_scale = SUN8I_VI_SCALER_SCALE_MAX; + } + + return drm_atomic_helper_check_plane_state(state, crtc_state, + min_scale, max_scale, + true, true); +} + +static void sun8i_vi_layer_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun8i_vi_layer *layer = plane_to_sun8i_vi_layer(plane); + unsigned int old_zpos = old_state->normalized_zpos; + struct sun8i_mixer *mixer = layer->mixer; + + sun8i_vi_layer_enable(mixer, layer->channel, layer->overlay, false, 0, + old_zpos); +} + +static void sun8i_vi_layer_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun8i_vi_layer *layer = plane_to_sun8i_vi_layer(plane); + unsigned int zpos = plane->state->normalized_zpos; + unsigned int old_zpos = old_state->normalized_zpos; + struct sun8i_mixer *mixer = layer->mixer; + + if (!plane->state->visible) { + sun8i_vi_layer_enable(mixer, layer->channel, + layer->overlay, false, 0, old_zpos); + return; + } + + sun8i_vi_layer_update_coord(mixer, layer->channel, + layer->overlay, plane, zpos); + sun8i_vi_layer_update_formats(mixer, layer->channel, + layer->overlay, plane); + sun8i_vi_layer_update_buffer(mixer, layer->channel, + layer->overlay, plane); + sun8i_vi_layer_enable(mixer, layer->channel, layer->overlay, + true, zpos, old_zpos); +} + +static struct drm_plane_helper_funcs sun8i_vi_layer_helper_funcs = { + .atomic_check = sun8i_vi_layer_atomic_check, + .atomic_disable = sun8i_vi_layer_atomic_disable, + .atomic_update = sun8i_vi_layer_atomic_update, +}; + +static const struct drm_plane_funcs sun8i_vi_layer_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .destroy = drm_plane_cleanup, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +/* + * While DE2 VI layer supports same RGB formats as UI layer, alpha + * channel is ignored. This structure lists all unique variants + * where alpha channel is replaced with "don't care" (X) channel. + */ +static const u32 sun8i_vi_layer_formats[] = { + DRM_FORMAT_BGR565, + DRM_FORMAT_BGR888, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XRGB8888, + + DRM_FORMAT_NV16, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV61, + DRM_FORMAT_UYVY, + DRM_FORMAT_VYUY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_YUV411, + DRM_FORMAT_YUV420, + DRM_FORMAT_YUV422, + DRM_FORMAT_YVU411, + DRM_FORMAT_YVU420, + DRM_FORMAT_YVU422, +}; + +struct sun8i_vi_layer *sun8i_vi_layer_init_one(struct drm_device *drm, + struct sun8i_mixer *mixer, + int index) +{ + struct sun8i_vi_layer *layer; + unsigned int plane_cnt; + int ret; + + layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); + if (!layer) + return ERR_PTR(-ENOMEM); + + /* possible crtcs are set later */ + ret = drm_universal_plane_init(drm, &layer->plane, 0, + &sun8i_vi_layer_funcs, + sun8i_vi_layer_formats, + ARRAY_SIZE(sun8i_vi_layer_formats), + NULL, DRM_PLANE_TYPE_OVERLAY, NULL); + if (ret) { + dev_err(drm->dev, "Couldn't initialize layer\n"); + return ERR_PTR(ret); + } + + plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num; + + ret = drm_plane_create_zpos_property(&layer->plane, index, + 0, plane_cnt - 1); + if (ret) { + dev_err(drm->dev, "Couldn't add zpos property\n"); + return ERR_PTR(ret); + } + + drm_plane_helper_add(&layer->plane, &sun8i_vi_layer_helper_funcs); + layer->mixer = mixer; + layer->channel = index; + layer->overlay = 0; + + return layer; +} diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h new file mode 100644 index 000000000..6996627a0 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN8I_VI_LAYER_H_ +#define _SUN8I_VI_LAYER_H_ + +#include <drm/drm_plane.h> + +#define SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x30 * (layer) + 0x0) +#define SUN8I_MIXER_CHAN_VI_LAYER_SIZE(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x30 * (layer) + 0x4) +#define SUN8I_MIXER_CHAN_VI_LAYER_COORD(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x30 * (layer) + 0x8) +#define SUN8I_MIXER_CHAN_VI_LAYER_PITCH(ch, layer, plane) \ + (0x2000 + 0x1000 * (ch) + 0x30 * (layer) + 0xc + 4 * (plane)) +#define SUN8I_MIXER_CHAN_VI_LAYER_TOP_LADDR(ch, layer, plane) \ + (0x2000 + 0x1000 * (ch) + 0x30 * (layer) + 0x18 + 4 * (plane)) +#define SUN8I_MIXER_CHAN_VI_OVL_SIZE(ch) (0x2000 + 0x1000 * (ch) + 0xe8) + +#define SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN BIT(0) +/* RGB mode should be set for RGB formats and cleared for YCbCr */ +#define SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE BIT(15) +#define SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET 8 +#define SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_MASK GENMASK(12, 8) + +struct sun8i_mixer; + +struct sun8i_vi_layer { + struct drm_plane plane; + struct sun8i_mixer *mixer; + int channel; + int overlay; +}; + +static inline struct sun8i_vi_layer * +plane_to_sun8i_vi_layer(struct drm_plane *plane) +{ + return container_of(plane, struct sun8i_vi_layer, plane); +} + +struct sun8i_vi_layer *sun8i_vi_layer_init_one(struct drm_device *drm, + struct sun8i_mixer *mixer, + int index); +#endif /* _SUN8I_VI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c new file mode 100644 index 000000000..d3f1acb23 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c @@ -0,0 +1,971 @@ +/* + * Copyright (C) 2017 Jernej Skrabec <jernej.skrabec@siol.net> + * + * Coefficients are taken from BSP driver, which is: + * Copyright (C) 2014-2015 Allwinner + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include "sun8i_vi_scaler.h" + +static const u32 lan3coefftab32_left[480] = { + 0x40000000, 0x40fe0000, 0x3ffd0100, 0x3efc0100, + 0x3efb0100, 0x3dfa0200, 0x3cf90200, 0x3bf80200, + 0x39f70200, 0x37f70200, 0x35f70200, 0x33f70200, + 0x31f70200, 0x2ef70200, 0x2cf70200, 0x2af70200, + 0x27f70200, 0x24f80100, 0x22f80100, 0x1ef90100, + 0x1cf90100, 0x19fa0100, 0x17fa0100, 0x14fb0100, + 0x11fc0000, 0x0ffc0000, 0x0cfd0000, 0x0afd0000, + 0x08fe0000, 0x05ff0000, 0x03ff0000, 0x02000000, + + 0x3806fc02, 0x3805fc02, 0x3803fd01, 0x3801fe01, + 0x3700fe01, 0x35ffff01, 0x35fdff01, 0x34fc0001, + 0x34fb0000, 0x33fa0000, 0x31fa0100, 0x2ff90100, + 0x2df80200, 0x2bf80200, 0x2af70200, 0x28f70200, + 0x27f70200, 0x24f70300, 0x22f70300, 0x1ff70300, + 0x1ef70300, 0x1cf70300, 0x1af70300, 0x18f70300, + 0x16f80300, 0x13f80300, 0x11f90300, 0x0ef90300, + 0x0efa0200, 0x0cfa0200, 0x0afb0200, 0x08fb0200, + + 0x320bfa02, 0x3309fa02, 0x3208fb02, 0x3206fb02, + 0x3205fb02, 0x3104fc02, 0x3102fc01, 0x3001fd01, + 0x3000fd01, 0x2ffffd01, 0x2efefe01, 0x2dfdfe01, + 0x2bfcff01, 0x29fcff01, 0x28fbff01, 0x27fa0001, + 0x26fa0000, 0x24f90000, 0x22f90100, 0x20f90100, + 0x1ff80100, 0x1ef80100, 0x1cf80100, 0x1af80200, + 0x18f80200, 0x17f80200, 0x15f80200, 0x12f80200, + 0x11f90200, 0x0ff90200, 0x0df90200, 0x0cfa0200, + + 0x2e0efa01, 0x2f0dfa01, 0x2f0bfa01, 0x2e0afa01, + 0x2e09fa01, 0x2e07fb01, 0x2d06fb01, 0x2d05fb01, + 0x2c04fb01, 0x2b03fc01, 0x2a02fc01, 0x2a01fc01, + 0x2800fd01, 0x28fffd01, 0x26fefd01, 0x25fefe01, + 0x24fdfe01, 0x23fcfe01, 0x21fcff01, 0x20fbff01, + 0x1efbff01, 0x1efbff00, 0x1cfa0000, 0x1bfa0000, + 0x19fa0000, 0x18fa0000, 0x17f90000, 0x15f90100, + 0x14f90100, 0x12f90100, 0x11f90100, 0x0ff90100, + + 0x2b10fa00, 0x2b0ffa00, 0x2b0efa00, 0x2b0cfa00, + 0x2b0bfa00, 0x2a0afb01, 0x2a09fb01, 0x2908fb01, + 0x2807fb01, 0x2806fb01, 0x2805fb01, 0x2604fc01, + 0x2503fc01, 0x2502fc01, 0x2401fc01, 0x2301fc01, + 0x2100fd01, 0x21fffd01, 0x21fffd01, 0x20fefd01, + 0x1dfefe01, 0x1cfdfe01, 0x1cfdfe00, 0x1bfcfe00, + 0x19fcff00, 0x19fbff00, 0x17fbff00, 0x16fbff00, + 0x15fbff00, 0x14fb0000, 0x13fa0000, 0x11fa0000, + + 0x2811fcff, 0x2810fcff, 0x280ffbff, 0x280efbff, + 0x270dfb00, 0x270cfb00, 0x270bfb00, 0x260afb00, + 0x2609fb00, 0x2508fb00, 0x2507fb00, 0x2407fb00, + 0x2406fc00, 0x2305fc00, 0x2204fc00, 0x2203fc00, + 0x2103fc00, 0x2002fc00, 0x1f01fd00, 0x1e01fd00, + 0x1d00fd00, 0x1dfffd00, 0x1cfffd00, 0x1bfefd00, + 0x1afefe00, 0x19fefe00, 0x18fdfe00, 0x17fdfe00, + 0x16fdfe00, 0x15fcff00, 0x13fcff00, 0x12fcff00, + + 0x2512fdfe, 0x2511fdff, 0x2410fdff, 0x240ffdff, + 0x240efcff, 0x240dfcff, 0x240dfcff, 0x240cfcff, + 0x230bfcff, 0x230afc00, 0x2209fc00, 0x2108fc00, + 0x2108fc00, 0x2007fc00, 0x2006fc00, 0x2005fc00, + 0x1f05fc00, 0x1e04fc00, 0x1e03fc00, 0x1c03fd00, + 0x1c02fd00, 0x1b02fd00, 0x1b01fd00, 0x1a00fd00, + 0x1900fd00, 0x1800fd00, 0x17fffe00, 0x16fffe00, + 0x16fefe00, 0x14fefe00, 0x13fefe00, 0x13fdfe00, + + 0x2212fffe, 0x2211fefe, 0x2211fefe, 0x2110fefe, + 0x210ffeff, 0x220efdff, 0x210dfdff, 0x210dfdff, + 0x210cfdff, 0x210bfdff, 0x200afdff, 0x200afdff, + 0x1f09fdff, 0x1f08fdff, 0x1d08fd00, 0x1c07fd00, + 0x1d06fd00, 0x1b06fd00, 0x1b05fd00, 0x1c04fd00, + 0x1b04fd00, 0x1a03fd00, 0x1a03fd00, 0x1902fd00, + 0x1802fd00, 0x1801fd00, 0x1701fd00, 0x1600fd00, + 0x1400fe00, 0x1400fe00, 0x14fffe00, 0x13fffe00, + + 0x201200fe, 0x201100fe, 0x1f11fffe, 0x2010fffe, + 0x1f0ffffe, 0x1e0ffffe, 0x1f0efeff, 0x1f0dfeff, + 0x1f0dfeff, 0x1e0cfeff, 0x1e0bfeff, 0x1d0bfeff, + 0x1d0afeff, 0x1d09fdff, 0x1d09fdff, 0x1c08fdff, + 0x1c07fdff, 0x1b07fd00, 0x1b06fd00, 0x1a06fd00, + 0x1a05fd00, 0x1805fd00, 0x1904fd00, 0x1804fd00, + 0x1703fd00, 0x1703fd00, 0x1602fe00, 0x1502fe00, + 0x1501fe00, 0x1401fe00, 0x1301fe00, 0x1300fe00, + + 0x1c1202fe, 0x1c1102fe, 0x1b1102fe, 0x1c1001fe, + 0x1b1001fe, 0x1b0f01ff, 0x1b0e00ff, 0x1b0e00ff, + 0x1b0d00ff, 0x1a0d00ff, 0x1a0c00ff, 0x1a0cffff, + 0x1a0bffff, 0x1a0bffff, 0x1a0affff, 0x180affff, + 0x1909ffff, 0x1809ffff, 0x1808ffff, 0x1808feff, + 0x1807feff, 0x1707fe00, 0x1606fe00, 0x1506fe00, + 0x1605fe00, 0x1505fe00, 0x1504fe00, 0x1304fe00, + 0x1304fe00, 0x1303fe00, 0x1203fe00, 0x1203fe00, + + 0x181104ff, 0x191103ff, 0x191003ff, 0x181003ff, + 0x180f03ff, 0x190f02ff, 0x190e02ff, 0x180e02ff, + 0x180d02ff, 0x180d01ff, 0x180d01ff, 0x180c01ff, + 0x180c01ff, 0x180b00ff, 0x170b00ff, 0x170a00ff, + 0x170a00ff, 0x170900ff, 0x160900ff, 0x160900ff, + 0x1608ffff, 0x1508ffff, 0x1507ff00, 0x1507ff00, + 0x1407ff00, 0x1306ff00, 0x1306ff00, 0x1305ff00, + 0x1205ff00, 0x1105ff00, 0x1204ff00, 0x1104ff00, + + 0x171005ff, 0x171005ff, 0x171004ff, 0x170f04ff, + 0x160f04ff, 0x170f03ff, 0x170e03ff, 0x160e03ff, + 0x160d03ff, 0x160d02ff, 0x160d02ff, 0x160c02ff, + 0x160c02ff, 0x160c02ff, 0x160b01ff, 0x150b01ff, + 0x150a01ff, 0x150a01ff, 0x150a01ff, 0x140901ff, + 0x14090000, 0x14090000, 0x14080000, 0x13080000, + 0x13070000, 0x12070000, 0x12070000, 0x12060000, + 0x11060000, 0x11060000, 0x11050000, 0x1105ff00, + + 0x14100600, 0x15100500, 0x150f0500, 0x150f0500, + 0x140f0500, 0x150e0400, 0x140e0400, 0x130e0400, + 0x140d0400, 0x150d0300, 0x130d0300, 0x140c0300, + 0x140c0300, 0x140c0200, 0x140b0200, 0x130b0200, + 0x120b0200, 0x130a0200, 0x130a0200, 0x130a0100, + 0x13090100, 0x12090100, 0x11090100, 0x12080100, + 0x11080100, 0x10080100, 0x11070100, 0x11070000, + 0x10070000, 0x11060000, 0x10060000, 0x10060000, + + 0x140f0600, 0x140f0600, 0x130f0600, 0x140f0500, + 0x140e0500, 0x130e0500, 0x130e0500, 0x140d0400, + 0x140d0400, 0x130d0400, 0x120d0400, 0x130c0400, + 0x130c0300, 0x130c0300, 0x130b0300, 0x130b0300, + 0x110b0300, 0x130a0200, 0x120a0200, 0x120a0200, + 0x120a0200, 0x12090200, 0x10090200, 0x11090100, + 0x11080100, 0x11080100, 0x10080100, 0x10080100, + 0x10070100, 0x10070100, 0x0f070100, 0x10060100, + + 0x120f0701, 0x130f0601, 0x130e0601, 0x130e0601, + 0x120e0601, 0x130e0501, 0x130e0500, 0x130d0500, + 0x120d0500, 0x120d0500, 0x130c0400, 0x130c0400, + 0x120c0400, 0x110c0400, 0x120b0400, 0x120b0300, + 0x120b0300, 0x120b0300, 0x120a0300, 0x110a0300, + 0x110a0200, 0x11090200, 0x11090200, 0x10090200, + 0x10090200, 0x10080200, 0x10080200, 0x10080100, + 0x0f080100, 0x10070100, 0x0f070100, 0x0f070100 +}; + +static const u32 lan3coefftab32_right[480] = { + 0x00000000, 0x00000002, 0x0000ff04, 0x0000ff06, + 0x0000fe08, 0x0000fd0a, 0x0000fd0c, 0x0000fc0f, + 0x0000fc12, 0x0001fb14, 0x0001fa17, 0x0001fa19, + 0x0001f91c, 0x0001f91f, 0x0001f822, 0x0001f824, + 0x0002f727, 0x0002f72a, 0x0002f72c, 0x0002f72f, + 0x0002f731, 0x0002f733, 0x0002f735, 0x0002f737, + 0x0002f73a, 0x0002f83b, 0x0002f93c, 0x0002fa3d, + 0x0001fb3e, 0x0001fc3f, 0x0001fd40, 0x0000fe40, + + 0x0002fc06, 0x0002fb08, 0x0002fb0a, 0x0002fa0c, + 0x0002fa0e, 0x0003f910, 0x0003f912, 0x0003f814, + 0x0003f816, 0x0003f719, 0x0003f71a, 0x0003f71d, + 0x0003f71f, 0x0003f721, 0x0003f723, 0x0003f725, + 0x0002f727, 0x0002f729, 0x0002f72b, 0x0002f82d, + 0x0002f82e, 0x0001f930, 0x0001fa31, 0x0000fa34, + 0x0000fb34, 0x0100fc35, 0x01fffd36, 0x01ffff37, + 0x01fe0037, 0x01fe0138, 0x01fd0338, 0x02fc0538, + + 0x0002fa0b, 0x0002fa0c, 0x0002f90e, 0x0002f910, + 0x0002f911, 0x0002f813, 0x0002f816, 0x0002f817, + 0x0002f818, 0x0002f81a, 0x0001f81c, 0x0001f81e, + 0x0001f820, 0x0001f921, 0x0001f923, 0x0000f925, + 0x0000fa26, 0x0100fa28, 0x01fffb29, 0x01fffc2a, + 0x01fffc2c, 0x01fefd2d, 0x01fefe2e, 0x01fdff2f, + 0x01fd0030, 0x01fd0130, 0x01fc0232, 0x02fc0432, + 0x02fb0532, 0x02fb0633, 0x02fb0833, 0x02fa0933, + + 0x0001fa0e, 0x0001f90f, 0x0001f911, 0x0001f913, + 0x0001f914, 0x0001f915, 0x0000f918, 0x0000fa18, + 0x0000fa1a, 0x0000fa1b, 0x0000fa1d, 0x00fffb1e, + 0x01fffb1f, 0x01fffb20, 0x01fffc22, 0x01fefc23, + 0x01fefd24, 0x01fefe25, 0x01fdfe27, 0x01fdff28, + 0x01fd0029, 0x01fc012a, 0x01fc022b, 0x01fc032b, + 0x01fb042d, 0x01fb052d, 0x01fb062e, 0x01fb072e, + 0x01fa092e, 0x01fa0a2f, 0x01fa0b2f, 0x01fa0d2f, + + 0x0000fa11, 0x0000fa12, 0x0000fa13, 0x0000fb14, + 0x00fffb16, 0x00fffb16, 0x00fffb17, 0x00fffb19, + 0x00fffc1a, 0x00fefc1c, 0x00fefd1c, 0x01fefd1d, + 0x01fefe1e, 0x01fdfe20, 0x01fdff21, 0x01fdff22, + 0x01fd0023, 0x01fc0124, 0x01fc0124, 0x01fc0225, + 0x01fc0326, 0x01fc0427, 0x01fb0528, 0x01fb0629, + 0x01fb0729, 0x01fb0829, 0x01fb092a, 0x01fb0a2a, + 0x00fa0b2c, 0x00fa0c2b, 0x00fa0e2b, 0x00fa0f2c, + + 0x00fffc11, 0x00fffc12, 0x00fffc14, 0x00fffc15, + 0x00fefd16, 0x00fefd17, 0x00fefd18, 0x00fefe19, + 0x00fefe1a, 0x00fdfe1d, 0x00fdff1d, 0x00fdff1e, + 0x00fd001d, 0x00fd011e, 0x00fd0120, 0x00fc0221, + 0x00fc0321, 0x00fc0323, 0x00fc0423, 0x00fc0523, + 0x00fc0624, 0x00fb0725, 0x00fb0726, 0x00fb0827, + 0x00fb0926, 0x00fb0a26, 0x00fb0b27, 0x00fb0c27, + 0x00fb0d27, 0xfffb0e28, 0xfffb0f29, 0xfffc1028, + + 0x00fefd13, 0x00fefd13, 0x00fefe14, 0x00fefe15, + 0x00fefe17, 0x00feff17, 0x00feff17, 0x00fd0018, + 0x00fd001a, 0x00fd001a, 0x00fd011b, 0x00fd021c, + 0x00fd021c, 0x00fd031d, 0x00fc031f, 0x00fc041f, + 0x00fc051f, 0x00fc0521, 0x00fc0621, 0x00fc0721, + 0x00fc0821, 0x00fc0822, 0x00fc0922, 0x00fc0a23, + 0xfffc0b24, 0xfffc0c24, 0xfffc0d24, 0xfffc0d25, + 0xfffc0e25, 0xfffd0f25, 0xfffd1025, 0xfffd1125, + + 0x00feff12, 0x00feff14, 0x00feff14, 0x00fe0015, + 0x00fe0015, 0x00fd0017, 0x00fd0118, 0x00fd0118, + 0x00fd0218, 0x00fd0219, 0x00fd031a, 0x00fd031a, + 0x00fd041b, 0x00fd041c, 0x00fd051c, 0x00fd061d, + 0x00fd061d, 0x00fd071e, 0x00fd081e, 0xfffd081f, + 0xfffd091f, 0xfffd0a20, 0xfffd0a20, 0xfffd0b21, + 0xfffd0c21, 0xfffd0d21, 0xfffd0d22, 0xfffd0e23, + 0xfffe0f22, 0xfefe1022, 0xfefe1122, 0xfefe1123, + + 0x00fe0012, 0x00fe0013, 0x00fe0114, 0x00fe0114, + 0x00fe0116, 0x00fe0216, 0x00fe0216, 0x00fd0317, + 0x00fd0317, 0x00fd0418, 0x00fd0419, 0x00fd0519, + 0x00fd051a, 0x00fd061b, 0x00fd061b, 0x00fd071c, + 0xfffd071e, 0xfffd081d, 0xfffd091d, 0xfffd091e, + 0xfffe0a1d, 0xfffe0b1e, 0xfffe0b1e, 0xfffe0c1e, + 0xfffe0d1f, 0xfffe0d1f, 0xfffe0e1f, 0xfeff0f1f, + 0xfeff0f20, 0xfeff1020, 0xfeff1120, 0xfe001120, + + 0x00fe0212, 0x00fe0312, 0x00fe0313, 0x00fe0314, + 0x00fe0414, 0x00fe0414, 0x00fe0416, 0x00fe0515, + 0x00fe0516, 0x00fe0616, 0x00fe0617, 0x00fe0717, + 0xfffe0719, 0xfffe0818, 0xffff0818, 0xffff0919, + 0xffff0919, 0xffff0a19, 0xffff0a1a, 0xffff0b1a, + 0xffff0b1b, 0xffff0c1a, 0xff000c1b, 0xff000d1b, + 0xff000d1b, 0xff000e1b, 0xff000e1c, 0xff010f1c, + 0xfe01101c, 0xfe01101d, 0xfe02111c, 0xfe02111c, + + 0x00ff0411, 0x00ff0411, 0x00ff0412, 0x00ff0512, + 0x00ff0513, 0x00ff0513, 0x00ff0613, 0x00ff0614, + 0x00ff0714, 0x00ff0715, 0x00ff0715, 0xffff0816, + 0xffff0816, 0xff000916, 0xff000917, 0xff000918, + 0xff000a17, 0xff000a18, 0xff000b18, 0xff000b18, + 0xff010c18, 0xff010c19, 0xff010d18, 0xff010d18, + 0xff020d18, 0xff020e19, 0xff020e19, 0xff020f19, + 0xff030f19, 0xff031019, 0xff031019, 0xff031119, + + 0x00ff0511, 0x00ff0511, 0x00000511, 0x00000611, + 0x00000612, 0x00000612, 0x00000712, 0x00000713, + 0x00000714, 0x00000814, 0x00000814, 0x00000914, + 0x00000914, 0xff010914, 0xff010a15, 0xff010a16, + 0xff010a17, 0xff010b16, 0xff010b16, 0xff020c16, + 0xff020c16, 0xff020c16, 0xff020d16, 0xff020d17, + 0xff030d17, 0xff030e17, 0xff030e17, 0xff030f17, + 0xff040f17, 0xff040f17, 0xff041017, 0xff051017, + + 0x00000610, 0x00000610, 0x00000611, 0x00000611, + 0x00000711, 0x00000712, 0x00010712, 0x00010812, + 0x00010812, 0x00010812, 0x00010913, 0x00010913, + 0x00010913, 0x00010a13, 0x00020a13, 0x00020a14, + 0x00020b14, 0x00020b14, 0x00020b14, 0x00020c14, + 0x00030c14, 0x00030c15, 0x00030d15, 0x00030d15, + 0x00040d15, 0x00040e15, 0x00040e15, 0x00040e16, + 0x00050f15, 0x00050f15, 0x00050f16, 0x00051015, + + 0x00000611, 0x00010610, 0x00010710, 0x00010710, + 0x00010711, 0x00010811, 0x00010811, 0x00010812, + 0x00010812, 0x00010912, 0x00020912, 0x00020912, + 0x00020a12, 0x00020a12, 0x00020a13, 0x00020a13, + 0x00030b13, 0x00030b13, 0x00030b14, 0x00030c13, + 0x00030c13, 0x00040c13, 0x00040d14, 0x00040d14, + 0x00040d15, 0x00040d15, 0x00050e14, 0x00050e14, + 0x00050e15, 0x00050f14, 0x00060f14, 0x00060f14, + + 0x0001070f, 0x0001070f, 0x00010710, 0x00010710, + 0x00010810, 0x00010810, 0x00020810, 0x00020811, + 0x00020911, 0x00020911, 0x00020912, 0x00020912, + 0x00020a12, 0x00030a12, 0x00030a12, 0x00030b12, + 0x00030b12, 0x00030b12, 0x00040b12, 0x00040c12, + 0x00040c13, 0x00040c14, 0x00040c14, 0x00050d13, + 0x00050d13, 0x00050d14, 0x00050e13, 0x01050e13, + 0x01060e13, 0x01060e13, 0x01060e14, 0x01060f13 +}; + +static const u32 lan2coefftab32[480] = { + 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, + + 0xff053804, 0xff063803, 0xff083801, 0xff093701, + 0xff0a3700, 0xff0c3500, 0xff0e34ff, 0xff1033fe, + 0xff1232fd, 0xfe1431fd, 0xfe162ffd, 0xfe182dfd, + 0xfd1b2cfc, 0xfd1d2afc, 0xfd1f28fc, 0xfd2126fc, + 0xfd2323fd, 0xfc2621fd, 0xfc281ffd, 0xfc2a1dfd, + 0xfc2c1bfd, 0xfd2d18fe, 0xfd2f16fe, 0xfd3114fe, + 0xfd3212ff, 0xfe3310ff, 0xff340eff, 0x00350cff, + 0x00360a00, 0x01360900, 0x02370700, 0x03370600, + + 0xff083207, 0xff093206, 0xff0a3205, 0xff0c3203, + 0xff0d3103, 0xff0e3102, 0xfe113001, 0xfe132f00, + 0xfe142e00, 0xfe162dff, 0xfe182bff, 0xfe192aff, + 0xfe1b29fe, 0xfe1d27fe, 0xfe1f25fe, 0xfd2124fe, + 0xfe2222fe, 0xfe2421fd, 0xfe251ffe, 0xfe271dfe, + 0xfe291bfe, 0xff2a19fe, 0xff2b18fe, 0xff2d16fe, + 0x002e14fe, 0x002f12ff, 0x013010ff, 0x02300fff, + 0x03310dff, 0x04310cff, 0x05310a00, 0x06310900, + + 0xff0a2e09, 0xff0b2e08, 0xff0c2e07, 0xff0e2d06, + 0xff0f2d05, 0xff102d04, 0xff122c03, 0xfe142c02, + 0xfe152b02, 0xfe172a01, 0xfe182901, 0xfe1a2800, + 0xfe1b2700, 0xfe1d2500, 0xff1e24ff, 0xfe2023ff, + 0xff2121ff, 0xff2320fe, 0xff241eff, 0x00251dfe, + 0x00261bff, 0x00281afe, 0x012818ff, 0x012a16ff, + 0x022a15ff, 0x032b13ff, 0x032c12ff, 0x052c10ff, + 0x052d0fff, 0x062d0d00, 0x072d0c00, 0x082d0b00, + + 0xff0c2a0b, 0xff0d2a0a, 0xff0e2a09, 0xff0f2a08, + 0xff102a07, 0xff112a06, 0xff132905, 0xff142904, + 0xff162803, 0xff172703, 0xff182702, 0xff1a2601, + 0xff1b2501, 0xff1c2401, 0xff1e2300, 0xff1f2200, + 0x00202000, 0x00211f00, 0x01221d00, 0x01231c00, + 0x01251bff, 0x02251aff, 0x032618ff, 0x032717ff, + 0x042815ff, 0x052814ff, 0x052913ff, 0x06291100, + 0x072a10ff, 0x082a0e00, 0x092a0d00, 0x0a2a0c00, + + 0xff0d280c, 0xff0e280b, 0xff0f280a, 0xff102809, + 0xff112808, 0xff122708, 0xff142706, 0xff152705, + 0xff162605, 0xff172604, 0xff192503, 0xff1a2403, + 0x001b2302, 0x001c2202, 0x001d2201, 0x001e2101, + 0x011f1f01, 0x01211e00, 0x01221d00, 0x02221c00, + 0x02231b00, 0x03241900, 0x04241800, 0x04251700, + 0x052616ff, 0x06261400, 0x072713ff, 0x08271100, + 0x08271100, 0x09271000, 0x0a280e00, 0x0b280d00, + + 0xff0e260d, 0xff0f260c, 0xff10260b, 0xff11260a, + 0xff122609, 0xff132608, 0xff142508, 0xff152507, + 0x00152506, 0x00172405, 0x00182305, 0x00192304, + 0x001b2203, 0x001c2103, 0x011d2002, 0x011d2002, + 0x011f1f01, 0x021f1e01, 0x02201d01, 0x03211c00, + 0x03221b00, 0x04221a00, 0x04231801, 0x05241700, + 0x06241600, 0x07241500, 0x08251300, 0x09251200, + 0x09261100, 0x0a261000, 0x0b260f00, 0x0c260e00, + + 0xff0e250e, 0xff0f250d, 0xff10250c, 0xff11250b, + 0x0011250a, 0x00132409, 0x00142408, 0x00152407, + 0x00162307, 0x00172306, 0x00182206, 0x00192205, + 0x011a2104, 0x011b2004, 0x011c2003, 0x021c1f03, + 0x021e1e02, 0x031e1d02, 0x03201c01, 0x04201b01, + 0x04211a01, 0x05221900, 0x05221801, 0x06231700, + 0x07231600, 0x07241500, 0x08241400, 0x09241300, + 0x0a241200, 0x0b241100, 0x0c241000, 0x0d240f00, + + 0x000e240e, 0x000f240d, 0x0010240c, 0x0011240b, + 0x0013230a, 0x0013230a, 0x00142309, 0x00152308, + 0x00162208, 0x00172207, 0x01182106, 0x01192105, + 0x011a2005, 0x021b1f04, 0x021b1f04, 0x021d1e03, + 0x031d1d03, 0x031e1d02, 0x041e1c02, 0x041f1b02, + 0x05201a01, 0x05211901, 0x06211801, 0x07221700, + 0x07221601, 0x08231500, 0x09231400, 0x0a231300, + 0x0a231300, 0x0b231200, 0x0c231100, 0x0d231000, + + 0x000f220f, 0x0010220e, 0x0011220d, 0x0012220c, + 0x0013220b, 0x0013220b, 0x0015210a, 0x0015210a, + 0x01162108, 0x01172008, 0x01182007, 0x02191f06, + 0x02191f06, 0x021a1e06, 0x031a1e05, 0x031c1d04, + 0x041c1c04, 0x041d1c03, 0x051d1b03, 0x051e1a03, + 0x061f1902, 0x061f1902, 0x07201801, 0x08201701, + 0x08211601, 0x09211501, 0x0a211500, 0x0b211400, + 0x0b221300, 0x0c221200, 0x0d221100, 0x0e221000, + + 0x0010210f, 0x0011210e, 0x0011210e, 0x0012210d, + 0x0013210c, 0x0014200c, 0x0114200b, 0x0115200a, + 0x01161f0a, 0x01171f09, 0x02171f08, 0x02181e08, + 0x03181e07, 0x031a1d06, 0x031a1d06, 0x041b1c05, + 0x041c1c04, 0x051c1b04, 0x051d1a04, 0x061d1a03, + 0x071d1903, 0x071e1803, 0x081e1802, 0x081f1702, + 0x091f1602, 0x0a201501, 0x0b1f1501, 0x0b201401, + 0x0c211300, 0x0d211200, 0x0e201200, 0x0e211100, + + 0x00102010, 0x0011200f, 0x0012200e, 0x0013200d, + 0x0013200d, 0x01141f0c, 0x01151f0b, 0x01151f0b, + 0x01161f0a, 0x02171e09, 0x02171e09, 0x03181d08, + 0x03191d07, 0x03191d07, 0x041a1c06, 0x041b1c05, + 0x051b1b05, 0x051c1b04, 0x061c1a04, 0x071d1903, + 0x071d1903, 0x081d1803, 0x081e1703, 0x091e1702, + 0x0a1f1601, 0x0a1f1502, 0x0b1f1501, 0x0c1f1401, + 0x0d201300, 0x0d201300, 0x0e201200, 0x0f201100, + + 0x00102010, 0x0011200f, 0x00121f0f, 0x00131f0e, + 0x00141f0d, 0x01141f0c, 0x01141f0c, 0x01151e0c, + 0x02161e0a, 0x02171e09, 0x03171d09, 0x03181d08, + 0x03181d08, 0x04191c07, 0x041a1c06, 0x051a1b06, + 0x051b1b05, 0x061b1a05, 0x061c1a04, 0x071c1904, + 0x081c1903, 0x081d1803, 0x091d1703, 0x091e1702, + 0x0a1e1602, 0x0b1e1502, 0x0c1e1501, 0x0c1f1401, + 0x0d1f1400, 0x0e1f1300, 0x0e1f1201, 0x0f1f1200, + + 0x00111e11, 0x00121e10, 0x00131e0f, 0x00131e0f, + 0x01131e0e, 0x01141d0e, 0x02151d0c, 0x02151d0c, + 0x02161d0b, 0x03161c0b, 0x03171c0a, 0x04171c09, + 0x04181b09, 0x05181b08, 0x05191b07, 0x06191a07, + 0x061a1a06, 0x071a1906, 0x071b1905, 0x081b1805, + 0x091b1804, 0x091c1704, 0x0a1c1703, 0x0a1c1604, + 0x0b1d1602, 0x0c1d1502, 0x0c1d1502, 0x0d1d1402, + 0x0e1d1401, 0x0e1e1301, 0x0f1e1300, 0x101e1200, + + 0x00111e11, 0x00121e10, 0x00131d10, 0x01131d0f, + 0x01141d0e, 0x01141d0e, 0x02151c0d, 0x02151c0d, + 0x03161c0b, 0x03161c0b, 0x04171b0a, 0x04171b0a, + 0x05171b09, 0x05181a09, 0x06181a08, 0x06191a07, + 0x07191907, 0x071a1906, 0x081a1806, 0x081a1806, + 0x091a1805, 0x0a1b1704, 0x0a1b1704, 0x0b1c1603, + 0x0b1c1603, 0x0c1c1503, 0x0d1c1502, 0x0d1d1402, + 0x0e1d1401, 0x0f1d1301, 0x0f1d1301, 0x101e1200, +}; + +static const u32 bicubic8coefftab32_left[480] = { + 0x40000000, 0x40ff0000, 0x3ffe0000, 0x3efe0000, + 0x3dfd0000, 0x3cfc0000, 0x3bfc0000, 0x39fc0000, + 0x36fc0000, 0x35fb0000, 0x33fb0000, 0x31fb0000, + 0x2ffb0000, 0x2cfb0000, 0x29fc0000, 0x27fc0000, + 0x24fc0000, 0x21fc0000, 0x1efd0000, 0x1cfd0000, + 0x19fd0000, 0x16fe0000, 0x14fe0000, 0x11fe0000, + 0x0dff0000, 0x0cff0000, 0x0aff0000, 0x08ff0000, + 0x05000000, 0x03000000, 0x02000000, 0x01000000, + + 0x3904ff00, 0x3903ff00, 0x3902ff00, 0x38010000, + 0x37000000, 0x36ff0000, 0x35ff0000, 0x34fe0000, + 0x32fe0000, 0x31fd0000, 0x30fd0000, 0x2efc0000, + 0x2cfc0000, 0x2afc0000, 0x28fc0000, 0x26fc0000, + 0x24fc0000, 0x22fc0000, 0x20fc0000, 0x1efc0000, + 0x1cfc0000, 0x19fc0000, 0x17fc0000, 0x15fd0000, + 0x12fd0000, 0x11fd0000, 0x0ffd0000, 0x0dfe0000, + 0x0bfe0000, 0x09fe0000, 0x08fe0000, 0x06ff0000, + + 0x3209fe00, 0x3407fe00, 0x3306fe00, 0x3305fe00, + 0x3204fe00, 0x3102ff00, 0x3102ff00, 0x3001ff00, + 0x2f00ff00, 0x2effff00, 0x2cff0000, 0x2bfe0000, + 0x29fe0000, 0x28fe0000, 0x26fd0000, 0x24fd0000, + 0x23fd0000, 0x21fd0000, 0x20fc0000, 0x1efc0000, + 0x1dfc0000, 0x1bfc0000, 0x19fc0000, 0x17fc0000, + 0x16fc0000, 0x14fc0000, 0x12fc0000, 0x10fd0000, + 0x0ffd0000, 0x0dfd0000, 0x0cfd0000, 0x0afd0000, + + 0x2e0cfd00, 0x2e0bfd00, 0x2e09fd00, 0x2e08fd00, + 0x2e07fd00, 0x2c06fe00, 0x2c05fe00, 0x2b04fe00, + 0x2b03fe00, 0x2a02fe00, 0x2901fe00, 0x2701ff00, + 0x2700ff00, 0x26ffff00, 0x24ffff00, 0x23ffff00, + 0x22feff00, 0x20fe0000, 0x1ffe0000, 0x1efd0000, + 0x1dfd0000, 0x1bfd0000, 0x1afd0000, 0x19fd0000, + 0x17fd0000, 0x15fd0000, 0x13fd0000, 0x12fd0000, + 0x11fd0000, 0x10fd0000, 0x0ffd0000, 0x0cfd0000, + + 0x2a0efd00, 0x2a0dfd00, 0x2a0cfd00, 0x290bfd00, + 0x290afd00, 0x2909fd00, 0x2908fd00, 0x2807fd00, + 0x2706fd00, 0x2705fd00, 0x2604fe00, 0x2603fe00, + 0x2502fe00, 0x2402fe00, 0x2401fe00, 0x2200fe00, + 0x2200fe00, 0x2000ff00, 0x1fffff00, 0x1effff00, + 0x1dfeff00, 0x1cfeff00, 0x1afeff00, 0x19feff00, + 0x17fe0000, 0x16fd0000, 0x15fd0000, 0x14fd0000, + 0x12fd0000, 0x11fd0000, 0x10fd0000, 0x0ffd0000, + + 0x2610fd00, 0x260ffd00, 0x260efd00, 0x260dfd00, + 0x260cfd00, 0x260bfd00, 0x260afd00, 0x2609fd00, + 0x2508fd00, 0x2507fd00, 0x2406fd00, 0x2406fd00, + 0x2305fd00, 0x2304fd00, 0x2203fe00, 0x2103fe00, + 0x2002fe00, 0x1f01fe00, 0x1e01fe00, 0x1e00fe00, + 0x1c00fe00, 0x1b00fe00, 0x1afffe00, 0x19ffff00, + 0x18ffff00, 0x17feff00, 0x16feff00, 0x15feff00, + 0x14feff00, 0x13feff00, 0x11feff00, 0x10fd0000, + + 0x2411feff, 0x2410feff, 0x240ffeff, 0x230efeff, + 0x240dfeff, 0x240cfeff, 0x230cfd00, 0x230bfd00, + 0x230afd00, 0x2309fd00, 0x2208fd00, 0x2108fd00, + 0x2007fd00, 0x2106fd00, 0x2005fd00, 0x1f05fd00, + 0x1f04fd00, 0x1e03fd00, 0x1d03fe00, 0x1c02fe00, + 0x1b02fe00, 0x1a01fe00, 0x1a01fe00, 0x1900fe00, + 0x1800fe00, 0x1700fe00, 0x16fffe00, 0x15fffe00, + 0x13ffff00, 0x12ffff00, 0x12feff00, 0x11feff00, + + 0x2212fffe, 0x2211fffe, 0x2210ffff, 0x220ffeff, + 0x220efeff, 0x210efeff, 0x210dfeff, 0x210cfeff, + 0x210bfeff, 0x200bfeff, 0x200afeff, 0x1f09feff, + 0x1f08feff, 0x1d08fe00, 0x1e07fd00, 0x1e06fd00, + 0x1d06fd00, 0x1c05fd00, 0x1b04fe00, 0x1a04fe00, + 0x1a03fe00, 0x1903fe00, 0x1802fe00, 0x1802fe00, + 0x1701fe00, 0x1601fe00, 0x1501fe00, 0x1500fe00, + 0x1400fe00, 0x1400fe00, 0x13fffe00, 0x12fffe00, + + 0x201200fe, 0x201100fe, 0x1f1100fe, 0x2010fffe, + 0x200ffffe, 0x1f0ffffe, 0x1f0efffe, 0x1e0dffff, + 0x1f0cfeff, 0x1e0cfeff, 0x1e0bfeff, 0x1e0afeff, + 0x1d0afeff, 0x1d09feff, 0x1c08feff, 0x1b08feff, + 0x1b07feff, 0x1a07feff, 0x1a06feff, 0x1a05feff, + 0x1805fe00, 0x1904fe00, 0x1704fe00, 0x1703fe00, + 0x1603fe00, 0x1602fe00, 0x1402fe00, 0x1402fe00, + 0x1401fe00, 0x1301fe00, 0x1201fe00, 0x1200fe00, + + 0x1c1202fe, 0x1c1102fe, 0x1b1102fe, 0x1c1001fe, + 0x1b1001fe, 0x1c0f01fe, 0x1b0f00fe, 0x1b0e00fe, + 0x1b0e00fe, 0x1b0d00fe, 0x1b0c00fe, 0x1a0cfffe, + 0x1a0bfffe, 0x1a0bfffe, 0x190afffe, 0x190afffe, + 0x1909fffe, 0x1709ffff, 0x1808ffff, 0x1708feff, + 0x1707feff, 0x1707feff, 0x1606feff, 0x1506feff, + 0x1505feff, 0x1505feff, 0x1404feff, 0x1404feff, + 0x1404feff, 0x1303feff, 0x1203feff, 0x1202feff, + + 0x191104fe, 0x191104fe, 0x191003fe, 0x191003fe, + 0x171003fe, 0x180f03fe, 0x180f02fe, 0x180e02fe, + 0x180e02fe, 0x180d01fe, 0x180d01fe, 0x180d01fe, + 0x170c01fe, 0x160c01fe, 0x170b00fe, 0x170b00fe, + 0x160a00fe, 0x160a00fe, 0x160a00fe, 0x150900fe, + 0x1509fffe, 0x1508fffe, 0x1508fffe, 0x1408fffe, + 0x1407fffe, 0x1307ffff, 0x1306ffff, 0x1206ffff, + 0x1206ffff, 0x1205ffff, 0x1205ffff, 0x1104feff, + + 0x161006ff, 0x161005ff, 0x161005ff, 0x160f05ff, + 0x160f04ff, 0x150f04ff, 0x150e04ff, 0x150e04ff, + 0x150e03ff, 0x150d03ff, 0x150d03ff, 0x150d02ff, + 0x140c02ff, 0x150c02fe, 0x150c02fe, 0x150b02fe, + 0x140b01fe, 0x140b01fe, 0x140a01fe, 0x140a01fe, + 0x140a01fe, 0x130900fe, 0x130900fe, 0x130900fe, + 0x130800fe, 0x120800fe, 0x120800fe, 0x120700fe, + 0x120700fe, 0x1107fffe, 0x1106fffe, 0x1106fffe, + + 0x140f0700, 0x140f0600, 0x140f0600, 0x140f0600, + 0x140e0600, 0x130e0500, 0x140e05ff, 0x130e05ff, + 0x140d05ff, 0x130d04ff, 0x130d04ff, 0x120d04ff, + 0x130c04ff, 0x130c03ff, 0x130c03ff, 0x120c03ff, + 0x120b03ff, 0x120b02ff, 0x120b02ff, 0x120a02ff, + 0x120a02ff, 0x110a02ff, 0x110a01ff, 0x120901ff, + 0x100901ff, 0x100901ff, 0x110801ff, 0x110801ff, + 0x100800ff, 0x100800ff, 0x100700ff, 0x100700fe, + + 0x120f0701, 0x120e0701, 0x120e0701, 0x120e0701, + 0x120e0600, 0x110e0600, 0x120d0600, 0x120d0600, + 0x120d0500, 0x120d0500, 0x110d0500, 0x110c0500, + 0x110c0500, 0x110c0400, 0x110c0400, 0x110b04ff, + 0x110b04ff, 0x110b04ff, 0x110b03ff, 0x110b03ff, + 0x110a03ff, 0x110a03ff, 0x100a03ff, 0x110a02ff, + 0x100902ff, 0x100902ff, 0x100902ff, 0x0f0902ff, + 0x0e0902ff, 0x100801ff, 0x0f0801ff, 0x0f0801ff, + + 0x100e0802, 0x100e0802, 0x110e0702, 0x110d0701, + 0x110d0701, 0x100d0701, 0x100d0701, 0x110d0601, + 0x110d0601, 0x110c0601, 0x110c0601, 0x100c0600, + 0x100c0500, 0x100c0500, 0x100c0500, 0x100b0500, + 0x100b0500, 0x100b0400, 0x100b0400, 0x0f0b0400, + 0x100a0400, 0x0f0a0400, 0x0f0a0400, 0x0f0a0300, + 0x0f0a03ff, 0x0f0903ff, 0x0f0903ff, 0x0f0903ff, + 0x0f0903ff, 0x0f0902ff, 0x0f0902ff, 0x0f0802ff +}; + +static const u32 bicubic8coefftab32_right[480] = { + 0x00000000, 0x00000001, 0x00000003, 0x00000004, + 0x00000006, 0x0000ff09, 0x0000ff0a, 0x0000ff0c, + 0x0000ff0f, 0x0000fe12, 0x0000fe14, 0x0000fe16, + 0x0000fd19, 0x0000fd1c, 0x0000fd1e, 0x0000fc21, + 0x0000fc24, 0x0000fc27, 0x0000fc29, 0x0000fb2c, + 0x0000fb2f, 0x0000fb31, 0x0000fb33, 0x0000fb36, + 0x0000fc38, 0x0000fc39, 0x0000fc3b, 0x0000fc3d, + 0x0000fd3e, 0x0000fe3f, 0x0000fe40, 0x0000ff40, + + 0x0000ff05, 0x0000ff06, 0x0000fe08, 0x0000fe09, + 0x0000fe0b, 0x0000fe0d, 0x0000fd0f, 0x0000fd11, + 0x0000fd13, 0x0000fd15, 0x0000fc17, 0x0000fc1a, + 0x0000fc1c, 0x0000fc1e, 0x0000fc20, 0x0000fc22, + 0x0000fc24, 0x0000fc26, 0x0000fc28, 0x0000fc2a, + 0x0000fc2c, 0x0000fc2f, 0x0000fd30, 0x0000fd31, + 0x0000fe33, 0x0000fe34, 0x0000ff35, 0x0000ff36, + 0x00000037, 0x00000138, 0x00ff0239, 0x00ff0339, + + 0x0000fe09, 0x0000fd0a, 0x0000fd0c, 0x0000fd0d, + 0x0000fd0f, 0x0000fd11, 0x0000fc12, 0x0000fc14, + 0x0000fc16, 0x0000fc18, 0x0000fc19, 0x0000fc1b, + 0x0000fc1d, 0x0000fc1e, 0x0000fc21, 0x0000fd22, + 0x0000fd23, 0x0000fd25, 0x0000fd27, 0x0000fe28, + 0x0000fe29, 0x0000fe2b, 0x0000ff2c, 0x00ffff2f, + 0x00ff002f, 0x00ff0130, 0x00ff0231, 0x00ff0232, + 0x00fe0432, 0x00fe0533, 0x00fe0633, 0x00fe0734, + + 0x0000fd0c, 0x0000fd0d, 0x0000fd0f, 0x0000fd10, + 0x0000fd11, 0x0000fd13, 0x0000fd14, 0x0000fd16, + 0x0000fd17, 0x0000fd19, 0x0000fd1b, 0x0000fd1c, + 0x0000fd1d, 0x0000fd1f, 0x0000fe20, 0x0000fe21, + 0x00fffe24, 0x00ffff24, 0x00ffff25, 0x00ffff27, + 0x00ff0027, 0x00ff0128, 0x00fe012a, 0x00fe022a, + 0x00fe032b, 0x00fe042c, 0x00fe052d, 0x00fe062d, + 0x00fd072e, 0x00fd082e, 0x00fd092e, 0x00fd0b2f, + + 0x0000fd0e, 0x0000fd0f, 0x0000fd10, 0x0000fd12, + 0x0000fd13, 0x0000fd14, 0x0000fd15, 0x0000fd17, + 0x0000fe18, 0x00fffe1a, 0x00fffe1b, 0x00fffe1c, + 0x00fffe1e, 0x00ffff1e, 0x00ffff1f, 0x00ff0021, + 0x00fe0022, 0x00fe0023, 0x00fe0124, 0x00fe0224, + 0x00fe0226, 0x00fe0326, 0x00fe0427, 0x00fd0528, + 0x00fd0628, 0x00fd0729, 0x00fd0829, 0x00fd0929, + 0x00fd0a2a, 0x00fd0b2a, 0x00fd0c2a, 0x00fd0d2a, + + 0x0000fd10, 0x0000fd11, 0x00fffe12, 0x00fffe13, + 0x00fffe14, 0x00fffe15, 0x00fffe16, 0x00fffe17, + 0x00ffff18, 0x00ffff19, 0x00feff1c, 0x00fe001b, + 0x00fe001d, 0x00fe001e, 0x00fe011e, 0x00fe011f, + 0x00fe0220, 0x00fe0321, 0x00fe0322, 0x00fd0423, + 0x00fd0524, 0x00fd0624, 0x00fd0626, 0x00fd0725, + 0x00fd0825, 0x00fd0926, 0x00fd0a26, 0x00fd0b26, + 0x00fd0c26, 0x00fd0d26, 0x00fd0e27, 0x00fd0f27, + + 0x00fffe11, 0x00fffe12, 0x00fffe13, 0x00ffff14, + 0x00ffff14, 0x00feff16, 0x00feff17, 0x00fe0017, + 0x00fe0018, 0x00fe0019, 0x00fe011a, 0x00fe011b, + 0x00fe021c, 0x00fe021c, 0x00fe031d, 0x00fd031f, + 0x00fd041f, 0x00fd0520, 0x00fd0520, 0x00fd0621, + 0x00fd0721, 0x00fd0822, 0x00fd0822, 0x00fd0923, + 0x00fd0a23, 0x00fd0b23, 0x00fd0b25, 0x00fe0c24, + 0x00fe0d24, 0x00fe0e24, 0x00fe0f24, 0x00fe1024, + + 0x00feff12, 0x00feff13, 0x00feff13, 0x00fe0014, + 0x00fe0015, 0x00fe0016, 0x00fe0116, 0x00fe0117, + 0x00fe0118, 0x00fe0218, 0x00fe0219, 0x00fe031a, + 0x00fe031b, 0x00fe041b, 0x00fd041d, 0x00fd051d, + 0x00fd061d, 0x00fd061f, 0x00fe071e, 0x00fe081e, + 0x00fe081f, 0x00fe091f, 0x00fe0a20, 0x00fe0a20, + 0x00fe0b21, 0x00fe0c21, 0x00fe0d21, 0x00fe0d22, + 0x00fe0e22, 0x00fe0f21, 0x00ff1021, 0x00ff1022, + + 0x00fe0012, 0x00fe0013, 0x00fe0113, 0x00fe0114, + 0x00fe0115, 0x00fe0215, 0x00fe0216, 0x00fe0217, + 0x00fe0317, 0x00fe0318, 0x00fe0418, 0x00fe0419, + 0x00fe0519, 0x00fe051a, 0x00fe061b, 0x00fe071b, + 0x00fe071c, 0x00fe081c, 0x00fe081d, 0x00fe091d, + 0x00fe0a1d, 0x00fe0a1d, 0x00fe0b1e, 0x00fe0c1e, + 0x00ff0c1e, 0x00ff0d1e, 0x00ff0e1f, 0x00ff0e1f, + 0x00ff0f1f, 0x00ff0f20, 0x0000101f, 0x0000111f, + + 0x00fe0212, 0x00fe0312, 0x00fe0313, 0x00fe0314, + 0x00fe0414, 0x00fe0414, 0x00fe0515, 0x00fe0516, + 0x00fe0516, 0x00fe0616, 0x00fe0617, 0x00fe0718, + 0x00fe0719, 0x00fe0818, 0x00ff0819, 0x00ff0918, + 0x00ff0919, 0x00ff0a19, 0x00ff0a19, 0x00ff0b1a, + 0x00ff0b1b, 0x00ff0c1a, 0x00000c1b, 0x00000d1b, + 0x00000d1c, 0x00000e1b, 0x00000e1d, 0x00010f1b, + 0x00010f1b, 0x0001101c, 0x0001101d, 0x0002111c, + + 0x00fe0412, 0x00fe0412, 0x00ff0512, 0x00ff0512, + 0x00ff0613, 0x00ff0613, 0x00ff0614, 0x00ff0714, + 0x00ff0714, 0x00ff0815, 0x00ff0815, 0x00ff0815, + 0x00ff0916, 0x00000916, 0x00000a16, 0x00000a16, + 0x00000a18, 0x00000b17, 0x00000b17, 0x00010c17, + 0x00010c18, 0x00010d18, 0x00010d18, 0x00010d19, + 0x00020e18, 0x00020e18, 0x00020f18, 0x00030f18, + 0x00030f18, 0x00031018, 0x00031018, 0x00041119, + + 0x00ff0610, 0x00ff0611, 0x00ff0611, 0x00ff0711, + 0x00000711, 0x00000712, 0x00000812, 0x00000812, + 0x00000813, 0x00000913, 0x00000913, 0x00000914, + 0x00010a14, 0x00010a14, 0x00010a14, 0x00010b14, + 0x00010b16, 0x00020b15, 0x00020c15, 0x00020c15, + 0x00020c15, 0x00020d17, 0x00030d16, 0x00030d16, + 0x00030e16, 0x00040e16, 0x00040e16, 0x00040f16, + 0x00040f16, 0x00050f17, 0x00051017, 0x00051017, + + 0x0000070f, 0x00000710, 0x00000710, 0x00000710, + 0x00000810, 0x00010811, 0x00010811, 0x00010911, + 0x00010911, 0x00010913, 0x00010913, 0x00020a12, + 0x00020a12, 0x00020a13, 0x00020b12, 0x00020b13, + 0x00030b13, 0x00030c13, 0x00030c13, 0x00030c14, + 0x00040c13, 0x00040d13, 0x00040d14, 0x00040d14, + 0x00050e14, 0x00050e14, 0x00050e14, 0x00050e14, + 0x00060f14, 0x00060f14, 0x00060f15, 0x00061015, + + 0x0001070f, 0x0001080f, 0x0001080f, 0x0001080f, + 0x00010811, 0x00020910, 0x00020910, 0x00020910, + 0x00020911, 0x00020a10, 0x00030a10, 0x00030a11, + 0x00030a11, 0x00030b11, 0x00030b11, 0x00040b12, + 0x00040b12, 0x00040c11, 0x00040c12, 0x00040c12, + 0x00050c12, 0x00050c12, 0x00050d12, 0x00050d12, + 0x00060d13, 0x00060d13, 0x00060e12, 0x00060e13, + 0x00070e13, 0x00070e13, 0x00070f13, 0x00070f13, + + 0x0002080e, 0x0002080e, 0x0002080e, 0x00020810, + 0x0002090f, 0x0003090f, 0x0003090f, 0x0003090f, + 0x0003090f, 0x00030a0f, 0x00030a0f, 0x00040a10, + 0x00040a11, 0x00040b10, 0x00040b10, 0x00040b11, + 0x00050b10, 0x00050b11, 0x00050c10, 0x00050c11, + 0x00050c11, 0x00060c11, 0x00060c11, 0x00060d11, + 0x00060d12, 0x00070d12, 0x00070d12, 0x00070e11, + 0x00070e11, 0x00070e12, 0x00080e11, 0x00080e12 +}; + +static const u32 bicubic4coefftab32[480] = { + 0x00004000, 0x000140ff, 0x00033ffe, 0x00043ffd, + 0x00063dfd, 0xff083dfc, 0xff0a3bfc, 0xff0c39fc, + 0xff0e37fc, 0xfe1136fb, 0xfe1433fb, 0xfe1631fb, + 0xfd192ffb, 0xfd1c2cfb, 0xfd1e29fc, 0xfc2127fc, + 0xfc2424fc, 0xfc2721fc, 0xfc291efd, 0xfb2c1cfd, + 0xfb2f19fd, 0xfb3116fe, 0xfb3314fe, 0xfb3611fe, + 0xfc370eff, 0xfc390cff, 0xfc3b0aff, 0xfc3d08ff, + 0xfd3d0600, 0xfd3f0400, 0xfe3f0300, 0xff400100, + + 0xfe053904, 0xfe063903, 0xfe083901, 0xfe0a3800, + 0xfd0b3800, 0xfe0d36ff, 0xfd0f35ff, 0xfd1134fe, + 0xfd1332fe, 0xfd1531fd, 0xfc1730fd, 0xfc1a2efc, + 0xfc1c2cfc, 0xfc1e2afc, 0xfc2028fc, 0xfc2226fc, + 0xfc2424fc, 0xfc2622fc, 0xfc2820fc, 0xfc2a1efc, + 0xfc2c1cfc, 0xfc2e1afc, 0xfd3017fc, 0xfd3115fd, + 0xfe3213fd, 0xfe3411fd, 0xff350ffd, 0xff360dfe, + 0x00370bfe, 0x013809fe, 0x023808fe, 0x033806ff, + + 0xfd093208, 0xfd0a3207, 0xfd0c3205, 0xfd0d3204, + 0xfc0f3203, 0xfc113102, 0xfc123002, 0xfc143000, + 0xfc152f00, 0xfc172d00, 0xfc192cff, 0xfc1b2bfe, + 0xfc1d29fe, 0xfc1e28fe, 0xfc2027fd, 0xfd2125fd, + 0xfd2323fd, 0xfd2521fd, 0xfd2720fc, 0xfe281efc, + 0xfe291dfc, 0xfe2b1bfc, 0xff2c19fc, 0x002d17fc, + 0x002e16fc, 0x012f14fc, 0x022f12fd, 0x023110fd, + 0x03310ffd, 0x05310dfd, 0x06320bfd, 0x07320afd, + + 0xfc0c2d0b, 0xfc0d2d0a, 0xfc0e2d09, 0xfc102d07, + 0xfc112c07, 0xfc132c05, 0xfc142c04, 0xfc162b03, + 0xfc172a03, 0xfc192a01, 0xfc1a2901, 0xfd1b2800, + 0xfd1c2700, 0xfd1e2500, 0xfe1f24ff, 0xfe2023ff, + 0xfe2222fe, 0xff2320fe, 0xff241ffe, 0x00251efd, + 0x00271cfd, 0x01271bfd, 0x01281afd, 0x022918fd, + 0x032a16fd, 0x032b15fd, 0x042b14fd, 0x052c12fd, + 0x072c10fd, 0x082c0ffd, 0x092c0efd, 0x0a2c0dfd, + + 0xfd0d290d, 0xfd0e290c, 0xfd0f290b, 0xfd11280a, + 0xfd122809, 0xfd132808, 0xfd142807, 0xfd162706, + 0xfd172705, 0xfd192604, 0xfe1a2503, 0xfe1b2502, + 0xfe1c2402, 0xfe1d2302, 0xff1e2201, 0xff1f2101, + 0x00202000, 0x00211f00, 0x01221eff, 0x02221dff, + 0x02241cfe, 0x03241bfe, 0x042519fe, 0x042618fe, + 0x052617fe, 0x062716fd, 0x072714fe, 0x082713fe, + 0x092812fd, 0x0a2811fd, 0x0b2810fd, 0x0c280ffd, + + 0xfd0f250f, 0xfd10250e, 0xfd11250d, 0xfd12250c, + 0xfd13250b, 0xfe13250a, 0xfe152409, 0xfe162408, + 0xfe172308, 0xff182306, 0xff192305, 0xff1a2205, + 0x001b2104, 0x001c2103, 0x001d2003, 0x011e1f02, + 0x011f1f01, 0x021f1e01, 0x03201d00, 0x03211c00, + 0x04211b00, 0x05221aff, 0x062219ff, 0x062318ff, + 0x082316ff, 0x082316ff, 0x092415fe, 0x0a2414fe, + 0x0b2413fe, 0x0c2412fe, 0x0d2411fe, 0x0e2410fe, + + 0xfe10230f, 0xfe11230e, 0xfe12220e, 0xfe13220d, + 0xfe14220c, 0xff14220b, 0xff15220a, 0xff16210a, + 0x00162109, 0x00172108, 0x00182008, 0x01192006, + 0x011a1f06, 0x021a1f05, 0x021b1e05, 0x031c1d04, + 0x031d1d03, 0x041d1c03, 0x041e1b03, 0x051e1b02, + 0x061f1a01, 0x06201901, 0x07201801, 0x08201800, + 0x09201700, 0x0a211500, 0x0b2115ff, 0x0c2114ff, + 0x0c2213ff, 0x0d2212ff, 0x0e2211ff, 0x0f2211fe, + + 0xff112010, 0xff12200f, 0xff12200f, 0xff13200e, + 0x0013200d, 0x0014200c, 0x00151f0c, 0x00161f0b, + 0x01161f0a, 0x01171e0a, 0x02171e09, 0x02181e08, + 0x03191d07, 0x03191d07, 0x041a1c06, 0x041b1c05, + 0x051b1b05, 0x051c1b04, 0x061c1a04, 0x071c1a03, + 0x071d1903, 0x081e1802, 0x091d1802, 0x091e1702, + 0x0a1f1601, 0x0b1f1600, 0x0b1f1501, 0x0c201400, + 0x0d1f1400, 0x0e2013ff, 0x0f1f1200, 0x102011ff, + + 0x00111f10, 0x00121e10, 0x00131e0f, 0x00131e0f, + 0x01131e0e, 0x01141e0d, 0x01151d0d, 0x02151d0c, + 0x02161d0b, 0x03161d0a, 0x03171c0a, 0x04171c09, + 0x04181c08, 0x05181b08, 0x05191b07, 0x06191a07, + 0x061a1a06, 0x071a1906, 0x071b1905, 0x081b1805, + 0x081c1804, 0x091c1704, 0x0a1c1703, 0x0a1d1603, + 0x0b1d1602, 0x0c1d1502, 0x0c1d1502, 0x0d1e1401, + 0x0e1d1401, 0x0e1e1301, 0x0f1e1300, 0x101e1200, + + 0x02111c11, 0x02121c10, 0x02131b10, 0x03131b0f, + 0x03131b0f, 0x03141b0e, 0x04141b0d, 0x04151a0d, + 0x05151a0c, 0x05151a0c, 0x05161a0b, 0x0616190b, + 0x0616190b, 0x0716190a, 0x0717180a, 0x08171809, + 0x08181808, 0x09181708, 0x09181708, 0x0a181707, + 0x0a191607, 0x0b191606, 0x0b1a1605, 0x0c1a1505, + 0x0c1a1505, 0x0d1a1504, 0x0d1b1404, 0x0e1b1403, + 0x0f1b1303, 0x0f1b1303, 0x101b1302, 0x101c1202, + + 0x04111a11, 0x04121911, 0x04131910, 0x0513190f, + 0x0513190f, 0x0513190f, 0x0613190e, 0x0614180e, + 0x0714180d, 0x0714180d, 0x0715180c, 0x0814180c, + 0x0815170c, 0x0816170b, 0x0916170a, 0x0916170a, + 0x0a16160a, 0x0a171609, 0x0a171609, 0x0b171608, + 0x0b171509, 0x0c171508, 0x0c181507, 0x0d171507, + 0x0d181407, 0x0e181406, 0x0e181406, 0x0e191306, + 0x0f191305, 0x0f191305, 0x10191304, 0x10191205, + + 0x05121811, 0x06121810, 0x06121810, 0x06131710, + 0x0713170f, 0x0713170f, 0x0713170f, 0x0813170e, + 0x0813170e, 0x0814170d, 0x0914160d, 0x0914160d, + 0x0914160d, 0x0a14160c, 0x0a15160b, 0x0a15150c, + 0x0b15150b, 0x0b15150b, 0x0b16150a, 0x0c15150a, + 0x0c16140a, 0x0d161409, 0x0d161409, 0x0d171408, + 0x0e161408, 0x0e171308, 0x0e171308, 0x0f171307, + 0x0f171307, 0x10171306, 0x10181206, 0x10181206, + + 0x07111711, 0x07121710, 0x07121611, 0x08121610, + 0x08121610, 0x0813160f, 0x0912160f, 0x0913160e, + 0x0913160e, 0x0913160e, 0x0a14150d, 0x0a14150d, + 0x0a14150d, 0x0b14150c, 0x0b14150c, 0x0b14150c, + 0x0c14140c, 0x0c15140b, 0x0c15140b, 0x0c15140b, + 0x0d15140a, 0x0d15140a, 0x0d15140a, 0x0e161309, + 0x0e161309, 0x0e161309, 0x0f151309, 0x0f161308, + 0x0f161209, 0x10161208, 0x10161208, 0x10171207, + + 0x0a111411, 0x0b111410, 0x0b111410, 0x0b111410, + 0x0b111410, 0x0b12140f, 0x0b12140f, 0x0c12130f, + 0x0c12130f, 0x0c12130f, 0x0c12130f, 0x0c12130f, + 0x0d12130e, 0x0d12130e, 0x0d12130e, 0x0d13130d, + 0x0d13130d, 0x0d13130d, 0x0e12130d, 0x0e13120d, + 0x0e13120d, 0x0e13120d, 0x0e13120d, 0x0f13120c, + 0x0f13120c, 0x0f13120c, 0x0f14120b, 0x0f14120b, + 0x1013120b, 0x1013120b, 0x1013120b, 0x1014110b, + + 0x0c111310, 0x0c111310, 0x0c111310, 0x0d101310, + 0x0d101310, 0x0d111210, 0x0d111210, 0x0d111210, + 0x0d12120f, 0x0d12120f, 0x0d12120f, 0x0d12120f, + 0x0e11120f, 0x0e12120e, 0x0e12120e, 0x0e12120e, + 0x0e12120e, 0x0e12120e, 0x0e12120e, 0x0e12120e, + 0x0f11120e, 0x0f12120d, 0x0f12120d, 0x0f12120d, + 0x0f12120d, 0x0f12110e, 0x0f12110e, 0x0f12110e, + 0x1012110d, 0x1012110d, 0x1013110c, 0x1013110c, +}; + +static int sun8i_vi_scaler_coef_index(unsigned int step) +{ + unsigned int scale, int_part, float_part; + + scale = step >> (SUN8I_VI_SCALER_SCALE_FRAC - 3); + int_part = scale >> 3; + float_part = scale & 0x7; + + switch (int_part) { + case 0: + return 0; + case 1: + return float_part; + case 2: + return 8 + (float_part >> 1); + case 3: + return 12; + case 4: + return 13; + default: + return 14; + } +} + +static void sun8i_vi_scaler_set_coeff(struct regmap *map, int layer, + u32 hstep, u32 vstep, + const struct drm_format_info *format) +{ + const u32 *ch_left, *ch_right, *cy; + int offset, i; + + if (format->hsub == 1 && format->vsub == 1) { + ch_left = lan3coefftab32_left; + ch_right = lan3coefftab32_right; + cy = lan2coefftab32; + } else { + ch_left = bicubic8coefftab32_left; + ch_right = bicubic8coefftab32_right; + cy = bicubic4coefftab32; + } + + offset = sun8i_vi_scaler_coef_index(hstep) * + SUN8I_VI_SCALER_COEFF_COUNT; + for (i = 0; i < SUN8I_VI_SCALER_COEFF_COUNT; i++) { + regmap_write(map, SUN8I_SCALER_VSU_YHCOEFF0(layer, i), + lan3coefftab32_left[offset + i]); + regmap_write(map, SUN8I_SCALER_VSU_YHCOEFF1(layer, i), + lan3coefftab32_right[offset + i]); + regmap_write(map, SUN8I_SCALER_VSU_CHCOEFF0(layer, i), + ch_left[offset + i]); + regmap_write(map, SUN8I_SCALER_VSU_CHCOEFF1(layer, i), + ch_right[offset + i]); + } + + offset = sun8i_vi_scaler_coef_index(hstep) * + SUN8I_VI_SCALER_COEFF_COUNT; + for (i = 0; i < SUN8I_VI_SCALER_COEFF_COUNT; i++) { + regmap_write(map, SUN8I_SCALER_VSU_YVCOEFF(layer, i), + lan2coefftab32[offset + i]); + regmap_write(map, SUN8I_SCALER_VSU_CVCOEFF(layer, i), + cy[offset + i]); + } +} + +void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable) +{ + u32 val; + + if (enable) + val = SUN8I_SCALER_VSU_CTRL_EN | + SUN8I_SCALER_VSU_CTRL_COEFF_RDY; + else + val = 0; + + regmap_write(mixer->engine.regs, SUN8I_SCALER_VSU_CTRL(layer), val); +} + +void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer, + u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, + u32 hscale, u32 vscale, u32 hphase, u32 vphase, + const struct drm_format_info *format) +{ + u32 chphase, cvphase; + u32 insize, outsize; + + hphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16; + vphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16; + hscale <<= SUN8I_VI_SCALER_SCALE_FRAC - 16; + vscale <<= SUN8I_VI_SCALER_SCALE_FRAC - 16; + + insize = SUN8I_VI_SCALER_SIZE(src_w, src_h); + outsize = SUN8I_VI_SCALER_SIZE(dst_w, dst_h); + + /* + * This is chroma V/H phase calculation as it appears in + * BSP driver. There is no detailed explanation. YUV 420 + * chroma is threated specialy for some reason. + */ + if (format->hsub == 2 && format->vsub == 2) { + chphase = hphase >> 1; + cvphase = (vphase >> 1) - + (1UL << (SUN8I_VI_SCALER_SCALE_FRAC - 2)); + } else { + chphase = hphase; + cvphase = vphase; + } + + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_OUTSIZE(layer), outsize); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_YINSIZE(layer), insize); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_YHSTEP(layer), hscale); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_YVSTEP(layer), vscale); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_YHPHASE(layer), hphase); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_YVPHASE(layer), vphase); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_CINSIZE(layer), + SUN8I_VI_SCALER_SIZE(src_w / format->hsub, + src_h / format->vsub)); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_CHSTEP(layer), + hscale / format->hsub); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_CVSTEP(layer), + vscale / format->vsub); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_CHPHASE(layer), chphase); + regmap_write(mixer->engine.regs, + SUN8I_SCALER_VSU_CVPHASE(layer), cvphase); + sun8i_vi_scaler_set_coeff(mixer->engine.regs, layer, + hscale, vscale, format); +} diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h new file mode 100644 index 000000000..a595ab643 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 Jernej Skrabec <jernej.skrabec@siol.net> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _SUN8I_VI_SCALER_H_ +#define _SUN8I_VI_SCALER_H_ + +#include <drm/drm_fourcc.h> +#include "sun8i_mixer.h" + +/* this two macros assumes 16 fractional bits which is standard in DRM */ +#define SUN8I_VI_SCALER_SCALE_MIN 1 +#define SUN8I_VI_SCALER_SCALE_MAX ((1UL << 20) - 1) + +#define SUN8I_VI_SCALER_SCALE_FRAC 20 +#define SUN8I_VI_SCALER_PHASE_FRAC 20 +#define SUN8I_VI_SCALER_COEFF_COUNT 32 +#define SUN8I_VI_SCALER_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1)) + +#define SUN8I_SCALER_VSU_CTRL(ch) (0x20000 + 0x20000 * (ch) + 0x0) +#define SUN8I_SCALER_VSU_OUTSIZE(ch) (0x20000 + 0x20000 * (ch) + 0x40) +#define SUN8I_SCALER_VSU_YINSIZE(ch) (0x20000 + 0x20000 * (ch) + 0x80) +#define SUN8I_SCALER_VSU_YHSTEP(ch) (0x20000 + 0x20000 * (ch) + 0x88) +#define SUN8I_SCALER_VSU_YVSTEP(ch) (0x20000 + 0x20000 * (ch) + 0x8c) +#define SUN8I_SCALER_VSU_YHPHASE(ch) (0x20000 + 0x20000 * (ch) + 0x90) +#define SUN8I_SCALER_VSU_YVPHASE(ch) (0x20000 + 0x20000 * (ch) + 0x98) +#define SUN8I_SCALER_VSU_CINSIZE(ch) (0x20000 + 0x20000 * (ch) + 0xc0) +#define SUN8I_SCALER_VSU_CHSTEP(ch) (0x20000 + 0x20000 * (ch) + 0xc8) +#define SUN8I_SCALER_VSU_CVSTEP(ch) (0x20000 + 0x20000 * (ch) + 0xcc) +#define SUN8I_SCALER_VSU_CHPHASE(ch) (0x20000 + 0x20000 * (ch) + 0xd0) +#define SUN8I_SCALER_VSU_CVPHASE(ch) (0x20000 + 0x20000 * (ch) + 0xd8) +#define SUN8I_SCALER_VSU_YHCOEFF0(ch, i) \ + (0x20000 + 0x20000 * (ch) + 0x200 + 0x4 * (i)) +#define SUN8I_SCALER_VSU_YHCOEFF1(ch, i) \ + (0x20000 + 0x20000 * (ch) + 0x300 + 0x4 * (i)) +#define SUN8I_SCALER_VSU_YVCOEFF(ch, i) \ + (0x20000 + 0x20000 * (ch) + 0x400 + 0x4 * (i)) +#define SUN8I_SCALER_VSU_CHCOEFF0(ch, i) \ + (0x20000 + 0x20000 * (ch) + 0x600 + 0x4 * (i)) +#define SUN8I_SCALER_VSU_CHCOEFF1(ch, i) \ + (0x20000 + 0x20000 * (ch) + 0x700 + 0x4 * (i)) +#define SUN8I_SCALER_VSU_CVCOEFF(ch, i) \ + (0x20000 + 0x20000 * (ch) + 0x800 + 0x4 * (i)) + +#define SUN8I_SCALER_VSU_CTRL_EN BIT(0) +#define SUN8I_SCALER_VSU_CTRL_COEFF_RDY BIT(4) + +void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable); +void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer, + u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, + u32 hscale, u32 vscale, u32 hphase, u32 vphase, + const struct drm_format_info *format); + +#endif diff --git a/drivers/gpu/drm/sun4i/sunxi_engine.h b/drivers/gpu/drm/sun4i/sunxi_engine.h new file mode 100644 index 000000000..d317ea04b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sunxi_engine.h @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUNXI_ENGINE_H_ +#define _SUNXI_ENGINE_H_ + +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); +}; + +/** + * struct sunxi_engine - the common parts of an engine for sun4i-drm driver + * @ops: the operations of the engine + * @node: the of device node of the engine + * @regs: the regmap of the engine + * @id: the id of the engine (-1 if not used) + */ +struct sunxi_engine { + const struct sunxi_engine_ops *ops; + + struct device_node *node; + struct regmap *regs; + + int id; + + /* Engine list management */ + struct list_head list; +}; + +/** + * sunxi_engine_commit() - commit all changes of the engine + * @engine: pointer to the engine + */ +static inline void +sunxi_engine_commit(struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->commit) + engine->ops->commit(engine); +} + +/** + * sunxi_engine_layers_init() - Create planes (layers) for the engine + * @drm: pointer to the drm_device for which planes will be created + * @engine: pointer to the engine + */ +static inline struct drm_plane ** +sunxi_engine_layers_init(struct drm_device *drm, struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->layers_init) + return engine->ops->layers_init(drm, engine); + return ERR_PTR(-ENOSYS); +} + +/** + * sunxi_engine_apply_color_correction - Apply the RGB2YUV color correction + * @engine: pointer to the engine + * + * This functionality is optional for an engine, however, if the engine is + * intended to be used with TV Encoder, the output will be incorrect + * without the color correction, due to TV Encoder expects the engine to + * output directly YUV signal. + */ +static inline void +sunxi_engine_apply_color_correction(struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->apply_color_correction) + engine->ops->apply_color_correction(engine); +} + +/** + * sunxi_engine_disable_color_correction - Disable the color space correction + * @engine: pointer to the engine + * + * This function is paired with apply_color_correction(). + */ +static inline void +sunxi_engine_disable_color_correction(struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->disable_color_correction) + engine->ops->disable_color_correction(engine); +} +#endif /* _SUNXI_ENGINE_H_ */ |