drm-misc-next for 4.19:

Cross-subsystem Changes:
 - many dt-bindings Doc changes
 
 Core Changes:
 - Encoder clean ups (Ville Syrjälä)
 - Connector Writeback improvements(Boris Brezillon)
 - Fake vblank support (Boris Brezillon)
 - API for in-kernel clients (Noralf Trønnes)
 - improvements to the path of finding panels(Boris Brezillon)
 
 Driver Changes:
 - initial support for the virtual display driver - vkms(Haneen Mohammed and Rodrigo Siqueira)
 - panel: add Rocktech RK070ER9427 LCD support (Jagan Teki)
 - panel: add support for the EDT ETM0700G0EDH6 and EDT ETM0700G0BDH6(Jan Tuerk)
 - panel: add DLC DLC0700YZG-1 (Philipp Zabel)
 - panel: add support for BOE HV070WSA-100 (Andrzej Hajda)
 - panel: add newhaven, nhd-4.3-480272ef-atxl LCD (Tomi Valkeinen)
 - panel: add support for Innolux G070Y2-L01 (Christoph Fritz)
 - panel: add support for DataImage SCF0700C48GGU18 (Michal Vokáč)
 - panel: add support for Sharp LQ035Q7DB03 (Vladimir Zapolskiy)
 - panel: p079zca: Refactor panel driver to support multiple panels (Lin Huang)
 - sun4i: Add R40 display engine compatible(Jernej Skrabec)
 -----BEGIN PGP SIGNATURE-----
 
 iQIcBAABAgAGBQJbRqmlAAoJEEN0HIUfOBk0I2wQALu/Kk5y7gVGZWFMP/uImqB5
 vbkNXX5rdzFfvXHbNE9GHVLtMLIJS705pou+wzuUzil3C/xjUUrP7mUdWEVgNl4s
 hj6norNP2Jhd6v+Jua5dwhamDi6RHcroWc6c3SYt//JjcZKtOb/oqwt+7Bu0I+yi
 I+62iMpOVjxOIaygthZiJD89Q0mRsFBELRC1iYm1SxXF0l1/9Hf5Xt9PbUvoMscb
 /74e0MqdqKNTopWz7TLIZT6mHDWLM8IklLkMBDEClZQUMQAfIvPSKhe7KGXnOB0T
 PzBd1MJeQ/rPyyJJtWY5BTng6C38XW5xmq+I4YUSIfRJZjJHf4vBEcJO3ErIxBGj
 LE957F/rT8IYxN0Z31wH0DZhP8KpL1KIXy3AOb7Yqqbu+SemKT/WaepAdFvKfFbO
 5BxBgeFSQg9soHv7ndjOWDQal1AmuIhK/UP18ytYO9T54fPBJ1TaaHUWacYnvo/0
 oQdCTghvFuZgpP3Tl3IisFO1xDpLq4Y4VqvP4xwSj2Id1MUp6MGh9W/A39bc8FA0
 JJcvQYYCcnf4GVQQem+QFk+bpXmzLZW3+/cMpU72zrP+8OKrxD5PwxLmhuqdRzLR
 2lMT5gXEuPl8K8wyOLJDiWbXfZB9jn7Mm+5lySKGKu6NYzLN+cbLPA7+tVsqrWMu
 XLd++4ntNy5hdvS/TO7j
 =Bepe
 -----END PGP SIGNATURE-----

Merge tag 'drm-misc-next-2018-07-11' of git://anongit.freedesktop.org/drm/drm-misc into drm-next

drm-misc-next for 4.19:

Cross-subsystem Changes:
- many dt-bindings Doc changes

Core Changes:
- Encoder clean ups (Ville Syrjälä)
- Connector Writeback improvements(Boris Brezillon)
- Fake vblank support (Boris Brezillon)
- API for in-kernel clients (Noralf Trønnes)
- improvements to the path of finding panels(Boris Brezillon)

Driver Changes:
- initial support for the virtual display driver - vkms(Haneen Mohammed and Rodrigo Siqueira)
- panel: add Rocktech RK070ER9427 LCD support (Jagan Teki)
- panel: add support for the EDT ETM0700G0EDH6 and EDT ETM0700G0BDH6(Jan Tuerk)
- panel: add DLC DLC0700YZG-1 (Philipp Zabel)
- panel: add support for BOE HV070WSA-100 (Andrzej Hajda)
- panel: add newhaven, nhd-4.3-480272ef-atxl LCD (Tomi Valkeinen)
- panel: add support for Innolux G070Y2-L01 (Christoph Fritz)
- panel: add support for DataImage SCF0700C48GGU18 (Michal Vokáč)
- panel: add support for Sharp LQ035Q7DB03 (Vladimir Zapolskiy)
- panel: p079zca: Refactor panel driver to support multiple panels (Lin Huang)
- sun4i: Add R40 display engine compatible(Jernej Skrabec)

Signed-off-by: Dave Airlie <airlied@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20180712011137.GA26620@juma
This commit is contained in:
Dave Airlie 2018-07-19 05:13:14 +10:00
commit 54c88a029a
108 changed files with 3258 additions and 924 deletions

View File

@ -74,6 +74,12 @@ Required properties for DSI:
The 3 clocks output from the DSI analog PHY: dsi[01]_byte,
dsi[01]_ddr2, and dsi[01]_ddr
Required properties for the TXP (writeback) block:
- compatible: Should be "brcm,bcm2835-txp"
- reg: Physical base address and length of the TXP block's registers
- interrupts: The interrupt number
See bindings/interrupt-controller/brcm,bcm2835-armctrl-ic.txt
[1] Documentation/devicetree/bindings/media/video-interfaces.txt
Example:

View File

