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. -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJZQZ40AAoJEBx+YmzsjxAgEDgP+wd29aHXZPIp7EGlCR9L1qPS 62Ps5lrbgwxGJx8Nf7T51tQLl0SDBHrI9xgrQLla1bdqItAMyIkJG2NQ+INN2ZSt Yz2aUZL3Xx9DE3dUhbLdfUejOCQZqopn8pPe2egeMYNXS5hsAhwwLB8AYcX3LLCN WFpbC3hcd4pih/teDGyJ4bn4I/teyA3qWleq73f+A/eBBbCI4aRZWvx34/G/pzUL KeHryE35evukgfOJmZ9wAYMbj8BWHV3t6xObk9TqYrgQ/Vzh7IkAWl1wkcd2qRSQ RVhfCI4s0jkd44EHMQLxqh27LQakSviIqZWsJEf91qvi5g9A4ZyIS/H4/dGusfbP 59QkREjOdnlVqTSfP5E0QzZbzdcbZhV2YoPp73Rv319lif4HqV/9Z7IhDBmNC2IZ g2O4J3DKvDjtP4A2s3CoGRVgxf1ag/K+Ig6AEQZL+ShrcHwPEBrVqfqp/MnV+Qg2 gRv0YimGMKReHaMYkT8NdDtqlAzaLWUFnEYRO92X0B4m3NZENMeeDZ8wMy26/yxo TJxwzxxsNON59mZESS0Ci88q/bMnFkyCYvuWIeHol2GgUK7yzN2ePf9p/eUPOziK gDbJaKhSZxH67Lerqg+zCFbuPGDu7ZGyOHWMgfG6Xh69bCJptJAJNK03TEvoBvpX CSdBObTExOj5S/m64+SI =HtFh -----END PGP SIGNATURE----- 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:
commit
7249e3d64e
|
@ -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;
|
||||
}
|
|
@ -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_ */
|
Loading…
Reference in New Issue