You've already forked linux-apfs
mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-05-01 15:00:59 -07:00
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
...
This commit is contained in:
@@ -4,6 +4,44 @@ Allwinner A10 Display Pipeline
|
||||
The Allwinner A10 Display pipeline is composed of several components
|
||||
that are going to be documented below:
|
||||
|
||||
For the input port of all components up to the TCON in the display
|
||||
pipeline, if there are multiple components, the local endpoint IDs
|
||||
must correspond to the index of the upstream block. For example, if
|
||||
the remote endpoint is Frontend 1, then the local endpoint ID must
|
||||
be 1.
|
||||
|
||||
Conversely, for the output ports of the same group, the remote endpoint
|
||||
ID must be the index of the local hardware block. If the local backend
|
||||
is backend 1, then the remote endpoint ID must be 1.
|
||||
|
||||
HDMI Encoder
|
||||
------------
|
||||
|
||||
The HDMI Encoder supports the HDMI video and audio outputs, and does
|
||||
CEC. It is one end of the pipeline.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun5i-a10s-hdmi
|
||||
- reg: base address and size of memory-mapped region
|
||||
- interrupts: interrupt associated to this IP
|
||||
- clocks: phandles to the clocks feeding the HDMI encoder
|
||||
* ahb: the HDMI interface clock
|
||||
* mod: the HDMI module clock
|
||||
* pll-0: the first video PLL
|
||||
* pll-1: the second video PLL
|
||||
- clock-names: the clock names mentioned above
|
||||
- dmas: phandles to the DMA channels used by the HDMI encoder
|
||||
* ddc-tx: The channel for DDC transmission
|
||||
* ddc-rx: The channel for DDC reception
|
||||
* audio-tx: The channel used for audio transmission
|
||||
- dma-names: the channel names mentioned above
|
||||
|
||||
- ports: A ports node with endpoint definitions as defined in
|
||||
Documentation/devicetree/bindings/media/video-interfaces.txt. The
|
||||
first port should be the input endpoint. The second should be the
|
||||
output, usually to an HDMI connector.
|
||||
|
||||
TV Encoder
|
||||
----------
|
||||
|
||||
@@ -31,6 +69,7 @@ Required properties:
|
||||
* allwinner,sun6i-a31-tcon
|
||||
* allwinner,sun6i-a31s-tcon
|
||||
* allwinner,sun8i-a33-tcon
|
||||
* allwinner,sun8i-v3s-tcon
|
||||
- reg: base address and size of memory-mapped region
|
||||
- interrupts: interrupt associated to this IP
|
||||
- clocks: phandles to the clocks feeding the TCON. Three are needed:
|
||||
@@ -47,12 +86,15 @@ Required properties:
|
||||
Documentation/devicetree/bindings/media/video-interfaces.txt. The
|
||||
first port should be the input endpoint, the second one the output
|
||||
|
||||
The output should have two endpoints. The first is the block
|
||||
connected to the TCON channel 0 (usually a panel or a bridge), the
|
||||
second the block connected to the TCON channel 1 (usually the TV
|
||||
encoder)
|
||||
The output may have multiple endpoints. The TCON has two channels,
|
||||
usually with the first channel being used for the panels interfaces
|
||||
(RGB, LVDS, etc.), and the second being used for the outputs that
|
||||
require another controller (TV Encoder, HDMI, etc.). The endpoints
|
||||
will take an extra property, allwinner,tcon-channel, to specify the
|
||||
channel the endpoint is associated to. If that property is not
|
||||
present, the endpoint number will be used as the channel number.
|
||||
|
||||
On SoCs other than the A33, there is one more clock required:
|
||||
On SoCs other than the A33 and V3s, there is one more clock required:
|
||||
- 'tcon-ch1': The clock driving the TCON channel 1
|
||||
|
||||
DRC
|
||||
@@ -138,6 +180,26 @@ Required properties:
|
||||
Documentation/devicetree/bindings/media/video-interfaces.txt. The
|
||||
first port should be the input endpoints, the second one the outputs
|
||||
|
||||
Display Engine 2.0 Mixer
|
||||
------------------------
|
||||
|
||||
The DE2 mixer have many functionalities, currently only layer blending is
|
||||
supported.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun8i-v3s-de2-mixer
|
||||
- reg: base address and size of the memory-mapped region.
|
||||
- clocks: phandles to the clocks feeding the mixer
|
||||
* bus: the mixer interface clock
|
||||
* mod: the mixer module clock
|
||||
- clock-names: the clock names mentioned above
|
||||
- resets: phandles to the reset controllers driving the mixer
|
||||
|
||||
- ports: A ports node with endpoint definitions as defined in
|
||||
Documentation/devicetree/bindings/media/video-interfaces.txt. The
|
||||
first port should be the input endpoints, the second one the output
|
||||
|
||||
|
||||
Display Engine Pipeline
|
||||
-----------------------
|
||||
@@ -148,13 +210,15 @@ extra node.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun5i-a10s-display-engine
|
||||
* allwinner,sun5i-a13-display-engine
|
||||
* allwinner,sun6i-a31-display-engine
|
||||
* allwinner,sun6i-a31s-display-engine
|
||||
* allwinner,sun8i-a33-display-engine
|
||||
* allwinner,sun8i-v3s-display-engine
|
||||
|
||||
- allwinner,pipelines: list of phandle to the display engine
|
||||
frontends available.
|
||||
frontends (DE 1.0) or mixers (DE 2.0) available.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -173,6 +237,57 @@ panel: panel {
|
||||
};
|
||||
};
|
||||
|
||||
connector {
|
||||
compatible = "hdmi-connector";
|
||||
type = "a";
|
||||
|
||||
port {
|
||||
hdmi_con_in: endpoint {
|
||||
remote-endpoint = <&hdmi_out_con>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
hdmi: hdmi@01c16000 {
|
||||
compatible = "allwinner,sun5i-a10s-hdmi";
|
||||
reg = <0x01c16000 0x1000>;
|
||||
interrupts = <58>;
|
||||
clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
|
||||
<&ccu CLK_PLL_VIDEO0_2X>,
|
||||
<&ccu CLK_PLL_VIDEO1_2X>;
|
||||
clock-names = "ahb", "mod", "pll-0", "pll-1";
|
||||
dmas = <&dma SUN4I_DMA_NORMAL 16>,
|
||||
<&dma SUN4I_DMA_NORMAL 16>,
|
||||
<&dma SUN4I_DMA_DEDICATED 24>;
|
||||
dma-names = "ddc-tx", "ddc-rx", "audio-tx";
|
||||
status = "disabled";
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0>;
|
||||
|
||||
hdmi_in_tcon0: endpoint {
|
||||
remote-endpoint = <&tcon0_out_hdmi>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <1>;
|
||||
|
||||
hdmi_out_con: endpoint {
|
||||
remote-endpoint = <&hdmi_con_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
tve0: tv-encoder@01c0a000 {
|
||||
compatible = "allwinner,sun4i-a10-tv-encoder";
|
||||
reg = <0x01c0a000 0x1000>;
|
||||
|
||||
@@ -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", ®);
|
||||
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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user