@ -0,0 +1,28 @@
BOE HV070WSA-100 7.01" WSVGA TFT LCD panel
Required properties:
- compatible: should be "boe,hv070wsa-100"
- power-supply: regulator to provide the VCC supply voltage (3.3 volts)
- enable-gpios: GPIO pin to enable and disable panel (active high)
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.
The device node can contain one 'port' child node with one child
'endpoint' node, according to the bindings defined in [1]. This
node should describe panel's video bus.
[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
Example:
panel: panel {
compatible = "boe,hv070wsa-100";
power-supply = <&vcc_3v3_reg>;
enable-gpios = <&gpd1 3 GPIO_ACTIVE_HIGH>;
port {
panel_ep: endpoint {
remote-endpoint = <&bridge_out_ep>;
};
};
};

View File

@ -0,0 +1,8 @@
DataImage, Inc. 7" WVGA (800x480) TFT LCD panel with 24-bit parallel interface.
Required properties:
- compatible: should be "dataimage,scf0700c48ggu18"
- power-supply: as specified in the base binding
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.

View File

@ -0,0 +1,13 @@
DLC Display Co. DLC0700YZG-1 7.0" WSVGA TFT LCD panel
Required properties:
- compatible: should be "dlc,dlc0700yzg-1"
- power-supply: See simple-panel.txt
Optional properties:
- reset-gpios: See panel-common.txt
- enable-gpios: See simple-panel.txt
- backlight: See simple-panel.txt
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.

View File

@ -0,0 +1,39 @@
Emerging Display Technology Corp. Displays
==========================================
Display bindings for EDT Display Technology Corp. Displays which are
compatible with the simple-panel binding, which is specified in
simple-panel.txt
5,7" WVGA TFT Panels
--------------------
+-----------------+---------------------+-------------------------------------+
| Identifier | compatbile | description |
+=================+=====================+=====================================+
| ET057090DHU | edt,et057090dhu | 5.7" VGA TFT LCD panel |
+-----------------+---------------------+-------------------------------------+
7,0" WVGA TFT Panels
--------------------
+-----------------+---------------------+-------------------------------------+
| Identifier | compatbile | description |
+=================+=====================+=====================================+
| ETM0700G0DH6 | edt,etm070080dh6 | WVGA TFT Display with capacitive |
| | | Touchscreen |
+-----------------+---------------------+-------------------------------------+
| ETM0700G0BDH6 | edt,etm070080bdh6 | Same as ETM0700G0DH6 but with |
| | | inverted pixel clock. |
+-----------------+---------------------+-------------------------------------+
| ETM0700G0EDH6 | edt,etm070080edh6 | Same display as the ETM0700G0BDH6, |
| | | but with changed Hardware for the |
| | | backlight and the touch interface |
+-----------------+---------------------+-------------------------------------+
| ET070080DH6 | edt,etm070080dh6 | Same timings as the ETM0700G0DH6, |
| | | but with resistive touch. |
+-----------------+---------------------+-------------------------------------+

View File

@ -1,10 +0,0 @@
Emerging Display Technology Corp. ET070080DH6 7.0" WVGA TFT LCD panel
Required properties:
- compatible: should be "edt,et070080dh6"
This panel is the same as ETM0700G0DH6 except for the touchscreen.
ET070080DH6 is the model with resistive touch.
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.

View File

@ -1,10 +0,0 @@
Emerging Display Technology Corp. ETM0700G0DH6 7.0" WVGA TFT LCD panel
Required properties:
- compatible: should be "edt,etm0700g0dh6"
This panel is the same as ET070080DH6 except for the touchscreen.
ETM0700G0DH6 is the model with capacitive multitouch.
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.

View File

@ -0,0 +1,12 @@
Innolux G070Y2-L01 7" WVGA (800x480) TFT LCD panel
Required properties:
- compatible: should be "innolux,g070y2-l01"
- power-supply: as specified in the base binding
Optional properties:
- backlight: as specified in the base binding
- enable-gpios: as specified in the base binding
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.

View File

@ -0,0 +1,24 @@
Innolux P097PFG 9.7" 1536x2048 TFT LCD panel
Required properties:
- compatible: should be "innolux,p097pfg"
- reg: DSI virtual channel of the peripheral
- avdd-supply: phandle of the regulator that provides positive voltage
- avee-supply: phandle of the regulator that provides negative voltage
- enable-gpios: panel enable gpio
Optional properties:
- backlight: phandle of the backlight device attached to the panel
Example:
&mipi_dsi {
panel {
compatible = "innolux,p079zca";
reg = <0>;
avdd-supply = <...>;
avee-supply = <...>;
backlight = <&backlight>;
enable-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
};
};

View File

@ -0,0 +1,22 @@
Kingdisplay KD097D04 9.7" 1536x2048 TFT LCD panel
Required properties:
- compatible: should be "kingdisplay,kd097d04"
- reg: DSI virtual channel of the peripheral
- power-supply: phandle of the regulator that provides the supply voltage
- enable-gpios: panel enable gpio
Optional properties:
- backlight: phandle of the backlight device attached to the panel
Example:
&mipi_dsi {
panel {
compatible = "kingdisplay,kd097d04";
reg = <0>;
power-supply = <...>;
backlight = <&backlight>;
enable-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
};
};

View File

@ -1,7 +1,7 @@
Emerging Display Technology Corp. 5.7" VGA TFT LCD panel
Newhaven Display International 480 x 272 TFT LCD panel
Required properties:
- compatible: should be "edt,et057090dhu"
- compatible: should be "newhaven,nhd-4.3-480272ef-atxl"
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.

View File

@ -0,0 +1,25 @@
Rocktech Display Ltd. RK070ER9427 800(RGB)x480 TFT LCD panel
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.
Required properties:
- compatible: should be "rocktech,rk070er9427"
Optional properties:
- backlight: phandle of the backlight device attached to the panel
Optional nodes:
- Video port for LCD panel input.
Example:
panel {
compatible = "rocktech,rk070er9427";
backlight = <&backlight_lcd>;
port {
lcd_panel_in: endpoint {
remote-endpoint = <&lcd_display_out>;
};
};
};

View File

@ -0,0 +1,12 @@
Sharp LQ035Q7DB03 3.5" QVGA TFT LCD panel
Required properties:
- compatible: should be "sharp,lq035q7db03"
- power-supply: phandle of the regulator that provides the supply voltage
Optional properties:
- enable-gpios: GPIO pin to enable or disable the panel
- backlight: phandle of the backlight device attached to the panel
This binding is compatible with the simple-panel binding, which is specified
in simple-panel.txt in this directory.

View File

@ -101,9 +101,9 @@ DWC HDMI PHY
Required properties:
- compatible: value must be one of:
* allwinner,sun50i-a64-hdmi-phy
* allwinner,sun8i-a83t-hdmi-phy
* allwinner,sun8i-h3-hdmi-phy
* allwinner,sun50i-a64-hdmi-phy
- reg: base address and size of memory-mapped region
- clocks: phandles to the clocks feeding the HDMI PHY
* bus: the HDMI PHY interface clock
@ -147,6 +147,7 @@ Required properties:
* allwinner,sun8i-a33-tcon
* allwinner,sun8i-a83t-tcon-lcd
* allwinner,sun8i-a83t-tcon-tv
* allwinner,sun8i-r40-tcon-tv
* allwinner,sun8i-v3s-tcon
* allwinner,sun9i-a80-tcon-lcd
* allwinner,sun9i-a80-tcon-tv
@ -181,7 +182,7 @@ For TCONs with channel 0, there is one more clock required:
For TCONs with channel 1, there is one more clock required:
- 'tcon-ch1': The clock driving the TCON channel 1
When TCON support LVDS (all TCONs except TV TCON on A83T and those found
When TCON support LVDS (all TCONs except TV TCONs on A83T, R40 and those found
in A13, H3, H5 and V3s SoCs), you need one more reset line:
- 'lvds': The reset line driving the LVDS logic
@ -399,6 +400,7 @@ Required properties:
* allwinner,sun8i-a33-display-engine
* allwinner,sun8i-a83t-display-engine
* allwinner,sun8i-h3-display-engine
* allwinner,sun8i-r40-display-engine
* allwinner,sun8i-v3s-display-engine
* allwinner,sun9i-a80-display-engine

View File

@ -86,6 +86,7 @@ cubietech Cubietech, Ltd.
cypress Cypress Semiconductor Corporation
cznic CZ.NIC, z.s.p.o.
dallas Maxim Integrated Products (formerly Dallas Semiconductor)
dataimage DataImage, Inc.
davicom DAVICOM Semiconductor, Inc.
delta Delta Electronics, Inc.
denx Denx Software Engineering
@ -94,6 +95,7 @@ dh DH electronics GmbH
digi Digi International Inc.
digilent Diglent, Inc.
dioo Dioo Microcircuit Co., Ltd
dlc DLC Display Co., Ltd.
dlg Dialog Semiconductor
dlink D-Link Corporation
dmo Data Modul AG
@ -189,6 +191,7 @@ keymile Keymile GmbH
khadas Khadas
kiebackpeter Kieback & Peter GmbH
kinetic Kinetic Technologies
kingdisplay King & Display Technology Co., Ltd.
kingnovel Kingnovel Technology Co., Ltd.
koe Kaohsiung Opto-Electronics Inc.
kosagi Sutajio Ko-Usagi PTE Ltd.

View File

@ -0,0 +1,12 @@
=================
Kernel clients
=================
.. kernel-doc:: drivers/gpu/drm/drm_client.c
:doc: overview
.. kernel-doc:: include/drm/drm_client.h
:internal:
.. kernel-doc:: drivers/gpu/drm/drm_client.c
:export:

View File

@ -10,6 +10,7 @@ Linux GPU Driver Developer's Guide
drm-kms
drm-kms-helpers
drm-uapi
drm-client
drivers
vga-switcheroo
vgaarbiter

28
Documentation/gpu/v3d.rst Normal file
View File

@ -0,0 +1,28 @@
=====================================
drm/v3d Broadcom V3D Graphics Driver
=====================================
.. kernel-doc:: drivers/gpu/drm/v3d/v3d_drv.c
:doc: Broadcom V3D Graphics Driver
GPU buffer object (BO) management
---------------------------------
.. kernel-doc:: drivers/gpu/drm/v3d/v3d_bo.c
:doc: V3D GEM BO management support
Address space management
===========================================
.. kernel-doc:: drivers/gpu/drm/v3d/v3d_mmu.c
:doc: Broadcom V3D MMU
GPU Scheduling
===========================================
.. kernel-doc:: drivers/gpu/drm/v3d/v3d_sched.c
:doc: Broadcom V3D scheduling
Interrupts
--------------
.. kernel-doc:: drivers/gpu/drm/v3d/v3d_irq.c
:doc: Interrupt management for the V3D engine

View File

@ -213,6 +213,17 @@ config DRM_VGEM
as used by Mesa's software renderer for enhanced performance.
If M is selected the module will be called vgem.
config DRM_VKMS
tristate "Virtual KMS (EXPERIMENTAL)"
depends on DRM
select DRM_KMS_HELPER
default n
help
Virtual Kernel Mode-Setting (VKMS) is used for testing or for
running GPU in a headless machines. Choose this option to get
a VKMS.
If M is selected the module will be called vkms.
source "drivers/gpu/drm/exynos/Kconfig"

View File

@ -18,7 +18,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \
drm_encoder.o drm_mode_object.o drm_property.o \
drm_plane.o drm_color_mgmt.o drm_print.o \
drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
drm_syncobj.o drm_lease.o drm_writeback.o
drm_syncobj.o drm_lease.o drm_writeback.o drm_client.o
drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
drm-$(CONFIG_DRM_VM) += drm_vm.o
@ -69,6 +69,7 @@ obj-$(CONFIG_DRM_SAVAGE)+= savage/
obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/
obj-$(CONFIG_DRM_VIA) +=via/
obj-$(CONFIG_DRM_VGEM) += vgem/
obj-$(CONFIG_DRM_VKMS) += vkms/
obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
obj-$(CONFIG_DRM_EXYNOS) +=exynos/
obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/

View File

@ -212,30 +212,21 @@ static void
amdgpu_connector_update_scratch_regs(struct drm_connector *connector,
enum drm_connector_status status)
{
struct drm_encoder *best_encoder = NULL;
struct drm_encoder *encoder = NULL;
struct drm_encoder *best_encoder;
struct drm_encoder *encoder;
const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private;
bool connected;
int i;
best_encoder = connector_funcs->best_encoder(connector);
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL,
connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if ((encoder == best_encoder) && (status == connector_status_connected))
connected = true;
else
connected = false;
amdgpu_atombios_encoder_set_bios_scratch_regs(connector, encoder, connected);
}
}
@ -246,17 +237,11 @@ amdgpu_connector_find_encoder(struct drm_connector *connector,
struct drm_encoder *encoder;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL,
connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (encoder->encoder_type == encoder_type)
return encoder;
}
return NULL;
}
@ -360,11 +345,13 @@ static int amdgpu_connector_ddc_get_modes(struct drm_connector *connector)
static struct drm_encoder *
amdgpu_connector_best_single_encoder(struct drm_connector *connector)
{
int enc_id = connector->encoder_ids[0];
struct drm_encoder *encoder;
int i;
/* pick the first one */
drm_connector_for_each_possible_encoder(connector, encoder, i)
return encoder;
/* pick the encoder ids */
if (enc_id)
return drm_encoder_find(connector->dev, NULL, enc_id);
return NULL;
}
@ -985,9 +972,8 @@ amdgpu_connector_dvi_detect(struct drm_connector *connector, bool force)
struct drm_device *dev = connector->dev;
struct amdgpu_device *adev = dev->dev_private;
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
struct drm_encoder *encoder = NULL;
const struct drm_encoder_helper_funcs *encoder_funcs;
int i, r;
int r;
enum drm_connector_status ret = connector_status_disconnected;
bool dret = false, broken_edid = false;
@ -1077,14 +1063,10 @@ amdgpu_connector_dvi_detect(struct drm_connector *connector, bool force)
/* find analog encoder */
if (amdgpu_connector->dac_load_detect) {
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL, connector->encoder_ids[i]);
if (!encoder)
continue;
struct drm_encoder *encoder;
int i;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (encoder->encoder_type != DRM_MODE_ENCODER_DAC &&
encoder->encoder_type != DRM_MODE_ENCODER_TVDAC)
continue;
@ -1132,18 +1114,11 @@ exit:
static struct drm_encoder *
amdgpu_connector_dvi_encoder(struct drm_connector *connector)
{
int enc_id = connector->encoder_ids[0];
struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector);
struct drm_encoder *encoder;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL, connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (amdgpu_connector->use_digital == true) {
if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS)
return encoder;
@ -1158,8 +1133,9 @@ amdgpu_connector_dvi_encoder(struct drm_connector *connector)
/* then check use digitial */
/* pick the first one */
if (enc_id)
return drm_encoder_find(connector->dev, NULL, enc_id);
drm_connector_for_each_possible_encoder(connector, encoder, i)
return encoder;
return NULL;
}
@ -1296,15 +1272,7 @@ u16 amdgpu_connector_encoder_get_dp_bridge_encoder_id(struct drm_connector *conn
struct amdgpu_encoder *amdgpu_encoder;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL,
connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
amdgpu_encoder = to_amdgpu_encoder(encoder);
switch (amdgpu_encoder->encoder_id) {
@ -1326,14 +1294,7 @@ static bool amdgpu_connector_encoder_is_hbr2(struct drm_connector *connector)
int i;
bool found = false;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL,
connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
amdgpu_encoder = to_amdgpu_encoder(encoder);
if (amdgpu_encoder->caps & ATOM_ENCODER_CAP_RECORD_HBR2)
found = true;

View File

@ -269,25 +269,18 @@ static int dce_virtual_early_init(void *handle)
static struct drm_encoder *
dce_virtual_encoder(struct drm_connector *connector)
{
int enc_id = connector->encoder_ids[0];
struct drm_encoder *encoder;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL, connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
return encoder;
}
/* pick the first one */
if (enc_id)
return drm_encoder_find(connector->dev, NULL, enc_id);
drm_connector_for_each_possible_encoder(connector, encoder, i)
return encoder;
return NULL;
}

View File

@ -1152,7 +1152,7 @@ static int cdns_dsi_attach(struct mipi_dsi_host *host,
np = of_node_get(dev->dev.of_node);
panel = of_drm_find_panel(np);
if (panel) {
if (!IS_ERR(panel)) {
bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DSI);
} else {
bridge = of_drm_find_bridge(dev->dev.of_node);

View File

@ -68,9 +68,9 @@ static int lvds_encoder_probe(struct platform_device *pdev)
panel = of_drm_find_panel(panel_node);
of_node_put(panel_node);
if (!panel) {
if (IS_ERR(panel)) {
dev_dbg(&pdev->dev, "panel not found, deferring probe\n");
return -EPROBE_DEFER;
return PTR_ERR(panel);
}
lvds_encoder->panel_bridge =

View File

@ -1111,6 +1111,7 @@ static void drm_atomic_plane_print_state(struct drm_printer *p,
drm_printf(p, "\tcrtc-pos=" DRM_RECT_FMT "\n", DRM_RECT_ARG(&dest));
drm_printf(p, "\tsrc-pos=" DRM_RECT_FP_FMT "\n", DRM_RECT_FP_ARG(&src));
drm_printf(p, "\trotation=%x\n", state->rotation);
drm_printf(p, "\tnormalized-zpos=%x\n", state->normalized_zpos);
drm_printf(p, "\tcolor-encoding=%s\n",
drm_get_color_encoding_name(state->color_encoding));
drm_printf(p, "\tcolor-range=%s\n",
@ -2427,6 +2428,7 @@ static int prepare_signaling(struct drm_device *dev,
}
for_each_new_connector_in_state(state, conn, conn_state, i) {
struct drm_writeback_connector *wb_conn;
struct drm_writeback_job *job;
struct drm_out_fence_state *f;
struct dma_fence *fence;
@ -2450,7 +2452,8 @@ static int prepare_signaling(struct drm_device *dev,
f[*num_fences].out_fence_ptr = fence_ptr;
*fence_state = f;
fence = drm_writeback_get_out_fence((struct drm_writeback_connector *)conn);
wb_conn = drm_connector_to_writeback(conn);
fence = drm_writeback_get_out_fence(wb_conn);
if (!fence)
return -ENOMEM;

View File

@ -645,7 +645,7 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
if (ret)
return ret;
connectors_mask += BIT(i);
connectors_mask |= BIT(i);
}
/*
@ -1184,10 +1184,12 @@ static void drm_atomic_helper_commit_writebacks(struct drm_device *dev,
const struct drm_connector_helper_funcs *funcs;
funcs = connector->helper_private;
if (!funcs->atomic_commit)
continue;
if (new_conn_state->writeback_job && new_conn_state->writeback_job->fb) {
WARN_ON(connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK);
funcs->atomic_commit(connector, new_conn_state->writeback_job);
funcs->atomic_commit(connector, new_conn_state);
}
}
}
@ -1448,6 +1450,8 @@ void drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
drm_atomic_helper_commit_modeset_enables(dev, old_state);
drm_atomic_helper_fake_vblank(old_state);
drm_atomic_helper_commit_hw_done(old_state);
drm_atomic_helper_wait_for_vblanks(dev, old_state);
@ -1477,6 +1481,8 @@ void drm_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
drm_atomic_helper_commit_planes(dev, old_state,
DRM_PLANE_COMMIT_ACTIVE_ONLY);
drm_atomic_helper_fake_vblank(old_state);
drm_atomic_helper_commit_hw_done(old_state);
drm_atomic_helper_wait_for_vblanks(dev, old_state);
@ -2051,6 +2057,45 @@ void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *old_state)
}
EXPORT_SYMBOL(drm_atomic_helper_wait_for_dependencies);
/**
* drm_atomic_helper_fake_vblank - fake VBLANK events if needed
* @old_state: atomic state object with old state structures
*
* This function walks all CRTCs and fake VBLANK events on those with
* &drm_crtc_state.no_vblank set to true and &drm_crtc_state.event != NULL.
* The primary use of this function is writeback connectors working in oneshot
* mode and faking VBLANK events. In this case they only fake the VBLANK event
* when a job is queued, and any change to the pipeline that does not touch the
* connector is leading to timeouts when calling
* drm_atomic_helper_wait_for_vblanks() or
* drm_atomic_helper_wait_for_flip_done().
*
* This is part of the atomic helper support for nonblocking commits, see
* drm_atomic_helper_setup_commit() for an overview.
*/
void drm_atomic_helper_fake_vblank(struct drm_atomic_state *old_state)
{
struct drm_crtc_state *new_crtc_state;
struct drm_crtc *crtc;
int i;
for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) {
unsigned long flags;
if (!new_crtc_state->no_vblank)
continue;
spin_lock_irqsave(&old_state->dev->event_lock, flags);
if (new_crtc_state->event) {
drm_crtc_send_vblank_event(crtc,
new_crtc_state->event);
new_crtc_state->event = NULL;
}
spin_unlock_irqrestore(&old_state->dev->event_lock, flags);
}
}
EXPORT_SYMBOL(drm_atomic_helper_fake_vblank);
/**
* drm_atomic_helper_commit_hw_done - setup possible nonblocking commit
* @old_state: atomic state object with old state structures

View File

@ -0,0 +1,406 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018 Noralf Trønnes
*/
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <drm/drm_client.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem.h>
#include <drm/drm_mode.h>
#include <drm/drm_print.h>
#include <drm/drmP.h>
#include "drm_crtc_internal.h"
#include "drm_internal.h"
/**
* DOC: overview
*
* This library provides support for clients running in the kernel like fbdev and bootsplash.
* Currently it's only partially implemented, just enough to support fbdev.
*
* GEM drivers which provide a GEM based dumb buffer with a virtual address are supported.
*/
static int drm_client_open(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
struct drm_file *file;
file = drm_file_alloc(dev->primary);
if (IS_ERR(file))
return PTR_ERR(file);
mutex_lock(&dev->filelist_mutex);
list_add(&file->lhead, &dev->filelist_internal);
mutex_unlock(&dev->filelist_mutex);
client->file = file;
return 0;
}
static void drm_client_close(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
mutex_lock(&dev->filelist_mutex);
list_del(&client->file->lhead);
mutex_unlock(&dev->filelist_mutex);
drm_file_free(client->file);
}
EXPORT_SYMBOL(drm_client_close);
/**
* drm_client_new - Create a DRM client
* @dev: DRM device
* @client: DRM client
* @name: Client name
* @funcs: DRM client functions (optional)
*
* The caller needs to hold a reference on @dev before calling this function.
* The client is freed when the &drm_device is unregistered. See drm_client_release().
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_client_new(struct drm_device *dev, struct drm_client_dev *client,
const char *name, const struct drm_client_funcs *funcs)
{
int ret;
if (!drm_core_check_feature(dev, DRIVER_MODESET) ||
!dev->driver->dumb_create || !dev->driver->gem_prime_vmap)
return -ENOTSUPP;
if (funcs && !try_module_get(funcs->owner))
return -ENODEV;
client->dev = dev;
client->name = name;
client->funcs = funcs;
ret = drm_client_open(client);
if (ret)
goto err_put_module;
mutex_lock(&dev->clientlist_mutex);
list_add(&client->list, &dev->clientlist);
mutex_unlock(&dev->clientlist_mutex);
drm_dev_get(dev);
return 0;
err_put_module:
if (funcs)
module_put(funcs->owner);
return ret;
}
EXPORT_SYMBOL(drm_client_new);
/**
* drm_client_release - Release DRM client resources
* @client: DRM client
*
* Releases resources by closing the &drm_file that was opened by drm_client_new().
* It is called automatically if the &drm_client_funcs.unregister callback is _not_ set.
*
* This function should only be called from the unregister callback. An exception
* is fbdev which cannot free the buffer if userspace has open file descriptors.
*
* Note:
* Clients cannot initiate a release by themselves. This is done to keep the code simple.
* The driver has to be unloaded before the client can be unloaded.
*/
void drm_client_release(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
DRM_DEV_DEBUG_KMS(dev->dev, "%s\n", client->name);
drm_client_close(client);
drm_dev_put(dev);
if (client->funcs)
module_put(client->funcs->owner);
}
EXPORT_SYMBOL(drm_client_release);
void drm_client_dev_unregister(struct drm_device *dev)
{
struct drm_client_dev *client, *tmp;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return;
mutex_lock(&dev->clientlist_mutex);
list_for_each_entry_safe(client, tmp, &dev->clientlist, list) {
list_del(&client->list);
if (client->funcs && client->funcs->unregister) {
client->funcs->unregister(client);
} else {
drm_client_release(client);
kfree(client);
}
}
mutex_unlock(&dev->clientlist_mutex);
}
/**
* drm_client_dev_hotplug - Send hotplug event to clients
* @dev: DRM device
*
* This function calls the &drm_client_funcs.hotplug callback on the attached clients.
*
* drm_kms_helper_hotplug_event() calls this function, so drivers that use it
* don't need to call this function themselves.
*/
void drm_client_dev_hotplug(struct drm_device *dev)
{
struct drm_client_dev *client;
int ret;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return;
mutex_lock(&dev->clientlist_mutex);
list_for_each_entry(client, &dev->clientlist, list) {
if (!client->funcs || !client->funcs->hotplug)
continue;
ret = client->funcs->hotplug(client);
DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->name, ret);
}
mutex_unlock(&dev->clientlist_mutex);
}
EXPORT_SYMBOL(drm_client_dev_hotplug);
void drm_client_dev_restore(struct drm_device *dev)
{
struct drm_client_dev *client;
int ret;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return;
mutex_lock(&dev->clientlist_mutex);
list_for_each_entry(client, &dev->clientlist, list) {
if (!client->funcs || !client->funcs->restore)
continue;
ret = client->funcs->restore(client);
DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->name, ret);
if (!ret) /* The first one to return zero gets the privilege to restore */
break;
}
mutex_unlock(&dev->clientlist_mutex);
}
static void drm_client_buffer_delete(struct drm_client_buffer *buffer)
{
struct drm_device *dev = buffer->client->dev;
if (buffer->vaddr && dev->driver->gem_prime_vunmap)
dev->driver->gem_prime_vunmap(buffer->gem, buffer->vaddr);
if (buffer->gem)
drm_gem_object_put_unlocked(buffer->gem);
drm_mode_destroy_dumb(dev, buffer->handle, buffer->client->file);
kfree(buffer);
}
static struct drm_client_buffer *
drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format)
{
struct drm_mode_create_dumb dumb_args = { };
struct drm_device *dev = client->dev;
struct drm_client_buffer *buffer;
struct drm_gem_object *obj;
void *vaddr;
int ret;
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
buffer->client = client;
dumb_args.width = width;
dumb_args.height = height;
dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
ret = drm_mode_create_dumb(dev, &dumb_args, client->file);
if (ret)
goto err_free;
buffer->handle = dumb_args.handle;
buffer->pitch = dumb_args.pitch;
obj = drm_gem_object_lookup(client->file, dumb_args.handle);
if (!obj) {
ret = -ENOENT;
goto err_delete;
}
buffer->gem = obj;
/*
* FIXME: The dependency on GEM here isn't required, we could
* convert the driver handle to a dma-buf instead and use the
* backend-agnostic dma-buf vmap support instead. This would
* require that the handle2fd prime ioctl is reworked to pull the
* fd_install step out of the driver backend hooks, to make that
* final step optional for internal users.
*/
vaddr = dev->driver->gem_prime_vmap(obj);
if (!vaddr) {
ret = -ENOMEM;
goto err_delete;
}
buffer->vaddr = vaddr;
return buffer;
err_delete:
drm_client_buffer_delete(buffer);
err_free:
kfree(buffer);
return ERR_PTR(ret);
}
static void drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
{
int ret;
if (!buffer->fb)
return;
ret = drm_mode_rmfb(buffer->client->dev, buffer->fb->base.id, buffer->client->file);
if (ret)
DRM_DEV_ERROR(buffer->client->dev->dev,
"Error removing FB:%u (%d)\n", buffer->fb->base.id, ret);
buffer->fb = NULL;
}
static int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
u32 width, u32 height, u32 format)
{
struct drm_client_dev *client = buffer->client;
struct drm_mode_fb_cmd fb_req = { };
const struct drm_format_info *info;
int ret;
info = drm_format_info(format);
fb_req.bpp = info->cpp[0] * 8;
fb_req.depth = info->depth;
fb_req.width = width;
fb_req.height = height;
fb_req.handle = buffer->handle;
fb_req.pitch = buffer->pitch;
ret = drm_mode_addfb(client->dev, &fb_req, client->file);
if (ret)
return ret;
buffer->fb = drm_framebuffer_lookup(client->dev, buffer->client->file, fb_req.fb_id);
if (WARN_ON(!buffer->fb))
return -ENOENT;
/* drop the reference we picked up in framebuffer lookup */
drm_framebuffer_put(buffer->fb);
strscpy(buffer->fb->comm, client->name, TASK_COMM_LEN);
return 0;
}
/**
* drm_client_framebuffer_create - Create a client framebuffer
* @client: DRM client
* @width: Framebuffer width
* @height: Framebuffer height
* @format: Buffer format
*
* This function creates a &drm_client_buffer which consists of a
* &drm_framebuffer backed by a dumb buffer.
* Call drm_client_framebuffer_delete() to free the buffer.
*
* Returns:
* Pointer to a client buffer or an error pointer on failure.
*/
struct drm_client_buffer *
drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format)
{
struct drm_client_buffer *buffer;
int ret;
buffer = drm_client_buffer_create(client, width, height, format);
if (IS_ERR(buffer))
return buffer;
ret = drm_client_buffer_addfb(buffer, width, height, format);
if (ret) {
drm_client_buffer_delete(buffer);
return ERR_PTR(ret);
}
return buffer;
}
EXPORT_SYMBOL(drm_client_framebuffer_create);
/**
* drm_client_framebuffer_delete - Delete a client framebuffer
* @buffer: DRM client buffer (can be NULL)
*/
void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
{
if (!buffer)
return;
drm_client_buffer_rmfb(buffer);
drm_client_buffer_delete(buffer);
}
EXPORT_SYMBOL(drm_client_framebuffer_delete);
#ifdef CONFIG_DEBUG_FS
static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
{
struct drm_info_node *node = m->private;
struct drm_device *dev = node->minor->dev;
struct drm_printer p = drm_seq_file_printer(m);
struct drm_client_dev *client;
mutex_lock(&dev->clientlist_mutex);
list_for_each_entry(client, &dev->clientlist, list)
drm_printf(&p, "%s\n", client->name);
mutex_unlock(&dev->clientlist_mutex);
return 0;
}
static const struct drm_info_list drm_client_debugfs_list[] = {
{ "internal_clients", drm_client_debugfs_internal_clients, 0 },
};
int drm_client_debugfs_init(struct drm_minor *minor)
{
return drm_debugfs_create_files(drm_client_debugfs_list,
ARRAY_SIZE(drm_client_debugfs_list),
minor->debugfs_root, minor);
}
#endif

View File

@ -321,7 +321,7 @@ int drm_mode_connector_attach_encoder(struct drm_connector *connector,
if (WARN_ON(connector->encoder))
return -EINVAL;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
for (i = 0; i < ARRAY_SIZE(connector->encoder_ids); i++) {
if (connector->encoder_ids[i] == 0) {
connector->encoder_ids[i] = encoder->base.id;
return 0;
@ -331,6 +331,29 @@ int drm_mode_connector_attach_encoder(struct drm_connector *connector,
}
EXPORT_SYMBOL(drm_mode_connector_attach_encoder);
/**
* drm_connector_has_possible_encoder - check if the connector and encoder are assosicated with each other
* @connector: the connector
* @encoder: the encoder
*
* Returns:
* True if @encoder is one of the possible encoders for @connector.
*/
bool drm_connector_has_possible_encoder(struct drm_connector *connector,
struct drm_encoder *encoder)
{
struct drm_encoder *enc;
int i;
drm_connector_for_each_possible_encoder(connector, enc, i) {
if (enc == encoder)
return true;
}
return false;
}
EXPORT_SYMBOL(drm_connector_has_possible_encoder);
static void drm_mode_remove(struct drm_connector *connector,
struct drm_display_mode *mode)
{
@ -1706,22 +1729,19 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
if (!connector)
return -ENOENT;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++)
if (connector->encoder_ids[i] != 0)
encoders_count++;
drm_connector_for_each_possible_encoder(connector, encoder, i)
encoders_count++;
if ((out_resp->count_encoders >= encoders_count) && encoders_count) {
copied = 0;
encoder_ptr = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr);
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] != 0) {
if (put_user(connector->encoder_ids[i],
encoder_ptr + copied)) {
ret = -EFAULT;
goto out;
}
copied++;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (put_user(encoder->base.id, encoder_ptr + copied)) {
ret = -EFAULT;
goto out;
}
copied++;
}
}
out_resp->count_encoders = encoders_count;

View File

@ -28,6 +28,7 @@
#include <linux/slab.h>
#include <linux/export.h>
#include <drm/drm_client.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_edid.h>
#include <drm/drm_atomic.h>
@ -164,6 +165,12 @@ int drm_debugfs_init(struct drm_minor *minor, int minor_id,
DRM_ERROR("Failed to create framebuffer debugfs file\n");
return ret;
}
ret = drm_client_debugfs_init(minor);
if (ret) {
DRM_ERROR("Failed to create client debugfs file\n");
return ret;
}
}
if (dev->driver->debugfs_init) {

View File

@ -139,6 +139,7 @@ static int crtc_crc_data_count(struct drm_crtc_crc *crc)
static void crtc_crc_cleanup(struct drm_crtc_crc *crc)
{
kfree(crc->entries);
crc->overflow = false;
crc->entries = NULL;
crc->head = 0;
crc->tail = 0;
@ -391,8 +392,14 @@ int drm_crtc_add_crc_entry(struct drm_crtc *crtc, bool has_frame,
tail = crc->tail;
if (CIRC_SPACE(head, tail, DRM_CRC_ENTRIES_NR) < 1) {
bool was_overflow = crc->overflow;
crc->overflow = true;
spin_unlock(&crc->lock);
DRM_ERROR("Overflow of CRC buffer, userspace reads too slow.\n");
if (!was_overflow)
DRM_ERROR("Overflow of CRC buffer, userspace reads too slow.\n");
return -ENOBUFS;
}

View File

@ -34,6 +34,7 @@
#include <linux/slab.h>
#include <linux/srcu.h>
#include <drm/drm_client.h>
#include <drm/drm_drv.h>
#include <drm/drmP.h>
@ -505,6 +506,8 @@ int drm_dev_init(struct drm_device *dev,
dev->driver = driver;
INIT_LIST_HEAD(&dev->filelist);
INIT_LIST_HEAD(&dev->filelist_internal);
INIT_LIST_HEAD(&dev->clientlist);
INIT_LIST_HEAD(&dev->ctxlist);
INIT_LIST_HEAD(&dev->vmalist);
INIT_LIST_HEAD(&dev->maplist);
@ -514,6 +517,7 @@ int drm_dev_init(struct drm_device *dev,
spin_lock_init(&dev->event_lock);
mutex_init(&dev->struct_mutex);
mutex_init(&dev->filelist_mutex);
mutex_init(&dev->clientlist_mutex);
mutex_init(&dev->ctxlist_mutex);
mutex_init(&dev->master_mutex);
@ -569,6 +573,7 @@ err_minors:
err_free:
mutex_destroy(&dev->master_mutex);
mutex_destroy(&dev->ctxlist_mutex);
mutex_destroy(&dev->clientlist_mutex);
mutex_destroy(&dev->filelist_mutex);
mutex_destroy(&dev->struct_mutex);
return ret;
@ -603,6 +608,7 @@ void drm_dev_fini(struct drm_device *dev)
mutex_destroy(&dev->master_mutex);
mutex_destroy(&dev->ctxlist_mutex);
mutex_destroy(&dev->clientlist_mutex);
mutex_destroy(&dev->filelist_mutex);
mutex_destroy(&dev->struct_mutex);
kfree(dev->unique);
@ -858,6 +864,8 @@ void drm_dev_unregister(struct drm_device *dev)
dev->registered = false;
drm_client_dev_unregister(dev);
if (drm_core_check_feature(dev, DRIVER_MODESET))
drm_modeset_unregister_all(dev);

View File

@ -18,6 +18,7 @@
*/
#include <drm/drmP.h>
#include <drm/drm_client.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_cma_helper.h>
@ -26,11 +27,8 @@
#include <drm/drm_print.h>
#include <linux/module.h>
#define DEFAULT_FBDEFIO_DELAY_MS 50
struct drm_fbdev_cma {
struct drm_fb_helper fb_helper;
const struct drm_framebuffer_funcs *fb_funcs;
};
/**
@ -44,36 +42,6 @@ struct drm_fbdev_cma {
*
* An fbdev framebuffer backed by cma is also available by calling
* drm_fb_cma_fbdev_init(). drm_fb_cma_fbdev_fini() tears it down.
* If the &drm_framebuffer_funcs.dirty callback is set, fb_deferred_io will be
* set up automatically. &drm_framebuffer_funcs.dirty is called by
* drm_fb_helper_deferred_io() in process context (&struct delayed_work).
*
* Example fbdev deferred io code::
*
* static int driver_fb_dirty(struct drm_framebuffer *fb,
* struct drm_file *file_priv,
* unsigned flags, unsigned color,
* struct drm_clip_rect *clips,
* unsigned num_clips)
* {
* struct drm_gem_cma_object *cma = drm_fb_cma_get_gem_obj(fb, 0);
* ... push changes ...
* return 0;
* }
*
* static struct drm_framebuffer_funcs driver_fb_funcs = {
* .destroy = drm_gem_fb_destroy,
* .create_handle = drm_gem_fb_create_handle,
* .dirty = driver_fb_dirty,
* };
*
* Initialize::
*
* fbdev = drm_fb_cma_fbdev_init_with_funcs(dev, 16,
* dev->mode_config.num_crtc,
* dev->mode_config.num_connector,
* &driver_fb_funcs);
*
*/
static inline struct drm_fbdev_cma *to_fbdev_cma(struct drm_fb_helper *helper)
@ -131,220 +99,6 @@ dma_addr_t drm_fb_cma_get_gem_addr(struct drm_framebuffer *fb,
}
EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_addr);
static int drm_fb_cma_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
return dma_mmap_writecombine(info->device, vma, info->screen_base,
info->fix.smem_start, info->fix.smem_len);
}
static struct fb_ops drm_fbdev_cma_ops = {
.owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS,
.fb_fillrect = drm_fb_helper_sys_fillrect,
.fb_copyarea = drm_fb_helper_sys_copyarea,
.fb_imageblit = drm_fb_helper_sys_imageblit,
.fb_mmap = drm_fb_cma_mmap,
};
static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info,
struct vm_area_struct *vma)
{
fb_deferred_io_mmap(info, vma);
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
return 0;
}
static int drm_fbdev_cma_defio_init(struct fb_info *fbi,
struct drm_gem_cma_object *cma_obj)
{
struct fb_deferred_io *fbdefio;
struct fb_ops *fbops;
/*
* Per device structures are needed because:
* fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap
* fbdefio: individual delays
*/
fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
if (!fbdefio || !fbops) {
kfree(fbdefio);
kfree(fbops);
return -ENOMEM;
}
/* can't be offset from vaddr since dirty() uses cma_obj */
fbi->screen_buffer = cma_obj->vaddr;
/* fb_deferred_io_fault() needs a physical address */
fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer));
*fbops = *fbi->fbops;
fbi->fbops = fbops;
fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS);
fbdefio->deferred_io = drm_fb_helper_deferred_io;
fbi->fbdefio = fbdefio;
fb_deferred_io_init(fbi);
fbi->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap;
return 0;
}
static void drm_fbdev_cma_defio_fini(struct fb_info *fbi)
{
if (!fbi->fbdefio)
return;
fb_deferred_io_cleanup(fbi);
kfree(fbi->fbdefio);
kfree(fbi->fbops);
}
static int
drm_fbdev_cma_create(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
struct drm_fbdev_cma *fbdev_cma = to_fbdev_cma(helper);
struct drm_device *dev = helper->dev;
struct drm_gem_cma_object *obj;
struct drm_framebuffer *fb;
unsigned int bytes_per_pixel;
unsigned long offset;
struct fb_info *fbi;
size_t size;
int ret;
DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
sizes->surface_width, sizes->surface_height,
sizes->surface_bpp);
bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
size = sizes->surface_width * sizes->surface_height * bytes_per_pixel;
obj = drm_gem_cma_create(dev, size);
if (IS_ERR(obj))
return -ENOMEM;
fbi = drm_fb_helper_alloc_fbi(helper);
if (IS_ERR(fbi)) {
ret = PTR_ERR(fbi);
goto err_gem_free_object;
}
fb = drm_gem_fbdev_fb_create(dev, sizes, 0, &obj->base,
fbdev_cma->fb_funcs);
if (IS_ERR(fb)) {
dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
ret = PTR_ERR(fb);
goto err_fb_info_destroy;
}
helper->fb = fb;
fbi->par = helper;
fbi->flags = FBINFO_FLAG_DEFAULT;
fbi->fbops = &drm_fbdev_cma_ops;
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth);
drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
offset = fbi->var.xoffset * bytes_per_pixel;
offset += fbi->var.yoffset * fb->pitches[0];
dev->mode_config.fb_base = (resource_size_t)obj->paddr;
fbi->screen_base = obj->vaddr + offset;
fbi->fix.smem_start = (unsigned long)(obj->paddr + offset);
fbi->screen_size = size;
fbi->fix.smem_len = size;
if (fb->funcs->dirty) {
ret = drm_fbdev_cma_defio_init(fbi, obj);
if (ret)
goto err_cma_destroy;
}
return 0;
err_cma_destroy:
drm_framebuffer_remove(fb);
err_fb_info_destroy:
drm_fb_helper_fini(helper);
err_gem_free_object:
drm_gem_object_put_unlocked(&obj->base);
return ret;
}
static const struct drm_fb_helper_funcs drm_fb_cma_helper_funcs = {
.fb_probe = drm_fbdev_cma_create,
};
/**
* drm_fb_cma_fbdev_init_with_funcs() - Allocate and initialize fbdev emulation
* @dev: DRM device
* @preferred_bpp: Preferred bits per pixel for the device.
* @dev->mode_config.preferred_depth is used if this is zero.
* @max_conn_count: Maximum number of connectors.
* @dev->mode_config.num_connector is used if this is zero.
* @funcs: Framebuffer functions, in particular a custom dirty() callback.
* Can be NULL.
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_fb_cma_fbdev_init_with_funcs(struct drm_device *dev,
unsigned int preferred_bpp, unsigned int max_conn_count,
const struct drm_framebuffer_funcs *funcs)
{
struct drm_fbdev_cma *fbdev_cma;
struct drm_fb_helper *fb_helper;
int ret;
if (!preferred_bpp)
preferred_bpp = dev->mode_config.preferred_depth;
if (!preferred_bpp)
preferred_bpp = 32;
if (!max_conn_count)
max_conn_count = dev->mode_config.num_connector;
fbdev_cma = kzalloc(sizeof(*fbdev_cma), GFP_KERNEL);
if (!fbdev_cma)
return -ENOMEM;
fbdev_cma->fb_funcs = funcs;
fb_helper = &fbdev_cma->fb_helper;
drm_fb_helper_prepare(dev, fb_helper, &drm_fb_cma_helper_funcs);
ret = drm_fb_helper_init(dev, fb_helper, max_conn_count);
if (ret < 0) {
DRM_DEV_ERROR(dev->dev, "Failed to initialize fbdev helper.\n");
goto err_free;
}
ret = drm_fb_helper_single_add_all_connectors(fb_helper);
if (ret < 0) {
DRM_DEV_ERROR(dev->dev, "Failed to add connectors.\n");
goto err_drm_fb_helper_fini;
}
ret = drm_fb_helper_initial_config(fb_helper, preferred_bpp);
if (ret < 0) {
DRM_DEV_ERROR(dev->dev, "Failed to set fbdev configuration.\n");
goto err_drm_fb_helper_fini;
}
return 0;
err_drm_fb_helper_fini:
drm_fb_helper_fini(fb_helper);
err_free:
kfree(fbdev_cma);
return ret;
}
EXPORT_SYMBOL_GPL(drm_fb_cma_fbdev_init_with_funcs);
/**
* drm_fb_cma_fbdev_init() - Allocate and initialize fbdev emulation
* @dev: DRM device
@ -359,8 +113,14 @@ EXPORT_SYMBOL_GPL(drm_fb_cma_fbdev_init_with_funcs);
int drm_fb_cma_fbdev_init(struct drm_device *dev, unsigned int preferred_bpp,
unsigned int max_conn_count)
{
return drm_fb_cma_fbdev_init_with_funcs(dev, preferred_bpp,
max_conn_count, NULL);
struct drm_fbdev_cma *fbdev_cma;
/* dev->fb_helper will indirectly point to fbdev_cma after this call */
fbdev_cma = drm_fbdev_cma_init(dev, preferred_bpp, max_conn_count);
if (IS_ERR(fbdev_cma))
return PTR_ERR(fbdev_cma);
return 0;
}
EXPORT_SYMBOL_GPL(drm_fb_cma_fbdev_init);
@ -370,87 +130,13 @@ EXPORT_SYMBOL_GPL(drm_fb_cma_fbdev_init);
*/
void drm_fb_cma_fbdev_fini(struct drm_device *dev)
{
struct drm_fb_helper *fb_helper = dev->fb_helper;
if (!fb_helper)
return;
/* Unregister if it hasn't been done already */
if (fb_helper->fbdev && fb_helper->fbdev->dev)
drm_fb_helper_unregister_fbi(fb_helper);
if (fb_helper->fbdev)
drm_fbdev_cma_defio_fini(fb_helper->fbdev);
if (fb_helper->fb)
drm_framebuffer_remove(fb_helper->fb);
drm_fb_helper_fini(fb_helper);
kfree(to_fbdev_cma(fb_helper));
if (dev->fb_helper)
drm_fbdev_cma_fini(to_fbdev_cma(dev->fb_helper));
}
EXPORT_SYMBOL_GPL(drm_fb_cma_fbdev_fini);
/**
* drm_fbdev_cma_init_with_funcs() - Allocate and initializes a drm_fbdev_cma struct
* @dev: DRM device
* @preferred_bpp: Preferred bits per pixel for the device
* @max_conn_count: Maximum number of connectors
* @funcs: fb helper functions, in particular a custom dirty() callback
*
* Returns a newly allocated drm_fbdev_cma struct or a ERR_PTR.
*/
struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
unsigned int preferred_bpp, unsigned int max_conn_count,
const struct drm_framebuffer_funcs *funcs)
{
struct drm_fbdev_cma *fbdev_cma;
struct drm_fb_helper *helper;
int ret;
fbdev_cma = kzalloc(sizeof(*fbdev_cma), GFP_KERNEL);
if (!fbdev_cma) {
dev_err(dev->dev, "Failed to allocate drm fbdev.\n");
return ERR_PTR(-ENOMEM);
}
fbdev_cma->fb_funcs = funcs;
helper = &fbdev_cma->fb_helper;
drm_fb_helper_prepare(dev, helper, &drm_fb_cma_helper_funcs);
ret = drm_fb_helper_init(dev, helper, max_conn_count);
if (ret < 0) {
dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
goto err_free;
}
ret = drm_fb_helper_single_add_all_connectors(helper);
if (ret < 0) {
dev_err(dev->dev, "Failed to add connectors.\n");
goto err_drm_fb_helper_fini;
}
ret = drm_fb_helper_initial_config(helper, preferred_bpp);
if (ret < 0) {
dev_err(dev->dev, "Failed to set initial hw configuration.\n");
goto err_drm_fb_helper_fini;
}
return fbdev_cma;
err_drm_fb_helper_fini:
drm_fb_helper_fini(helper);
err_free:
kfree(fbdev_cma);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(drm_fbdev_cma_init_with_funcs);
static const struct drm_framebuffer_funcs drm_fb_cma_funcs = {
.destroy = drm_gem_fb_destroy,
.create_handle = drm_gem_fb_create_handle,
static const struct drm_fb_helper_funcs drm_fb_cma_helper_funcs = {
.fb_probe = drm_fb_helper_generic_probe,
};
/**
@ -464,9 +150,33 @@ static const struct drm_framebuffer_funcs drm_fb_cma_funcs = {
struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
unsigned int preferred_bpp, unsigned int max_conn_count)
{
return drm_fbdev_cma_init_with_funcs(dev, preferred_bpp,
max_conn_count,
&drm_fb_cma_funcs);
struct drm_fbdev_cma *fbdev_cma;
struct drm_fb_helper *fb_helper;
int ret;
fbdev_cma = kzalloc(sizeof(*fbdev_cma), GFP_KERNEL);
if (!fbdev_cma)
return ERR_PTR(-ENOMEM);
fb_helper = &fbdev_cma->fb_helper;
ret = drm_client_new(dev, &fb_helper->client, "fbdev", NULL);
if (ret)
goto err_free;
ret = drm_fb_helper_fbdev_setup(dev, fb_helper, &drm_fb_cma_helper_funcs,
preferred_bpp, max_conn_count);
if (ret)
goto err_client_put;
return fbdev_cma;
err_client_put:
drm_client_release(&fb_helper->client);
err_free:
kfree(fbdev_cma);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(drm_fbdev_cma_init);
@ -477,14 +187,7 @@ EXPORT_SYMBOL_GPL(drm_fbdev_cma_init);
void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma)
{
drm_fb_helper_unregister_fbi(&fbdev_cma->fb_helper);
if (fbdev_cma->fb_helper.fbdev)
drm_fbdev_cma_defio_fini(fbdev_cma->fb_helper.fbdev);
if (fbdev_cma->fb_helper.fb)
drm_framebuffer_remove(fbdev_cma->fb_helper.fb);
drm_fb_helper_fini(&fbdev_cma->fb_helper);
kfree(fbdev_cma);
/* All resources have now been freed by drm_fbdev_fb_destroy() */
}
EXPORT_SYMBOL_GPL(drm_fbdev_cma_fini);

View File

@ -30,6 +30,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/console.h>
#include <linux/dma-buf.h>
#include <linux/kernel.h>
#include <linux/sysrq.h>
#include <linux/slab.h>
@ -66,6 +67,9 @@ static DEFINE_MUTEX(kernel_fb_helper_lock);
* helper functions used by many drivers to implement the kernel mode setting
* interfaces.
*
* Drivers that support a dumb buffer with a virtual address and mmap support,
* should try out the generic fbdev emulation using drm_fbdev_generic_setup().
*
* Setup fbdev emulation by calling drm_fb_helper_fbdev_setup() and tear it
* down by calling drm_fb_helper_fbdev_teardown().
*
@ -738,6 +742,24 @@ static void drm_fb_helper_resume_worker(struct work_struct *work)
console_unlock();
}
static void drm_fb_helper_dirty_blit_real(struct drm_fb_helper *fb_helper,
struct drm_clip_rect *clip)
{
struct drm_framebuffer *fb = fb_helper->fb;
unsigned int cpp = drm_format_plane_cpp(fb->format->format, 0);
size_t offset = clip->y1 * fb->pitches[0] + clip->x1 * cpp;
void *src = fb_helper->fbdev->screen_buffer + offset;
void *dst = fb_helper->buffer->vaddr + offset;
size_t len = (clip->x2 - clip->x1) * cpp;
unsigned int y;
for (y = clip->y1; y < clip->y2; y++) {
memcpy(dst, src, len);
src += fb->pitches[0];
dst += fb->pitches[0];
}
}
static void drm_fb_helper_dirty_work(struct work_struct *work)
{
struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper,
@ -753,8 +775,12 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
spin_unlock_irqrestore(&helper->dirty_lock, flags);
/* call dirty callback only when it has been really touched */
if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2)
if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2) {
/* Generic fbdev uses a shadow buffer */
if (helper->buffer)
drm_fb_helper_dirty_blit_real(helper, &clip_copy);
helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
}
}
/**
@ -2323,6 +2349,20 @@ retry:
return true;
}
static bool connector_has_possible_crtc(struct drm_connector *connector,
struct drm_crtc *crtc)
{
struct drm_encoder *encoder;
int i;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (encoder->possible_crtcs & drm_crtc_mask(crtc))
return true;
}
return false;
}
static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
struct drm_fb_helper_crtc **best_crtcs,
struct drm_display_mode **modes,
@ -2331,7 +2371,6 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
int c, o;
struct drm_connector *connector;
const struct drm_connector_helper_funcs *connector_funcs;
struct drm_encoder *encoder;
int my_score, best_score, score;
struct drm_fb_helper_crtc **crtcs, *crtc;
struct drm_fb_helper_connector *fb_helper_conn;
@ -2362,20 +2401,6 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
connector_funcs = connector->helper_private;
/*
* If the DRM device implements atomic hooks and ->best_encoder() is
* NULL we fallback to the default drm_atomic_helper_best_encoder()
* helper.
*/
if (drm_drv_uses_atomic_modeset(fb_helper->dev) &&
!connector_funcs->best_encoder)
encoder = drm_atomic_helper_best_encoder(connector);
else
encoder = connector_funcs->best_encoder(connector);
if (!encoder)
goto out;
/*
* select a crtc for this connector and then attempt to configure
* remaining connectors
@ -2383,7 +2408,8 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
for (c = 0; c < fb_helper->crtc_count; c++) {
crtc = &fb_helper->crtc_info[c];
if ((encoder->possible_crtcs & (1 << c)) == 0)
if (!connector_has_possible_crtc(connector,
crtc->mode_set.crtc))
continue;
for (o = 0; o < n; o++)
@ -2410,7 +2436,7 @@ static int drm_pick_crtcs(struct drm_fb_helper *fb_helper,
sizeof(struct drm_fb_helper_crtc *));
}
}
out:
kfree(crtcs);
return best_score;
}
@ -2921,6 +2947,294 @@ void drm_fb_helper_output_poll_changed(struct drm_device *dev)
}
EXPORT_SYMBOL(drm_fb_helper_output_poll_changed);
/* @user: 1=userspace, 0=fbcon */
static int drm_fbdev_fb_open(struct fb_info *info, int user)
{
struct drm_fb_helper *fb_helper = info->par;
if (!try_module_get(fb_helper->dev->driver->fops->owner))
return -ENODEV;
return 0;
}
static int drm_fbdev_fb_release(struct fb_info *info, int user)
{
struct drm_fb_helper *fb_helper = info->par;
module_put(fb_helper->dev->driver->fops->owner);
return 0;
}
/*
* fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
* unregister_framebuffer() or fb_release().
*/
static void drm_fbdev_fb_destroy(struct fb_info *info)
{
struct drm_fb_helper *fb_helper = info->par;
struct fb_info *fbi = fb_helper->fbdev;
struct fb_ops *fbops = NULL;
void *shadow = NULL;
if (fbi->fbdefio) {
fb_deferred_io_cleanup(fbi);
shadow = fbi->screen_buffer;
fbops = fbi->fbops;
}
drm_fb_helper_fini(fb_helper);
if (shadow) {
vfree(shadow);
kfree(fbops);
}
drm_client_framebuffer_delete(fb_helper->buffer);
/*
* FIXME:
* Remove conditional when all CMA drivers have been moved over to using
* drm_fbdev_generic_setup().
*/
if (fb_helper->client.funcs) {
drm_client_release(&fb_helper->client);
kfree(fb_helper);
}
}
static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct drm_fb_helper *fb_helper = info->par;
if (fb_helper->dev->driver->gem_prime_mmap)
return fb_helper->dev->driver->gem_prime_mmap(fb_helper->buffer->gem, vma);
else
return -ENODEV;
}
static struct fb_ops drm_fbdev_fb_ops = {
.owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS,
.fb_open = drm_fbdev_fb_open,
.fb_release = drm_fbdev_fb_release,
.fb_destroy = drm_fbdev_fb_destroy,
.fb_mmap = drm_fbdev_fb_mmap,
.fb_read = drm_fb_helper_sys_read,
.fb_write = drm_fb_helper_sys_write,
.fb_fillrect = drm_fb_helper_sys_fillrect,
.fb_copyarea = drm_fb_helper_sys_copyarea,
.fb_imageblit = drm_fb_helper_sys_imageblit,
};
static struct fb_deferred_io drm_fbdev_defio = {
.delay = HZ / 20,
.deferred_io = drm_fb_helper_deferred_io,
};
/**
* drm_fb_helper_generic_probe - Generic fbdev emulation probe helper
* @fb_helper: fbdev helper structure
* @sizes: describes fbdev size and scanout surface size
*
* This function uses the client API to crate a framebuffer backed by a dumb buffer.
*
* The _sys_ versions are used for &fb_ops.fb_read, fb_write, fb_fillrect,
* fb_copyarea, fb_imageblit.
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
struct drm_fb_helper_surface_size *sizes)
{
struct drm_client_dev *client = &fb_helper->client;
struct drm_client_buffer *buffer;
struct drm_framebuffer *fb;
struct fb_info *fbi;
u32 format;
int ret;
DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
sizes->surface_width, sizes->surface_height,
sizes->surface_bpp);
format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth);
buffer = drm_client_framebuffer_create(client, sizes->surface_width,
sizes->surface_height, format);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
fb_helper->buffer = buffer;
fb_helper->fb = buffer->fb;
fb = buffer->fb;
fbi = drm_fb_helper_alloc_fbi(fb_helper);
if (IS_ERR(fbi)) {
ret = PTR_ERR(fbi);
goto err_free_buffer;
}
fbi->par = fb_helper;
fbi->fbops = &drm_fbdev_fb_ops;
fbi->screen_size = fb->height * fb->pitches[0];
fbi->fix.smem_len = fbi->screen_size;
fbi->screen_buffer = buffer->vaddr;
strcpy(fbi->fix.id, "DRM emulated");
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth);
drm_fb_helper_fill_var(fbi, fb_helper, sizes->fb_width, sizes->fb_height);
if (fb->funcs->dirty) {
struct fb_ops *fbops;
void *shadow;
/*
* fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per
* instance version is necessary.
*/
fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
shadow = vzalloc(fbi->screen_size);
if (!fbops || !shadow) {
kfree(fbops);
vfree(shadow);
ret = -ENOMEM;
goto err_fb_info_destroy;
}
*fbops = *fbi->fbops;
fbi->fbops = fbops;
fbi->screen_buffer = shadow;
fbi->fbdefio = &drm_fbdev_defio;
fb_deferred_io_init(fbi);
}
return 0;
err_fb_info_destroy:
drm_fb_helper_fini(fb_helper);
err_free_buffer:
drm_client_framebuffer_delete(buffer);
return ret;
}
EXPORT_SYMBOL(drm_fb_helper_generic_probe);
static const struct drm_fb_helper_funcs drm_fb_helper_generic_funcs = {
.fb_probe = drm_fb_helper_generic_probe,
};
static void drm_fbdev_client_unregister(struct drm_client_dev *client)
{
struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
if (fb_helper->fbdev) {
drm_fb_helper_unregister_fbi(fb_helper);
/* drm_fbdev_fb_destroy() takes care of cleanup */
return;
}
/* Did drm_fb_helper_fbdev_setup() run? */
if (fb_helper->dev)
drm_fb_helper_fini(fb_helper);
drm_client_release(client);
kfree(fb_helper);
}
static int drm_fbdev_client_restore(struct drm_client_dev *client)
{
struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
return 0;
}
static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
{
struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
struct drm_device *dev = client->dev;
int ret;
/* If drm_fb_helper_fbdev_setup() failed, we only try once */
if (!fb_helper->dev && fb_helper->funcs)
return 0;
if (dev->fb_helper)
return drm_fb_helper_hotplug_event(dev->fb_helper);
if (!dev->mode_config.num_connector)
return 0;
ret = drm_fb_helper_fbdev_setup(dev, fb_helper, &drm_fb_helper_generic_funcs,
fb_helper->preferred_bpp, 0);
if (ret) {
fb_helper->dev = NULL;
fb_helper->fbdev = NULL;
return ret;
}
return 0;
}
static const struct drm_client_funcs drm_fbdev_client_funcs = {
.owner = THIS_MODULE,
.unregister = drm_fbdev_client_unregister,
.restore = drm_fbdev_client_restore,
.hotplug = drm_fbdev_client_hotplug,
};
/**
* drm_fb_helper_generic_fbdev_setup() - Setup generic fbdev emulation
* @dev: DRM device
* @preferred_bpp: Preferred bits per pixel for the device.
* @dev->mode_config.preferred_depth is used if this is zero.
*
* This function sets up generic fbdev emulation for drivers that supports
* dumb buffers with a virtual address and that can be mmap'ed.
*
* Restore, hotplug events and teardown are all taken care of. Drivers that do
* suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves.
* Simple drivers might use drm_mode_config_helper_suspend().
*
* Drivers that set the dirty callback on their framebuffer will get a shadow
* fbdev buffer that is blitted onto the real buffer. This is done in order to
* make deferred I/O work with all kinds of buffers.
*
* This function is safe to call even when there are no connectors present.
* Setup will be retried on the next hotplug event.
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
{
struct drm_fb_helper *fb_helper;
int ret;
if (!drm_fbdev_emulation)
return 0;
fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL);
if (!fb_helper)
return -ENOMEM;
ret = drm_client_new(dev, &fb_helper->client, "fbdev", &drm_fbdev_client_funcs);
if (ret) {
kfree(fb_helper);
return ret;
}
fb_helper->preferred_bpp = preferred_bpp;
drm_fbdev_client_hotplug(&fb_helper->client);
return 0;
}
EXPORT_SYMBOL(drm_fbdev_generic_setup);
/* The Kconfig DRM_KMS_HELPER selects FRAMEBUFFER_CONSOLE (if !EXPERT)
* but the module doesn't depend on any fb console symbols. At least
* attempt to load fbcon to avoid leaving the system without a usable console.

View File

@ -35,6 +35,7 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <drm/drm_client.h>
#include <drm/drm_file.h>
#include <drm/drmP.h>
@ -444,6 +445,8 @@ void drm_lastclose(struct drm_device * dev)
if (drm_core_check_feature(dev, DRIVER_LEGACY))
drm_legacy_dev_reinit(dev);
drm_client_dev_restore(dev);
}
/**

View File

@ -659,10 +659,12 @@ EXPORT_SYMBOL_GPL(drm_display_mode_to_videomode);
* drm_bus_flags_from_videomode - extract information about pixelclk and
* DE polarity from videomode and store it in a separate variable
* @vm: videomode structure to use
* @bus_flags: information about pixelclk and DE polarity will be stored here
* @bus_flags: information about pixelclk, sync and DE polarity will be stored
* here
*
* Sets DRM_BUS_FLAG_DE_(LOW|HIGH) and DRM_BUS_FLAG_PIXDATA_(POS|NEG)EDGE
* in @bus_flags according to DISPLAY_FLAGS found in @vm
* Sets DRM_BUS_FLAG_DE_(LOW|HIGH), DRM_BUS_FLAG_PIXDATA_(POS|NEG)EDGE and
* DISPLAY_FLAGS_SYNC_(POS|NEG)EDGE in @bus_flags according to DISPLAY_FLAGS
* found in @vm
*/
void drm_bus_flags_from_videomode(const struct videomode *vm, u32 *bus_flags)
{
@ -672,6 +674,11 @@ void drm_bus_flags_from_videomode(const struct videomode *vm, u32 *bus_flags)
if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
*bus_flags |= DRM_BUS_FLAG_PIXDATA_NEGEDGE;
if (vm->flags & DISPLAY_FLAGS_SYNC_POSEDGE)
*bus_flags |= DRM_BUS_FLAG_SYNC_POSEDGE;
if (vm->flags & DISPLAY_FLAGS_SYNC_NEGEDGE)
*bus_flags |= DRM_BUS_FLAG_SYNC_NEGEDGE;
if (vm->flags & DISPLAY_FLAGS_DE_LOW)
*bus_flags |= DRM_BUS_FLAG_DE_LOW;
if (vm->flags & DISPLAY_FLAGS_DE_HIGH)
@ -684,7 +691,7 @@ EXPORT_SYMBOL_GPL(drm_bus_flags_from_videomode);
* of_get_drm_display_mode - get a drm_display_mode from devicetree
* @np: device_node with the timing specification
* @dmode: will be set to the return value
* @bus_flags: information about pixelclk and DE polarity
* @bus_flags: information about pixelclk, sync and DE polarity
* @index: index into the list of display timings in devicetree
*
* This function is expensive and should only be used, if only one mode is to be

View File

@ -239,10 +239,17 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
if (!remote)
return -ENODEV;
if (!of_device_is_available(remote)) {
of_node_put(remote);
return -ENODEV;
}
if (panel) {
*panel = of_drm_find_panel(remote);
if (*panel)
if (!IS_ERR(*panel))
ret = 0;
else
*panel = NULL;
}
/* No panel found yet, check for a bridge next. */

View File

@ -151,12 +151,19 @@ EXPORT_SYMBOL(drm_panel_detach);
* tree node. If a matching panel is found, return a pointer to it.
*
* Return: A pointer to the panel registered for the specified device tree
* node or NULL if no panel matching the device tree node can be found.
* node or an ERR_PTR() if no panel matching the device tree node can be found.
* Possible error codes returned by this function:
* - EPROBE_DEFER: the panel device has not been probed yet, and the caller
* should retry later
* - ENODEV: the device is not available (status != "okay" or "ok")
*/
struct drm_panel *of_drm_find_panel(const struct device_node *np)
{
struct drm_panel *panel;
if (!of_device_is_available(np))
return ERR_PTR(-ENODEV);
mutex_lock(&panel_lock);
list_for_each_entry(panel, &panel_list, list) {
@ -167,7 +174,7 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np)
}
mutex_unlock(&panel_lock);
return NULL;
return ERR_PTR(-EPROBE_DEFER);
}
EXPORT_SYMBOL(of_drm_find_panel);
#endif

