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:
Dave Airlie 2018-09-07 09:34:04 +10:00
commit f5169a17af
130 changed files with 4529 additions and 691 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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