Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_SUN4I
tristate "DRM Support for Allwinner A10 Display Engine"
depends on DRM && COMMON_CLK
depends on ARCH_SUNXI || COMPILE_TEST
select DRM_CLIENT_SELECTION
select DRM_GEM_DMA_HELPER
select DRM_KMS_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/A10s/A20/A31 HDMI Controller Support"
depends on ARM || COMPILE_TEST
select DRM_DISPLAY_HDMI_HELPER
select DRM_DISPLAY_HDMI_STATE_HELPER
select DRM_DISPLAY_HELPER
default DRM_SUN4I
help
Choose this option if you have an Allwinner A10/A10s/A20/A31
SoC with an HDMI controller.
config DRM_SUN4I_HDMI_CEC
bool "Allwinner A10/A10s/A20/A31 HDMI CEC Support"
depends on DRM_SUN4I_HDMI
select CEC_CORE
select CEC_PIN
help
Choose this option if you have an Allwinner A10/A10s/A20/A31
SoC with an HDMI controller and want to use CEC.
config DRM_SUN4I_BACKEND
tristate "Support for Allwinner A10 Display Engine Backend"
depends on ARM || COMPILE_TEST
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/A64 MIPI-DSI Controller Support"
default DRM_SUN4I
select CRC_CCITT
select DRM_MIPI_DSI
select RESET_CONTROLLER
select PHY_SUN6I_MIPI_DPHY
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_mipi_dsi.
config DRM_SUN8I_DW_HDMI
tristate "Support for Allwinner version of DesignWare HDMI"
depends on DRM_SUN4I
default DRM_SUN4I
select DRM_DW_HDMI
help
Choose this option if you have an Allwinner SoC with the
DesignWare HDMI controller. SoCs that support HDMI and
have a Display Engine 2.0 contain this controller. 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 DRM_SUN4I
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
HDMI, TVE and LCD.
endif
@@ -0,0 +1,40 @@
# 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_tcon_dclk.o
sun4i-tcon-y += sun4i_lvds.o
sun4i-tcon-y += sun4i_tcon.o
sun4i-tcon-y += sun4i_rgb.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_mipi_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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,211 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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_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;
const struct sun4i_backend_quirks *quirks;
};
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);
bool sun4i_backend_format_is_supported(uint32_t fmt, uint64_t modifier);
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);
void sun4i_backend_cleanup_layer(struct sun4i_backend *backend,
int layer);
#endif /* _SUN4I_BACKEND_H_ */
@@ -0,0 +1,256 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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 <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_modes.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.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_atomic_commit *state)
{
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
crtc);
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, crtc_state);
return ret;
}
static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
crtc);
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_atomic_commit *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, crtc, state);
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_atomic_commit *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_atomic_commit *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);
sunxi_engine_mode_set(scrtc->engine, 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;
}
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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_ */
@@ -0,0 +1,459 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/aperture.h>
#include <linux/component.h>
#include <linux/dma-mapping.h>
#include <linux/kfifo.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_module.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "sun4i_drv.h"
#include "sun4i_frontend.h"
#include "sun4i_framebuffer.h"
#include "sun4i_tcon.h"
#include "sun8i_tcon_top.h"
static int drm_sun4i_gem_dumb_create(struct drm_file *file_priv,
struct drm_device *drm,
struct drm_mode_create_dumb *args)
{
/* The hardware only allows even pitches for YUV buffers. */
args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 2);
return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
}
DEFINE_DRM_GEM_DMA_FOPS(sun4i_drv_fops);
static const struct drm_driver sun4i_drv_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
/* Generic Operations */
.fops = &sun4i_drv_fops,
.name = "sun4i-drm",
.desc = "Allwinner sun4i Display Engine",
.major = 1,
.minor = 0,
/* GEM Operations */
DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(drm_sun4i_gem_dumb_create),
DRM_FBDEV_DMA_DRIVER_OPS,
};
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;
}
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 unbind_all;
/* Remove early framebuffers (ie. simplefb) */
ret = aperture_remove_all_conflicting_devices(sun4i_drv_driver.name);
if (ret)
goto unbind_all;
sun4i_framebuffer_init(drm);
/* Enable connectors polling */
drm_kms_helper_poll_init(drm);
ret = drm_dev_register(drm, 0);
if (ret)
goto finish_poll;
drm_client_setup(drm, NULL);
dev_set_drvdata(dev, drm);
return 0;
finish_poll:
drm_kms_helper_poll_fini(drm);
unbind_all:
component_unbind_all(dev, NULL);
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);
dev_set_drvdata(dev, NULL);
drm_dev_unregister(drm);
drm_kms_helper_poll_fini(drm);
drm_atomic_helper_shutdown(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-a23-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);
}
/*
* 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, component_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;
}
#ifdef CONFIG_PM_SLEEP
static int sun4i_drv_drm_sys_suspend(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
return drm_mode_config_helper_suspend(drm);
}
static int sun4i_drv_drm_sys_resume(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
return drm_mode_config_helper_resume(drm);
}
#endif
static const struct dev_pm_ops sun4i_drv_drm_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(sun4i_drv_drm_sys_suspend,
sun4i_drv_drm_sys_resume)
};
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);
/*
* DE2 and DE3 cores actually supports 40-bit addresses, but
* driver does not.
*/
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
dma_set_max_seg_size(&pdev->dev, UINT_MAX);
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 void sun4i_drv_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &sun4i_drv_master_ops);
}
static void sun4i_drv_shutdown(struct platform_device *pdev)
{
drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}
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-a23-display-engine" },
{ .compatible = "allwinner,sun8i-a33-display-engine" },
{ .compatible = "allwinner,sun8i-a83t-display-engine" },
{ .compatible = "allwinner,sun8i-h3-display-engine" },
{ .compatible = "allwinner,sun8i-r40-display-engine" },
{ .compatible = "allwinner,sun8i-v3s-display-engine" },
{ .compatible = "allwinner,sun9i-a80-display-engine" },
{ .compatible = "allwinner,sun20i-d1-display-engine" },
{ .compatible = "allwinner,sun50i-a64-display-engine" },
{ .compatible = "allwinner,sun50i-h6-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,
.shutdown = sun4i_drv_shutdown,
.driver = {
.name = "sun4i-drm",
.of_match_table = sun4i_drv_of_table,
.pm = &sun4i_drv_drm_pm_ops,
},
};
drm_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");
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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_ */
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include "sun4i_drv.h"
#include "sun4i_framebuffer.h"
static int sun4i_de_atomic_check(struct drm_device *dev,
struct drm_atomic_commit *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 = {
.atomic_check = sun4i_de_atomic_check,
.atomic_commit = drm_atomic_helper_commit,
.fb_create = drm_gem_fb_create,
};
static const struct drm_mode_config_helper_funcs sun4i_de_mode_config_helpers = {
.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
};
void 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;
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#ifndef _SUN4I_FRAMEBUFFER_H_
#define _SUN4I_FRAMEBUFFER_H_
void sun4i_framebuffer_init(struct drm_device *drm);
#endif /* _SUN4I_FRAMEBUFFER_H_ */
@@ -0,0 +1,732 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Free Electrons
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <drm/drm_device.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.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,
};
/*
* These coefficients are taken from the A33 BSP from Allwinner.
*
* The first three values of each row are coded as 13-bit signed fixed-point
* numbers, with 10 bits for the fractional part. The fourth value is a
* constant coded as a 14-bit signed fixed-point number with 4 bits for the
* fractional part.
*
* The values in table order give the following colorspace translation:
* 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.
*/
const u32 sunxi_bt601_yuv2rgb_coef[12] = {
0x000004a7, 0x00001e6f, 0x00001cbf, 0x00000877,
0x000004a7, 0x00000000, 0x00000662, 0x00003211,
0x000004a7, 0x00000812, 0x00000000, 0x00002eb1,
};
EXPORT_SYMBOL(sunxi_bt601_yuv2rgb_coef);
static void sun4i_frontend_scaler_init(struct sun4i_frontend *frontend)
{
int i;
if (frontend->data->has_coef_access_ctrl)
regmap_write_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG,
SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL,
SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL);
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]);
}
if (frontend->data->has_coef_rdy)
regmap_write_bits(frontend->regs,
SUN4I_FRONTEND_FRM_CTRL_REG,
SUN4I_FRONTEND_FRM_CTRL_COEF_RDY,
SUN4I_FRONTEND_FRM_CTRL_COEF_RDY);
}
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);
static bool sun4i_frontend_format_chroma_requires_swap(uint32_t fmt)
{
switch (fmt) {
case DRM_FORMAT_YVU411:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_YVU422:
case DRM_FORMAT_YVU444:
return true;
default:
return false;
}
}
static bool sun4i_frontend_format_supports_tiling(uint32_t fmt)
{
switch (fmt) {
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV16:
case DRM_FORMAT_NV21:
case DRM_FORMAT_NV61:
case DRM_FORMAT_YUV411:
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YUV422:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_YVU422:
case DRM_FORMAT_YVU411:
return true;
default:
return false;
}
}
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;
unsigned int strides[3] = {};
dma_addr_t dma_addr;
bool swap;
if (fb->modifier == DRM_FORMAT_MOD_ALLWINNER_TILED) {
unsigned int width = state->src_w >> 16;
unsigned int offset;
strides[0] = SUN4I_FRONTEND_LINESTRD_TILED(fb->pitches[0]);
/*
* The X1 offset is the offset to the bottom-right point in the
* end tile, which is the final pixel (at offset width - 1)
* within the end tile (with a 32-byte mask).
*/
offset = (width - 1) & (32 - 1);
regmap_write(frontend->regs, SUN4I_FRONTEND_TB_OFF0_REG,
SUN4I_FRONTEND_TB_OFF_X1(offset));
if (fb->format->num_planes > 1) {
strides[1] =
SUN4I_FRONTEND_LINESTRD_TILED(fb->pitches[1]);
regmap_write(frontend->regs, SUN4I_FRONTEND_TB_OFF1_REG,
SUN4I_FRONTEND_TB_OFF_X1(offset));
}
if (fb->format->num_planes > 2) {
strides[2] =
SUN4I_FRONTEND_LINESTRD_TILED(fb->pitches[2]);
regmap_write(frontend->regs, SUN4I_FRONTEND_TB_OFF2_REG,
SUN4I_FRONTEND_TB_OFF_X1(offset));
}
} else {
strides[0] = fb->pitches[0];
if (fb->format->num_planes > 1)
strides[1] = fb->pitches[1];
if (fb->format->num_planes > 2)
strides[2] = fb->pitches[2];
}
/* Set the line width */
DRM_DEBUG_DRIVER("Frontend stride: %d bytes\n", fb->pitches[0]);
regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD0_REG,
strides[0]);
if (fb->format->num_planes > 1)
regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD1_REG,
strides[1]);
if (fb->format->num_planes > 2)
regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD2_REG,
strides[2]);
/* Some planar formats require chroma channel swapping by hand. */
swap = sun4i_frontend_format_chroma_requires_swap(fb->format->format);
/* Set the physical address of the buffer in memory */
dma_addr = drm_fb_dma_get_gem_addr(fb, state, 0);
DRM_DEBUG_DRIVER("Setting buffer #0 address to %pad\n", &dma_addr);
regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR0_REG, dma_addr);
if (fb->format->num_planes > 1) {
dma_addr = drm_fb_dma_get_gem_addr(fb, state, swap ? 2 : 1);
DRM_DEBUG_DRIVER("Setting buffer #1 address to %pad\n",
&dma_addr);
regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR1_REG,
dma_addr);
}
if (fb->format->num_planes > 2) {
dma_addr = drm_fb_dma_get_gem_addr(fb, state, swap ? 1 : 2);
DRM_DEBUG_DRIVER("Setting buffer #2 address to %pad\n",
&dma_addr);
regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR2_REG,
dma_addr);
}
}
EXPORT_SYMBOL(sun4i_frontend_update_buffer);
static int
sun4i_frontend_drm_format_to_input_fmt(const struct drm_format_info *format,
u32 *val)
{
if (!format->is_yuv)
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_RGB;
else if (drm_format_info_is_yuv_sampling_411(format))
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV411;
else if (drm_format_info_is_yuv_sampling_420(format))
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV420;
else if (drm_format_info_is_yuv_sampling_422(format))
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV422;
else if (drm_format_info_is_yuv_sampling_444(format))
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV444;
else
return -EINVAL;
return 0;
}
static int
sun4i_frontend_drm_format_to_input_mode(const struct drm_format_info *format,
uint64_t modifier, u32 *val)
{
bool tiled = (modifier == DRM_FORMAT_MOD_ALLWINNER_TILED);
switch (format->num_planes) {
case 1:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_PACKED;
return 0;
case 2:
*val = tiled ? SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_MB32_SEMIPLANAR
: SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_SEMIPLANAR;
return 0;
case 3:
*val = tiled ? SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_MB32_PLANAR
: SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_PLANAR;
return 0;
default:
return -EINVAL;
}
}
static int
sun4i_frontend_drm_format_to_input_sequence(const struct drm_format_info *format,
u32 *val)
{
/* Planar formats have an explicit input sequence. */
if (drm_format_info_is_yuv_planar(format)) {
*val = 0;
return 0;
}
switch (format->format) {
case DRM_FORMAT_BGRX8888:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_BGRX;
return 0;
case DRM_FORMAT_NV12:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UV;
return 0;
case DRM_FORMAT_NV16:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UV;
return 0;
case DRM_FORMAT_NV21:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VU;
return 0;
case DRM_FORMAT_NV61:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VU;
return 0;
case DRM_FORMAT_UYVY:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UYVY;
return 0;
case DRM_FORMAT_VYUY:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VYUY;
return 0;
case DRM_FORMAT_XRGB8888:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_XRGB;
return 0;
case DRM_FORMAT_YUYV:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_YUYV;
return 0;
case DRM_FORMAT_YVYU:
*val = SUN4I_FRONTEND_INPUT_FMT_DATA_PS_YVYU;
return 0;
default:
return -EINVAL;
}
}
static int sun4i_frontend_drm_format_to_output_fmt(uint32_t fmt, u32 *val)
{
switch (fmt) {
case DRM_FORMAT_BGRX8888:
*val = SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT_BGRX8888;
return 0;
case DRM_FORMAT_XRGB8888:
*val = SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT_XRGB8888;
return 0;
default:
return -EINVAL;
}
}
static const uint32_t sun4i_frontend_formats[] = {
DRM_FORMAT_BGRX8888,
DRM_FORMAT_NV12,
DRM_FORMAT_NV16,
DRM_FORMAT_NV21,
DRM_FORMAT_NV61,
DRM_FORMAT_UYVY,
DRM_FORMAT_VYUY,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_YUV411,
DRM_FORMAT_YUV420,
DRM_FORMAT_YUV422,
DRM_FORMAT_YUV444,
DRM_FORMAT_YUYV,
DRM_FORMAT_YVU411,
DRM_FORMAT_YVU420,
DRM_FORMAT_YVU422,
DRM_FORMAT_YVU444,
DRM_FORMAT_YVYU,
};
bool sun4i_frontend_format_is_supported(uint32_t fmt, uint64_t modifier)
{
unsigned int i;
if (modifier == DRM_FORMAT_MOD_ALLWINNER_TILED)
return sun4i_frontend_format_supports_tiling(fmt);
else if (modifier != DRM_FORMAT_MOD_LINEAR)
return false;
for (i = 0; i < ARRAY_SIZE(sun4i_frontend_formats); i++)
if (sun4i_frontend_formats[i] == fmt)
return true;
return false;
}
EXPORT_SYMBOL(sun4i_frontend_format_is_supported);
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;
const struct drm_format_info *format = fb->format;
uint64_t modifier = fb->modifier;
unsigned int ch1_phase_idx;
u32 out_fmt_val;
u32 in_fmt_val, in_mod_val, in_ps_val;
unsigned int i;
u32 bypass;
int ret;
ret = sun4i_frontend_drm_format_to_input_fmt(format, &in_fmt_val);
if (ret) {
DRM_DEBUG_DRIVER("Invalid input format\n");
return ret;
}
ret = sun4i_frontend_drm_format_to_input_mode(format, modifier,
&in_mod_val);
if (ret) {
DRM_DEBUG_DRIVER("Invalid input mode\n");
return ret;
}
ret = sun4i_frontend_drm_format_to_input_sequence(format, &in_ps_val);
if (ret) {
DRM_DEBUG_DRIVER("Invalid pixel sequence\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.
*/
ch1_phase_idx = (format->num_planes > 1) ? 1 : 0;
regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZPHASE_REG,
frontend->data->ch_phase[0]);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZPHASE_REG,
frontend->data->ch_phase[ch1_phase_idx]);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE0_REG,
frontend->data->ch_phase[0]);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE0_REG,
frontend->data->ch_phase[ch1_phase_idx]);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE1_REG,
frontend->data->ch_phase[0]);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE1_REG,
frontend->data->ch_phase[ch1_phase_idx]);
/*
* Checking the input format is sufficient since we currently only
* support RGB output formats to the backend. If YUV output formats
* ever get supported, an YUV input and output would require bypassing
* the CSC engine too.
*/
if (format->is_yuv) {
/* Setup the CSC engine for YUV to RGB conversion. */
bypass = 0;
for (i = 0; i < ARRAY_SIZE(sunxi_bt601_yuv2rgb_coef); i++)
regmap_write(frontend->regs,
SUN4I_FRONTEND_CSC_COEF_REG(i),
sunxi_bt601_yuv2rgb_coef[i]);
} else {
bypass = SUN4I_FRONTEND_BYPASS_CSC_EN;
}
regmap_update_bits(frontend->regs, SUN4I_FRONTEND_BYPASS_REG,
SUN4I_FRONTEND_BYPASS_CSC_EN, bypass);
regmap_write(frontend->regs, SUN4I_FRONTEND_INPUT_FMT_REG,
in_mod_val | in_fmt_val | in_ps_val);
/*
* 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,
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;
struct drm_framebuffer *fb = state->fb;
uint32_t luma_width, luma_height;
uint32_t chroma_width, chroma_height;
/* Set height and width */
DRM_DEBUG_DRIVER("Frontend size W: %u H: %u\n",
state->crtc_w, state->crtc_h);
luma_width = state->src_w >> 16;
luma_height = state->src_h >> 16;
chroma_width = DIV_ROUND_UP(luma_width, fb->format->hsub);
chroma_height = DIV_ROUND_UP(luma_height, fb->format->vsub);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_INSIZE_REG,
SUN4I_FRONTEND_INSIZE(luma_height, luma_width));
regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_INSIZE_REG,
SUN4I_FRONTEND_INSIZE(chroma_height, chroma_width));
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,
(luma_width << 16) / state->crtc_w);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZFACT_REG,
(chroma_width << 16) / state->crtc_w);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTFACT_REG,
(luma_height << 16) / state->crtc_h);
regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTFACT_REG,
(chroma_height << 16) / 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 const 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;
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;
frontend->data = of_device_get_match_data(dev);
if (!frontend->data)
return -ENODEV;
regs = devm_platform_ioremap_resource(pdev, 0);
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 void sun4i_frontend_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun4i_frontend_ops);
}
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);
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,
};
static const struct sun4i_frontend_data sun4i_a10_frontend = {
.ch_phase = { 0x000, 0xfc000 },
.has_coef_rdy = true,
};
static const struct sun4i_frontend_data sun8i_a33_frontend = {
.ch_phase = { 0x400, 0xfc400 },
.has_coef_access_ctrl = true,
};
const struct of_device_id sun4i_frontend_of_table[] = {
{
.compatible = "allwinner,sun4i-a10-display-frontend",
.data = &sun4i_a10_frontend
},
{
.compatible = "allwinner,sun7i-a20-display-frontend",
.data = &sun4i_a10_frontend
},
{
.compatible = "allwinner,sun8i-a23-display-frontend",
.data = &sun8i_a33_frontend
},
{
.compatible = "allwinner,sun8i-a33-display-frontend",
.data = &sun8i_a33_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");
@@ -0,0 +1,151 @@
// 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>
#include <linux/mod_devicetable.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_BUF_ADDR1_REG 0x024
#define SUN4I_FRONTEND_BUF_ADDR2_REG 0x028
#define SUN4I_FRONTEND_TB_OFF0_REG 0x030
#define SUN4I_FRONTEND_TB_OFF1_REG 0x034
#define SUN4I_FRONTEND_TB_OFF2_REG 0x038
#define SUN4I_FRONTEND_TB_OFF_X1(x1) ((x1) << 16)
#define SUN4I_FRONTEND_TB_OFF_Y0(y0) ((y0) << 8)
#define SUN4I_FRONTEND_TB_OFF_X0(x0) (x0)
#define SUN4I_FRONTEND_LINESTRD0_REG 0x040
#define SUN4I_FRONTEND_LINESTRD1_REG 0x044
#define SUN4I_FRONTEND_LINESTRD2_REG 0x048
/*
* In tiled mode, the stride is defined as the distance between the start of the
* end line of the current tile and the start of the first line in the next
* vertical tile.
*
* Tiles are represented in row-major order, thus the end line of current tile
* starts at: 31 * 32 (31 lines of 32 cols), the next vertical tile starts at:
* 32-bit-aligned-width * 32 and the distance is:
* 32 * (32-bit-aligned-width - 31).
*/
#define SUN4I_FRONTEND_LINESTRD_TILED(stride) (((stride) - 31) * 32)
#define SUN4I_FRONTEND_INPUT_FMT_REG 0x04c
#define SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_PLANAR (0 << 8)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_PACKED (1 << 8)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_SEMIPLANAR (2 << 8)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_MB32_PLANAR (4 << 8)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_MOD_MB32_SEMIPLANAR (6 << 8)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV444 (0 << 4)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV422 (1 << 4)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV420 (2 << 4)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_YUV411 (3 << 4)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_FMT_RGB (5 << 4)
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UYVY 0
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_YUYV 1
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VYUY 2
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_YVYU 3
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_UV 0
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_VU 1
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_BGRX 0
#define SUN4I_FRONTEND_INPUT_FMT_DATA_PS_XRGB 1
#define SUN4I_FRONTEND_OUTPUT_FMT_REG 0x05c
#define SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT_BGRX8888 1
#define SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT_XRGB8888 2
#define SUN4I_FRONTEND_CSC_COEF_REG(c) (0x070 + (0x4 * (c)))
#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_data {
bool has_coef_access_ctrl;
bool has_coef_rdy;
u32 ch_phase[2];
};
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;
const struct sun4i_frontend_data *data;
};
extern const struct of_device_id sun4i_frontend_of_table[];
extern const u32 sunxi_bt601_yuv2rgb_coef[12];
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);
bool sun4i_frontend_format_is_supported(uint32_t fmt, uint64_t modifier);
#endif /* _SUN4I_FRONTEND_H_ */
@@ -0,0 +1,297 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2016 Maxime Ripard
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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;
struct i2c_adapter *ddc_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;
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_ */
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Free Electrons
* Copyright (C) 2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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 int sun4i_ddc_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct sun4i_ddc *ddc = hw_to_ddc(hw);
req->rate = sun4i_ddc_calc_divider(req->rate, req->best_parent_rate,
ddc->pre_div, ddc->m_offset, NULL, NULL);
return 0;
}
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, &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,
.determine_rate = sun4i_ddc_determine_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;
}
@@ -0,0 +1,748 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Maxime Ripard
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/i2c.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include "sun4i_backend.h"
#include "sun4i_crtc.h"
#include "sun4i_drv.h"
#include "sun4i_hdmi.h"
#define drm_encoder_to_sun4i_hdmi(e) \
container_of_const(e, struct sun4i_hdmi, encoder)
#define drm_connector_to_sun4i_hdmi(c) \
container_of_const(c, struct sun4i_hdmi, connector)
static int sun4i_hdmi_clear_avi_infoframe(struct drm_connector *connector)
{
drm_warn_once(connector->dev, "clearing of AVI infoframe is not implemented\n");
return 0;
}
static int sun4i_hdmi_write_avi_infoframe(struct drm_connector *connector,
const u8 *buffer, size_t len)
{
struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
int i;
for (i = 0; i < len; i++)
writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
return 0;
}
static int sun4i_hdmi_clear_hdmi_infoframe(struct drm_connector *connector)
{
drm_warn_once(connector->dev, "HDMI VSI not implemented\n");
return 0;
}
static int sun4i_hdmi_write_hdmi_infoframe(struct drm_connector *connector,
const u8 *buffer, size_t len)
{
drm_warn_once(connector->dev, "HDMI VSI not implemented\n");
return 0;
}
static void sun4i_hdmi_disable(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
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_atomic_commit *state)
{
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
struct drm_connector *connector = &hdmi->connector;
struct drm_display_info *display = &connector->display_info;
struct drm_connector_state *conn_state =
drm_atomic_get_new_connector_state(state, connector);
unsigned long long tmds_rate = conn_state->hdmi.tmds_char_rate;
unsigned int x, y;
u32 val = 0;
DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
clk_set_rate(hdmi->mod_clk, tmds_rate);
clk_set_rate(hdmi->tmds_clk, tmds_rate);
/* 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);
clk_prepare_enable(hdmi->tmds_clk);
drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
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 (display->is_hdmi)
val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
}
static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
.atomic_disable = sun4i_hdmi_disable,
.atomic_enable = sun4i_hdmi_enable,
};
static enum drm_mode_status
sun4i_hdmi_connector_clock_valid(const struct drm_connector *connector,
const struct drm_display_mode *mode,
unsigned long long clock)
{
const struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
unsigned long diff = div_u64(clock, 200); /* +-0.5% allowed by HDMI spec */
long rounded_rate;
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
return MODE_BAD;
/* HDMI 1.0 max TMDS character rate */
if (clock > HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ)
return MODE_CLOCK_HIGH;
rounded_rate = clk_round_rate(hdmi->tmds_clk, clock);
if (rounded_rate > 0 &&
max_t(unsigned long, rounded_rate, clock) -
min_t(unsigned long, rounded_rate, clock) < diff)
return MODE_OK;
return MODE_NOCLOCK;
}
static int sun4i_hdmi_get_modes(struct drm_connector *connector)
{
struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
const struct drm_edid *drm_edid;
int ret;
drm_edid = drm_edid_read_ddc(connector, hdmi->ddc_i2c ?: hdmi->i2c);
drm_edid_connector_update(connector, drm_edid);
cec_s_phys_addr(hdmi->cec_adap,
connector->display_info.source_physical_address, false);
if (!drm_edid)
return 0;
DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
connector->display_info.is_hdmi ? "an HDMI" : "a DVI");
ret = drm_edid_connector_add_modes(connector);
drm_edid_free(drm_edid);
return ret;
}
static struct i2c_adapter *sun4i_hdmi_get_ddc(struct device *dev)
{
struct device_node *phandle, *remote;
struct i2c_adapter *ddc;
remote = of_graph_get_remote_node(dev->of_node, 1, -1);
if (!remote)
return ERR_PTR(-EINVAL);
phandle = of_parse_phandle(remote, "ddc-i2c-bus", 0);
of_node_put(remote);
if (!phandle)
return ERR_PTR(-ENODEV);
ddc = of_get_i2c_adapter_by_node(phandle);
of_node_put(phandle);
if (!ddc)
return ERR_PTR(-EPROBE_DEFER);
return ddc;
}
static const struct drm_connector_hdmi_funcs sun4i_hdmi_hdmi_connector_funcs = {
.tmds_char_rate_valid = sun4i_hdmi_connector_clock_valid,
.avi = {
.clear_infoframe = sun4i_hdmi_clear_avi_infoframe,
.write_infoframe = sun4i_hdmi_write_avi_infoframe,
},
.hdmi = {
.clear_infoframe = sun4i_hdmi_clear_hdmi_infoframe,
.write_infoframe = sun4i_hdmi_write_hdmi_infoframe,
},
};
static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
.atomic_check = drm_atomic_helper_connector_hdmi_check,
.mode_valid = drm_hdmi_connector_mode_valid,
.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 void sun4i_hdmi_connector_reset(struct drm_connector *connector)
{
drm_atomic_helper_connector_reset(connector);
__drm_atomic_helper_connector_hdmi_reset(connector, connector->state);
}
static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
.detect = sun4i_hdmi_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.reset = sun4i_hdmi_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 int 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 cec_connector_info conn_info;
struct sun4i_drv *drv = drm->dev_private;
struct sun4i_hdmi *hdmi;
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;
hdmi->base = devm_platform_ioremap_resource(pdev, 0);
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;
}
hdmi->ddc_i2c = sun4i_hdmi_get_ddc(dev);
if (IS_ERR(hdmi->ddc_i2c)) {
ret = PTR_ERR(hdmi->ddc_i2c);
if (ret == -ENODEV)
hdmi->ddc_i2c = NULL;
else
goto err_del_i2c_adapter;
}
drm_encoder_helper_add(&hdmi->encoder,
&sun4i_hdmi_helper_funcs);
ret = drm_simple_encoder_init(drm, &hdmi->encoder,
DRM_MODE_ENCODER_TMDS);
if (ret) {
dev_err(dev, "Couldn't initialise the HDMI encoder\n");
goto err_put_ddc_i2c;
}
hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
dev->of_node);
if (!hdmi->encoder.possible_crtcs) {
ret = -EPROBE_DEFER;
goto err_put_ddc_i2c;
}
#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops,
hdmi, "sun4i", CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO);
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 = drmm_connector_hdmi_init(drm, &hdmi->connector,
/*
* NOTE: Those are likely to be
* wrong, but I couldn't find the
* actual ones in the BSP.
*/
"AW", "HDMI",
&sun4i_hdmi_connector_funcs,
&sun4i_hdmi_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA,
hdmi->ddc_i2c,
BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444),
8);
if (ret) {
dev_err(dev,
"Couldn't initialise the HDMI connector\n");
goto err_cleanup_connector;
}
cec_fill_conn_info_from_drm(&conn_info, &hdmi->connector);
cec_s_conn_info(hdmi->cec_adap, &conn_info);
/* 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_put_ddc_i2c:
i2c_put_adapter(hdmi->ddc_i2c);
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);
i2c_put_adapter(hdmi->ddc_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 void sun4i_hdmi_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun4i_hdmi_ops);
}
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");
@@ -0,0 +1,316 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com>
* Copyright (C) 2017 Jonathan Liu <net147@gmail.com>
*/
#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)
ioread8_rep(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);
else
iowrite8_rep(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, &reg);
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->algo = &sun4i_hdmi_i2c_algorithm;
strscpy(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;
}
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Free Electrons
* Copyright (C) 2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk-provider.h>
#include <linux/io.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 = false;
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;
}
@@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_gem_atomic_helper.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_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_obj(*state);
if (state)
__drm_atomic_helper_plane_reset(plane, &state->state);
}
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_obj(*copy);
if (!copy)
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &copy->state);
copy->uses_frontend = orig->uses_frontend;
return &copy->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_atomic_commit *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state,
plane);
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_atomic_commit *state)
{
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
struct sun4i_layer_state *layer_state = state_to_sun4i_layer_state(new_state);
struct sun4i_layer *layer = plane_to_sun4i_layer(plane);
struct sun4i_backend *backend = layer->backend;
struct sun4i_frontend *frontend = backend->frontend;
sun4i_backend_cleanup_layer(backend, layer->id);
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_XRGB8888);
sun4i_backend_update_layer_frontend(backend, layer->id,
DRM_FORMAT_XRGB8888);
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 bool sun4i_layer_format_mod_supported(struct drm_plane *plane,
uint32_t format, uint64_t modifier)
{
struct sun4i_layer *layer = plane_to_sun4i_layer(plane);
if (IS_ERR_OR_NULL(layer->backend->frontend))
return sun4i_backend_format_is_supported(format, modifier);
return sun4i_backend_format_is_supported(format, modifier) ||
sun4i_frontend_format_is_supported(format, modifier);
}
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,
.format_mod_supported = sun4i_layer_format_mod_supported,
};
static const uint32_t sun4i_layer_formats[] = {
DRM_FORMAT_ARGB8888,
DRM_FORMAT_ARGB4444,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_RGBA5551,
DRM_FORMAT_RGBA4444,
DRM_FORMAT_RGB888,
DRM_FORMAT_RGB565,
DRM_FORMAT_NV12,
DRM_FORMAT_NV16,
DRM_FORMAT_NV21,
DRM_FORMAT_NV61,
DRM_FORMAT_UYVY,
DRM_FORMAT_VYUY,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_YUV411,
DRM_FORMAT_YUV420,
DRM_FORMAT_YUV422,
DRM_FORMAT_YUV444,
DRM_FORMAT_YUYV,
DRM_FORMAT_YVU411,
DRM_FORMAT_YVU420,
DRM_FORMAT_YVU422,
DRM_FORMAT_YVU444,
DRM_FORMAT_YVYU,
};
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 const uint64_t sun4i_layer_modifiers[] = {
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_ALLWINNER_TILED,
DRM_FORMAT_MOD_INVALID
};
static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm,
struct sun4i_backend *backend,
enum drm_plane_type type,
unsigned int id)
{
const uint64_t *modifiers = sun4i_layer_modifiers;
const uint32_t *formats = sun4i_layer_formats;
unsigned int formats_len = ARRAY_SIZE(sun4i_layer_formats);
struct sun4i_layer *layer;
int ret;
layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL);
if (!layer)
return ERR_PTR(-ENOMEM);
layer->id = id;
layer->backend = backend;
if (IS_ERR_OR_NULL(backend->frontend)) {
formats = sun4i_backend_layer_formats;
formats_len = ARRAY_SIZE(sun4i_backend_layer_formats);
modifiers = NULL;
}
/* possible crtcs are set later */
ret = drm_universal_plane_init(drm, &layer->plane, 0,
&sun4i_backend_layer_funcs,
formats, formats_len,
modifiers, 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);
drm_plane_create_alpha_property(&layer->plane);
drm_plane_create_zpos_property(&layer->plane, layer->id,
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, i);
if (IS_ERR(layer)) {
dev_err(drm->dev, "Couldn't initialize %s plane\n",
i ? "overlay" : "primary");
return ERR_CAST(layer);
}
planes[i] = &layer->plane;
}
return planes;
}
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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_ */
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Free Electrons
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.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 drm_panel *panel;
};
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);
return drm_panel_get_modes(lvds->panel, connector);
}
static const 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)
{
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);
DRM_DEBUG_DRIVER("Enabling LVDS output\n");
if (lvds->panel) {
drm_panel_prepare(lvds->panel);
drm_panel_enable(lvds->panel);
}
}
static void sun4i_lvds_encoder_disable(struct drm_encoder *encoder)
{
struct sun4i_lvds *lvds = drm_encoder_to_sun4i_lvds(encoder);
DRM_DEBUG_DRIVER("Disabling LVDS output\n");
if (lvds->panel) {
drm_panel_disable(lvds->panel);
drm_panel_unprepare(lvds->panel);
}
}
static const struct drm_encoder_helper_funcs sun4i_lvds_enc_helper_funcs = {
.disable = sun4i_lvds_encoder_disable,
.enable = sun4i_lvds_encoder_enable,
};
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;
encoder = &lvds->encoder;
ret = drm_of_find_panel_or_bridge(tcon->dev->of_node, 1, 0,
&lvds->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_simple_encoder_init(drm, &lvds->encoder,
DRM_MODE_ENCODER_LVDS);
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 (lvds->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);
}
if (bridge) {
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
if (ret)
goto err_cleanup_connector;
}
return 0;
err_cleanup_connector:
drm_encoder_cleanup(&lvds->encoder);
err_out:
return ret;
}
EXPORT_SYMBOL(sun4i_lvds_init);
@@ -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_ */
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.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;
struct drm_panel *panel;
struct drm_bridge *bridge;
};
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);
return drm_panel_get_modes(rgb->panel, connector);
}
/*
* VESA DMT defines a tolerance of 0.5% on the pixel clock, while the
* CVT spec reuses that tolerance in its examples, so it looks to be a
* good default tolerance for the EDID-based modes. Define it to 5 per
* mille to avoid floating point operations.
*/
#define SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE 5
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 long rate = mode->clock * 1000;
unsigned long long lowest, highest;
unsigned long 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");
/*
* TODO: We should use the struct display_timing if available
* and / or trying to stretch the timings within that
* tolerancy to take care of panels that we wouldn't be able
* to have a exact match for.
*/
if (rgb->panel) {
DRM_DEBUG_DRIVER("RGB panel used, skipping clock rate checks");
goto out;
}
/*
* That shouldn't ever happen unless something is really wrong, but it
* doesn't harm to check.
*/
if (!rgb->bridge)
goto out;
tcon->dclk_min_div = 6;
tcon->dclk_max_div = 127;
rounded_rate = clk_round_rate(tcon->dclk, rate);
lowest = rate * (1000 - SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE);
do_div(lowest, 1000);
if (rounded_rate < lowest)
return MODE_CLOCK_LOW;
highest = rate * (1000 + SUN4I_RGB_DOTCLOCK_TOLERANCE_PER_MILLE);
do_div(highest, 1000);
if (rounded_rate > highest)
return MODE_CLOCK_HIGH;
out:
DRM_DEBUG_DRIVER("Clock rate OK\n");
return MODE_OK;
}
static const 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)
{
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);
DRM_DEBUG_DRIVER("Enabling RGB output\n");
if (rgb->panel) {
drm_panel_prepare(rgb->panel);
drm_panel_enable(rgb->panel);
}
}
static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
{
struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
DRM_DEBUG_DRIVER("Disabling RGB output\n");
if (rgb->panel) {
drm_panel_disable(rgb->panel);
drm_panel_unprepare(rgb->panel);
}
}
static const 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,
};
int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon)
{
struct drm_encoder *encoder;
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,
&rgb->panel, &rgb->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_simple_encoder_init(drm, &rgb->encoder,
DRM_MODE_ENCODER_NONE);
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 (rgb->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);
}
if (rgb->bridge) {
ret = drm_bridge_attach(encoder, rgb->bridge, NULL, 0);
if (ret)
goto err_cleanup_connector;
}
return 0;
err_cleanup_connector:
drm_encoder_cleanup(&rgb->encoder);
err_out:
return ret;
}
EXPORT_SYMBOL(sun4i_rgb_init);
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#ifndef _SUN4I_RGB_H_
#define _SUN4I_RGB_H_
int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon);
#endif /* _SUN4I_RGB_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,303 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Boris Brezillon <boris.brezillon@free-electrons.com>
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#ifndef __SUN4I_TCON_H__
#define __SUN4I_TCON_H__
#include <drm/drm_crtc.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mod_devicetable.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_TCON0_FRM_CTL_EN BIT(31)
#define SUN4I_TCON0_FRM_CTL_MODE_R BIT(6)
#define SUN4I_TCON0_FRM_CTL_MODE_G BIT(5)
#define SUN4I_TCON0_FRM_CTL_MODE_B BIT(4)
#define SUN4I_TCON0_FRM_SEED_PR_REG 0x14
#define SUN4I_TCON0_FRM_SEED_PG_REG 0x18
#define SUN4I_TCON0_FRM_SEED_PB_REG 0x1c
#define SUN4I_TCON0_FRM_SEED_LR_REG 0x20
#define SUN4I_TCON0_FRM_SEED_LG_REG 0x24
#define SUN4I_TCON0_FRM_SEED_LB_REG 0x28
#define SUN4I_TCON0_FRM_TBL0_REG 0x2c
#define SUN4I_TCON0_FRM_TBL1_REG 0x30
#define SUN4I_TCON0_FRM_TBL2_REG 0x34
#define SUN4I_TCON0_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_DE_NEGATIVE BIT(27)
#define SUN4I_TCON0_IO_POL_DCLK_DRIVE_NEGEDGE BIT(26)
#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
/* there is no documentation about this bit */
#define SUN4I_TCON1_IO_POL_UNKNOWN BIT(26)
#define SUN4I_TCON1_IO_POL_HSYNC_POSITIVE BIT(25)
#define SUN4I_TCON1_IO_POL_VSYNC_POSITIVE BIT(24)
#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 SUN4I_TCON0_LVDS_ANA0_DCHS BIT(16)
#define SUN4I_TCON0_LVDS_ANA0_PD (BIT(20) | BIT(21))
#define SUN4I_TCON0_LVDS_ANA0_EN_MB BIT(22)
#define SUN4I_TCON0_LVDS_ANA0_REG_C (BIT(24) | BIT(25))
#define SUN4I_TCON0_LVDS_ANA0_REG_V (BIT(26) | BIT(27))
#define SUN4I_TCON0_LVDS_ANA0_CK_EN (BIT(29) | BIT(28))
#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_TCON0_LVDS_ANA1_REG 0x224
#define SUN4I_TCON0_LVDS_ANA1_INIT (0x1f << 26 | 0x1f << 10)
#define SUN4I_TCON0_LVDS_ANA1_UPDATE (0x1f << 16 | 0x1f << 00)
#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? */
bool polarity_in_ch0; /* some tcon1 channels have polarity bits in tcon0 pol register */
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 *);
/* handler for LVDS setup routine */
void (*setup_lvds_phy)(struct sun4i_tcon *tcon,
const struct drm_encoder *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;
/* 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__ */
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Free Electrons
* Copyright (C) 2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk-provider.h>
#include <linux/regmap.h>
#include "sun4i_tcon.h"
#include "sun4i_tcon_dclk.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 int sun4i_dclk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
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)req->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(req->rate - rounded / i) <
abs(req->rate - best_parent / best_div)) {
best_parent = rounded;
best_div = i;
}
}
out:
req->best_parent_rate = best_parent;
req->rate = best_parent / best_div;
return 0;
}
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,
.determine_rate = sun4i_dclk_determine_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);
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#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_ */
@@ -0,0 +1,572 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.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;
unsigned int tv_mode;
u32 mode;
u32 chroma_freq;
u16 back_porch;
u16 front_porch;
u16 vblank_level;
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[] = {
{
.tv_mode = DRM_MODE_TV_MODE_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,
.vblank_level = 240,
.color_gains = &ntsc_color_gains,
.burst_levels = &ntsc_burst_levels,
.video_levels = &ntsc_video_levels,
.resync_params = &ntsc_resync_parameters,
},
{
.tv_mode = DRM_MODE_TV_MODE_PAL,
.mode = SUN4I_TVE_CFG0_RES_576i,
.chroma_freq = 0x2a098acb,
.back_porch = 138,
.front_porch = 24,
.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 const struct tv_mode *
sun4i_tv_find_tv_by_mode(unsigned int mode)
{
int i;
for (i = 0; i < ARRAY_SIZE(tv_modes); i++) {
const struct tv_mode *tv_mode = &tv_modes[i];
if (tv_mode->tv_mode == mode)
return tv_mode;
}
return NULL;
}
static void sun4i_tv_disable(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
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 drm_atomic_commit *state)
{
struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
struct drm_crtc_state *crtc_state =
drm_atomic_get_new_crtc_state(state, encoder->crtc);
struct drm_display_mode *mode = &crtc_state->mode;
struct drm_connector *connector = &tv->connector;
struct drm_connector_state *conn_state =
drm_atomic_get_new_connector_state(state, connector);
const struct tv_mode *tv_mode =
sun4i_tv_find_tv_by_mode(conn_state->tv.mode);
DRM_DEBUG_DRIVER("Enabling the TV Output\n");
/* 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(mode->vtotal));
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);
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 const struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = {
.atomic_disable = sun4i_tv_disable,
.atomic_enable = sun4i_tv_enable,
};
static const struct drm_connector_helper_funcs sun4i_tv_comp_connector_helper_funcs = {
.atomic_check = drm_atomic_helper_connector_tv_check,
.get_modes = drm_connector_helper_tv_get_modes,
};
static void sun4i_tv_connector_reset(struct drm_connector *connector)
{
drm_atomic_helper_connector_reset(connector);
drm_atomic_helper_connector_tv_reset(connector);
}
static const struct drm_connector_funcs sun4i_tv_comp_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = sun4i_tv_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const 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;
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);
regs = devm_platform_ioremap_resource(pdev, 0);
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_simple_encoder_init(drm, &tv->encoder,
DRM_MODE_ENCODER_TVDAC);
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_encoder;
}
tv->connector.interlace_allowed = true;
drm_connector_attach_encoder(&tv->connector, &tv->encoder);
ret = drm_mode_create_tv_properties(drm,
BIT(DRM_MODE_TV_MODE_NTSC) |
BIT(DRM_MODE_TV_MODE_PAL));
if (ret)
goto err_cleanup_connector;
drm_object_attach_property(&tv->connector.base,
drm->mode_config.tv_mode_property,
DRM_MODE_TV_MODE_NTSC);
return 0;
err_cleanup_connector:
drm_connector_cleanup(&tv->connector);
err_cleanup_encoder:
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);
reset_control_assert(tv->reset);
}
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 void sun4i_tv_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun4i_tv_ops);
}
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");
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Free Electrons
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/module.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;
}
ret = clk_set_rate_exclusive(drc->mod_clk, 300000000);
if (ret) {
dev_err(dev, "Couldn't set the module clock frequency\n");
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_rate_exclusive_put(drc->mod_clk);
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 void sun6i_drc_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun6i_drc_ops);
}
static const struct of_device_id sun6i_drc_of_table[] = {
{ .compatible = "allwinner,sun6i-a31-drc" },
{ .compatible = "allwinner,sun6i-a31s-drc" },
{ .compatible = "allwinner,sun8i-a23-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");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
// 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>
#define SUN6I_DSI_TCON_DIV 4
struct sun6i_dsi_variant {
bool has_mod_clk;
bool set_mod_clk;
};
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 regulator *regulator;
struct reset_control *reset;
struct phy *dphy;
struct device *dev;
struct mipi_dsi_device *device;
struct drm_device *drm;
struct drm_panel *panel;
const struct sun6i_dsi_variant *variant;
};
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);
};
#endif /* _SUN6I_MIPI_DSI_H_ */
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net>
*/
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
#include "sun8i_csc.h"
#include "sun8i_mixer.h"
enum sun8i_csc_mode {
SUN8I_CSC_MODE_OFF,
SUN8I_CSC_MODE_YUV2RGB,
SUN8I_CSC_MODE_YVU2RGB,
};
static const u32 ccsc_base[][2] = {
[CCSC_MIXER0_LAYOUT] = {CCSC00_OFFSET, CCSC01_OFFSET},
[CCSC_MIXER1_LAYOUT] = {CCSC10_OFFSET, CCSC11_OFFSET},
[CCSC_D1_MIXER0_LAYOUT] = {CCSC00_OFFSET, CCSC01_D1_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[2][2][12] = {
[DRM_COLOR_YCBCR_LIMITED_RANGE] = {
[DRM_COLOR_YCBCR_BT601] = {
0x000004A8, 0x00000000, 0x00000662, 0xFFFC8451,
0x000004A8, 0xFFFFFE6F, 0xFFFFFCC0, 0x00021E4D,
0x000004A8, 0x00000811, 0x00000000, 0xFFFBACA9,
},
[DRM_COLOR_YCBCR_BT709] = {
0x000004A8, 0x00000000, 0x0000072B, 0xFFFC1F99,
0x000004A8, 0xFFFFFF26, 0xFFFFFDDF, 0x00013383,
0x000004A8, 0x00000873, 0x00000000, 0xFFFB7BEF,
}
},
[DRM_COLOR_YCBCR_FULL_RANGE] = {
[DRM_COLOR_YCBCR_BT601] = {
0x00000400, 0x00000000, 0x0000059B, 0xFFFD322E,
0x00000400, 0xFFFFFEA0, 0xFFFFFD25, 0x00021DD5,
0x00000400, 0x00000716, 0x00000000, 0xFFFC74BD,
},
[DRM_COLOR_YCBCR_BT709] = {
0x00000400, 0x00000000, 0x0000064C, 0xFFFCD9B4,
0x00000400, 0xFFFFFF41, 0xFFFFFE21, 0x00014F96,
0x00000400, 0x0000076C, 0x00000000, 0xFFFC49EF,
}
},
};
/*
* DE3 has a bit different CSC units. Factors are in two's complement format.
* First three factors in a row are multiplication factors which have 17 bits
* for fractional part. Fourth value in a row is comprised of two factors.
* Upper 16 bits represents difference, which is subtracted from the input
* value before multiplication and lower 16 bits represents constant, which
* is addes at the end.
*
* x' = c00 * (x + d0) + c01 * (y + d1) + c02 * (z + d2) + const0
* y' = c10 * (x + d0) + c11 * (y + d1) + c12 * (z + d2) + const1
* z' = c20 * (x + d0) + c21 * (y + d1) + c22 * (z + d2) + const2
*
* Please note that above formula is true only for Blender CSC. Other DE3 CSC
* units takes only positive value for difference. From what can be deducted
* from BSP driver code, those units probably automatically assume that
* difference has to be subtracted.
*
* Layout of factors in table:
* c00 c01 c02 [d0 const0]
* c10 c11 c12 [d1 const1]
* c20 c21 c22 [d2 const2]
*/
static const u32 yuv2rgb_de3[2][3][12] = {
[DRM_COLOR_YCBCR_LIMITED_RANGE] = {
[DRM_COLOR_YCBCR_BT601] = {
0x0002542A, 0x00000000, 0x0003312A, 0xFFC00000,
0x0002542A, 0xFFFF376B, 0xFFFE5FC3, 0xFE000000,
0x0002542A, 0x000408D2, 0x00000000, 0xFE000000,
},
[DRM_COLOR_YCBCR_BT709] = {
0x0002542A, 0x00000000, 0x000395E2, 0xFFC00000,
0x0002542A, 0xFFFF92D2, 0xFFFEEF27, 0xFE000000,
0x0002542A, 0x0004398C, 0x00000000, 0xFE000000,
},
[DRM_COLOR_YCBCR_BT2020] = {
0x0002542A, 0x00000000, 0x00035B7B, 0xFFC00000,
0x0002542A, 0xFFFFA017, 0xFFFEB2FC, 0xFE000000,
0x0002542A, 0x00044896, 0x00000000, 0xFE000000,
}
},
[DRM_COLOR_YCBCR_FULL_RANGE] = {
[DRM_COLOR_YCBCR_BT601] = {
0x00020000, 0x00000000, 0x0002CDD2, 0x00000000,
0x00020000, 0xFFFF4FCE, 0xFFFE925D, 0xFE000000,
0x00020000, 0x00038B43, 0x00000000, 0xFE000000,
},
[DRM_COLOR_YCBCR_BT709] = {
0x00020000, 0x00000000, 0x0003264C, 0x00000000,
0x00020000, 0xFFFFA018, 0xFFFF1053, 0xFE000000,
0x00020000, 0x0003B611, 0x00000000, 0xFE000000,
},
[DRM_COLOR_YCBCR_BT2020] = {
0x00020000, 0x00000000, 0x0002F2FE, 0x00000000,
0x00020000, 0xFFFFABC0, 0xFFFEDB78, 0xFE000000,
0x00020000, 0x0003C346, 0x00000000, 0xFE000000,
}
},
};
static void sun8i_csc_setup(struct regmap *map, u32 base,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range)
{
u32 base_reg, val;
const u32 *table;
int i;
table = yuv2rgb[range][encoding];
switch (mode) {
case SUN8I_CSC_MODE_OFF:
val = 0;
break;
case SUN8I_CSC_MODE_YUV2RGB:
val = SUN8I_CSC_CTRL_EN;
base_reg = SUN8I_CSC_COEFF(base, 0);
regmap_bulk_write(map, base_reg, table, 12);
break;
case SUN8I_CSC_MODE_YVU2RGB:
val = SUN8I_CSC_CTRL_EN;
for (i = 0; i < 12; i++) {
if ((i & 3) == 1)
base_reg = SUN8I_CSC_COEFF(base, i + 1);
else if ((i & 3) == 2)
base_reg = SUN8I_CSC_COEFF(base, i - 1);
else
base_reg = SUN8I_CSC_COEFF(base, i);
regmap_write(map, base_reg, table[i]);
}
break;
default:
val = 0;
DRM_WARN("Wrong CSC mode specified.\n");
return;
}
regmap_write(map, SUN8I_CSC_CTRL(base), val);
}
static void sun8i_de3_ccsc_setup(struct regmap *map, int layer,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range)
{
u32 addr, val, mask;
const u32 *table;
int i;
mask = SUN50I_MIXER_BLEND_CSC_CTL_EN(layer);
table = yuv2rgb_de3[range][encoding];
switch (mode) {
case SUN8I_CSC_MODE_OFF:
val = 0;
break;
case SUN8I_CSC_MODE_YUV2RGB:
val = mask;
addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE, layer, 0);
regmap_bulk_write(map, addr, table, 12);
break;
case SUN8I_CSC_MODE_YVU2RGB:
val = mask;
for (i = 0; i < 12; i++) {
if ((i & 3) == 1)
addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE,
layer,
i + 1);
else if ((i & 3) == 2)
addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE,
layer,
i - 1);
else
addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE,
layer, i);
regmap_write(map, addr, table[i]);
}
break;
default:
val = 0;
DRM_WARN("Wrong CSC mode specified.\n");
return;
}
regmap_update_bits(map, SUN50I_MIXER_BLEND_CSC_CTL(DE3_BLD_BASE),
mask, val);
}
static u32 sun8i_csc_get_mode(struct drm_plane_state *state)
{
const struct drm_format_info *format;
if (!state->crtc || !state->visible)
return SUN8I_CSC_MODE_OFF;
format = state->fb->format;
if (!format->is_yuv)
return SUN8I_CSC_MODE_OFF;
switch (format->format) {
case DRM_FORMAT_YVU411:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_YVU422:
case DRM_FORMAT_YVU444:
return SUN8I_CSC_MODE_YVU2RGB;
default:
return SUN8I_CSC_MODE_YUV2RGB;
}
}
void sun8i_csc_config(struct sun8i_layer *layer,
struct drm_plane_state *state)
{
u32 mode = sun8i_csc_get_mode(state);
u32 base;
if (layer->cfg->de_type == SUN8I_MIXER_DE3) {
sun8i_de3_ccsc_setup(layer->regs, layer->channel,
mode, state->color_encoding,
state->color_range);
return;
}
base = ccsc_base[layer->cfg->ccsc][layer->channel];
sun8i_csc_setup(layer->regs, base,
mode, state->color_encoding,
state->color_range);
}
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net>
*/
#ifndef _SUN8I_CSC_H_
#define _SUN8I_CSC_H_
#include <drm/drm_color_mgmt.h>
struct drm_plane_state;
struct sun8i_layer;
/* VI channel CSC units offsets */
#define CCSC00_OFFSET 0xAA050
#define CCSC01_OFFSET 0xFA050
#define CCSC01_D1_OFFSET 0xFA000
#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)
void sun8i_csc_config(struct sun8i_layer *layer,
struct drm_plane_state *state);
#endif
@@ -0,0 +1,302 @@
// 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/of.h>
#include <linux/platform_device.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_of.h>
#include <drm/drm_simple_kms_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 enum drm_mode_status
sun8i_dw_hdmi_mode_valid_a83t(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
if (mode->clock > 297000)
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static enum drm_mode_status
sun8i_dw_hdmi_mode_valid_h6(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
/*
* Controller support maximum of 594 MHz, which correlates to
* 4K@60Hz 4:4:4 or RGB.
*/
if (mode->clock > 594000)
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;
hdmi->quirks = of_device_get_match_data(dev);
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))
return dev_err_probe(dev, PTR_ERR(hdmi->rst_ctrl),
"Could not get ctrl reset control\n");
hdmi->clk_tmds = devm_clk_get(dev, "tmds");
if (IS_ERR(hdmi->clk_tmds))
return dev_err_probe(dev, PTR_ERR(hdmi->clk_tmds),
"Couldn't get the tmds clock\n");
hdmi->regulator = devm_regulator_get(dev, "hvcc");
if (IS_ERR(hdmi->regulator))
return dev_err_probe(dev, PTR_ERR(hdmi->regulator),
"Couldn't get regulator\n");
ret = regulator_enable(hdmi->regulator);
if (ret) {
dev_err(dev, "Failed to enable regulator\n");
return ret;
}
ret = reset_control_deassert(hdmi->rst_ctrl);
if (ret) {
dev_err(dev, "Could not deassert ctrl reset control\n");
goto err_disable_regulator;
}
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_get(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;
}
ret = sun8i_hdmi_phy_init(hdmi->phy);
if (ret)
goto err_disable_clk_tmds;
drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
plat_data->mode_valid = hdmi->quirks->mode_valid;
plat_data->use_drm_infoframe = hdmi->quirks->use_drm_infoframe;
sun8i_hdmi_phy_set_ops(hdmi->phy, plat_data);
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);
err_disable_clk_tmds:
clk_disable_unprepare(hdmi->clk_tmds);
err_assert_ctrl_reset:
reset_control_assert(hdmi->rst_ctrl);
err_disable_regulator:
regulator_disable(hdmi->regulator);
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_deinit(hdmi->phy);
clk_disable_unprepare(hdmi->clk_tmds);
reset_control_assert(hdmi->rst_ctrl);
regulator_disable(hdmi->regulator);
}
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 void sun8i_dw_hdmi_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
}
static const struct sun8i_dw_hdmi_quirks sun8i_a83t_quirks = {
.mode_valid = sun8i_dw_hdmi_mode_valid_a83t,
};
static const struct sun8i_dw_hdmi_quirks sun50i_h6_quirks = {
.mode_valid = sun8i_dw_hdmi_mode_valid_h6,
.use_drm_infoframe = true,
};
static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
{
.compatible = "allwinner,sun8i-a83t-dw-hdmi",
.data = &sun8i_a83t_quirks,
},
{
.compatible = "allwinner,sun50i-h6-dw-hdmi",
.data = &sun50i_h6_quirks,
},
{ /* 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,
},
};
static int __init sun8i_dw_hdmi_init(void)
{
int ret;
ret = platform_driver_register(&sun8i_dw_hdmi_pltfm_driver);
if (ret)
return ret;
ret = platform_driver_register(&sun8i_hdmi_phy_driver);
if (ret) {
platform_driver_unregister(&sun8i_dw_hdmi_pltfm_driver);
return ret;
}
return ret;
}
static void __exit sun8i_dw_hdmi_exit(void)
{
platform_driver_unregister(&sun8i_dw_hdmi_pltfm_driver);
platform_driver_unregister(&sun8i_hdmi_phy_driver);
}
module_init(sun8i_dw_hdmi_init);
module_exit(sun8i_dw_hdmi_exit);
MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
MODULE_DESCRIPTION("Allwinner DW HDMI bridge");
MODULE_LICENSE("GPL");
@@ -0,0 +1,210 @@
/* 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/regulator/consumer.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;
const struct dw_hdmi_curr_ctrl *cur_ctr;
const struct dw_hdmi_mpll_config *mpll_cfg;
const struct dw_hdmi_phy_config *phy_cfg;
const struct dw_hdmi_phy_ops *phy_ops;
void (*phy_init)(struct sun8i_hdmi_phy *phy);
};
struct sun8i_hdmi_phy {
struct clk *clk_bus;
struct clk *clk_mod;
struct clk *clk_phy;
struct clk *clk_pll0;
struct clk *clk_pll1;
struct device *dev;
unsigned int rcal;
struct regmap *regs;
struct reset_control *rst_phy;
const struct sun8i_hdmi_phy_variant *variant;
};
struct sun8i_dw_hdmi_quirks {
enum drm_mode_status (*mode_valid)(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *info,
const struct drm_display_mode *mode);
unsigned int use_drm_infoframe : 1;
};
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 regulator *regulator;
const struct sun8i_dw_hdmi_quirks *quirks;
struct reset_control *rst_ctrl;
};
extern struct platform_driver sun8i_hdmi_phy_driver;
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_get(struct sun8i_dw_hdmi *hdmi, struct device_node *node);
int sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
void sun8i_hdmi_phy_deinit(struct sun8i_hdmi_phy *phy);
void sun8i_hdmi_phy_set_ops(struct sun8i_hdmi_phy *phy,
struct dw_hdmi_plat_data *plat_data);
int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
bool second_parent);
#endif /* _SUN8I_DW_HDMI_H_ */
@@ -0,0 +1,738 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
*/
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.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 const struct dw_hdmi_mpll_config sun50i_h6_mpll_cfg[] = {
{
30666000, {
{ 0x00b3, 0x0000 },
{ 0x2153, 0x0000 },
{ 0x40f3, 0x0000 },
},
}, {
36800000, {
{ 0x00b3, 0x0000 },
{ 0x2153, 0x0000 },
{ 0x40a2, 0x0001 },
},
}, {
46000000, {
{ 0x00b3, 0x0000 },
{ 0x2142, 0x0001 },
{ 0x40a2, 0x0001 },
},
}, {
61333000, {
{ 0x0072, 0x0001 },
{ 0x2142, 0x0001 },
{ 0x40a2, 0x0001 },
},
}, {
73600000, {
{ 0x0072, 0x0001 },
{ 0x2142, 0x0001 },
{ 0x4061, 0x0002 },
},
}, {
92000000, {
{ 0x0072, 0x0001 },
{ 0x2145, 0x0002 },
{ 0x4061, 0x0002 },
},
}, {
122666000, {
{ 0x0051, 0x0002 },
{ 0x2145, 0x0002 },
{ 0x4061, 0x0002 },
},
}, {
147200000, {
{ 0x0051, 0x0002 },
{ 0x2145, 0x0002 },
{ 0x4064, 0x0003 },
},
}, {
184000000, {
{ 0x0051, 0x0002 },
{ 0x214c, 0x0003 },
{ 0x4064, 0x0003 },
},
}, {
226666000, {
{ 0x0040, 0x0003 },
{ 0x214c, 0x0003 },
{ 0x4064, 0x0003 },
},
}, {
272000000, {
{ 0x0040, 0x0003 },
{ 0x214c, 0x0003 },
{ 0x5a64, 0x0003 },
},
}, {
340000000, {
{ 0x0040, 0x0003 },
{ 0x3b4c, 0x0003 },
{ 0x5a64, 0x0003 },
},
}, {
594000000, {
{ 0x1a40, 0x0003 },
{ 0x3b4c, 0x0003 },
{ 0x5a64, 0x0003 },
},
}, {
~0UL, {
{ 0x0000, 0x0000 },
{ 0x0000, 0x0000 },
{ 0x0000, 0x0000 },
},
}
};
static const struct dw_hdmi_curr_ctrl sun50i_h6_cur_ctr[] = {
/* pixelclk bpp8 bpp10 bpp12 */
{ 27000000, { 0x0012, 0x0000, 0x0000 }, },
{ 74250000, { 0x0013, 0x001a, 0x001b }, },
{ 148500000, { 0x0019, 0x0033, 0x0034 }, },
{ 297000000, { 0x0019, 0x001b, 0x001b }, },
{ 594000000, { 0x0010, 0x001b, 0x001b }, },
{ ~0UL, { 0x0000, 0x0000, 0x0000 }, }
};
static const struct dw_hdmi_phy_config sun50i_h6_phy_config[] = {
/*pixelclk symbol term vlev*/
{ 27000000, 0x8009, 0x0007, 0x02b0 },
{ 74250000, 0x8009, 0x0006, 0x022d },
{ 148500000, 0x8029, 0x0006, 0x0270 },
{ 297000000, 0x8039, 0x0005, 0x01ab },
{ 594000000, 0x8029, 0x0000, 0x008a },
{ ~0UL, 0x0000, 0x0000, 0x0000}
};
static void sun8i_hdmi_phy_set_polarity(struct sun8i_hdmi_phy *phy,
const struct drm_display_mode *mode)
{
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);
};
static int sun8i_a83t_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *display,
const struct drm_display_mode *mode)
{
unsigned int clk_rate = mode->crtc_clock * 1000;
struct sun8i_hdmi_phy *phy = data;
sun8i_hdmi_phy_set_polarity(phy, mode);
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_gen2_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 void sun8i_a83t_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
{
struct sun8i_hdmi_phy *phy = data;
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 const struct dw_hdmi_phy_ops sun8i_a83t_hdmi_phy_ops = {
.init = sun8i_a83t_hdmi_phy_config,
.disable = sun8i_a83t_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 int sun8i_h3_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *display,
const struct drm_display_mode *mode)
{
unsigned int clk_rate = mode->crtc_clock * 1000;
struct sun8i_hdmi_phy *phy = data;
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;
if (phy->variant->has_phy_clk)
clk_set_rate(phy->clk_phy, clk_rate);
sun8i_hdmi_phy_set_polarity(phy, mode);
/* 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 void sun8i_h3_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
{
struct sun8i_hdmi_phy *phy = data;
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 const struct dw_hdmi_phy_ops sun8i_h3_hdmi_phy_ops = {
.init = sun8i_h3_hdmi_phy_config,
.disable = sun8i_h3_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_unlock(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);
}
static void sun50i_hdmi_phy_init_h6(struct sun8i_hdmi_phy *phy)
{
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);
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
0xffff0000, 0x80c00000);
}
static void sun8i_hdmi_phy_init_a83t(struct sun8i_hdmi_phy *phy)
{
sun8i_hdmi_phy_unlock(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;
sun8i_hdmi_phy_unlock(phy);
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;
}
int sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy)
{
int ret;
ret = reset_control_deassert(phy->rst_phy);
if (ret) {
dev_err(phy->dev, "Cannot deassert phy reset control: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(phy->clk_bus);
if (ret) {
dev_err(phy->dev, "Cannot enable bus clock: %d\n", ret);
goto err_assert_rst_phy;
}
ret = clk_prepare_enable(phy->clk_mod);
if (ret) {
dev_err(phy->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, phy->dev,
phy->variant->has_second_pll);
if (ret) {
dev_err(phy->dev, "Couldn't create the PHY clock\n");
goto err_disable_clk_mod;
}
clk_prepare_enable(phy->clk_phy);
}
phy->variant->phy_init(phy);
return 0;
err_disable_clk_mod:
clk_disable_unprepare(phy->clk_mod);
err_disable_clk_bus:
clk_disable_unprepare(phy->clk_bus);
err_assert_rst_phy:
reset_control_assert(phy->rst_phy);
return ret;
}
void sun8i_hdmi_phy_deinit(struct sun8i_hdmi_phy *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);
}
void sun8i_hdmi_phy_set_ops(struct sun8i_hdmi_phy *phy,
struct dw_hdmi_plat_data *plat_data)
{
const struct sun8i_hdmi_phy_variant *variant = phy->variant;
if (variant->phy_ops) {
plat_data->phy_ops = variant->phy_ops;
plat_data->phy_name = "sun8i_dw_hdmi_phy";
plat_data->phy_data = phy;
} else {
plat_data->mpll_cfg = variant->mpll_cfg;
plat_data->cur_ctr = variant->cur_ctr;
plat_data->phy_config = variant->phy_cfg;
}
}
static const struct regmap_config sun8i_hdmi_phy_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = SUN8I_HDMI_PHY_CEC_REG,
.name = "phy"
};
static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
.phy_ops = &sun8i_a83t_hdmi_phy_ops,
.phy_init = &sun8i_hdmi_phy_init_a83t,
};
static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
.has_phy_clk = true,
.phy_ops = &sun8i_h3_hdmi_phy_ops,
.phy_init = &sun8i_hdmi_phy_init_h3,
};
static const struct sun8i_hdmi_phy_variant sun8i_r40_hdmi_phy = {
.has_phy_clk = true,
.has_second_pll = true,
.phy_ops = &sun8i_h3_hdmi_phy_ops,
.phy_init = &sun8i_hdmi_phy_init_h3,
};
static const struct sun8i_hdmi_phy_variant sun50i_a64_hdmi_phy = {
.has_phy_clk = true,
.phy_ops = &sun8i_h3_hdmi_phy_ops,
.phy_init = &sun8i_hdmi_phy_init_h3,
};
static const struct sun8i_hdmi_phy_variant sun50i_h6_hdmi_phy = {
.cur_ctr = sun50i_h6_cur_ctr,
.mpll_cfg = sun50i_h6_mpll_cfg,
.phy_cfg = sun50i_h6_phy_config,
.phy_init = &sun50i_hdmi_phy_init_h6,
};
static const struct of_device_id sun8i_hdmi_phy_of_table[] = {
{
.compatible = "allwinner,sun8i-a83t-hdmi-phy",
.data = &sun8i_a83t_hdmi_phy,
},
{
.compatible = "allwinner,sun8i-h3-hdmi-phy",
.data = &sun8i_h3_hdmi_phy,
},
{
.compatible = "allwinner,sun8i-r40-hdmi-phy",
.data = &sun8i_r40_hdmi_phy,
},
{
.compatible = "allwinner,sun50i-a64-hdmi-phy",
.data = &sun50i_a64_hdmi_phy,
},
{
.compatible = "allwinner,sun50i-h6-hdmi-phy",
.data = &sun50i_h6_hdmi_phy,
},
{ /* sentinel */ }
};
int sun8i_hdmi_phy_get(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
{
struct platform_device *pdev = of_find_device_by_node(node);
struct sun8i_hdmi_phy *phy;
if (!pdev)
return -EPROBE_DEFER;
phy = platform_get_drvdata(pdev);
if (!phy) {
put_device(&pdev->dev);
return -EPROBE_DEFER;
}
hdmi->phy = phy;
put_device(&pdev->dev);
return 0;
}
static int sun8i_hdmi_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sun8i_hdmi_phy *phy;
void __iomem *regs;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->variant = of_device_get_match_data(dev);
phy->dev = dev;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return dev_err_probe(dev, PTR_ERR(regs),
"Couldn't map the HDMI PHY registers\n");
phy->regs = devm_regmap_init_mmio(dev, regs,
&sun8i_hdmi_phy_regmap_config);
if (IS_ERR(phy->regs))
return dev_err_probe(dev, PTR_ERR(phy->regs),
"Couldn't create the HDMI PHY regmap\n");
phy->clk_bus = devm_clk_get(dev, "bus");
if (IS_ERR(phy->clk_bus))
return dev_err_probe(dev, PTR_ERR(phy->clk_bus),
"Could not get bus clock\n");
phy->clk_mod = devm_clk_get(dev, "mod");
if (IS_ERR(phy->clk_mod))
return dev_err_probe(dev, PTR_ERR(phy->clk_mod),
"Could not get mod clock\n");
if (phy->variant->has_phy_clk) {
phy->clk_pll0 = devm_clk_get(dev, "pll-0");
if (IS_ERR(phy->clk_pll0))
return dev_err_probe(dev, PTR_ERR(phy->clk_pll0),
"Could not get pll-0 clock\n");
if (phy->variant->has_second_pll) {
phy->clk_pll1 = devm_clk_get(dev, "pll-1");
if (IS_ERR(phy->clk_pll1))
return dev_err_probe(dev, PTR_ERR(phy->clk_pll1),
"Could not get pll-1 clock\n");
}
}
phy->rst_phy = devm_reset_control_get_shared(dev, "phy");
if (IS_ERR(phy->rst_phy))
return dev_err_probe(dev, PTR_ERR(phy->rst_phy),
"Could not get phy reset control\n");
platform_set_drvdata(pdev, phy);
return 0;
}
struct platform_driver sun8i_hdmi_phy_driver = {
.probe = sun8i_hdmi_phy_probe,
.driver = {
.name = "sun8i-hdmi-phy",
.of_match_table = sun8i_hdmi_phy_of_table,
},
};
@@ -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 = ((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 = (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;
}
@@ -0,0 +1,947 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 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
*/
#include <linux/component.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "sun4i_drv.h"
#include "sun8i_mixer.h"
#include "sun8i_ui_layer.h"
#include "sun8i_vi_layer.h"
#include "sunxi_engine.h"
struct de2_fmt_info {
u32 drm_fmt;
u32 de2_fmt;
};
static const struct de2_fmt_info de2_formats[] = {
{
.drm_fmt = DRM_FORMAT_ARGB8888,
.de2_fmt = SUN8I_MIXER_FBFMT_ARGB8888,
},
{
.drm_fmt = DRM_FORMAT_ABGR8888,
.de2_fmt = SUN8I_MIXER_FBFMT_ABGR8888,
},
{
.drm_fmt = DRM_FORMAT_RGBA8888,
.de2_fmt = SUN8I_MIXER_FBFMT_RGBA8888,
},
{
.drm_fmt = DRM_FORMAT_BGRA8888,
.de2_fmt = SUN8I_MIXER_FBFMT_BGRA8888,
},
{
.drm_fmt = DRM_FORMAT_XRGB8888,
.de2_fmt = SUN8I_MIXER_FBFMT_XRGB8888,
},
{
.drm_fmt = DRM_FORMAT_XBGR8888,
.de2_fmt = SUN8I_MIXER_FBFMT_XBGR8888,
},
{
.drm_fmt = DRM_FORMAT_RGBX8888,
.de2_fmt = SUN8I_MIXER_FBFMT_RGBX8888,
},
{
.drm_fmt = DRM_FORMAT_BGRX8888,
.de2_fmt = SUN8I_MIXER_FBFMT_BGRX8888,
},
{
.drm_fmt = DRM_FORMAT_RGB888,
.de2_fmt = SUN8I_MIXER_FBFMT_RGB888,
},
{
.drm_fmt = DRM_FORMAT_BGR888,
.de2_fmt = SUN8I_MIXER_FBFMT_BGR888,
},
{
.drm_fmt = DRM_FORMAT_RGB565,
.de2_fmt = SUN8I_MIXER_FBFMT_RGB565,
},
{
.drm_fmt = DRM_FORMAT_BGR565,
.de2_fmt = SUN8I_MIXER_FBFMT_BGR565,
},
{
.drm_fmt = DRM_FORMAT_ARGB4444,
.de2_fmt = SUN8I_MIXER_FBFMT_ARGB4444,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_XRGB4444,
.de2_fmt = SUN8I_MIXER_FBFMT_ARGB4444,
},
{
.drm_fmt = DRM_FORMAT_ABGR4444,
.de2_fmt = SUN8I_MIXER_FBFMT_ABGR4444,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_XBGR4444,
.de2_fmt = SUN8I_MIXER_FBFMT_ABGR4444,
},
{
.drm_fmt = DRM_FORMAT_RGBA4444,
.de2_fmt = SUN8I_MIXER_FBFMT_RGBA4444,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_RGBX4444,
.de2_fmt = SUN8I_MIXER_FBFMT_RGBA4444,
},
{
.drm_fmt = DRM_FORMAT_BGRA4444,
.de2_fmt = SUN8I_MIXER_FBFMT_BGRA4444,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_BGRX4444,
.de2_fmt = SUN8I_MIXER_FBFMT_BGRA4444,
},
{
.drm_fmt = DRM_FORMAT_ARGB1555,
.de2_fmt = SUN8I_MIXER_FBFMT_ARGB1555,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_XRGB1555,
.de2_fmt = SUN8I_MIXER_FBFMT_ARGB1555,
},
{
.drm_fmt = DRM_FORMAT_ABGR1555,
.de2_fmt = SUN8I_MIXER_FBFMT_ABGR1555,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_XBGR1555,
.de2_fmt = SUN8I_MIXER_FBFMT_ABGR1555,
},
{
.drm_fmt = DRM_FORMAT_RGBA5551,
.de2_fmt = SUN8I_MIXER_FBFMT_RGBA5551,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_RGBX5551,
.de2_fmt = SUN8I_MIXER_FBFMT_RGBA5551,
},
{
.drm_fmt = DRM_FORMAT_BGRA5551,
.de2_fmt = SUN8I_MIXER_FBFMT_BGRA5551,
},
{
/* for DE2 VI layer which ignores alpha */
.drm_fmt = DRM_FORMAT_BGRX5551,
.de2_fmt = SUN8I_MIXER_FBFMT_BGRA5551,
},
{
.drm_fmt = DRM_FORMAT_ARGB2101010,
.de2_fmt = SUN8I_MIXER_FBFMT_ARGB2101010,
},
{
.drm_fmt = DRM_FORMAT_ABGR2101010,
.de2_fmt = SUN8I_MIXER_FBFMT_ABGR2101010,
},
{
.drm_fmt = DRM_FORMAT_RGBA1010102,
.de2_fmt = SUN8I_MIXER_FBFMT_RGBA1010102,
},
{
.drm_fmt = DRM_FORMAT_BGRA1010102,
.de2_fmt = SUN8I_MIXER_FBFMT_BGRA1010102,
},
{
.drm_fmt = DRM_FORMAT_UYVY,
.de2_fmt = SUN8I_MIXER_FBFMT_UYVY,
},
{
.drm_fmt = DRM_FORMAT_VYUY,
.de2_fmt = SUN8I_MIXER_FBFMT_VYUY,
},
{
.drm_fmt = DRM_FORMAT_YUYV,
.de2_fmt = SUN8I_MIXER_FBFMT_YUYV,
},
{
.drm_fmt = DRM_FORMAT_YVYU,
.de2_fmt = SUN8I_MIXER_FBFMT_YVYU,
},
{
.drm_fmt = DRM_FORMAT_NV16,
.de2_fmt = SUN8I_MIXER_FBFMT_NV16,
},
{
.drm_fmt = DRM_FORMAT_NV61,
.de2_fmt = SUN8I_MIXER_FBFMT_NV61,
},
{
.drm_fmt = DRM_FORMAT_NV12,
.de2_fmt = SUN8I_MIXER_FBFMT_NV12,
},
{
.drm_fmt = DRM_FORMAT_NV21,
.de2_fmt = SUN8I_MIXER_FBFMT_NV21,
},
{
.drm_fmt = DRM_FORMAT_YUV422,
.de2_fmt = SUN8I_MIXER_FBFMT_YUV422,
},
{
.drm_fmt = DRM_FORMAT_YUV420,
.de2_fmt = SUN8I_MIXER_FBFMT_YUV420,
},
{
.drm_fmt = DRM_FORMAT_YUV411,
.de2_fmt = SUN8I_MIXER_FBFMT_YUV411,
},
{
.drm_fmt = DRM_FORMAT_YVU422,
.de2_fmt = SUN8I_MIXER_FBFMT_YUV422,
},
{
.drm_fmt = DRM_FORMAT_YVU420,
.de2_fmt = SUN8I_MIXER_FBFMT_YUV420,
},
{
.drm_fmt = DRM_FORMAT_YVU411,
.de2_fmt = SUN8I_MIXER_FBFMT_YUV411,
},
{
.drm_fmt = DRM_FORMAT_P010,
.de2_fmt = SUN8I_MIXER_FBFMT_P010_YUV,
},
{
.drm_fmt = DRM_FORMAT_P210,
.de2_fmt = SUN8I_MIXER_FBFMT_P210_YUV,
},
};
int sun8i_mixer_drm_format_to_hw(u32 format, u32 *hw_format)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(de2_formats); ++i)
if (de2_formats[i].drm_fmt == format) {
*hw_format = de2_formats[i].de2_fmt;
return 0;
}
return -EINVAL;
}
static void sun8i_mixer_commit(struct sunxi_engine *engine,
struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine);
u32 bld_base = sun8i_blender_base(mixer);
struct regmap *bld_regs = sun8i_blender_regmap(mixer);
struct drm_plane_state *plane_state;
struct drm_plane *plane;
u32 route = 0, pipe_en = 0;
DRM_DEBUG_DRIVER("Committing changes\n");
drm_for_each_plane(plane, state->dev) {
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
int w, h, x, y, zpos;
bool enable;
if (!(plane->possible_crtcs & drm_crtc_mask(crtc)))
continue;
plane_state = drm_atomic_get_new_plane_state(state, plane);
if (!plane_state)
plane_state = plane->state;
enable = plane_state->crtc && plane_state->visible;
zpos = plane_state->normalized_zpos;
x = plane_state->dst.x1;
y = plane_state->dst.y1;
w = drm_rect_width(&plane_state->dst);
h = drm_rect_height(&plane_state->dst);
DRM_DEBUG_DRIVER(" plane %d: chan=%d ovl=%d en=%d zpos=%d x=%d y=%d w=%d h=%d\n",
plane->base.id, layer->index, layer->overlay,
enable, zpos, x, y, w, h);
if (!enable)
continue;
/* Route layer to pipe based on zpos */
route |= layer->index << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos);
pipe_en |= SUN8I_MIXER_BLEND_PIPE_CTL_EN(zpos);
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos),
SUN8I_MIXER_COORD(x, y));
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos),
SUN8I_MIXER_SIZE(w, h));
}
regmap_write(bld_regs, SUN8I_MIXER_BLEND_ROUTE(bld_base), route);
regmap_write(bld_regs, SUN8I_MIXER_BLEND_PIPE_CTL(bld_base),
pipe_en | SUN8I_MIXER_BLEND_PIPE_CTL_FC_EN(0));
if (mixer->cfg->de_type != SUN8I_MIXER_DE33)
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 plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num;
enum drm_plane_type type;
unsigned int phy_index;
int i;
planes = devm_kcalloc(drm->dev, plane_cnt + 1, sizeof(*planes), GFP_KERNEL);
if (!planes)
return ERR_PTR(-ENOMEM);
for (i = 0; i < mixer->cfg->vi_num; i++) {
struct sun8i_layer *layer;
if (i == 0 && !mixer->cfg->ui_num)
type = DRM_PLANE_TYPE_PRIMARY;
else
type = DRM_PLANE_TYPE_OVERLAY;
phy_index = i;
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
phy_index = mixer->cfg->map[i];
layer = sun8i_vi_layer_init_one(drm, type, mixer->engine.regs,
i, phy_index, plane_cnt,
&mixer->cfg->lay_cfg);
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++) {
unsigned int index = mixer->cfg->vi_num + i;
struct sun8i_layer *layer;
if (i == 0)
type = DRM_PLANE_TYPE_PRIMARY;
else
type = DRM_PLANE_TYPE_OVERLAY;
phy_index = index;
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
phy_index = mixer->cfg->map[index];
layer = sun8i_ui_layer_init_one(drm, type, mixer->engine.regs,
index, phy_index, plane_cnt,
&mixer->cfg->lay_cfg);
if (IS_ERR(layer)) {
dev_err(drm->dev, "Couldn't initialize %s plane\n",
i ? "overlay" : "primary");
return ERR_CAST(layer);
}
planes[index] = &layer->plane;
}
return planes;
}
static void sun8i_mixer_mode_set(struct sunxi_engine *engine,
const struct drm_display_mode *mode)
{
struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine);
struct regmap *bld_regs;
u32 bld_base, size, val;
bool interlaced;
bld_base = sun8i_blender_base(mixer);
bld_regs = sun8i_blender_regmap(mixer);
interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE);
size = SUN8I_MIXER_SIZE(mode->hdisplay, mode->vdisplay);
DRM_DEBUG_DRIVER("Updating global size W: %u H: %u\n",
mode->hdisplay, mode->vdisplay);
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
regmap_write(mixer->top_regs, SUN50I_MIXER_GLOBAL_SIZE, size);
else
regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_SIZE, size);
regmap_write(bld_regs, SUN8I_MIXER_BLEND_OUTSIZE(bld_base), size);
if (interlaced)
val = SUN8I_MIXER_BLEND_OUTCTL_INTERLACED;
else
val = 0;
regmap_update_bits(bld_regs, SUN8I_MIXER_BLEND_OUTCTL(bld_base),
SUN8I_MIXER_BLEND_OUTCTL_INTERLACED, val);
DRM_DEBUG_DRIVER("Switching display mixer interlaced mode %s\n",
interlaced ? "on" : "off");
}
static const struct sunxi_engine_ops sun8i_engine_ops = {
.commit = sun8i_mixer_commit,
.layers_init = sun8i_layers_init,
.mode_set = sun8i_mixer_mode_set,
};
static const struct regmap_config sun8i_mixer_regmap_config = {
.name = "layers",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 0xffffc, /* guessed */
};
static const struct regmap_config sun8i_top_regmap_config = {
.name = "top",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 0x3c,
};
static const struct regmap_config sun8i_disp_regmap_config = {
.name = "display",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 0x20000,
};
static int sun8i_mixer_of_get_id(struct device_node *node)
{
struct device_node *ep, *remote;
struct of_endpoint of_ep;
/* Output port is 1, and we want the first endpoint. */
ep = of_graph_get_endpoint_by_regs(node, 1, -1);
if (!ep)
return -EINVAL;
remote = of_graph_get_remote_endpoint(ep);
of_node_put(ep);
if (!remote)
return -EINVAL;
of_graph_parse_endpoint(remote, &of_ep);
of_node_put(remote);
return of_ep.id;
}
static void sun8i_mixer_init(struct sun8i_mixer *mixer)
{
struct regmap *top_regs, *disp_regs;
unsigned int base = sun8i_blender_base(mixer);
int plane_cnt, i;
if (mixer->cfg->de_type == SUN8I_MIXER_DE33) {
top_regs = mixer->top_regs;
disp_regs = mixer->disp_regs;
} else {
top_regs = mixer->engine.regs;
disp_regs = mixer->engine.regs;
}
/* Enable the mixer */
regmap_write(top_regs, SUN8I_MIXER_GLOBAL_CTL,
SUN8I_MIXER_GLOBAL_CTL_RT_EN);
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
regmap_write(top_regs, SUN50I_MIXER_GLOBAL_CLK, 1);
/* Set background color to black */
regmap_write(disp_regs, SUN8I_MIXER_BLEND_BKCOLOR(base),
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(disp_regs, SUN8I_MIXER_BLEND_PIPE_CTL(base),
SUN8I_MIXER_BLEND_PIPE_CTL_FC_EN(0));
regmap_write(disp_regs, SUN8I_MIXER_BLEND_ATTR_FCOLOR(base, 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(disp_regs,
SUN8I_MIXER_BLEND_MODE(base, i),
SUN8I_MIXER_BLEND_MODE_DEF);
regmap_update_bits(disp_regs, SUN8I_MIXER_BLEND_PIPE_CTL(base),
SUN8I_MIXER_BLEND_PIPE_CTL_EN_MSK, 0);
}
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;
void __iomem *regs;
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;
/*
* This assume we have the same DMA constraints for all our the
* devices in our pipeline (all the backends, but also the
* frontends). This sounds bad, but it has always been the case
* for us, and DRM doesn't do per-device allocation either, so
* we would need to fix DRM first...
*
* Always use the first bound backend as the DMA device. While
* our device trees always have all backends enabled, some in
* the wild may actually have the first one disabled. If both
* are enabled, the order in which they are bound is guaranteed
* since the driver adds components in order.
*/
if (drm_dev_dma_dev(drm) == drm->dev)
drm_dev_set_dma_dev(drm, dev);
/*
* 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;
regs = devm_platform_ioremap_resource(pdev, 0);
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);
}
if (mixer->cfg->de_type == SUN8I_MIXER_DE33) {
regs = devm_platform_ioremap_resource_byname(pdev, "top");
if (IS_ERR(regs))
return PTR_ERR(regs);
mixer->top_regs = devm_regmap_init_mmio(dev, regs,
&sun8i_top_regmap_config);
if (IS_ERR(mixer->top_regs)) {
dev_err(dev, "Couldn't create the top regmap\n");
return PTR_ERR(mixer->top_regs);
}
regs = devm_platform_ioremap_resource_byname(pdev, "display");
if (IS_ERR(regs))
return PTR_ERR(regs);
mixer->disp_regs = devm_regmap_init_mmio(dev, regs,
&sun8i_disp_regmap_config);
if (IS_ERR(mixer->disp_regs)) {
dev_err(dev, "Couldn't create the disp regmap\n");
return PTR_ERR(mixer->disp_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 registers and disable unused sub-engines */
if (mixer->cfg->de_type == SUN8I_MIXER_DE3) {
for (i = 0; i < DE3_MIXER_UNIT_SIZE; i += 4)
regmap_write(mixer->engine.regs, i, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_FCE_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_PEAK_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_LCTI_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_BLS_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_FCC_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_DNS_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_DRC_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_FMT_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_CDC0_EN, 0);
regmap_write(mixer->engine.regs, SUN50I_MIXER_CDC1_EN, 0);
} else if (mixer->cfg->de_type == SUN8I_MIXER_DE2) {
for (i = 0; i < DE2_MIXER_UNIT_SIZE; i += 4)
regmap_write(mixer->engine.regs, i, 0);
regmap_write(mixer->engine.regs, SUN8I_MIXER_FCE_EN, 0);
regmap_write(mixer->engine.regs, SUN8I_MIXER_BWS_EN, 0);
regmap_write(mixer->engine.regs, SUN8I_MIXER_LTI_EN, 0);
regmap_write(mixer->engine.regs, SUN8I_MIXER_PEAK_EN, 0);
regmap_write(mixer->engine.regs, SUN8I_MIXER_ASE_EN, 0);
regmap_write(mixer->engine.regs, SUN8I_MIXER_FCC_EN, 0);
regmap_write(mixer->engine.regs, SUN8I_MIXER_DCSC_EN, 0);
}
sun8i_mixer_init(mixer);
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 void sun8i_mixer_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun8i_mixer_ops);
}
static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_a83t_mixer1_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 432000000,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_r40_mixer0_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_r40_mixer1_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 2,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 150000000,
.vi_num = 2,
.ui_num = 1,
};
static const struct sun8i_mixer_cfg sun20i_d1_mixer0_cfg = {
.lay_cfg = {
.ccsc = CCSC_D1_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun20i_d1_mixer1_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x1,
.scanline_yuv = 1024,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.ui_num = 0,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_a64_mixer0_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = {
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_h6_mixer0_cfg = {
.lay_cfg = {
.de_type = SUN8I_MIXER_DE3,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
},
.de_type = SUN8I_MIXER_DE3,
.mod_rate = 600000000,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_h616_mixer0_cfg = {
.lay_cfg = {
.de_type = SUN8I_MIXER_DE33,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
},
.de_type = SUN8I_MIXER_DE33,
.mod_rate = 600000000,
.ui_num = 3,
.vi_num = 1,
.map = {0, 6, 7, 8},
};
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-r40-de2-mixer-0",
.data = &sun8i_r40_mixer0_cfg,
},
{
.compatible = "allwinner,sun8i-r40-de2-mixer-1",
.data = &sun8i_r40_mixer1_cfg,
},
{
.compatible = "allwinner,sun8i-v3s-de2-mixer",
.data = &sun8i_v3s_mixer_cfg,
},
{
.compatible = "allwinner,sun20i-d1-de2-mixer-0",
.data = &sun20i_d1_mixer0_cfg,
},
{
.compatible = "allwinner,sun20i-d1-de2-mixer-1",
.data = &sun20i_d1_mixer1_cfg,
},
{
.compatible = "allwinner,sun50i-a64-de2-mixer-0",
.data = &sun50i_a64_mixer0_cfg,
},
{
.compatible = "allwinner,sun50i-a64-de2-mixer-1",
.data = &sun50i_a64_mixer1_cfg,
},
{
.compatible = "allwinner,sun50i-h6-de3-mixer-0",
.data = &sun50i_h6_mixer0_cfg,
},
{
.compatible = "allwinner,sun50i-h616-de33-mixer-0",
.data = &sun50i_h616_mixer0_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");
@@ -0,0 +1,274 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io>
*/
#ifndef _SUN8I_MIXER_H_
#define _SUN8I_MIXER_H_
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <drm/drm_plane.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 SUN50I_MIXER_GLOBAL_SIZE 0x8
#define SUN50I_MIXER_GLOBAL_CLK 0xc
#define SUN8I_MIXER_GLOBAL_CTL_RT_EN BIT(0)
#define SUN8I_MIXER_GLOBAL_DBUFF_ENABLE BIT(0)
#define DE2_MIXER_UNIT_SIZE 0x6000
#define DE3_MIXER_UNIT_SIZE 0x3000
#define DE2_BLD_BASE 0x1000
#define DE2_CH_BASE 0x2000
#define DE2_CH_SIZE 0x1000
#define DE3_BLD_BASE 0x0800
#define DE3_CH_BASE 0x1000
#define DE3_CH_SIZE 0x0800
#define DE33_CH_BASE 0x1000
#define DE33_CH_SIZE 0x20000
#define SUN8I_MIXER_BLEND_PIPE_CTL(base) ((base) + 0)
#define SUN8I_MIXER_BLEND_ATTR_FCOLOR(base, x) ((base) + 0x4 + 0x10 * (x))
#define SUN8I_MIXER_BLEND_ATTR_INSIZE(base, x) ((base) + 0x8 + 0x10 * (x))
#define SUN8I_MIXER_BLEND_ATTR_COORD(base, x) ((base) + 0xc + 0x10 * (x))
#define SUN8I_MIXER_BLEND_ROUTE(base) ((base) + 0x80)
#define SUN8I_MIXER_BLEND_PREMULTIPLY(base) ((base) + 0x84)
#define SUN8I_MIXER_BLEND_BKCOLOR(base) ((base) + 0x88)
#define SUN8I_MIXER_BLEND_OUTSIZE(base) ((base) + 0x8c)
#define SUN8I_MIXER_BLEND_MODE(base, x) ((base) + 0x90 + 0x04 * (x))
#define SUN8I_MIXER_BLEND_CK_CTL(base) ((base) + 0xb0)
#define SUN8I_MIXER_BLEND_CK_CFG(base) ((base) + 0xb4)
#define SUN8I_MIXER_BLEND_CK_MAX(base, x) ((base) + 0xc0 + 0x04 * (x))
#define SUN8I_MIXER_BLEND_CK_MIN(base, x) ((base) + 0xe0 + 0x04 * (x))
#define SUN8I_MIXER_BLEND_OUTCTL(base) ((base) + 0xfc)
#define SUN50I_MIXER_BLEND_CSC_CTL(base) ((base) + 0x100)
#define SUN50I_MIXER_BLEND_CSC_COEFF(base, layer, x) \
((base) + 0x110 + (layer) * 0x30 + (x) * 4)
#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 SUN50I_MIXER_BLEND_CSC_CTL_EN(ch) BIT(ch)
#define SUN50I_MIXER_BLEND_CSC_CONST_VAL(d, c) (((d) << 16) | ((c) & 0xffff))
#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_ARGB2101010 20
#define SUN8I_MIXER_FBFMT_ABGR2101010 21
#define SUN8I_MIXER_FBFMT_RGBA1010102 22
#define SUN8I_MIXER_FBFMT_BGRA1010102 23
#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
/* format 15 doesn't exist */
#define SUN8I_MIXER_FBFMT_P010_YUV 16
/* format 17 is P010 YVU */
#define SUN8I_MIXER_FBFMT_P210_YUV 18
/* format 19 is P210 YVU */
/* format 20 is packed YVU444 10-bit */
/* format 21 is packed YUV444 10-bit */
/*
* Sub-engines listed bellow are unused for 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
#define SUN50I_MIXER_FCE_EN 0x70000
#define SUN50I_MIXER_PEAK_EN 0x70800
#define SUN50I_MIXER_LCTI_EN 0x71000
#define SUN50I_MIXER_BLS_EN 0x71800
#define SUN50I_MIXER_FCC_EN 0x72000
#define SUN50I_MIXER_DNS_EN 0x80000
#define SUN50I_MIXER_DRC_EN 0xa0000
#define SUN50I_MIXER_FMT_EN 0xa8000
#define SUN50I_MIXER_CDC0_EN 0xd0000
#define SUN50I_MIXER_CDC1_EN 0xd8000
enum {
/* First mixer or second mixer with VEP support. */
CCSC_MIXER0_LAYOUT,
/* Second mixer without VEP support. */
CCSC_MIXER1_LAYOUT,
/* First mixer with the MMIO layout found in the D1 SoC. */
CCSC_D1_MIXER0_LAYOUT,
};
enum sun8i_mixer_type {
SUN8I_MIXER_DE2,
SUN8I_MIXER_DE3,
SUN8I_MIXER_DE33,
};
/**
* struct sun8i_layer_cfg - layer configuration
* @vi_scaler_num: Number of VI scalers. Used on DE2 and DE3.
* @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 from the enumeration above.
* @de_type: sun8i_mixer_type enum representing the display engine generation.
* @scaline_yuv: size of a scanline for VI scaler for YUV formats.
* @de2_fcc_alpha: use FCC for missing DE2 VI alpha capability
* Most DE2 cores has FCC. If number of VI planes is one, enable this.
*/
struct sun8i_layer_cfg {
unsigned int vi_scaler_num;
int scaler_mask;
int ccsc;
unsigned int de_type;
unsigned int scanline_yuv;
unsigned int de2_fcc_alpha : 1;
};
/**
* struct sun8i_mixer_cfg - mixer HW configuration
* @lay_cfg: layer configuration
* @vi_num: number of VI channels
* @ui_num: number of UI channels
* @de_type: sun8i_mixer_type enum representing the display engine generation.
* @mod_rate: module clock rate that needs to be set in order to have
* a functional block.
* @map: channel map for DE variants processing YUV separately (DE33)
*/
struct sun8i_mixer_cfg {
struct sun8i_layer_cfg lay_cfg;
int vi_num;
int ui_num;
unsigned int de_type;
unsigned long mod_rate;
unsigned int map[6];
};
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;
struct regmap *top_regs;
struct regmap *disp_regs;
};
enum {
SUN8I_LAYER_TYPE_UI,
SUN8I_LAYER_TYPE_VI,
};
struct sun8i_layer {
struct drm_plane plane;
int type;
int index;
int channel;
int overlay;
struct regmap *regs;
const struct sun8i_layer_cfg *cfg;
};
static inline struct sun8i_layer *
plane_to_sun8i_layer(struct drm_plane *plane)
{
return container_of(plane, struct sun8i_layer, plane);
}
static inline struct sun8i_mixer *
engine_to_sun8i_mixer(struct sunxi_engine *engine)
{
return container_of(engine, struct sun8i_mixer, engine);
}
static inline u32
sun8i_blender_base(struct sun8i_mixer *mixer)
{
return mixer->cfg->de_type == SUN8I_MIXER_DE3 ? DE3_BLD_BASE : DE2_BLD_BASE;
}
static inline struct regmap *
sun8i_blender_regmap(struct sun8i_mixer *mixer)
{
return mixer->cfg->de_type == SUN8I_MIXER_DE33 ?
mixer->disp_regs : mixer->engine.regs;
}
static inline u32
sun8i_channel_base(struct sun8i_layer *layer)
{
if (layer->cfg->de_type == SUN8I_MIXER_DE33)
return DE33_CH_BASE + layer->channel * DE33_CH_SIZE;
else if (layer->cfg->de_type == SUN8I_MIXER_DE3)
return DE3_CH_BASE + layer->channel * DE3_CH_SIZE;
else
return DE2_CH_BASE + layer->channel * DE2_CH_SIZE;
}
int sun8i_mixer_drm_format_to_hw(u32 format, u32 *hw_format);
#endif /* _SUN8I_MIXER_H_ */
@@ -0,0 +1,312 @@
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
#include <linux/bitfield.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <dt-bindings/clock/sun8i-tcon-top.h>
#include "sun8i_tcon_top.h"
struct sun8i_tcon_top_quirks {
bool has_tcon_tv1;
bool has_dsi;
};
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;
const struct sun8i_tcon_top_quirks *quirks;
void __iomem *regs;
int ret, i;
quirks = of_device_get_match_data(&pdev->dev);
tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL);
if (!tcon_top)
return -ENOMEM;
clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, CLK_NUM),
GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
clk_data->num = CLK_NUM;
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);
}
regs = devm_platform_ioremap_resource(pdev, 0);
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.
*/
i = 0;
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, i++);
if (quirks->has_tcon_tv1)
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, i++);
if (quirks->has_dsi)
clk_data->hws[CLK_TCON_TOP_DSI] =
sun8i_tcon_top_register_gate(dev, "dsi", regs,
&tcon_top->reg_lock,
TCON_TOP_TCON_DSI_GATE, i++);
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;
}
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 void sun8i_tcon_top_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun8i_tcon_top_ops);
}
static const struct sun8i_tcon_top_quirks sun8i_r40_tcon_top_quirks = {
.has_tcon_tv1 = true,
.has_dsi = true,
};
static const struct sun8i_tcon_top_quirks sun20i_d1_tcon_top_quirks = {
.has_dsi = true,
};
static const struct sun8i_tcon_top_quirks sun50i_h6_tcon_top_quirks = {
/* Nothing special */
};
/* 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[] = {
{
.compatible = "allwinner,sun8i-r40-tcon-top",
.data = &sun8i_r40_tcon_top_quirks
},
{
.compatible = "allwinner,sun20i-d1-tcon-top",
.data = &sun20i_d1_tcon_top_quirks
},
{
.compatible = "allwinner,sun50i-h6-tcon-top",
.data = &sun50i_h6_tcon_top_quirks
},
{ /* 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");
@@ -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_ */
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 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>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "sun8i_mixer.h"
#include "sun8i_ui_layer.h"
#include "sun8i_ui_scaler.h"
#include "sun8i_vi_scaler.h"
static void sun8i_ui_layer_disable(struct sun8i_layer *layer)
{
u32 ch_base = sun8i_channel_base(layer);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), 0);
}
static void sun8i_ui_layer_update_attributes(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
const struct drm_format_info *fmt;
u32 val, ch_base, hw_fmt;
ch_base = sun8i_channel_base(layer);
fmt = state->fb->format;
sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA(state->alpha >> 8);
val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ?
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_PIXEL :
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_COMBINED;
val |= hw_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET;
val |= SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN;
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), val);
}
static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
u32 src_w, src_h, dst_w, dst_h;
u32 outsize, insize;
u32 hphase, vphase;
u32 ch_base;
DRM_DEBUG_DRIVER("Updating UI channel %d overlay %d\n",
layer->channel, layer->overlay);
ch_base = sun8i_channel_base(layer);
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);
/* 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(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch_base, layer->overlay),
insize);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch_base),
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;
if (layer->cfg->de_type == SUN8I_MIXER_DE33) {
sun8i_vi_scaler_setup(layer, src_w, src_h, dst_w, dst_h,
hscale, vscale, hphase, vphase,
state->fb->format);
sun8i_vi_scaler_enable(layer, true);
} else {
sun8i_ui_scaler_setup(layer, src_w, src_h, dst_w, dst_h,
hscale, vscale, hphase, vphase);
sun8i_ui_scaler_enable(layer, true);
}
} else {
DRM_DEBUG_DRIVER("HW scaling is not needed\n");
if (layer->cfg->de_type == SUN8I_MIXER_DE33)
sun8i_vi_scaler_enable(layer, false);
else
sun8i_ui_scaler_enable(layer, false);
}
}
static void sun8i_ui_layer_update_buffer(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct drm_framebuffer *fb = state->fb;
dma_addr_t dma_addr;
u32 ch_base;
ch_base = sun8i_channel_base(layer);
/* Get the start of the displayed memory */
dma_addr = drm_fb_dma_get_gem_addr(fb, state, 0);
/* Set the line width */
DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch_base, layer->overlay),
fb->pitches[0]);
DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &dma_addr);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, layer->overlay),
lower_32_bits(dma_addr));
}
static int sun8i_ui_layer_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_crtc_state *crtc_state;
const struct drm_format_info *fmt;
int min_scale, max_scale, ret;
u32 hw_fmt;
if (!crtc)
return 0;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (WARN_ON(!crtc_state))
return -EINVAL;
fmt = new_plane_state->fb->format;
ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
if (ret || fmt->is_yuv) {
DRM_DEBUG_DRIVER("Invalid plane format\n");
return -EINVAL;
}
min_scale = DRM_PLANE_NO_SCALING;
max_scale = DRM_PLANE_NO_SCALING;
if (layer->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(new_plane_state,
crtc_state,
min_scale, max_scale,
true, true);
}
static void sun8i_ui_layer_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
if (!new_state->crtc || !new_state->visible) {
sun8i_ui_layer_disable(layer);
return;
}
sun8i_ui_layer_update_attributes(layer, plane);
sun8i_ui_layer_update_coord(layer, plane);
sun8i_ui_layer_update_buffer(layer, plane);
}
static const struct drm_plane_helper_funcs sun8i_ui_layer_helper_funcs = {
.atomic_check = sun8i_ui_layer_atomic_check,
.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,
};
static const uint64_t sun8i_layer_modifiers[] = {
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_INVALID
};
struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm,
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg)
{
struct sun8i_layer *layer;
int ret;
layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL);
if (!layer)
return ERR_PTR(-ENOMEM);
layer->type = SUN8I_LAYER_TYPE_UI;
layer->index = index;
layer->channel = phy_index;
layer->overlay = 0;
layer->regs = regs;
layer->cfg = cfg;
/* 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),
sun8i_layer_modifiers, type, NULL);
if (ret) {
dev_err(drm->dev, "Couldn't initialize layer\n");
return ERR_PTR(ret);
}
ret = drm_plane_create_alpha_property(&layer->plane);
if (ret) {
dev_err(drm->dev, "Couldn't add alpha property\n");
return ERR_PTR(ret);
}
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_ui_layer_helper_funcs);
return layer;
}
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* 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>
*/
#ifndef _SUN8I_UI_LAYER_H_
#define _SUN8I_UI_LAYER_H_
#include <drm/drm_plane.h>
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR(base, layer) \
((base) + 0x20 * (layer) + 0x0)
#define SUN8I_MIXER_CHAN_UI_LAYER_SIZE(base, layer) \
((base) + 0x20 * (layer) + 0x4)
#define SUN8I_MIXER_CHAN_UI_LAYER_COORD(base, layer) \
((base) + 0x20 * (layer) + 0x8)
#define SUN8I_MIXER_CHAN_UI_LAYER_PITCH(base, layer) \
((base) + 0x20 * (layer) + 0xc)
#define SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(base, layer) \
((base) + 0x20 * (layer) + 0x10)
#define SUN8I_MIXER_CHAN_UI_LAYER_BOT_LADDR(base, layer) \
((base) + 0x20 * (layer) + 0x14)
#define SUN8I_MIXER_CHAN_UI_LAYER_FCOLOR(base, layer) \
((base) + 0x20 * (layer) + 0x18)
#define SUN8I_MIXER_CHAN_UI_TOP_HADDR(base) \
((base) + 0x80)
#define SUN8I_MIXER_CHAN_UI_BOT_HADDR(base) \
((base) + 0x84)
#define SUN8I_MIXER_CHAN_UI_OVL_SIZE(base) \
((base) + 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)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA(x) ((x) << 24)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_PIXEL ((0) << 1)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_LAYER ((1) << 1)
#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_COMBINED ((2) << 1)
struct sun8i_mixer;
struct sun8i_layer;
struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm,
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg);
#endif /* _SUN8I_UI_LAYER_H_ */
@@ -0,0 +1,181 @@
/*
* 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"
#include "sun8i_vi_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 u32 sun8i_ui_scaler_base(struct sun8i_layer *layer)
{
int offset = layer->cfg->vi_scaler_num;
if (layer->cfg->de_type == SUN8I_MIXER_DE3)
return DE3_VI_SCALER_UNIT_BASE +
DE3_VI_SCALER_UNIT_SIZE * offset +
DE3_UI_SCALER_UNIT_SIZE * (layer->channel - offset);
else
return DE2_VI_SCALER_UNIT_BASE +
DE2_VI_SCALER_UNIT_SIZE * offset +
DE2_UI_SCALER_UNIT_SIZE * (layer->channel - offset);
}
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_layer *layer, bool enable)
{
u32 val, base;
base = sun8i_ui_scaler_base(layer);
if (enable)
val = SUN8I_SCALER_GSU_CTRL_EN |
SUN8I_SCALER_GSU_CTRL_COEFF_RDY;
else
val = 0;
regmap_write(layer->regs, SUN8I_SCALER_GSU_CTRL(base), val);
}
void sun8i_ui_scaler_setup(struct sun8i_layer *layer,
u32 src_w, u32 src_h, u32 dst_w, u32 dst_h,
u32 hscale, u32 vscale, u32 hphase, u32 vphase)
{
u32 insize, outsize;
int i, offset;
u32 base;
base = sun8i_ui_scaler_base(layer);
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);
regmap_write(layer->regs,
SUN8I_SCALER_GSU_OUTSIZE(base), outsize);
regmap_write(layer->regs,
SUN8I_SCALER_GSU_INSIZE(base), insize);
regmap_write(layer->regs,
SUN8I_SCALER_GSU_HSTEP(base), hscale);
regmap_write(layer->regs,
SUN8I_SCALER_GSU_VSTEP(base), vscale);
regmap_write(layer->regs,
SUN8I_SCALER_GSU_HPHASE(base), hphase);
regmap_write(layer->regs,
SUN8I_SCALER_GSU_VPHASE(base), 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(layer->regs,
SUN8I_SCALER_GSU_HCOEFF(base, i),
lan2coefftab16[offset + i]);
}
@@ -0,0 +1,43 @@
/*
* 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"
#define DE2_UI_SCALER_UNIT_SIZE 0x10000
#define DE3_UI_SCALER_UNIT_SIZE 0x08000
/* 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(base) ((base) + 0x0)
#define SUN8I_SCALER_GSU_OUTSIZE(base) ((base) + 0x40)
#define SUN8I_SCALER_GSU_INSIZE(base) ((base) + 0x80)
#define SUN8I_SCALER_GSU_HSTEP(base) ((base) + 0x88)
#define SUN8I_SCALER_GSU_VSTEP(base) ((base) + 0x8c)
#define SUN8I_SCALER_GSU_HPHASE(base) ((base) + 0x90)
#define SUN8I_SCALER_GSU_VPHASE(base) ((base) + 0x98)
#define SUN8I_SCALER_GSU_HCOEFF(base, index) ((base) + 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_layer *layer, bool enable);
void sun8i_ui_scaler_setup(struct sun8i_layer *layer,
u32 src_w, u32 src_h, u32 dst_w, u32 dst_h,
u32 hscale, u32 vscale, u32 hphase, u32 vphase);
#endif
@@ -0,0 +1,467 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_blend.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "sun4i_crtc.h"
#include "sun8i_csc.h"
#include "sun8i_mixer.h"
#include "sun8i_vi_layer.h"
#include "sun8i_vi_scaler.h"
static void sun8i_vi_layer_disable(struct sun8i_layer *layer)
{
u32 ch_base = sun8i_channel_base(layer);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), 0);
}
static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
const struct drm_format_info *fmt;
u32 val, ch_base, hw_fmt;
ch_base = sun8i_channel_base(layer);
fmt = state->fb->format;
sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
val = hw_fmt << SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET;
if (!fmt->is_yuv)
val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE;
val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN;
if (layer->cfg->de_type >= SUN8I_MIXER_DE3) {
val |= SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA(state->alpha >> 8);
val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ?
SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_PIXEL :
SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_COMBINED;
}
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), val);
if (layer->cfg->de2_fcc_alpha) {
regmap_write(layer->regs,
SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG,
SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8));
}
}
static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(state->crtc);
struct sun8i_mixer *mixer = engine_to_sun8i_mixer(scrtc->engine);
const struct drm_format_info *format = state->fb->format;
u32 src_w, src_h, dst_w, dst_h;
u32 outsize, insize;
u32 hphase, vphase;
u32 hn = 0, hm = 0;
u32 vn = 0, vm = 0;
bool subsampled;
u32 ch_base;
DRM_DEBUG_DRIVER("Updating VI channel %d overlay %d\n",
layer->channel, layer->overlay);
ch_base = sun8i_channel_base(layer);
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(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_SIZE(ch_base, layer->overlay),
insize);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_OVL_SIZE(ch_base),
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) {
unsigned int scanline, required;
struct drm_display_mode *mode;
u32 hscale, vscale, fps;
u64 ability;
DRM_DEBUG_DRIVER("HW scaling is enabled\n");
mode = &plane->state->crtc->state->mode;
fps = (mode->clock * 1000) / (mode->vtotal * mode->htotal);
ability = clk_get_rate(mixer->mod_clk);
/* BSP algorithm assumes 80% efficiency of VI scaler unit */
ability *= 80;
do_div(ability, mode->vdisplay * fps * max(src_w, dst_w));
required = src_h * 100 / dst_h;
if (ability < required) {
DRM_DEBUG_DRIVER("Using vertical coarse scaling\n");
vm = src_h;
vn = (u32)ability * dst_h / 100;
src_h = vn;
}
/* it seems that every RGB scaler has buffer for 2048 pixels */
scanline = subsampled ? layer->cfg->scanline_yuv : 2048;
if (src_w > scanline) {
DRM_DEBUG_DRIVER("Using horizontal coarse scaling\n");
hm = src_w;
hn = scanline;
src_w = hn;
}
hscale = (src_w << 16) / dst_w;
vscale = (src_h << 16) / dst_h;
sun8i_vi_scaler_setup(layer, src_w, src_h, dst_w, dst_h,
hscale, vscale, hphase, vphase, format);
sun8i_vi_scaler_enable(layer, true);
} else {
DRM_DEBUG_DRIVER("HW scaling is not needed\n");
sun8i_vi_scaler_enable(layer, false);
}
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_HDS_Y(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(hn) |
SUN8I_MIXER_CHAN_VI_DS_M(hm));
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_HDS_UV(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(hn) |
SUN8I_MIXER_CHAN_VI_DS_M(hm));
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_VDS_Y(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(vn) |
SUN8I_MIXER_CHAN_VI_DS_M(vm));
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_VDS_UV(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(vn) |
SUN8I_MIXER_CHAN_VI_DS_M(vm));
}
static void sun8i_vi_layer_update_buffer(struct sun8i_layer *layer,
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;
dma_addr_t dma_addr;
u32 ch_base;
int i;
ch_base = sun8i_channel_base(layer);
for (i = 0; i < format->num_planes; i++) {
/* Get the start of the displayed memory */
dma_addr = drm_fb_dma_get_gem_addr(fb, state, i);
/* Set the line width */
DRM_DEBUG_DRIVER("Layer %d. line width: %d bytes\n",
i + 1, fb->pitches[i]);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_PITCH(ch_base,
layer->overlay, i),
fb->pitches[i]);
DRM_DEBUG_DRIVER("Setting %d. buffer address to %pad\n",
i + 1, &dma_addr);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_TOP_LADDR(ch_base,
layer->overlay, i),
lower_32_bits(dma_addr));
}
}
static int sun8i_vi_layer_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_crtc_state *crtc_state;
const struct drm_format_info *fmt;
int min_scale, max_scale, ret;
u32 hw_fmt;
if (!crtc)
return 0;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (WARN_ON(!crtc_state))
return -EINVAL;
fmt = new_plane_state->fb->format;
ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
if (ret) {
DRM_DEBUG_DRIVER("Invalid plane format\n");
return ret;
}
min_scale = DRM_PLANE_NO_SCALING;
max_scale = DRM_PLANE_NO_SCALING;
if (layer->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(new_plane_state,
crtc_state,
min_scale, max_scale,
true, true);
}
static void sun8i_vi_layer_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
if (!new_state->crtc || !new_state->visible) {
sun8i_vi_layer_disable(layer);
return;
}
sun8i_vi_layer_update_attributes(layer, plane);
sun8i_vi_layer_update_coord(layer, plane);
sun8i_csc_config(layer, new_state);
sun8i_vi_layer_update_buffer(layer, plane);
}
static const struct drm_plane_helper_funcs sun8i_vi_layer_helper_funcs = {
.atomic_check = sun8i_vi_layer_atomic_check,
.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,
};
static const u32 sun8i_vi_layer_de3_formats[] = {
DRM_FORMAT_ABGR1555,
DRM_FORMAT_ABGR2101010,
DRM_FORMAT_ABGR4444,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_ARGB2101010,
DRM_FORMAT_ARGB4444,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_BGR565,
DRM_FORMAT_BGR888,
DRM_FORMAT_BGRA1010102,
DRM_FORMAT_BGRA5551,
DRM_FORMAT_BGRA4444,
DRM_FORMAT_BGRA8888,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_RGB565,
DRM_FORMAT_RGB888,
DRM_FORMAT_RGBA1010102,
DRM_FORMAT_RGBA4444,
DRM_FORMAT_RGBA5551,
DRM_FORMAT_RGBA8888,
DRM_FORMAT_RGBX8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_NV16,
DRM_FORMAT_NV12,
DRM_FORMAT_NV21,
DRM_FORMAT_NV61,
DRM_FORMAT_P010,
DRM_FORMAT_P210,
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,
};
static const uint64_t sun8i_layer_modifiers[] = {
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_INVALID
};
struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg)
{
u32 supported_encodings, supported_ranges;
unsigned int format_count;
struct sun8i_layer *layer;
const u32 *formats;
int ret;
layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL);
if (!layer)
return ERR_PTR(-ENOMEM);
layer->type = SUN8I_LAYER_TYPE_VI;
layer->index = index;
layer->channel = phy_index;
layer->overlay = 0;
layer->regs = regs;
layer->cfg = cfg;
if (layer->cfg->de_type >= SUN8I_MIXER_DE3) {
formats = sun8i_vi_layer_de3_formats;
format_count = ARRAY_SIZE(sun8i_vi_layer_de3_formats);
} else {
formats = sun8i_vi_layer_formats;
format_count = ARRAY_SIZE(sun8i_vi_layer_formats);
}
/* possible crtcs are set later */
ret = drm_universal_plane_init(drm, &layer->plane, 0,
&sun8i_vi_layer_funcs,
formats, format_count,
sun8i_layer_modifiers,
type, NULL);
if (ret) {
dev_err(drm->dev, "Couldn't initialize layer\n");
return ERR_PTR(ret);
}
if (layer->cfg->de2_fcc_alpha || layer->cfg->de_type >= SUN8I_MIXER_DE3) {
ret = drm_plane_create_alpha_property(&layer->plane);
if (ret) {
dev_err(drm->dev, "Couldn't add alpha property\n");
return ERR_PTR(ret);
}
}
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);
}
supported_encodings = BIT(DRM_COLOR_YCBCR_BT601) |
BIT(DRM_COLOR_YCBCR_BT709);
if (layer->cfg->de_type >= SUN8I_MIXER_DE3)
supported_encodings |= BIT(DRM_COLOR_YCBCR_BT2020);
supported_ranges = BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
BIT(DRM_COLOR_YCBCR_FULL_RANGE);
ret = drm_plane_create_color_properties(&layer->plane,
supported_encodings,
supported_ranges,
DRM_COLOR_YCBCR_BT709,
DRM_COLOR_YCBCR_LIMITED_RANGE);
if (ret) {
dev_err(drm->dev, "Couldn't add encoding and range properties!\n");
return ERR_PTR(ret);
}
drm_plane_helper_add(&layer->plane, &sun8i_vi_layer_helper_funcs);
return layer;
}
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net>
*/
#ifndef _SUN8I_VI_LAYER_H_
#define _SUN8I_VI_LAYER_H_
#include <drm/drm_plane.h>
#define SUN8I_MIXER_CHAN_VI_LAYER_ATTR(base, layer) \
((base) + 0x30 * (layer) + 0x0)
#define SUN8I_MIXER_CHAN_VI_LAYER_SIZE(base, layer) \
((base) + 0x30 * (layer) + 0x4)
#define SUN8I_MIXER_CHAN_VI_LAYER_COORD(base, layer) \
((base) + 0x30 * (layer) + 0x8)
#define SUN8I_MIXER_CHAN_VI_LAYER_PITCH(base, layer, plane) \
((base) + 0x30 * (layer) + 0xc + 4 * (plane))
#define SUN8I_MIXER_CHAN_VI_LAYER_TOP_LADDR(base, layer, plane) \
((base) + 0x30 * (layer) + 0x18 + 4 * (plane))
#define SUN8I_MIXER_CHAN_VI_OVL_SIZE(base) \
((base) + 0xe8)
#define SUN8I_MIXER_CHAN_VI_HDS_Y(base) \
((base) + 0xf0)
#define SUN8I_MIXER_CHAN_VI_HDS_UV(base) \
((base) + 0xf4)
#define SUN8I_MIXER_CHAN_VI_VDS_Y(base) \
((base) + 0xf8)
#define SUN8I_MIXER_CHAN_VI_VDS_UV(base) \
((base) + 0xfc)
#define SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG \
(0xAA000 + 0x90)
#define SUN8I_MIXER_FCC_GLOBAL_ALPHA(x) ((x) << 24)
#define SUN8I_MIXER_FCC_GLOBAL_ALPHA_MASK GENMASK(31, 24)
#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)
#define SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_MASK GENMASK(2, 1)
#define SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MASK GENMASK(31, 24)
#define SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA(x) ((x) << 24)
#define SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_PIXEL ((0) << 1)
#define SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_LAYER ((1) << 1)
#define SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_COMBINED ((2) << 1)
#define SUN8I_MIXER_CHAN_VI_DS_N(x) ((x) << 16)
#define SUN8I_MIXER_CHAN_VI_DS_M(x) ((x) << 0)
struct sun8i_mixer;
struct sun8i_layer;
struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg);
#endif /* _SUN8I_VI_LAYER_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,80 @@
/*
* 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"
#define DE2_VI_SCALER_UNIT_BASE 0x20000
#define DE2_VI_SCALER_UNIT_SIZE 0x20000
#define DE3_VI_SCALER_UNIT_BASE 0x20000
#define DE3_VI_SCALER_UNIT_SIZE 0x08000
#define DE33_VI_SCALER_UNIT_BASE 0x4000
/* 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(base) ((base) + 0x0)
#define SUN50I_SCALER_VSU_SCALE_MODE(base) ((base) + 0x10)
#define SUN50I_SCALER_VSU_DIR_THR(base) ((base) + 0x20)
#define SUN50I_SCALER_VSU_EDGE_THR(base) ((base) + 0x24)
#define SUN50I_SCALER_VSU_EDSCL_CTRL(base) ((base) + 0x28)
#define SUN50I_SCALER_VSU_ANGLE_THR(base) ((base) + 0x2c)
#define SUN8I_SCALER_VSU_OUTSIZE(base) ((base) + 0x40)
#define SUN8I_SCALER_VSU_YINSIZE(base) ((base) + 0x80)
#define SUN8I_SCALER_VSU_YHSTEP(base) ((base) + 0x88)
#define SUN8I_SCALER_VSU_YVSTEP(base) ((base) + 0x8c)
#define SUN8I_SCALER_VSU_YHPHASE(base) ((base) + 0x90)
#define SUN8I_SCALER_VSU_YVPHASE(base) ((base) + 0x98)
#define SUN8I_SCALER_VSU_CINSIZE(base) ((base) + 0xc0)
#define SUN8I_SCALER_VSU_CHSTEP(base) ((base) + 0xc8)
#define SUN8I_SCALER_VSU_CVSTEP(base) ((base) + 0xcc)
#define SUN8I_SCALER_VSU_CHPHASE(base) ((base) + 0xd0)
#define SUN8I_SCALER_VSU_CVPHASE(base) ((base) + 0xd8)
#define SUN8I_SCALER_VSU_YHCOEFF0(base, i) ((base) + 0x200 + 0x4 * (i))
#define SUN8I_SCALER_VSU_YHCOEFF1(base, i) ((base) + 0x300 + 0x4 * (i))
#define SUN8I_SCALER_VSU_YVCOEFF(base, i) ((base) + 0x400 + 0x4 * (i))
#define SUN8I_SCALER_VSU_CHCOEFF0(base, i) ((base) + 0x600 + 0x4 * (i))
#define SUN8I_SCALER_VSU_CHCOEFF1(base, i) ((base) + 0x700 + 0x4 * (i))
#define SUN8I_SCALER_VSU_CVCOEFF(base, i) ((base) + 0x800 + 0x4 * (i))
#define SUN8I_SCALER_VSU_CTRL_EN BIT(0)
#define SUN8I_SCALER_VSU_CTRL_COEFF_RDY BIT(4)
#define SUN50I_SCALER_VSU_SUB_ZERO_DIR_THR(x) (((x) << 24) & 0xFF)
#define SUN50I_SCALER_VSU_ZERO_DIR_THR(x) (((x) << 16) & 0xFF)
#define SUN50I_SCALER_VSU_HORZ_DIR_THR(x) (((x) << 8) & 0xFF)
#define SUN50I_SCALER_VSU_VERT_DIR_THR(x) ((x) & 0xFF)
#define SUN50I_SCALER_VSU_SCALE_MODE_UI 0
#define SUN50I_SCALER_VSU_SCALE_MODE_NORMAL 1
#define SUN50I_SCALER_VSU_SCALE_MODE_ED_SCALE 2
#define SUN50I_SCALER_VSU_EDGE_SHIFT(x) (((x) << 16) & 0xF)
#define SUN50I_SCALER_VSU_EDGE_OFFSET(x) ((x) & 0xFF)
#define SUN50I_SCALER_VSU_ANGLE_SHIFT(x) (((x) << 16) & 0xF)
#define SUN50I_SCALER_VSU_ANGLE_OFFSET(x) ((x) & 0xFF)
void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable);
void sun8i_vi_scaler_setup(struct sun8i_layer *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
@@ -0,0 +1,221 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io>
*/
#ifndef _SUNXI_ENGINE_H_
#define _SUNXI_ENGINE_H_
struct drm_plane;
struct drm_crtc;
struct drm_device;
struct drm_crtc_state;
struct drm_display_mode;
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,
struct drm_crtc *crtc,
struct drm_atomic_commit *state);
/**
* @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);
/**
* @mode_set:
*
* This callback is used to set mode related parameters
* like interlacing, screen size, etc. once per mode set.
*
* This function is optional.
*/
void (*mode_set)(struct sunxi_engine *engine,
const struct drm_display_mode *mode);
};
/**
* 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)
* @list: engine list management
*/
struct sunxi_engine {
const struct sunxi_engine_ops *ops;
struct device_node *node;
struct regmap *regs;
int id;
struct list_head list;
};
/**
* sunxi_engine_commit() - commit all changes of the engine
* @engine: pointer to the engine
* @crtc: pointer to crtc the engine is associated with
* @state: atomic state
*/
static inline void
sunxi_engine_commit(struct sunxi_engine *engine,
struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
if (engine->ops && engine->ops->commit)
engine->ops->commit(engine, crtc, state);
}
/**
* 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
*
* Returns: The array of struct drm_plane backing the layers, or an
* error pointer on failure.
*/
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);
}
/**
* sunxi_engine_mode_set - Inform engine of a new mode
* @engine: pointer to the engine
* @mode: new mode
*
* Engine can use this functionality to set specifics once per mode change.
*/
static inline void
sunxi_engine_mode_set(struct sunxi_engine *engine,
const struct drm_display_mode *mode)
{
if (engine->ops && engine->ops->mode_set)
engine->ops->mode_set(engine, mode);
}
#endif /* _SUNXI_ENGINE_H_ */