View File

@ -33,6 +33,7 @@
#include <linux/moduleparam.h>
#include <drm/drmP.h>
#include <drm/drm_client.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_crtc_helper.h>
@ -88,9 +89,9 @@ drm_mode_validate_pipeline(struct drm_display_mode *mode,
struct drm_connector *connector)
{
struct drm_device *dev = connector->dev;
uint32_t *ids = connector->encoder_ids;
enum drm_mode_status ret = MODE_OK;
unsigned int i;
struct drm_encoder *encoder;
int i;
/* Step 1: Validate against connector */
ret = drm_connector_mode_valid(connector, mode);
@ -98,13 +99,9 @@ drm_mode_validate_pipeline(struct drm_display_mode *mode,
return ret;
/* Step 2: Validate against encoders and crtcs */
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
struct drm_encoder *encoder = drm_encoder_find(dev, NULL, ids[i]);
drm_connector_for_each_possible_encoder(connector, encoder, i) {
struct drm_crtc *crtc;
if (!encoder)
continue;
ret = drm_encoder_mode_valid(encoder, mode);
if (ret != MODE_OK) {
/* No point in continuing for crtc check as this encoder
@ -563,6 +560,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
drm_sysfs_hotplug_event(dev);
if (dev->mode_config.funcs->output_poll_changed)
dev->mode_config.funcs->output_poll_changed(dev);
drm_client_dev_hotplug(dev);
}
EXPORT_SYMBOL(drm_kms_helper_hotplug_event);

View File

@ -22,10 +22,13 @@
* Writeback connectors are used to expose hardware which can write the output
* from a CRTC to a memory buffer. They are used and act similarly to other
* types of connectors, with some important differences:
* - Writeback connectors don't provide a way to output visually to the user.
* - Writeback connectors should always report as "disconnected" (so that
* clients which don't understand them will ignore them).
* - Writeback connectors don't have EDID.
*
* * Writeback connectors don't provide a way to output visually to the user.
*
* * Writeback connectors should always report as "disconnected" (so that
* clients which don't understand them will ignore them).
*
* * Writeback connectors don't have EDID.
*
* A framebuffer may only be attached to a writeback connector when the
* connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the

View File

@ -232,9 +232,11 @@ static int exynos_dp_probe(struct platform_device *pdev)
np = of_parse_phandle(dev->of_node, "panel", 0);
if (np) {
dp->plat_data.panel = of_drm_find_panel(np);
of_node_put(np);
if (!dp->plat_data.panel)
return -EPROBE_DEFER;
if (IS_ERR(dp->plat_data.panel))
return PTR_ERR(dp->plat_data.panel);
goto out;
}

View File

@ -240,8 +240,8 @@ struct drm_encoder *exynos_dpi_probe(struct device *dev)
if (ctx->panel_node) {
ctx->panel = of_drm_find_panel(ctx->panel_node);
if (!ctx->panel)
return ERR_PTR(-EPROBE_DEFER);
if (IS_ERR(ctx->panel))
return ERR_CAST(ctx->panel);
}
return &ctx->encoder;

View File

@ -1519,6 +1519,9 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
dsi->format = device->format;
dsi->mode_flags = device->mode_flags;
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(dsi->panel))
dsi->panel = NULL;
if (dsi->panel) {
drm_panel_attach(dsi->panel, &dsi->connector);
dsi->connector.status = connector_status_connected;

View File

@ -148,8 +148,9 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
if (panel_node) {
fsl_dev->connector.panel = of_drm_find_panel(panel_node);
of_node_put(panel_node);
if (!fsl_dev->connector.panel)
return -EPROBE_DEFER;
if (IS_ERR(fsl_dev->connector.panel))
return PTR_ERR(fsl_dev->connector.panel);
return fsl_dcu_attach_panel(fsl_dev, fsl_dev->connector.panel);
}

View File

@ -403,20 +403,10 @@ static struct drm_encoder *intel_mst_atomic_best_encoder(struct drm_connector *c
return &intel_dp->mst_encoders[crtc->pipe]->base.base;
}
static struct drm_encoder *intel_mst_best_encoder(struct drm_connector *connector)
{
struct intel_connector *intel_connector = to_intel_connector(connector);
struct intel_dp *intel_dp = intel_connector->mst_port;
if (!intel_dp)
return NULL;
return &intel_dp->mst_encoders[0]->base.base;
}
static const struct drm_connector_helper_funcs intel_dp_mst_connector_helper_funcs = {
.get_modes = intel_dp_mst_get_modes,
.mode_valid = intel_dp_mst_mode_valid,
.atomic_best_encoder = intel_mst_atomic_best_encoder,
.best_encoder = intel_mst_best_encoder,
.atomic_check = intel_dp_mst_atomic_check,
};

View File

@ -341,7 +341,7 @@ static void mdp4_lcdc_encoder_disable(struct drm_encoder *encoder)
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0);
panel = of_drm_find_panel(mdp4_lcdc_encoder->panel_node);
if (panel) {
if (!IS_ERR(panel)) {
drm_panel_disable(panel);
drm_panel_unprepare(panel);
}
@ -410,7 +410,7 @@ static void mdp4_lcdc_encoder_enable(struct drm_encoder *encoder)
dev_err(dev->dev, "failed to enable lcdc_clk: %d\n", ret);
panel = of_drm_find_panel(mdp4_lcdc_encoder->panel_node);
if (panel) {
if (!IS_ERR(panel)) {
drm_panel_prepare(panel);
drm_panel_enable(panel);
}

View File

@ -34,9 +34,12 @@ static enum drm_connector_status mdp4_lvds_connector_detect(
struct mdp4_lvds_connector *mdp4_lvds_connector =
to_mdp4_lvds_connector(connector);
if (!mdp4_lvds_connector->panel)
if (!mdp4_lvds_connector->panel) {
mdp4_lvds_connector->panel =
of_drm_find_panel(mdp4_lvds_connector->panel_node);
if (IS_ERR(mdp4_lvds_connector->panel))
mdp4_lvds_connector->panel = NULL;
}
return mdp4_lvds_connector->panel ?
connector_status_connected :

View File

@ -1898,7 +1898,7 @@ int msm_dsi_host_register(struct mipi_dsi_host *host, bool check_defer)
* output
*/
if (check_defer && msm_host->device_node) {
if (!of_drm_find_panel(msm_host->device_node))
if (IS_ERR(of_drm_find_panel(msm_host->device_node)))
if (!of_drm_find_bridge(msm_host->device_node))
return -EPROBE_DEFER;
}

View File

@ -751,12 +751,8 @@ struct drm_connector *msm_dsi_manager_ext_bridge_init(u8 id)
connector_list = &dev->mode_config.connector_list;
list_for_each_entry(connector, connector_list, head) {
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == encoder->base.id)
return connector;
}
if (drm_connector_has_possible_encoder(connector, encoder))
return connector;
}
return ERR_PTR(-ENODEV);

View File

@ -363,19 +363,11 @@ module_param_named(hdmimhz, nouveau_hdmimhz, int, 0400);
struct nouveau_encoder *
find_encoder(struct drm_connector *connector, int type)
{
struct drm_device *dev = connector->dev;
struct nouveau_encoder *nv_encoder;
struct drm_encoder *enc;
int i, id;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
id = connector->encoder_ids[i];
if (!id)
break;
enc = drm_encoder_find(dev, NULL, id);
if (!enc)
continue;
drm_connector_for_each_possible_encoder(connector, enc, i) {
nv_encoder = nouveau_encoder(enc);
if (type == DCB_OUTPUT_ANY ||
@ -420,7 +412,7 @@ nouveau_connector_ddc_detect(struct drm_connector *connector)
struct nouveau_connector *nv_connector = nouveau_connector(connector);
struct nouveau_drm *drm = nouveau_drm(dev);
struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device);
struct nouveau_encoder *nv_encoder;
struct nouveau_encoder *nv_encoder = NULL;
struct drm_encoder *encoder;
int i, panel = -ENODEV;
@ -436,14 +428,7 @@ nouveau_connector_ddc_detect(struct drm_connector *connector)
}
}
for (i = 0; nv_encoder = NULL, i < DRM_CONNECTOR_MAX_ENCODER; i++) {
int id = connector->encoder_ids[i];
if (id == 0)
break;
encoder = drm_encoder_find(dev, NULL, id);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
nv_encoder = nouveau_encoder(encoder);
if (nv_encoder->dcb->type == DCB_OUTPUT_DP) {

View File

@ -334,7 +334,7 @@ static int ili9881c_prepare(struct drm_panel *panel)
if (ret)
return ret;
mipi_dsi_dcs_exit_sleep_mode(ctx->dsi);
ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi);
if (ret)
return ret;

View File

@ -11,6 +11,7 @@
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regulator/consumer.h>
#include <drm/drmP.h>
@ -20,12 +21,41 @@
#include <video/mipi_display.h>
struct panel_init_cmd {
size_t len;
const char *data;
};
#define _INIT_CMD(...) { \
.len = sizeof((char[]){__VA_ARGS__}), \
.data = (char[]){__VA_ARGS__} }
struct panel_desc {
const struct drm_display_mode *mode;
unsigned int bpc;
struct {
unsigned int width;
unsigned int height;
} size;
unsigned long flags;
enum mipi_dsi_pixel_format format;
const struct panel_init_cmd *init_cmds;
unsigned int lanes;
const char * const *supply_names;
unsigned int num_supplies;
unsigned int sleep_mode_delay;
unsigned int power_down_delay;
};
struct innolux_panel {
struct drm_panel base;
struct mipi_dsi_device *link;
const struct panel_desc *desc;
struct backlight_device *backlight;
struct regulator *supply;
struct regulator_bulk_data *supplies;
unsigned int num_supplies;
struct gpio_desc *enable_gpio;
bool prepared;
@ -72,12 +102,16 @@ static int innolux_panel_unprepare(struct drm_panel *panel)
return err;
}
if (innolux->desc->sleep_mode_delay)
msleep(innolux->desc->sleep_mode_delay);
gpiod_set_value_cansleep(innolux->enable_gpio, 0);
/* T8: 80ms - 1000ms */
msleep(80);
if (innolux->desc->power_down_delay)
msleep(innolux->desc->power_down_delay);
err = regulator_disable(innolux->supply);
err = regulator_bulk_disable(innolux->desc->num_supplies,
innolux->supplies);
if (err < 0)
return err;
@ -89,24 +123,55 @@ static int innolux_panel_unprepare(struct drm_panel *panel)
static int innolux_panel_prepare(struct drm_panel *panel)
{
struct innolux_panel *innolux = to_innolux_panel(panel);
int err, regulator_err;
int err;
if (innolux->prepared)
return 0;
gpiod_set_value_cansleep(innolux->enable_gpio, 0);
err = regulator_enable(innolux->supply);
err = regulator_bulk_enable(innolux->desc->num_supplies,
innolux->supplies);
if (err < 0)
return err;
/* T2: 15ms - 1000ms */
usleep_range(15000, 16000);
/* p079zca: t2 (20ms), p097pfg: t4 (15ms) */
usleep_range(20000, 21000);
gpiod_set_value_cansleep(innolux->enable_gpio, 1);
/* T4: 15ms - 1000ms */
usleep_range(15000, 16000);
/* p079zca: t4, p097pfg: t5 */
usleep_range(20000, 21000);
if (innolux->desc->init_cmds) {
const struct panel_init_cmd *cmds =
innolux->desc->init_cmds;
unsigned int i;
for (i = 0; cmds[i].len != 0; i++) {
const struct panel_init_cmd *cmd = &cmds[i];
err = mipi_dsi_generic_write(innolux->link, cmd->data,
cmd->len);
if (err < 0) {
dev_err(panel->dev,
"failed to write command %u\n", i);
goto poweroff;
}
/*
* Included by random guessing, because without this
* (or at least, some delay), the panel sometimes
* didn't appear to pick up the command sequence.
*/
err = mipi_dsi_dcs_nop(innolux->link);
if (err < 0) {
dev_err(panel->dev,
"failed to send DCS nop: %d\n", err);
goto poweroff;
}
}
}
err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
if (err < 0) {
@ -133,12 +198,9 @@ static int innolux_panel_prepare(struct drm_panel *panel)
return 0;
poweroff:
regulator_err = regulator_disable(innolux->supply);
if (regulator_err)
DRM_DEV_ERROR(panel->dev, "failed to disable regulator: %d\n",
regulator_err);
gpiod_set_value_cansleep(innolux->enable_gpio, 0);
regulator_bulk_disable(innolux->desc->num_supplies, innolux->supplies);
return err;
}
@ -162,7 +224,11 @@ static int innolux_panel_enable(struct drm_panel *panel)
return 0;
}
static const struct drm_display_mode default_mode = {
static const char * const innolux_p079zca_supply_names[] = {
"power",
};
static const struct drm_display_mode innolux_p079zca_mode = {
.clock = 56900,
.hdisplay = 768,
.hsync_start = 768 + 40,
@ -175,15 +241,181 @@ static const struct drm_display_mode default_mode = {
.vrefresh = 60,
};
static const struct panel_desc innolux_p079zca_panel_desc = {
.mode = &innolux_p079zca_mode,
.bpc = 8,
.size = {
.width = 120,
.height = 160,
},
.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_LPM,
.format = MIPI_DSI_FMT_RGB888,
.lanes = 4,
.supply_names = innolux_p079zca_supply_names,
.num_supplies = ARRAY_SIZE(innolux_p079zca_supply_names),
.power_down_delay = 80, /* T8: 80ms - 1000ms */
};
static const char * const innolux_p097pfg_supply_names[] = {
"avdd",
"avee",
};
static const struct drm_display_mode innolux_p097pfg_mode = {
.clock = 229000,
.hdisplay = 1536,
.hsync_start = 1536 + 100,
.hsync_end = 1536 + 100 + 24,
.htotal = 1536 + 100 + 24 + 100,
.vdisplay = 2048,
.vsync_start = 2048 + 100,
.vsync_end = 2048 + 100 + 2,
.vtotal = 2048 + 100 + 2 + 18,
.vrefresh = 60,
};
/*
* Display manufacturer failed to provide init sequencing according to
* https://chromium-review.googlesource.com/c/chromiumos/third_party/coreboot/+/892065/
* so the init sequence stems from a register dump of a working panel.
*/
static const struct panel_init_cmd innolux_p097pfg_init_cmds[] = {
/* page 0 */
_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
_INIT_CMD(0xB1, 0xE8, 0x11),
_INIT_CMD(0xB2, 0x25, 0x02),
_INIT_CMD(0xB5, 0x08, 0x00),
_INIT_CMD(0xBC, 0x0F, 0x00),
_INIT_CMD(0xB8, 0x03, 0x06, 0x00, 0x00),
_INIT_CMD(0xBD, 0x01, 0x90, 0x14, 0x14),
_INIT_CMD(0x6F, 0x01),
_INIT_CMD(0xC0, 0x03),
_INIT_CMD(0x6F, 0x02),
_INIT_CMD(0xC1, 0x0D),
_INIT_CMD(0xD9, 0x01, 0x09, 0x70),
_INIT_CMD(0xC5, 0x12, 0x21, 0x00),
_INIT_CMD(0xBB, 0x93, 0x93),
/* page 1 */
_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01),
_INIT_CMD(0xB3, 0x3C, 0x3C),
_INIT_CMD(0xB4, 0x0F, 0x0F),
_INIT_CMD(0xB9, 0x45, 0x45),
_INIT_CMD(0xBA, 0x14, 0x14),
_INIT_CMD(0xCA, 0x02),
_INIT_CMD(0xCE, 0x04),
_INIT_CMD(0xC3, 0x9B, 0x9B),
_INIT_CMD(0xD8, 0xC0, 0x03),
_INIT_CMD(0xBC, 0x82, 0x01),
_INIT_CMD(0xBD, 0x9E, 0x01),
/* page 2 */
_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x02),
_INIT_CMD(0xB0, 0x82),
_INIT_CMD(0xD1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x82, 0x00, 0xA5,
0x00, 0xC1, 0x00, 0xEA, 0x01, 0x0D, 0x01, 0x40),
_INIT_CMD(0xD2, 0x01, 0x6A, 0x01, 0xA8, 0x01, 0xDC, 0x02, 0x29,
0x02, 0x67, 0x02, 0x68, 0x02, 0xA8, 0x02, 0xF0),
_INIT_CMD(0xD3, 0x03, 0x19, 0x03, 0x49, 0x03, 0x67, 0x03, 0x8C,
0x03, 0xA6, 0x03, 0xC7, 0x03, 0xDE, 0x03, 0xEC),
_INIT_CMD(0xD4, 0x03, 0xFF, 0x03, 0xFF),
_INIT_CMD(0xE0, 0x00, 0x00, 0x00, 0x86, 0x00, 0xC5, 0x00, 0xE5,
0x00, 0xFF, 0x01, 0x26, 0x01, 0x45, 0x01, 0x75),
_INIT_CMD(0xE1, 0x01, 0x9C, 0x01, 0xD5, 0x02, 0x05, 0x02, 0x4D,
0x02, 0x86, 0x02, 0x87, 0x02, 0xC3, 0x03, 0x03),
_INIT_CMD(0xE2, 0x03, 0x2A, 0x03, 0x56, 0x03, 0x72, 0x03, 0x94,
0x03, 0xAC, 0x03, 0xCB, 0x03, 0xE0, 0x03, 0xED),
_INIT_CMD(0xE3, 0x03, 0xFF, 0x03, 0xFF),
/* page 3 */
_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03),
_INIT_CMD(0xB0, 0x00, 0x00, 0x00, 0x00),
_INIT_CMD(0xB1, 0x00, 0x00, 0x00, 0x00),
_INIT_CMD(0xB2, 0x00, 0x00, 0x06, 0x04, 0x01, 0x40, 0x85),
_INIT_CMD(0xB3, 0x10, 0x07, 0xFC, 0x04, 0x01, 0x40, 0x80),
_INIT_CMD(0xB6, 0xF0, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
0x40, 0x80),
_INIT_CMD(0xBA, 0xC5, 0x07, 0x00, 0x04, 0x11, 0x25, 0x8C),
_INIT_CMD(0xBB, 0xC5, 0x07, 0x00, 0x03, 0x11, 0x25, 0x8C),
_INIT_CMD(0xC0, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
_INIT_CMD(0xC1, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x80),
_INIT_CMD(0xC4, 0x00, 0x00),
_INIT_CMD(0xEF, 0x41),
/* page 4 */
_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04),
_INIT_CMD(0xEC, 0x4C),
/* page 5 */
_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x05),
_INIT_CMD(0xB0, 0x13, 0x03, 0x03, 0x01),
_INIT_CMD(0xB1, 0x30, 0x00),
_INIT_CMD(0xB2, 0x02, 0x02, 0x00),
_INIT_CMD(0xB3, 0x82, 0x23, 0x82, 0x9D),
_INIT_CMD(0xB4, 0xC5, 0x75, 0x24, 0x57),
_INIT_CMD(0xB5, 0x00, 0xD4, 0x72, 0x11, 0x11, 0xAB, 0x0A),
_INIT_CMD(0xB6, 0x00, 0x00, 0xD5, 0x72, 0x24, 0x56),
_INIT_CMD(0xB7, 0x5C, 0xDC, 0x5C, 0x5C),
_INIT_CMD(0xB9, 0x0C, 0x00, 0x00, 0x01, 0x00),
_INIT_CMD(0xC0, 0x75, 0x11, 0x11, 0x54, 0x05),
_INIT_CMD(0xC6, 0x00, 0x00, 0x00, 0x00),
_INIT_CMD(0xD0, 0x00, 0x48, 0x08, 0x00, 0x00),
_INIT_CMD(0xD1, 0x00, 0x48, 0x09, 0x00, 0x00),
/* page 6 */
_INIT_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x06),
_INIT_CMD(0xB0, 0x02, 0x32, 0x32, 0x08, 0x2F),
_INIT_CMD(0xB1, 0x2E, 0x15, 0x14, 0x13, 0x12),
_INIT_CMD(0xB2, 0x11, 0x10, 0x00, 0x3D, 0x3D),
_INIT_CMD(0xB3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
_INIT_CMD(0xB4, 0x3D, 0x32),
_INIT_CMD(0xB5, 0x03, 0x32, 0x32, 0x09, 0x2F),
_INIT_CMD(0xB6, 0x2E, 0x1B, 0x1A, 0x19, 0x18),
_INIT_CMD(0xB7, 0x17, 0x16, 0x01, 0x3D, 0x3D),
_INIT_CMD(0xB8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
_INIT_CMD(0xB9, 0x3D, 0x32),
_INIT_CMD(0xC0, 0x01, 0x32, 0x32, 0x09, 0x2F),
_INIT_CMD(0xC1, 0x2E, 0x1A, 0x1B, 0x16, 0x17),
_INIT_CMD(0xC2, 0x18, 0x19, 0x03, 0x3D, 0x3D),
_INIT_CMD(0xC3, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
_INIT_CMD(0xC4, 0x3D, 0x32),
_INIT_CMD(0xC5, 0x00, 0x32, 0x32, 0x08, 0x2F),
_INIT_CMD(0xC6, 0x2E, 0x14, 0x15, 0x10, 0x11),
_INIT_CMD(0xC7, 0x12, 0x13, 0x02, 0x3D, 0x3D),
_INIT_CMD(0xC8, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D),
_INIT_CMD(0xC9, 0x3D, 0x32),
{},
};
static const struct panel_desc innolux_p097pfg_panel_desc = {
.mode = &innolux_p097pfg_mode,
.bpc = 8,
.size = {
.width = 147,
.height = 196,
},
.flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_LPM,
.format = MIPI_DSI_FMT_RGB888,
.init_cmds = innolux_p097pfg_init_cmds,
.lanes = 4,
.supply_names = innolux_p097pfg_supply_names,
.num_supplies = ARRAY_SIZE(innolux_p097pfg_supply_names),
.sleep_mode_delay = 100, /* T15 */
};
static int innolux_panel_get_modes(struct drm_panel *panel)
{
struct innolux_panel *innolux = to_innolux_panel(panel);
const struct drm_display_mode *m = innolux->desc->mode;
struct drm_display_mode *mode;
mode = drm_mode_duplicate(panel->drm, &default_mode);
mode = drm_mode_duplicate(panel->drm, m);
if (!mode) {
DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n",
default_mode.hdisplay, default_mode.vdisplay,
default_mode.vrefresh);
m->hdisplay, m->vdisplay, m->vrefresh);
return -ENOMEM;
}
@ -191,9 +423,11 @@ static int innolux_panel_get_modes(struct drm_panel *panel)
drm_mode_probed_add(panel->connector, mode);
panel->connector->display_info.width_mm = 120;
panel->connector->display_info.height_mm = 160;
panel->connector->display_info.bpc = 8;
panel->connector->display_info.width_mm =
innolux->desc->size.width;
panel->connector->display_info.height_mm =
innolux->desc->size.height;
panel->connector->display_info.bpc = innolux->desc->bpc;
return 1;
}
@ -207,19 +441,42 @@ static const struct drm_panel_funcs innolux_panel_funcs = {
};
static const struct of_device_id innolux_of_match[] = {
{ .compatible = "innolux,p079zca", },
{ .compatible = "innolux,p079zca",
.data = &innolux_p079zca_panel_desc
},
{ .compatible = "innolux,p097pfg",
.data = &innolux_p097pfg_panel_desc
},
{ }
};
MODULE_DEVICE_TABLE(of, innolux_of_match);
static int innolux_panel_add(struct innolux_panel *innolux)
static int innolux_panel_add(struct mipi_dsi_device *dsi,
const struct panel_desc *desc)
{
struct device *dev = &innolux->link->dev;
int err;
struct innolux_panel *innolux;
struct device *dev = &dsi->dev;
int err, i;
innolux->supply = devm_regulator_get(dev, "power");
if (IS_ERR(innolux->supply))
return PTR_ERR(innolux->supply);
innolux = devm_kzalloc(dev, sizeof(*innolux), GFP_KERNEL);
if (!innolux)
return -ENOMEM;
innolux->desc = desc;
innolux->supplies = devm_kcalloc(dev, desc->num_supplies,
sizeof(*innolux->supplies),
GFP_KERNEL);
if (!innolux->supplies)
return -ENOMEM;
for (i = 0; i < desc->num_supplies; i++)
innolux->supplies[i].supply = desc->supply_names[i];
err = devm_regulator_bulk_get(dev, desc->num_supplies,
innolux->supplies);
if (err < 0)
return err;
innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
GPIOD_OUT_HIGH);
@ -230,15 +487,21 @@ static int innolux_panel_add(struct innolux_panel *innolux)
}
innolux->backlight = devm_of_find_backlight(dev);
if (IS_ERR(innolux->backlight))
return PTR_ERR(innolux->backlight);
drm_panel_init(&innolux->base);
innolux->base.funcs = &innolux_panel_funcs;
innolux->base.dev = &innolux->link->dev;
innolux->base.dev = dev;
return drm_panel_add(&innolux->base);
err = drm_panel_add(&innolux->base);
if (err < 0)
return err;
mipi_dsi_set_drvdata(dsi, innolux);
innolux->link = dsi;
return 0;
}
static void innolux_panel_del(struct innolux_panel *innolux)
@ -249,28 +512,19 @@ static void innolux_panel_del(struct innolux_panel *innolux)
static int innolux_panel_probe(struct mipi_dsi_device *dsi)
{
struct innolux_panel *innolux;
const struct panel_desc *desc;
int err;
dsi->lanes = 4;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_LPM;
desc = of_device_get_match_data(&dsi->dev);
dsi->mode_flags = desc->flags;
dsi->format = desc->format;
dsi->lanes = desc->lanes;
innolux = devm_kzalloc(&dsi->dev, sizeof(*innolux), GFP_KERNEL);
if (!innolux)
return -ENOMEM;
mipi_dsi_set_drvdata(dsi, innolux);
innolux->link = dsi;
err = innolux_panel_add(innolux);
err = innolux_panel_add(dsi, desc);
if (err < 0)
return err;
err = mipi_dsi_attach(dsi);
return err;
return mipi_dsi_attach(dsi);
}
static int innolux_panel_remove(struct mipi_dsi_device *dsi)
@ -317,5 +571,6 @@ static struct mipi_dsi_driver innolux_panel_driver = {
module_mipi_dsi_driver(innolux_panel_driver);
MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
MODULE_DESCRIPTION("Innolux P079ZCA panel driver");
MODULE_LICENSE("GPL v2");

View File

@ -823,7 +823,7 @@ static void s6e8aa0_read_mtp_id(struct s6e8aa0 *ctx)
int ret, i;
ret = s6e8aa0_dcs_read(ctx, 0xd1, id, ARRAY_SIZE(id));
if (ret < ARRAY_SIZE(id) || id[0] == 0x00) {
if (ret < 0 || ret < ARRAY_SIZE(id) || id[0] == 0x00) {
dev_err(ctx->dev, "read id failed\n");
ctx->error = -EIO;
return;

View File

@ -772,6 +772,28 @@ static const struct panel_desc avic_tm070ddh03 = {
},
};
static const struct drm_display_mode boe_hv070wsa_mode = {
.clock = 40800,
.hdisplay = 1024,
.hsync_start = 1024 + 90,
.hsync_end = 1024 + 90 + 90,
.htotal = 1024 + 90 + 90 + 90,
.vdisplay = 600,
.vsync_start = 600 + 3,
.vsync_end = 600 + 3 + 4,
.vtotal = 600 + 3 + 4 + 3,
.vrefresh = 60,
};
static const struct panel_desc boe_hv070wsa = {
.modes = &boe_hv070wsa_mode,
.num_modes = 1,
.size = {
.width = 154,
.height = 90,
},
};
static const struct drm_display_mode boe_nv101wxmn51_modes[] = {
{
.clock = 71900,
@ -884,6 +906,61 @@ static const struct panel_desc chunghwa_claa101wb01 = {
},
};
static const struct drm_display_mode dataimage_scf0700c48ggu18_mode = {
.clock = 33260,
.hdisplay = 800,
.hsync_start = 800 + 40,
.hsync_end = 800 + 40 + 128,
.htotal = 800 + 40 + 128 + 88,
.vdisplay = 480,
.vsync_start = 480 + 10,
.vsync_end = 480 + 10 + 2,
.vtotal = 480 + 10 + 2 + 33,
.vrefresh = 60,
.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
};
static const struct panel_desc dataimage_scf0700c48ggu18 = {
.modes = &dataimage_scf0700c48ggu18_mode,
.num_modes = 1,
.bpc = 8,
.size = {
.width = 152,
.height = 91,
},
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE,
};
static const struct display_timing dlc_dlc0700yzg_1_timing = {
.pixelclock = { 45000000, 51200000, 57000000 },
.hactive = { 1024, 1024, 1024 },
.hfront_porch = { 100, 106, 113 },
.hback_porch = { 100, 106, 113 },
.hsync_len = { 100, 108, 114 },
.vactive = { 600, 600, 600 },
.vfront_porch = { 8, 11, 15 },
.vback_porch = { 8, 11, 15 },
.vsync_len = { 9, 13, 15 },
.flags = DISPLAY_FLAGS_DE_HIGH,
};
static const struct panel_desc dlc_dlc0700yzg_1 = {
.timings = &dlc_dlc0700yzg_1_timing,
.num_timings = 1,
.bpc = 6,
.size = {
.width = 154,
.height = 86,
},
.delay = {
.prepare = 30,
.enable = 200,
.disable = 200,
},
.bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,
};
static const struct drm_display_mode edt_et057090dhu_mode = {
.clock = 25175,
.hdisplay = 640,
@ -936,6 +1013,18 @@ static const struct panel_desc edt_etm0700g0dh6 = {
.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_NEGEDGE,
};
static const struct panel_desc edt_etm0700g0bdh6 = {
.modes = &edt_etm0700g0dh6_mode,
.num_modes = 1,
.bpc = 6,
.size = {
.width = 152,
.height = 91,
},
.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE,
};
static const struct drm_display_mode foxlink_fl500wvr00_a0t_mode = {
.clock = 32260,
.hdisplay = 800,
@ -1113,6 +1202,36 @@ static const struct panel_desc innolux_at070tn92 = {
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
};
static const struct display_timing innolux_g070y2_l01_timing = {
.pixelclock = { 28000000, 29500000, 32000000 },
.hactive = { 800, 800, 800 },
.hfront_porch = { 61, 91, 141 },
.hback_porch = { 60, 90, 140 },
.hsync_len = { 12, 12, 12 },
.vactive = { 480, 480, 480 },
.vfront_porch = { 4, 9, 30 },
.vback_porch = { 4, 8, 28 },
.vsync_len = { 2, 2, 2 },
.flags = DISPLAY_FLAGS_DE_HIGH,
};
static const struct panel_desc innolux_g070y2_l01 = {
.timings = &innolux_g070y2_l01_timing,
.num_timings = 1,
.bpc = 6,
.size = {
.width = 152,
.height = 91,
},
.delay = {
.prepare = 10,
.enable = 100,
.disable = 100,
.unprepare = 800,
},
.bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
};
static const struct display_timing innolux_g101ice_l01_timing = {
.pixelclock = { 60400000, 71100000, 74700000 },
.hactive = { 1280, 1280, 1280 },
@ -1562,6 +1681,33 @@ static const struct panel_desc netron_dy_e231732 = {
.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
};
static const struct drm_display_mode newhaven_nhd_43_480272ef_atxl_mode = {
.clock = 9000,
.hdisplay = 480,
.hsync_start = 480 + 2,
.hsync_end = 480 + 2 + 41,
.htotal = 480 + 2 + 41 + 2,
.vdisplay = 272,
.vsync_start = 272 + 2,
.vsync_end = 272 + 2 + 10,
.vtotal = 272 + 2 + 10 + 2,
.vrefresh = 60,
.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
};
static const struct panel_desc newhaven_nhd_43_480272ef_atxl = {
.modes = &newhaven_nhd_43_480272ef_atxl_mode,
.num_modes = 1,
.bpc = 8,
.size = {
.width = 95,
.height = 54,
},
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE |
DRM_BUS_FLAG_SYNC_POSEDGE,
};
static const struct display_timing nlt_nl192108ac18_02d_timing = {
.pixelclock = { 130000000, 148350000, 163000000 },
.hactive = { 1920, 1920, 1920 },
@ -1747,6 +1893,36 @@ static const struct panel_desc qd43003c0_40 = {
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
};
static const struct display_timing rocktech_rk070er9427_timing = {
.pixelclock = { 26400000, 33300000, 46800000 },
.hactive = { 800, 800, 800 },
.hfront_porch = { 16, 210, 354 },
.hback_porch = { 46, 46, 46 },
.hsync_len = { 1, 1, 1 },
.vactive = { 480, 480, 480 },
.vfront_porch = { 7, 22, 147 },
.vback_porch = { 23, 23, 23 },
.vsync_len = { 1, 1, 1 },
.flags = DISPLAY_FLAGS_DE_HIGH,
};
static const struct panel_desc rocktech_rk070er9427 = {
.timings = &rocktech_rk070er9427_timing,
.num_timings = 1,
.bpc = 6,
.size = {
.width = 154,
.height = 86,
},
.delay = {
.prepare = 41,
.enable = 50,
.unprepare = 41,
.disable = 50,
},
.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
};
static const struct drm_display_mode samsung_lsn122dl01_c01_mode = {
.clock = 271560,
.hdisplay = 2560,
@ -1815,6 +1991,30 @@ static const struct panel_desc samsung_ltn140at29_301 = {
},
};
static const struct drm_display_mode sharp_lq035q7db03_mode = {
.clock = 5500,
.hdisplay = 240,
.hsync_start = 240 + 16,
.hsync_end = 240 + 16 + 7,
.htotal = 240 + 16 + 7 + 5,
.vdisplay = 320,
.vsync_start = 320 + 9,
.vsync_end = 320 + 9 + 1,
.vtotal = 320 + 9 + 1 + 7,
.vrefresh = 60,
};
static const struct panel_desc sharp_lq035q7db03 = {
.modes = &sharp_lq035q7db03_mode,
.num_modes = 1,
.bpc = 6,
.size = {
.width = 54,
.height = 72,
},
.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
};
static const struct display_timing sharp_lq101k1ly04_timing = {
.pixelclock = { 60000000, 65000000, 80000000 },
.hactive = { 1280, 1280, 1280 },
@ -2166,6 +2366,9 @@ static const struct of_device_id platform_of_match[] = {
}, {
.compatible = "avic,tm070ddh03",
.data = &avic_tm070ddh03,
}, {
.compatible = "boe,hv070wsa-100",
.data = &boe_hv070wsa
}, {
.compatible = "boe,nv101wxmn51",
.data = &boe_nv101wxmn51,
@ -2178,6 +2381,12 @@ static const struct of_device_id platform_of_match[] = {
}, {
.compatible = "chunghwa,claa101wb01",
.data = &chunghwa_claa101wb01
}, {
.compatible = "dataimage,scf0700c48ggu18",
.data = &dataimage_scf0700c48ggu18,
}, {
.compatible = "dlc,dlc0700yzg-1",
.data = &dlc_dlc0700yzg_1,
}, {
.compatible = "edt,et057090dhu",
.data = &edt_et057090dhu,
@ -2187,6 +2396,12 @@ static const struct of_device_id platform_of_match[] = {
}, {
.compatible = "edt,etm0700g0dh6",
.data = &edt_etm0700g0dh6,
}, {
.compatible = "edt,etm0700g0bdh6",
.data = &edt_etm0700g0bdh6,
}, {
.compatible = "edt,etm0700g0edh6",
.data = &edt_etm0700g0bdh6,
}, {
.compatible = "foxlink,fl500wvr00-a0t",
.data = &foxlink_fl500wvr00_a0t,
@ -2209,10 +2424,13 @@ static const struct of_device_id platform_of_match[] = {
.compatible = "innolux,at070tn92",
.data = &innolux_at070tn92,
}, {
.compatible ="innolux,g101ice-l01",
.compatible = "innolux,g070y2-l01",
.data = &innolux_g070y2_l01,
}, {
.compatible = "innolux,g101ice-l01",
.data = &innolux_g101ice_l01
}, {
.compatible ="innolux,g121i1-l01",
.compatible = "innolux,g121i1-l01",
.data = &innolux_g121i1_l01
}, {
.compatible = "innolux,g121x1-l03",
@ -2262,6 +2480,9 @@ static const struct of_device_id platform_of_match[] = {
}, {
.compatible = "netron-dy,e231732",
.data = &netron_dy_e231732,
}, {
.compatible = "newhaven,nhd-4.3-480272ef-atxl",
.data = &newhaven_nhd_43_480272ef_atxl,
}, {
.compatible = "nlt,nl192108ac18-02d",
.data = &nlt_nl192108ac18_02d,
@ -2283,6 +2504,9 @@ static const struct of_device_id platform_of_match[] = {
}, {
.compatible = "qiaodian,qd43003c0-40",
.data = &qd43003c0_40,
}, {
.compatible = "rocktech,rk070er9427",
.data = &rocktech_rk070er9427,
}, {
.compatible = "samsung,lsn122dl01-c01",
.data = &samsung_lsn122dl01_c01,
@ -2292,6 +2516,9 @@ static const struct of_device_id platform_of_match[] = {
}, {
.compatible = "samsung,ltn140at29-301",
.data = &samsung_ltn140at29_301,
}, {
.compatible = "sharp,lq035q7db03",
.data = &sharp_lq035q7db03,
}, {
.compatible = "sharp,lq101k1ly04",
.data = &sharp_lq101k1ly04,

View File

@ -250,6 +250,8 @@ static struct drm_driver pl111_drm_driver = {
.gem_prime_import_sg_table = pl111_gem_import_sg_table,
.gem_prime_export = drm_gem_prime_export,
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
.gem_prime_mmap = drm_gem_cma_prime_mmap,
.gem_prime_vmap = drm_gem_cma_prime_vmap,
#if defined(CONFIG_DEBUG_FS)
.debugfs_init = pl111_debugfs_init,

View File

@ -244,23 +244,15 @@ radeon_connector_update_scratch_regs(struct drm_connector *connector, enum drm_c
{
struct drm_device *dev = connector->dev;
struct radeon_device *rdev = dev->dev_private;
struct drm_encoder *best_encoder = NULL;
struct drm_encoder *encoder = NULL;
struct drm_encoder *best_encoder;
struct drm_encoder *encoder;
const struct drm_connector_helper_funcs *connector_funcs = connector->helper_private;
bool connected;
int i;
best_encoder = connector_funcs->best_encoder(connector);
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL,
connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if ((encoder == best_encoder) && (status == connector_status_connected))
connected = true;
else
@ -270,7 +262,6 @@ radeon_connector_update_scratch_regs(struct drm_connector *connector, enum drm_c
radeon_atombios_connected_scratch_regs(connector, encoder, connected);
else
radeon_combios_connected_scratch_regs(connector, encoder, connected);
}
}
@ -279,17 +270,11 @@ static struct drm_encoder *radeon_find_encoder(struct drm_connector *connector,
struct drm_encoder *encoder;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL, connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (encoder->encoder_type == encoder_type)
return encoder;
}
return NULL;
}
@ -393,10 +378,13 @@ static int radeon_ddc_get_modes(struct drm_connector *connector)
static struct drm_encoder *radeon_best_single_encoder(struct drm_connector *connector)
{
int enc_id = connector->encoder_ids[0];
/* pick the encoder ids */
if (enc_id)
return drm_encoder_find(connector->dev, NULL, enc_id);
struct drm_encoder *encoder;
int i;
/* pick the first one */
drm_connector_for_each_possible_encoder(connector, encoder, i)
return encoder;
return NULL;
}
@ -436,19 +424,19 @@ radeon_connector_analog_encoder_conflict_solve(struct drm_connector *connector,
struct drm_device *dev = connector->dev;
struct drm_connector *conflict;
struct radeon_connector *radeon_conflict;
int i;
list_for_each_entry(conflict, &dev->mode_config.connector_list, head) {
struct drm_encoder *enc;
int i;
if (conflict == connector)
continue;
radeon_conflict = to_radeon_connector(conflict);
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (conflict->encoder_ids[i] == 0)
break;
drm_connector_for_each_possible_encoder(conflict, enc, i) {
/* if the IDs match */
if (conflict->encoder_ids[i] == encoder->base.id) {
if (enc == encoder) {
if (conflict->status != connector_status_connected)
continue;
@ -1256,7 +1244,7 @@ radeon_dvi_detect(struct drm_connector *connector, bool force)
struct radeon_connector *radeon_connector = to_radeon_connector(connector);
struct drm_encoder *encoder = NULL;
const struct drm_encoder_helper_funcs *encoder_funcs;
int i, r;
int r;
enum drm_connector_status ret = connector_status_disconnected;
bool dret = false, broken_edid = false;
@ -1374,15 +1362,9 @@ radeon_dvi_detect(struct drm_connector *connector, bool force)
/* find analog encoder */
if (radeon_connector->dac_load_detect) {
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL,
connector->encoder_ids[i]);
if (!encoder)
continue;
int i;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (encoder->encoder_type != DRM_MODE_ENCODER_DAC &&
encoder->encoder_type != DRM_MODE_ENCODER_TVDAC)
continue;
@ -1458,18 +1440,11 @@ exit:
/* okay need to be smart in here about which encoder to pick */
static struct drm_encoder *radeon_dvi_encoder(struct drm_connector *connector)
{
int enc_id = connector->encoder_ids[0];
struct radeon_connector *radeon_connector = to_radeon_connector(connector);
struct drm_encoder *encoder;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL, connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
if (radeon_connector->use_digital == true) {
if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS)
return encoder;
@ -1484,8 +1459,9 @@ static struct drm_encoder *radeon_dvi_encoder(struct drm_connector *connector)
/* then check use digitial */
/* pick the first one */
if (enc_id)
return drm_encoder_find(connector->dev, NULL, enc_id);
drm_connector_for_each_possible_encoder(connector, encoder, i)
return encoder;
return NULL;
}
@ -1628,14 +1604,7 @@ u16 radeon_connector_encoder_get_dp_bridge_encoder_id(struct drm_connector *conn
struct radeon_encoder *radeon_encoder;
int i;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL, connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
radeon_encoder = to_radeon_encoder(encoder);
switch (radeon_encoder->encoder_id) {
@ -1657,14 +1626,7 @@ static bool radeon_connector_encoder_is_hbr2(struct drm_connector *connector)
int i;
bool found = false;
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {
if (connector->encoder_ids[i] == 0)
break;
encoder = drm_encoder_find(connector->dev, NULL, connector->encoder_ids[i]);
if (!encoder)
continue;
drm_connector_for_each_possible_encoder(connector, encoder, i) {
radeon_encoder = to_radeon_encoder(encoder);
if (radeon_encoder->caps & ATOM_ENCODER_CAP_RECORD_HBR2)
found = true;

View File

@ -434,8 +434,8 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
ret = -EPROBE_DEFER;
} else {
lvds->panel = of_drm_find_panel(remote);
if (!lvds->panel)
ret = -EPROBE_DEFER;
if (IS_ERR(lvds->panel))
ret = PTR_ERR(lvds->panel);
}
done:

View File

@ -595,7 +595,7 @@ static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
dsi->format = device->format;
dsi->mode_flags = device->mode_flags;
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (dsi->panel)
if (!IS_ERR(dsi->panel))
return drm_panel_attach(dsi->panel, &dsi->connector);
return -EINVAL;

View File

@ -387,7 +387,9 @@ sti_dvo_connector_detect(struct drm_connector *connector, bool force)
if (!dvo->panel) {
dvo->panel = of_drm_find_panel(dvo->panel_node);
if (dvo->panel)
if (IS_ERR(dvo->panel))
dvo->panel = NULL;
else
drm_panel_attach(dvo->panel, connector);
}

View File

@ -457,6 +457,14 @@ ltdc_crtc_mode_valid(struct drm_crtc *crtc,
int target_max = target + CLK_TOLERANCE_HZ;
int result;
result = clk_round_rate(ldev->pixel_clk, target);
DRM_DEBUG_DRIVER("clk rate target %d, available %d\n", target, result);
/* Filter modes according to the max frequency supported by the pads */
if (result > ldev->caps.pad_max_freq_hz)
return MODE_CLOCK_HIGH;
/*
* Accept all "preferred" modes:
* - this is important for panels because panel clock tolerances are
@ -468,10 +476,6 @@ ltdc_crtc_mode_valid(struct drm_crtc *crtc,
if (mode->type & DRM_MODE_TYPE_PREFERRED)
return MODE_OK;
result = clk_round_rate(ldev->pixel_clk, target);
DRM_DEBUG_DRIVER("clk rate target %d, available %d\n", target, result);
/*
* Filter modes according to the clock value, particularly useful for
* hdmi modes that require precise pixel clocks.
@ -991,11 +995,15 @@ static int ltdc_get_caps(struct drm_device *ddev)
* does not work on 2nd layer.
*/
ldev->caps.non_alpha_only_l1 = true;
ldev->caps.pad_max_freq_hz = 90000000;
if (ldev->caps.hw_version == HWVER_10200)
ldev->caps.pad_max_freq_hz = 65000000;
break;
case HWVER_20101:
ldev->caps.reg_ofs = REG_OFS_4;
ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a1;
ldev->caps.non_alpha_only_l1 = false;
ldev->caps.pad_max_freq_hz = 150000000;
break;
default:
return -ENODEV;
@ -1074,8 +1082,11 @@ int ltdc_load(struct drm_device *ddev)
}
}
if (!IS_ERR(rstc))
if (!IS_ERR(rstc)) {
reset_control_assert(rstc);
usleep_range(10, 20);
reset_control_deassert(rstc);
}
/* Disable interrupts */
reg_clear(ldev->regs, LTDC_IER,

View File

@ -18,6 +18,7 @@ struct ltdc_caps {
u32 bus_width; /* bus width (32 or 64 bits) */
const u32 *pix_fmt_hw; /* supported pixel formats */
bool non_alpha_only_l1; /* non-native no-alpha formats on layer 1 */
int pad_max_freq_hz; /* max frequency supported by pad */
};
#define LTDC_MAX_LAYER 4

View File

@ -417,6 +417,7 @@ static const struct of_device_id sun4i_drv_of_table[] = {
{ .compatible = "allwinner,sun8i-a33-display-engine" },
{ .compatible = "allwinner,sun8i-a83t-display-engine" },
{ .compatible = "allwinner,sun8i-h3-display-engine" },
{ .compatible = "allwinner,sun8i-r40-display-engine" },
{ .compatible = "allwinner,sun8i-v3s-display-engine" },
{ .compatible = "allwinner,sun9i-a80-display-engine" },
{ }

View File

@ -811,6 +811,7 @@ sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv,
* remote output id. If this for some reason can't be done, 0
* is used as input port id.
*/
of_node_put(port);
port = of_graph_get_remote_port(ep);
if (!of_property_read_u32(port, "reg", &reg) && reg > 0)
reg -= 1;

View File

@ -13,6 +13,7 @@
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/phy/phy.h>
@ -247,10 +248,8 @@ static u16 sun6i_dsi_crc_compute(u8 const *buffer, size_t len)
return crc_ccitt(0xffff, buffer, len);
}
static u16 sun6i_dsi_crc_repeat_compute(u8 pd, size_t len)
static u16 sun6i_dsi_crc_repeat(u8 pd, u8 *buffer, size_t len)
{
u8 buffer[len];
memset(buffer, pd, len);
return sun6i_dsi_crc_compute(buffer, len);
@ -274,11 +273,11 @@ static u32 sun6i_dsi_build_blk0_pkt(u8 vc, u16 wc)
wc & 0xff, wc >> 8);
}
static u32 sun6i_dsi_build_blk1_pkt(u16 pd, size_t len)
static u32 sun6i_dsi_build_blk1_pkt(u16 pd, u8 *buffer, size_t len)
{
u32 val = SUN6I_DSI_BLK_PD(pd);
return val | SUN6I_DSI_BLK_PF(sun6i_dsi_crc_repeat_compute(pd, len));
return val | SUN6I_DSI_BLK_PF(sun6i_dsi_crc_repeat(pd, buffer, len));
}
static void sun6i_dsi_inst_abort(struct sun6i_dsi *dsi)
@ -452,6 +451,54 @@ static void sun6i_dsi_setup_timings(struct sun6i_dsi *dsi,
struct mipi_dsi_device *device = dsi->device;
unsigned int Bpp = mipi_dsi_pixel_format_to_bpp(device->format) / 8;
u16 hbp, hfp, hsa, hblk, vblk;
size_t bytes;
u8 *buffer;
/* Do all timing calculations up front to allocate buffer space */
/*
* A sync period is composed of a blanking packet (4 bytes +
* payload + 2 bytes) and a sync event packet (4 bytes). Its
* minimal size is therefore 10 bytes
*/
#define HSA_PACKET_OVERHEAD 10
hsa = max((unsigned int)HSA_PACKET_OVERHEAD,
(mode->hsync_end - mode->hsync_start) * Bpp - HSA_PACKET_OVERHEAD);
/*
* The backporch is set using a blanking packet (4 bytes +
* payload + 2 bytes). Its minimal size is therefore 6 bytes
*/
#define HBP_PACKET_OVERHEAD 6
hbp = max((unsigned int)HBP_PACKET_OVERHEAD,
(mode->hsync_start - mode->hdisplay) * Bpp - HBP_PACKET_OVERHEAD);
/*
* The frontporch is set using a blanking packet (4 bytes +
* payload + 2 bytes). Its minimal size is therefore 6 bytes
*/
#define HFP_PACKET_OVERHEAD 6
hfp = max((unsigned int)HFP_PACKET_OVERHEAD,
(mode->htotal - mode->hsync_end) * Bpp - HFP_PACKET_OVERHEAD);
/*
* hblk seems to be the line + porches length.
*/
hblk = mode->htotal * Bpp - hsa;
/*
* And I'm not entirely sure what vblk is about. The driver in
* Allwinner BSP is using a rather convoluted calculation
* there only for 4 lanes. However, using 0 (the !4 lanes
* case) even with a 4 lanes screen seems to work...
*/
vblk = 0;
/* How many bytes do we need to send all payloads? */
bytes = max_t(size_t, max(max(hfp, hblk), max(hsa, hbp)), vblk);
buffer = kmalloc(bytes, GFP_KERNEL);
if (WARN_ON(!buffer))
return;
regmap_write(dsi->regs, SUN6I_DSI_BASIC_CTL_REG, 0);
@ -485,63 +532,37 @@ static void sun6i_dsi_setup_timings(struct sun6i_dsi *dsi,
SUN6I_DSI_BASIC_SIZE1_VACT(mode->vdisplay) |
SUN6I_DSI_BASIC_SIZE1_VT(mode->vtotal));
/*
* A sync period is composed of a blanking packet (4 bytes +
* payload + 2 bytes) and a sync event packet (4 bytes). Its
* minimal size is therefore 10 bytes
*/
#define HSA_PACKET_OVERHEAD 10
hsa = max((unsigned int)HSA_PACKET_OVERHEAD,
(mode->hsync_end - mode->hsync_start) * Bpp - HSA_PACKET_OVERHEAD);
/* sync */
regmap_write(dsi->regs, SUN6I_DSI_BLK_HSA0_REG,
sun6i_dsi_build_blk0_pkt(device->channel, hsa));
regmap_write(dsi->regs, SUN6I_DSI_BLK_HSA1_REG,
sun6i_dsi_build_blk1_pkt(0, hsa));
sun6i_dsi_build_blk1_pkt(0, buffer, hsa));
/*
* The backporch is set using a blanking packet (4 bytes +
* payload + 2 bytes). Its minimal size is therefore 6 bytes
*/
#define HBP_PACKET_OVERHEAD 6
hbp = max((unsigned int)HBP_PACKET_OVERHEAD,
(mode->hsync_start - mode->hdisplay) * Bpp - HBP_PACKET_OVERHEAD);
/* backporch */
regmap_write(dsi->regs, SUN6I_DSI_BLK_HBP0_REG,
sun6i_dsi_build_blk0_pkt(device->channel, hbp));
regmap_write(dsi->regs, SUN6I_DSI_BLK_HBP1_REG,
sun6i_dsi_build_blk1_pkt(0, hbp));
sun6i_dsi_build_blk1_pkt(0, buffer, hbp));
/*
* The frontporch is set using a blanking packet (4 bytes +
* payload + 2 bytes). Its minimal size is therefore 6 bytes
*/
#define HFP_PACKET_OVERHEAD 6
hfp = max((unsigned int)HFP_PACKET_OVERHEAD,
(mode->htotal - mode->hsync_end) * Bpp - HFP_PACKET_OVERHEAD);
/* frontporch */
regmap_write(dsi->regs, SUN6I_DSI_BLK_HFP0_REG,
sun6i_dsi_build_blk0_pkt(device->channel, hfp));
regmap_write(dsi->regs, SUN6I_DSI_BLK_HFP1_REG,
sun6i_dsi_build_blk1_pkt(0, hfp));
sun6i_dsi_build_blk1_pkt(0, buffer, hfp));
/*
* hblk seems to be the line + porches length.
*/
hblk = mode->htotal * Bpp - hsa;
/* hblk */
regmap_write(dsi->regs, SUN6I_DSI_BLK_HBLK0_REG,
sun6i_dsi_build_blk0_pkt(device->channel, hblk));
regmap_write(dsi->regs, SUN6I_DSI_BLK_HBLK1_REG,
sun6i_dsi_build_blk1_pkt(0, hblk));
sun6i_dsi_build_blk1_pkt(0, buffer, hblk));
/*
* And I'm not entirely sure what vblk is about. The driver in
* Allwinner BSP is using a rather convoluted calculation
* there only for 4 lanes. However, using 0 (the !4 lanes
* case) even with a 4 lanes screen seems to work...
*/
vblk = 0;
/* vblk */
regmap_write(dsi->regs, SUN6I_DSI_BLK_VBLK0_REG,
sun6i_dsi_build_blk0_pkt(device->channel, vblk));
regmap_write(dsi->regs, SUN6I_DSI_BLK_VBLK1_REG,
sun6i_dsi_build_blk1_pkt(0, vblk));
sun6i_dsi_build_blk1_pkt(0, buffer, vblk));
kfree(buffer);
}
static int sun6i_dsi_start(struct sun6i_dsi *dsi,
@ -812,8 +833,8 @@ static int sun6i_dsi_attach(struct mipi_dsi_host *host,
dsi->device = device;
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (!dsi->panel)
return -EINVAL;
if (IS_ERR(dsi->panel))
return PTR_ERR(dsi->panel);
dev_info(host->dev, "Attached device %s\n", device->name);

View File

@ -53,22 +53,14 @@ static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm,
struct device_node *port, *ep, *remote, *remote_port;
u32 crtcs = 0;
port = of_graph_get_port_by_id(node, 0);
if (!port)
return 0;
ep = of_get_next_available_child(port, NULL);
if (!ep)
return 0;
remote = of_graph_get_remote_port_parent(ep);
remote = of_graph_get_remote_node(node, 0, -1);
if (!remote)
return 0;
if (sun8i_dw_hdmi_node_is_tcon_top(remote)) {
port = of_graph_get_port_by_id(remote, 4);
if (!port)
return 0;
goto crtcs_exit;
for_each_child_of_node(port, ep) {
remote_port = of_graph_get_remote_port(ep);
@ -81,6 +73,9 @@ static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm,
crtcs = drm_of_find_possible_crtcs(drm, node);
}
crtcs_exit:
of_node_put(remote);
return crtcs;
}

View File

@ -21,8 +21,8 @@
#include <linux/component.h>
#include <linux/dma-mapping.h>
#include <linux/reset.h>
#include <linux/of_device.h>
#include <linux/reset.h>
#include "sun4i_drv.h"
#include "sun8i_mixer.h"

View File

@ -14,45 +14,95 @@
#include "sun8i_tcon_top.h"
static int sun8i_tcon_top_get_connected_ep_id(struct device_node *node,
int port_id)
static bool sun8i_tcon_top_node_is_tcon_top(struct device_node *node)
{
struct device_node *ep, *remote, *port;
struct of_endpoint endpoint;
port = of_graph_get_port_by_id(node, port_id);
if (!port)
return -ENOENT;
for_each_available_child_of_node(port, ep) {
remote = of_graph_get_remote_port_parent(ep);
if (!remote)
continue;
if (of_device_is_available(remote)) {
of_graph_parse_endpoint(ep, &endpoint);
of_node_put(remote);
return endpoint.id;
}
of_node_put(remote);
}
return -ENOENT;
return !!of_match_node(sun8i_tcon_top_of_table, node);
}
int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon)
{
struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
unsigned long flags;
u32 val;
if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
dev_err(dev, "Device is not TCON TOP!\n");
return -EINVAL;
}
if (tcon < 2 || tcon > 3) {
dev_err(dev, "TCON index must be 2 or 3!\n");
return -EINVAL;
}
spin_lock_irqsave(&tcon_top->reg_lock, flags);
val = readl(tcon_top->regs + TCON_TOP_GATE_SRC_REG);
val &= ~TCON_TOP_HDMI_SRC_MSK;
val |= FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, tcon - 1);
writel(val, tcon_top->regs + TCON_TOP_GATE_SRC_REG);
spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
return 0;
}
EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src);
int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon)
{
struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
unsigned long flags;
u32 reg;
if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
dev_err(dev, "Device is not TCON TOP!\n");
return -EINVAL;
}
if (mixer > 1) {
dev_err(dev, "Mixer index is too high!\n");
return -EINVAL;
}
if (tcon > 3) {
dev_err(dev, "TCON index is too high!\n");
return -EINVAL;
}
spin_lock_irqsave(&tcon_top->reg_lock, flags);
reg = readl(tcon_top->regs + TCON_TOP_PORT_SEL_REG);
if (mixer == 0) {
reg &= ~TCON_TOP_PORT_DE0_MSK;
reg |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, tcon);
} else {
reg &= ~TCON_TOP_PORT_DE1_MSK;
reg |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, tcon);
}
writel(reg, tcon_top->regs + TCON_TOP_PORT_SEL_REG);
spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
return 0;
}
EXPORT_SYMBOL(sun8i_tcon_top_de_config);
static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev,
struct clk *parent,
const char *parent,
void __iomem *regs,
spinlock_t *lock,
u8 bit, int name_index)
{
const char *clk_name, *parent_name;
int ret;
int ret, index;
index = of_property_match_string(dev->of_node, "clock-names", parent);
if (index < 0)
return index;
parent_name = of_clk_get_parent_name(dev->of_node, index);
parent_name = __clk_get_name(parent);
ret = of_property_read_string_index(dev->of_node,
"clock-output-names", name_index,
&clk_name);
@ -69,14 +119,11 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct clk *dsi, *tcon_tv0, *tcon_tv1, *tve0, *tve1;
struct clk_hw_onecell_data *clk_data;
struct sun8i_tcon_top *tcon_top;
bool mixer0_unused = false;
struct resource *res;
void __iomem *regs;
int ret, i, id;
u32 val;
int ret, i;
tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL);
if (!tcon_top)
@ -103,38 +150,9 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
return PTR_ERR(tcon_top->bus);
}
dsi = devm_clk_get(dev, "dsi");
if (IS_ERR(dsi)) {
dev_err(dev, "Couldn't get the dsi clock\n");
return PTR_ERR(dsi);
}
tcon_tv0 = devm_clk_get(dev, "tcon-tv0");
if (IS_ERR(tcon_tv0)) {
dev_err(dev, "Couldn't get the tcon-tv0 clock\n");
return PTR_ERR(tcon_tv0);
}
tcon_tv1 = devm_clk_get(dev, "tcon-tv1");
if (IS_ERR(tcon_tv1)) {
dev_err(dev, "Couldn't get the tcon-tv1 clock\n");
return PTR_ERR(tcon_tv1);
}
tve0 = devm_clk_get(dev, "tve0");
if (IS_ERR(tve0)) {
dev_err(dev, "Couldn't get the tve0 clock\n");
return PTR_ERR(tve0);
}
tve1 = devm_clk_get(dev, "tve1");
if (IS_ERR(tve1)) {
dev_err(dev, "Couldn't get the tve1 clock\n");
return PTR_ERR(tve1);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
tcon_top->regs = regs;
if (IS_ERR(regs))
return PTR_ERR(regs);
@ -150,50 +168,6 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
goto err_assert_reset;
}
val = 0;
/* check if HDMI mux output is connected */
if (sun8i_tcon_top_get_connected_ep_id(dev->of_node, 5) >= 0) {
/* find HDMI input endpoint id, if it is connected at all*/
id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 4);
if (id >= 0)
val = FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, id + 1);
else
DRM_DEBUG_DRIVER("TCON TOP HDMI input is not connected\n");
} else {
DRM_DEBUG_DRIVER("TCON TOP HDMI output is not connected\n");
}
writel(val, regs + TCON_TOP_GATE_SRC_REG);
val = 0;
/* process mixer0 mux output */
id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 1);
if (id >= 0) {
val = FIELD_PREP(TCON_TOP_PORT_DE0_MSK, id);
} else {
DRM_DEBUG_DRIVER("TCON TOP mixer0 output is not connected\n");
mixer0_unused = true;
}
/* process mixer1 mux output */
id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 3);
if (id >= 0) {
val |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, id);
/*
* mixer0 mux has priority over mixer1 mux. We have to
* make sure mixer0 doesn't overtake TCON from mixer1.
*/
if (mixer0_unused && id == 0)
val |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, 1);
} else {
DRM_DEBUG_DRIVER("TCON TOP mixer1 output is not connected\n");
}
writel(val, regs + TCON_TOP_PORT_SEL_REG);
/*
* TCON TOP has two muxes, which select parent clock for each TCON TV
* channel clock. Parent could be either TCON TV or TVE clock. For now
@ -203,17 +177,17 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
* to TVE clock parent.
*/
clk_data->hws[CLK_TCON_TOP_TV0] =
sun8i_tcon_top_register_gate(dev, tcon_tv0, regs,
sun8i_tcon_top_register_gate(dev, "tcon-tv0", regs,
&tcon_top->reg_lock,
TCON_TOP_TCON_TV0_GATE, 0);
clk_data->hws[CLK_TCON_TOP_TV1] =
sun8i_tcon_top_register_gate(dev, tcon_tv1, regs,
sun8i_tcon_top_register_gate(dev, "tcon-tv1", regs,
&tcon_top->reg_lock,
TCON_TOP_TCON_TV1_GATE, 1);
clk_data->hws[CLK_TCON_TOP_DSI] =
sun8i_tcon_top_register_gate(dev, dsi, regs,
sun8i_tcon_top_register_gate(dev, "dsi", regs,
&tcon_top->reg_lock,
TCON_TOP_TCON_DSI_GATE, 2);

View File

@ -26,6 +26,7 @@
struct sun8i_tcon_top {
struct clk *bus;
struct clk_hw_onecell_data *clk_data;
void __iomem *regs;
struct reset_control *rst;
/*
@ -37,4 +38,7 @@ struct sun8i_tcon_top {
extern const struct of_device_id sun8i_tcon_top_of_table[];
int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon);
int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon);
#endif /* _SUN8I_TCON_TOP_H_ */

View File

@ -1411,6 +1411,9 @@ static int tegra_dsi_host_attach(struct mipi_dsi_host *host,
struct tegra_output *output = &dsi->output;
output->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(output->panel))
output->panel = NULL;
if (output->panel && output->connector.dev) {
drm_panel_attach(output->panel, &output->connector);
drm_helper_hpd_irq_event(output->connector.dev);

View File

@ -110,8 +110,8 @@ int tegra_output_probe(struct tegra_output *output)
panel = of_parse_phandle(output->of_node, "nvidia,panel", 0);
if (panel) {
output->panel = of_drm_find_panel(panel);
if (!output->panel)
return -EPROBE_DEFER;
if (IS_ERR(output->panel))
return PTR_ERR(output->panel);
of_node_put(panel);
}

View File

@ -103,12 +103,11 @@ struct drm_connector *tilcdc_encoder_find_connector(struct drm_device *ddev,
struct drm_encoder *encoder)
{
struct drm_connector *connector;
int i;
list_for_each_entry(connector, &ddev->mode_config.connector_list, head)
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++)
if (connector->encoder_ids[i] == encoder->base.id)
return connector;
list_for_each_entry(connector, &ddev->mode_config.connector_list, head) {
if (drm_connector_has_possible_encoder(connector, encoder))
return connector;
}
dev_err(ddev->dev, "No connector found for %s encoder (id %d)\n",
encoder->name, encoder->base.id);

View File

@ -204,7 +204,7 @@ static int tinydrm_register(struct tinydrm_device *tdev)
if (ret)
return ret;
ret = drm_fb_cma_fbdev_init_with_funcs(drm, 0, 0, tdev->fb_funcs);
ret = drm_fbdev_generic_setup(drm, 0);
if (ret)
DRM_ERROR("Failed to initialize fbdev: %d\n", ret);
@ -214,7 +214,6 @@ static int tinydrm_register(struct tinydrm_device *tdev)
static void tinydrm_unregister(struct tinydrm_device *tdev)
{
drm_atomic_helper_shutdown(tdev->drm);
drm_fb_cma_fbdev_fini(tdev->drm);
drm_dev_unregister(tdev->drm);
}

View File

@ -368,7 +368,6 @@ static struct drm_driver ili9225_driver = {
DRIVER_ATOMIC,
.fops = &ili9225_fops,
TINYDRM_GEM_DRIVER_OPS,
.lastclose = drm_fb_helper_lastclose,
.name = "ili9225",
.desc = "Ilitek ILI9225",
.date = "20171106",

View File

@ -145,7 +145,6 @@ static struct drm_driver ili9341_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC,
.fops = &ili9341_fops,
TINYDRM_GEM_DRIVER_OPS,
.lastclose = drm_fb_helper_lastclose,
.debugfs_init = mipi_dbi_debugfs_init,
.name = "ili9341",
.desc = "Ilitek ILI9341",

View File

@ -154,7 +154,6 @@ static struct drm_driver mi0283qt_driver = {
DRIVER_ATOMIC,
.fops = &mi0283qt_fops,
TINYDRM_GEM_DRIVER_OPS,
.lastclose = drm_fb_helper_lastclose,
.debugfs_init = mipi_dbi_debugfs_init,
.name = "mi0283qt",
.desc = "Multi-Inno MI0283QT",

View File

@ -260,6 +260,8 @@ static const struct drm_framebuffer_funcs mipi_dbi_fb_funcs = {
/**
* mipi_dbi_enable_flush - MIPI DBI enable helper
* @mipi: MIPI DBI structure
* @crtc_state: CRTC state
* @plane_state: Plane state
*
* This function sets &mipi_dbi->enabled, flushes the whole framebuffer and
* enables the backlight. Drivers can use this in their

View File

@ -304,7 +304,6 @@ static struct drm_driver st7586_driver = {
DRIVER_ATOMIC,
.fops = &st7586_fops,
TINYDRM_GEM_DRIVER_OPS,
.lastclose = drm_fb_helper_lastclose,
.debugfs_init = mipi_dbi_debugfs_init,
.name = "st7586",
.desc = "Sitronix ST7586",

View File

@ -120,7 +120,6 @@ static struct drm_driver st7735r_driver = {
DRIVER_ATOMIC,
.fops = &st7735r_fops,
TINYDRM_GEM_DRIVER_OPS,
.lastclose = drm_fb_helper_lastclose,
.debugfs_init = mipi_dbi_debugfs_init,
.name = "st7735r",
.desc = "Sitronix ST7735R",

View File

@ -227,37 +227,19 @@ v3d_set_mmap_vma_flags(struct vm_area_struct *vma)
vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
}
int v3d_gem_fault(struct vm_fault *vmf)
vm_fault_t v3d_gem_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct drm_gem_object *obj = vma->vm_private_data;
struct v3d_bo *bo = to_v3d_bo(obj);
unsigned long pfn;
pfn_t pfn;
pgoff_t pgoff;
int ret;
/* We don't use vmf->pgoff since that has the fake offset: */
pgoff = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
pfn = page_to_pfn(bo->pages[pgoff]);
pfn = __pfn_to_pfn_t(page_to_pfn(bo->pages[pgoff]), PFN_DEV);
ret = vm_insert_mixed(vma, vmf->address, __pfn_to_pfn_t(pfn, PFN_DEV));
switch (ret) {
case -EAGAIN:
case 0:
case -ERESTARTSYS:
case -EINTR:
case -EBUSY:
/*
* EBUSY is ok: this just means that another thread
* already did the job.
*/
return VM_FAULT_NOPAGE;
case -ENOMEM:
return VM_FAULT_OOM;
default:
return VM_FAULT_SIGBUS;
}
return vmf_insert_mixed(vma, vmf->address, pfn);
}
int v3d_mmap(struct file *filp, struct vm_area_struct *vma)

View File

@ -2,6 +2,7 @@
/* Copyright (C) 2015-2018 Broadcom */
#include <linux/reservation.h>
#include <linux/mm_types.h>
#include <drm/drmP.h>
#include <drm/drm_encoder.h>
#include <drm/drm_gem.h>
@ -183,6 +184,8 @@ struct v3d_job {
/* GPU virtual addresses of the start/end of the CL job. */
u32 start, end;
u32 timedout_ctca, timedout_ctra;
};
struct v3d_exec_info {
@ -252,7 +255,7 @@ int v3d_mmap_bo_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv);
int v3d_get_bo_offset_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv);
int v3d_gem_fault(struct vm_fault *vmf);
vm_fault_t v3d_gem_fault(struct vm_fault *vmf);
int v3d_mmap(struct file *filp, struct vm_area_struct *vma);
struct reservation_object *v3d_prime_res_obj(struct drm_gem_object *obj);
int v3d_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);

View File

@ -35,19 +35,7 @@ static const char *v3d_fence_get_timeline_name(struct dma_fence *fence)
return "v3d-render";
}
static bool v3d_fence_enable_signaling(struct dma_fence *fence)
{
return true;
}
const struct dma_fence_ops v3d_fence_ops = {
.get_driver_name = v3d_fence_get_driver_name,
.get_timeline_name = v3d_fence_get_timeline_name,
.enable_signaling = v3d_fence_enable_signaling,
/* Each of our fences gets signaled as complete by the IRQ
* handler, so we rely on the core's tracking of signaling.
*/
.signaled = NULL,
.wait = dma_fence_default_wait,
.release = dma_fence_free,
};

View File

@ -222,6 +222,7 @@
#define V3D_CLE_CTNCA(n) (V3D_CLE_CT0CA + 4 * n)
#define V3D_CLE_CT0RA 0x00118
#define V3D_CLE_CT1RA 0x0011c
#define V3D_CLE_CTNRA(n) (V3D_CLE_CT0RA + 4 * n)
#define V3D_CLE_CT0LC 0x00120
#define V3D_CLE_CT1LC 0x00124
#define V3D_CLE_CT0PC 0x00128

View File

@ -14,8 +14,8 @@
* to the HW only when it has completed the last one, instead of
* filling up the CT[01]Q FIFOs with jobs. Similarly, we use
* v3d_job_dependency() to manage the dependency between bin and
* render, instead of having the clients submit jobs with using the
* HW's semaphores to interlock between them.
* render, instead of having the clients submit jobs using the HW's
* semaphores to interlock between them.
*/
#include <linux/kthread.h>
@ -153,7 +153,25 @@ v3d_job_timedout(struct drm_sched_job *sched_job)
struct v3d_job *job = to_v3d_job(sched_job);
struct v3d_exec_info *exec = job->exec;
struct v3d_dev *v3d = exec->v3d;
enum v3d_queue job_q = job == &exec->bin ? V3D_BIN : V3D_RENDER;
enum v3d_queue q;
u32 ctca = V3D_CORE_READ(0, V3D_CLE_CTNCA(job_q));
u32 ctra = V3D_CORE_READ(0, V3D_CLE_CTNRA(job_q));
/* If the current address or return address have changed, then
* the GPU has probably made progress and we should delay the
* reset. This could fail if the GPU got in an infinite loop
* in the CL, but that is pretty unlikely outside of an i-g-t
* testcase.
*/
if (job->timedout_ctca != ctca || job->timedout_ctra != ctra) {
job->timedout_ctca = ctca;
job->timedout_ctra = ctra;
schedule_delayed_work(&job->base.work_tdr,
job->base.sched->timeout);
return;
}
mutex_lock(&v3d->reset_lock);

View File

@ -19,6 +19,7 @@ vc4-y := \
vc4_plane.o \
vc4_render_cl.o \
vc4_trace_points.o \
vc4_txp.o \
vc4_v3d.o \
vc4_validate.o \
vc4_validate_shaders.o

View File

@ -46,6 +46,8 @@ struct vc4_crtc_state {
struct drm_crtc_state base;
/* Dlist area for this CRTC configuration. */
struct drm_mm_node mm;
bool feed_txp;
bool txp_armed;
};
static inline struct vc4_crtc_state *
@ -324,10 +326,8 @@ static struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc)
return NULL;
}
static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc)
static void vc4_crtc_config_pv(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc);
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
@ -338,12 +338,6 @@ static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc)
bool is_dsi = (vc4_encoder->type == VC4_ENCODER_TYPE_DSI0 ||
vc4_encoder->type == VC4_ENCODER_TYPE_DSI1);
u32 format = is_dsi ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24;
bool debug_dump_regs = false;
if (debug_dump_regs) {
DRM_INFO("CRTC %d regs before:\n", drm_crtc_index(crtc));
vc4_crtc_dump_regs(vc4_crtc);
}
/* Reset the PV fifo. */
CRTC_WRITE(PV_CONTROL, 0);
@ -419,6 +413,49 @@ static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc)
PV_CONTROL_CLK_SELECT) |
PV_CONTROL_FIFO_CLR |
PV_CONTROL_EN);
}
static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
bool debug_dump_regs = false;
if (debug_dump_regs) {
DRM_INFO("CRTC %d regs before:\n", drm_crtc_index(crtc));
vc4_crtc_dump_regs(vc4_crtc);
}
if (vc4_crtc->channel == 2) {
u32 dispctrl;
u32 dsp3_mux;
/*
* SCALER_DISPCTRL_DSP3 = X, where X < 2 means 'connect DSP3 to
* FIFO X'.
* SCALER_DISPCTRL_DSP3 = 3 means 'disable DSP 3'.
*
* DSP3 is connected to FIFO2 unless the transposer is
* enabled. In this case, FIFO 2 is directly accessed by the
* TXP IP, and we need to disable the FIFO2 -> pixelvalve1
* route.
*/
if (vc4_state->feed_txp)
dsp3_mux = VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX);
else
dsp3_mux = VC4_SET_FIELD(2, SCALER_DISPCTRL_DSP3_MUX);
dispctrl = HVS_READ(SCALER_DISPCTRL) &
~SCALER_DISPCTRL_DSP3_MUX_MASK;
HVS_WRITE(SCALER_DISPCTRL, dispctrl | dsp3_mux);
}
if (!vc4_state->feed_txp)
vc4_crtc_config_pv(crtc);
HVS_WRITE(SCALER_DISPBKGNDX(vc4_crtc->channel),
SCALER_DISPBKGND_AUTOHS |
@ -499,6 +536,13 @@ static void vc4_crtc_atomic_disable(struct drm_crtc *crtc,
}
}
void vc4_crtc_txp_armed(struct drm_crtc_state *state)
{
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
vc4_state->txp_armed = true;
}
static void vc4_crtc_update_dlist(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
@ -514,8 +558,11 @@ static void vc4_crtc_update_dlist(struct drm_crtc *crtc)
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
spin_lock_irqsave(&dev->event_lock, flags);
vc4_crtc->event = crtc->state->event;
crtc->state->event = NULL;
if (!vc4_state->feed_txp || vc4_state->txp_armed) {
vc4_crtc->event = crtc->state->event;
crtc->state->event = NULL;
}
HVS_WRITE(SCALER_DISPLISTX(vc4_crtc->channel),
vc4_state->mm.start);
@ -533,8 +580,8 @@ static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_device *dev = crtc->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
struct drm_crtc_state *state = crtc->state;
struct drm_display_mode *mode = &state->adjusted_mode;
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
require_hvs_enabled(dev);
@ -546,15 +593,21 @@ static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
/* Turn on the scaler, which will wait for vstart to start
* compositing.
* When feeding the transposer, we should operate in oneshot
* mode.
*/
HVS_WRITE(SCALER_DISPCTRLX(vc4_crtc->channel),
VC4_SET_FIELD(mode->hdisplay, SCALER_DISPCTRLX_WIDTH) |
VC4_SET_FIELD(mode->vdisplay, SCALER_DISPCTRLX_HEIGHT) |
SCALER_DISPCTRLX_ENABLE);
SCALER_DISPCTRLX_ENABLE |
(vc4_state->feed_txp ? SCALER_DISPCTRLX_ONESHOT : 0));
/* Turn on the pixel valve, which will emit the vstart signal. */
CRTC_WRITE(PV_V_CONTROL,
CRTC_READ(PV_V_CONTROL) | PV_VCONTROL_VIDEN);
/* When feeding the transposer block the pixelvalve is unneeded and
* should not be enabled.
*/
if (!vc4_state->feed_txp)
CRTC_WRITE(PV_V_CONTROL,
CRTC_READ(PV_V_CONTROL) | PV_VCONTROL_VIDEN);
}
static enum drm_mode_status vc4_crtc_mode_valid(struct drm_crtc *crtc,
@ -579,8 +632,10 @@ static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_plane *plane;
unsigned long flags;
const struct drm_plane_state *plane_state;
struct drm_connector *conn;
struct drm_connector_state *conn_state;
u32 dlist_count = 0;
int ret;
int ret, i;
/* The pixelvalve can only feed one encoder (and encoders are
* 1:1 with connectors.)
@ -600,6 +655,24 @@ static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
if (ret)
return ret;
for_each_new_connector_in_state(state->state, conn, conn_state, i) {
if (conn_state->crtc != crtc)
continue;
/* The writeback connector is implemented using the transposer
* block which is directly taking its data from the HVS FIFO.
*/
if (conn->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) {
state->no_vblank = true;
vc4_state->feed_txp = true;
} else {
state->no_vblank = false;
vc4_state->feed_txp = false;
}
break;
}
return 0;
}
@ -713,7 +786,8 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
spin_lock_irqsave(&dev->event_lock, flags);
if (vc4_crtc->event &&
(vc4_state->mm.start == HVS_READ(SCALER_DISPLACTX(chan)))) {
(vc4_state->mm.start == HVS_READ(SCALER_DISPLACTX(chan)) ||
vc4_state->feed_txp)) {
drm_crtc_send_vblank_event(crtc, vc4_crtc->event);
vc4_crtc->event = NULL;
drm_crtc_vblank_put(crtc);
@ -721,6 +795,13 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
spin_unlock_irqrestore(&dev->event_lock, flags);
}
void vc4_crtc_handle_vblank(struct vc4_crtc *crtc)
{
crtc->t_vblank = ktime_get();
drm_crtc_handle_vblank(&crtc->base);
vc4_crtc_handle_page_flip(crtc);
}
static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
{
struct vc4_crtc *vc4_crtc = data;
@ -728,10 +809,8 @@ static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
irqreturn_t ret = IRQ_NONE;
if (stat & PV_INT_VFP_START) {
vc4_crtc->t_vblank = ktime_get();
CRTC_WRITE(PV_INTSTAT, PV_INT_VFP_START);
drm_crtc_handle_vblank(&vc4_crtc->base);
vc4_crtc_handle_page_flip(vc4_crtc);
vc4_crtc_handle_vblank(vc4_crtc);
ret = IRQ_HANDLED;
}
@ -884,12 +963,15 @@ static int vc4_page_flip(struct drm_crtc *crtc,
static struct drm_crtc_state *vc4_crtc_duplicate_state(struct drm_crtc *crtc)
{
struct vc4_crtc_state *vc4_state;
struct vc4_crtc_state *vc4_state, *old_vc4_state;
vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
if (!vc4_state)
return NULL;
old_vc4_state = to_vc4_crtc_state(crtc->state);
vc4_state->feed_txp = old_vc4_state->feed_txp;
__drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
return &vc4_state->base;
}
@ -987,9 +1069,17 @@ static void vc4_set_crtc_possible_masks(struct drm_device *drm,
struct drm_encoder *encoder;
drm_for_each_encoder(encoder, drm) {
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
struct vc4_encoder *vc4_encoder;
int i;
/* HVS FIFO2 can feed the TXP IP. */
if (crtc_data->hvs_channel == 2 &&
encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL) {
encoder->possible_crtcs |= drm_crtc_mask(crtc);
continue;
}
vc4_encoder = to_vc4_encoder(encoder);
for (i = 0; i < ARRAY_SIZE(crtc_data->encoder_types); i++) {
if (vc4_encoder->type == encoder_types[i]) {
vc4_encoder->clock_select = i;

View File

@ -21,6 +21,7 @@ static const struct drm_info_list vc4_debugfs_list[] = {
{"dsi1_regs", vc4_dsi_debugfs_regs, 0, (void *)(uintptr_t)1},
{"hdmi_regs", vc4_hdmi_debugfs_regs, 0},
{"vec_regs", vc4_vec_debugfs_regs, 0},
{"txp_regs", vc4_txp_debugfs_regs, 0},
{"hvs_regs", vc4_hvs_debugfs_regs, 0},
{"crtc0_regs", vc4_crtc_debugfs_regs, 0, (void *)(uintptr_t)0},
{"crtc1_regs", vc4_crtc_debugfs_regs, 0, (void *)(uintptr_t)1},

View File

@ -344,6 +344,7 @@ static struct platform_driver *const component_drivers[] = {
&vc4_vec_driver,
&vc4_dpi_driver,
&vc4_dsi_driver,
&vc4_txp_driver,
&vc4_hvs_driver,
&vc4_crtc_driver,
&vc4_v3d_driver,

View File

@ -73,6 +73,7 @@ struct vc4_dev {
struct vc4_dpi *dpi;
struct vc4_dsi *dsi1;
struct vc4_vec *vec;
struct vc4_txp *txp;
struct vc4_hang_state *hang_state;
@ -698,6 +699,8 @@ bool vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id,
bool in_vblank_irq, int *vpos, int *hpos,
ktime_t *stime, ktime_t *etime,
const struct drm_display_mode *mode);
void vc4_crtc_handle_vblank(struct vc4_crtc *crtc);
void vc4_crtc_txp_armed(struct drm_crtc_state *state);
/* vc4_debugfs.c */
int vc4_debugfs_init(struct drm_minor *minor);
@ -745,6 +748,10 @@ int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused);
extern struct platform_driver vc4_vec_driver;
int vc4_vec_debugfs_regs(struct seq_file *m, void *unused);
/* vc4_txp.c */
extern struct platform_driver vc4_txp_driver;
int vc4_txp_debugfs_regs(struct seq_file *m, void *unused);
/* vc4_irq.c */
irqreturn_t vc4_irq(int irq, void *arg);
void vc4_irq_preinstall(struct drm_device *dev);

View File

@ -1612,8 +1612,18 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0,
&panel, &dsi->bridge);
if (ret)
if (ret) {
/* If the bridge or panel pointed by dev->of_node is not
* enabled, just return 0 here so that we don't prevent the DRM
* dev from being registered. Of course that means the DSI
* encoder won't be exposed, but that's not a problem since
* nothing is connected to it.
*/
if (ret == -ENODEV)
return 0;
return ret;
}
if (panel) {
dsi->bridge = devm_drm_panel_bridge_add(dev, panel,
@ -1664,7 +1674,8 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master,
struct vc4_dev *vc4 = to_vc4_dev(drm);
struct vc4_dsi *dsi = dev_get_drvdata(dev);
pm_runtime_disable(dev);
if (dsi->bridge)
pm_runtime_disable(dev);
vc4_dsi_encoder_destroy(dsi->encoder);

View File

@ -153,18 +153,11 @@ vc4_atomic_complete_commit(struct drm_atomic_state *state)
drm_atomic_helper_commit_modeset_enables(dev, state);
/* Make sure that drm_atomic_helper_wait_for_vblanks()
* actually waits for vblank. If we're doing a full atomic
* modeset (as opposed to a vc4_update_plane() short circuit),
* then we need to wait for scanout to be done with our
* display lists before we free it and potentially reallocate
* and overwrite the dlist memory with a new modeset.
*/
state->legacy_cursor_update = false;
drm_atomic_helper_fake_vblank(state);
drm_atomic_helper_commit_hw_done(state);
drm_atomic_helper_wait_for_vblanks(dev, state);
drm_atomic_helper_wait_for_flip_done(dev, state);
drm_atomic_helper_cleanup_planes(dev, state);

View File

@ -0,0 +1,477 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2018 Broadcom
*
* Authors:
* Eric Anholt <eric@anholt.net>
* Boris Brezillon <boris.brezillon@bootlin.com>
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_panel.h>
#include <drm/drm_writeback.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
/* Base address of the output. Raster formats must be 4-byte aligned,
* T and LT must be 16-byte aligned or maybe utile-aligned (docs are
* inconsistent, but probably utile).
*/
#define TXP_DST_PTR 0x00
/* Pitch in bytes for raster images, 16-byte aligned. For tiled, it's
* the width in tiles.
*/
#define TXP_DST_PITCH 0x04
/* For T-tiled imgaes, DST_PITCH should be the number of tiles wide,
* shifted up.
*/
# define TXP_T_TILE_WIDTH_SHIFT 7
/* For LT-tiled images, DST_PITCH should be the number of utiles wide,
* shifted up.
*/
# define TXP_LT_TILE_WIDTH_SHIFT 4
/* Pre-rotation width/height of the image. Must match HVS config.
*
* If TFORMAT and 32-bit, limit is 1920 for 32-bit and 3840 to 16-bit
* and width/height must be tile or utile-aligned as appropriate. If
* transposing (rotating), width is limited to 1920.
*
* Height is limited to various numbers between 4088 and 4095. I'd
* just use 4088 to be safe.
*/
#define TXP_DIM 0x08
# define TXP_HEIGHT_SHIFT 16
# define TXP_HEIGHT_MASK GENMASK(31, 16)
# define TXP_WIDTH_SHIFT 0
# define TXP_WIDTH_MASK GENMASK(15, 0)
#define TXP_DST_CTRL 0x0c
/* These bits are set to 0x54 */
#define TXP_PILOT_SHIFT 24
#define TXP_PILOT_MASK GENMASK(31, 24)
/* Bits 22-23 are set to 0x01 */
#define TXP_VERSION_SHIFT 22
#define TXP_VERSION_MASK GENMASK(23, 22)
/* Powers down the internal memory. */
# define TXP_POWERDOWN BIT(21)
/* Enables storing the alpha component in 8888/4444, instead of
* filling with ~ALPHA_INVERT.
*/
# define TXP_ALPHA_ENABLE BIT(20)
/* 4 bits, each enables stores for a channel in each set of 4 bytes.
* Set to 0xf for normal operation.
*/
# define TXP_BYTE_ENABLE_SHIFT 16
# define TXP_BYTE_ENABLE_MASK GENMASK(19, 16)
/* Debug: Generate VSTART again at EOF. */
# define TXP_VSTART_AT_EOF BIT(15)
/* Debug: Terminate the current frame immediately. Stops AXI
* writes.
*/
# define TXP_ABORT BIT(14)
# define TXP_DITHER BIT(13)
/* Inverts alpha if TXP_ALPHA_ENABLE, chooses fill value for
* !TXP_ALPHA_ENABLE.
*/
# define TXP_ALPHA_INVERT BIT(12)
/* Note: I've listed the channels here in high bit (in byte 3/2/1) to
* low bit (in byte 0) order.
*/
# define TXP_FORMAT_SHIFT 8
# define TXP_FORMAT_MASK GENMASK(11, 8)
# define TXP_FORMAT_ABGR4444 0
# define TXP_FORMAT_ARGB4444 1
# define TXP_FORMAT_BGRA4444 2
# define TXP_FORMAT_RGBA4444 3
# define TXP_FORMAT_BGR565 6
# define TXP_FORMAT_RGB565 7
/* 888s are non-rotated, raster-only */
# define TXP_FORMAT_BGR888 8
# define TXP_FORMAT_RGB888 9
# define TXP_FORMAT_ABGR8888 12
# define TXP_FORMAT_ARGB8888 13
# define TXP_FORMAT_BGRA8888 14
# define TXP_FORMAT_RGBA8888 15
/* If TFORMAT is set, generates LT instead of T format. */
# define TXP_LINEAR_UTILE BIT(7)
/* Rotate output by 90 degrees. */
# define TXP_TRANSPOSE BIT(6)
/* Generate a tiled format for V3D. */
# define TXP_TFORMAT BIT(5)
/* Generates some undefined test mode output. */
# define TXP_TEST_MODE BIT(4)
/* Request odd field from HVS. */
# define TXP_FIELD BIT(3)
/* Raise interrupt when idle. */
# define TXP_EI BIT(2)
/* Set when generating a frame, clears when idle. */
# define TXP_BUSY BIT(1)
/* Starts a frame. Self-clearing. */
# define TXP_GO BIT(0)
/* Number of lines received and committed to memory. */
#define TXP_PROGRESS 0x10
#define TXP_READ(offset) readl(txp->regs + (offset))
#define TXP_WRITE(offset, val) writel(val, txp->regs + (offset))
struct vc4_txp {
struct platform_device *pdev;
struct drm_writeback_connector connector;
void __iomem *regs;
};
static inline struct vc4_txp *encoder_to_vc4_txp(struct drm_encoder *encoder)
{
return container_of(encoder, struct vc4_txp, connector.encoder);
}
static inline struct vc4_txp *connector_to_vc4_txp(struct drm_connector *conn)
{
return container_of(conn, struct vc4_txp, connector.base);
}
#define TXP_REG(reg) { reg, #reg }
static const struct {
u32 reg;
const char *name;
} txp_regs[] = {
TXP_REG(TXP_DST_PTR),
TXP_REG(TXP_DST_PITCH),
TXP_REG(TXP_DIM),
TXP_REG(TXP_DST_CTRL),
TXP_REG(TXP_PROGRESS),
};
#ifdef CONFIG_DEBUG_FS
int vc4_txp_debugfs_regs(struct seq_file *m, void *unused)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
struct drm_device *dev = node->minor->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_txp *txp = vc4->txp;
int i;
if (!txp)
return 0;
for (i = 0; i < ARRAY_SIZE(txp_regs); i++) {
seq_printf(m, "%s (0x%04x): 0x%08x\n",
txp_regs[i].name, txp_regs[i].reg,
TXP_READ(txp_regs[i].reg));
}
return 0;
}
#endif
static int vc4_txp_connector_get_modes(struct drm_connector *connector)
{
struct drm_device *dev = connector->dev;
return drm_add_modes_noedid(connector, dev->mode_config.max_width,
dev->mode_config.max_height);
}
static enum drm_mode_status
vc4_txp_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct drm_device *dev = connector->dev;
struct drm_mode_config *mode_config = &dev->mode_config;
int w = mode->hdisplay, h = mode->vdisplay;
if (w < mode_config->min_width || w > mode_config->max_width)
return MODE_BAD_HVALUE;
if (h < mode_config->min_height || h > mode_config->max_height)
return MODE_BAD_VVALUE;
return MODE_OK;
}
static const u32 drm_fmts[] = {
DRM_FORMAT_RGB888,
DRM_FORMAT_BGR888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_RGBX8888,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_RGBA8888,
DRM_FORMAT_BGRA8888,
};
static const u32 txp_fmts[] = {
TXP_FORMAT_RGB888,
TXP_FORMAT_BGR888,
TXP_FORMAT_ARGB8888,
TXP_FORMAT_ABGR8888,
TXP_FORMAT_ARGB8888,
TXP_FORMAT_ABGR8888,
TXP_FORMAT_RGBA8888,
TXP_FORMAT_BGRA8888,
TXP_FORMAT_RGBA8888,
TXP_FORMAT_BGRA8888,
};
static int vc4_txp_connector_atomic_check(struct drm_connector *conn,
struct drm_connector_state *conn_state)
{
struct drm_crtc_state *crtc_state;
struct drm_gem_cma_object *gem;
struct drm_framebuffer *fb;
int i;
if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
return 0;
crtc_state = drm_atomic_get_new_crtc_state(conn_state->state,
conn_state->crtc);
fb = conn_state->writeback_job->fb;
if (fb->width != crtc_state->mode.hdisplay ||
fb->height != crtc_state->mode.vdisplay) {
DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n",
fb->width, fb->height);
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(drm_fmts); i++) {
if (fb->format->format == drm_fmts[i])
break;
}
if (i == ARRAY_SIZE(drm_fmts))
return -EINVAL;
gem = drm_fb_cma_get_gem_obj(fb, 0);
/* Pitch must be aligned on 16 bytes. */
if (fb->pitches[0] & GENMASK(3, 0))
return -EINVAL;
vc4_crtc_txp_armed(crtc_state);
return 0;
}
static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
struct drm_connector_state *conn_state)
{
struct vc4_txp *txp = connector_to_vc4_txp(conn);
struct drm_gem_cma_object *gem;
struct drm_display_mode *mode;
struct drm_framebuffer *fb;
u32 ctrl;
int i;
if (WARN_ON(!conn_state->writeback_job ||
!conn_state->writeback_job->fb))
return;
mode = &conn_state->crtc->state->adjusted_mode;
fb = conn_state->writeback_job->fb;
for (i = 0; i < ARRAY_SIZE(drm_fmts); i++) {
if (fb->format->format == drm_fmts[i])
break;
}
if (WARN_ON(i == ARRAY_SIZE(drm_fmts)))
return;
ctrl = TXP_GO | TXP_VSTART_AT_EOF | TXP_EI |
VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE) |
VC4_SET_FIELD(txp_fmts[i], TXP_FORMAT);
if (fb->format->has_alpha)
ctrl |= TXP_ALPHA_ENABLE;
gem = drm_fb_cma_get_gem_obj(fb, 0);
TXP_WRITE(TXP_DST_PTR, gem->paddr + fb->offsets[0]);
TXP_WRITE(TXP_DST_PITCH, fb->pitches[0]);
TXP_WRITE(TXP_DIM,
VC4_SET_FIELD(mode->hdisplay, TXP_WIDTH) |
VC4_SET_FIELD(mode->vdisplay, TXP_HEIGHT));
TXP_WRITE(TXP_DST_CTRL, ctrl);
drm_writeback_queue_job(&txp->connector, conn_state->writeback_job);
}
static const struct drm_connector_helper_funcs vc4_txp_connector_helper_funcs = {
.get_modes = vc4_txp_connector_get_modes,
.mode_valid = vc4_txp_connector_mode_valid,
.atomic_check = vc4_txp_connector_atomic_check,
.atomic_commit = vc4_txp_connector_atomic_commit,
};
static enum drm_connector_status
vc4_txp_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static void vc4_txp_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs vc4_txp_connector_funcs = {
.detect = vc4_txp_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = vc4_txp_connector_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static void vc4_txp_encoder_disable(struct drm_encoder *encoder)
{
struct vc4_txp *txp = encoder_to_vc4_txp(encoder);
if (TXP_READ(TXP_DST_CTRL) & TXP_BUSY) {
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
TXP_WRITE(TXP_DST_CTRL, TXP_ABORT);
while (TXP_READ(TXP_DST_CTRL) & TXP_BUSY &&
time_before(jiffies, timeout))
;
WARN_ON(TXP_READ(TXP_DST_CTRL) & TXP_BUSY);
}
TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN);
}
static const struct drm_encoder_helper_funcs vc4_txp_encoder_helper_funcs = {
.disable = vc4_txp_encoder_disable,
};
static irqreturn_t vc4_txp_interrupt(int irq, void *data)
{
struct vc4_txp *txp = data;
TXP_WRITE(TXP_DST_CTRL, TXP_READ(TXP_DST_CTRL) & ~TXP_EI);
vc4_crtc_handle_vblank(to_vc4_crtc(txp->connector.base.state->crtc));
drm_writeback_signal_completion(&txp->connector, 0);
return IRQ_HANDLED;
}
static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dev *vc4 = to_vc4_dev(drm);
struct vc4_txp *txp;
int ret, irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
txp = devm_kzalloc(dev, sizeof(*txp), GFP_KERNEL);
if (!txp)
return -ENOMEM;
txp->pdev = pdev;
txp->regs = vc4_ioremap_regs(pdev, 0);
if (IS_ERR(txp->regs))
return PTR_ERR(txp->regs);
drm_connector_helper_add(&txp->connector.base,
&vc4_txp_connector_helper_funcs);
ret = drm_writeback_connector_init(drm, &txp->connector,
&vc4_txp_connector_funcs,
&vc4_txp_encoder_helper_funcs,
drm_fmts, ARRAY_SIZE(drm_fmts));
if (ret)
return ret;
ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0,
dev_name(dev), txp);
if (ret)
return ret;
dev_set_drvdata(dev, txp);
vc4->txp = txp;
return 0;
}
static void vc4_txp_unbind(struct device *dev, struct device *master,
void *data)
{
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dev *vc4 = to_vc4_dev(drm);
struct vc4_txp *txp = dev_get_drvdata(dev);
vc4_txp_connector_destroy(&txp->connector.base);
vc4->txp = NULL;
}
static const struct component_ops vc4_txp_ops = {
.bind = vc4_txp_bind,
.unbind = vc4_txp_unbind,
};
static int vc4_txp_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &vc4_txp_ops);
}
static int vc4_txp_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &vc4_txp_ops);
return 0;
}
static const struct of_device_id vc4_txp_dt_match[] = {
{ .compatible = "brcm,bcm2835-txp" },
{ /* sentinel */ },
};
struct platform_driver vc4_txp_driver = {
.probe = vc4_txp_probe,
.remove = vc4_txp_remove,
.driver = {
.name = "vc4_txp",
.of_match_table = vc4_txp_dt_match,
},
};

