diff options
author | Dave Airlie <airlied@redhat.com> | 2017-06-16 03:02:35 +0300 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2017-06-16 03:02:35 +0300 |
commit | 7249e3d64ee512521087de802d3f3ad504258535 (patch) | |
tree | 4ca6053e519e6be3d7139d3f92a972009807eab5 /drivers/gpu/drm/sun4i | |
parent | 04d4fb5fa63876d8e7cf67f2788aecfafc6a28a7 (diff) | |
parent | 110d33dd428ea49b9482bcb780fb096dfb4dcd3e (diff) | |
download | linux-7249e3d64ee512521087de802d3f3ad504258535.tar.xz |
Merge tag 'sunxi-drm-for-4.13' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux into drm-next
sun4i-drm changes for 4.13
An unusually big pull request for this merge window, with three notable
features:
- V3s display engine support. This is especially notable because it uses
a different display engine used on the newer Allwinner SoCs (H3, A64
and the likes) that will be quite easily supported now.
- HDMI support for the old Allwinner SoCs. This is enabled only on the
A10s for now, but should be really easy to extend to deal with A10, A20
and A31
- Preliminary work to deal with dual-pipeline SoCs (A10, A20, A31, H3,
etc.). It currently ignores the second pipeline, but we can use the
dual-pipelines bindings. This will be useful to enable the display
pipeline while we work on the dual-pipeline.
* tag 'sunxi-drm-for-4.13' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux: (27 commits)
drm/sun4i: Add compatible for the A10s pipeline
drm/sun4i: Add HDMI support
dt-bindings: display: sun4i: Add allwinner,tcon-channel property
dt-bindings: display: sun4i: Add HDMI display bindings
drm/sun4i: Ignore the generic connectors for components
drm/sun4i: tcon: multiply the vtotal when not in interlace
drm/sun4i: tcon: Change vertical total size computation inconsistency
drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
drm/sun4i: tcon: Switch mux on only for composite
drm/sun4i: tcon: Move the muxing out of the mode set function
drm/sun4i: tcon: Add channel debug
drm/sun4i: tcon: add support for V3s TCON
drm/sun4i: Add compatible string for V3s display engine
drm/sun4i: add support for Allwinner DE2 mixers
drm/sun4i: add a Kconfig option for sun4i-backend
drm/sun4i: abstract a engine type
drm/sun4i: return only planes for layers created
dt-bindings: add bindings for DE2 on V3s SoC
drm/sun4i: backend: Clarify sun4i_backend_layer_enable debug message
drm/sun4i: Set TCON clock inside sun4i_tconX_mode_set
...
Diffstat (limited to 'drivers/gpu/drm/sun4i')
23 files changed, 2164 insertions, 99 deletions
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index a4b357db8856..5bcad8f5fb4f 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -12,3 +12,31 @@ config DRM_SUN4I Choose this option if you have an Allwinner SoC with a Display Engine. If M is selected the module will be called sun4i-drm. + +config DRM_SUN4I_HDMI + tristate "Allwinner A10 HDMI Controller Support" + depends on DRM_SUN4I + default DRM_SUN4I + help + Choose this option if you have an Allwinner SoC with an HDMI + controller. + +config DRM_SUN4I_BACKEND + tristate "Support for Allwinner A10 Display Engine Backend" + depends on DRM_SUN4I + 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_SUN8I_MIXER + tristate "Support for Allwinner Display Engine 2.0 Mixer" + depends on DRM_SUN4I + default MACH_SUN8I + help + Choose this option if you have an Allwinner SoC with the + Allwinner Display Engine 2.0, which has a mixer to do some + graphics mixture and feed graphics to TCON, If M is + selected the module will be called sun8i-mixer. diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 59b757350a1f..e29fd3a2ba9c 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -1,13 +1,23 @@ sun4i-drm-y += sun4i_drv.o sun4i-drm-y += sun4i_framebuffer.o +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o + sun4i-tcon-y += sun4i_tcon.o sun4i-tcon-y += sun4i_rgb.o sun4i-tcon-y += sun4i_dotclock.o sun4i-tcon-y += sun4i_crtc.o -sun4i-tcon-y += sun4i_layer.o + +sun4i-backend-y += sun4i_backend.o sun4i_layer.o + +sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o -obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o + +obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o +obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o +obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index d660741ba475..cf480218daa5 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -19,10 +19,14 @@ #include <drm/drm_plane_helper.h> #include <linux/component.h> +#include <linux/list.h> +#include <linux/of_graph.h> #include <linux/reset.h> #include "sun4i_backend.h" #include "sun4i_drv.h" +#include "sun4i_layer.h" +#include "sunxi_engine.h" static const u32 sunxi_rgb2yuv_coef[12] = { 0x00000107, 0x00000204, 0x00000064, 0x00000108, @@ -30,58 +34,55 @@ static const u32 sunxi_rgb2yuv_coef[12] = { 0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808 }; -void sun4i_backend_apply_color_correction(struct sun4i_backend *backend) +static void sun4i_backend_apply_color_correction(struct sunxi_engine *engine) { int i; DRM_DEBUG_DRIVER("Applying RGB to YUV color correction\n"); /* Set color correction */ - regmap_write(backend->regs, SUN4I_BACKEND_OCCTL_REG, + regmap_write(engine->regs, SUN4I_BACKEND_OCCTL_REG, SUN4I_BACKEND_OCCTL_ENABLE); for (i = 0; i < 12; i++) - regmap_write(backend->regs, SUN4I_BACKEND_OCRCOEF_REG(i), + regmap_write(engine->regs, SUN4I_BACKEND_OCRCOEF_REG(i), sunxi_rgb2yuv_coef[i]); } -EXPORT_SYMBOL(sun4i_backend_apply_color_correction); -void sun4i_backend_disable_color_correction(struct sun4i_backend *backend) +static void sun4i_backend_disable_color_correction(struct sunxi_engine *engine) { DRM_DEBUG_DRIVER("Disabling color correction\n"); /* Disable color correction */ - regmap_update_bits(backend->regs, SUN4I_BACKEND_OCCTL_REG, + regmap_update_bits(engine->regs, SUN4I_BACKEND_OCCTL_REG, SUN4I_BACKEND_OCCTL_ENABLE, 0); } -EXPORT_SYMBOL(sun4i_backend_disable_color_correction); -void sun4i_backend_commit(struct sun4i_backend *backend) +static void sun4i_backend_commit(struct sunxi_engine *engine) { DRM_DEBUG_DRIVER("Committing changes\n"); - regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, + regmap_write(engine->regs, SUN4I_BACKEND_REGBUFFCTL_REG, SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS | SUN4I_BACKEND_REGBUFFCTL_LOADCTL); } -EXPORT_SYMBOL(sun4i_backend_commit); void sun4i_backend_layer_enable(struct sun4i_backend *backend, int layer, bool enable) { u32 val; - DRM_DEBUG_DRIVER("Enabling layer %d\n", layer); + DRM_DEBUG_DRIVER("%sabling layer %d\n", enable ? "En" : "Dis", + layer); if (enable) val = SUN4I_BACKEND_MODCTL_LAY_EN(layer); else val = 0; - regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, SUN4I_BACKEND_MODCTL_LAY_EN(layer), val); } -EXPORT_SYMBOL(sun4i_backend_layer_enable); static int sun4i_backend_drm_format_to_layer(struct drm_plane *plane, u32 format, u32 *mode) @@ -141,33 +142,33 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, if (plane->type == DRM_PLANE_TYPE_PRIMARY) { DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", state->crtc_w, state->crtc_h); - regmap_write(backend->regs, SUN4I_BACKEND_DISSIZE_REG, + regmap_write(backend->engine.regs, SUN4I_BACKEND_DISSIZE_REG, SUN4I_BACKEND_DISSIZE(state->crtc_w, state->crtc_h)); } /* Set the line width */ DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); - regmap_write(backend->regs, SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), + regmap_write(backend->engine.regs, + SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), fb->pitches[0] * 8); /* Set height and width */ DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", state->crtc_w, state->crtc_h); - regmap_write(backend->regs, SUN4I_BACKEND_LAYSIZE_REG(layer), + regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYSIZE_REG(layer), SUN4I_BACKEND_LAYSIZE(state->crtc_w, state->crtc_h)); /* Set base coordinates */ DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", state->crtc_x, state->crtc_y); - regmap_write(backend->regs, SUN4I_BACKEND_LAYCOOR_REG(layer), + regmap_write(backend->engine.regs, SUN4I_BACKEND_LAYCOOR_REG(layer), SUN4I_BACKEND_LAYCOOR(state->crtc_x, state->crtc_y)); return 0; } -EXPORT_SYMBOL(sun4i_backend_update_layer_coord); int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, int layer, struct drm_plane *plane) @@ -182,7 +183,7 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, interlaced = plane->state->crtc->state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE; - regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, SUN4I_BACKEND_MODCTL_ITLMOD_EN, interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0); @@ -196,12 +197,12 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, return ret; } - regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG1(layer), + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_ATTCTL_REG1(layer), SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); return 0; } -EXPORT_SYMBOL(sun4i_backend_update_layer_formats); int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, int layer, struct drm_plane *plane) @@ -229,19 +230,19 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, /* Write the 32 lower bits of the address (in bits) */ lo_paddr = paddr << 3; DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); - regmap_write(backend->regs, SUN4I_BACKEND_LAYFB_L32ADD_REG(layer), + regmap_write(backend->engine.regs, + SUN4I_BACKEND_LAYFB_L32ADD_REG(layer), lo_paddr); /* And the upper bits */ hi_paddr = paddr >> 29; DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr); - regmap_update_bits(backend->regs, SUN4I_BACKEND_LAYFB_H4ADD_REG, + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_LAYFB_H4ADD_REG, SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer), SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr)); return 0; } -EXPORT_SYMBOL(sun4i_backend_update_layer_buffer); static int sun4i_backend_init_sat(struct device *dev) { struct sun4i_backend *backend = dev_get_drvdata(dev); @@ -288,6 +289,52 @@ static int sun4i_backend_free_sat(struct device *dev) { return 0; } +/* + * The display backend can take video output from the display frontend, or + * the display enhancement unit on the A80, as input for one it its layers. + * This relationship within the display pipeline is encoded in the device + * tree with of_graph, and we use it here to figure out which backend, if + * there are 2 or more, we are currently probing. The number would be in + * the "reg" property of the upstream output port endpoint. + */ +static int sun4i_backend_of_get_id(struct device_node *node) +{ + struct device_node *port, *ep; + int ret = -EINVAL; + + /* input is port 0 */ + port = of_graph_get_port_by_id(node, 0); + if (!port) + return -EINVAL; + + /* try finding an upstream endpoint */ + for_each_available_child_of_node(port, ep) { + struct device_node *remote; + u32 reg; + + remote = of_parse_phandle(ep, "remote-endpoint", 0); + if (!remote) + continue; + + ret = of_property_read_u32(remote, "reg", ®); + if (ret) + continue; + + ret = reg; + } + + of_node_put(port); + + return ret; +} + +static const struct sunxi_engine_ops sun4i_backend_engine_ops = { + .commit = sun4i_backend_commit, + .layers_init = sun4i_layers_init, + .apply_color_correction = sun4i_backend_apply_color_correction, + .disable_color_correction = sun4i_backend_disable_color_correction, +}; + static struct regmap_config sun4i_backend_regmap_config = { .reg_bits = 32, .val_bits = 32, @@ -310,18 +357,23 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, if (!backend) return -ENOMEM; dev_set_drvdata(dev, backend); - drv->backend = backend; + + backend->engine.node = dev->of_node; + backend->engine.ops = &sun4i_backend_engine_ops; + backend->engine.id = sun4i_backend_of_get_id(dev->of_node); + if (backend->engine.id < 0) + return backend->engine.id; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(dev, res); if (IS_ERR(regs)) return PTR_ERR(regs); - backend->regs = devm_regmap_init_mmio(dev, regs, - &sun4i_backend_regmap_config); - if (IS_ERR(backend->regs)) { - dev_err(dev, "Couldn't create the backend0 regmap\n"); - return PTR_ERR(backend->regs); + backend->engine.regs = devm_regmap_init_mmio(dev, regs, + &sun4i_backend_regmap_config); + if (IS_ERR(backend->engine.regs)) { + dev_err(dev, "Couldn't create the backend regmap\n"); + return PTR_ERR(backend->engine.regs); } backend->reset = devm_reset_control_get(dev, NULL); @@ -369,16 +421,18 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, } } + list_add_tail(&backend->engine.list, &drv->engine_list); + /* Reset the registers */ for (i = 0x800; i < 0x1000; i += 4) - regmap_write(backend->regs, i, 0); + regmap_write(backend->engine.regs, i, 0); /* Disable registers autoloading */ - regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, + regmap_write(backend->engine.regs, SUN4I_BACKEND_REGBUFFCTL_REG, SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS); /* Enable the backend */ - regmap_write(backend->regs, SUN4I_BACKEND_MODCTL_REG, + regmap_write(backend->engine.regs, SUN4I_BACKEND_MODCTL_REG, SUN4I_BACKEND_MODCTL_DEBE_EN | SUN4I_BACKEND_MODCTL_START_CTL); @@ -400,6 +454,8 @@ static void sun4i_backend_unbind(struct device *dev, struct device *master, { struct sun4i_backend *backend = dev_get_drvdata(dev); + list_del(&backend->engine.list); + if (of_device_is_compatible(dev->of_node, "allwinner,sun8i-a33-display-backend")) sun4i_backend_free_sat(dev); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index 83e63cc702b4..21945af67a9d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -14,9 +14,13 @@ #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) @@ -139,7 +143,7 @@ #define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p))) struct sun4i_backend { - struct regmap *regs; + struct sunxi_engine engine; struct reset_control *reset; @@ -151,10 +155,11 @@ struct sun4i_backend { struct reset_control *sat_reset; }; -void sun4i_backend_apply_color_correction(struct sun4i_backend *backend); -void sun4i_backend_disable_color_correction(struct sun4i_backend *backend); - -void sun4i_backend_commit(struct sun4i_backend *backend); +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); diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index 3c876c3a356a..f8c70439d1e2 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -25,10 +25,9 @@ #include <video/videomode.h> -#include "sun4i_backend.h" #include "sun4i_crtc.h" #include "sun4i_drv.h" -#include "sun4i_layer.h" +#include "sunxi_engine.h" #include "sun4i_tcon.h" static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, @@ -56,7 +55,7 @@ static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, DRM_DEBUG_DRIVER("Committing plane changes\n"); - sun4i_backend_commit(scrtc->backend); + sunxi_engine_commit(scrtc->engine); if (event) { crtc->state->event = NULL; @@ -135,36 +134,37 @@ static const struct drm_crtc_funcs sun4i_crtc_funcs = { }; struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, - struct sun4i_backend *backend, + 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->backend = backend; + scrtc->engine = engine; scrtc->tcon = tcon; /* Create our layers */ - scrtc->layers = sun4i_layers_init(drm, scrtc->backend); - if (IS_ERR(scrtc->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; scrtc->layers[i]; i++) { - struct sun4i_layer *layer = scrtc->layers[i]; + for (i = 0; planes[i]; i++) { + struct drm_plane *plane = planes[i]; - switch (layer->plane.type) { + switch (plane->type) { case DRM_PLANE_TYPE_PRIMARY: - primary = &layer->plane; + primary = plane; break; case DRM_PLANE_TYPE_CURSOR: - cursor = &layer->plane; + cursor = plane; break; default: break; @@ -188,12 +188,12 @@ struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, 1); /* Set possible_crtcs to this crtc for overlay planes */ - for (i = 0; scrtc->layers[i]; i++) { + for (i = 0; planes[i]; i++) { uint32_t possible_crtcs = BIT(drm_crtc_index(&scrtc->crtc)); - struct sun4i_layer *layer = scrtc->layers[i]; + struct drm_plane *plane = planes[i]; - if (layer->plane.type == DRM_PLANE_TYPE_OVERLAY) - layer->plane.possible_crtcs = possible_crtcs; + if (plane->type == DRM_PLANE_TYPE_OVERLAY) + plane->possible_crtcs = possible_crtcs; } return scrtc; diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.h b/drivers/gpu/drm/sun4i/sun4i_crtc.h index 230cb8f0d601..bf0ce36eb518 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.h +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.h @@ -17,9 +17,8 @@ struct sun4i_crtc { struct drm_crtc crtc; struct drm_pending_vblank_event *event; - struct sun4i_backend *backend; + struct sunxi_engine *engine; struct sun4i_tcon *tcon; - struct sun4i_layer **layers; }; static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc) @@ -28,7 +27,7 @@ static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc) } struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, - struct sun4i_backend *backend, + struct sunxi_engine *engine, struct sun4i_tcon *tcon); #endif /* _SUN4I_CRTC_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index c26d5888f8e1..abc7d8fe06b4 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -91,6 +91,8 @@ static int sun4i_drv_bind(struct device *dev) goto free_drm; } drm->dev_private = drv; + INIT_LIST_HEAD(&drv->engine_list); + INIT_LIST_HEAD(&drv->tcon_list); ret = of_reserved_mem_device_init(dev); if (ret && ret != -ENODEV) { @@ -162,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = { .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,sun5i-a13-display-frontend") || @@ -174,7 +181,8 @@ static bool sun4i_drv_node_is_tcon(struct device_node *node) return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || - of_device_is_compatible(node, "allwinner,sun8i-a33-tcon"); + of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") || + of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon"); } static int compare_of(struct device *dev, void *data) @@ -202,6 +210,13 @@ static int sun4i_drv_add_endpoints(struct device *dev, !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 (!sun4i_drv_node_is_frontend(node)) { /* Add current component */ DRM_DEBUG_DRIVER("Adding component %s\n", @@ -288,10 +303,12 @@ static int sun4i_drv_remove(struct platform_device *pdev) } static const struct of_device_id sun4i_drv_of_table[] = { + { .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,sun8i-a33-display-engine" }, + { .compatible = "allwinner,sun8i-v3s-display-engine" }, { } }; MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h index 5df50126ff52..a960c89270cc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.h +++ b/drivers/gpu/drm/sun4i/sun4i_drv.h @@ -14,11 +14,12 @@ #define _SUN4I_DRV_H_ #include <linux/clk.h> +#include <linux/list.h> #include <linux/regmap.h> struct sun4i_drv { - struct sun4i_backend *backend; - struct sun4i_tcon *tcon; + struct list_head engine_list; + struct list_head tcon_list; struct drm_fbdev_cma *fbdev; }; diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h new file mode 100644 index 000000000000..2f2f2ff1ea63 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_HDMI_H_ +#define _SUN4I_HDMI_H_ + +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> + +#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_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_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) + +#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_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_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_FIFO_CTRL_REG 0x510 +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) + +#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c + +#define SUN4I_HDMI_DDC_CMD_REG 0x520 +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6 + +#define SUN4I_HDMI_DDC_CLK_REG 0x528 +#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 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 + +enum sun4i_hdmi_pkt_type { + SUN4I_HDMI_PKT_AVI = 2, + SUN4I_HDMI_PKT_END = 15, +}; + +struct sun4i_hdmi { + struct drm_connector connector; + struct drm_encoder encoder; + struct device *dev; + + void __iomem *base; + + /* Parent clocks */ + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *pll0_clk; + struct clk *pll1_clk; + + /* And the clocks we create */ + struct clk *ddc_clk; + struct clk *tmds_clk; + + struct sun4i_drv *drv; + + bool hdmi_monitor; +}; + +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); +int sun4i_tmds_create(struct sun4i_hdmi *hdmi); + +#endif /* _SUN4I_HDMI_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c new file mode 100644 index 000000000000..4692e8c345ed --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk-provider.h> + +#include "sun4i_tcon.h" +#include "sun4i_hdmi.h" + +struct sun4i_ddc { + struct clk_hw hw; + struct sun4i_hdmi *hdmi; +}; + +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, + u8 *m, u8 *n) +{ + unsigned long best_rate = 0; + u8 best_m = 0, best_n = 0, _m, _n; + + for (_m = 0; _m < 8; _m++) { + for (_n = 0; _n < 8; _n++) { + unsigned long tmp_rate; + + tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1); + + if (tmp_rate > rate) + continue; + + if (abs(rate - tmp_rate) < abs(rate - best_rate)) { + best_rate = tmp_rate; + best_m = _m; + best_n = _n; + } + } + } + + if (m && n) { + *m = best_m; + *n = best_n; + } + + return best_rate; +} + +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL); +} + +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sun4i_ddc *ddc = hw_to_ddc(hw); + u32 reg; + u8 m, n; + + reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); + m = (reg >> 3) & 0x7; + n = reg & 0x7; + + return (((parent_rate / 2) / 10) >> n) / (m + 1); +} + +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, &div_m, &div_n); + + writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n), + ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG); + + return 0; +} + +static const struct clk_ops sun4i_ddc_ops = { + .recalc_rate = sun4i_ddc_recalc_rate, + .round_rate = sun4i_ddc_round_rate, + .set_rate = sun4i_ddc_set_rate, +}; + +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) +{ + struct clk_init_data init; + struct sun4i_ddc *ddc; + const char *parent_name; + + parent_name = __clk_get_name(parent); + if (!parent_name) + return -ENODEV; + + ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL); + if (!ddc) + return -ENOMEM; + + 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; + + hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); + if (IS_ERR(hdmi->ddc_clk)) + return PTR_ERR(hdmi->ddc_clk); + + return 0; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c new file mode 100644 index 000000000000..d3398f6250ef --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_encoder.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/iopoll.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include "sun4i_backend.h" +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_hdmi.h" +#include "sun4i_tcon.h" + +#define DDC_SEGMENT_ADDR 0x30 + +static inline struct sun4i_hdmi * +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_hdmi, + encoder); +} + +static inline struct sun4i_hdmi * +drm_connector_to_sun4i_hdmi(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_hdmi, + connector); +} + +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi, + struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; + u8 buffer[17]; + int i, ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); + if (ret < 0) { + DRM_ERROR("Failed to get infoframes from mode\n"); + return ret; + } + + ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (ret < 0) { + DRM_ERROR("Failed to pack infoframes\n"); + return ret; + } + + for (i = 0; i < sizeof(buffer); i++) + writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i)); + + return 0; +} + +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *mode = &crtc_state->mode; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return -EINVAL; + + return 0; +} + +static void sun4i_hdmi_disable(struct drm_encoder *encoder) +{ + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + struct sun4i_tcon *tcon = crtc->tcon; + 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); + + sun4i_tcon_channel_disable(tcon, 1); +} + +static void sun4i_hdmi_enable(struct drm_encoder *encoder) +{ + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + struct sun4i_tcon *tcon = crtc->tcon; + u32 val = 0; + + DRM_DEBUG_DRIVER("Enabling the HDMI Output\n"); + + sun4i_tcon_channel_enable(tcon, 1); + + sun4i_hdmi_setup_avi_infoframes(hdmi, mode); + val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); + val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); + writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0)); + + val = SUN4I_HDMI_VID_CTRL_ENABLE; + if (hdmi->hdmi_monitor) + val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE; + + writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); +} + +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); + struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); + struct sun4i_tcon *tcon = crtc->tcon; + unsigned int x, y; + u32 val; + + sun4i_tcon1_mode_set(tcon, mode); + sun4i_tcon_set_mux(tcon, 1, encoder); + + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); + clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000); + clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000); + + /* Set input sync enable */ + writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC, + hdmi->base + SUN4I_HDMI_UNKNOWN_REG); + + /* Setup timing registers */ + writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) | + SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay), + hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG); + + x = mode->htotal - mode->hsync_start; + y = mode->vtotal - mode->vsync_start; + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), + hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG); + + x = mode->hsync_start - mode->hdisplay; + y = mode->vsync_start - mode->vdisplay; + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), + hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG); + + x = mode->hsync_end - mode->hsync_start; + y = mode->vsync_end - mode->vsync_start; + writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y), + hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG); + + val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC; + + writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG); +} + +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = { + .atomic_check = sun4i_hdmi_atomic_check, + .disable = sun4i_hdmi_disable, + .enable = sun4i_hdmi_enable, + .mode_set = sun4i_hdmi_mode_set, +}; + +static const struct drm_encoder_funcs sun4i_hdmi_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi, + unsigned int blk, unsigned int offset, + u8 *buf, unsigned int count) +{ + unsigned long reg; + int i; + + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; + writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ, + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + + writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) | + SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) | + SUN4I_HDMI_DDC_ADDR_OFFSET(offset) | + SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR), + hdmi->base + SUN4I_HDMI_DDC_ADDR_REG); + + reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); + writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR, + hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG); + + writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG); + writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ, + hdmi->base + SUN4I_HDMI_DDC_CMD_REG); + + reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD, + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, + !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD), + 100, 100000)) + return -EIO; + + for (i = 0; i < count; i++) + buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG); + + return 0; +} + +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk, + size_t length) +{ + struct sun4i_hdmi *hdmi = data; + int retry = 2, i; + + do { + for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) { + unsigned char offset = blk * EDID_LENGTH + i; + unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE, + length - i); + int ret; + + ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset, + buf + i, count); + if (ret) + return ret; + } + } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--)); + + return 0; +} + +static int sun4i_hdmi_get_modes(struct drm_connector *connector) +{ + struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); + unsigned long reg; + struct edid *edid; + int ret; + + /* Reset i2c controller */ + writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET, + hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg, + !(reg & SUN4I_HDMI_DDC_CTRL_RESET), + 100, 2000)) + return -EIO; + + writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE | + SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE, + hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG); + + clk_prepare_enable(hdmi->ddc_clk); + clk_set_rate(hdmi->ddc_clk, 100000); + + edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi); + if (!edid) + return 0; + + hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid); + DRM_DEBUG_DRIVER("Monitor is %s monitor\n", + hdmi->hdmi_monitor ? "an HDMI" : "a DVI"); + + drm_mode_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + clk_disable_unprepare(hdmi->ddc_clk); + + return ret; +} + +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = { + .get_modes = sun4i_hdmi_get_modes, +}; + +static enum drm_connector_status +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector); + unsigned long reg; + + if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg, + reg & SUN4I_HDMI_HPD_HIGH, + 0, 500000)) + return connector_status_disconnected; + + return connector_status_connected; +} + +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = sun4i_hdmi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int sun4i_hdmi_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_hdmi *hdmi; + struct resource *res; + u32 reg; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + dev_set_drvdata(dev, hdmi); + hdmi->dev = dev; + hdmi->drv = drv; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdmi->base)) { + dev_err(dev, "Couldn't map the HDMI encoder registers\n"); + return PTR_ERR(hdmi->base); + } + + 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"); + return PTR_ERR(hdmi->bus_clk); + } + 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"); + return PTR_ERR(hdmi->mod_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"); + return PTR_ERR(hdmi->pll0_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"); + return PTR_ERR(hdmi->pll1_clk); + } + + ret = sun4i_tmds_create(hdmi); + if (ret) { + dev_err(dev, "Couldn't create the TMDS clock\n"); + return ret; + } + + writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG); + + writel(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, + hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG); + + /* + * 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. + */ + reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + reg &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK; + reg |= 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; + writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG); + + reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); + reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK; + reg |= 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; + writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); + + ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk); + if (ret) { + dev_err(dev, "Couldn't create the DDC clock\n"); + return ret; + } + + drm_encoder_helper_add(&hdmi->encoder, + &sun4i_hdmi_helper_funcs); + ret = drm_encoder_init(drm, + &hdmi->encoder, + &sun4i_hdmi_funcs, + DRM_MODE_ENCODER_TMDS, + NULL); + if (ret) { + dev_err(dev, "Couldn't initialise the HDMI encoder\n"); + return ret; + } + + hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, + dev->of_node); + if (!hdmi->encoder.possible_crtcs) + return -EPROBE_DEFER; + + drm_connector_helper_add(&hdmi->connector, + &sun4i_hdmi_connector_helper_funcs); + ret = drm_connector_init(drm, &hdmi->connector, + &sun4i_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + dev_err(dev, + "Couldn't initialise the HDMI connector\n"); + goto err_cleanup_connector; + } + + /* There is no HPD interrupt, so we need to poll the controller */ + hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder); + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&hdmi->encoder); + return ret; +} + +static void sun4i_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); + + drm_connector_cleanup(&hdmi->connector); + drm_encoder_cleanup(&hdmi->encoder); +} + +static const struct component_ops sun4i_hdmi_ops = { + .bind = sun4i_hdmi_bind, + .unbind = sun4i_hdmi_unbind, +}; + +static int sun4i_hdmi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_hdmi_ops); +} + +static int sun4i_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_hdmi_ops); + + return 0; +} + +static const struct of_device_id sun4i_hdmi_of_table[] = { + { .compatible = "allwinner,sun5i-a10s-hdmi" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table); + +static struct platform_driver sun4i_hdmi_driver = { + .probe = sun4i_hdmi_probe, + .remove = sun4i_hdmi_remove, + .driver = { + .name = "sun4i-hdmi", + .of_match_table = sun4i_hdmi_of_table, + }, +}; +module_platform_driver(sun4i_hdmi_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c new file mode 100644 index 000000000000..5cf2527bffc8 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk-provider.h> + +#include "sun4i_tcon.h" +#include "sun4i_hdmi.h" + +struct sun4i_tmds { + struct clk_hw hw; + struct sun4i_hdmi *hdmi; +}; + +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, + bool *half) +{ + unsigned long best_rate = 0; + u8 best_m = 0, m; + bool is_double; + + for (m = 1; m < 16; 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; + } + } + } + + 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 clk_hw *parent; + unsigned long best_parent = 0; + unsigned long rate = req->rate; + int best_div = 1, best_half = 1; + int i, j; + + /* + * 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. + */ + + parent = clk_hw_get_parent_by_index(hw, 0); + if (!parent) + return -EINVAL; + + for (i = 1; i < 3; i++) { + for (j = 1; j < 16; 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 (abs(rate - rounded / i) < + abs(rate - best_parent / best_div)) { + best_parent = rounded; + best_div = i; + } + } + } + +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; + 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, &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->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; + + hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw); + if (IS_ERR(hdmi->tmds_clk)) + return PTR_ERR(hdmi->tmds_clk); + + return 0; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c index f26bde5b9117..ead4f9d4c1ee 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.c +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -11,12 +11,12 @@ */ #include <drm/drm_atomic_helper.h> -#include <drm/drm_crtc.h> #include <drm/drm_plane_helper.h> #include <drm/drmP.h> #include "sun4i_backend.h" #include "sun4i_layer.h" +#include "sunxi_engine.h" struct sun4i_plane_desc { enum drm_plane_type type; @@ -128,15 +128,16 @@ static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, return layer; } -struct sun4i_layer **sun4i_layers_init(struct drm_device *drm, - struct sun4i_backend *backend) +struct drm_plane **sun4i_layers_init(struct drm_device *drm, + struct sunxi_engine *engine) { - struct sun4i_layer **layers; + struct drm_plane **planes; + struct sun4i_backend *backend = engine_to_sun4i_backend(engine); int i; - layers = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1, - sizeof(*layers), GFP_KERNEL); - if (!layers) + planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1, + sizeof(*planes), GFP_KERNEL); + if (!planes) return ERR_PTR(-ENOMEM); /* @@ -173,13 +174,13 @@ struct sun4i_layer **sun4i_layers_init(struct drm_device *drm, DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", i ? "overlay" : "primary", plane->pipe); - regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG0(i), + regmap_update_bits(engine->regs, SUN4I_BACKEND_ATTCTL_REG0(i), SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK, SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(plane->pipe)); layer->id = i; - layers[i] = layer; + planes[i] = &layer->plane; }; - return layers; + return planes; } diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h index 4be1f0919df2..4e84f438b346 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.h +++ b/drivers/gpu/drm/sun4i/sun4i_layer.h @@ -13,6 +13,8 @@ #ifndef _SUN4I_LAYER_H_ #define _SUN4I_LAYER_H_ +struct sunxi_engine; + struct sun4i_layer { struct drm_plane plane; struct sun4i_drv *drv; @@ -26,7 +28,7 @@ plane_to_sun4i_layer(struct drm_plane *plane) return container_of(plane, struct sun4i_layer, plane); } -struct sun4i_layer **sun4i_layers_init(struct drm_device *drm, - struct sun4i_backend *backend); +struct drm_plane **sun4i_layers_init(struct drm_device *drm, + struct sunxi_engine *engine); #endif /* _SUN4I_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index 67f0b91a99de..422b191faa77 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -175,8 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, struct sun4i_tcon *tcon = rgb->tcon; sun4i_tcon0_mode_set(tcon, mode); - - clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + sun4i_tcon_set_mux(tcon, 0, encoder); /* FIXME: This seems to be board specific */ clk_set_phase(tcon->dclk, 120); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 9a83a85529ac..d9791292553e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -30,6 +30,7 @@ #include "sun4i_drv.h" #include "sun4i_rgb.h" #include "sun4i_tcon.h" +#include "sunxi_engine.h" void sun4i_tcon_disable(struct sun4i_tcon *tcon) { @@ -54,6 +55,8 @@ EXPORT_SYMBOL(sun4i_tcon_enable); void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) { + DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel); + /* Disable the TCON's channel */ if (channel == 0) { regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, @@ -71,6 +74,8 @@ EXPORT_SYMBOL(sun4i_tcon_channel_disable); void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) { + DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel); + /* Enable the TCON's channel */ if (channel == 0) { regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, @@ -104,6 +109,29 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) } EXPORT_SYMBOL(sun4i_tcon_enable_vblank); +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, + struct drm_encoder *encoder) +{ + u32 val; + + if (!tcon->quirks->has_unknown_mux) + return; + + if (channel != 1) + return; + + if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) + val = 1; + else + val = 0; + + /* + * FIXME: Undocumented bits + */ + regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); +} +EXPORT_SYMBOL(sun4i_tcon_set_mux); + static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, int channel) { @@ -129,6 +157,9 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, u8 clk_delay; u32 val = 0; + /* Configure the dot clock */ + clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + /* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 0); regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, @@ -163,7 +194,7 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, /* Set vertical display timings */ regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, - SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) | + SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); /* Set Hsync and Vsync length */ @@ -198,12 +229,15 @@ EXPORT_SYMBOL(sun4i_tcon0_mode_set); void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, struct drm_display_mode *mode) { - unsigned int bp, hsync, vsync; + unsigned int bp, hsync, vsync, vtotal; u8 clk_delay; u32 val; WARN_ON(!tcon->quirks->has_channel_1); + /* Configure the dot clock */ + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); + /* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 1); regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, @@ -235,19 +269,37 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); /* Set horizontal display timings */ - bp = mode->crtc_htotal - mode->crtc_hsync_end; + bp = mode->crtc_htotal - mode->crtc_hsync_start; DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", mode->htotal, bp); regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); - /* Set vertical display timings */ - bp = mode->crtc_vtotal - mode->crtc_vsync_end; + bp = mode->crtc_vtotal - mode->crtc_vsync_start; DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", - mode->vtotal, bp); + mode->crtc_vtotal, bp); + + /* + * The vertical resolution needs to be doubled in all + * cases. We could use crtc_vtotal and always multiply by two, + * but that leads to a rounding error in interlace when vtotal + * is odd. + * + * This happens with TV's PAL for example, where vtotal will + * be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be + * 624, which apparently confuses the hardware. + * + * To work around this, we will always use vtotal, and + * multiply by two only if we're not in interlace. + */ + vtotal = mode->vtotal; + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) + vtotal = vtotal * 2; + + /* Set vertical display timings */ regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, - SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) | + SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) | SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); /* Set Hsync and Vsync length */ @@ -262,12 +314,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, SUN4I_TCON_GCTL_IOMAP_MASK, SUN4I_TCON_GCTL_IOMAP_TCON1); - - /* - * FIXME: Undocumented bits - */ - if (tcon->quirks->has_unknown_mux) - regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1); } EXPORT_SYMBOL(sun4i_tcon1_mode_set); @@ -402,21 +448,79 @@ static int sun4i_tcon_init_regmap(struct device *dev, return 0; } +/* + * On SoCs with the old display pipeline design (Display Engine 1.0), + * the TCON is always tied to just one backend. Hence we can traverse + * the of_graph upwards to find the backend our tcon is connected to, + * and take its ID as our own. + * + * We can either identify backends from their compatible strings, which + * means maintaining a large list of them. Or, since the backend is + * registered and binded before the TCON, we can just go through the + * list of registered backends and compare the device node. + * + * As the structures now store engines instead of backends, here this + * function in fact searches the corresponding engine, and the ID is + * requested via the get_id function of the engine. + */ +static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, + struct device_node *node) +{ + struct device_node *port, *ep, *remote; + struct sunxi_engine *engine; + + port = of_graph_get_port_by_id(node, 0); + if (!port) + return ERR_PTR(-EINVAL); + + for_each_available_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote) + continue; + + /* does this node match any registered engines? */ + list_for_each_entry(engine, &drv->engine_list, list) { + if (remote == engine->node) { + of_node_put(remote); + of_node_put(port); + return engine; + } + } + + /* keep looking through upstream ports */ + engine = sun4i_tcon_find_engine(drv, remote); + if (!IS_ERR(engine)) { + of_node_put(remote); + of_node_put(port); + return engine; + } + } + + return ERR_PTR(-EINVAL); +} + static int sun4i_tcon_bind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = data; struct sun4i_drv *drv = drm->dev_private; + struct sunxi_engine *engine; struct sun4i_tcon *tcon; int ret; + engine = sun4i_tcon_find_engine(drv, dev->of_node); + if (IS_ERR(engine)) { + dev_err(dev, "Couldn't find matching engine\n"); + return -EPROBE_DEFER; + } + tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); if (!tcon) return -ENOMEM; dev_set_drvdata(dev, tcon); - drv->tcon = tcon; tcon->drm = drm; tcon->dev = dev; + tcon->id = engine->id; tcon->quirks = of_device_get_match_data(dev); tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); @@ -459,7 +563,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, goto err_free_dotclock; } - tcon->crtc = sun4i_crtc_init(drm, drv->backend, tcon); + tcon->crtc = sun4i_crtc_init(drm, engine, tcon); if (IS_ERR(tcon->crtc)) { dev_err(dev, "Couldn't create our CRTC\n"); ret = PTR_ERR(tcon->crtc); @@ -470,6 +574,8 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, if (ret < 0) goto err_free_clocks; + list_add_tail(&tcon->list, &drv->tcon_list); + return 0; err_free_dotclock: @@ -486,6 +592,7 @@ static void sun4i_tcon_unbind(struct device *dev, struct device *master, { struct sun4i_tcon *tcon = dev_get_drvdata(dev); + list_del(&tcon->list); sun4i_dclk_free(tcon); sun4i_tcon_free_clocks(tcon); } @@ -533,11 +640,16 @@ static const struct sun4i_tcon_quirks sun8i_a33_quirks = { /* nothing is supported */ }; +static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { + /* nothing is supported */ +}; + static const struct of_device_id sun4i_tcon_of_table[] = { { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, + { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, { } }; MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index f636343a935d..e3c50ecdcd04 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -17,6 +17,7 @@ #include <drm/drm_crtc.h> #include <linux/kernel.h> +#include <linux/list.h> #include <linux/reset.h> #define SUN4I_TCON_GCTL_REG 0x0 @@ -51,7 +52,7 @@ #define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) #define SUN4I_TCON0_BASIC2_REG 0x50 -#define SUN4I_TCON0_BASIC2_V_TOTAL(total) ((((total) * 2) & 0x1fff) << 16) +#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 @@ -172,6 +173,11 @@ struct sun4i_tcon { /* 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); @@ -190,6 +196,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); /* Mode Related Controls */ void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon, bool enable); +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, + struct drm_encoder *encoder); void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, struct drm_display_mode *mode); void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 49c49431a053..338b9e5bb2a3 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -22,10 +22,10 @@ #include <drm/drm_of.h> #include <drm/drm_panel.h> -#include "sun4i_backend.h" #include "sun4i_crtc.h" #include "sun4i_drv.h" #include "sun4i_tcon.h" +#include "sunxi_engine.h" #define SUN4I_TVE_EN_REG 0x000 #define SUN4I_TVE_EN_DAC_MAP_MASK GENMASK(19, 4) @@ -353,7 +353,6 @@ static void sun4i_tv_disable(struct drm_encoder *encoder) struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); struct sun4i_tcon *tcon = crtc->tcon; - struct sun4i_backend *backend = crtc->backend; DRM_DEBUG_DRIVER("Disabling the TV Output\n"); @@ -362,7 +361,8 @@ static void sun4i_tv_disable(struct drm_encoder *encoder) regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_ENABLE, 0); - sun4i_backend_disable_color_correction(backend); + + sunxi_engine_disable_color_correction(crtc->engine); } static void sun4i_tv_enable(struct drm_encoder *encoder) @@ -370,11 +370,10 @@ static void sun4i_tv_enable(struct drm_encoder *encoder) struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); struct sun4i_tcon *tcon = crtc->tcon; - struct sun4i_backend *backend = crtc->backend; DRM_DEBUG_DRIVER("Enabling the TV Output\n"); - sun4i_backend_apply_color_correction(backend); + sunxi_engine_apply_color_correction(crtc->engine); regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_ENABLE, @@ -393,6 +392,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); sun4i_tcon1_mode_set(tcon, mode); + sun4i_tcon_set_mux(tcon, 1, encoder); /* Enable and map the DAC to the output */ regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, @@ -486,8 +486,6 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, SUN4I_TVE_RESYNC_FIELD : 0)); regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0); - - clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); } static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = { diff --git a/drivers/gpu/drm/sun4i/sun8i_layer.c b/drivers/gpu/drm/sun4i/sun8i_layer.c new file mode 100644 index 000000000000..e627eeece658 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_layer.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) Icenowy Zheng <icenowy@aosc.io> + * + * Based on sun4i_layer.h, which is: + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "sun8i_layer.h" +#include "sun8i_mixer.h" + +struct sun8i_plane_desc { + enum drm_plane_type type; + const uint32_t *formats; + uint32_t nformats; +}; + +static void sun8i_mixer_layer_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun8i_layer *layer = plane_to_sun8i_layer(plane); + struct sun8i_mixer *mixer = layer->mixer; + + sun8i_mixer_layer_enable(mixer, layer->id, false); +} + +static void sun8i_mixer_layer_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun8i_layer *layer = plane_to_sun8i_layer(plane); + struct sun8i_mixer *mixer = layer->mixer; + + sun8i_mixer_update_layer_coord(mixer, layer->id, plane); + sun8i_mixer_update_layer_formats(mixer, layer->id, plane); + sun8i_mixer_update_layer_buffer(mixer, layer->id, plane); + sun8i_mixer_layer_enable(mixer, layer->id, true); +} + +static struct drm_plane_helper_funcs sun8i_mixer_layer_helper_funcs = { + .atomic_disable = sun8i_mixer_layer_atomic_disable, + .atomic_update = sun8i_mixer_layer_atomic_update, +}; + +static const struct drm_plane_funcs sun8i_mixer_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 uint32_t sun8i_mixer_layer_formats[] = { + DRM_FORMAT_RGB888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, +}; + +static const struct sun8i_plane_desc sun8i_mixer_planes[] = { + { + .type = DRM_PLANE_TYPE_PRIMARY, + .formats = sun8i_mixer_layer_formats, + .nformats = ARRAY_SIZE(sun8i_mixer_layer_formats), + }, +}; + +static struct sun8i_layer *sun8i_layer_init_one(struct drm_device *drm, + struct sun8i_mixer *mixer, + const struct sun8i_plane_desc *plane) +{ + struct sun8i_layer *layer; + int ret; + + layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); + if (!layer) + return ERR_PTR(-ENOMEM); + + /* possible crtcs are set later */ + ret = drm_universal_plane_init(drm, &layer->plane, 0, + &sun8i_mixer_layer_funcs, + plane->formats, plane->nformats, + plane->type, NULL); + if (ret) { + dev_err(drm->dev, "Couldn't initialize layer\n"); + return ERR_PTR(ret); + } + + drm_plane_helper_add(&layer->plane, + &sun8i_mixer_layer_helper_funcs); + layer->mixer = mixer; + + return layer; +} + +struct drm_plane **sun8i_layers_init(struct drm_device *drm, + struct sunxi_engine *engine) +{ + struct drm_plane **planes; + struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine); + int i; + + planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun8i_mixer_planes) + 1, + sizeof(*planes), GFP_KERNEL); + if (!planes) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ARRAY_SIZE(sun8i_mixer_planes); i++) { + const struct sun8i_plane_desc *plane = &sun8i_mixer_planes[i]; + struct sun8i_layer *layer; + + layer = sun8i_layer_init_one(drm, mixer, plane); + if (IS_ERR(layer)) { + dev_err(drm->dev, "Couldn't initialize %s plane\n", + i ? "overlay" : "primary"); + return ERR_CAST(layer); + }; + + layer->id = i; + planes[i] = &layer->plane; + }; + + return planes; +} diff --git a/drivers/gpu/drm/sun4i/sun8i_layer.h b/drivers/gpu/drm/sun4i/sun8i_layer.h new file mode 100644 index 000000000000..e5eccd27cff0 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_layer.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) Icenowy Zheng <icenowy@aosc.io> + * + * Based on sun4i_layer.h, which is: + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN8I_LAYER_H_ +#define _SUN8I_LAYER_H_ + +struct sunxi_engine; + +struct sun8i_layer { + struct drm_plane plane; + struct sun4i_drv *drv; + struct sun8i_mixer *mixer; + int id; +}; + +static inline struct sun8i_layer * +plane_to_sun8i_layer(struct drm_plane *plane) +{ + return container_of(plane, struct sun8i_layer, plane); +} + +struct drm_plane **sun8i_layers_init(struct drm_device *drm, + struct sunxi_engine *engine); +#endif /* _SUN8I_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c new file mode 100644 index 000000000000..cb193c5f1686 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> + * + * Based on sun4i_backend.c, which is: + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> + +#include <linux/component.h> +#include <linux/dma-mapping.h> +#include <linux/reset.h> +#include <linux/of_device.h> + +#include "sun4i_drv.h" +#include "sun8i_mixer.h" +#include "sun8i_layer.h" +#include "sunxi_engine.h" + +static void sun8i_mixer_commit(struct sunxi_engine *engine) +{ + DRM_DEBUG_DRIVER("Committing changes\n"); + + regmap_write(engine->regs, SUN8I_MIXER_GLOBAL_DBUFF, + SUN8I_MIXER_GLOBAL_DBUFF_ENABLE); +} + +void sun8i_mixer_layer_enable(struct sun8i_mixer *mixer, + int layer, bool enable) +{ + u32 val; + /* Currently the first UI channel is used */ + int chan = mixer->cfg->vi_num; + + DRM_DEBUG_DRIVER("Enabling layer %d in channel %d\n", layer, chan); + + if (enable) + val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; + else + val = 0; + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN, val); + + /* Set the alpha configuration */ + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_DEF); + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_DEF); +} + +static int sun8i_mixer_drm_format_to_layer(struct drm_plane *plane, + u32 format, u32 *mode) +{ + switch (format) { + case DRM_FORMAT_ARGB8888: + *mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_ARGB8888; + break; + + case DRM_FORMAT_XRGB8888: + *mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_XRGB8888; + break; + + case DRM_FORMAT_RGB888: + *mode = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_RGB888; + break; + + default: + return -EINVAL; + } + + return 0; +} + +int sun8i_mixer_update_layer_coord(struct sun8i_mixer *mixer, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + /* Currently the first UI channel is used */ + int chan = mixer->cfg->vi_num; + + DRM_DEBUG_DRIVER("Updating layer %d\n", layer); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_SIZE, + SUN8I_MIXER_SIZE(state->crtc_w, + state->crtc_h)); + DRM_DEBUG_DRIVER("Updating blender size\n"); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_BLEND_ATTR_INSIZE(0), + SUN8I_MIXER_SIZE(state->crtc_w, + state->crtc_h)); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_OUTSIZE, + SUN8I_MIXER_SIZE(state->crtc_w, + state->crtc_h)); + DRM_DEBUG_DRIVER("Updating channel size\n"); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_OVL_SIZE(chan), + SUN8I_MIXER_SIZE(state->crtc_w, + state->crtc_h)); + } + + /* Set the line width */ + DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_PITCH(chan, layer), + fb->pitches[0]); + + /* Set height and width */ + DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_SIZE(chan, layer), + SUN8I_MIXER_SIZE(state->crtc_w, state->crtc_h)); + + /* Set base coordinates */ + DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", + state->crtc_x, state->crtc_y); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_COORD(chan, layer), + SUN8I_MIXER_COORD(state->crtc_x, state->crtc_y)); + + return 0; +} + +int sun8i_mixer_update_layer_formats(struct sun8i_mixer *mixer, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + bool interlaced = false; + u32 val; + /* Currently the first UI channel is used */ + int chan = mixer->cfg->vi_num; + int ret; + + if (plane->state->crtc) + interlaced = plane->state->crtc->state->adjusted_mode.flags + & DRM_MODE_FLAG_INTERLACE; + + regmap_update_bits(mixer->engine.regs, SUN8I_MIXER_BLEND_OUTCTL, + SUN8I_MIXER_BLEND_OUTCTL_INTERLACED, + interlaced ? + SUN8I_MIXER_BLEND_OUTCTL_INTERLACED : 0); + + DRM_DEBUG_DRIVER("Switching display mixer interlaced mode %s\n", + interlaced ? "on" : "off"); + + ret = sun8i_mixer_drm_format_to_layer(plane, fb->format->format, + &val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid format\n"); + return ret; + } + + regmap_update_bits(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(chan, layer), + SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val); + + return 0; +} + +int sun8i_mixer_update_layer_buffer(struct sun8i_mixer *mixer, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *gem; + dma_addr_t paddr; + /* Currently the first UI channel is used */ + int chan = mixer->cfg->vi_num; + int bpp; + + /* Get the physical address of the buffer in memory */ + gem = drm_fb_cma_get_gem_obj(fb, 0); + + DRM_DEBUG_DRIVER("Using GEM @ %pad\n", &gem->paddr); + + /* Compute the start of the displayed memory */ + bpp = fb->format->cpp[0]; + paddr = gem->paddr + fb->offsets[0]; + + /* Fixup framebuffer address for src coordinates */ + paddr += (state->src_x >> 16) * bpp; + paddr += (state->src_y >> 16) * fb->pitches[0]; + + /* + * The hardware cannot correctly deal with negative crtc + * coordinates, the display is cropped to the requested size, + * but the display content is not moved. + * Manually move the display content by fixup the framebuffer + * address when crtc_x or crtc_y is negative, like what we + * have did for src_x and src_y. + */ + if (state->crtc_x < 0) + paddr += -state->crtc_x * bpp; + if (state->crtc_y < 0) + paddr += -state->crtc_y * fb->pitches[0]; + + DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); + + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(chan, layer), + lower_32_bits(paddr)); + + return 0; +} + +static const struct sunxi_engine_ops sun8i_engine_ops = { + .commit = sun8i_mixer_commit, + .layers_init = sun8i_layers_init, +}; + +static struct regmap_config sun8i_mixer_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0xbfffc, /* guessed */ +}; + +static int sun8i_mixer_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun8i_mixer *mixer; + struct resource *res; + void __iomem *regs; + int 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; + /* The ID of the mixer currently doesn't matter */ + mixer->engine.id = -1; + + mixer->cfg = of_device_get_match_data(dev); + if (!mixer->cfg) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + mixer->engine.regs = devm_regmap_init_mmio(dev, regs, + &sun8i_mixer_regmap_config); + if (IS_ERR(mixer->engine.regs)) { + dev_err(dev, "Couldn't create the mixer regmap\n"); + return PTR_ERR(mixer->engine.regs); + } + + mixer->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(mixer->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(mixer->reset); + } + + ret = reset_control_deassert(mixer->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + mixer->bus_clk = devm_clk_get(dev, "bus"); + if (IS_ERR(mixer->bus_clk)) { + dev_err(dev, "Couldn't get the mixer bus clock\n"); + ret = PTR_ERR(mixer->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(mixer->bus_clk); + + mixer->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(mixer->mod_clk)) { + dev_err(dev, "Couldn't get the mixer module clock\n"); + ret = PTR_ERR(mixer->mod_clk); + goto err_disable_bus_clk; + } + clk_prepare_enable(mixer->mod_clk); + + list_add_tail(&mixer->engine.list, &drv->engine_list); + + /* Reset the registers */ + for (i = 0x0; i < 0x20000; i += 4) + regmap_write(mixer->engine.regs, i, 0); + + /* Enable the mixer */ + regmap_write(mixer->engine.regs, SUN8I_MIXER_GLOBAL_CTL, + SUN8I_MIXER_GLOBAL_CTL_RT_EN); + + /* Initialize blender */ + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_FCOLOR_CTL, + SUN8I_MIXER_BLEND_FCOLOR_CTL_DEF); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_PREMULTIPLY, + SUN8I_MIXER_BLEND_PREMULTIPLY_DEF); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_BKCOLOR, + SUN8I_MIXER_BLEND_BKCOLOR_DEF); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_MODE(0), + SUN8I_MIXER_BLEND_MODE_DEF); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_CK_CTL, + SUN8I_MIXER_BLEND_CK_CTL_DEF); + + regmap_write(mixer->engine.regs, + SUN8I_MIXER_BLEND_ATTR_FCOLOR(0), + SUN8I_MIXER_BLEND_ATTR_FCOLOR_DEF); + + /* Select the first UI channel */ + DRM_DEBUG_DRIVER("Selecting channel %d (first UI channel)\n", + mixer->cfg->vi_num); + regmap_write(mixer->engine.regs, SUN8I_MIXER_BLEND_ROUTE, + mixer->cfg->vi_num); + + return 0; + +err_disable_bus_clk: + clk_disable_unprepare(mixer->bus_clk); +err_assert_reset: + reset_control_assert(mixer->reset); + return ret; +} + +static void sun8i_mixer_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun8i_mixer *mixer = dev_get_drvdata(dev); + + list_del(&mixer->engine.list); + + clk_disable_unprepare(mixer->mod_clk); + clk_disable_unprepare(mixer->bus_clk); + reset_control_assert(mixer->reset); +} + +static const struct component_ops sun8i_mixer_ops = { + .bind = sun8i_mixer_bind, + .unbind = sun8i_mixer_unbind, +}; + +static int sun8i_mixer_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun8i_mixer_ops); +} + +static int sun8i_mixer_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun8i_mixer_ops); + + return 0; +} + +static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { + .vi_num = 2, + .ui_num = 1, +}; + +static const struct of_device_id sun8i_mixer_of_table[] = { + { + .compatible = "allwinner,sun8i-v3s-de2-mixer", + .data = &sun8i_v3s_mixer_cfg, + }, + { } +}; +MODULE_DEVICE_TABLE(of, sun8i_mixer_of_table); + +static struct platform_driver sun8i_mixer_platform_driver = { + .probe = sun8i_mixer_probe, + .remove = sun8i_mixer_remove, + .driver = { + .name = "sun8i-mixer", + .of_match_table = sun8i_mixer_of_table, + }, +}; +module_platform_driver(sun8i_mixer_platform_driver); + +MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>"); +MODULE_DESCRIPTION("Allwinner DE2 Mixer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h new file mode 100644 index 000000000000..4785ac090b8c --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN8I_MIXER_H_ +#define _SUN8I_MIXER_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sunxi_engine.h" + +#define SUN8I_MIXER_MAX_CHAN_COUNT 4 + +#define SUN8I_MIXER_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1)) +#define SUN8I_MIXER_COORD(x, y) ((y) << 16 | (x)) + +#define SUN8I_MIXER_GLOBAL_CTL 0x0 +#define SUN8I_MIXER_GLOBAL_STATUS 0x4 +#define SUN8I_MIXER_GLOBAL_DBUFF 0x8 +#define SUN8I_MIXER_GLOBAL_SIZE 0xc + +#define SUN8I_MIXER_GLOBAL_CTL_RT_EN 0x1 + +#define SUN8I_MIXER_GLOBAL_DBUFF_ENABLE 0x1 + +#define SUN8I_MIXER_BLEND_FCOLOR_CTL 0x1000 +#define SUN8I_MIXER_BLEND_ATTR_FCOLOR(x) (0x1004 + 0x10 * (x) + 0x0) +#define SUN8I_MIXER_BLEND_ATTR_INSIZE(x) (0x1004 + 0x10 * (x) + 0x4) +#define SUN8I_MIXER_BLEND_ATTR_OFFSET(x) (0x1004 + 0x10 * (x) + 0x8) +#define SUN8I_MIXER_BLEND_ROUTE 0x1080 +#define SUN8I_MIXER_BLEND_PREMULTIPLY 0x1084 +#define SUN8I_MIXER_BLEND_BKCOLOR 0x1088 +#define SUN8I_MIXER_BLEND_OUTSIZE 0x108c +#define SUN8I_MIXER_BLEND_MODE(x) (0x1090 + 0x04 * (x)) +#define SUN8I_MIXER_BLEND_CK_CTL 0x10b0 +#define SUN8I_MIXER_BLEND_CK_CFG 0x10b4 +#define SUN8I_MIXER_BLEND_CK_MAX(x) (0x10c0 + 0x04 * (x)) +#define SUN8I_MIXER_BLEND_CK_MIN(x) (0x10e0 + 0x04 * (x)) +#define SUN8I_MIXER_BLEND_OUTCTL 0x10fc + +/* The following numbers are some still unknown magic numbers */ +#define SUN8I_MIXER_BLEND_ATTR_FCOLOR_DEF 0xff000000 +#define SUN8I_MIXER_BLEND_FCOLOR_CTL_DEF 0x00000101 +#define SUN8I_MIXER_BLEND_PREMULTIPLY_DEF 0x0 +#define SUN8I_MIXER_BLEND_BKCOLOR_DEF 0xff000000 +#define SUN8I_MIXER_BLEND_MODE_DEF 0x03010301 +#define SUN8I_MIXER_BLEND_CK_CTL_DEF 0x0 + +#define SUN8I_MIXER_BLEND_OUTCTL_INTERLACED BIT(1) + +/* + * VI channels are not used now, but the support of them may be introduced in + * the future. + */ + +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x0) +#define SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x4) +#define SUN8I_MIXER_CHAN_UI_LAYER_COORD(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x8) +#define SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0xc) +#define SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x10) +#define SUN8I_MIXER_CHAN_UI_LAYER_BOT_LADDR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x14) +#define SUN8I_MIXER_CHAN_UI_LAYER_FCOLOR(ch, layer) \ + (0x2000 + 0x1000 * (ch) + 0x20 * (layer) + 0x18) +#define SUN8I_MIXER_CHAN_UI_TOP_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x80) +#define SUN8I_MIXER_CHAN_UI_BOT_HADDR(ch) (0x2000 + 0x1000 * (ch) + 0x84) +#define SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch) (0x2000 + 0x1000 * (ch) + 0x88) + +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN BIT(0) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK GENMASK(2, 1) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK GENMASK(11, 8) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK GENMASK(31, 24) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_DEF (1 << 1) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_ARGB8888 (0 << 8) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_XRGB8888 (4 << 8) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_RGB888 (8 << 8) +#define SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_DEF (0xff << 24) + +/* + * These sub-engines are still unknown now, the EN registers are here only to + * be used to disable these sub-engines. + */ +#define SUN8I_MIXER_VSU_EN 0x20000 +#define SUN8I_MIXER_GSU1_EN 0x30000 +#define SUN8I_MIXER_GSU2_EN 0x40000 +#define SUN8I_MIXER_GSU3_EN 0x50000 +#define SUN8I_MIXER_FCE_EN 0xa0000 +#define SUN8I_MIXER_BWS_EN 0xa2000 +#define SUN8I_MIXER_LTI_EN 0xa4000 +#define SUN8I_MIXER_PEAK_EN 0xa6000 +#define SUN8I_MIXER_ASE_EN 0xa8000 +#define SUN8I_MIXER_FCC_EN 0xaa000 +#define SUN8I_MIXER_DCSC_EN 0xb0000 + +struct sun8i_mixer_cfg { + int vi_num; + int ui_num; +}; + +struct sun8i_mixer { + struct sunxi_engine engine; + + const struct sun8i_mixer_cfg *cfg; + + struct reset_control *reset; + + struct clk *bus_clk; + struct clk *mod_clk; +}; + +static inline struct sun8i_mixer * +engine_to_sun8i_mixer(struct sunxi_engine *engine) +{ + return container_of(engine, struct sun8i_mixer, engine); +} + +void sun8i_mixer_layer_enable(struct sun8i_mixer *mixer, + int layer, bool enable); +int sun8i_mixer_update_layer_coord(struct sun8i_mixer *mixer, + int layer, struct drm_plane *plane); +int sun8i_mixer_update_layer_formats(struct sun8i_mixer *mixer, + int layer, struct drm_plane *plane); +int sun8i_mixer_update_layer_buffer(struct sun8i_mixer *mixer, + int layer, struct drm_plane *plane); +#endif /* _SUN8I_MIXER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sunxi_engine.h b/drivers/gpu/drm/sun4i/sunxi_engine.h new file mode 100644 index 000000000000..4cb70ae65c79 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sunxi_engine.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUNXI_ENGINE_H_ +#define _SUNXI_ENGINE_H_ + +struct drm_plane; +struct drm_device; + +struct sunxi_engine; + +struct sunxi_engine_ops { + void (*commit)(struct sunxi_engine *engine); + struct drm_plane **(*layers_init)(struct drm_device *drm, + struct sunxi_engine *engine); + + void (*apply_color_correction)(struct sunxi_engine *engine); + void (*disable_color_correction)(struct sunxi_engine *engine); +}; + +/** + * struct sunxi_engine - the common parts of an engine for sun4i-drm driver + * @ops: the operations of the engine + * @node: the of device node of the engine + * @regs: the regmap of the engine + * @id: the id of the engine (-1 if not used) + */ +struct sunxi_engine { + const struct sunxi_engine_ops *ops; + + struct device_node *node; + struct regmap *regs; + + int id; + + /* Engine list management */ + struct list_head list; +}; + +/** + * sunxi_engine_commit() - commit all changes of the engine + * @engine: pointer to the engine + */ +static inline void +sunxi_engine_commit(struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->commit) + engine->ops->commit(engine); +} + +/** + * sunxi_engine_layers_init() - Create planes (layers) for the engine + * @drm: pointer to the drm_device for which planes will be created + * @engine: pointer to the engine + */ +static inline struct drm_plane ** +sunxi_engine_layers_init(struct drm_device *drm, struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->layers_init) + return engine->ops->layers_init(drm, engine); + return ERR_PTR(-ENOSYS); +} + +/** + * sunxi_engine_apply_color_correction - Apply the RGB2YUV color correction + * @engine: pointer to the engine + * + * This functionality is optional for an engine, however, if the engine is + * intended to be used with TV Encoder, the output will be incorrect + * without the color correction, due to TV Encoder expects the engine to + * output directly YUV signal. + */ +static inline void +sunxi_engine_apply_color_correction(struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->apply_color_correction) + engine->ops->apply_color_correction(engine); +} + +/** + * sunxi_engine_disable_color_correction - Disable the color space correction + * @engine: pointer to the engine + * + * This function is paired with apply_color_correction(). + */ +static inline void +sunxi_engine_disable_color_correction(struct sunxi_engine *engine) +{ + if (engine->ops && engine->ops->disable_color_correction) + engine->ops->disable_color_correction(engine); +} +#endif /* _SUNXI_ENGINE_H_ */ |