mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 15:41:36 +00:00
drm-misc-next for 4.20:
UAPI Changes: - Add userspace dma-buf device to turn memfd regions into dma-bufs (Gerd) Cross-subsystem Changes: - None Core Changes: - Remove user logspam and useless lock in vma_offset_mgr destroy (Chris) Driver Changes: - various: fbdev: Wrap remove_conflicting_framebuffers with resource_len accessors to remove a bunch of cargo-cult (Michał) - rockchip: Add rgb output iface support + fixes (Sandy/Heiko) - nouveau/amdgpu: Add cec-over-aux support (Hans) - sun4i: Add support for Allwinner A64 (Jagan) Cc: Gerd Hoffmann <kraxel@redhat.com> Cc: Chris Wilson <chris@chris-wilson.co.uk> Cc: Michał Mirosław <mirq-linux@rere.qmqm.pl> Cc: Heiko Stuebner <heiko@sntech.de> Cc: Sandy Huang <hjc@rock-chips.com> Cc: Hans Verkuil <hans.verkuil@cisco.com> Cc: Jagan Teki <jagan@amarulasolutions.com> -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEEfxcpfMSgdnQMs+QqlvcN/ahKBwoFAluQOdQACgkQlvcN/ahK BwpmLAf/cZeOlBegXiXvU9MK6yB3dj4JN9mtkXelxtv69Gjs7k3MeSeduAn5PNcC NIDiM6x/nFWgDHWtUgXeJKlYNqb00MI/kS5yb9MUHNE+wR+NTqc8mHdgdbXkaOXd n+Ox9HPAXiZ7djqe+dmoqt+C2kAfCltLGC+//1OJBIr604kgUl/aQcacoup8WJIB +W9Qb/JT/0mfS+lgKIp+SUKZnMUXKjL+Qb4SBrz5uv6DN1EEaqdO9J+UBbTbhpVO lBIMnpdB8fjACaiThRg59i9MthVq0a7ac8xqJZ7smGl+Y03c7tsK2n86DebAyymp WYCk+8LUyRTgrf9yrIleBq/uG2Dlbg== =rlAr -----END PGP SIGNATURE----- Merge tag 'drm-misc-next-2018-09-05' of git://anongit.freedesktop.org/drm/drm-misc into drm-next drm-misc-next for 4.20: UAPI Changes: - Add userspace dma-buf device to turn memfd regions into dma-bufs (Gerd) - Add per-plane blend mode property (Lowry) - Change in drm_fourcc.h is documentation only (Brian) Cross-subsystem Changes: - None Core Changes: - Remove user logspam and useless lock in vma_offset_mgr destroy (Chris) - Add get/verify_crc_source for improved crc source selection (Mahesh) - Add __drm_atomic_helper_plane_reset to reduce copypasta (Alexandru) Driver Changes: - various: Replance ref/unref calls with drm_dev_get/put (Thomas) - bridge: Add driver for TI SN65DSI86 chip (Sandeep) - rockchip: Add PX30 support (Sandy) - sun4i: Add support for R40 TCON (Jernej) - vkms: Continued building out vkms, added gem support (Haneen)Driver Changes: - various: fbdev: Wrap remove_conflicting_framebuffers with resource_len accessors to remove a bunch of cargo-cult (Michał) - rockchip: Add rgb output iface support + fixes (Sandy/Heiko) - nouveau/amdgpu: Add cec-over-aux support (Hans) - sun4i: Add support for Allwinner A64 (Jagan) Cc: Gerd Hoffmann <kraxel@redhat.com> Cc: Chris Wilson <chris@chris-wilson.co.uk> Cc: Michał Mirosław <mirq-linux@rere.qmqm.pl> Cc: Heiko Stuebner <heiko@sntech.de> Cc: Sandy Huang <hjc@rock-chips.com> Cc: Hans Verkuil <hans.verkuil@cisco.com> Cc: Jagan Teki <jagan@amarulasolutions.com> Signed-off-by: Dave Airlie <airlied@redhat.com> From: Sean Paul <sean@poorly.run> Link: https://patchwork.freedesktop.org/patch/msgid/20180905202210.GA95199@art_vandelay
This commit is contained in:
commit
f5169a17af
@ -15,6 +15,13 @@ Required children nodes:
|
||||
to external devices using the OF graph reprensentation (see ../graph.txt).
|
||||
At least one port node is required.
|
||||
|
||||
Optional properties in grandchild nodes:
|
||||
Any endpoint grandchild node may specify a desired video interface
|
||||
according to ../../media/video-interfaces.txt, specifically
|
||||
- bus-width: recognized values are <12>, <16>, <18> and <24>, and
|
||||
override any output mode selection heuristic, forcing "rgb444",
|
||||
"rgb565", "rgb666" and "rgb888" respectively.
|
||||
|
||||
Example:
|
||||
|
||||
hlcdc: hlcdc@f0030000 {
|
||||
@ -50,3 +57,19 @@ Example:
|
||||
#pwm-cells = <3>;
|
||||
};
|
||||
};
|
||||
|
||||
Example 2: With a video interface override to force rgb565; as above
|
||||
but with these changes/additions:
|
||||
|
||||
&hlcdc {
|
||||
hlcdc-display-controller {
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb565>;
|
||||
|
||||
port@0 {
|
||||
hlcdc_panel_output: endpoint@0 {
|
||||
bus-width = <16>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -22,7 +22,13 @@ among others.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Must be "lvds-encoder"
|
||||
- compatible: Must be one or more of the following
|
||||
- "ti,ds90c185" for the TI DS90C185 FPD-Link Serializer
|
||||
- "lvds-encoder" for a generic LVDS encoder device
|
||||
|
||||
When compatible with the generic version, nodes must list the
|
||||
device-specific version corresponding to the device first
|
||||
followed by the generic version.
|
||||
|
||||
Required nodes:
|
||||
|
||||
|
@ -0,0 +1,87 @@
|
||||
SN65DSI86 DSI to eDP bridge chip
|
||||
--------------------------------
|
||||
|
||||
This is the binding for Texas Instruments SN65DSI86 bridge.
|
||||
http://www.ti.com/general/docs/lit/getliterature.tsp?genericPartNumber=sn65dsi86&fileType=pdf
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "ti,sn65dsi86"
|
||||
- reg: i2c address of the chip, 0x2d as per datasheet
|
||||
- enable-gpios: gpio specification for bridge_en pin (active high)
|
||||
|
||||
- vccio-supply: A 1.8V supply that powers up the digital IOs.
|
||||
- vpll-supply: A 1.8V supply that powers up the displayport PLL.
|
||||
- vcca-supply: A 1.2V supply that powers up the analog circuits.
|
||||
- vcc-supply: A 1.2V supply that powers up the digital core.
|
||||
|
||||
Optional properties:
|
||||
- interrupts-extended: Specifier for the SN65DSI86 interrupt line.
|
||||
|
||||
- gpio-controller: Marks the device has a GPIO controller.
|
||||
- #gpio-cells : Should be two. The first cell is the pin number and
|
||||
the second cell is used to specify flags.
|
||||
See ../../gpio/gpio.txt for more information.
|
||||
- #pwm-cells : Should be one. See ../../pwm/pwm.txt for description of
|
||||
the cell formats.
|
||||
|
||||
- clock-names: should be "refclk"
|
||||
- clocks: Specification for input reference clock. The reference
|
||||
clock rate must be 12 MHz, 19.2 MHz, 26 MHz, 27 MHz or 38.4 MHz.
|
||||
|
||||
- data-lanes: See ../../media/video-interface.txt
|
||||
- lane-polarities: See ../../media/video-interface.txt
|
||||
|
||||
- suspend-gpios: specification for GPIO1 pin on bridge (active low)
|
||||
|
||||
Required nodes:
|
||||
This device has two video ports. Their connections are modelled using the
|
||||
OF graph bindings specified in Documentation/devicetree/bindings/graph.txt.
|
||||
|
||||
- Video port 0 for DSI input
|
||||
- Video port 1 for eDP output
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
edp-bridge@2d {
|
||||
compatible = "ti,sn65dsi86";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0x2d>;
|
||||
|
||||
enable-gpios = <&msmgpio 33 GPIO_ACTIVE_HIGH>;
|
||||
suspend-gpios = <&msmgpio 34 GPIO_ACTIVE_LOW>;
|
||||
|
||||
interrupts-extended = <&gpio3 4 IRQ_TYPE_EDGE_FALLING>;
|
||||
|
||||
vccio-supply = <&pm8916_l17>;
|
||||
vcca-supply = <&pm8916_l6>;
|
||||
vpll-supply = <&pm8916_l17>;
|
||||
vcc-supply = <&pm8916_l6>;
|
||||
|
||||
clock-names = "refclk";
|
||||
clocks = <&input_refclk>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
|
||||
edp_bridge_in: endpoint {
|
||||
remote-endpoint = <&dsi_out>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
|
||||
edp_bridge_out: endpoint {
|
||||
data-lanes = <2 1 3 0>;
|
||||
lane-polarities = <0 1 0 1>;
|
||||
remote-endpoint = <&edp_panel_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
TC358764 MIPI-DSI to LVDS panel bridge
|
||||
|
||||
Required properties:
|
||||
- compatible: "toshiba,tc358764"
|
||||
- reg: the virtual channel number of a DSI peripheral
|
||||
- vddc-supply: core voltage supply, 1.2V
|
||||
- vddio-supply: I/O voltage supply, 1.8V or 3.3V
|
||||
- vddlvds-supply: LVDS1/2 voltage supply, 3.3V
|
||||
- reset-gpios: a GPIO spec for the reset pin
|
||||
|
||||
The device node can contain following 'port' child nodes,
|
||||
according to the OF graph bindings defined in [1]:
|
||||
0: DSI Input, not required, if the bridge is DSI controlled
|
||||
1: LVDS Output, mandatory
|
||||
|
||||
[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
Example:
|
||||
|
||||
bridge@0 {
|
||||
reg = <0>;
|
||||
compatible = "toshiba,tc358764";
|
||||
vddc-supply = <&vcc_1v2_reg>;
|
||||
vddio-supply = <&vcc_1v8_reg>;
|
||||
vddlvds-supply = <&vcc_3v3_reg>;
|
||||
reset-gpios = <&gpd1 6 GPIO_ACTIVE_LOW>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds_ep: endpoint {
|
||||
remote-endpoint = <&panel_ep>;
|
||||
};
|
||||
};
|
||||
};
|
@ -16,7 +16,7 @@ The following assumes that only a single peripheral is connected to a DSI
|
||||
host. Experience shows that this is true for the large majority of setups.
|
||||
|
||||
DSI host
|
||||
--------
|
||||
========
|
||||
|
||||
In addition to the standard properties and those defined by the parent bus of
|
||||
a DSI host, the following properties apply to a node representing a DSI host.
|
||||
@ -29,12 +29,24 @@ Required properties:
|
||||
- #size-cells: Should be 0. There are cases where it makes sense to use a
|
||||
different value here. See below.
|
||||
|
||||
DSI peripheral
|
||||
--------------
|
||||
Optional properties:
|
||||
- clock-master: boolean. Should be enabled if the host is being used in
|
||||
conjunction with another DSI host to drive the same peripheral. Hardware
|
||||
supporting such a configuration generally requires the data on both the busses
|
||||
to be driven by the same clock. Only the DSI host instance controlling this
|
||||
clock should contain this property.
|
||||
|
||||
Peripherals are represented as child nodes of the DSI host's node. Properties
|
||||
described here apply to all DSI peripherals, but individual bindings may want
|
||||
to define additional, device-specific properties.
|
||||
DSI peripheral
|
||||
==============
|
||||
|
||||
Peripherals with DSI as control bus, or no control bus
|
||||
------------------------------------------------------
|
||||
|
||||
Peripherals with the DSI bus as the primary control bus, or peripherals with
|
||||
no control bus but use the DSI bus to transmit pixel data are represented
|
||||
as child nodes of the DSI host's node. Properties described here apply to all
|
||||
DSI peripherals, but individual bindings may want to define additional,
|
||||
device-specific properties.
|
||||
|
||||
Required properties:
|
||||
- reg: The virtual channel number of a DSI peripheral. Must be in the range
|
||||
@ -49,9 +61,37 @@ case two alternative representations can be chosen:
|
||||
property is the number of the first virtual channel and the second cell is
|
||||
the number of consecutive virtual channels.
|
||||
|
||||
Example
|
||||
-------
|
||||
Peripherals with a different control bus
|
||||
----------------------------------------
|
||||
|
||||
There are peripherals that have I2C/SPI (or some other non-DSI bus) as the
|
||||
primary control bus, but are also connected to a DSI bus (mostly for the data
|
||||
path). Connections between such peripherals and a DSI host can be represented
|
||||
using the graph bindings [1], [2].
|
||||
|
||||
Peripherals that support dual channel DSI
|
||||
-----------------------------------------
|
||||
|
||||
Peripherals with higher bandwidth requirements can be connected to 2 DSI
|
||||
busses. Each DSI bus/channel drives some portion of the pixel data (generally
|
||||
left/right half of each line of the display, or even/odd lines of the display).
|
||||
The graph bindings should be used to represent the multiple DSI busses that are
|
||||
connected to this peripheral. Each DSI host's output endpoint can be linked to
|
||||
an input endpoint of the DSI peripheral.
|
||||
|
||||
[1] Documentation/devicetree/bindings/graph.txt
|
||||
[2] Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
Examples
|
||||
========
|
||||
- (1), (2) and (3) are examples of a DSI host and peripheral on the DSI bus
|
||||
with different virtual channel configurations.
|
||||
- (4) is an example of a peripheral on a I2C control bus connected to a
|
||||
DSI host using of-graph bindings.
|
||||
- (5) is an example of 2 DSI hosts driving a dual-channel DSI peripheral,
|
||||
which uses I2C as its primary control bus.
|
||||
|
||||
1)
|
||||
dsi-host {
|
||||
...
|
||||
|
||||
@ -67,6 +107,7 @@ Example
|
||||
...
|
||||
};
|
||||
|
||||
2)
|
||||
dsi-host {
|
||||
...
|
||||
|
||||
@ -82,6 +123,7 @@ Example
|
||||
...
|
||||
};
|
||||
|
||||
3)
|
||||
dsi-host {
|
||||
...
|
||||
|
||||
@ -96,3 +138,98 @@ Example
|
||||
|
||||
...
|
||||
};
|
||||
|
||||
4)
|
||||
i2c-host {
|
||||
...
|
||||
|
||||
dsi-bridge@35 {
|
||||
compatible = "...";
|
||||
reg = <0x35>;
|
||||
|
||||
ports {
|
||||
...
|
||||
|
||||
port {
|
||||
bridge_mipi_in: endpoint {
|
||||
remote-endpoint = <&host_mipi_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dsi-host {
|
||||
...
|
||||
|
||||
ports {
|
||||
...
|
||||
|
||||
port {
|
||||
host_mipi_out: endpoint {
|
||||
remote-endpoint = <&bridge_mipi_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
5)
|
||||
i2c-host {
|
||||
dsi-bridge@35 {
|
||||
compatible = "...";
|
||||
reg = <0x35>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
dsi0_in: endpoint {
|
||||
remote-endpoint = <&dsi0_out>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
dsi1_in: endpoint {
|
||||
remote-endpoint = <&dsi1_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dsi0-host {
|
||||
...
|
||||
|
||||
/*
|
||||
* this DSI instance drives the clock for both the host
|
||||
* controllers
|
||||
*/
|
||||
clock-master;
|
||||
|
||||
ports {
|
||||
...
|
||||
|
||||
port {
|
||||
dsi0_out: endpoint {
|
||||
remote-endpoint = <&dsi0_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dsi1-host {
|
||||
...
|
||||
|
||||
ports {
|
||||
...
|
||||
|
||||
port {
|
||||
dsi1_out: endpoint {
|
||||
remote-endpoint = <&dsi1_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -8,6 +8,9 @@ Required properties:
|
||||
- compatible: value should be one of the following
|
||||
"rockchip,rk3036-vop";
|
||||
"rockchip,rk3126-vop";
|
||||
"rockchip,px30-vop-lit";
|
||||
"rockchip,px30-vop-big";
|
||||
"rockchip,rk3188-vop";
|
||||
"rockchip,rk3288-vop";
|
||||
"rockchip,rk3368-vop";
|
||||
"rockchip,rk3366-vop";
|
||||
|
@ -78,6 +78,7 @@ Required properties:
|
||||
|
||||
- compatible: value must be one of:
|
||||
* "allwinner,sun8i-a83t-dw-hdmi"
|
||||
* "allwinner,sun50i-a64-dw-hdmi", "allwinner,sun8i-a83t-dw-hdmi"
|
||||
- reg: base address and size of memory-mapped region
|
||||
- reg-io-width: See dw_hdmi.txt. Shall be 1.
|
||||
- interrupts: HDMI interrupt number
|
||||
@ -96,6 +97,9 @@ Required properties:
|
||||
first port should be the input endpoint. The second should be the
|
||||
output, usually to an HDMI connector.
|
||||
|
||||
Optional properties:
|
||||
- hvcc-supply: the VCC power supply of the controller
|
||||
|
||||
DWC HDMI PHY
|
||||
------------
|
||||
|
||||
@ -151,6 +155,8 @@ Required properties:
|
||||
* allwinner,sun8i-v3s-tcon
|
||||
* allwinner,sun9i-a80-tcon-lcd
|
||||
* allwinner,sun9i-a80-tcon-tv
|
||||
* "allwinner,sun50i-a64-tcon-lcd", "allwinner,sun8i-a83t-tcon-lcd"
|
||||
* "allwinner,sun50i-a64-tcon-tv", "allwinner,sun8i-a83t-tcon-tv"
|
||||
- reg: base address and size of memory-mapped region
|
||||
- interrupts: interrupt associated to this IP
|
||||
- clocks: phandles to the clocks feeding the TCON.
|
||||
@ -370,6 +376,8 @@ Required properties:
|
||||
* allwinner,sun8i-a83t-de2-mixer-1
|
||||
* allwinner,sun8i-h3-de2-mixer-0
|
||||
* allwinner,sun8i-v3s-de2-mixer
|
||||
* allwinner,sun50i-a64-de2-mixer-0
|
||||
* allwinner,sun50i-a64-de2-mixer-1
|
||||
- reg: base address and size of the memory-mapped region.
|
||||
- clocks: phandles to the clocks feeding the mixer
|
||||
* bus: the mixer interface clock
|
||||
@ -403,6 +411,7 @@ Required properties:
|
||||
* allwinner,sun8i-r40-display-engine
|
||||
* allwinner,sun8i-v3s-display-engine
|
||||
* allwinner,sun9i-a80-display-engine
|
||||
* allwinner,sun50i-a64-display-engine
|
||||
|
||||
- allwinner,pipelines: list of phandle to the display engine
|
||||
frontends (DE 1.0) or mixers (DE 2.0) available.
|
||||
|
@ -323,6 +323,12 @@ Frame Buffer Functions Reference
|
||||
DRM Format Handling
|
||||
===================
|
||||
|
||||
.. kernel-doc:: include/uapi/drm/drm_fourcc.h
|
||||
:doc: overview
|
||||
|
||||
Format Functions Reference
|
||||
--------------------------
|
||||
|
||||
.. kernel-doc:: include/drm/drm_fourcc.h
|
||||
:internal:
|
||||
|
||||
|
@ -297,7 +297,7 @@ made up of several fields, the more interesting ones being:
|
||||
struct vm_operations_struct {
|
||||
void (*open)(struct vm_area_struct * area);
|
||||
void (*close)(struct vm_area_struct * area);
|
||||
int (*fault)(struct vm_fault *vmf);
|
||||
vm_fault_t (*fault)(struct vm_fault *vmf);
|
||||
};
|
||||
|
||||
|
||||
|
@ -272,6 +272,7 @@ Code Seq#(hex) Include File Comments
|
||||
't' 90-91 linux/toshiba.h toshiba and toshiba_acpi SMM
|
||||
'u' 00-1F linux/smb_fs.h gone
|
||||
'u' 20-3F linux/uvcvideo.h USB video class host driver
|
||||
'u' 40-4f linux/udmabuf.h userspace dma-buf misc device
|
||||
'v' 00-1F linux/ext2_fs.h conflict!
|
||||
'v' 00-1F linux/fs.h conflict!
|
||||
'v' 00-0F linux/sonypi.h conflict!
|
||||
|
@ -15343,6 +15343,14 @@ F: arch/x86/um/
|
||||
F: fs/hostfs/
|
||||
F: fs/hppfs/
|
||||
|
||||
USERSPACE DMA BUFFER DRIVER
|
||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
||||
S: Maintained
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
F: drivers/dma-buf/udmabuf.c
|
||||
F: include/uapi/linux/udmabuf.h
|
||||
T: git git://anongit.freedesktop.org/drm/drm-misc
|
||||
|
||||
USERSPACE I/O (UIO)
|
||||
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
S: Maintained
|
||||
|
@ -30,4 +30,12 @@ config SW_SYNC
|
||||
WARNING: improper use of this can result in deadlocking kernel
|
||||
drivers from userspace. Intended for test and debug only.
|
||||
|
||||
config UDMABUF
|
||||
bool "userspace dmabuf misc driver"
|
||||
default n
|
||||
depends on DMA_SHARED_BUFFER
|
||||
help
|
||||
A driver to let userspace turn memfd regions into dma-bufs.
|
||||
Qemu can use this to create host dmabufs for guest framebuffers.
|
||||
|
||||
endmenu
|
||||
|
@ -1,3 +1,4 @@
|
||||
obj-y := dma-buf.o dma-fence.o dma-fence-array.o reservation.o seqno-fence.o
|
||||
obj-$(CONFIG_SYNC_FILE) += sync_file.o
|
||||
obj-$(CONFIG_SW_SYNC) += sw_sync.o sync_debug.o
|
||||
obj-$(CONFIG_UDMABUF) += udmabuf.o
|
||||
|
@ -405,7 +405,6 @@ struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
|
||||
|| !exp_info->ops->map_dma_buf
|
||||
|| !exp_info->ops->unmap_dma_buf
|
||||
|| !exp_info->ops->release
|
||||
|| !exp_info->ops->map
|
||||
|| !exp_info->ops->mmap)) {
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
288
drivers/dma-buf/udmabuf.c
Normal file
288
drivers/dma-buf/udmabuf.c
Normal file
@ -0,0 +1,288 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/shmem_fs.h>
|
||||
#include <linux/memfd.h>
|
||||
|
||||
#include <uapi/linux/udmabuf.h>
|
||||
|
||||
struct udmabuf {
|
||||
u32 pagecount;
|
||||
struct page **pages;
|
||||
};
|
||||
|
||||
static int udmabuf_vm_fault(struct vm_fault *vmf)
|
||||
{
|
||||
struct vm_area_struct *vma = vmf->vma;
|
||||
struct udmabuf *ubuf = vma->vm_private_data;
|
||||
|
||||
if (WARN_ON(vmf->pgoff >= ubuf->pagecount))
|
||||
return VM_FAULT_SIGBUS;
|
||||
|
||||
vmf->page = ubuf->pages[vmf->pgoff];
|
||||
get_page(vmf->page);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct vm_operations_struct udmabuf_vm_ops = {
|
||||
.fault = udmabuf_vm_fault,
|
||||
};
|
||||
|
||||
static int mmap_udmabuf(struct dma_buf *buf, struct vm_area_struct *vma)
|
||||
{
|
||||
struct udmabuf *ubuf = buf->priv;
|
||||
|
||||
if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0)
|
||||
return -EINVAL;
|
||||
|
||||
vma->vm_ops = &udmabuf_vm_ops;
|
||||
vma->vm_private_data = ubuf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sg_table *map_udmabuf(struct dma_buf_attachment *at,
|
||||
enum dma_data_direction direction)
|
||||
{
|
||||
struct udmabuf *ubuf = at->dmabuf->priv;
|
||||
struct sg_table *sg;
|
||||
|
||||
sg = kzalloc(sizeof(*sg), GFP_KERNEL);
|
||||
if (!sg)
|
||||
goto err1;
|
||||
if (sg_alloc_table_from_pages(sg, ubuf->pages, ubuf->pagecount,
|
||||
0, ubuf->pagecount << PAGE_SHIFT,
|
||||
GFP_KERNEL) < 0)
|
||||
goto err2;
|
||||
if (!dma_map_sg(at->dev, sg->sgl, sg->nents, direction))
|
||||
goto err3;
|
||||
|
||||
return sg;
|
||||
|
||||
err3:
|
||||
sg_free_table(sg);
|
||||
err2:
|
||||
kfree(sg);
|
||||
err1:
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
static void unmap_udmabuf(struct dma_buf_attachment *at,
|
||||
struct sg_table *sg,
|
||||
enum dma_data_direction direction)
|
||||
{
|
||||
sg_free_table(sg);
|
||||
kfree(sg);
|
||||
}
|
||||
|
||||
static void release_udmabuf(struct dma_buf *buf)
|
||||
{
|
||||
struct udmabuf *ubuf = buf->priv;
|
||||
pgoff_t pg;
|
||||
|
||||
for (pg = 0; pg < ubuf->pagecount; pg++)
|
||||
put_page(ubuf->pages[pg]);
|
||||
kfree(ubuf->pages);
|
||||
kfree(ubuf);
|
||||
}
|
||||
|
||||
static void *kmap_udmabuf(struct dma_buf *buf, unsigned long page_num)
|
||||
{
|
||||
struct udmabuf *ubuf = buf->priv;
|
||||
struct page *page = ubuf->pages[page_num];
|
||||
|
||||
return kmap(page);
|
||||
}
|
||||
|
||||
static void kunmap_udmabuf(struct dma_buf *buf, unsigned long page_num,
|
||||
void *vaddr)
|
||||
{
|
||||
kunmap(vaddr);
|
||||
}
|
||||
|
||||
static struct dma_buf_ops udmabuf_ops = {
|
||||
.map_dma_buf = map_udmabuf,
|
||||
.unmap_dma_buf = unmap_udmabuf,
|
||||
.release = release_udmabuf,
|
||||
.map = kmap_udmabuf,
|
||||
.unmap = kunmap_udmabuf,
|
||||
.mmap = mmap_udmabuf,
|
||||
};
|
||||
|
||||
#define SEALS_WANTED (F_SEAL_SHRINK)
|
||||
#define SEALS_DENIED (F_SEAL_WRITE)
|
||||
|
||||
static long udmabuf_create(struct udmabuf_create_list *head,
|
||||
struct udmabuf_create_item *list)
|
||||
{
|
||||
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
|
||||
struct file *memfd = NULL;
|
||||
struct udmabuf *ubuf;
|
||||
struct dma_buf *buf;
|
||||
pgoff_t pgoff, pgcnt, pgidx, pgbuf;
|
||||
struct page *page;
|
||||
int seals, ret = -EINVAL;
|
||||
u32 i, flags;
|
||||
|
||||
ubuf = kzalloc(sizeof(struct udmabuf), GFP_KERNEL);
|
||||
if (!ubuf)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < head->count; i++) {
|
||||
if (!IS_ALIGNED(list[i].offset, PAGE_SIZE))
|
||||
goto err_free_ubuf;
|
||||
if (!IS_ALIGNED(list[i].size, PAGE_SIZE))
|
||||
goto err_free_ubuf;
|
||||
ubuf->pagecount += list[i].size >> PAGE_SHIFT;
|
||||
}
|
||||
ubuf->pages = kmalloc_array(ubuf->pagecount, sizeof(struct page *),
|
||||
GFP_KERNEL);
|
||||
if (!ubuf->pages) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_ubuf;
|
||||
}
|
||||
|
||||
pgbuf = 0;
|
||||
for (i = 0; i < head->count; i++) {
|
||||
memfd = fget(list[i].memfd);
|
||||
if (!memfd)
|
||||
goto err_put_pages;
|
||||
if (!shmem_mapping(file_inode(memfd)->i_mapping))
|
||||
goto err_put_pages;
|
||||
seals = memfd_fcntl(memfd, F_GET_SEALS, 0);
|
||||
if (seals == -EINVAL ||
|
||||
(seals & SEALS_WANTED) != SEALS_WANTED ||
|
||||
(seals & SEALS_DENIED) != 0)
|
||||
goto err_put_pages;
|
||||
pgoff = list[i].offset >> PAGE_SHIFT;
|
||||
pgcnt = list[i].size >> PAGE_SHIFT;
|
||||
for (pgidx = 0; pgidx < pgcnt; pgidx++) {
|
||||
page = shmem_read_mapping_page(
|
||||
file_inode(memfd)->i_mapping, pgoff + pgidx);
|
||||
if (IS_ERR(page)) {
|
||||
ret = PTR_ERR(page);
|
||||
goto err_put_pages;
|
||||
}
|
||||
ubuf->pages[pgbuf++] = page;
|
||||
}
|
||||
fput(memfd);
|
||||
}
|
||||
memfd = NULL;
|
||||
|
||||
exp_info.ops = &udmabuf_ops;
|
||||
exp_info.size = ubuf->pagecount << PAGE_SHIFT;
|
||||
exp_info.priv = ubuf;
|
||||
|
||||
buf = dma_buf_export(&exp_info);
|
||||
if (IS_ERR(buf)) {
|
||||
ret = PTR_ERR(buf);
|
||||
goto err_put_pages;
|
||||
}
|
||||
|
||||
flags = 0;
|
||||
if (head->flags & UDMABUF_FLAGS_CLOEXEC)
|
||||
flags |= O_CLOEXEC;
|
||||
return dma_buf_fd(buf, flags);
|
||||
|
||||
err_put_pages:
|
||||
while (pgbuf > 0)
|
||||
put_page(ubuf->pages[--pgbuf]);
|
||||
err_free_ubuf:
|
||||
if (memfd)
|
||||
fput(memfd);
|
||||
kfree(ubuf->pages);
|
||||
kfree(ubuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long udmabuf_ioctl_create(struct file *filp, unsigned long arg)
|
||||
{
|
||||
struct udmabuf_create create;
|
||||
struct udmabuf_create_list head;
|
||||
struct udmabuf_create_item list;
|
||||
|
||||
if (copy_from_user(&create, (void __user *)arg,
|
||||
sizeof(struct udmabuf_create)))
|
||||
return -EFAULT;
|
||||
|
||||
head.flags = create.flags;
|
||||
head.count = 1;
|
||||
list.memfd = create.memfd;
|
||||
list.offset = create.offset;
|
||||
list.size = create.size;
|
||||
|
||||
return udmabuf_create(&head, &list);
|
||||
}
|
||||
|
||||
static long udmabuf_ioctl_create_list(struct file *filp, unsigned long arg)
|
||||
{
|
||||
struct udmabuf_create_list head;
|
||||
struct udmabuf_create_item *list;
|
||||
int ret = -EINVAL;
|
||||
u32 lsize;
|
||||
|
||||
if (copy_from_user(&head, (void __user *)arg, sizeof(head)))
|
||||
return -EFAULT;
|
||||
if (head.count > 1024)
|
||||
return -EINVAL;
|
||||
lsize = sizeof(struct udmabuf_create_item) * head.count;
|
||||
list = memdup_user((void __user *)(arg + sizeof(head)), lsize);
|
||||
if (IS_ERR(list))
|
||||
return PTR_ERR(list);
|
||||
|
||||
ret = udmabuf_create(&head, list);
|
||||
kfree(list);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long udmabuf_ioctl(struct file *filp, unsigned int ioctl,
|
||||
unsigned long arg)
|
||||
{
|
||||
long ret;
|
||||
|
||||
switch (ioctl) {
|
||||
case UDMABUF_CREATE:
|
||||
ret = udmabuf_ioctl_create(filp, arg);
|
||||
break;
|
||||
case UDMABUF_CREATE_LIST:
|
||||
ret = udmabuf_ioctl_create_list(filp, arg);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations udmabuf_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = udmabuf_ioctl,
|
||||
};
|
||||
|
||||
static struct miscdevice udmabuf_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "udmabuf",
|
||||
.fops = &udmabuf_fops,
|
||||
};
|
||||
|
||||
static int __init udmabuf_dev_init(void)
|
||||
{
|
||||
return misc_register(&udmabuf_misc);
|
||||
}
|
||||
|
||||
static void __exit udmabuf_dev_exit(void)
|
||||
{
|
||||
misc_deregister(&udmabuf_misc);
|
||||
}
|
||||
|
||||
module_init(udmabuf_dev_init)
|
||||
module_exit(udmabuf_dev_exit)
|
||||
|
||||
MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -785,28 +785,6 @@ MODULE_DEVICE_TABLE(pci, pciidlist);
|
||||
|
||||
static struct drm_driver kms_driver;
|
||||
|
||||
static int amdgpu_kick_out_firmware_fb(struct pci_dev *pdev)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
bool primary = false;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return -ENOMEM;
|
||||
|
||||
ap->ranges[0].base = pci_resource_start(pdev, 0);
|
||||
ap->ranges[0].size = pci_resource_len(pdev, 0);
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
|
||||
#endif
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "amdgpudrmfb", primary);
|
||||
kfree(ap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int amdgpu_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
@ -834,7 +812,7 @@ static int amdgpu_pci_probe(struct pci_dev *pdev,
|
||||
return ret;
|
||||
|
||||
/* Get rid of things like offb */
|
||||
ret = amdgpu_kick_out_firmware_fb(pdev);
|
||||
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, 0, "amdgpudrmfb");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -896,6 +896,7 @@ amdgpu_dm_update_connector_after_detect(struct amdgpu_dm_connector *aconnector)
|
||||
aconnector->dc_sink = sink;
|
||||
if (sink->dc_edid.length == 0) {
|
||||
aconnector->edid = NULL;
|
||||
drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
|
||||
} else {
|
||||
aconnector->edid =
|
||||
(struct edid *) sink->dc_edid.raw_edid;
|
||||
@ -903,10 +904,13 @@ amdgpu_dm_update_connector_after_detect(struct amdgpu_dm_connector *aconnector)
|
||||
|
||||
drm_connector_update_edid_property(connector,
|
||||
aconnector->edid);
|
||||
drm_dp_cec_set_edid(&aconnector->dm_dp_aux.aux,
|
||||
aconnector->edid);
|
||||
}
|
||||
amdgpu_dm_add_sink_to_freesync_module(connector, aconnector->edid);
|
||||
|
||||
} else {
|
||||
drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux);
|
||||
amdgpu_dm_remove_sink_from_freesync_module(connector);
|
||||
drm_connector_update_edid_property(connector, NULL);
|
||||
aconnector->num_modes = 0;
|
||||
@ -1061,8 +1065,10 @@ static void handle_hpd_rx_irq(void *param)
|
||||
(dc_link->type == dc_connection_mst_branch))
|
||||
dm_handle_hpd_rx_irq(aconnector);
|
||||
|
||||
if (dc_link->type != dc_connection_mst_branch)
|
||||
if (dc_link->type != dc_connection_mst_branch) {
|
||||
drm_dp_cec_irq(&aconnector->dm_dp_aux.aux);
|
||||
mutex_unlock(&aconnector->hpd_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void register_hpd_handlers(struct amdgpu_device *adev)
|
||||
@ -2595,6 +2601,7 @@ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = {
|
||||
.atomic_duplicate_state = dm_crtc_duplicate_state,
|
||||
.atomic_destroy_state = dm_crtc_destroy_state,
|
||||
.set_crc_source = amdgpu_dm_crtc_set_crc_source,
|
||||
.verify_crc_source = amdgpu_dm_crtc_verify_crc_source,
|
||||
.enable_vblank = dm_enable_vblank,
|
||||
.disable_vblank = dm_disable_vblank,
|
||||
};
|
||||
@ -2730,6 +2737,7 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector)
|
||||
dm->backlight_dev = NULL;
|
||||
}
|
||||
#endif
|
||||
drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux);
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
kfree(connector);
|
||||
|
@ -258,11 +258,14 @@ amdgpu_dm_remove_sink_from_freesync_module(struct drm_connector *connector);
|
||||
|
||||
/* amdgpu_dm_crc.c */
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name,
|
||||
size_t *values_cnt);
|
||||
int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name);
|
||||
int amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc,
|
||||
const char *src_name,
|
||||
size_t *values_cnt);
|
||||
void amdgpu_dm_crtc_handle_crc_irq(struct drm_crtc *crtc);
|
||||
#else
|
||||
#define amdgpu_dm_crtc_set_crc_source NULL
|
||||
#define amdgpu_dm_crtc_verify_crc_source NULL
|
||||
#define amdgpu_dm_crtc_handle_crc_irq(x)
|
||||
#endif
|
||||
|
||||
|
@ -46,8 +46,23 @@ static enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source)
|
||||
return AMDGPU_DM_PIPE_CRC_SOURCE_INVALID;
|
||||
}
|
||||
|
||||
int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name,
|
||||
size_t *values_cnt)
|
||||
int
|
||||
amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc, const char *src_name,
|
||||
size_t *values_cnt)
|
||||
{
|
||||
enum amdgpu_dm_pipe_crc_source source = dm_parse_crc_source(src_name);
|
||||
|
||||
if (source < 0) {
|
||||
DRM_DEBUG_DRIVER("Unknown CRC source %s for CRTC%d\n",
|
||||
src_name, crtc->index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*values_cnt = 3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name)
|
||||
{
|
||||
struct dm_crtc_state *crtc_state = to_dm_crtc_state(crtc->state);
|
||||
struct dc_stream_state *stream_state = crtc_state->stream;
|
||||
@ -83,7 +98,6 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*values_cnt = 3;
|
||||
/* Reset crc_skipped on dm state */
|
||||
crtc_state->crc_skip_count = 0;
|
||||
return 0;
|
||||
|
@ -496,6 +496,8 @@ void amdgpu_dm_initialize_dp_connector(struct amdgpu_display_manager *dm,
|
||||
aconnector->dm_dp_aux.ddc_service = aconnector->dc_link->ddc;
|
||||
|
||||
drm_dp_aux_register(&aconnector->dm_dp_aux.aux);
|
||||
drm_dp_cec_register_connector(&aconnector->dm_dp_aux.aux,
|
||||
aconnector->base.name, dm->adev->dev);
|
||||
aconnector->mst_mgr.cbs = &dm_mst_cbs;
|
||||
drm_dp_mst_topology_mgr_init(
|
||||
&aconnector->mst_mgr,
|
||||
|
@ -78,11 +78,8 @@ static void malidp_plane_reset(struct drm_plane *plane)
|
||||
kfree(state);
|
||||
plane->state = NULL;
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (state) {
|
||||
state->base.plane = plane;
|
||||
state->base.rotation = DRM_MODE_ROTATE_0;
|
||||
plane->state = &state->base;
|
||||
}
|
||||
if (state)
|
||||
__drm_atomic_helper_plane_reset(plane, &state->base);
|
||||
}
|
||||
|
||||
static struct
|
||||
|
@ -101,18 +101,34 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c)
|
||||
(adj->crtc_hdisplay - 1) |
|
||||
((adj->crtc_vdisplay - 1) << 16));
|
||||
|
||||
cfg = 0;
|
||||
cfg = ATMEL_HLCDC_CLKSEL;
|
||||
|
||||
prate = clk_get_rate(crtc->dc->hlcdc->sys_clk);
|
||||
prate = 2 * clk_get_rate(crtc->dc->hlcdc->sys_clk);
|
||||
mode_rate = adj->crtc_clock * 1000;
|
||||
if ((prate / 2) < mode_rate) {
|
||||
prate *= 2;
|
||||
cfg |= ATMEL_HLCDC_CLKSEL;
|
||||
}
|
||||
|
||||
div = DIV_ROUND_UP(prate, mode_rate);
|
||||
if (div < 2)
|
||||
if (div < 2) {
|
||||
div = 2;
|
||||
} else if (ATMEL_HLCDC_CLKDIV(div) & ~ATMEL_HLCDC_CLKDIV_MASK) {
|
||||
/* The divider ended up too big, try a lower base rate. */
|
||||
cfg &= ~ATMEL_HLCDC_CLKSEL;
|
||||
prate /= 2;
|
||||
div = DIV_ROUND_UP(prate, mode_rate);
|
||||
if (ATMEL_HLCDC_CLKDIV(div) & ~ATMEL_HLCDC_CLKDIV_MASK)
|
||||
div = ATMEL_HLCDC_CLKDIV_MASK;
|
||||
} else {
|
||||
int div_low = prate / mode_rate;
|
||||
|
||||
if (div_low >= 2 &&
|
||||
((prate / div_low - mode_rate) <
|
||||
10 * (mode_rate - prate / div)))
|
||||
/*
|
||||
* At least 10 times better when using a higher
|
||||
* frequency than requested, instead of a lower.
|
||||
* So, go with that.
|
||||
*/
|
||||
div = div_low;
|
||||
}
|
||||
|
||||
cfg |= ATMEL_HLCDC_CLKDIV(div);
|
||||
|
||||
@ -226,6 +242,55 @@ static void atmel_hlcdc_crtc_atomic_enable(struct drm_crtc *c,
|
||||
#define ATMEL_HLCDC_RGB888_OUTPUT BIT(3)
|
||||
#define ATMEL_HLCDC_OUTPUT_MODE_MASK GENMASK(3, 0)
|
||||
|
||||
static int atmel_hlcdc_connector_output_mode(struct drm_connector_state *state)
|
||||
{
|
||||
struct drm_connector *connector = state->connector;
|
||||
struct drm_display_info *info = &connector->display_info;
|
||||
struct drm_encoder *encoder;
|
||||
unsigned int supported_fmts = 0;
|
||||
int j;
|
||||
|
||||
encoder = state->best_encoder;
|
||||
if (!encoder)
|
||||
encoder = connector->encoder;
|
||||
|
||||
switch (atmel_hlcdc_encoder_get_bus_fmt(encoder)) {
|
||||
case 0:
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB444_1X12:
|
||||
return ATMEL_HLCDC_RGB444_OUTPUT;
|
||||
case MEDIA_BUS_FMT_RGB565_1X16:
|
||||
return ATMEL_HLCDC_RGB565_OUTPUT;
|
||||
case MEDIA_BUS_FMT_RGB666_1X18:
|
||||
return ATMEL_HLCDC_RGB666_OUTPUT;
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
return ATMEL_HLCDC_RGB888_OUTPUT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (j = 0; j < info->num_bus_formats; j++) {
|
||||
switch (info->bus_formats[j]) {
|
||||
case MEDIA_BUS_FMT_RGB444_1X12:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB444_OUTPUT;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB565_1X16:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB565_OUTPUT;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB666_1X18:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB666_OUTPUT;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB888_OUTPUT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return supported_fmts;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_crtc_select_output_mode(struct drm_crtc_state *state)
|
||||
{
|
||||
unsigned int output_fmts = ATMEL_HLCDC_OUTPUT_MODE_MASK;
|
||||
@ -238,31 +303,12 @@ static int atmel_hlcdc_crtc_select_output_mode(struct drm_crtc_state *state)
|
||||
crtc = drm_crtc_to_atmel_hlcdc_crtc(state->crtc);
|
||||
|
||||
for_each_new_connector_in_state(state->state, connector, cstate, i) {
|
||||
struct drm_display_info *info = &connector->display_info;
|
||||
unsigned int supported_fmts = 0;
|
||||
int j;
|
||||
|
||||
if (!cstate->crtc)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < info->num_bus_formats; j++) {
|
||||
switch (info->bus_formats[j]) {
|
||||
case MEDIA_BUS_FMT_RGB444_1X12:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB444_OUTPUT;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB565_1X16:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB565_OUTPUT;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB666_1X18:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB666_OUTPUT;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
supported_fmts |= ATMEL_HLCDC_RGB888_OUTPUT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
supported_fmts = atmel_hlcdc_connector_output_mode(cstate);
|
||||
|
||||
if (crtc->dc->desc->conflicting_output_formats)
|
||||
output_fmts &= supported_fmts;
|
||||
|
@ -441,5 +441,6 @@ void atmel_hlcdc_crtc_irq(struct drm_crtc *c);
|
||||
int atmel_hlcdc_crtc_create(struct drm_device *dev);
|
||||
|
||||
int atmel_hlcdc_create_outputs(struct drm_device *dev);
|
||||
int atmel_hlcdc_encoder_get_bus_fmt(struct drm_encoder *encoder);
|
||||
|
||||
#endif /* DRM_ATMEL_HLCDC_H */
|
||||
|
@ -27,33 +27,94 @@
|
||||
|
||||
#include "atmel_hlcdc_dc.h"
|
||||
|
||||
struct atmel_hlcdc_rgb_output {
|
||||
struct drm_encoder encoder;
|
||||
int bus_fmt;
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs atmel_hlcdc_panel_encoder_funcs = {
|
||||
.destroy = drm_encoder_cleanup,
|
||||
};
|
||||
|
||||
static struct atmel_hlcdc_rgb_output *
|
||||
atmel_hlcdc_encoder_to_rgb_output(struct drm_encoder *encoder)
|
||||
{
|
||||
return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder);
|
||||
}
|
||||
|
||||
int atmel_hlcdc_encoder_get_bus_fmt(struct drm_encoder *encoder)
|
||||
{
|
||||
struct atmel_hlcdc_rgb_output *output;
|
||||
|
||||
output = atmel_hlcdc_encoder_to_rgb_output(encoder);
|
||||
|
||||
return output->bus_fmt;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_of_bus_fmt(const struct device_node *ep)
|
||||
{
|
||||
u32 bus_width;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(ep, "bus-width", &bus_width);
|
||||
if (ret == -EINVAL)
|
||||
return 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (bus_width) {
|
||||
case 12:
|
||||
return MEDIA_BUS_FMT_RGB444_1X12;
|
||||
case 16:
|
||||
return MEDIA_BUS_FMT_RGB565_1X16;
|
||||
case 18:
|
||||
return MEDIA_BUS_FMT_RGB666_1X18;
|
||||
case 24:
|
||||
return MEDIA_BUS_FMT_RGB888_1X24;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
struct atmel_hlcdc_rgb_output *output;
|
||||
struct device_node *ep;
|
||||
struct drm_panel *panel;
|
||||
struct drm_bridge *bridge;
|
||||
int ret;
|
||||
|
||||
ep = of_graph_get_endpoint_by_regs(dev->dev->of_node, 0, endpoint);
|
||||
if (!ep)
|
||||
return -ENODEV;
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(dev->dev->of_node, 0, endpoint,
|
||||
&panel, &bridge);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
of_node_put(ep);
|
||||
return ret;
|
||||
}
|
||||
|
||||
encoder = devm_kzalloc(dev->dev, sizeof(*encoder), GFP_KERNEL);
|
||||
if (!encoder)
|
||||
output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL);
|
||||
if (!output) {
|
||||
of_node_put(ep);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
output->bus_fmt = atmel_hlcdc_of_bus_fmt(ep);
|
||||
of_node_put(ep);
|
||||
if (output->bus_fmt < 0) {
|
||||
dev_err(dev->dev, "endpoint %d: invalid bus width\n", endpoint);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = drm_encoder_init(dev, encoder,
|
||||
ret = drm_encoder_init(dev, &output->encoder,
|
||||
&atmel_hlcdc_panel_encoder_funcs,
|
||||
DRM_MODE_ENCODER_NONE, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
encoder->possible_crtcs = 0x1;
|
||||
output->encoder.possible_crtcs = 0x1;
|
||||
|
||||
if (panel) {
|
||||
bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_Unknown);
|
||||
@ -62,7 +123,7 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
|
||||
}
|
||||
|
||||
if (bridge) {
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(&output->encoder, bridge, NULL);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
@ -70,7 +131,7 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
|
||||
drm_panel_bridge_remove(bridge);
|
||||
}
|
||||
|
||||
drm_encoder_cleanup(encoder);
|
||||
drm_encoder_cleanup(&output->encoder);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -78,12 +139,23 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
|
||||
int atmel_hlcdc_create_outputs(struct drm_device *dev)
|
||||
{
|
||||
int endpoint, ret = 0;
|
||||
int attached = 0;
|
||||
|
||||
for (endpoint = 0; !ret; endpoint++)
|
||||
/*
|
||||
* Always scan the first few endpoints even if we get -ENODEV,
|
||||
* but keep going after that as long as we keep getting hits.
|
||||
*/
|
||||
for (endpoint = 0; !ret || endpoint < 4; endpoint++) {
|
||||
ret = atmel_hlcdc_attach_endpoint(dev, endpoint);
|
||||
if (ret == -ENODEV)
|
||||
continue;
|
||||
if (ret)
|
||||
break;
|
||||
attached++;
|
||||
}
|
||||
|
||||
/* At least one device was successfully attached.*/
|
||||
if (ret == -ENODEV && endpoint)
|
||||
if (ret == -ENODEV && attached)
|
||||
return 0;
|
||||
|
||||
return ret;
|
||||
|
@ -942,10 +942,7 @@ static void atmel_hlcdc_plane_reset(struct drm_plane *p)
|
||||
"Failed to allocate initial plane state\n");
|
||||
return;
|
||||
}
|
||||
|
||||
p->state = &state->base;
|
||||
p->state->alpha = DRM_BLEND_ALPHA_OPAQUE;
|
||||
p->state->plane = p;
|
||||
__drm_atomic_helper_plane_reset(p, &state->base);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,22 +143,6 @@ static const struct dev_pm_ops bochs_pm_ops = {
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* pci interface */
|
||||
|
||||
static int bochs_kick_out_firmware_fb(struct pci_dev *pdev)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return -ENOMEM;
|
||||
|
||||
ap->ranges[0].base = pci_resource_start(pdev, 0);
|
||||
ap->ranges[0].size = pci_resource_len(pdev, 0);
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "bochsdrmfb", false);
|
||||
kfree(ap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bochs_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
@ -171,7 +155,7 @@ static int bochs_pci_probe(struct pci_dev *pdev,
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = bochs_kick_out_firmware_fb(pdev);
|
||||
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, 0, "bochsdrmfb");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -430,7 +430,7 @@ static void bochs_bo_unref(struct bochs_bo **bo)
|
||||
return;
|
||||
|
||||
tbo = &((*bo)->bo);
|
||||
ttm_bo_unref(&tbo);
|
||||
ttm_bo_put(tbo);
|
||||
*bo = NULL;
|
||||
}
|
||||
|
||||
|
@ -112,6 +112,14 @@ config DRM_THINE_THC63LVD1024
|
||||
---help---
|
||||
Thine THC63LVD1024 LVDS/parallel converter driver.
|
||||
|
||||
config DRM_TOSHIBA_TC358764
|
||||
tristate "TC358764 DSI/LVDS bridge"
|
||||
depends on DRM && DRM_PANEL
|
||||
depends on OF
|
||||
select DRM_MIPI_DSI
|
||||
help
|
||||
Toshiba TC358764 DSI/LVDS bridge driver.
|
||||
|
||||
config DRM_TOSHIBA_TC358767
|
||||
tristate "Toshiba TC358767 eDP bridge"
|
||||
depends on OF
|
||||
@ -128,6 +136,16 @@ config DRM_TI_TFP410
|
||||
---help---
|
||||
Texas Instruments TFP410 DVI/HDMI Transmitter driver
|
||||
|
||||
config DRM_TI_SN65DSI86
|
||||
tristate "TI SN65DSI86 DSI to eDP bridge"
|
||||
depends on OF
|
||||
select DRM_KMS_HELPER
|
||||
select REGMAP_I2C
|
||||
select DRM_PANEL
|
||||
select DRM_MIPI_DSI
|
||||
help
|
||||
Texas Instruments SN65DSI86 DSI to eDP Bridge driver
|
||||
|
||||
source "drivers/gpu/drm/bridge/analogix/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/bridge/adv7511/Kconfig"
|
||||
|
@ -10,8 +10,10 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
|
||||
obj-$(CONFIG_DRM_SII902X) += sii902x.o
|
||||
obj-$(CONFIG_DRM_SII9234) += sii9234.o
|
||||
obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
|
||||
obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
|
||||
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
|
||||
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
|
||||
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
|
||||
obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
|
||||
obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
|
||||
obj-y += synopsys/
|
||||
|
@ -1,5 +1,3 @@
|
||||
#ccflags-y := -Iinclude/drm
|
||||
|
||||
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
|
||||
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
|
||||
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
|
||||
|
499
drivers/gpu/drm/bridge/tc358764.c
Normal file
499
drivers/gpu/drm/bridge/tc358764.c
Normal file
@ -0,0 +1,499 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2018 Samsung Electronics Co., Ltd
|
||||
*
|
||||
* Authors:
|
||||
* Andrzej Hajda <a.hajda@samsung.com>
|
||||
* Maciej Purski <m.purski@samsung.com>
|
||||
*/
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end))
|
||||
#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
|
||||
|
||||
/* PPI layer registers */
|
||||
#define PPI_STARTPPI 0x0104 /* START control bit */
|
||||
#define PPI_LPTXTIMECNT 0x0114 /* LPTX timing signal */
|
||||
#define PPI_LANEENABLE 0x0134 /* Enables each lane */
|
||||
#define PPI_TX_RX_TA 0x013C /* BTA timing parameters */
|
||||
#define PPI_D0S_CLRSIPOCOUNT 0x0164 /* Assertion timer for Lane 0 */
|
||||
#define PPI_D1S_CLRSIPOCOUNT 0x0168 /* Assertion timer for Lane 1 */
|
||||
#define PPI_D2S_CLRSIPOCOUNT 0x016C /* Assertion timer for Lane 2 */
|
||||
#define PPI_D3S_CLRSIPOCOUNT 0x0170 /* Assertion timer for Lane 3 */
|
||||
#define PPI_START_FUNCTION 1
|
||||
|
||||
/* DSI layer registers */
|
||||
#define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX */
|
||||
#define DSI_LANEENABLE 0x0210 /* Enables each lane */
|
||||
#define DSI_RX_START 1
|
||||
|
||||
/* Video path registers */
|
||||
#define VP_CTRL 0x0450 /* Video Path Control */
|
||||
#define VP_CTRL_MSF(v) FLD_VAL(v, 0, 0) /* Magic square in RGB666 */
|
||||
#define VP_CTRL_VTGEN(v) FLD_VAL(v, 4, 4) /* Use chip clock for timing */
|
||||
#define VP_CTRL_EVTMODE(v) FLD_VAL(v, 5, 5) /* Event mode */
|
||||
#define VP_CTRL_RGB888(v) FLD_VAL(v, 8, 8) /* RGB888 mode */
|
||||
#define VP_CTRL_VSDELAY(v) FLD_VAL(v, 31, 20) /* VSYNC delay */
|
||||
#define VP_CTRL_HSPOL BIT(17) /* Polarity of HSYNC signal */
|
||||
#define VP_CTRL_DEPOL BIT(18) /* Polarity of DE signal */
|
||||
#define VP_CTRL_VSPOL BIT(19) /* Polarity of VSYNC signal */
|
||||
#define VP_HTIM1 0x0454 /* Horizontal Timing Control 1 */
|
||||
#define VP_HTIM1_HBP(v) FLD_VAL(v, 24, 16)
|
||||
#define VP_HTIM1_HSYNC(v) FLD_VAL(v, 8, 0)
|
||||
#define VP_HTIM2 0x0458 /* Horizontal Timing Control 2 */
|
||||
#define VP_HTIM2_HFP(v) FLD_VAL(v, 24, 16)
|
||||
#define VP_HTIM2_HACT(v) FLD_VAL(v, 10, 0)
|
||||
#define VP_VTIM1 0x045C /* Vertical Timing Control 1 */
|
||||
#define VP_VTIM1_VBP(v) FLD_VAL(v, 23, 16)
|
||||
#define VP_VTIM1_VSYNC(v) FLD_VAL(v, 7, 0)
|
||||
#define VP_VTIM2 0x0460 /* Vertical Timing Control 2 */
|
||||
#define VP_VTIM2_VFP(v) FLD_VAL(v, 23, 16)
|
||||
#define VP_VTIM2_VACT(v) FLD_VAL(v, 10, 0)
|
||||
#define VP_VFUEN 0x0464 /* Video Frame Timing Update Enable */
|
||||
|
||||
/* LVDS registers */
|
||||
#define LV_MX0003 0x0480 /* Mux input bit 0 to 3 */
|
||||
#define LV_MX0407 0x0484 /* Mux input bit 4 to 7 */
|
||||
#define LV_MX0811 0x0488 /* Mux input bit 8 to 11 */
|
||||
#define LV_MX1215 0x048C /* Mux input bit 12 to 15 */
|
||||
#define LV_MX1619 0x0490 /* Mux input bit 16 to 19 */
|
||||
#define LV_MX2023 0x0494 /* Mux input bit 20 to 23 */
|
||||
#define LV_MX2427 0x0498 /* Mux input bit 24 to 27 */
|
||||
#define LV_MX(b0, b1, b2, b3) (FLD_VAL(b0, 4, 0) | FLD_VAL(b1, 12, 8) | \
|
||||
FLD_VAL(b2, 20, 16) | FLD_VAL(b3, 28, 24))
|
||||
|
||||
/* Input bit numbers used in mux registers */
|
||||
enum {
|
||||
LVI_R0,
|
||||
LVI_R1,
|
||||
LVI_R2,
|
||||
LVI_R3,
|
||||
LVI_R4,
|
||||
LVI_R5,
|
||||
LVI_R6,
|
||||
LVI_R7,
|
||||
LVI_G0,
|
||||
LVI_G1,
|
||||
LVI_G2,
|
||||
LVI_G3,
|
||||
LVI_G4,
|
||||
LVI_G5,
|
||||
LVI_G6,
|
||||
LVI_G7,
|
||||
LVI_B0,
|
||||
LVI_B1,
|
||||
LVI_B2,
|
||||
LVI_B3,
|
||||
LVI_B4,
|
||||
LVI_B5,
|
||||
LVI_B6,
|
||||
LVI_B7,
|
||||
LVI_HS,
|
||||
LVI_VS,
|
||||
LVI_DE,
|
||||
LVI_L0
|
||||
};
|
||||
|
||||
#define LV_CFG 0x049C /* LVDS Configuration */
|
||||
#define LV_PHY0 0x04A0 /* LVDS PHY 0 */
|
||||
#define LV_PHY0_RST(v) FLD_VAL(v, 22, 22) /* PHY reset */
|
||||
#define LV_PHY0_IS(v) FLD_VAL(v, 15, 14)
|
||||
#define LV_PHY0_ND(v) FLD_VAL(v, 4, 0) /* Frequency range select */
|
||||
#define LV_PHY0_PRBS_ON(v) FLD_VAL(v, 20, 16) /* Clock/Data Flag pins */
|
||||
|
||||
/* System registers */
|
||||
#define SYS_RST 0x0504 /* System Reset */
|
||||
#define SYS_ID 0x0580 /* System ID */
|
||||
|
||||
#define SYS_RST_I2CS BIT(0) /* Reset I2C-Slave controller */
|
||||
#define SYS_RST_I2CM BIT(1) /* Reset I2C-Master controller */
|
||||
#define SYS_RST_LCD BIT(2) /* Reset LCD controller */
|
||||
#define SYS_RST_BM BIT(3) /* Reset Bus Management controller */
|
||||
#define SYS_RST_DSIRX BIT(4) /* Reset DSI-RX and App controller */
|
||||
#define SYS_RST_REG BIT(5) /* Reset Register module */
|
||||
|
||||
#define LPX_PERIOD 2
|
||||
#define TTA_SURE 3
|
||||
#define TTA_GET 0x20000
|
||||
|
||||
/* Lane enable PPI and DSI register bits */
|
||||
#define LANEENABLE_CLEN BIT(0)
|
||||
#define LANEENABLE_L0EN BIT(1)
|
||||
#define LANEENABLE_L1EN BIT(2)
|
||||
#define LANEENABLE_L2EN BIT(3)
|
||||
#define LANEENABLE_L3EN BIT(4)
|
||||
|
||||
/* LVCFG fields */
|
||||
#define LV_CFG_LVEN BIT(0)
|
||||
#define LV_CFG_LVDLINK BIT(1)
|
||||
#define LV_CFG_CLKPOL1 BIT(2)
|
||||
#define LV_CFG_CLKPOL2 BIT(3)
|
||||
|
||||
static const char * const tc358764_supplies[] = {
|
||||
"vddc", "vddio", "vddlvds"
|
||||
};
|
||||
|
||||
struct tc358764 {
|
||||
struct device *dev;
|
||||
struct drm_bridge bridge;
|
||||
struct drm_connector connector;
|
||||
struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)];
|
||||
struct gpio_desc *gpio_reset;
|
||||
struct drm_panel *panel;
|
||||
int error;
|
||||
};
|
||||
|
||||
static int tc358764_clear_error(struct tc358764 *ctx)
|
||||
{
|
||||
int ret = ctx->error;
|
||||
|
||||
ctx->error = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tc358764_read(struct tc358764 *ctx, u16 addr, u32 *val)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
|
||||
ssize_t ret;
|
||||
|
||||
if (ctx->error)
|
||||
return;
|
||||
|
||||
cpu_to_le16s(&addr);
|
||||
ret = mipi_dsi_generic_read(dsi, &addr, sizeof(addr), val, sizeof(*val));
|
||||
if (ret >= 0)
|
||||
le32_to_cpus(val);
|
||||
|
||||
dev_dbg(ctx->dev, "read: %d, addr: %d\n", addr, *val);
|
||||
}
|
||||
|
||||
static void tc358764_write(struct tc358764 *ctx, u16 addr, u32 val)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
|
||||
ssize_t ret;
|
||||
u8 data[6];
|
||||
|
||||
if (ctx->error)
|
||||
return;
|
||||
|
||||
data[0] = addr;
|
||||
data[1] = addr >> 8;
|
||||
data[2] = val;
|
||||
data[3] = val >> 8;
|
||||
data[4] = val >> 16;
|
||||
data[5] = val >> 24;
|
||||
|
||||
ret = mipi_dsi_generic_write(dsi, data, sizeof(data));
|
||||
if (ret < 0)
|
||||
ctx->error = ret;
|
||||
}
|
||||
|
||||
static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge)
|
||||
{
|
||||
return container_of(bridge, struct tc358764, bridge);
|
||||
}
|
||||
|
||||
static inline
|
||||
struct tc358764 *connector_to_tc358764(struct drm_connector *connector)
|
||||
{
|
||||
return container_of(connector, struct tc358764, connector);
|
||||
}
|
||||
|
||||
static int tc358764_init(struct tc358764 *ctx)
|
||||
{
|
||||
u32 v = 0;
|
||||
|
||||
tc358764_read(ctx, SYS_ID, &v);
|
||||
if (ctx->error)
|
||||
return tc358764_clear_error(ctx);
|
||||
dev_info(ctx->dev, "ID: %#x\n", v);
|
||||
|
||||
/* configure PPI counters */
|
||||
tc358764_write(ctx, PPI_TX_RX_TA, TTA_GET | TTA_SURE);
|
||||
tc358764_write(ctx, PPI_LPTXTIMECNT, LPX_PERIOD);
|
||||
tc358764_write(ctx, PPI_D0S_CLRSIPOCOUNT, 5);
|
||||
tc358764_write(ctx, PPI_D1S_CLRSIPOCOUNT, 5);
|
||||
tc358764_write(ctx, PPI_D2S_CLRSIPOCOUNT, 5);
|
||||
tc358764_write(ctx, PPI_D3S_CLRSIPOCOUNT, 5);
|
||||
|
||||
/* enable four data lanes and clock lane */
|
||||
tc358764_write(ctx, PPI_LANEENABLE, LANEENABLE_L3EN | LANEENABLE_L2EN |
|
||||
LANEENABLE_L1EN | LANEENABLE_L0EN | LANEENABLE_CLEN);
|
||||
tc358764_write(ctx, DSI_LANEENABLE, LANEENABLE_L3EN | LANEENABLE_L2EN |
|
||||
LANEENABLE_L1EN | LANEENABLE_L0EN | LANEENABLE_CLEN);
|
||||
|
||||
/* start */
|
||||
tc358764_write(ctx, PPI_STARTPPI, PPI_START_FUNCTION);
|
||||
tc358764_write(ctx, DSI_STARTDSI, DSI_RX_START);
|
||||
|
||||
/* configure video path */
|
||||
tc358764_write(ctx, VP_CTRL, VP_CTRL_VSDELAY(15) | VP_CTRL_RGB888(1) |
|
||||
VP_CTRL_EVTMODE(1) | VP_CTRL_HSPOL | VP_CTRL_VSPOL);
|
||||
|
||||
/* reset PHY */
|
||||
tc358764_write(ctx, LV_PHY0, LV_PHY0_RST(1) |
|
||||
LV_PHY0_PRBS_ON(4) | LV_PHY0_IS(2) | LV_PHY0_ND(6));
|
||||
tc358764_write(ctx, LV_PHY0, LV_PHY0_PRBS_ON(4) | LV_PHY0_IS(2) |
|
||||
LV_PHY0_ND(6));
|
||||
|
||||
/* reset bridge */
|
||||
tc358764_write(ctx, SYS_RST, SYS_RST_LCD);
|
||||
|
||||
/* set bit order */
|
||||
tc358764_write(ctx, LV_MX0003, LV_MX(LVI_R0, LVI_R1, LVI_R2, LVI_R3));
|
||||
tc358764_write(ctx, LV_MX0407, LV_MX(LVI_R4, LVI_R7, LVI_R5, LVI_G0));
|
||||
tc358764_write(ctx, LV_MX0811, LV_MX(LVI_G1, LVI_G2, LVI_G6, LVI_G7));
|
||||
tc358764_write(ctx, LV_MX1215, LV_MX(LVI_G3, LVI_G4, LVI_G5, LVI_B0));
|
||||
tc358764_write(ctx, LV_MX1619, LV_MX(LVI_B6, LVI_B7, LVI_B1, LVI_B2));
|
||||
tc358764_write(ctx, LV_MX2023, LV_MX(LVI_B3, LVI_B4, LVI_B5, LVI_L0));
|
||||
tc358764_write(ctx, LV_MX2427, LV_MX(LVI_HS, LVI_VS, LVI_DE, LVI_R6));
|
||||
tc358764_write(ctx, LV_CFG, LV_CFG_CLKPOL2 | LV_CFG_CLKPOL1 |
|
||||
LV_CFG_LVEN);
|
||||
|
||||
return tc358764_clear_error(ctx);
|
||||
}
|
||||
|
||||
static void tc358764_reset(struct tc358764 *ctx)
|
||||
{
|
||||
gpiod_set_value(ctx->gpio_reset, 1);
|
||||
usleep_range(1000, 2000);
|
||||
gpiod_set_value(ctx->gpio_reset, 0);
|
||||
usleep_range(1000, 2000);
|
||||
}
|
||||
|
||||
static int tc358764_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct tc358764 *ctx = connector_to_tc358764(connector);
|
||||
|
||||
return drm_panel_get_modes(ctx->panel);
|
||||
}
|
||||
|
||||
static const
|
||||
struct drm_connector_helper_funcs tc358764_connector_helper_funcs = {
|
||||
.get_modes = tc358764_get_modes,
|
||||
};
|
||||
|
||||
static const struct drm_connector_funcs tc358764_connector_funcs = {
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = drm_connector_cleanup,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static void tc358764_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tc358764 *ctx = bridge_to_tc358764(bridge);
|
||||
int ret = drm_panel_disable(bridge_to_tc358764(bridge)->panel);
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "error disabling panel (%d)\n", ret);
|
||||
}
|
||||
|
||||
static void tc358764_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tc358764 *ctx = bridge_to_tc358764(bridge);
|
||||
int ret;
|
||||
|
||||
ret = drm_panel_unprepare(ctx->panel);
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "error unpreparing panel (%d)\n", ret);
|
||||
tc358764_reset(ctx);
|
||||
usleep_range(10000, 15000);
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "error disabling regulators (%d)\n", ret);
|
||||
}
|
||||
|
||||
static void tc358764_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tc358764 *ctx = bridge_to_tc358764(bridge);
|
||||
int ret;
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "error enabling regulators (%d)\n", ret);
|
||||
usleep_range(10000, 15000);
|
||||
tc358764_reset(ctx);
|
||||
ret = tc358764_init(ctx);
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
|
||||
ret = drm_panel_prepare(ctx->panel);
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "error preparing panel (%d)\n", ret);
|
||||
}
|
||||
|
||||
static void tc358764_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tc358764 *ctx = bridge_to_tc358764(bridge);
|
||||
int ret = drm_panel_enable(ctx->panel);
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
|
||||
}
|
||||
|
||||
static int tc358764_attach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tc358764 *ctx = bridge_to_tc358764(bridge);
|
||||
struct drm_device *drm = bridge->dev;
|
||||
int ret;
|
||||
|
||||
ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
|
||||
ret = drm_connector_init(drm, &ctx->connector,
|
||||
&tc358764_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize connector\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(&ctx->connector,
|
||||
&tc358764_connector_helper_funcs);
|
||||
drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
|
||||
drm_panel_attach(ctx->panel, &ctx->connector);
|
||||
ctx->connector.funcs->reset(&ctx->connector);
|
||||
drm_fb_helper_add_one_connector(drm->fb_helper, &ctx->connector);
|
||||
drm_connector_register(&ctx->connector);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tc358764_detach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tc358764 *ctx = bridge_to_tc358764(bridge);
|
||||
struct drm_device *drm = bridge->dev;
|
||||
|
||||
drm_connector_unregister(&ctx->connector);
|
||||
drm_fb_helper_remove_one_connector(drm->fb_helper, &ctx->connector);
|
||||
drm_panel_detach(ctx->panel);
|
||||
ctx->panel = NULL;
|
||||
drm_connector_unreference(&ctx->connector);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs tc358764_bridge_funcs = {
|
||||
.disable = tc358764_disable,
|
||||
.post_disable = tc358764_post_disable,
|
||||
.enable = tc358764_enable,
|
||||
.pre_enable = tc358764_pre_enable,
|
||||
.attach = tc358764_attach,
|
||||
.detach = tc358764_detach,
|
||||
};
|
||||
|
||||
static int tc358764_parse_dt(struct tc358764 *ctx)
|
||||
{
|
||||
struct device *dev = ctx->dev;
|
||||
int ret;
|
||||
|
||||
ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(ctx->gpio_reset)) {
|
||||
dev_err(dev, "no reset GPIO pin provided\n");
|
||||
return PTR_ERR(ctx->gpio_reset);
|
||||
}
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(ctx->dev->of_node, 1, 0, &ctx->panel,
|
||||
NULL);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "cannot find panel (%d)\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc358764_configure_regulators(struct tc358764 *ctx)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ctx->supplies); ++i)
|
||||
ctx->supplies[i].supply = tc358764_supplies[i];
|
||||
|
||||
ret = devm_regulator_bulk_get(ctx->dev, ARRAY_SIZE(ctx->supplies),
|
||||
ctx->supplies);
|
||||
if (ret < 0)
|
||||
dev_err(ctx->dev, "failed to get regulators: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc358764_probe(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct device *dev = &dsi->dev;
|
||||
struct tc358764 *ctx;
|
||||
int ret;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(struct tc358764), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
mipi_dsi_set_drvdata(dsi, ctx);
|
||||
|
||||
ctx->dev = dev;
|
||||
|
||||
dsi->lanes = 4;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST
|
||||
| MIPI_DSI_MODE_VIDEO_AUTO_VERT | MIPI_DSI_MODE_LPM;
|
||||
|
||||
ret = tc358764_parse_dt(ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = tc358764_configure_regulators(ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx->bridge.funcs = &tc358764_bridge_funcs;
|
||||
ctx->bridge.of_node = dev->of_node;
|
||||
|
||||
drm_bridge_add(&ctx->bridge);
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
if (ret < 0) {
|
||||
drm_bridge_remove(&ctx->bridge);
|
||||
dev_err(dev, "failed to attach dsi\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc358764_remove(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct tc358764 *ctx = mipi_dsi_get_drvdata(dsi);
|
||||
|
||||
mipi_dsi_detach(dsi);
|
||||
drm_bridge_remove(&ctx->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id tc358764_of_match[] = {
|
||||
{ .compatible = "toshiba,tc358764" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tc358764_of_match);
|
||||
|
||||
static struct mipi_dsi_driver tc358764_driver = {
|
||||
.probe = tc358764_probe,
|
||||
.remove = tc358764_remove,
|
||||
.driver = {
|
||||
.name = "tc358764",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = tc358764_of_match,
|
||||
},
|
||||
};
|
||||
module_mipi_dsi_driver(tc358764_driver);
|
||||
|
||||
MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
|
||||
MODULE_AUTHOR("Maciej Purski <m.purski@samsung.com>");
|
||||
MODULE_DESCRIPTION("MIPI-DSI based Driver for TC358764 DSI/LVDS Bridge");
|
||||
MODULE_LICENSE("GPL v2");
|
779
drivers/gpu/drm/bridge/ti-sn65dsi86.c
Normal file
779
drivers/gpu/drm/bridge/ti-sn65dsi86.c
Normal file
@ -0,0 +1,779 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_dp_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define SN_DEVICE_REV_REG 0x08
|
||||
#define SN_DPPLL_SRC_REG 0x0A
|
||||
#define DPPLL_CLK_SRC_DSICLK BIT(0)
|
||||
#define REFCLK_FREQ_MASK GENMASK(3, 1)
|
||||
#define REFCLK_FREQ(x) ((x) << 1)
|
||||
#define DPPLL_SRC_DP_PLL_LOCK BIT(7)
|
||||
#define SN_PLL_ENABLE_REG 0x0D
|
||||
#define SN_DSI_LANES_REG 0x10
|
||||
#define CHA_DSI_LANES_MASK GENMASK(4, 3)
|
||||
#define CHA_DSI_LANES(x) ((x) << 3)
|
||||
#define SN_DSIA_CLK_FREQ_REG 0x12
|
||||
#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG 0x20
|
||||
#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG 0x24
|
||||
#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG 0x2C
|
||||
#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG 0x2D
|
||||
#define CHA_HSYNC_POLARITY BIT(7)
|
||||
#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG 0x30
|
||||
#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG 0x31
|
||||
#define CHA_VSYNC_POLARITY BIT(7)
|
||||
#define SN_CHA_HORIZONTAL_BACK_PORCH_REG 0x34
|
||||
#define SN_CHA_VERTICAL_BACK_PORCH_REG 0x36
|
||||
#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG 0x38
|
||||
#define SN_CHA_VERTICAL_FRONT_PORCH_REG 0x3A
|
||||
#define SN_ENH_FRAME_REG 0x5A
|
||||
#define VSTREAM_ENABLE BIT(3)
|
||||
#define SN_DATA_FORMAT_REG 0x5B
|
||||
#define SN_HPD_DISABLE_REG 0x5C
|
||||
#define HPD_DISABLE BIT(0)
|
||||
#define SN_AUX_WDATA_REG(x) (0x64 + (x))
|
||||
#define SN_AUX_ADDR_19_16_REG 0x74
|
||||
#define SN_AUX_ADDR_15_8_REG 0x75
|
||||
#define SN_AUX_ADDR_7_0_REG 0x76
|
||||
#define SN_AUX_LENGTH_REG 0x77
|
||||
#define SN_AUX_CMD_REG 0x78
|
||||
#define AUX_CMD_SEND BIT(1)
|
||||
#define AUX_CMD_REQ(x) ((x) << 4)
|
||||
#define SN_AUX_RDATA_REG(x) (0x79 + (x))
|
||||
#define SN_SSC_CONFIG_REG 0x93
|
||||
#define DP_NUM_LANES_MASK GENMASK(5, 4)
|
||||
#define DP_NUM_LANES(x) ((x) << 4)
|
||||
#define SN_DATARATE_CONFIG_REG 0x94
|
||||
#define DP_DATARATE_MASK GENMASK(7, 5)
|
||||
#define DP_DATARATE(x) ((x) << 5)
|
||||
#define SN_ML_TX_MODE_REG 0x96
|
||||
#define ML_TX_MAIN_LINK_OFF 0
|
||||
#define ML_TX_NORMAL_MODE BIT(0)
|
||||
#define SN_AUX_CMD_STATUS_REG 0xF4
|
||||
#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
|
||||
#define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
|
||||
#define AUX_IRQ_STATUS_NAT_I2C_FAIL BIT(6)
|
||||
|
||||
#define MIN_DSI_CLK_FREQ_MHZ 40
|
||||
|
||||
/* fudge factor required to account for 8b/10b encoding */
|
||||
#define DP_CLK_FUDGE_NUM 10
|
||||
#define DP_CLK_FUDGE_DEN 8
|
||||
|
||||
/* Matches DP_AUX_MAX_PAYLOAD_BYTES (for now) */
|
||||
#define SN_AUX_MAX_PAYLOAD_BYTES 16
|
||||
|
||||
#define SN_REGULATOR_SUPPLY_NUM 4
|
||||
|
||||
struct ti_sn_bridge {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct drm_dp_aux aux;
|
||||
struct drm_bridge bridge;
|
||||
struct drm_connector connector;
|
||||
struct device_node *host_node;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct clk *refclk;
|
||||
struct drm_panel *panel;
|
||||
struct gpio_desc *enable_gpio;
|
||||
struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM];
|
||||
};
|
||||
|
||||
static const struct regmap_range ti_sn_bridge_volatile_ranges[] = {
|
||||
{ .range_min = 0, .range_max = 0xFF },
|
||||
};
|
||||
|
||||
static const struct regmap_access_table ti_sn_bridge_volatile_table = {
|
||||
.yes_ranges = ti_sn_bridge_volatile_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(ti_sn_bridge_volatile_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_config ti_sn_bridge_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.volatile_table = &ti_sn_bridge_volatile_table,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
|
||||
static void ti_sn_bridge_write_u16(struct ti_sn_bridge *pdata,
|
||||
unsigned int reg, u16 val)
|
||||
{
|
||||
regmap_write(pdata->regmap, reg, val & 0xFF);
|
||||
regmap_write(pdata->regmap, reg + 1, val >> 8);
|
||||
}
|
||||
|
||||
static int __maybe_unused ti_sn_bridge_resume(struct device *dev)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = regulator_bulk_enable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to enable supplies %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpiod_set_value(pdata->enable_gpio, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused ti_sn_bridge_suspend(struct device *dev)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
gpiod_set_value(pdata->enable_gpio, 0);
|
||||
|
||||
ret = regulator_bulk_disable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies);
|
||||
if (ret)
|
||||
DRM_ERROR("failed to disable supplies %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ti_sn_bridge_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(ti_sn_bridge_suspend, ti_sn_bridge_resume, NULL)
|
||||
};
|
||||
|
||||
/* Connector funcs */
|
||||
static struct ti_sn_bridge *
|
||||
connector_to_ti_sn_bridge(struct drm_connector *connector)
|
||||
{
|
||||
return container_of(connector, struct ti_sn_bridge, connector);
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
|
||||
|
||||
return drm_panel_get_modes(pdata->panel);
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
/* maximum supported resolution is 4K at 60 fps */
|
||||
if (mode->clock > 594000)
|
||||
return MODE_CLOCK_HIGH;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
|
||||
.get_modes = ti_sn_bridge_connector_get_modes,
|
||||
.mode_valid = ti_sn_bridge_connector_mode_valid,
|
||||
};
|
||||
|
||||
static enum drm_connector_status
|
||||
ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
/**
|
||||
* TODO: Currently if drm_panel is present, then always
|
||||
* return the status as connected. Need to add support to detect
|
||||
* device state for hot pluggable scenarios.
|
||||
*/
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.detect = ti_sn_bridge_connector_detect,
|
||||
.destroy = drm_connector_cleanup,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge)
|
||||
{
|
||||
return container_of(bridge, struct ti_sn_bridge, bridge);
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
|
||||
{
|
||||
unsigned int i;
|
||||
const char * const ti_sn_bridge_supply_names[] = {
|
||||
"vcca", "vcc", "vccio", "vpll",
|
||||
};
|
||||
|
||||
for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++)
|
||||
pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
|
||||
|
||||
return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM,
|
||||
pdata->supplies);
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_attach(struct drm_bridge *bridge)
|
||||
{
|
||||
int ret, val;
|
||||
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
|
||||
struct mipi_dsi_host *host;
|
||||
struct mipi_dsi_device *dsi;
|
||||
const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
|
||||
ret = drm_connector_init(bridge->dev, &pdata->connector,
|
||||
&ti_sn_bridge_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_eDP);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize connector with drm\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(&pdata->connector,
|
||||
&ti_sn_bridge_connector_helper_funcs);
|
||||
drm_connector_attach_encoder(&pdata->connector, bridge->encoder);
|
||||
|
||||
/*
|
||||
* TODO: ideally finding host resource and dsi dev registration needs
|
||||
* to be done in bridge probe. But some existing DSI host drivers will
|
||||
* wait for any of the drm_bridge/drm_panel to get added to the global
|
||||
* bridge/panel list, before completing their probe. So if we do the
|
||||
* dsi dev registration part in bridge probe, before populating in
|
||||
* the global bridge list, then it will cause deadlock as dsi host probe
|
||||
* will never complete, neither our bridge probe. So keeping it here
|
||||
* will satisfy most of the existing host drivers. Once the host driver
|
||||
* is fixed we can move the below code to bridge probe safely.
|
||||
*/
|
||||
host = of_find_mipi_dsi_host_by_node(pdata->host_node);
|
||||
if (!host) {
|
||||
DRM_ERROR("failed to find dsi host\n");
|
||||
ret = -ENODEV;
|
||||
goto err_dsi_host;
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
DRM_ERROR("failed to create dsi device\n");
|
||||
ret = PTR_ERR(dsi);
|
||||
goto err_dsi_host;
|
||||
}
|
||||
|
||||
/* TODO: setting to 4 lanes always for now */
|
||||
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_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
|
||||
|
||||
/* check if continuous dsi clock is required or not */
|
||||
pm_runtime_get_sync(pdata->dev);
|
||||
regmap_read(pdata->regmap, SN_DPPLL_SRC_REG, &val);
|
||||
pm_runtime_put(pdata->dev);
|
||||
if (!(val & DPPLL_CLK_SRC_DSICLK))
|
||||
dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to attach dsi to host\n");
|
||||
goto err_dsi_attach;
|
||||
}
|
||||
pdata->dsi = dsi;
|
||||
|
||||
/* attach panel to bridge */
|
||||
drm_panel_attach(pdata->panel, &pdata->connector);
|
||||
|
||||
return 0;
|
||||
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
err_dsi_host:
|
||||
drm_connector_cleanup(&pdata->connector);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ti_sn_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
|
||||
|
||||
drm_panel_disable(pdata->panel);
|
||||
|
||||
/* disable video stream */
|
||||
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, 0);
|
||||
/* semi auto link training mode OFF */
|
||||
regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0);
|
||||
/* disable DP PLL */
|
||||
regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0);
|
||||
|
||||
drm_panel_unprepare(pdata->panel);
|
||||
}
|
||||
|
||||
static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn_bridge *pdata)
|
||||
{
|
||||
u32 bit_rate_khz, clk_freq_khz;
|
||||
struct drm_display_mode *mode =
|
||||
&pdata->bridge.encoder->crtc->state->adjusted_mode;
|
||||
|
||||
bit_rate_khz = mode->clock *
|
||||
mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
|
||||
clk_freq_khz = bit_rate_khz / (pdata->dsi->lanes * 2);
|
||||
|
||||
return clk_freq_khz;
|
||||
}
|
||||
|
||||
/* clk frequencies supported by bridge in Hz in case derived from REFCLK pin */
|
||||
static const u32 ti_sn_bridge_refclk_lut[] = {
|
||||
12000000,
|
||||
19200000,
|
||||
26000000,
|
||||
27000000,
|
||||
38400000,
|
||||
};
|
||||
|
||||
/* clk frequencies supported by bridge in Hz in case derived from DACP/N pin */
|
||||
static const u32 ti_sn_bridge_dsiclk_lut[] = {
|
||||
468000000,
|
||||
384000000,
|
||||
416000000,
|
||||
486000000,
|
||||
460800000,
|
||||
};
|
||||
|
||||
static void ti_sn_bridge_set_refclk_freq(struct ti_sn_bridge *pdata)
|
||||
{
|
||||
int i;
|
||||
u32 refclk_rate;
|
||||
const u32 *refclk_lut;
|
||||
size_t refclk_lut_size;
|
||||
|
||||
if (pdata->refclk) {
|
||||
refclk_rate = clk_get_rate(pdata->refclk);
|
||||
refclk_lut = ti_sn_bridge_refclk_lut;
|
||||
refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_refclk_lut);
|
||||
clk_prepare_enable(pdata->refclk);
|
||||
} else {
|
||||
refclk_rate = ti_sn_bridge_get_dsi_freq(pdata) * 1000;
|
||||
refclk_lut = ti_sn_bridge_dsiclk_lut;
|
||||
refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_dsiclk_lut);
|
||||
}
|
||||
|
||||
/* for i equals to refclk_lut_size means default frequency */
|
||||
for (i = 0; i < refclk_lut_size; i++)
|
||||
if (refclk_lut[i] == refclk_rate)
|
||||
break;
|
||||
|
||||
regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK,
|
||||
REFCLK_FREQ(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* LUT index corresponds to register value and
|
||||
* LUT values corresponds to dp data rate supported
|
||||
* by the bridge in Mbps unit.
|
||||
*/
|
||||
static const unsigned int ti_sn_bridge_dp_rate_lut[] = {
|
||||
0, 1620, 2160, 2430, 2700, 3240, 4320, 5400
|
||||
};
|
||||
|
||||
static void ti_sn_bridge_set_dsi_dp_rate(struct ti_sn_bridge *pdata)
|
||||
{
|
||||
unsigned int bit_rate_mhz, clk_freq_mhz, dp_rate_mhz;
|
||||
unsigned int val, i;
|
||||
struct drm_display_mode *mode =
|
||||
&pdata->bridge.encoder->crtc->state->adjusted_mode;
|
||||
|
||||
/* set DSIA clk frequency */
|
||||
bit_rate_mhz = (mode->clock / 1000) *
|
||||
mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
|
||||
clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
|
||||
|
||||
/* for each increment in val, frequency increases by 5MHz */
|
||||
val = (MIN_DSI_CLK_FREQ_MHZ / 5) +
|
||||
(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF);
|
||||
regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
|
||||
|
||||
/* set DP data rate */
|
||||
dp_rate_mhz = ((bit_rate_mhz / pdata->dsi->lanes) * DP_CLK_FUDGE_NUM) /
|
||||
DP_CLK_FUDGE_DEN;
|
||||
for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1; i++)
|
||||
if (ti_sn_bridge_dp_rate_lut[i] > dp_rate_mhz)
|
||||
break;
|
||||
|
||||
regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG,
|
||||
DP_DATARATE_MASK, DP_DATARATE(i));
|
||||
}
|
||||
|
||||
static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata)
|
||||
{
|
||||
struct drm_display_mode *mode =
|
||||
&pdata->bridge.encoder->crtc->state->adjusted_mode;
|
||||
u8 hsync_polarity = 0, vsync_polarity = 0;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
||||
hsync_polarity = CHA_HSYNC_POLARITY;
|
||||
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
||||
vsync_polarity = CHA_VSYNC_POLARITY;
|
||||
|
||||
ti_sn_bridge_write_u16(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
|
||||
mode->hdisplay);
|
||||
ti_sn_bridge_write_u16(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
|
||||
mode->vdisplay);
|
||||
regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
|
||||
(mode->hsync_end - mode->hsync_start) & 0xFF);
|
||||
regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
|
||||
(((mode->hsync_end - mode->hsync_start) >> 8) & 0x7F) |
|
||||
hsync_polarity);
|
||||
regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
|
||||
(mode->vsync_end - mode->vsync_start) & 0xFF);
|
||||
regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
|
||||
(((mode->vsync_end - mode->vsync_start) >> 8) & 0x7F) |
|
||||
vsync_polarity);
|
||||
|
||||
regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
|
||||
(mode->htotal - mode->hsync_end) & 0xFF);
|
||||
regmap_write(pdata->regmap, SN_CHA_VERTICAL_BACK_PORCH_REG,
|
||||
(mode->vtotal - mode->vsync_end) & 0xFF);
|
||||
|
||||
regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
|
||||
(mode->hsync_start - mode->hdisplay) & 0xFF);
|
||||
regmap_write(pdata->regmap, SN_CHA_VERTICAL_FRONT_PORCH_REG,
|
||||
(mode->vsync_start - mode->vdisplay) & 0xFF);
|
||||
|
||||
usleep_range(10000, 10500); /* 10ms delay recommended by spec */
|
||||
}
|
||||
|
||||
static void ti_sn_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* FIXME:
|
||||
* This 70ms was found necessary by experimentation. If it's not
|
||||
* present, link training fails. It seems like it can go anywhere from
|
||||
* pre_enable() up to semi-auto link training initiation below.
|
||||
*
|
||||
* Neither the datasheet for the bridge nor the panel tested mention a
|
||||
* delay of this magnitude in the timing requirements. So for now, add
|
||||
* the mystery delay until someone figures out a better fix.
|
||||
*/
|
||||
msleep(70);
|
||||
|
||||
/* DSI_A lane config */
|
||||
val = CHA_DSI_LANES(4 - pdata->dsi->lanes);
|
||||
regmap_update_bits(pdata->regmap, SN_DSI_LANES_REG,
|
||||
CHA_DSI_LANES_MASK, val);
|
||||
|
||||
/* DP lane config */
|
||||
val = DP_NUM_LANES(pdata->dsi->lanes - 1);
|
||||
regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
|
||||
val);
|
||||
|
||||
/* set dsi/dp clk frequency value */
|
||||
ti_sn_bridge_set_dsi_dp_rate(pdata);
|
||||
|
||||
/* enable DP PLL */
|
||||
regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1);
|
||||
|
||||
ret = regmap_read_poll_timeout(pdata->regmap, SN_DPPLL_SRC_REG, val,
|
||||
val & DPPLL_SRC_DP_PLL_LOCK, 1000,
|
||||
50 * 1000);
|
||||
if (ret) {
|
||||
DRM_ERROR("DP_PLL_LOCK polling failed (%d)\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SN65DSI86 only supports ASSR Display Authentication method and
|
||||
* this method is enabled by default. An eDP panel must support this
|
||||
* authentication method. We need to enable this method in the eDP panel
|
||||
* at DisplayPort address 0x0010A prior to link training.
|
||||
*/
|
||||
drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET,
|
||||
DP_ALTERNATE_SCRAMBLER_RESET_ENABLE);
|
||||
|
||||
/* Semi auto link training mode */
|
||||
regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
|
||||
ret = regmap_read_poll_timeout(pdata->regmap, SN_ML_TX_MODE_REG, val,
|
||||
val == ML_TX_MAIN_LINK_OFF ||
|
||||
val == ML_TX_NORMAL_MODE, 1000,
|
||||
500 * 1000);
|
||||
if (ret) {
|
||||
DRM_ERROR("Training complete polling failed (%d)\n", ret);
|
||||
return;
|
||||
} else if (val == ML_TX_MAIN_LINK_OFF) {
|
||||
DRM_ERROR("Link training failed, link is off\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* config video parameters */
|
||||
ti_sn_bridge_set_video_timings(pdata);
|
||||
|
||||
/* enable video stream */
|
||||
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE,
|
||||
VSTREAM_ENABLE);
|
||||
|
||||
drm_panel_enable(pdata->panel);
|
||||
}
|
||||
|
||||
static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
|
||||
|
||||
pm_runtime_get_sync(pdata->dev);
|
||||
|
||||
/* configure bridge ref_clk */
|
||||
ti_sn_bridge_set_refclk_freq(pdata);
|
||||
|
||||
/* in case drm_panel is connected then HPD is not supported */
|
||||
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE,
|
||||
HPD_DISABLE);
|
||||
|
||||
drm_panel_prepare(pdata->panel);
|
||||
}
|
||||
|
||||
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
|
||||
|
||||
if (pdata->refclk)
|
||||
clk_disable_unprepare(pdata->refclk);
|
||||
|
||||
pm_runtime_put_sync(pdata->dev);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
|
||||
.attach = ti_sn_bridge_attach,
|
||||
.pre_enable = ti_sn_bridge_pre_enable,
|
||||
.enable = ti_sn_bridge_enable,
|
||||
.disable = ti_sn_bridge_disable,
|
||||
.post_disable = ti_sn_bridge_post_disable,
|
||||
};
|
||||
|
||||
static struct ti_sn_bridge *aux_to_ti_sn_bridge(struct drm_dp_aux *aux)
|
||||
{
|
||||
return container_of(aux, struct ti_sn_bridge, aux);
|
||||
}
|
||||
|
||||
static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux,
|
||||
struct drm_dp_aux_msg *msg)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = aux_to_ti_sn_bridge(aux);
|
||||
u32 request = msg->request & ~DP_AUX_I2C_MOT;
|
||||
u32 request_val = AUX_CMD_REQ(msg->request);
|
||||
u8 *buf = (u8 *)msg->buffer;
|
||||
unsigned int val;
|
||||
int ret, i;
|
||||
|
||||
if (msg->size > SN_AUX_MAX_PAYLOAD_BYTES)
|
||||
return -EINVAL;
|
||||
|
||||
switch (request) {
|
||||
case DP_AUX_NATIVE_WRITE:
|
||||
case DP_AUX_I2C_WRITE:
|
||||
case DP_AUX_NATIVE_READ:
|
||||
case DP_AUX_I2C_READ:
|
||||
regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regmap_write(pdata->regmap, SN_AUX_ADDR_19_16_REG,
|
||||
(msg->address >> 16) & 0xF);
|
||||
regmap_write(pdata->regmap, SN_AUX_ADDR_15_8_REG,
|
||||
(msg->address >> 8) & 0xFF);
|
||||
regmap_write(pdata->regmap, SN_AUX_ADDR_7_0_REG, msg->address & 0xFF);
|
||||
|
||||
regmap_write(pdata->regmap, SN_AUX_LENGTH_REG, msg->size);
|
||||
|
||||
if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE) {
|
||||
for (i = 0; i < msg->size; i++)
|
||||
regmap_write(pdata->regmap, SN_AUX_WDATA_REG(i),
|
||||
buf[i]);
|
||||
}
|
||||
|
||||
regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val | AUX_CMD_SEND);
|
||||
|
||||
ret = regmap_read_poll_timeout(pdata->regmap, SN_AUX_CMD_REG, val,
|
||||
!(val & AUX_CMD_SEND), 200,
|
||||
50 * 1000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
else if ((val & AUX_IRQ_STATUS_NAT_I2C_FAIL)
|
||||
|| (val & AUX_IRQ_STATUS_AUX_RPLY_TOUT)
|
||||
|| (val & AUX_IRQ_STATUS_AUX_SHORT))
|
||||
return -ENXIO;
|
||||
|
||||
if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE)
|
||||
return msg->size;
|
||||
|
||||
for (i = 0; i < msg->size; i++) {
|
||||
unsigned int val;
|
||||
ret = regmap_read(pdata->regmap, SN_AUX_RDATA_REG(i),
|
||||
&val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
WARN_ON(val & ~0xFF);
|
||||
buf[i] = (u8)(val & 0xFF);
|
||||
}
|
||||
|
||||
return msg->size;
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
|
||||
{
|
||||
struct device_node *np = pdata->dev->of_node;
|
||||
|
||||
pdata->host_node = of_graph_get_remote_node(np, 0, 0);
|
||||
|
||||
if (!pdata->host_node) {
|
||||
DRM_ERROR("remote dsi host node not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct ti_sn_bridge *pdata;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||
DRM_ERROR("device doesn't support I2C\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pdata = devm_kzalloc(&client->dev, sizeof(struct ti_sn_bridge),
|
||||
GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
pdata->regmap = devm_regmap_init_i2c(client,
|
||||
&ti_sn_bridge_regmap_config);
|
||||
if (IS_ERR(pdata->regmap)) {
|
||||
DRM_ERROR("regmap i2c init failed\n");
|
||||
return PTR_ERR(pdata->regmap);
|
||||
}
|
||||
|
||||
pdata->dev = &client->dev;
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(pdata->dev->of_node, 1, 0,
|
||||
&pdata->panel, NULL);
|
||||
if (ret) {
|
||||
DRM_ERROR("could not find any panel node\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&client->dev, pdata);
|
||||
|
||||
pdata->enable_gpio = devm_gpiod_get(pdata->dev, "enable",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pdata->enable_gpio)) {
|
||||
DRM_ERROR("failed to get enable gpio from DT\n");
|
||||
ret = PTR_ERR(pdata->enable_gpio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = ti_sn_bridge_parse_regulators(pdata);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to parse regulators\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pdata->refclk = devm_clk_get(pdata->dev, "refclk");
|
||||
if (IS_ERR(pdata->refclk)) {
|
||||
ret = PTR_ERR(pdata->refclk);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
return ret;
|
||||
DRM_DEBUG_KMS("refclk not found\n");
|
||||
pdata->refclk = NULL;
|
||||
}
|
||||
|
||||
ret = ti_sn_bridge_parse_dsi_host(pdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_runtime_enable(pdata->dev);
|
||||
|
||||
i2c_set_clientdata(client, pdata);
|
||||
|
||||
pdata->aux.name = "ti-sn65dsi86-aux";
|
||||
pdata->aux.dev = pdata->dev;
|
||||
pdata->aux.transfer = ti_sn_aux_transfer;
|
||||
drm_dp_aux_register(&pdata->aux);
|
||||
|
||||
pdata->bridge.funcs = &ti_sn_bridge_funcs;
|
||||
pdata->bridge.of_node = client->dev.of_node;
|
||||
|
||||
drm_bridge_add(&pdata->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_remove(struct i2c_client *client)
|
||||
{
|
||||
struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
|
||||
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
|
||||
of_node_put(pdata->host_node);
|
||||
|
||||
pm_runtime_disable(pdata->dev);
|
||||
|
||||
if (pdata->dsi) {
|
||||
mipi_dsi_detach(pdata->dsi);
|
||||
mipi_dsi_device_unregister(pdata->dsi);
|
||||
}
|
||||
|
||||
drm_bridge_remove(&pdata->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_device_id ti_sn_bridge_id[] = {
|
||||
{ "ti,sn65dsi86", 0},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
|
||||
|
||||
static const struct of_device_id ti_sn_bridge_match_table[] = {
|
||||
{.compatible = "ti,sn65dsi86"},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
|
||||
|
||||
static struct i2c_driver ti_sn_bridge_driver = {
|
||||
.driver = {
|
||||
.name = "ti_sn65dsi86",
|
||||
.of_match_table = ti_sn_bridge_match_table,
|
||||
.pm = &ti_sn_bridge_pm_ops,
|
||||
},
|
||||
.probe = ti_sn_bridge_probe,
|
||||
.remove = ti_sn_bridge_remove,
|
||||
.id_table = ti_sn_bridge_id,
|
||||
};
|
||||
module_i2c_driver(ti_sn_bridge_driver);
|
||||
|
||||
MODULE_AUTHOR("Sandeep Panda <spanda@codeaurora.org>");
|
||||
MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -16,11 +16,11 @@
|
||||
#include "cirrus_drv.h"
|
||||
|
||||
int cirrus_modeset = -1;
|
||||
int cirrus_bpp = 24;
|
||||
int cirrus_bpp = 16;
|
||||
|
||||
MODULE_PARM_DESC(modeset, "Disable/Enable modesetting");
|
||||
module_param_named(modeset, cirrus_modeset, int, 0400);
|
||||
MODULE_PARM_DESC(bpp, "Max bits-per-pixel (default:24)");
|
||||
MODULE_PARM_DESC(bpp, "Max bits-per-pixel (default:16)");
|
||||
module_param_named(bpp, cirrus_bpp, int, 0400);
|
||||
|
||||
/*
|
||||
@ -42,33 +42,12 @@ static const struct pci_device_id pciidlist[] = {
|
||||
};
|
||||
|
||||
|
||||
static int cirrus_kick_out_firmware_fb(struct pci_dev *pdev)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
bool primary = false;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return -ENOMEM;
|
||||
|
||||
ap->ranges[0].base = pci_resource_start(pdev, 0);
|
||||
ap->ranges[0].size = pci_resource_len(pdev, 0);
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
|
||||
#endif
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "cirrusdrmfb", primary);
|
||||
kfree(ap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cirrus_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cirrus_kick_out_firmware_fb(pdev);
|
||||
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, 0, "cirrusdrmfb");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -146,7 +146,7 @@ struct cirrus_device {
|
||||
|
||||
struct cirrus_fbdev {
|
||||
struct drm_fb_helper helper;
|
||||
struct drm_framebuffer gfb;
|
||||
struct drm_framebuffer *gfb;
|
||||
void *sysram;
|
||||
int size;
|
||||
int x1, y1, x2, y2; /* dirty rect */
|
||||
|
@ -22,14 +22,14 @@ static void cirrus_dirty_update(struct cirrus_fbdev *afbdev,
|
||||
struct drm_gem_object *obj;
|
||||
struct cirrus_bo *bo;
|
||||
int src_offset, dst_offset;
|
||||
int bpp = afbdev->gfb.format->cpp[0];
|
||||
int bpp = afbdev->gfb->format->cpp[0];
|
||||
int ret = -EBUSY;
|
||||
bool unmap = false;
|
||||
bool store_for_later = false;
|
||||
int x2, y2;
|
||||
unsigned long flags;
|
||||
|
||||
obj = afbdev->gfb.obj[0];
|
||||
obj = afbdev->gfb->obj[0];
|
||||
bo = gem_to_cirrus_bo(obj);
|
||||
|
||||
/*
|
||||
@ -82,7 +82,7 @@ static void cirrus_dirty_update(struct cirrus_fbdev *afbdev,
|
||||
}
|
||||
for (i = y; i < y + height; i++) {
|
||||
/* assume equal stride for now */
|
||||
src_offset = dst_offset = i * afbdev->gfb.pitches[0] + (x * bpp);
|
||||
src_offset = dst_offset = i * afbdev->gfb->pitches[0] + (x * bpp);
|
||||
memcpy_toio(bo->kmap.virtual + src_offset, afbdev->sysram + src_offset, width * bpp);
|
||||
|
||||
}
|
||||
@ -192,23 +192,26 @@ static int cirrusfb_create(struct drm_fb_helper *helper,
|
||||
return -ENOMEM;
|
||||
|
||||
info = drm_fb_helper_alloc_fbi(helper);
|
||||
if (IS_ERR(info))
|
||||
return PTR_ERR(info);
|
||||
if (IS_ERR(info)) {
|
||||
ret = PTR_ERR(info);
|
||||
goto err_vfree;
|
||||
}
|
||||
|
||||
info->par = gfbdev;
|
||||
|
||||
ret = cirrus_framebuffer_init(cdev->dev, &gfbdev->gfb, &mode_cmd, gobj);
|
||||
fb = kzalloc(sizeof(*fb), GFP_KERNEL);
|
||||
if (!fb) {
|
||||
ret = -ENOMEM;
|
||||
goto err_drm_gem_object_put_unlocked;
|
||||
}
|
||||
|
||||
ret = cirrus_framebuffer_init(cdev->dev, fb, &mode_cmd, gobj);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_kfree;
|
||||
|
||||
gfbdev->sysram = sysram;
|
||||
gfbdev->size = size;
|
||||
|
||||
fb = &gfbdev->gfb;
|
||||
if (!fb) {
|
||||
DRM_INFO("fb is NULL\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
gfbdev->gfb = fb;
|
||||
|
||||
/* setup helper */
|
||||
gfbdev->helper.fb = fb;
|
||||
@ -241,24 +244,27 @@ static int cirrusfb_create(struct drm_fb_helper *helper,
|
||||
DRM_INFO(" pitch is %d\n", fb->pitches[0]);
|
||||
|
||||
return 0;
|
||||
|
||||
err_kfree:
|
||||
kfree(fb);
|
||||
err_drm_gem_object_put_unlocked:
|
||||
drm_gem_object_put_unlocked(gobj);
|
||||
err_vfree:
|
||||
vfree(sysram);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cirrus_fbdev_destroy(struct drm_device *dev,
|
||||
struct cirrus_fbdev *gfbdev)
|
||||
{
|
||||
struct drm_framebuffer *gfb = &gfbdev->gfb;
|
||||
struct drm_framebuffer *gfb = gfbdev->gfb;
|
||||
|
||||
drm_fb_helper_unregister_fbi(&gfbdev->helper);
|
||||
|
||||
if (gfb->obj[0]) {
|
||||
drm_gem_object_put_unlocked(gfb->obj[0]);
|
||||
gfb->obj[0] = NULL;
|
||||
}
|
||||
|
||||
vfree(gfbdev->sysram);
|
||||
drm_fb_helper_fini(&gfbdev->helper);
|
||||
drm_framebuffer_unregister_private(gfb);
|
||||
drm_framebuffer_cleanup(gfb);
|
||||
if (gfb)
|
||||
drm_framebuffer_put(gfb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -271,7 +277,6 @@ int cirrus_fbdev_init(struct cirrus_device *cdev)
|
||||
{
|
||||
struct cirrus_fbdev *gfbdev;
|
||||
int ret;
|
||||
int bpp_sel = 24;
|
||||
|
||||
/*bpp_sel = 8;*/
|
||||
gfbdev = kzalloc(sizeof(struct cirrus_fbdev), GFP_KERNEL);
|
||||
@ -296,7 +301,7 @@ int cirrus_fbdev_init(struct cirrus_device *cdev)
|
||||
/* disable all the possible outputs/crtcs before entering KMS mode */
|
||||
drm_helper_disable_unused_functions(cdev->dev);
|
||||
|
||||
return drm_fb_helper_initial_config(&gfbdev->helper, bpp_sel);
|
||||
return drm_fb_helper_initial_config(&gfbdev->helper, cirrus_bpp);
|
||||
}
|
||||
|
||||
void cirrus_fbdev_fini(struct cirrus_device *cdev)
|
||||
|
@ -269,7 +269,7 @@ static void cirrus_bo_unref(struct cirrus_bo **bo)
|
||||
return;
|
||||
|
||||
tbo = &((*bo)->bo);
|
||||
ttm_bo_unref(&tbo);
|
||||
ttm_bo_put(tbo);
|
||||
*bo = NULL;
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ static int cirrus_crtc_do_set_base(struct drm_crtc *crtc,
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (&cdev->mode_info.gfbdev->gfb == crtc->primary->fb) {
|
||||
if (cdev->mode_info.gfbdev->gfb == crtc->primary->fb) {
|
||||
/* if pushing console in kmap it */
|
||||
ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap);
|
||||
if (ret)
|
||||
@ -512,7 +512,7 @@ int cirrus_modeset_init(struct cirrus_device *cdev)
|
||||
cdev->dev->mode_config.max_height = CIRRUS_MAX_FB_HEIGHT;
|
||||
|
||||
cdev->dev->mode_config.fb_base = cdev->mc.vram_base;
|
||||
cdev->dev->mode_config.preferred_depth = 24;
|
||||
cdev->dev->mode_config.preferred_depth = cirrus_bpp;
|
||||
/* don't prefer a shadow on virt GPU */
|
||||
cdev->dev->mode_config.prefer_shadow = 0;
|
||||
|
||||
|
@ -895,6 +895,8 @@ static int drm_atomic_plane_set_property(struct drm_plane *plane,
|
||||
state->src_h = val;
|
||||
} else if (property == plane->alpha_property) {
|
||||
state->alpha = val;
|
||||
} else if (property == plane->blend_mode_property) {
|
||||
state->pixel_blend_mode = val;
|
||||
} else if (property == plane->rotation_property) {
|
||||
if (!is_power_of_2(val & DRM_MODE_ROTATE_MASK)) {
|
||||
DRM_DEBUG_ATOMIC("[PLANE:%d:%s] bad rotation bitmask: 0x%llx\n",
|
||||
@ -968,6 +970,8 @@ drm_atomic_plane_get_property(struct drm_plane *plane,
|
||||
*val = state->src_h;
|
||||
} else if (property == plane->alpha_property) {
|
||||
*val = state->alpha;
|
||||
} else if (property == plane->blend_mode_property) {
|
||||
*val = state->pixel_blend_mode;
|
||||
} else if (property == plane->rotation_property) {
|
||||
*val = state->rotation;
|
||||
} else if (property == plane->zpos_property) {
|
||||
|
@ -3554,6 +3554,29 @@ void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_plane_reset - resets planes state to default values
|
||||
* @plane: plane object, must not be NULL
|
||||
* @state: atomic plane state, must not be NULL
|
||||
*
|
||||
* Initializes plane state to default. This is useful for drivers that subclass
|
||||
* the plane state.
|
||||
*/
|
||||
void __drm_atomic_helper_plane_reset(struct drm_plane *plane,
|
||||
struct drm_plane_state *state)
|
||||
{
|
||||
state->plane = plane;
|
||||
state->rotation = DRM_MODE_ROTATE_0;
|
||||
|
||||
/* Reset the alpha value to fully opaque if it matters */
|
||||
if (plane->alpha_property)
|
||||
state->alpha = plane->alpha_property->values[1];
|
||||
state->pixel_blend_mode = DRM_MODE_BLEND_PREMULTI;
|
||||
|
||||
plane->state = state;
|
||||
}
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_plane_reset);
|
||||
|
||||
/**
|
||||
* drm_atomic_helper_plane_reset - default &drm_plane_funcs.reset hook for planes
|
||||
* @plane: drm plane
|
||||
@ -3568,15 +3591,8 @@ void drm_atomic_helper_plane_reset(struct drm_plane *plane)
|
||||
|
||||
kfree(plane->state);
|
||||
plane->state = kzalloc(sizeof(*plane->state), GFP_KERNEL);
|
||||
|
||||
if (plane->state) {
|
||||
plane->state->plane = plane;
|
||||
plane->state->rotation = DRM_MODE_ROTATE_0;
|
||||
|
||||
/* Reset the alpha value to fully opaque if it matters */
|
||||
if (plane->alpha_property)
|
||||
plane->state->alpha = plane->alpha_property->values[1];
|
||||
}
|
||||
if (plane->state)
|
||||
__drm_atomic_helper_plane_reset(plane, plane->state);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_plane_reset);
|
||||
|
||||
|
@ -107,6 +107,52 @@
|
||||
* planes. Without this property the primary plane is always below the cursor
|
||||
* plane, and ordering between all other planes is undefined.
|
||||
*
|
||||
* pixel blend mode:
|
||||
* Pixel blend mode is set up with drm_plane_create_blend_mode_property().
|
||||
* It adds a blend mode for alpha blending equation selection, describing
|
||||
* how the pixels from the current plane are composited with the
|
||||
* background.
|
||||
*
|
||||
* Three alpha blending equations are defined:
|
||||
*
|
||||
* "None":
|
||||
* Blend formula that ignores the pixel alpha::
|
||||
*
|
||||
* out.rgb = plane_alpha * fg.rgb +
|
||||
* (1 - plane_alpha) * bg.rgb
|
||||
*
|
||||
* "Pre-multiplied":
|
||||
* Blend formula that assumes the pixel color values
|
||||
* have been already pre-multiplied with the alpha
|
||||
* channel values::
|
||||
*
|
||||
* out.rgb = plane_alpha * fg.rgb +
|
||||
* (1 - (plane_alpha * fg.alpha)) * bg.rgb
|
||||
*
|
||||
* "Coverage":
|
||||
* Blend formula that assumes the pixel color values have not
|
||||
* been pre-multiplied and will do so when blending them to the
|
||||
* background color values::
|
||||
*
|
||||
* out.rgb = plane_alpha * fg.alpha * fg.rgb +
|
||||
* (1 - (plane_alpha * fg.alpha)) * bg.rgb
|
||||
*
|
||||
* Using the following symbols:
|
||||
*
|
||||
* "fg.rgb":
|
||||
* Each of the RGB component values from the plane's pixel
|
||||
* "fg.alpha":
|
||||
* Alpha component value from the plane's pixel. If the plane's
|
||||
* pixel format has no alpha component, then this is assumed to be
|
||||
* 1.0. In these cases, this property has no effect, as all three
|
||||
* equations become equivalent.
|
||||
* "bg.rgb":
|
||||
* Each of the RGB component values from the background
|
||||
* "plane_alpha":
|
||||
* Plane alpha value set by the plane "alpha" property. If the
|
||||
* plane does not expose the "alpha" property, then this is
|
||||
* assumed to be 1.0
|
||||
*
|
||||
* Note that all the property extensions described here apply either to the
|
||||
* plane or the CRTC (e.g. for the background color, which currently is not
|
||||
* exposed and assumed to be black).
|
||||
@ -448,3 +494,80 @@ int drm_atomic_normalize_zpos(struct drm_device *dev,
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_normalize_zpos);
|
||||
|
||||
/**
|
||||
* drm_plane_create_blend_mode_property - create a new blend mode property
|
||||
* @plane: drm plane
|
||||
* @supported_modes: bitmask of supported modes, must include
|
||||
* BIT(DRM_MODE_BLEND_PREMULTI). Current DRM assumption is
|
||||
* that alpha is premultiplied, and old userspace can break if
|
||||
* the property defaults to anything else.
|
||||
*
|
||||
* This creates a new property describing the blend mode.
|
||||
*
|
||||
* The property exposed to userspace is an enumeration property (see
|
||||
* drm_property_create_enum()) called "pixel blend mode" and has the
|
||||
* following enumeration values:
|
||||
*
|
||||
* "None":
|
||||
* Blend formula that ignores the pixel alpha.
|
||||
*
|
||||
* "Pre-multiplied":
|
||||
* Blend formula that assumes the pixel color values have been already
|
||||
* pre-multiplied with the alpha channel values.
|
||||
*
|
||||
* "Coverage":
|
||||
* Blend formula that assumes the pixel color values have not been
|
||||
* pre-multiplied and will do so when blending them to the background color
|
||||
* values.
|
||||
*
|
||||
* RETURNS:
|
||||
* Zero for success or -errno
|
||||
*/
|
||||
int drm_plane_create_blend_mode_property(struct drm_plane *plane,
|
||||
unsigned int supported_modes)
|
||||
{
|
||||
struct drm_device *dev = plane->dev;
|
||||
struct drm_property *prop;
|
||||
static const struct drm_prop_enum_list props[] = {
|
||||
{ DRM_MODE_BLEND_PIXEL_NONE, "None" },
|
||||
{ DRM_MODE_BLEND_PREMULTI, "Pre-multiplied" },
|
||||
{ DRM_MODE_BLEND_COVERAGE, "Coverage" },
|
||||
};
|
||||
unsigned int valid_mode_mask = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
|
||||
BIT(DRM_MODE_BLEND_PREMULTI) |
|
||||
BIT(DRM_MODE_BLEND_COVERAGE);
|
||||
int i;
|
||||
|
||||
if (WARN_ON((supported_modes & ~valid_mode_mask) ||
|
||||
((supported_modes & BIT(DRM_MODE_BLEND_PREMULTI)) == 0)))
|
||||
return -EINVAL;
|
||||
|
||||
prop = drm_property_create(dev, DRM_MODE_PROP_ENUM,
|
||||
"pixel blend mode",
|
||||
hweight32(supported_modes));
|
||||
if (!prop)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(props); i++) {
|
||||
int ret;
|
||||
|
||||
if (!(BIT(props[i].type) & supported_modes))
|
||||
continue;
|
||||
|
||||
ret = drm_property_add_enum(prop, props[i].type,
|
||||
props[i].name);
|
||||
|
||||
if (ret) {
|
||||
drm_property_destroy(dev, prop);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
drm_object_attach_property(&plane->base, prop, DRM_MODE_BLEND_PREMULTI);
|
||||
plane->blend_mode_property = prop;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_plane_create_blend_mode_property);
|
||||
|
@ -68,8 +68,29 @@ static int crc_control_show(struct seq_file *m, void *data)
|
||||
{
|
||||
struct drm_crtc *crtc = m->private;
|
||||
|
||||
seq_printf(m, "%s\n", crtc->crc.source);
|
||||
if (crtc->funcs->get_crc_sources) {
|
||||
size_t count;
|
||||
const char *const *sources = crtc->funcs->get_crc_sources(crtc,
|
||||
&count);
|
||||
size_t values_cnt;
|
||||
int i;
|
||||
|
||||
if (count == 0 || !sources)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
if (!crtc->funcs->verify_crc_source(crtc, sources[i],
|
||||
&values_cnt)) {
|
||||
if (strcmp(sources[i], crtc->crc.source))
|
||||
seq_printf(m, "%s\n", sources[i]);
|
||||
else
|
||||
seq_printf(m, "%s*\n", sources[i]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
out:
|
||||
seq_printf(m, "%s*\n", crtc->crc.source);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -87,6 +108,8 @@ static ssize_t crc_control_write(struct file *file, const char __user *ubuf,
|
||||
struct drm_crtc *crtc = m->private;
|
||||
struct drm_crtc_crc *crc = &crtc->crc;
|
||||
char *source;
|
||||
size_t values_cnt;
|
||||
int ret;
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
@ -104,6 +127,10 @@ static ssize_t crc_control_write(struct file *file, const char __user *ubuf,
|
||||
if (source[len] == '\n')
|
||||
source[len] = '\0';
|
||||
|
||||
ret = crtc->funcs->verify_crc_source(crtc, source, &values_cnt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spin_lock_irq(&crc->lock);
|
||||
|
||||
if (crc->opened) {
|
||||
@ -168,57 +195,41 @@ static int crtc_crc_open(struct inode *inode, struct file *filep)
|
||||
return ret;
|
||||
}
|
||||
|
||||
spin_lock_irq(&crc->lock);
|
||||
if (!crc->opened)
|
||||
crc->opened = true;
|
||||
else
|
||||
ret = -EBUSY;
|
||||
spin_unlock_irq(&crc->lock);
|
||||
|
||||
ret = crtc->funcs->verify_crc_source(crtc, crc->source, &values_cnt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = crtc->funcs->set_crc_source(crtc, crc->source, &values_cnt);
|
||||
if (WARN_ON(values_cnt > DRM_MAX_CRC_NR))
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON(values_cnt == 0))
|
||||
return -EINVAL;
|
||||
|
||||
entries = kcalloc(DRM_CRC_ENTRIES_NR, sizeof(*entries), GFP_KERNEL);
|
||||
if (!entries)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irq(&crc->lock);
|
||||
if (!crc->opened) {
|
||||
crc->opened = true;
|
||||
crc->entries = entries;
|
||||
crc->values_cnt = values_cnt;
|
||||
} else {
|
||||
ret = -EBUSY;
|
||||
}
|
||||
spin_unlock_irq(&crc->lock);
|
||||
|
||||
if (ret) {
|
||||
kfree(entries);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = crtc->funcs->set_crc_source(crtc, crc->source);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
if (WARN_ON(values_cnt > DRM_MAX_CRC_NR)) {
|
||||
ret = -EINVAL;
|
||||
goto err_disable;
|
||||
}
|
||||
|
||||
if (WARN_ON(values_cnt == 0)) {
|
||||
ret = -EINVAL;
|
||||
goto err_disable;
|
||||
}
|
||||
|
||||
entries = kcalloc(DRM_CRC_ENTRIES_NR, sizeof(*entries), GFP_KERNEL);
|
||||
if (!entries) {
|
||||
ret = -ENOMEM;
|
||||
goto err_disable;
|
||||
}
|
||||
|
||||
spin_lock_irq(&crc->lock);
|
||||
crc->entries = entries;
|
||||
crc->values_cnt = values_cnt;
|
||||
|
||||
/*
|
||||
* Only return once we got a first frame, so userspace doesn't have to
|
||||
* guess when this particular piece of HW will be ready to start
|
||||
* generating CRCs.
|
||||
*/
|
||||
ret = wait_event_interruptible_lock_irq(crc->wq,
|
||||
crtc_crc_data_count(crc),
|
||||
crc->lock);
|
||||
spin_unlock_irq(&crc->lock);
|
||||
|
||||
if (ret)
|
||||
goto err_disable;
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable:
|
||||
crtc->funcs->set_crc_source(crtc, NULL, &values_cnt);
|
||||
err:
|
||||
spin_lock_irq(&crc->lock);
|
||||
crtc_crc_cleanup(crc);
|
||||
@ -230,9 +241,8 @@ static int crtc_crc_release(struct inode *inode, struct file *filep)
|
||||
{
|
||||
struct drm_crtc *crtc = filep->f_inode->i_private;
|
||||
struct drm_crtc_crc *crc = &crtc->crc;
|
||||
size_t values_cnt;
|
||||
|
||||
crtc->funcs->set_crc_source(crtc, NULL, &values_cnt);
|
||||
crtc->funcs->set_crc_source(crtc, NULL);
|
||||
|
||||
spin_lock_irq(&crc->lock);
|
||||
crtc_crc_cleanup(crc);
|
||||
@ -338,7 +348,7 @@ int drm_debugfs_crtc_crc_add(struct drm_crtc *crtc)
|
||||
{
|
||||
struct dentry *crc_ent, *ent;
|
||||
|
||||
if (!crtc->funcs->set_crc_source)
|
||||
if (!crtc->funcs->set_crc_source || !crtc->funcs->verify_crc_source)
|
||||
return 0;
|
||||
|
||||
crc_ent = debugfs_create_dir("crc", crtc->debugfs_entry);
|
||||
|
@ -16,7 +16,9 @@
|
||||
* here. Quite a few active (mini-)DP-to-HDMI or USB-C-to-HDMI adapters
|
||||
* have a converter chip that supports CEC-Tunneling-over-AUX (usually the
|
||||
* Parade PS176), but they do not wire up the CEC pin, thus making CEC
|
||||
* useless.
|
||||
* useless. Note that MegaChips 2900-based adapters appear to have good
|
||||
* support for CEC tunneling. Those adapters that I have tested using
|
||||
* this chipset all have the CEC line connected.
|
||||
*
|
||||
* Sadly there is no way for this driver to know this. What happens is
|
||||
* that a /dev/cecX device is created that is isolated and unable to see
|
||||
@ -238,6 +240,10 @@ void drm_dp_cec_irq(struct drm_dp_aux *aux)
|
||||
u8 cec_irq;
|
||||
int ret;
|
||||
|
||||
/* No transfer function was set, so not a DP connector */
|
||||
if (!aux->transfer)
|
||||
return;
|
||||
|
||||
mutex_lock(&aux->cec.lock);
|
||||
if (!aux->cec.adap)
|
||||
goto unlock;
|
||||
@ -293,6 +299,10 @@ void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid)
|
||||
unsigned int num_las = 1;
|
||||
u8 cap;
|
||||
|
||||
/* No transfer function was set, so not a DP connector */
|
||||
if (!aux->transfer)
|
||||
return;
|
||||
|
||||
#ifndef CONFIG_MEDIA_CEC_RC
|
||||
/*
|
||||
* CEC_CAP_RC is part of CEC_CAP_DEFAULTS, but it is stripped by
|
||||
@ -361,6 +371,10 @@ EXPORT_SYMBOL(drm_dp_cec_set_edid);
|
||||
*/
|
||||
void drm_dp_cec_unset_edid(struct drm_dp_aux *aux)
|
||||
{
|
||||
/* No transfer function was set, so not a DP connector */
|
||||
if (!aux->transfer)
|
||||
return;
|
||||
|
||||
cancel_delayed_work_sync(&aux->cec.unregister_work);
|
||||
|
||||
mutex_lock(&aux->cec.lock);
|
||||
@ -404,6 +418,8 @@ void drm_dp_cec_register_connector(struct drm_dp_aux *aux, const char *name,
|
||||
struct device *parent)
|
||||
{
|
||||
WARN_ON(aux->cec.adap);
|
||||
if (WARN_ON(!aux->transfer))
|
||||
return;
|
||||
aux->cec.name = name;
|
||||
aux->cec.parent = parent;
|
||||
INIT_DELAYED_WORK(&aux->cec.unregister_work,
|
||||
|
@ -850,7 +850,8 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
|
||||
return ret;
|
||||
|
||||
case DP_AUX_I2C_REPLY_NACK:
|
||||
DRM_DEBUG_KMS("I2C nack (result=%d, size=%zu\n", ret, msg->size);
|
||||
DRM_DEBUG_KMS("I2C nack (result=%d, size=%zu)\n",
|
||||
ret, msg->size);
|
||||
aux->i2c_nack_count++;
|
||||
return -EREMOTEIO;
|
||||
|
||||
|
@ -439,6 +439,7 @@ static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx
|
||||
if (idx > raw->curlen)
|
||||
goto fail_len;
|
||||
repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx];
|
||||
idx++;
|
||||
if (idx > raw->curlen)
|
||||
goto fail_len;
|
||||
|
||||
|
@ -86,14 +86,21 @@ dma_addr_t drm_fb_cma_get_gem_addr(struct drm_framebuffer *fb,
|
||||
{
|
||||
struct drm_gem_cma_object *obj;
|
||||
dma_addr_t paddr;
|
||||
u8 h_div = 1, v_div = 1;
|
||||
|
||||
obj = drm_fb_cma_get_gem_obj(fb, plane);
|
||||
if (!obj)
|
||||
return 0;
|
||||
|
||||
paddr = obj->paddr + fb->offsets[plane];
|
||||
paddr += fb->format->cpp[plane] * (state->src_x >> 16);
|
||||
paddr += fb->pitches[plane] * (state->src_y >> 16);
|
||||
|
||||
if (plane > 0) {
|
||||
h_div = fb->format->hsub;
|
||||
v_div = fb->format->vsub;
|
||||
}
|
||||
|
||||
paddr += (fb->format->cpp[plane] * (state->src_x >> 16)) / h_div;
|
||||
paddr += (fb->pitches[plane] * (state->src_y >> 16)) / v_div;
|
||||
|
||||
return paddr;
|
||||
}
|
||||
@ -218,21 +225,6 @@ void drm_fbdev_cma_hotplug_event(struct drm_fbdev_cma *fbdev_cma)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_fbdev_cma_hotplug_event);
|
||||
|
||||
/**
|
||||
* drm_fbdev_cma_set_suspend - wrapper around drm_fb_helper_set_suspend
|
||||
* @fbdev_cma: The drm_fbdev_cma struct, may be NULL
|
||||
* @state: desired state, zero to resume, non-zero to suspend
|
||||
*
|
||||
* Calls drm_fb_helper_set_suspend, which is a wrapper around
|
||||
* fb_set_suspend implemented by fbdev core.
|
||||
*/
|
||||
void drm_fbdev_cma_set_suspend(struct drm_fbdev_cma *fbdev_cma, bool state)
|
||||
{
|
||||
if (fbdev_cma)
|
||||
drm_fb_helper_set_suspend(&fbdev_cma->fb_helper, state);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fbdev_cma_set_suspend);
|
||||
|
||||
/**
|
||||
* drm_fbdev_cma_set_suspend_unlocked - wrapper around
|
||||
* drm_fb_helper_set_suspend_unlocked
|
||||
|
@ -436,7 +436,7 @@ struct sg_table *drm_gem_cma_prime_get_sg_table(struct drm_gem_object *obj)
|
||||
|
||||
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
|
||||
if (!sgt)
|
||||
return NULL;
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = dma_get_sgtable(obj->dev->dev, sgt, cma_obj->vaddr,
|
||||
cma_obj->paddr, obj->size);
|
||||
@ -447,7 +447,7 @@ struct sg_table *drm_gem_cma_prime_get_sg_table(struct drm_gem_object *obj)
|
||||
|
||||
out:
|
||||
kfree(sgt);
|
||||
return NULL;
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gem_cma_prime_get_sg_table);
|
||||
|
||||
|
@ -152,7 +152,9 @@ EXPORT_SYMBOL(drm_panel_detach);
|
||||
*
|
||||
* Return: A pointer to the panel registered for the specified device tree
|
||||
* 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")
|
||||
|
@ -120,14 +120,6 @@ static int drm_syncobj_fence_get_or_add_callback(struct drm_syncobj *syncobj,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_syncobj_add_callback - adds a callback to syncobj::cb_list
|
||||
* @syncobj: Sync object to which to add the callback
|
||||
* @cb: Callback to add
|
||||
* @func: Func to use when initializing the drm_syncobj_cb struct
|
||||
*
|
||||
* This adds a callback to be called next time the fence is replaced
|
||||
*/
|
||||
void drm_syncobj_add_callback(struct drm_syncobj *syncobj,
|
||||
struct drm_syncobj_cb *cb,
|
||||
drm_syncobj_func_t func)
|
||||
@ -136,13 +128,7 @@ void drm_syncobj_add_callback(struct drm_syncobj *syncobj,
|
||||
drm_syncobj_add_callback_locked(syncobj, cb, func);
|
||||
spin_unlock(&syncobj->lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_syncobj_add_callback);
|
||||
|
||||
/**
|
||||
* drm_syncobj_add_callback - removes a callback to syncobj::cb_list
|
||||
* @syncobj: Sync object from which to remove the callback
|
||||
* @cb: Callback to remove
|
||||
*/
|
||||
void drm_syncobj_remove_callback(struct drm_syncobj *syncobj,
|
||||
struct drm_syncobj_cb *cb)
|
||||
{
|
||||
@ -150,7 +136,6 @@ void drm_syncobj_remove_callback(struct drm_syncobj *syncobj,
|
||||
list_del_init(&cb->node);
|
||||
spin_unlock(&syncobj->lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_syncobj_remove_callback);
|
||||
|
||||
/**
|
||||
* drm_syncobj_replace_fence - replace fence in a sync object.
|
||||
|
@ -873,8 +873,8 @@ static void send_vblank_event(struct drm_device *dev,
|
||||
* handler by calling drm_crtc_send_vblank_event() and make sure that there's no
|
||||
* possible race with the hardware committing the atomic update.
|
||||
*
|
||||
* Caller must hold a vblank reference for the event @e, which will be dropped
|
||||
* when the next vblank arrives.
|
||||
* Caller must hold a vblank reference for the event @e acquired by a
|
||||
* drm_crtc_vblank_get(), which will be dropped when the next vblank arrives.
|
||||
*/
|
||||
void drm_crtc_arm_vblank_event(struct drm_crtc *crtc,
|
||||
struct drm_pending_vblank_event *e)
|
||||
@ -1541,7 +1541,7 @@ int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
|
||||
if (vblwait->request.type &
|
||||
~(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK |
|
||||
_DRM_VBLANK_HIGH_CRTC_MASK)) {
|
||||
DRM_ERROR("Unsupported type value 0x%x, supported mask 0x%x\n",
|
||||
DRM_DEBUG("Unsupported type value 0x%x, supported mask 0x%x\n",
|
||||
vblwait->request.type,
|
||||
(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK |
|
||||
_DRM_VBLANK_HIGH_CRTC_MASK));
|
||||
|
@ -103,10 +103,7 @@ EXPORT_SYMBOL(drm_vma_offset_manager_init);
|
||||
*/
|
||||
void drm_vma_offset_manager_destroy(struct drm_vma_offset_manager *mgr)
|
||||
{
|
||||
/* take the lock to protect against buggy drivers */
|
||||
write_lock(&mgr->vm_lock);
|
||||
drm_mm_takedown(&mgr->vm_addr_space_mm);
|
||||
write_unlock(&mgr->vm_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_vma_offset_manager_destroy);
|
||||
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include <linux/mm_types.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_global.h>
|
||||
#include <drm/gma_drm.h>
|
||||
#include "psb_reg.h"
|
||||
#include "psb_intel_drv.h"
|
||||
|
@ -45,11 +45,6 @@ static const char *i915_clflush_get_timeline_name(struct dma_fence *fence)
|
||||
return "clflush";
|
||||
}
|
||||
|
||||
static bool i915_clflush_enable_signaling(struct dma_fence *fence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void i915_clflush_release(struct dma_fence *fence)
|
||||
{
|
||||
struct clflush *clflush = container_of(fence, typeof(*clflush), dma);
|
||||
@ -63,8 +58,6 @@ static void i915_clflush_release(struct dma_fence *fence)
|
||||
static const struct dma_fence_ops i915_clflush_ops = {
|
||||
.get_driver_name = i915_clflush_get_driver_name,
|
||||
.get_timeline_name = i915_clflush_get_timeline_name,
|
||||
.enable_signaling = i915_clflush_enable_signaling,
|
||||
.wait = dma_fence_default_wait,
|
||||
.release = i915_clflush_release,
|
||||
};
|
||||
|
||||
|
@ -12896,6 +12896,8 @@ static const struct drm_crtc_funcs intel_crtc_funcs = {
|
||||
.atomic_duplicate_state = intel_crtc_duplicate_state,
|
||||
.atomic_destroy_state = intel_crtc_destroy_state,
|
||||
.set_crc_source = intel_crtc_set_crc_source,
|
||||
.verify_crc_source = intel_crtc_verify_crc_source,
|
||||
.get_crc_sources = intel_crtc_get_crc_sources,
|
||||
};
|
||||
|
||||
struct wait_rps_boost {
|
||||
|
@ -2172,12 +2172,17 @@ void lspcon_wait_pcon_mode(struct intel_lspcon *lspcon);
|
||||
|
||||
/* intel_pipe_crc.c */
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
int intel_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name,
|
||||
size_t *values_cnt);
|
||||
int intel_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name);
|
||||
int intel_crtc_verify_crc_source(struct drm_crtc *crtc,
|
||||
const char *source_name, size_t *values_cnt);
|
||||
const char *const *intel_crtc_get_crc_sources(struct drm_crtc *crtc,
|
||||
size_t *count);
|
||||
void intel_crtc_disable_pipe_crc(struct intel_crtc *crtc);
|
||||
void intel_crtc_enable_pipe_crc(struct intel_crtc *crtc);
|
||||
#else
|
||||
#define intel_crtc_set_crc_source NULL
|
||||
#define intel_crtc_verify_crc_source NULL
|
||||
#define intel_crtc_get_crc_sources NULL
|
||||
static inline void intel_crtc_disable_pipe_crc(struct intel_crtc *crtc)
|
||||
{
|
||||
}
|
||||
|
@ -468,8 +468,122 @@ void intel_display_crc_init(struct drm_i915_private *dev_priv)
|
||||
}
|
||||
}
|
||||
|
||||
int intel_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name,
|
||||
size_t *values_cnt)
|
||||
static int i8xx_crc_source_valid(struct drm_i915_private *dev_priv,
|
||||
const enum intel_pipe_crc_source source)
|
||||
{
|
||||
switch (source) {
|
||||
case INTEL_PIPE_CRC_SOURCE_PIPE:
|
||||
case INTEL_PIPE_CRC_SOURCE_NONE:
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int i9xx_crc_source_valid(struct drm_i915_private *dev_priv,
|
||||
const enum intel_pipe_crc_source source)
|
||||
{
|
||||
switch (source) {
|
||||
case INTEL_PIPE_CRC_SOURCE_PIPE:
|
||||
case INTEL_PIPE_CRC_SOURCE_TV:
|
||||
case INTEL_PIPE_CRC_SOURCE_DP_B:
|
||||
case INTEL_PIPE_CRC_SOURCE_DP_C:
|
||||
case INTEL_PIPE_CRC_SOURCE_DP_D:
|
||||
case INTEL_PIPE_CRC_SOURCE_NONE:
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int vlv_crc_source_valid(struct drm_i915_private *dev_priv,
|
||||
const enum intel_pipe_crc_source source)
|
||||
{
|
||||
switch (source) {
|
||||
case INTEL_PIPE_CRC_SOURCE_PIPE:
|
||||
case INTEL_PIPE_CRC_SOURCE_DP_B:
|
||||
case INTEL_PIPE_CRC_SOURCE_DP_C:
|
||||
case INTEL_PIPE_CRC_SOURCE_DP_D:
|
||||
case INTEL_PIPE_CRC_SOURCE_NONE:
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ilk_crc_source_valid(struct drm_i915_private *dev_priv,
|
||||
const enum intel_pipe_crc_source source)
|
||||
{
|
||||
switch (source) {
|
||||
case INTEL_PIPE_CRC_SOURCE_PIPE:
|
||||
case INTEL_PIPE_CRC_SOURCE_PLANE1:
|
||||
case INTEL_PIPE_CRC_SOURCE_PLANE2:
|
||||
case INTEL_PIPE_CRC_SOURCE_NONE:
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ivb_crc_source_valid(struct drm_i915_private *dev_priv,
|
||||
const enum intel_pipe_crc_source source)
|
||||
{
|
||||
switch (source) {
|
||||
case INTEL_PIPE_CRC_SOURCE_PIPE:
|
||||
case INTEL_PIPE_CRC_SOURCE_PLANE1:
|
||||
case INTEL_PIPE_CRC_SOURCE_PLANE2:
|
||||
case INTEL_PIPE_CRC_SOURCE_PF:
|
||||
case INTEL_PIPE_CRC_SOURCE_NONE:
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
intel_is_valid_crc_source(struct drm_i915_private *dev_priv,
|
||||
const enum intel_pipe_crc_source source)
|
||||
{
|
||||
if (IS_GEN2(dev_priv))
|
||||
return i8xx_crc_source_valid(dev_priv, source);
|
||||
else if (INTEL_GEN(dev_priv) < 5)
|
||||
return i9xx_crc_source_valid(dev_priv, source);
|
||||
else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
|
||||
return vlv_crc_source_valid(dev_priv, source);
|
||||
else if (IS_GEN5(dev_priv) || IS_GEN6(dev_priv))
|
||||
return ilk_crc_source_valid(dev_priv, source);
|
||||
else
|
||||
return ivb_crc_source_valid(dev_priv, source);
|
||||
}
|
||||
|
||||
const char *const *intel_crtc_get_crc_sources(struct drm_crtc *crtc,
|
||||
size_t *count)
|
||||
{
|
||||
*count = ARRAY_SIZE(pipe_crc_sources);
|
||||
return pipe_crc_sources;
|
||||
}
|
||||
|
||||
int intel_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
|
||||
size_t *values_cnt)
|
||||
{
|
||||
struct drm_i915_private *dev_priv = to_i915(crtc->dev);
|
||||
enum intel_pipe_crc_source source;
|
||||
|
||||
if (display_crc_ctl_parse_source(source_name, &source) < 0) {
|
||||
DRM_DEBUG_DRIVER("unknown source %s\n", source_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (source == INTEL_PIPE_CRC_SOURCE_AUTO ||
|
||||
intel_is_valid_crc_source(dev_priv, source) == 0) {
|
||||
*values_cnt = 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int intel_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name)
|
||||
{
|
||||
struct drm_i915_private *dev_priv = to_i915(crtc->dev);
|
||||
struct intel_pipe_crc *pipe_crc = &dev_priv->pipe_crc[crtc->index];
|
||||
@ -508,7 +622,6 @@ int intel_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name,
|
||||
}
|
||||
|
||||
pipe_crc->skipped = 0;
|
||||
*values_cnt = 5;
|
||||
|
||||
out:
|
||||
intel_display_power_put(dev_priv, power_domain);
|
||||
|
@ -611,17 +611,9 @@ static const char *mock_name(struct dma_fence *fence)
|
||||
return "mock";
|
||||
}
|
||||
|
||||
static bool mock_enable_signaling(struct dma_fence *fence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct dma_fence_ops mock_fence_ops = {
|
||||
.get_driver_name = mock_name,
|
||||
.get_timeline_name = mock_name,
|
||||
.enable_signaling = mock_enable_signaling,
|
||||
.wait = dma_fence_default_wait,
|
||||
.release = dma_fence_free,
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(mock_fence_lock);
|
||||
|
@ -281,16 +281,13 @@ static void ipu_plane_state_reset(struct drm_plane *plane)
|
||||
ipu_state = to_ipu_plane_state(plane->state);
|
||||
__drm_atomic_helper_plane_destroy_state(plane->state);
|
||||
kfree(ipu_state);
|
||||
plane->state = NULL;
|
||||
}
|
||||
|
||||
ipu_state = kzalloc(sizeof(*ipu_state), GFP_KERNEL);
|
||||
|
||||
if (ipu_state) {
|
||||
ipu_state->base.plane = plane;
|
||||
ipu_state->base.rotation = DRM_MODE_ROTATE_0;
|
||||
}
|
||||
|
||||
plane->state = &ipu_state->base;
|
||||
if (ipu_state)
|
||||
__drm_atomic_helper_plane_reset(plane, &ipu_state->base);
|
||||
}
|
||||
|
||||
static struct drm_plane_state *
|
||||
|
@ -42,29 +42,10 @@ static const struct pci_device_id pciidlist[] = {
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, pciidlist);
|
||||
|
||||
static void mgag200_kick_out_firmware_fb(struct pci_dev *pdev)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
bool primary = false;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return;
|
||||
|
||||
ap->ranges[0].base = pci_resource_start(pdev, 0);
|
||||
ap->ranges[0].size = pci_resource_len(pdev, 0);
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
|
||||
#endif
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "mgag200drmfb", primary);
|
||||
kfree(ap);
|
||||
}
|
||||
|
||||
|
||||
static int mga_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
{
|
||||
mgag200_kick_out_firmware_fb(pdev);
|
||||
drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, 0, "mgag200drmfb");
|
||||
|
||||
return drm_get_pci_dev(pdev, ent, &driver);
|
||||
}
|
||||
|
@ -124,20 +124,11 @@ static int mga_probe_vram(struct mga_device *mdev, void __iomem *mem)
|
||||
static int mga_vram_init(struct mga_device *mdev)
|
||||
{
|
||||
void __iomem *mem;
|
||||
struct apertures_struct *aper = alloc_apertures(1);
|
||||
if (!aper)
|
||||
return -ENOMEM;
|
||||
|
||||
/* BAR 0 is VRAM */
|
||||
mdev->mc.vram_base = pci_resource_start(mdev->dev->pdev, 0);
|
||||
mdev->mc.vram_window = pci_resource_len(mdev->dev->pdev, 0);
|
||||
|
||||
aper->ranges[0].base = mdev->mc.vram_base;
|
||||
aper->ranges[0].size = mdev->mc.vram_window;
|
||||
|
||||
drm_fb_helper_remove_conflicting_framebuffers(aper, "mgafb", true);
|
||||
kfree(aper);
|
||||
|
||||
if (!devm_request_mem_region(mdev->dev->dev, mdev->mc.vram_base, mdev->mc.vram_window,
|
||||
"mgadrmfb_vram")) {
|
||||
DRM_ERROR("can't reserve VRAM\n");
|
||||
|
@ -119,11 +119,6 @@ static const char *msm_fence_get_timeline_name(struct dma_fence *fence)
|
||||
return f->fctx->name;
|
||||
}
|
||||
|
||||
static bool msm_fence_enable_signaling(struct dma_fence *fence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool msm_fence_signaled(struct dma_fence *fence)
|
||||
{
|
||||
struct msm_fence *f = to_msm_fence(fence);
|
||||
@ -133,10 +128,7 @@ static bool msm_fence_signaled(struct dma_fence *fence)
|
||||
static const struct dma_fence_ops msm_fence_ops = {
|
||||
.get_driver_name = msm_fence_get_driver_name,
|
||||
.get_timeline_name = msm_fence_get_timeline_name,
|
||||
.enable_signaling = msm_fence_enable_signaling,
|
||||
.signaled = msm_fence_signaled,
|
||||
.wait = dma_fence_default_wait,
|
||||
.release = dma_fence_free,
|
||||
};
|
||||
|
||||
struct dma_fence *
|
||||
|
@ -400,8 +400,10 @@ nouveau_connector_destroy(struct drm_connector *connector)
|
||||
kfree(nv_connector->edid);
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
if (nv_connector->aux.transfer)
|
||||
if (nv_connector->aux.transfer) {
|
||||
drm_dp_cec_unregister_connector(&nv_connector->aux);
|
||||
drm_dp_aux_unregister(&nv_connector->aux);
|
||||
}
|
||||
kfree(connector);
|
||||
}
|
||||
|
||||
@ -608,6 +610,7 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
|
||||
|
||||
nouveau_connector_set_encoder(connector, nv_encoder);
|
||||
conn_status = connector_status_connected;
|
||||
drm_dp_cec_set_edid(&nv_connector->aux, nv_connector->edid);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -1108,11 +1111,14 @@ nouveau_connector_hotplug(struct nvif_notify *notify)
|
||||
|
||||
if (rep->mask & NVIF_NOTIFY_CONN_V0_IRQ) {
|
||||
NV_DEBUG(drm, "service %s\n", name);
|
||||
drm_dp_cec_irq(&nv_connector->aux);
|
||||
if ((nv_encoder = find_encoder(connector, DCB_OUTPUT_DP)))
|
||||
nv50_mstm_service(nv_encoder->dp.mstm);
|
||||
} else {
|
||||
bool plugged = (rep->mask != NVIF_NOTIFY_CONN_V0_UNPLUG);
|
||||
|
||||
if (!plugged)
|
||||
drm_dp_cec_unset_edid(&nv_connector->aux);
|
||||
NV_DEBUG(drm, "%splugged %s\n", plugged ? "" : "un", name);
|
||||
if ((nv_encoder = find_encoder(connector, DCB_OUTPUT_DP))) {
|
||||
if (!plugged)
|
||||
@ -1302,7 +1308,6 @@ nouveau_connector_create(struct drm_device *dev, int index)
|
||||
kfree(nv_connector);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
funcs = &nouveau_connector_funcs;
|
||||
break;
|
||||
default:
|
||||
@ -1356,6 +1361,14 @@ nouveau_connector_create(struct drm_device *dev, int index)
|
||||
break;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case DRM_MODE_CONNECTOR_DisplayPort:
|
||||
case DRM_MODE_CONNECTOR_eDP:
|
||||
drm_dp_cec_register_connector(&nv_connector->aux,
|
||||
connector->name, dev->dev);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = nvif_notify_init(&disp->disp.object, nouveau_connector_hotplug,
|
||||
true, NV04_DISP_NTFY_CONN,
|
||||
&(struct nvif_notify_conn_req_v0) {
|
||||
|
@ -526,6 +526,5 @@ static const struct dma_fence_ops nouveau_fence_ops_uevent = {
|
||||
.get_timeline_name = nouveau_fence_get_timeline_name,
|
||||
.enable_signaling = nouveau_fence_enable_signaling,
|
||||
.signaled = nouveau_fence_is_signaled,
|
||||
.wait = dma_fence_default_wait,
|
||||
.release = nouveau_fence_release
|
||||
};
|
||||
|
@ -37,7 +37,8 @@ static bool qxl_head_enabled(struct qxl_head *head)
|
||||
return head->width && head->height;
|
||||
}
|
||||
|
||||
static void qxl_alloc_client_monitors_config(struct qxl_device *qdev, unsigned count)
|
||||
static int qxl_alloc_client_monitors_config(struct qxl_device *qdev,
|
||||
unsigned int count)
|
||||
{
|
||||
if (qdev->client_monitors_config &&
|
||||
count > qdev->client_monitors_config->count) {
|
||||
@ -49,15 +50,17 @@ static void qxl_alloc_client_monitors_config(struct qxl_device *qdev, unsigned c
|
||||
sizeof(struct qxl_monitors_config) +
|
||||
sizeof(struct qxl_head) * count, GFP_KERNEL);
|
||||
if (!qdev->client_monitors_config)
|
||||
return;
|
||||
return -ENOMEM;
|
||||
}
|
||||
qdev->client_monitors_config->count = count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
MONITORS_CONFIG_MODIFIED,
|
||||
MONITORS_CONFIG_UNCHANGED,
|
||||
MONITORS_CONFIG_BAD_CRC,
|
||||
MONITORS_CONFIG_ERROR,
|
||||
};
|
||||
|
||||
static int qxl_display_copy_rom_client_monitors_config(struct qxl_device *qdev)
|
||||
@ -87,7 +90,10 @@ static int qxl_display_copy_rom_client_monitors_config(struct qxl_device *qdev)
|
||||
&& (num_monitors != qdev->client_monitors_config->count)) {
|
||||
status = MONITORS_CONFIG_MODIFIED;
|
||||
}
|
||||
qxl_alloc_client_monitors_config(qdev, num_monitors);
|
||||
if (qxl_alloc_client_monitors_config(qdev, num_monitors)) {
|
||||
status = MONITORS_CONFIG_ERROR;
|
||||
return status;
|
||||
}
|
||||
/* we copy max from the client but it isn't used */
|
||||
qdev->client_monitors_config->max_allowed =
|
||||
qdev->monitors_config->max_allowed;
|
||||
@ -161,6 +167,10 @@ void qxl_display_read_client_monitors_config(struct qxl_device *qdev)
|
||||
break;
|
||||
udelay(5);
|
||||
}
|
||||
if (status == MONITORS_CONFIG_ERROR) {
|
||||
DRM_DEBUG_KMS("ignoring client monitors config: error");
|
||||
return;
|
||||
}
|
||||
if (status == MONITORS_CONFIG_BAD_CRC) {
|
||||
DRM_DEBUG_KMS("ignoring client monitors config: bad crc");
|
||||
return;
|
||||
|
@ -119,7 +119,7 @@ qxl_pci_remove(struct pci_dev *pdev)
|
||||
|
||||
dev->dev_private = NULL;
|
||||
kfree(qdev);
|
||||
drm_dev_unref(dev);
|
||||
drm_dev_put(dev);
|
||||
}
|
||||
|
||||
static const struct file_operations qxl_fops = {
|
||||
@ -136,20 +136,11 @@ static int qxl_drm_freeze(struct drm_device *dev)
|
||||
{
|
||||
struct pci_dev *pdev = dev->pdev;
|
||||
struct qxl_device *qdev = dev->dev_private;
|
||||
struct drm_crtc *crtc;
|
||||
int ret;
|
||||
|
||||
drm_kms_helper_poll_disable(dev);
|
||||
|
||||
console_lock();
|
||||
qxl_fbdev_set_suspend(qdev, 1);
|
||||
console_unlock();
|
||||
|
||||
/* unpin the front buffers */
|
||||
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
|
||||
const struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
|
||||
if (crtc->enabled)
|
||||
(*crtc_funcs->disable)(crtc);
|
||||
}
|
||||
ret = drm_mode_config_helper_suspend(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
qxl_destroy_monitors_object(qdev);
|
||||
qxl_surf_evict(qdev);
|
||||
@ -175,14 +166,7 @@ static int qxl_drm_resume(struct drm_device *dev, bool thaw)
|
||||
}
|
||||
|
||||
qxl_create_monitors_object(qdev);
|
||||
drm_helper_resume_force_mode(dev);
|
||||
|
||||
console_lock();
|
||||
qxl_fbdev_set_suspend(qdev, 0);
|
||||
console_unlock();
|
||||
|
||||
drm_kms_helper_poll_enable(dev);
|
||||
return 0;
|
||||
return drm_mode_config_helper_resume(dev);
|
||||
}
|
||||
|
||||
static int qxl_pm_suspend(struct device *dev)
|
||||
|
@ -40,7 +40,7 @@ void qxl_gem_object_free(struct drm_gem_object *gobj)
|
||||
qxl_surface_evict(qdev, qobj, false);
|
||||
|
||||
tbo = &qobj->tbo;
|
||||
ttm_bo_unref(&tbo);
|
||||
ttm_bo_put(tbo);
|
||||
}
|
||||
|
||||
int qxl_gem_object_create(struct qxl_device *qdev, int size,
|
||||
|
@ -102,8 +102,10 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
int r, sb;
|
||||
|
||||
r = drm_dev_init(&qdev->ddev, drv, &pdev->dev);
|
||||
if (r)
|
||||
return r;
|
||||
if (r) {
|
||||
pr_err("Unable to init drm dev");
|
||||
goto error;
|
||||
}
|
||||
|
||||
qdev->ddev.pdev = pdev;
|
||||
pci_set_drvdata(pdev, &qdev->ddev);
|
||||
@ -121,6 +123,11 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
qdev->io_base = pci_resource_start(pdev, 3);
|
||||
|
||||
qdev->vram_mapping = io_mapping_create_wc(qdev->vram_base, pci_resource_len(pdev, 0));
|
||||
if (!qdev->vram_mapping) {
|
||||
pr_err("Unable to create vram_mapping");
|
||||
r = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pci_resource_len(pdev, 4) > 0) {
|
||||
/* 64bit surface bar present */
|
||||
@ -139,6 +146,11 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
qdev->surface_mapping =
|
||||
io_mapping_create_wc(qdev->surfaceram_base,
|
||||
qdev->surfaceram_size);
|
||||
if (!qdev->surface_mapping) {
|
||||
pr_err("Unable to create surface_mapping");
|
||||
r = -ENOMEM;
|
||||
goto vram_mapping_free;
|
||||
}
|
||||
}
|
||||
|
||||
DRM_DEBUG_KMS("qxl: vram %llx-%llx(%dM %dk), surface %llx-%llx(%dM %dk, %s)\n",
|
||||
@ -155,20 +167,29 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
qdev->rom = ioremap(qdev->rom_base, qdev->rom_size);
|
||||
if (!qdev->rom) {
|
||||
pr_err("Unable to ioremap ROM\n");
|
||||
return -ENOMEM;
|
||||
r = -ENOMEM;
|
||||
goto surface_mapping_free;
|
||||
}
|
||||
|
||||
qxl_check_device(qdev);
|
||||
if (!qxl_check_device(qdev)) {
|
||||
r = -ENODEV;
|
||||
goto surface_mapping_free;
|
||||
}
|
||||
|
||||
r = qxl_bo_init(qdev);
|
||||
if (r) {
|
||||
DRM_ERROR("bo init failed %d\n", r);
|
||||
return r;
|
||||
goto rom_unmap;
|
||||
}
|
||||
|
||||
qdev->ram_header = ioremap(qdev->vram_base +
|
||||
qdev->rom->ram_header_offset,
|
||||
sizeof(*qdev->ram_header));
|
||||
if (!qdev->ram_header) {
|
||||
DRM_ERROR("Unable to ioremap RAM header\n");
|
||||
r = -ENOMEM;
|
||||
goto bo_fini;
|
||||
}
|
||||
|
||||
qdev->command_ring = qxl_ring_create(&(qdev->ram_header->cmd_ring_hdr),
|
||||
sizeof(struct qxl_command),
|
||||
@ -176,6 +197,11 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
qdev->io_base + QXL_IO_NOTIFY_CMD,
|
||||
false,
|
||||
&qdev->display_event);
|
||||
if (!qdev->command_ring) {
|
||||
DRM_ERROR("Unable to create command ring\n");
|
||||
r = -ENOMEM;
|
||||
goto ram_header_unmap;
|
||||
}
|
||||
|
||||
qdev->cursor_ring = qxl_ring_create(
|
||||
&(qdev->ram_header->cursor_ring_hdr),
|
||||
@ -185,12 +211,23 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
false,
|
||||
&qdev->cursor_event);
|
||||
|
||||
if (!qdev->cursor_ring) {
|
||||
DRM_ERROR("Unable to create cursor ring\n");
|
||||
r = -ENOMEM;
|
||||
goto command_ring_free;
|
||||
}
|
||||
|
||||
qdev->release_ring = qxl_ring_create(
|
||||
&(qdev->ram_header->release_ring_hdr),
|
||||
sizeof(uint64_t),
|
||||
QXL_RELEASE_RING_SIZE, 0, true,
|
||||
NULL);
|
||||
|
||||
if (!qdev->release_ring) {
|
||||
DRM_ERROR("Unable to create release ring\n");
|
||||
r = -ENOMEM;
|
||||
goto cursor_ring_free;
|
||||
}
|
||||
/* TODO - slot initialization should happen on reset. where is our
|
||||
* reset handler? */
|
||||
qdev->n_mem_slots = qdev->rom->slots_end;
|
||||
@ -203,6 +240,12 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
kmalloc_array(qdev->n_mem_slots, sizeof(struct qxl_memslot),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!qdev->mem_slots) {
|
||||
DRM_ERROR("Unable to alloc mem slots\n");
|
||||
r = -ENOMEM;
|
||||
goto release_ring_free;
|
||||
}
|
||||
|
||||
idr_init(&qdev->release_idr);
|
||||
spin_lock_init(&qdev->release_idr_lock);
|
||||
spin_lock_init(&qdev->release_lock);
|
||||
@ -218,8 +261,10 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
|
||||
/* must initialize irq before first async io - slot creation */
|
||||
r = qxl_irq_init(qdev);
|
||||
if (r)
|
||||
return r;
|
||||
if (r) {
|
||||
DRM_ERROR("Unable to init qxl irq\n");
|
||||
goto mem_slots_free;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that virtual is surface0. We rely on the single ioremap done
|
||||
@ -243,6 +288,27 @@ int qxl_device_init(struct qxl_device *qdev,
|
||||
INIT_WORK(&qdev->gc_work, qxl_gc_work);
|
||||
|
||||
return 0;
|
||||
|
||||
mem_slots_free:
|
||||
kfree(qdev->mem_slots);
|
||||
release_ring_free:
|
||||
qxl_ring_free(qdev->release_ring);
|
||||
cursor_ring_free:
|
||||
qxl_ring_free(qdev->cursor_ring);
|
||||
command_ring_free:
|
||||
qxl_ring_free(qdev->command_ring);
|
||||
ram_header_unmap:
|
||||
iounmap(qdev->ram_header);
|
||||
bo_fini:
|
||||
qxl_bo_fini(qdev);
|
||||
rom_unmap:
|
||||
iounmap(qdev->rom);
|
||||
surface_mapping_free:
|
||||
io_mapping_free(qdev->surface_mapping);
|
||||
vram_mapping_free:
|
||||
io_mapping_free(qdev->vram_mapping);
|
||||
error:
|
||||
return r;
|
||||
}
|
||||
|
||||
void qxl_device_fini(struct qxl_device *qdev)
|
||||
|
@ -316,27 +316,6 @@ static struct drm_driver kms_driver;
|
||||
|
||||
bool radeon_device_is_virtual(void);
|
||||
|
||||
static int radeon_kick_out_firmware_fb(struct pci_dev *pdev)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
bool primary = false;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return -ENOMEM;
|
||||
|
||||
ap->ranges[0].base = pci_resource_start(pdev, 0);
|
||||
ap->ranges[0].size = pci_resource_len(pdev, 0);
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW;
|
||||
#endif
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "radeondrmfb", primary);
|
||||
kfree(ap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int radeon_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
@ -346,7 +325,7 @@ static int radeon_pci_probe(struct pci_dev *pdev,
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
/* Get rid of things like offb */
|
||||
ret = radeon_kick_out_firmware_fb(pdev);
|
||||
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, 0, "radeondrmfb");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -691,6 +691,65 @@ static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
|
||||
.atomic_disable = rcar_du_crtc_atomic_disable,
|
||||
};
|
||||
|
||||
static void rcar_du_crtc_crc_init(struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
||||
const char **sources;
|
||||
unsigned int count;
|
||||
int i = -1;
|
||||
|
||||
/* CRC available only on Gen3 HW. */
|
||||
if (rcdu->info->gen < 3)
|
||||
return;
|
||||
|
||||
/* Reserve 1 for "auto" source. */
|
||||
count = rcrtc->vsp->num_planes + 1;
|
||||
|
||||
sources = kmalloc_array(count, sizeof(*sources), GFP_KERNEL);
|
||||
if (!sources)
|
||||
return;
|
||||
|
||||
sources[0] = kstrdup("auto", GFP_KERNEL);
|
||||
if (!sources[0])
|
||||
goto error;
|
||||
|
||||
for (i = 0; i < rcrtc->vsp->num_planes; ++i) {
|
||||
struct drm_plane *plane = &rcrtc->vsp->planes[i].plane;
|
||||
char name[16];
|
||||
|
||||
sprintf(name, "plane%u", plane->base.id);
|
||||
sources[i + 1] = kstrdup(name, GFP_KERNEL);
|
||||
if (!sources[i + 1])
|
||||
goto error;
|
||||
}
|
||||
|
||||
rcrtc->sources = sources;
|
||||
rcrtc->sources_count = count;
|
||||
return;
|
||||
|
||||
error:
|
||||
while (i >= 0) {
|
||||
kfree(sources[i]);
|
||||
i--;
|
||||
}
|
||||
kfree(sources);
|
||||
}
|
||||
|
||||
static void rcar_du_crtc_crc_cleanup(struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (!rcrtc->sources)
|
||||
return;
|
||||
|
||||
for (i = 0; i < rcrtc->sources_count; i++)
|
||||
kfree(rcrtc->sources[i]);
|
||||
kfree(rcrtc->sources);
|
||||
|
||||
rcrtc->sources = NULL;
|
||||
rcrtc->sources_count = 0;
|
||||
}
|
||||
|
||||
static struct drm_crtc_state *
|
||||
rcar_du_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
|
||||
{
|
||||
@ -717,6 +776,15 @@ static void rcar_du_crtc_atomic_destroy_state(struct drm_crtc *crtc,
|
||||
kfree(to_rcar_crtc_state(state));
|
||||
}
|
||||
|
||||
static void rcar_du_crtc_cleanup(struct drm_crtc *crtc)
|
||||
{
|
||||
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
||||
|
||||
rcar_du_crtc_crc_cleanup(rcrtc);
|
||||
|
||||
return drm_crtc_cleanup(crtc);
|
||||
}
|
||||
|
||||
static void rcar_du_crtc_reset(struct drm_crtc *crtc)
|
||||
{
|
||||
struct rcar_du_crtc_state *state;
|
||||
@ -756,17 +824,11 @@ static void rcar_du_crtc_disable_vblank(struct drm_crtc *crtc)
|
||||
rcrtc->vblank_enable = false;
|
||||
}
|
||||
|
||||
static int rcar_du_crtc_set_crc_source(struct drm_crtc *crtc,
|
||||
const char *source_name,
|
||||
size_t *values_cnt)
|
||||
static int rcar_du_crtc_parse_crc_source(struct rcar_du_crtc *rcrtc,
|
||||
const char *source_name,
|
||||
enum vsp1_du_crc_source *source)
|
||||
{
|
||||
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
||||
struct drm_modeset_acquire_ctx ctx;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
struct drm_atomic_state *state;
|
||||
enum vsp1_du_crc_source source;
|
||||
unsigned int index = 0;
|
||||
unsigned int i;
|
||||
unsigned int index;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
@ -774,31 +836,72 @@ static int rcar_du_crtc_set_crc_source(struct drm_crtc *crtc,
|
||||
* CRC on an input plane (%u is the plane ID), and "auto" to compute the
|
||||
* CRC on the composer (VSP) output.
|
||||
*/
|
||||
|
||||
if (!source_name) {
|
||||
source = VSP1_DU_CRC_NONE;
|
||||
*source = VSP1_DU_CRC_NONE;
|
||||
return 0;
|
||||
} else if (!strcmp(source_name, "auto")) {
|
||||
source = VSP1_DU_CRC_OUTPUT;
|
||||
*source = VSP1_DU_CRC_OUTPUT;
|
||||
return 0;
|
||||
} else if (strstarts(source_name, "plane")) {
|
||||
source = VSP1_DU_CRC_PLANE;
|
||||
unsigned int i;
|
||||
|
||||
*source = VSP1_DU_CRC_PLANE;
|
||||
|
||||
ret = kstrtouint(source_name + strlen("plane"), 10, &index);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < rcrtc->vsp->num_planes; ++i) {
|
||||
if (index == rcrtc->vsp->planes[i].plane.base.id) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
if (index == rcrtc->vsp->planes[i].plane.base.id)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= rcrtc->vsp->num_planes)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int rcar_du_crtc_verify_crc_source(struct drm_crtc *crtc,
|
||||
const char *source_name,
|
||||
size_t *values_cnt)
|
||||
{
|
||||
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
||||
enum vsp1_du_crc_source source;
|
||||
|
||||
if (rcar_du_crtc_parse_crc_source(rcrtc, source_name, &source) < 0) {
|
||||
DRM_DEBUG_DRIVER("unknown source %s\n", source_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*values_cnt = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *const *rcar_du_crtc_get_crc_sources(struct drm_crtc *crtc,
|
||||
size_t *count)
|
||||
{
|
||||
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
||||
|
||||
*count = rcrtc->sources_count;
|
||||
return rcrtc->sources;
|
||||
}
|
||||
|
||||
static int rcar_du_crtc_set_crc_source(struct drm_crtc *crtc,
|
||||
const char *source_name)
|
||||
{
|
||||
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
||||
struct drm_modeset_acquire_ctx ctx;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
struct drm_atomic_state *state;
|
||||
enum vsp1_du_crc_source source;
|
||||
unsigned int index;
|
||||
int ret;
|
||||
|
||||
ret = rcar_du_crtc_parse_crc_source(rcrtc, source_name, &source);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
index = ret;
|
||||
|
||||
/* Perform an atomic commit to set the CRC source. */
|
||||
drm_modeset_acquire_init(&ctx, 0);
|
||||
@ -853,7 +956,7 @@ static const struct drm_crtc_funcs crtc_funcs_gen2 = {
|
||||
|
||||
static const struct drm_crtc_funcs crtc_funcs_gen3 = {
|
||||
.reset = rcar_du_crtc_reset,
|
||||
.destroy = drm_crtc_cleanup,
|
||||
.destroy = rcar_du_crtc_cleanup,
|
||||
.set_config = drm_atomic_helper_set_config,
|
||||
.page_flip = drm_atomic_helper_page_flip,
|
||||
.atomic_duplicate_state = rcar_du_crtc_atomic_duplicate_state,
|
||||
@ -861,6 +964,8 @@ static const struct drm_crtc_funcs crtc_funcs_gen3 = {
|
||||
.enable_vblank = rcar_du_crtc_enable_vblank,
|
||||
.disable_vblank = rcar_du_crtc_disable_vblank,
|
||||
.set_crc_source = rcar_du_crtc_set_crc_source,
|
||||
.verify_crc_source = rcar_du_crtc_verify_crc_source,
|
||||
.get_crc_sources = rcar_du_crtc_get_crc_sources,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@ -999,5 +1104,7 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
|
||||
return ret;
|
||||
}
|
||||
|
||||
rcar_du_crtc_crc_init(rcrtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -67,6 +67,9 @@ struct rcar_du_crtc {
|
||||
struct rcar_du_group *group;
|
||||
struct rcar_du_vsp *vsp;
|
||||
unsigned int vsp_pipe;
|
||||
|
||||
const char *const *sources;
|
||||
unsigned int sources_count;
|
||||
};
|
||||
|
||||
#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)
|
||||
|
@ -690,14 +690,12 @@ static void rcar_du_plane_reset(struct drm_plane *plane)
|
||||
if (state == NULL)
|
||||
return;
|
||||
|
||||
__drm_atomic_helper_plane_reset(plane, &state->state);
|
||||
|
||||
state->hwindex = -1;
|
||||
state->source = RCAR_DU_PLANE_MEMORY;
|
||||
state->colorkey = RCAR_DU_COLORKEY_NONE;
|
||||
state->state.zpos = plane->type == DRM_PLANE_TYPE_PRIMARY ? 0 : 1;
|
||||
|
||||
plane->state = &state->state;
|
||||
plane->state->alpha = DRM_BLEND_ALPHA_OPAQUE;
|
||||
plane->state->plane = plane;
|
||||
}
|
||||
|
||||
static int rcar_du_plane_atomic_set_property(struct drm_plane *plane,
|
||||
|
@ -346,11 +346,8 @@ static void rcar_du_vsp_plane_reset(struct drm_plane *plane)
|
||||
if (state == NULL)
|
||||
return;
|
||||
|
||||
state->state.alpha = DRM_BLEND_ALPHA_OPAQUE;
|
||||
__drm_atomic_helper_plane_reset(plane, &state->state);
|
||||
state->state.zpos = plane->type == DRM_PLANE_TYPE_PRIMARY ? 0 : 1;
|
||||
|
||||
plane->state = &state->state;
|
||||
plane->state->plane = plane;
|
||||
}
|
||||
|
||||
static const struct drm_plane_funcs rcar_du_vsp_plane_funcs = {
|
||||
|
@ -8,6 +8,7 @@ config DRM_ROCKCHIP
|
||||
select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
|
||||
select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
|
||||
select DRM_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
|
||||
select DRM_RGB if ROCKCHIP_RGB
|
||||
select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC
|
||||
help
|
||||
Choose this option if you have a Rockchip soc chipset.
|
||||
@ -23,7 +24,7 @@ config ROCKCHIP_ANALOGIX_DP
|
||||
help
|
||||
This selects support for Rockchip SoC specific extensions
|
||||
for the Analogix Core DP driver. If you want to enable DP
|
||||
on RK3288 based SoC, you should selet this option.
|
||||
on RK3288 or RK3399 based SoC, you should select this option.
|
||||
|
||||
config ROCKCHIP_CDN_DP
|
||||
bool "Rockchip cdn DP"
|
||||
@ -39,16 +40,16 @@ config ROCKCHIP_DW_HDMI
|
||||
help
|
||||
This selects support for Rockchip SoC specific extensions
|
||||
for the Synopsys DesignWare HDMI driver. If you want to
|
||||
enable HDMI on RK3288 based SoC, you should selet this
|
||||
option.
|
||||
enable HDMI on RK3288 or RK3399 based SoC, you should select
|
||||
this option.
|
||||
|
||||
config ROCKCHIP_DW_MIPI_DSI
|
||||
bool "Rockchip specific extensions for Synopsys DW MIPI DSI"
|
||||
help
|
||||
This selects support for Rockchip SoC specific extensions
|
||||
for the Synopsys DesignWare HDMI driver. If you want to
|
||||
enable MIPI DSI on RK3288 based SoC, you should selet this
|
||||
option.
|
||||
This selects support for Rockchip SoC specific extensions
|
||||
for the Synopsys DesignWare HDMI driver. If you want to
|
||||
enable MIPI DSI on RK3288 or RK3399 based SoC, you should
|
||||
select this option.
|
||||
|
||||
config ROCKCHIP_INNO_HDMI
|
||||
bool "Rockchip specific extensions for Innosilicon HDMI"
|
||||
@ -66,4 +67,14 @@ config ROCKCHIP_LVDS
|
||||
Rockchip rk3288 SoC has LVDS TX Controller can be used, and it
|
||||
support LVDS, rgb, dual LVDS output mode. say Y to enable its
|
||||
driver.
|
||||
|
||||
config ROCKCHIP_RGB
|
||||
bool "Rockchip RGB support"
|
||||
depends on DRM_ROCKCHIP
|
||||
depends on PINCTRL
|
||||
help
|
||||
Choose this option to enable support for Rockchip RGB output.
|
||||
Some Rockchip CRTCs, like rv1108, can directly output parallel
|
||||
and serial RGB format to panel or connect to a conversion chip.
|
||||
say Y to enable its driver.
|
||||
endif
|
||||
|
@ -14,5 +14,6 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
|
||||
rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o
|
||||
rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
|
||||
rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
|
||||
rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
|
||||
|
||||
obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/iommu.h>
|
||||
@ -184,7 +185,7 @@ err_mode_config_cleanup:
|
||||
err_free:
|
||||
drm_dev->dev_private = NULL;
|
||||
dev_set_drvdata(dev, NULL);
|
||||
drm_dev_unref(drm_dev);
|
||||
drm_dev_put(drm_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -204,7 +205,7 @@ static void rockchip_drm_unbind(struct device *dev)
|
||||
|
||||
drm_dev->dev_private = NULL;
|
||||
dev_set_drvdata(dev, NULL);
|
||||
drm_dev_unref(drm_dev);
|
||||
drm_dev_put(drm_dev);
|
||||
}
|
||||
|
||||
static const struct file_operations rockchip_drm_driver_fops = {
|
||||
@ -243,60 +244,18 @@ static struct drm_driver rockchip_drm_driver = {
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static void rockchip_drm_fb_suspend(struct drm_device *drm)
|
||||
{
|
||||
struct rockchip_drm_private *priv = drm->dev_private;
|
||||
|
||||
console_lock();
|
||||
drm_fb_helper_set_suspend(&priv->fbdev_helper, 1);
|
||||
console_unlock();
|
||||
}
|
||||
|
||||
static void rockchip_drm_fb_resume(struct drm_device *drm)
|
||||
{
|
||||
struct rockchip_drm_private *priv = drm->dev_private;
|
||||
|
||||
console_lock();
|
||||
drm_fb_helper_set_suspend(&priv->fbdev_helper, 0);
|
||||
console_unlock();
|
||||
}
|
||||
|
||||
static int rockchip_drm_sys_suspend(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(dev);
|
||||
struct rockchip_drm_private *priv;
|
||||
|
||||
if (!drm)
|
||||
return 0;
|
||||
|
||||
drm_kms_helper_poll_disable(drm);
|
||||
rockchip_drm_fb_suspend(drm);
|
||||
|
||||
priv = drm->dev_private;
|
||||
priv->state = drm_atomic_helper_suspend(drm);
|
||||
if (IS_ERR(priv->state)) {
|
||||
rockchip_drm_fb_resume(drm);
|
||||
drm_kms_helper_poll_enable(drm);
|
||||
return PTR_ERR(priv->state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return drm_mode_config_helper_suspend(drm);
|
||||
}
|
||||
|
||||
static int rockchip_drm_sys_resume(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(dev);
|
||||
struct rockchip_drm_private *priv;
|
||||
|
||||
if (!drm)
|
||||
return 0;
|
||||
|
||||
priv = drm->dev_private;
|
||||
drm_atomic_helper_resume(drm, priv->state);
|
||||
rockchip_drm_fb_resume(drm);
|
||||
drm_kms_helper_poll_enable(drm);
|
||||
|
||||
return 0;
|
||||
return drm_mode_config_helper_resume(drm);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -309,6 +268,53 @@ static const struct dev_pm_ops rockchip_drm_pm_ops = {
|
||||
static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
|
||||
static int num_rockchip_sub_drivers;
|
||||
|
||||
/*
|
||||
* Check if a vop endpoint is leading to a rockchip subdriver or bridge.
|
||||
* Should be called from the component bind stage of the drivers
|
||||
* to ensure that all subdrivers are probed.
|
||||
*
|
||||
* @ep: endpoint of a rockchip vop
|
||||
*
|
||||
* returns true if subdriver, false if external bridge and -ENODEV
|
||||
* if remote port does not contain a device.
|
||||
*/
|
||||
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep)
|
||||
{
|
||||
struct device_node *node = of_graph_get_remote_port_parent(ep);
|
||||
struct platform_device *pdev;
|
||||
struct device_driver *drv;
|
||||
int i;
|
||||
|
||||
if (!node)
|
||||
return -ENODEV;
|
||||
|
||||
/* status disabled will prevent creation of platform-devices */
|
||||
pdev = of_find_device_by_node(node);
|
||||
of_node_put(node);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* All rockchip subdrivers have probed at this point, so
|
||||
* any device not having a driver now is an external bridge.
|
||||
*/
|
||||
drv = pdev->dev.driver;
|
||||
if (!drv) {
|
||||
platform_device_put(pdev);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_rockchip_sub_drivers; i++) {
|
||||
if (rockchip_sub_drivers[i] == to_platform_driver(drv)) {
|
||||
platform_device_put(pdev);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
platform_device_put(pdev);
|
||||
return false;
|
||||
}
|
||||
|
||||
static int compare_dev(struct device *dev, void *data)
|
||||
{
|
||||
return dev == (struct device *)data;
|
||||
|
@ -51,7 +51,6 @@ struct rockchip_crtc_state {
|
||||
struct rockchip_drm_private {
|
||||
struct drm_fb_helper fbdev_helper;
|
||||
struct drm_gem_object *fbdev_bo;
|
||||
struct drm_atomic_state *state;
|
||||
struct iommu_domain *domain;
|
||||
struct mutex mm_lock;
|
||||
struct drm_mm mm;
|
||||
@ -65,6 +64,7 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
|
||||
struct device *dev);
|
||||
int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout);
|
||||
|
||||
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
|
||||
extern struct platform_driver cdn_dp_driver;
|
||||
extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
|
||||
extern struct platform_driver dw_mipi_dsi_driver;
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/overflow.h>
|
||||
|
||||
#include <linux/reset.h>
|
||||
#include <linux/delay.h>
|
||||
@ -41,6 +42,7 @@
|
||||
#include "rockchip_drm_fb.h"
|
||||
#include "rockchip_drm_psr.h"
|
||||
#include "rockchip_drm_vop.h"
|
||||
#include "rockchip_rgb.h"
|
||||
|
||||
#define VOP_WIN_SET(x, win, name, v) \
|
||||
vop_reg_set(vop, &win->phy->name, win->base, ~0, v, #name)
|
||||
@ -92,6 +94,7 @@ struct vop_win {
|
||||
struct vop *vop;
|
||||
};
|
||||
|
||||
struct rockchip_rgb;
|
||||
struct vop {
|
||||
struct drm_crtc crtc;
|
||||
struct device *dev;
|
||||
@ -135,6 +138,9 @@ struct vop {
|
||||
/* vop dclk reset */
|
||||
struct reset_control *dclk_rst;
|
||||
|
||||
/* optional internal rgb encoder */
|
||||
struct rockchip_rgb *rgb;
|
||||
|
||||
struct vop_win win[];
|
||||
};
|
||||
|
||||
@ -1111,7 +1117,7 @@ static struct drm_connector *vop_get_edp_connector(struct vop *vop)
|
||||
}
|
||||
|
||||
static int vop_crtc_set_crc_source(struct drm_crtc *crtc,
|
||||
const char *source_name, size_t *values_cnt)
|
||||
const char *source_name)
|
||||
{
|
||||
struct vop *vop = to_vop(crtc);
|
||||
struct drm_connector *connector;
|
||||
@ -1121,8 +1127,6 @@ static int vop_crtc_set_crc_source(struct drm_crtc *crtc,
|
||||
if (!connector)
|
||||
return -EINVAL;
|
||||
|
||||
*values_cnt = 3;
|
||||
|
||||
if (source_name && strcmp(source_name, "auto") == 0)
|
||||
ret = analogix_dp_start_crc(connector);
|
||||
else if (!source_name)
|
||||
@ -1132,9 +1136,28 @@ static int vop_crtc_set_crc_source(struct drm_crtc *crtc,
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
vop_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
|
||||
size_t *values_cnt)
|
||||
{
|
||||
if (source_name && strcmp(source_name, "auto") != 0)
|
||||
return -EINVAL;
|
||||
|
||||
*values_cnt = 3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
static int vop_crtc_set_crc_source(struct drm_crtc *crtc,
|
||||
const char *source_name, size_t *values_cnt)
|
||||
const char *source_name)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int
|
||||
vop_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
|
||||
size_t *values_cnt)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
@ -1150,6 +1173,7 @@ static const struct drm_crtc_funcs vop_crtc_funcs = {
|
||||
.enable_vblank = vop_crtc_enable_vblank,
|
||||
.disable_vblank = vop_crtc_disable_vblank,
|
||||
.set_crc_source = vop_crtc_set_crc_source,
|
||||
.verify_crc_source = vop_crtc_verify_crc_source,
|
||||
};
|
||||
|
||||
static void vop_fb_unref_worker(struct drm_flip_work *work, void *val)
|
||||
@ -1561,7 +1585,6 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
|
||||
struct drm_device *drm_dev = data;
|
||||
struct vop *vop;
|
||||
struct resource *res;
|
||||
size_t alloc_size;
|
||||
int ret, irq;
|
||||
|
||||
vop_data = of_device_get_match_data(dev);
|
||||
@ -1569,8 +1592,8 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
|
||||
return -ENODEV;
|
||||
|
||||
/* Allocate vop struct and its vop_win array */
|
||||
alloc_size = sizeof(*vop) + sizeof(*vop->win) * vop_data->win_size;
|
||||
vop = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
|
||||
vop = devm_kzalloc(dev, struct_size(vop, win, vop_data->win_size),
|
||||
GFP_KERNEL);
|
||||
if (!vop)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -1620,6 +1643,14 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
|
||||
if (ret)
|
||||
goto err_disable_pm_runtime;
|
||||
|
||||
if (vop->data->feature & VOP_FEATURE_INTERNAL_RGB) {
|
||||
vop->rgb = rockchip_rgb_init(dev, &vop->crtc, vop->drm_dev);
|
||||
if (IS_ERR(vop->rgb)) {
|
||||
ret = PTR_ERR(vop->rgb);
|
||||
goto err_disable_pm_runtime;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_pm_runtime:
|
||||
@ -1632,6 +1663,9 @@ static void vop_unbind(struct device *dev, struct device *master, void *data)
|
||||
{
|
||||
struct vop *vop = dev_get_drvdata(dev);
|
||||
|
||||
if (vop->rgb)
|
||||
rockchip_rgb_fini(vop->rgb);
|
||||
|
||||
pm_runtime_disable(dev);
|
||||
vop_destroy_crtc(vop);
|
||||
|
||||
|
@ -162,6 +162,7 @@ struct vop_data {
|
||||
unsigned int win_size;
|
||||
|
||||
#define VOP_FEATURE_OUTPUT_RGB10 BIT(0)
|
||||
#define VOP_FEATURE_INTERNAL_RGB BIT(1)
|
||||
u64 feature;
|
||||
};
|
||||
|
||||
|
173
drivers/gpu/drm/rockchip/rockchip_rgb.c
Normal file
173
drivers/gpu/drm/rockchip/rockchip_rgb.c
Normal file
@ -0,0 +1,173 @@
|
||||
//SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
|
||||
* Author:
|
||||
* Sandy Huang <hjc@rock-chips.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_dp_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_of.h>
|
||||
|
||||
#include <linux/component.h>
|
||||
#include <linux/of_graph.h>
|
||||
|
||||
#include "rockchip_drm_drv.h"
|
||||
#include "rockchip_drm_vop.h"
|
||||
|
||||
#define encoder_to_rgb(c) container_of(c, struct rockchip_rgb, encoder)
|
||||
|
||||
struct rockchip_rgb {
|
||||
struct device *dev;
|
||||
struct drm_device *drm_dev;
|
||||
struct drm_bridge *bridge;
|
||||
struct drm_encoder encoder;
|
||||
int output_mode;
|
||||
};
|
||||
|
||||
static int
|
||||
rockchip_rgb_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
|
||||
struct drm_connector *connector = conn_state->connector;
|
||||
struct drm_display_info *info = &connector->display_info;
|
||||
u32 bus_format;
|
||||
|
||||
if (info->num_bus_formats)
|
||||
bus_format = info->bus_formats[0];
|
||||
else
|
||||
bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
||||
|
||||
switch (bus_format) {
|
||||
case MEDIA_BUS_FMT_RGB666_1X18:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_P666;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB565_1X16:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_P565;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
|
||||
default:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_P888;
|
||||
break;
|
||||
}
|
||||
|
||||
s->output_type = DRM_MODE_CONNECTOR_LVDS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const
|
||||
struct drm_encoder_helper_funcs rockchip_rgb_encoder_helper_funcs = {
|
||||
.atomic_check = rockchip_rgb_encoder_atomic_check,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs rockchip_rgb_encoder_funcs = {
|
||||
.destroy = drm_encoder_cleanup,
|
||||
};
|
||||
|
||||
struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
|
||||
struct drm_crtc *crtc,
|
||||
struct drm_device *drm_dev)
|
||||
{
|
||||
struct rockchip_rgb *rgb;
|
||||
struct drm_encoder *encoder;
|
||||
struct device_node *port, *endpoint;
|
||||
u32 endpoint_id;
|
||||
int ret = 0, child_count = 0;
|
||||
struct drm_panel *panel;
|
||||
struct drm_bridge *bridge;
|
||||
|
||||
rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
|
||||
if (!rgb)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
rgb->dev = dev;
|
||||
rgb->drm_dev = drm_dev;
|
||||
|
||||
port = of_graph_get_port_by_id(dev->of_node, 0);
|
||||
if (!port)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
for_each_child_of_node(port, endpoint) {
|
||||
if (of_property_read_u32(endpoint, "reg", &endpoint_id))
|
||||
endpoint_id = 0;
|
||||
|
||||
if (rockchip_drm_endpoint_is_subdriver(endpoint) > 0)
|
||||
continue;
|
||||
|
||||
child_count++;
|
||||
ret = drm_of_find_panel_or_bridge(dev->of_node, 0, endpoint_id,
|
||||
&panel, &bridge);
|
||||
if (!ret)
|
||||
break;
|
||||
}
|
||||
|
||||
of_node_put(port);
|
||||
|
||||
/* if the rgb output is not connected to anything, just return */
|
||||
if (!child_count)
|
||||
return NULL;
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
DRM_DEV_ERROR(dev, "failed to find panel or bridge %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
encoder = &rgb->encoder;
|
||||
encoder->possible_crtcs = drm_crtc_mask(crtc);
|
||||
|
||||
ret = drm_encoder_init(drm_dev, encoder, &rockchip_rgb_encoder_funcs,
|
||||
DRM_MODE_ENCODER_NONE, NULL);
|
||||
if (ret < 0) {
|
||||
DRM_DEV_ERROR(drm_dev->dev,
|
||||
"failed to initialize encoder: %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
drm_encoder_helper_add(encoder, &rockchip_rgb_encoder_helper_funcs);
|
||||
|
||||
if (panel) {
|
||||
bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_LVDS);
|
||||
if (IS_ERR(bridge))
|
||||
return ERR_CAST(bridge);
|
||||
}
|
||||
|
||||
rgb->bridge = bridge;
|
||||
|
||||
ret = drm_bridge_attach(encoder, rgb->bridge, NULL);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(drm_dev->dev,
|
||||
"failed to attach bridge: %d\n", ret);
|
||||
goto err_free_encoder;
|
||||
}
|
||||
|
||||
return rgb;
|
||||
|
||||
err_free_encoder:
|
||||
drm_encoder_cleanup(encoder);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rockchip_rgb_init);
|
||||
|
||||
void rockchip_rgb_fini(struct rockchip_rgb *rgb)
|
||||
{
|
||||
drm_panel_bridge_remove(rgb->bridge);
|
||||
drm_encoder_cleanup(&rgb->encoder);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rockchip_rgb_fini);
|
33
drivers/gpu/drm/rockchip/rockchip_rgb.h
Normal file
33
drivers/gpu/drm/rockchip/rockchip_rgb.h
Normal file
@ -0,0 +1,33 @@
|
||||
//SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
|
||||
* Author:
|
||||
* Sandy Huang <hjc@rock-chips.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_ROCKCHIP_RGB
|
||||
struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
|
||||
struct drm_crtc *crtc,
|
||||
struct drm_device *drm_dev);
|
||||
void rockchip_rgb_fini(struct rockchip_rgb *rgb);
|
||||
#else
|
||||
static inline struct rockchip_rgb *rockchip_rgb_init(struct device *dev,
|
||||
struct drm_crtc *crtc,
|
||||
struct drm_device *drm_dev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void rockchip_rgb_fini(struct rockchip_rgb *rgb)
|
||||
{
|
||||
}
|
||||
#endif
|
@ -177,6 +177,215 @@ static const struct vop_data rk3126_vop = {
|
||||
.win_size = ARRAY_SIZE(rk3126_vop_win_data),
|
||||
};
|
||||
|
||||
static const int px30_vop_intrs[] = {
|
||||
FS_INTR,
|
||||
0, 0,
|
||||
LINE_FLAG_INTR,
|
||||
0,
|
||||
BUS_ERROR_INTR,
|
||||
0, 0,
|
||||
DSP_HOLD_VALID_INTR,
|
||||
};
|
||||
|
||||
static const struct vop_intr px30_intr = {
|
||||
.intrs = px30_vop_intrs,
|
||||
.nintrs = ARRAY_SIZE(px30_vop_intrs),
|
||||
.line_flag_num[0] = VOP_REG(PX30_LINE_FLAG, 0xfff, 0),
|
||||
.status = VOP_REG_MASK_SYNC(PX30_INTR_STATUS, 0xffff, 0),
|
||||
.enable = VOP_REG_MASK_SYNC(PX30_INTR_EN, 0xffff, 0),
|
||||
.clear = VOP_REG_MASK_SYNC(PX30_INTR_CLEAR, 0xffff, 0),
|
||||
};
|
||||
|
||||
static const struct vop_common px30_common = {
|
||||
.standby = VOP_REG_SYNC(PX30_SYS_CTRL2, 0x1, 1),
|
||||
.out_mode = VOP_REG(PX30_DSP_CTRL2, 0xf, 16),
|
||||
.dsp_blank = VOP_REG(PX30_DSP_CTRL2, 0x1, 14),
|
||||
.cfg_done = VOP_REG_SYNC(PX30_REG_CFG_DONE, 0x1, 0),
|
||||
};
|
||||
|
||||
static const struct vop_modeset px30_modeset = {
|
||||
.htotal_pw = VOP_REG(PX30_DSP_HTOTAL_HS_END, 0x0fff0fff, 0),
|
||||
.hact_st_end = VOP_REG(PX30_DSP_HACT_ST_END, 0x0fff0fff, 0),
|
||||
.vtotal_pw = VOP_REG(PX30_DSP_VTOTAL_VS_END, 0x0fff0fff, 0),
|
||||
.vact_st_end = VOP_REG(PX30_DSP_VACT_ST_END, 0x0fff0fff, 0),
|
||||
};
|
||||
|
||||
static const struct vop_output px30_output = {
|
||||
.rgb_pin_pol = VOP_REG(PX30_DSP_CTRL0, 0xf, 1),
|
||||
.mipi_pin_pol = VOP_REG(PX30_DSP_CTRL0, 0xf, 25),
|
||||
.rgb_en = VOP_REG(PX30_DSP_CTRL0, 0x1, 0),
|
||||
.mipi_en = VOP_REG(PX30_DSP_CTRL0, 0x1, 24),
|
||||
};
|
||||
|
||||
static const struct vop_scl_regs px30_win_scl = {
|
||||
.scale_yrgb_x = VOP_REG(PX30_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0),
|
||||
.scale_yrgb_y = VOP_REG(PX30_WIN0_SCL_FACTOR_YRGB, 0xffff, 16),
|
||||
.scale_cbcr_x = VOP_REG(PX30_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0),
|
||||
.scale_cbcr_y = VOP_REG(PX30_WIN0_SCL_FACTOR_CBR, 0xffff, 16),
|
||||
};
|
||||
|
||||
static const struct vop_win_phy px30_win0_data = {
|
||||
.scl = &px30_win_scl,
|
||||
.data_formats = formats_win_full,
|
||||
.nformats = ARRAY_SIZE(formats_win_full),
|
||||
.enable = VOP_REG(PX30_WIN0_CTRL0, 0x1, 0),
|
||||
.format = VOP_REG(PX30_WIN0_CTRL0, 0x7, 1),
|
||||
.rb_swap = VOP_REG(PX30_WIN0_CTRL0, 0x1, 12),
|
||||
.act_info = VOP_REG(PX30_WIN0_ACT_INFO, 0xffffffff, 0),
|
||||
.dsp_info = VOP_REG(PX30_WIN0_DSP_INFO, 0xffffffff, 0),
|
||||
.dsp_st = VOP_REG(PX30_WIN0_DSP_ST, 0xffffffff, 0),
|
||||
.yrgb_mst = VOP_REG(PX30_WIN0_YRGB_MST0, 0xffffffff, 0),
|
||||
.uv_mst = VOP_REG(PX30_WIN0_CBR_MST0, 0xffffffff, 0),
|
||||
.yrgb_vir = VOP_REG(PX30_WIN0_VIR, 0x1fff, 0),
|
||||
.uv_vir = VOP_REG(PX30_WIN0_VIR, 0x1fff, 16),
|
||||
};
|
||||
|
||||
static const struct vop_win_phy px30_win1_data = {
|
||||
.data_formats = formats_win_lite,
|
||||
.nformats = ARRAY_SIZE(formats_win_lite),
|
||||
.enable = VOP_REG(PX30_WIN1_CTRL0, 0x1, 0),
|
||||
.format = VOP_REG(PX30_WIN1_CTRL0, 0x7, 4),
|
||||
.rb_swap = VOP_REG(PX30_WIN1_CTRL0, 0x1, 12),
|
||||
.dsp_info = VOP_REG(PX30_WIN1_DSP_INFO, 0xffffffff, 0),
|
||||
.dsp_st = VOP_REG(PX30_WIN1_DSP_ST, 0xffffffff, 0),
|
||||
.yrgb_mst = VOP_REG(PX30_WIN1_MST, 0xffffffff, 0),
|
||||
.yrgb_vir = VOP_REG(PX30_WIN1_VIR, 0x1fff, 0),
|
||||
};
|
||||
|
||||
static const struct vop_win_phy px30_win2_data = {
|
||||
.data_formats = formats_win_lite,
|
||||
.nformats = ARRAY_SIZE(formats_win_lite),
|
||||
.gate = VOP_REG(PX30_WIN2_CTRL0, 0x1, 4),
|
||||
.enable = VOP_REG(PX30_WIN2_CTRL0, 0x1, 0),
|
||||
.format = VOP_REG(PX30_WIN2_CTRL0, 0x3, 5),
|
||||
.rb_swap = VOP_REG(PX30_WIN2_CTRL0, 0x1, 20),
|
||||
.dsp_info = VOP_REG(PX30_WIN2_DSP_INFO0, 0x0fff0fff, 0),
|
||||
.dsp_st = VOP_REG(PX30_WIN2_DSP_ST0, 0x1fff1fff, 0),
|
||||
.yrgb_mst = VOP_REG(PX30_WIN2_MST0, 0xffffffff, 0),
|
||||
.yrgb_vir = VOP_REG(PX30_WIN2_VIR0_1, 0x1fff, 0),
|
||||
};
|
||||
|
||||
static const struct vop_win_data px30_vop_big_win_data[] = {
|
||||
{ .base = 0x00, .phy = &px30_win0_data,
|
||||
.type = DRM_PLANE_TYPE_PRIMARY },
|
||||
{ .base = 0x00, .phy = &px30_win1_data,
|
||||
.type = DRM_PLANE_TYPE_OVERLAY },
|
||||
{ .base = 0x00, .phy = &px30_win2_data,
|
||||
.type = DRM_PLANE_TYPE_CURSOR },
|
||||
};
|
||||
|
||||
static const struct vop_data px30_vop_big = {
|
||||
.intr = &px30_intr,
|
||||
.feature = VOP_FEATURE_INTERNAL_RGB,
|
||||
.common = &px30_common,
|
||||
.modeset = &px30_modeset,
|
||||
.output = &px30_output,
|
||||
.win = px30_vop_big_win_data,
|
||||
.win_size = ARRAY_SIZE(px30_vop_big_win_data),
|
||||
};
|
||||
|
||||
static const struct vop_win_data px30_vop_lit_win_data[] = {
|
||||
{ .base = 0x00, .phy = &px30_win1_data,
|
||||
.type = DRM_PLANE_TYPE_PRIMARY },
|
||||
};
|
||||
|
||||
static const struct vop_data px30_vop_lit = {
|
||||
.intr = &px30_intr,
|
||||
.feature = VOP_FEATURE_INTERNAL_RGB,
|
||||
.common = &px30_common,
|
||||
.modeset = &px30_modeset,
|
||||
.output = &px30_output,
|
||||
.win = px30_vop_lit_win_data,
|
||||
.win_size = ARRAY_SIZE(px30_vop_lit_win_data),
|
||||
};
|
||||
|
||||
static const struct vop_scl_regs rk3188_win_scl = {
|
||||
.scale_yrgb_x = VOP_REG(RK3188_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0),
|
||||
.scale_yrgb_y = VOP_REG(RK3188_WIN0_SCL_FACTOR_YRGB, 0xffff, 16),
|
||||
.scale_cbcr_x = VOP_REG(RK3188_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0),
|
||||
.scale_cbcr_y = VOP_REG(RK3188_WIN0_SCL_FACTOR_CBR, 0xffff, 16),
|
||||
};
|
||||
|
||||
static const struct vop_win_phy rk3188_win0_data = {
|
||||
.scl = &rk3188_win_scl,
|
||||
.data_formats = formats_win_full,
|
||||
.nformats = ARRAY_SIZE(formats_win_full),
|
||||
.enable = VOP_REG(RK3188_SYS_CTRL, 0x1, 0),
|
||||
.format = VOP_REG(RK3188_SYS_CTRL, 0x7, 3),
|
||||
.rb_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 15),
|
||||
.act_info = VOP_REG(RK3188_WIN0_ACT_INFO, 0x1fff1fff, 0),
|
||||
.dsp_info = VOP_REG(RK3188_WIN0_DSP_INFO, 0x0fff0fff, 0),
|
||||
.dsp_st = VOP_REG(RK3188_WIN0_DSP_ST, 0x1fff1fff, 0),
|
||||
.yrgb_mst = VOP_REG(RK3188_WIN0_YRGB_MST0, 0xffffffff, 0),
|
||||
.uv_mst = VOP_REG(RK3188_WIN0_CBR_MST0, 0xffffffff, 0),
|
||||
.yrgb_vir = VOP_REG(RK3188_WIN_VIR, 0x1fff, 0),
|
||||
};
|
||||
|
||||
static const struct vop_win_phy rk3188_win1_data = {
|
||||
.data_formats = formats_win_lite,
|
||||
.nformats = ARRAY_SIZE(formats_win_lite),
|
||||
.enable = VOP_REG(RK3188_SYS_CTRL, 0x1, 1),
|
||||
.format = VOP_REG(RK3188_SYS_CTRL, 0x7, 6),
|
||||
.rb_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 19),
|
||||
/* no act_info on window1 */
|
||||
.dsp_info = VOP_REG(RK3188_WIN1_DSP_INFO, 0x07ff07ff, 0),
|
||||
.dsp_st = VOP_REG(RK3188_WIN1_DSP_ST, 0x0fff0fff, 0),
|
||||
.yrgb_mst = VOP_REG(RK3188_WIN1_MST, 0xffffffff, 0),
|
||||
.yrgb_vir = VOP_REG(RK3188_WIN_VIR, 0x1fff, 16),
|
||||
};
|
||||
|
||||
static const struct vop_modeset rk3188_modeset = {
|
||||
.htotal_pw = VOP_REG(RK3188_DSP_HTOTAL_HS_END, 0x0fff0fff, 0),
|
||||
.hact_st_end = VOP_REG(RK3188_DSP_HACT_ST_END, 0x0fff0fff, 0),
|
||||
.vtotal_pw = VOP_REG(RK3188_DSP_VTOTAL_VS_END, 0x0fff0fff, 0),
|
||||
.vact_st_end = VOP_REG(RK3188_DSP_VACT_ST_END, 0x0fff0fff, 0),
|
||||
};
|
||||
|
||||
static const struct vop_output rk3188_output = {
|
||||
.pin_pol = VOP_REG(RK3188_DSP_CTRL0, 0xf, 4),
|
||||
};
|
||||
|
||||
static const struct vop_common rk3188_common = {
|
||||
.gate_en = VOP_REG(RK3188_SYS_CTRL, 0x1, 31),
|
||||
.standby = VOP_REG(RK3188_SYS_CTRL, 0x1, 30),
|
||||
.out_mode = VOP_REG(RK3188_DSP_CTRL0, 0xf, 0),
|
||||
.cfg_done = VOP_REG(RK3188_REG_CFG_DONE, 0x1, 0),
|
||||
.dsp_blank = VOP_REG(RK3188_DSP_CTRL1, 0x3, 24),
|
||||
};
|
||||
|
||||
static const struct vop_win_data rk3188_vop_win_data[] = {
|
||||
{ .base = 0x00, .phy = &rk3188_win0_data,
|
||||
.type = DRM_PLANE_TYPE_PRIMARY },
|
||||
{ .base = 0x00, .phy = &rk3188_win1_data,
|
||||
.type = DRM_PLANE_TYPE_CURSOR },
|
||||
};
|
||||
|
||||
static const int rk3188_vop_intrs[] = {
|
||||
0,
|
||||
FS_INTR,
|
||||
LINE_FLAG_INTR,
|
||||
BUS_ERROR_INTR,
|
||||
};
|
||||
|
||||
static const struct vop_intr rk3188_vop_intr = {
|
||||
.intrs = rk3188_vop_intrs,
|
||||
.nintrs = ARRAY_SIZE(rk3188_vop_intrs),
|
||||
.line_flag_num[0] = VOP_REG(RK3188_INT_STATUS, 0xfff, 12),
|
||||
.status = VOP_REG(RK3188_INT_STATUS, 0xf, 0),
|
||||
.enable = VOP_REG(RK3188_INT_STATUS, 0xf, 4),
|
||||
.clear = VOP_REG(RK3188_INT_STATUS, 0xf, 8),
|
||||
};
|
||||
|
||||
static const struct vop_data rk3188_vop = {
|
||||
.intr = &rk3188_vop_intr,
|
||||
.common = &rk3188_common,
|
||||
.modeset = &rk3188_modeset,
|
||||
.output = &rk3188_output,
|
||||
.win = rk3188_vop_win_data,
|
||||
.win_size = ARRAY_SIZE(rk3188_vop_win_data),
|
||||
.feature = VOP_FEATURE_INTERNAL_RGB,
|
||||
};
|
||||
|
||||
static const struct vop_scl_extension rk3288_win_full_scl_ext = {
|
||||
.cbcr_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 31),
|
||||
.cbcr_vsu_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 30),
|
||||
@ -541,6 +750,12 @@ static const struct of_device_id vop_driver_dt_match[] = {
|
||||
.data = &rk3036_vop },
|
||||
{ .compatible = "rockchip,rk3126-vop",
|
||||
.data = &rk3126_vop },
|
||||
{ .compatible = "rockchip,px30-vop-big",
|
||||
.data = &px30_vop_big },
|
||||
{ .compatible = "rockchip,px30-vop-lit",
|
||||
.data = &px30_vop_lit },
|
||||
{ .compatible = "rockchip,rk3188-vop",
|
||||
.data = &rk3188_vop },
|
||||
{ .compatible = "rockchip,rk3288-vop",
|
||||
.data = &rk3288_vop },
|
||||
{ .compatible = "rockchip,rk3368-vop",
|
||||
|
@ -884,4 +884,103 @@
|
||||
#define RK3126_WIN1_DSP_ST 0x54
|
||||
/* rk3126 register definition end */
|
||||
|
||||
/* px30 register definition */
|
||||
#define PX30_REG_CFG_DONE 0x00000
|
||||
#define PX30_VERSION 0x00004
|
||||
#define PX30_DSP_BG 0x00008
|
||||
#define PX30_MCU_CTRL 0x0000c
|
||||
#define PX30_SYS_CTRL0 0x00010
|
||||
#define PX30_SYS_CTRL1 0x00014
|
||||
#define PX30_SYS_CTRL2 0x00018
|
||||
#define PX30_DSP_CTRL0 0x00020
|
||||
#define PX30_DSP_CTRL2 0x00028
|
||||
#define PX30_VOP_STATUS 0x0002c
|
||||
#define PX30_LINE_FLAG 0x00030
|
||||
#define PX30_INTR_EN 0x00034
|
||||
#define PX30_INTR_CLEAR 0x00038
|
||||
#define PX30_INTR_STATUS 0x0003c
|
||||
#define PX30_WIN0_CTRL0 0x00050
|
||||
#define PX30_WIN0_CTRL1 0x00054
|
||||
#define PX30_WIN0_COLOR_KEY 0x00058
|
||||
#define PX30_WIN0_VIR 0x0005c
|
||||
#define PX30_WIN0_YRGB_MST0 0x00060
|
||||
#define PX30_WIN0_CBR_MST0 0x00064
|
||||
#define PX30_WIN0_ACT_INFO 0x00068
|
||||
#define PX30_WIN0_DSP_INFO 0x0006c
|
||||
#define PX30_WIN0_DSP_ST 0x00070
|
||||
#define PX30_WIN0_SCL_FACTOR_YRGB 0x00074
|
||||
#define PX30_WIN0_SCL_FACTOR_CBR 0x00078
|
||||
#define PX30_WIN0_SCL_OFFSET 0x0007c
|
||||
#define PX30_WIN0_ALPHA_CTRL 0x00080
|
||||
#define PX30_WIN1_CTRL0 0x00090
|
||||
#define PX30_WIN1_CTRL1 0x00094
|
||||
#define PX30_WIN1_VIR 0x00098
|
||||
#define PX30_WIN1_MST 0x000a0
|
||||
#define PX30_WIN1_DSP_INFO 0x000a4
|
||||
#define PX30_WIN1_DSP_ST 0x000a8
|
||||
#define PX30_WIN1_COLOR_KEY 0x000ac
|
||||
#define PX30_WIN1_ALPHA_CTRL 0x000bc
|
||||
#define PX30_HWC_CTRL0 0x000e0
|
||||
#define PX30_HWC_CTRL1 0x000e4
|
||||
#define PX30_HWC_MST 0x000e8
|
||||
#define PX30_HWC_DSP_ST 0x000ec
|
||||
#define PX30_HWC_ALPHA_CTRL 0x000f0
|
||||
#define PX30_DSP_HTOTAL_HS_END 0x00100
|
||||
#define PX30_DSP_HACT_ST_END 0x00104
|
||||
#define PX30_DSP_VTOTAL_VS_END 0x00108
|
||||
#define PX30_DSP_VACT_ST_END 0x0010c
|
||||
#define PX30_DSP_VS_ST_END_F1 0x00110
|
||||
#define PX30_DSP_VACT_ST_END_F1 0x00114
|
||||
#define PX30_BCSH_CTRL 0x00160
|
||||
#define PX30_BCSH_COL_BAR 0x00164
|
||||
#define PX30_BCSH_BCS 0x00168
|
||||
#define PX30_BCSH_H 0x0016c
|
||||
#define PX30_FRC_LOWER01_0 0x00170
|
||||
#define PX30_FRC_LOWER01_1 0x00174
|
||||
#define PX30_FRC_LOWER10_0 0x00178
|
||||
#define PX30_FRC_LOWER10_1 0x0017c
|
||||
#define PX30_FRC_LOWER11_0 0x00180
|
||||
#define PX30_FRC_LOWER11_1 0x00184
|
||||
#define PX30_MCU_RW_BYPASS_PORT 0x0018c
|
||||
#define PX30_WIN2_CTRL0 0x00190
|
||||
#define PX30_WIN2_CTRL1 0x00194
|
||||
#define PX30_WIN2_VIR0_1 0x00198
|
||||
#define PX30_WIN2_VIR2_3 0x0019c
|
||||
#define PX30_WIN2_MST0 0x001a0
|
||||
#define PX30_WIN2_DSP_INFO0 0x001a4
|
||||
#define PX30_WIN2_DSP_ST0 0x001a8
|
||||
#define PX30_WIN2_COLOR_KEY 0x001ac
|
||||
#define PX30_WIN2_ALPHA_CTRL 0x001bc
|
||||
#define PX30_BLANKING_VALUE 0x001f4
|
||||
#define PX30_FLAG_REG_FRM_VALID 0x001f8
|
||||
#define PX30_FLAG_REG 0x001fc
|
||||
#define PX30_HWC_LUT_ADDR 0x00600
|
||||
#define PX30_GAMMA_LUT_ADDR 0x00a00
|
||||
/* px30 register definition end */
|
||||
|
||||
/* rk3188 register definition */
|
||||
#define RK3188_SYS_CTRL 0x00
|
||||
#define RK3188_DSP_CTRL0 0x04
|
||||
#define RK3188_DSP_CTRL1 0x08
|
||||
#define RK3188_INT_STATUS 0x10
|
||||
#define RK3188_WIN0_YRGB_MST0 0x20
|
||||
#define RK3188_WIN0_CBR_MST0 0x24
|
||||
#define RK3188_WIN0_YRGB_MST1 0x28
|
||||
#define RK3188_WIN0_CBR_MST1 0x2c
|
||||
#define RK3188_WIN_VIR 0x30
|
||||
#define RK3188_WIN0_ACT_INFO 0x34
|
||||
#define RK3188_WIN0_DSP_INFO 0x38
|
||||
#define RK3188_WIN0_DSP_ST 0x3c
|
||||
#define RK3188_WIN0_SCL_FACTOR_YRGB 0x40
|
||||
#define RK3188_WIN0_SCL_FACTOR_CBR 0x44
|
||||
#define RK3188_WIN1_MST 0x4c
|
||||
#define RK3188_WIN1_DSP_INFO 0x50
|
||||
#define RK3188_WIN1_DSP_ST 0x54
|
||||
#define RK3188_DSP_HTOTAL_HS_END 0x6c
|
||||
#define RK3188_DSP_HACT_ST_END 0x70
|
||||
#define RK3188_DSP_VTOTAL_VS_END 0x74
|
||||
#define RK3188_DSP_VACT_ST_END 0x78
|
||||
#define RK3188_REG_CFG_DONE 0x90
|
||||
/* rk3188 register definition end */
|
||||
|
||||
#endif /* _ROCKCHIP_VOP_REG_H */
|
||||
|
@ -721,7 +721,6 @@ static int sti_hda_bind(struct device *dev, struct device *master, void *data)
|
||||
return 0;
|
||||
|
||||
err_sysfs:
|
||||
drm_bridge_remove(bridge);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -1315,7 +1315,6 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
|
||||
return 0;
|
||||
|
||||
err_sysfs:
|
||||
drm_bridge_remove(bridge);
|
||||
hdmi->drm_connector = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -34,6 +34,9 @@
|
||||
struct sun4i_backend_quirks {
|
||||
/* backend <-> TCON muxing selection done in backend */
|
||||
bool needs_output_muxing;
|
||||
|
||||
/* alpha at the lowest z position is not always supported */
|
||||
bool supports_lowest_plane_alpha;
|
||||
};
|
||||
|
||||
static const u32 sunxi_rgb2yuv_coef[12] = {
|
||||
@ -60,32 +63,6 @@ static const u32 sunxi_bt601_yuv2rgb_coef[12] = {
|
||||
0x000004a7, 0x00000812, 0x00000000, 0x00002eb1,
|
||||
};
|
||||
|
||||
static inline bool sun4i_backend_format_is_planar_yuv(uint32_t format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_YUV411:
|
||||
case DRM_FORMAT_YUV422:
|
||||
case DRM_FORMAT_YUV444:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool sun4i_backend_format_is_packed_yuv422(uint32_t format)
|
||||
{
|
||||
switch (format) {
|
||||
case DRM_FORMAT_YUYV:
|
||||
case DRM_FORMAT_YVYU:
|
||||
case DRM_FORMAT_UYVY:
|
||||
case DRM_FORMAT_VYUY:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void sun4i_backend_apply_color_correction(struct sunxi_engine *engine)
|
||||
{
|
||||
int i;
|
||||
@ -215,7 +192,8 @@ static int sun4i_backend_update_yuv_format(struct sun4i_backend *backend,
|
||||
{
|
||||
struct drm_plane_state *state = plane->state;
|
||||
struct drm_framebuffer *fb = state->fb;
|
||||
uint32_t format = fb->format->format;
|
||||
const struct drm_format_info *format = fb->format;
|
||||
const uint32_t fmt = format->format;
|
||||
u32 val = SUN4I_BACKEND_IYUVCTL_EN;
|
||||
int i;
|
||||
|
||||
@ -233,16 +211,16 @@ static int sun4i_backend_update_yuv_format(struct sun4i_backend *backend,
|
||||
SUN4I_BACKEND_ATTCTL_REG0_LAY_YUVEN);
|
||||
|
||||
/* TODO: Add support for the multi-planar YUV formats */
|
||||
if (sun4i_backend_format_is_packed_yuv422(format))
|
||||
if (format->num_planes == 1)
|
||||
val |= SUN4I_BACKEND_IYUVCTL_FBFMT_PACKED_YUV422;
|
||||
else
|
||||
DRM_DEBUG_DRIVER("Unsupported YUV format (0x%x)\n", format);
|
||||
DRM_DEBUG_DRIVER("Unsupported YUV format (0x%x)\n", fmt);
|
||||
|
||||
/*
|
||||
* Allwinner seems to list the pixel sequence from right to left, while
|
||||
* DRM lists it from left to right.
|
||||
*/
|
||||
switch (format) {
|
||||
switch (fmt) {
|
||||
case DRM_FORMAT_YUYV:
|
||||
val |= SUN4I_BACKEND_IYUVCTL_FBPS_VYUY;
|
||||
break;
|
||||
@ -257,7 +235,7 @@ static int sun4i_backend_update_yuv_format(struct sun4i_backend *backend,
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_DRIVER("Unsupported YUV pixel sequence (0x%x)\n",
|
||||
format);
|
||||
fmt);
|
||||
}
|
||||
|
||||
regmap_write(backend->engine.regs, SUN4I_BACKEND_IYUVCTL_REG, val);
|
||||
@ -457,12 +435,14 @@ static int sun4i_backend_atomic_check(struct sunxi_engine *engine,
|
||||
struct drm_crtc_state *crtc_state)
|
||||
{
|
||||
struct drm_plane_state *plane_states[SUN4I_BACKEND_NUM_LAYERS] = { 0 };
|
||||
struct sun4i_backend *backend = engine_to_sun4i_backend(engine);
|
||||
struct drm_atomic_state *state = crtc_state->state;
|
||||
struct drm_device *drm = state->dev;
|
||||
struct drm_plane *plane;
|
||||
unsigned int num_planes = 0;
|
||||
unsigned int num_alpha_planes = 0;
|
||||
unsigned int num_frontend_planes = 0;
|
||||
unsigned int num_alpha_planes_max = 1;
|
||||
unsigned int num_yuv_planes = 0;
|
||||
unsigned int current_pipe = 0;
|
||||
unsigned int i;
|
||||
@ -526,33 +506,40 @@ static int sun4i_backend_atomic_check(struct sunxi_engine *engine,
|
||||
* the layer with the highest priority.
|
||||
*
|
||||
* The second step is the actual alpha blending, that takes
|
||||
* the two pipes as input, and uses the eventual alpha
|
||||
* the two pipes as input, and uses the potential alpha
|
||||
* component to do the transparency between the two.
|
||||
*
|
||||
* This two steps scenario makes us unable to guarantee a
|
||||
* This two-step scenario makes us unable to guarantee a
|
||||
* robust alpha blending between the 4 layers in all
|
||||
* situations, since this means that we need to have one layer
|
||||
* with alpha at the lowest position of our two pipes.
|
||||
*
|
||||
* However, we cannot even do that, since the hardware has a
|
||||
* bug where the lowest plane of the lowest pipe (pipe 0,
|
||||
* priority 0), if it has any alpha, will discard the pixel
|
||||
* entirely and just display the pixels in the background
|
||||
* color (black by default).
|
||||
* However, we cannot even do that on every platform, since
|
||||
* the hardware has a bug where the lowest plane of the lowest
|
||||
* pipe (pipe 0, priority 0), if it has any alpha, will
|
||||
* discard the pixel data entirely and just display the pixels
|
||||
* in the background color (black by default).
|
||||
*
|
||||
* This means that we effectively have only three valid
|
||||
* configurations with alpha, all of them with the alpha being
|
||||
* on pipe1 with the lowest position, which can be 1, 2 or 3
|
||||
* depending on the number of planes and their zpos.
|
||||
* This means that on the affected platforms, we effectively
|
||||
* have only three valid configurations with alpha, all of
|
||||
* them with the alpha being on pipe1 with the lowest
|
||||
* position, which can be 1, 2 or 3 depending on the number of
|
||||
* planes and their zpos.
|
||||
*/
|
||||
if (num_alpha_planes > SUN4I_BACKEND_NUM_ALPHA_LAYERS) {
|
||||
|
||||
/* For platforms that are not affected by the issue described above. */
|
||||
if (backend->quirks->supports_lowest_plane_alpha)
|
||||
num_alpha_planes_max++;
|
||||
|
||||
if (num_alpha_planes > num_alpha_planes_max) {
|
||||
DRM_DEBUG_DRIVER("Too many planes with alpha, rejecting...\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* We can't have an alpha plane at the lowest position */
|
||||
if (plane_states[0]->fb->format->has_alpha ||
|
||||
(plane_states[0]->alpha != DRM_BLEND_ALPHA_OPAQUE))
|
||||
if (!backend->quirks->supports_lowest_plane_alpha &&
|
||||
(plane_states[0]->fb->format->has_alpha ||
|
||||
(plane_states[0]->alpha != DRM_BLEND_ALPHA_OPAQUE)))
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 1; i < num_planes; i++) {
|
||||
@ -876,6 +863,8 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
|
||||
: SUN4I_BACKEND_MODCTL_OUT_LCD0));
|
||||
}
|
||||
|
||||
backend->quirks = quirks;
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_ram_clk:
|
||||
@ -935,9 +924,11 @@ static const struct sun4i_backend_quirks sun6i_backend_quirks = {
|
||||
|
||||
static const struct sun4i_backend_quirks sun7i_backend_quirks = {
|
||||
.needs_output_muxing = true,
|
||||
.supports_lowest_plane_alpha = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_backend_quirks sun8i_a33_backend_quirks = {
|
||||
.supports_lowest_plane_alpha = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_backend_quirks sun9i_backend_quirks = {
|
||||
|
@ -167,7 +167,6 @@
|
||||
#define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p)))
|
||||
|
||||
#define SUN4I_BACKEND_NUM_LAYERS 4
|
||||
#define SUN4I_BACKEND_NUM_ALPHA_LAYERS 1
|
||||
#define SUN4I_BACKEND_NUM_FRONTEND_LAYERS 1
|
||||
#define SUN4I_BACKEND_NUM_YUV_PLANES 1
|
||||
|
||||
@ -187,6 +186,8 @@ struct sun4i_backend {
|
||||
/* Protects against races in the frontend teardown */
|
||||
spinlock_t frontend_lock;
|
||||
bool frontend_teardown;
|
||||
|
||||
const struct sun4i_backend_quirks *quirks;
|
||||
};
|
||||
|
||||
static inline struct sun4i_backend *
|
||||
|
@ -61,22 +61,6 @@ static struct drm_driver sun4i_drv_driver = {
|
||||
/* Frame Buffer Operations */
|
||||
};
|
||||
|
||||
static void sun4i_remove_framebuffers(void)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return;
|
||||
|
||||
/* The framebuffer can be located anywhere in RAM */
|
||||
ap->ranges[0].base = 0;
|
||||
ap->ranges[0].size = ~0;
|
||||
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "sun4i-drm-fb", false);
|
||||
kfree(ap);
|
||||
}
|
||||
|
||||
static int sun4i_drv_bind(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm;
|
||||
@ -119,7 +103,7 @@ static int sun4i_drv_bind(struct device *dev)
|
||||
drm->irq_enabled = true;
|
||||
|
||||
/* Remove early framebuffers (ie. simplefb) */
|
||||
sun4i_remove_framebuffers();
|
||||
drm_fb_helper_remove_conflicting_framebuffers(NULL, "sun4i-drm-fb", false);
|
||||
|
||||
/* Create our framebuffer */
|
||||
ret = sun4i_framebuffer_init(drm);
|
||||
@ -421,6 +405,7 @@ static const struct of_device_id sun4i_drv_of_table[] = {
|
||||
{ .compatible = "allwinner,sun8i-r40-display-engine" },
|
||||
{ .compatible = "allwinner,sun8i-v3s-display-engine" },
|
||||
{ .compatible = "allwinner,sun9i-a80-display-engine" },
|
||||
{ .compatible = "allwinner,sun50i-a64-display-engine" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);
|
||||
|
@ -35,9 +35,7 @@ static void sun4i_backend_layer_reset(struct drm_plane *plane)
|
||||
|
||||
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
||||
if (state) {
|
||||
plane->state = &state->state;
|
||||
plane->state->plane = plane;
|
||||
plane->state->alpha = DRM_BLEND_ALPHA_OPAQUE;
|
||||
__drm_atomic_helper_plane_reset(plane, &state->state);
|
||||
plane->state->zpos = layer->id;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <drm/drm_modes.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include <uapi/drm/drm_mode.h>
|
||||
|
||||
@ -35,6 +36,7 @@
|
||||
#include "sun4i_rgb.h"
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sun6i_mipi_dsi.h"
|
||||
#include "sun8i_tcon_top.h"
|
||||
#include "sunxi_engine.h"
|
||||
|
||||
static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder)
|
||||
@ -474,6 +476,33 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
|
||||
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
||||
val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
|
||||
|
||||
/*
|
||||
* On A20 and similar SoCs, the only way to achieve Positive Edge
|
||||
* (Rising Edge), is setting dclk clock phase to 2/3(240°).
|
||||
* By default TCON works in Negative Edge(Falling Edge),
|
||||
* this is why phase is set to 0 in that case.
|
||||
* Unfortunately there's no way to logically invert dclk through
|
||||
* IO_POL register.
|
||||
* The only acceptable way to work, triple checked with scope,
|
||||
* is using clock phase set to 0° for Negative Edge and set to 240°
|
||||
* for Positive Edge.
|
||||
* On A33 and similar SoCs there would be a 90° phase option,
|
||||
* but it divides also dclk by 2.
|
||||
* Following code is a way to avoid quirks all around TCON
|
||||
* and DOTCLOCK drivers.
|
||||
*/
|
||||
if (!IS_ERR(tcon->panel)) {
|
||||
struct drm_panel *panel = tcon->panel;
|
||||
struct drm_connector *connector = panel->connector;
|
||||
struct drm_display_info display_info = connector->display_info;
|
||||
|
||||
if (display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_POSEDGE)
|
||||
clk_set_phase(tcon->dclk, 240);
|
||||
|
||||
if (display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
|
||||
clk_set_phase(tcon->dclk, 0);
|
||||
}
|
||||
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG,
|
||||
SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE,
|
||||
val);
|
||||
@ -880,6 +909,36 @@ static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv,
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static bool sun4i_tcon_connected_to_tcon_top(struct device_node *node)
|
||||
{
|
||||
struct device_node *remote;
|
||||
bool ret = false;
|
||||
|
||||
remote = of_graph_get_remote_node(node, 0, -1);
|
||||
if (remote) {
|
||||
ret = !!of_match_node(sun8i_tcon_top_of_table, remote);
|
||||
of_node_put(remote);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sun4i_tcon_get_index(struct sun4i_drv *drv)
|
||||
{
|
||||
struct list_head *pos;
|
||||
int size = 0;
|
||||
|
||||
/*
|
||||
* Because TCON is added to the list at the end of the probe
|
||||
* (after this function is called), index of the current TCON
|
||||
* will be same as current TCON list size.
|
||||
*/
|
||||
list_for_each(pos, &drv->tcon_list)
|
||||
++size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* On SoCs with the old display pipeline design (Display Engine 1.0),
|
||||
* we assumed the TCON was always tied to just one backend. However
|
||||
@ -928,8 +987,24 @@ static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv,
|
||||
* connections between the backend and TCON?
|
||||
*/
|
||||
if (of_get_child_count(port) > 1) {
|
||||
/* Get our ID directly from an upstream endpoint */
|
||||
int id = sun4i_tcon_of_get_id_from_port(port);
|
||||
int id;
|
||||
|
||||
/*
|
||||
* When pipeline has the same number of TCONs and engines which
|
||||
* are represented by frontends/backends (DE1) or mixers (DE2),
|
||||
* we match them by their respective IDs. However, if pipeline
|
||||
* contains TCON TOP, chances are that there are either more
|
||||
* TCONs than engines (R40) or TCONs with non-consecutive ids.
|
||||
* (H6). In that case it's easier just use TCON index in list
|
||||
* as an id. That means that on R40, any 2 TCONs can be enabled
|
||||
* in DT out of 4 (there are 2 mixers). Due to the design of
|
||||
* TCON TOP, remaining 2 TCONs can't be connected to anything
|
||||
* anyway.
|
||||
*/
|
||||
if (sun4i_tcon_connected_to_tcon_top(node))
|
||||
id = sun4i_tcon_get_index(drv);
|
||||
else
|
||||
id = sun4i_tcon_of_get_id_from_port(port);
|
||||
|
||||
/* Get our engine by matching our ID */
|
||||
engine = sun4i_tcon_get_engine_by_id(drv, id);
|
||||
@ -1244,6 +1319,40 @@ static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder)
|
||||
{
|
||||
struct device_node *port, *remote;
|
||||
struct platform_device *pdev;
|
||||
int id, ret;
|
||||
|
||||
/* find TCON TOP platform device and TCON id */
|
||||
|
||||
port = of_graph_get_port_by_id(tcon->dev->of_node, 0);
|
||||
if (!port)
|
||||
return -EINVAL;
|
||||
|
||||
id = sun4i_tcon_of_get_id_from_port(port);
|
||||
of_node_put(port);
|
||||
|
||||
remote = of_graph_get_remote_node(tcon->dev->of_node, 0, -1);
|
||||
if (!remote)
|
||||
return -EINVAL;
|
||||
|
||||
pdev = of_find_device_by_node(remote);
|
||||
of_node_put(remote);
|
||||
if (!pdev)
|
||||
return -EINVAL;
|
||||
|
||||
if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS) {
|
||||
ret = sun8i_tcon_top_set_hdmi_src(&pdev->dev, id);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return sun8i_tcon_top_de_config(&pdev->dev, tcon->id, id);
|
||||
}
|
||||
|
||||
static const struct sun4i_tcon_quirks sun4i_a10_quirks = {
|
||||
.has_channel_0 = true,
|
||||
.has_channel_1 = true,
|
||||
@ -1291,6 +1400,11 @@ static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = {
|
||||
.has_channel_1 = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun8i_r40_tv_quirks = {
|
||||
.has_channel_1 = true,
|
||||
.set_mux = sun8i_r40_tcon_tv_set_mux,
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun8i_v3s_quirks = {
|
||||
.has_channel_0 = true,
|
||||
};
|
||||
@ -1315,6 +1429,7 @@ const struct of_device_id sun4i_tcon_of_table[] = {
|
||||
{ .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks },
|
||||
{ .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks },
|
||||
{ .compatible = "allwinner,sun8i-a83t-tcon-tv", .data = &sun8i_a83t_tv_quirks },
|
||||
{ .compatible = "allwinner,sun8i-r40-tcon-tv", .data = &sun8i_r40_tv_quirks },
|
||||
{ .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks },
|
||||
{ .compatible = "allwinner,sun9i-a80-tcon-lcd", .data = &sun9i_a80_tcon_lcd_quirks },
|
||||
{ .compatible = "allwinner,sun9i-a80-tcon-tv", .data = &sun9i_a80_tcon_tv_quirks },
|
||||
|
@ -125,10 +125,22 @@ static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
|
||||
return PTR_ERR(hdmi->clk_tmds);
|
||||
}
|
||||
|
||||
hdmi->regulator = devm_regulator_get(dev, "hvcc");
|
||||
if (IS_ERR(hdmi->regulator)) {
|
||||
dev_err(dev, "Couldn't get regulator\n");
|
||||
return PTR_ERR(hdmi->regulator);
|
||||
}
|
||||
|
||||
ret = regulator_enable(hdmi->regulator);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable regulator\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_control_deassert(hdmi->rst_ctrl);
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not deassert ctrl reset control\n");
|
||||
return ret;
|
||||
goto err_disable_regulator;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(hdmi->clk_tmds);
|
||||
@ -183,6 +195,8 @@ err_disable_clk_tmds:
|
||||
clk_disable_unprepare(hdmi->clk_tmds);
|
||||
err_assert_ctrl_reset:
|
||||
reset_control_assert(hdmi->rst_ctrl);
|
||||
err_disable_regulator:
|
||||
regulator_disable(hdmi->regulator);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -196,6 +210,7 @@ static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
|
||||
sun8i_hdmi_phy_remove(hdmi);
|
||||
clk_disable_unprepare(hdmi->clk_tmds);
|
||||
reset_control_assert(hdmi->rst_ctrl);
|
||||
regulator_disable(hdmi->regulator);
|
||||
}
|
||||
|
||||
static const struct component_ops sun8i_dw_hdmi_ops = {
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#define SUN8I_HDMI_PHY_DBG_CTRL_REG 0x0000
|
||||
@ -176,6 +177,7 @@ struct sun8i_dw_hdmi {
|
||||
struct drm_encoder encoder;
|
||||
struct sun8i_hdmi_phy *phy;
|
||||
struct dw_hdmi_plat_data plat_data;
|
||||
struct regulator *regulator;
|
||||
struct reset_control *rst_ctrl;
|
||||
};
|
||||
|
||||
|
@ -569,6 +569,22 @@ static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
|
||||
.mod_rate = 150000000,
|
||||
};
|
||||
|
||||
static const struct sun8i_mixer_cfg sun50i_a64_mixer0_cfg = {
|
||||
.ccsc = 0,
|
||||
.mod_rate = 297000000,
|
||||
.scaler_mask = 0xf,
|
||||
.ui_num = 3,
|
||||
.vi_num = 1,
|
||||
};
|
||||
|
||||
static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = {
|
||||
.ccsc = 1,
|
||||
.mod_rate = 297000000,
|
||||
.scaler_mask = 0x3,
|
||||
.ui_num = 1,
|
||||
.vi_num = 1,
|
||||
};
|
||||
|
||||
static const struct of_device_id sun8i_mixer_of_table[] = {
|
||||
{
|
||||
.compatible = "allwinner,sun8i-a83t-de2-mixer-0",
|
||||
@ -594,6 +610,14 @@ static const struct of_device_id sun8i_mixer_of_table[] = {
|
||||
.compatible = "allwinner,sun8i-v3s-de2-mixer",
|
||||
.data = &sun8i_v3s_mixer_cfg,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun50i-a64-de2-mixer-0",
|
||||
.data = &sun50i_a64_mixer0_cfg,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun50i-a64-de2-mixer-1",
|
||||
.data = &sun50i_a64_mixer1_cfg,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun8i_mixer_of_table);
|
||||
|
@ -129,8 +129,7 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
|
||||
if (!tcon_top)
|
||||
return -ENOMEM;
|
||||
|
||||
clk_data = devm_kzalloc(dev, sizeof(*clk_data) +
|
||||
sizeof(*clk_data->hws) * CLK_NUM,
|
||||
clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, CLK_NUM),
|
||||
GFP_KERNEL);
|
||||
if (!clk_data)
|
||||
return -ENOMEM;
|
||||
|
@ -1187,6 +1187,10 @@ static int host1x_drm_probe(struct host1x_device *dev)
|
||||
|
||||
dev_set_drvdata(&dev->dev, drm);
|
||||
|
||||
err = drm_fb_helper_remove_conflicting_framebuffers(NULL, "tegradrmfb", false);
|
||||
if (err < 0)
|
||||
goto unref;
|
||||
|
||||
err = drm_dev_register(drm, 0);
|
||||
if (err < 0)
|
||||
goto unref;
|
||||
|
@ -135,7 +135,7 @@ static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
|
||||
/*
|
||||
* We don't embed drm_device, because that prevent us from using
|
||||
* devm_kzalloc() to allocate tinydrm_device in the driver since
|
||||
* drm_dev_unref() frees the structure. The devm_ functions provide
|
||||
* drm_dev_put() frees the structure. The devm_ functions provide
|
||||
* for easy error handling.
|
||||
*/
|
||||
drm = drm_dev_alloc(driver, parent);
|
||||
@ -155,7 +155,7 @@ static void tinydrm_fini(struct tinydrm_device *tdev)
|
||||
drm_mode_config_cleanup(tdev->drm);
|
||||
mutex_destroy(&tdev->dirty_lock);
|
||||
tdev->drm->dev_private = NULL;
|
||||
drm_dev_unref(tdev->drm);
|
||||
drm_dev_put(tdev->drm);
|
||||
}
|
||||
|
||||
static void devm_tinydrm_release(void *data)
|
||||
@ -172,7 +172,7 @@ static void devm_tinydrm_release(void *data)
|
||||
*
|
||||
* This function initializes @tdev, the underlying DRM device and it's
|
||||
* mode_config. Resources will be automatically freed on driver detach (devres)
|
||||
* using drm_mode_config_cleanup() and drm_dev_unref().
|
||||
* using drm_mode_config_cleanup() and drm_dev_put().
|
||||
*
|
||||
* Returns:
|
||||
* Zero on success, negative error code on failure.
|
||||
|
@ -248,24 +248,6 @@ static void vc4_match_add_drivers(struct device *dev,
|
||||
}
|
||||
}
|
||||
|
||||
static void vc4_kick_out_firmware_fb(void)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return;
|
||||
|
||||
/* Since VC4 is a UMA device, the simplefb node may have been
|
||||
* located anywhere in memory.
|
||||
*/
|
||||
ap->ranges[0].base = 0;
|
||||
ap->ranges[0].size = ~0;
|
||||
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "vc4drmfb", false);
|
||||
kfree(ap);
|
||||
}
|
||||
|
||||
static int vc4_drm_bind(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
@ -298,7 +280,7 @@ static int vc4_drm_bind(struct device *dev)
|
||||
if (ret)
|
||||
goto gem_destroy;
|
||||
|
||||
vc4_kick_out_firmware_fb();
|
||||
drm_fb_helper_remove_conflicting_framebuffers(NULL, "vc4drmfb", false);
|
||||
|
||||
ret = drm_dev_register(drm, 0);
|
||||
if (ret < 0)
|
||||
|
@ -200,9 +200,7 @@ static void vc4_plane_reset(struct drm_plane *plane)
|
||||
if (!vc4_state)
|
||||
return;
|
||||
|
||||
plane->state = &vc4_state->base;
|
||||
plane->state->alpha = DRM_BLEND_ALPHA_OPAQUE;
|
||||
vc4_state->base.plane = plane;
|
||||
__drm_atomic_helper_plane_reset(plane, &vc4_state->base);
|
||||
}
|
||||
|
||||
static void vc4_dlist_write(struct vc4_plane_state *vc4_state, u32 val)
|
||||
|
@ -504,7 +504,7 @@ out_free:
|
||||
static void __exit vgem_exit(void)
|
||||
{
|
||||
drm_dev_unregister(&vgem_device->drm);
|
||||
drm_dev_unref(&vgem_device->drm);
|
||||
drm_dev_put(&vgem_device->drm);
|
||||
}
|
||||
|
||||
module_init(vgem_init);
|
||||
|
@ -43,16 +43,6 @@ static const char *vgem_fence_get_timeline_name(struct dma_fence *fence)
|
||||
return "unbound";
|
||||
}
|
||||
|
||||
static bool vgem_fence_signaled(struct dma_fence *fence)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool vgem_fence_enable_signaling(struct dma_fence *fence)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void vgem_fence_release(struct dma_fence *base)
|
||||
{
|
||||
struct vgem_fence *fence = container_of(base, typeof(*fence), base);
|
||||
@ -76,9 +66,6 @@ static void vgem_fence_timeline_value_str(struct dma_fence *fence, char *str,
|
||||
static const struct dma_fence_ops vgem_fence_ops = {
|
||||
.get_driver_name = vgem_fence_get_driver_name,
|
||||
.get_timeline_name = vgem_fence_get_timeline_name,
|
||||
.enable_signaling = vgem_fence_enable_signaling,
|
||||
.signaled = vgem_fence_signaled,
|
||||
.wait = dma_fence_default_wait,
|
||||
.release = vgem_fence_release,
|
||||
|
||||
.fence_value_str = vgem_fence_value_str,
|
||||
|
@ -109,6 +109,9 @@ static void virtio_gpu_crtc_mode_set_nofb(struct drm_crtc *crtc)
|
||||
static void virtio_gpu_crtc_atomic_enable(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *old_state)
|
||||
{
|
||||
struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc);
|
||||
|
||||
output->enabled = true;
|
||||
}
|
||||
|
||||
static void virtio_gpu_crtc_atomic_disable(struct drm_crtc *crtc,
|
||||
@ -119,6 +122,7 @@ static void virtio_gpu_crtc_atomic_disable(struct drm_crtc *crtc,
|
||||
struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc);
|
||||
|
||||
virtio_gpu_cmd_set_scanout(vgdev, output->index, 0, 0, 0, 0, 0);
|
||||
output->enabled = false;
|
||||
}
|
||||
|
||||
static int virtio_gpu_crtc_atomic_check(struct drm_crtc *crtc,
|
||||
|
@ -28,26 +28,6 @@
|
||||
|
||||
#include "virtgpu_drv.h"
|
||||
|
||||
static void virtio_pci_kick_out_firmware_fb(struct pci_dev *pci_dev)
|
||||
{
|
||||
struct apertures_struct *ap;
|
||||
bool primary;
|
||||
|
||||
ap = alloc_apertures(1);
|
||||
if (!ap)
|
||||
return;
|
||||
|
||||
ap->ranges[0].base = pci_resource_start(pci_dev, 0);
|
||||
ap->ranges[0].size = pci_resource_len(pci_dev, 0);
|
||||
|
||||
primary = pci_dev->resource[PCI_ROM_RESOURCE].flags
|
||||
& IORESOURCE_ROM_SHADOW;
|
||||
|
||||
drm_fb_helper_remove_conflicting_framebuffers(ap, "virtiodrmfb", primary);
|
||||
|
||||
kfree(ap);
|
||||
}
|
||||
|
||||
int drm_virtio_init(struct drm_driver *driver, struct virtio_device *vdev)
|
||||
{
|
||||
struct drm_device *dev;
|
||||
@ -69,7 +49,9 @@ int drm_virtio_init(struct drm_driver *driver, struct virtio_device *vdev)
|
||||
pname);
|
||||
dev->pdev = pdev;
|
||||
if (vga)
|
||||
virtio_pci_kick_out_firmware_fb(pdev);
|
||||
drm_fb_helper_remove_conflicting_pci_framebuffers(pdev,
|
||||
0,
|
||||
"virtiodrmfb");
|
||||
|
||||
snprintf(unique, sizeof(unique), "pci:%s", pname);
|
||||
ret = drm_dev_set_unique(dev, unique);
|
||||
@ -85,6 +67,6 @@ int drm_virtio_init(struct drm_driver *driver, struct virtio_device *vdev)
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
drm_dev_unref(dev);
|
||||
drm_dev_put(dev);
|
||||
return ret;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user