View File

@ -0,0 +1,3 @@
vkms-y := vkms_drv.o vkms_plane.o vkms_output.o vkms_crtc.o
obj-$(CONFIG_DRM_VKMS) += vkms.o

View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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 "vkms_drv.h"
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
static const struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.destroy = drm_crtc_cleanup,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
};
int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
struct drm_plane *primary, struct drm_plane *cursor)
{
int ret;
ret = drm_crtc_init_with_planes(dev, crtc, primary, cursor,
&vkms_crtc_funcs, NULL);
if (ret) {
DRM_ERROR("Failed to init CRTC\n");
return ret;
}
return ret;
}

View File

@ -0,0 +1,139 @@
/*
* 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/module.h>
#include <drm/drm_gem.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_atomic_helper.h>
#include "vkms_drv.h"
#define DRIVER_NAME "vkms"
#define DRIVER_DESC "Virtual Kernel Mode Setting"
#define DRIVER_DATE "20180514"
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0
#define XRES_MIN 32
#define YRES_MIN 32
#define XRES_MAX 8192
#define YRES_MAX 8192
static struct vkms_device *vkms_device;
static const struct file_operations vkms_driver_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.mmap = drm_gem_mmap,
.unlocked_ioctl = drm_ioctl,
.compat_ioctl = drm_compat_ioctl,
.poll = drm_poll,
.read = drm_read,
.llseek = no_llseek,
.release = drm_release,
};
static void vkms_release(struct drm_device *dev)
{
struct vkms_device *vkms = container_of(dev, struct vkms_device, drm);
platform_device_unregister(vkms->platform);
drm_mode_config_cleanup(&vkms->drm);
drm_dev_fini(&vkms->drm);
}
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
.release = vkms_release,
.fops = &vkms_driver_fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
};
static const struct drm_mode_config_funcs vkms_mode_funcs = {
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static int vkms_modeset_init(struct vkms_device *vkmsdev)
{
struct drm_device *dev = &vkmsdev->drm;
drm_mode_config_init(dev);
dev->mode_config.funcs = &vkms_mode_funcs;
dev->mode_config.min_width = XRES_MIN;
dev->mode_config.min_height = YRES_MIN;
dev->mode_config.max_width = XRES_MAX;
dev->mode_config.max_height = YRES_MAX;
return vkms_output_init(vkmsdev);
}
static int __init vkms_init(void)
{
int ret;
vkms_device = kzalloc(sizeof(*vkms_device), GFP_KERNEL);
if (!vkms_device)
return -ENOMEM;
ret = drm_dev_init(&vkms_device->drm, &vkms_driver, NULL);
if (ret)
goto out_free;
vkms_device->platform =
platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
if (IS_ERR(vkms_device->platform)) {
ret = PTR_ERR(vkms_device->platform);
goto out_fini;
}
ret = vkms_modeset_init(vkms_device);
if (ret)
goto out_unregister;
ret = drm_dev_register(&vkms_device->drm, 0);
if (ret)
goto out_unregister;
return 0;
out_unregister:
platform_device_unregister(vkms_device->platform);
out_fini:
drm_dev_fini(&vkms_device->drm);
out_free:
kfree(vkms_device);
return ret;
}
static void __exit vkms_exit(void)
{
if (!vkms_device) {
DRM_INFO("vkms_device is NULL.\n");
return;
}
drm_dev_unregister(&vkms_device->drm);
drm_dev_put(&vkms_device->drm);
kfree(vkms_device);
}
module_init(vkms_init);
module_exit(vkms_exit);
MODULE_AUTHOR("Haneen Mohammed <hamohammed.sa@gmail.com>");
MODULE_AUTHOR("Rodrigo Siqueira <rodrigosiqueiramelo@gmail.com>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,31 @@
#ifndef _VKMS_DRV_H_
#define _VKMS_DRV_H_
#include <drm/drmP.h>
#include <drm/drm.h>
#include <drm/drm_encoder.h>
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};
struct vkms_output {
struct drm_crtc crtc;
struct drm_encoder encoder;
struct drm_connector connector;
};
struct vkms_device {
struct drm_device drm;
struct platform_device *platform;
struct vkms_output output;
};
int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
struct drm_plane *primary, struct drm_plane *cursor);
int vkms_output_init(struct vkms_device *vkmsdev);
struct drm_plane *vkms_plane_init(struct vkms_device *vkmsdev);
#endif /* _VKMS_DRV_H_ */

