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
  ...
This commit is contained in:
Dave Airlie
2017-06-16 10:02:35 +10:00
کامیت 7249e3d64e
24فایلهای تغییر یافته به همراه2285 افزوده شده و 105 حذف شده

مشاهده پرونده

@@ -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.

مشاهده پرونده

@@ -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

مشاهده پرونده

@@ -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", &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);

مشاهده پرونده

@@ -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);

مشاهده پرونده

@@ -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;

مشاهده پرونده

@@ -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_ */

مشاهده پرونده

@@ -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);

مشاهده پرونده

@@ -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;
};

مشاهده پرونده

@@ -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_ */

مشاهده پرونده

@@ -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;
}

مشاهده پرونده

@@ -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");

مشاهده پرونده

@@ -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;
}

مشاهده پرونده

@@ -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;
}

مشاهده پرونده

@@ -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_ */

مشاهده پرونده

@@ -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);

مشاهده پرونده

@@ -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);

مشاهده پرونده

@@ -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,

مشاهده پرونده

@@ -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 = {

مشاهده پرونده

@@ -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;
}

مشاهده پرونده

@@ -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_ */

مشاهده پرونده

@@ -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");

مشاهده پرونده

@@ -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_ */

مشاهده پرونده

@@ -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_ */