View File

@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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 "vkms_drv.h"
#include <drm/drm_crtc_helper.h>
static void vkms_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs vkms_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = vkms_connector_destroy,
};
static const struct drm_encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
int vkms_output_init(struct vkms_device *vkmsdev)
{
struct vkms_output *output = &vkmsdev->output;
struct drm_device *dev = &vkmsdev->drm;
struct drm_connector *connector = &output->connector;
struct drm_encoder *encoder = &output->encoder;
struct drm_crtc *crtc = &output->crtc;
struct drm_plane *primary;
int ret;
primary = vkms_plane_init(vkmsdev);
if (IS_ERR(primary))
return PTR_ERR(primary);
ret = vkms_crtc_init(dev, crtc, primary, NULL);
if (ret)
goto err_crtc;
ret = drm_connector_init(dev, connector, &vkms_connector_funcs,
DRM_MODE_CONNECTOR_VIRTUAL);
if (ret) {
DRM_ERROR("Failed to init connector\n");
goto err_connector;
}
ret = drm_connector_register(connector);
if (ret) {
DRM_ERROR("Failed to register connector\n");
goto err_connector_register;
}
ret = drm_encoder_init(dev, encoder, &vkms_encoder_funcs,
DRM_MODE_ENCODER_VIRTUAL, NULL);
if (ret) {
DRM_ERROR("Failed to init encoder\n");
goto err_encoder;
}
encoder->possible_crtcs = 1;
ret = drm_mode_connector_attach_encoder(connector, encoder);
if (ret) {
DRM_ERROR("Failed to attach connector to encoder\n");
goto err_attach;
}
drm_mode_config_reset(dev);
return 0;
err_attach:
drm_encoder_cleanup(encoder);
err_encoder:
drm_connector_unregister(connector);
err_connector_register:
drm_connector_cleanup(connector);
err_connector:
drm_crtc_cleanup(crtc);
err_crtc:
drm_plane_cleanup(primary);
return ret;
}

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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 "vkms_drv.h"
#include <drm/drm_plane_helper.h>
#include <drm/drm_atomic_helper.h>
static const struct drm_plane_funcs vkms_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
struct drm_plane *vkms_plane_init(struct vkms_device *vkmsdev)
{
struct drm_device *dev = &vkmsdev->drm;
struct drm_plane *plane;
const u32 *formats;
int ret, nformats;
plane = kzalloc(sizeof(*plane), GFP_KERNEL);
if (!plane)
return ERR_PTR(-ENOMEM);
formats = vkms_formats;
nformats = ARRAY_SIZE(vkms_formats);
ret = drm_universal_plane_init(dev, plane, 0,
&vkms_plane_funcs,
formats, nformats,
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
if (ret) {
kfree(plane);
return ERR_PTR(ret);
}
return plane;
}

View File

@ -100,6 +100,7 @@ int __must_check drm_atomic_helper_swap_state(struct drm_atomic_state *state,
int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
bool nonblock);
void drm_atomic_helper_wait_for_dependencies(struct drm_atomic_state *state);
void drm_atomic_helper_fake_vblank(struct drm_atomic_state *state);
void drm_atomic_helper_commit_hw_done(struct drm_atomic_state *state);
void drm_atomic_helper_commit_cleanup_done(struct drm_atomic_state *state);

139
include/drm/drm_client.h Normal file
View File

@ -0,0 +1,139 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _DRM_CLIENT_H_
#define _DRM_CLIENT_H_
#include <linux/types.h>
struct drm_client_dev;
struct drm_device;
struct drm_file;
struct drm_framebuffer;
struct drm_gem_object;
struct drm_minor;
struct module;
/**
* struct drm_client_funcs - DRM client callbacks
*/
struct drm_client_funcs {
/**
* @owner: The module owner
*/
struct module *owner;
/**
* @unregister:
*
* Called when &drm_device is unregistered. The client should respond by
* releasing it's resources using drm_client_release().
*
* This callback is optional.
*/
void (*unregister)(struct drm_client_dev *client);
/**
* @restore:
*
* Called on drm_lastclose(). The first client instance in the list that
* returns zero gets the privilege to restore and no more clients are
* called. This callback is not called after @unregister has been called.
*
* This callback is optional.
*/
int (*restore)(struct drm_client_dev *client);
/**
* @hotplug:
*
* Called on drm_kms_helper_hotplug_event().
* This callback is not called after @unregister has been called.
*
* This callback is optional.
*/
int (*hotplug)(struct drm_client_dev *client);
};
/**
* struct drm_client_dev - DRM client instance
*/
struct drm_client_dev {
/**
* @dev: DRM device
*/
struct drm_device *dev;
/**
* @name: Name of the client.
*/
const char *name;
/**
* @list:
*
* List of all clients of a DRM device, linked into
* &drm_device.clientlist. Protected by &drm_device.clientlist_mutex.
*/
struct list_head list;
/**
* @funcs: DRM client functions (optional)
*/
const struct drm_client_funcs *funcs;
/**
* @file: DRM file
*/
struct drm_file *file;
};
int drm_client_new(struct drm_device *dev, struct drm_client_dev *client,
const char *name, const struct drm_client_funcs *funcs);
void drm_client_release(struct drm_client_dev *client);
void drm_client_dev_unregister(struct drm_device *dev);
void drm_client_dev_hotplug(struct drm_device *dev);
void drm_client_dev_restore(struct drm_device *dev);
/**
* struct drm_client_buffer - DRM client buffer
*/
struct drm_client_buffer {
/**
* @client: DRM client
*/
struct drm_client_dev *client;
/**
* @handle: Buffer handle
*/
u32 handle;
/**
* @pitch: Buffer pitch
*/
u32 pitch;
/**
* @gem: GEM object backing this buffer
*/
struct drm_gem_object *gem;
/**
* @vaddr: Virtual address for the buffer
*/
void *vaddr;
/**
* @fb: DRM framebuffer
*/
struct drm_framebuffer *fb;
};
struct drm_client_buffer *
drm_client_framebuffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format);
void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
int drm_client_debugfs_init(struct drm_minor *minor);
#endif

View File

@ -290,6 +290,10 @@ struct drm_display_info {
#define DRM_BUS_FLAG_DATA_MSB_TO_LSB (1<<4)
/* data is transmitted LSB to MSB on the bus */
#define DRM_BUS_FLAG_DATA_LSB_TO_MSB (1<<5)
/* drive sync on pos. edge */
#define DRM_BUS_FLAG_SYNC_POSEDGE (1<<6)
/* drive sync on neg. edge */
#define DRM_BUS_FLAG_SYNC_NEGEDGE (1<<7)
/**
* @bus_flags: Additional information (like pixel signal polarity) for
@ -1187,6 +1191,9 @@ struct drm_connector *
drm_connector_list_iter_next(struct drm_connector_list_iter *iter);
void drm_connector_list_iter_end(struct drm_connector_list_iter *iter);
bool drm_connector_has_possible_encoder(struct drm_connector *connector,
struct drm_encoder *encoder);
/**
* drm_for_each_connector_iter - connector_list iterator macro
* @connector: &struct drm_connector pointer used as cursor
@ -1199,4 +1206,17 @@ void drm_connector_list_iter_end(struct drm_connector_list_iter *iter);
#define drm_for_each_connector_iter(connector, iter) \
while ((connector = drm_connector_list_iter_next(iter)))
/**
* drm_connector_for_each_possible_encoder - iterate connector's possible encoders
* @connector: &struct drm_connector pointer
* @encoder: &struct drm_encoder pointer used as cursor
* @__i: int iteration cursor, for macro-internal use
*/
#define drm_connector_for_each_possible_encoder(connector, encoder, __i) \
for ((__i) = 0; (__i) < ARRAY_SIZE((connector)->encoder_ids) && \
(connector)->encoder_ids[(__i)] != 0; (__i)++) \
for_each_if((encoder) = \
drm_encoder_find((connector)->dev, NULL, \
(connector)->encoder_ids[(__i)])) \
#endif

View File

@ -119,6 +119,29 @@ struct drm_crtc_state {
bool zpos_changed : 1;
bool color_mgmt_changed : 1;
/**
* @no_vblank:
*
* Reflects the ability of a CRTC to send VBLANK events. This state
* usually depends on the pipeline configuration, and the main usuage
* is CRTCs feeding a writeback connector operating in oneshot mode.
* In this case the VBLANK event is only generated when a job is queued
* to the writeback connector, and we want the core to fake VBLANK
* events when this part of the pipeline hasn't changed but others had
* or when the CRTC and connectors are being disabled.
*
* __drm_atomic_helper_crtc_duplicate_state() will not reset the value
* from the current state, the CRTC driver is then responsible for
* updating this field when needed.
*
* Note that the combination of &drm_crtc_state.event == NULL and
* &drm_crtc_state.no_blank == true is valid and usually used when the
* writeback connector attached to the CRTC has a new job queued. In
* this case the driver will send the VBLANK event on its own when the
* writeback job is complete.
*/
bool no_vblank : 1;
/* attached planes bitmask:
* WARNING: transitional helpers do not maintain plane_mask so
* drivers not converted over to atomic helpers should not rely

Some files were not shown because too many files have changed in this diff Show More