drm-misc-next for 4.19:

Cross-subsystem Changes:
 devicetree documentation
 dt-bindings defintions for sun8i (Jernej Skrabec)
 
 Core Changes:
 Consider drivers setting DRIVER_ATOMIC as atomic (Eric Anholt)
 Improvements for in-kernel clients (Noralf Trønnes)
 Export and rename drm_crtc_port_mask() (Jernej Skrabec)
 
 Driver Changes:
 v3d: Add looking for GPU scheduler jobs management (Eric Anholt)
 Add Ilitek ILI9881c panel driver(Maxime Ripard)
 rockchip: vop: fixup linebuffer mode calc error (Sandy Huang)
 tinydrm: new driver for ILI9341 display panels (David Lechner)
 sun4i: Add TCON TOP driver (Jernej Skrabec)
 -----BEGIN PGP SIGNATURE-----
 
 iQIcBAABAgAGBQJbNDKgAAoJEEN0HIUfOBk0+fkP/RLj9BYlPQcxbhXr8Z7fWX2p
 6CoTLrfa1AB3kaNFXFdYZBgELFC7pIjcEaRrY+3X9LoLf/KHuxtRsB4MlbLmr8e0
 qLaz3lNveVeJsi/8OM3m10lP63groZwOjaSII2dz4V4OFrRWdJ6MSIc7gtHh1Iy6
 Gfmj3OlqTGjhBjUpNf1WThIsmb+24r2BbwTNrS1iQnPkQOoBHMu7jMBVa9EhospM
 riZY3wJNNokLjxdIDEc5g01FnWMHh+Z8EcXIrV2jP52aJkltqFy1fXmPBG21cVtr
 1wiC66Flhpxv0yLxGMMulj+NdZwnCEErQqh2A/wYlhOY7sin7Fmk7Y5llhj9pUNy
 ODRSZrtRRVyLWpfGjwcaSOTcYGk8HHhqU8MoLQagXdV3StoZivlIf2Vh8I7K64Ik
 k9hc8Ugg/V26FUyY8d4vaXP6suE/ev4oIrZNxKdFDIHPc4/4qp3TCncNeLft4Br6
 dg8qZTK47nMyPtEvm/Q+9B3UxZ85sAlPmRW0Ji/bEt06OG11zLcWNd+BuQ8JCAlk
 aad+H/j/xerpQ6mp6PJ/islN3oevW4gR/x/eKZ3Xdt7RJY/yy4CLsxRuiGsSzY7T
 XVSXrTuDnn0Judc9oETZAzZZeIhKiq+dqKnifr7imJKmXGxAx04LO0cToIzaOTsM
 5gb+TL2IeQtYMfggNcZe
 =IXNj
 -----END PGP SIGNATURE-----

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

drm-misc-next for 4.19:

Cross-subsystem Changes:
devicetree documentation
dt-bindings defintions for sun8i (Jernej Skrabec)

Core Changes:
Consider drivers setting DRIVER_ATOMIC as atomic (Eric Anholt)
Improvements for in-kernel clients (Noralf Trønnes)
Export and rename drm_crtc_port_mask() (Jernej Skrabec)

Driver Changes:
v3d: Add looking for GPU scheduler jobs management (Eric Anholt)
Add Ilitek ILI9881c panel driver(Maxime Ripard)
rockchip: vop: fixup linebuffer mode calc error (Sandy Huang)
tinydrm: new driver for ILI9341 display panels (David Lechner)
sun4i: Add TCON TOP driver (Jernej Skrabec)

Signed-off-by: Dave Airlie <airlied@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20180628010018.GA10929@juma
This commit is contained in:
Dave Airlie 2018-06-28 13:28:31 +10:00
commit eab9766931
45 changed files with 1855 additions and 322 deletions

View File

@ -0,0 +1,27 @@
Ilitek ILI9341 display panels
This binding is for display panels using an Ilitek ILI9341 controller in SPI
mode.
Required properties:
- compatible: "adafruit,yx240qv29", "ilitek,ili9341"
- dc-gpios: D/C pin
- reset-gpios: Reset pin
The node for this driver must be a child node of a SPI controller, hence
all mandatory properties described in ../spi/spi-bus.txt must be specified.
Optional properties:
- rotation: panel rotation in degrees counter clockwise (0,90,180,270)
- backlight: phandle of the backlight device attached to the panel
Example:
display@0{
compatible = "adafruit,yx240qv29", "ilitek,ili9341";
reg = <0>;
spi-max-frequency = <32000000>;
dc-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>;
rotation = <270>;
backlight = <&backlight>;
};

View File

@ -0,0 +1,20 @@
Ilitek ILI9881c based MIPI-DSI panels
Required properties:
- compatible: must be "ilitek,ili9881c" and one of:
* "bananapi,lhr050h41"
- reg: DSI virtual channel used by that screen
- power-supply: phandle to the power regulator
- reset-gpios: a GPIO phandle for the reset pin
Optional properties:
- backlight: phandle to the backlight used
Example:
panel@0 {
compatible = "bananapi,lhr050h41", "ilitek,ili9881c";
reg = <0>;
power-supply = <&reg_display>;
reset-gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL05 */
backlight = <&pwm_bl>;
};

View File

@ -101,6 +101,7 @@ DWC HDMI PHY
Required properties:
- compatible: value must be one of:
* allwinner,sun50i-a64-hdmi-phy
* allwinner,sun8i-a83t-hdmi-phy
* allwinner,sun8i-h3-hdmi-phy
- reg: base address and size of memory-mapped region
@ -111,8 +112,9 @@ Required properties:
- resets: phandle to the reset controller driving the PHY
- reset-names: must be "phy"
H3 HDMI PHY requires additional clock:
H3 and A64 HDMI PHY require additional clocks:
- pll-0: parent of phy clock
- pll-1: second possible phy clock parent (A64 only)
TV Encoder
----------
@ -187,6 +189,62 @@ And on the A23, A31, A31s and A33, you need one more clock line:
- 'lvds-alt': An alternative clock source, separate from the TCON channel 0
clock, that can be used to drive the LVDS clock
TCON TOP
--------
TCON TOPs main purpose is to configure whole display pipeline. It determines
relationships between mixers and TCONs, selects source TCON for HDMI, muxes
LCD and TV encoder GPIO output, selects TV encoder clock source and contains
additional TV TCON and DSI gates.
It allows display pipeline to be configured in very different ways:
/ LCD0/LVDS0
/ [0] TCON-LCD0
| \ MIPI DSI
mixer0 |
\ / [1] TCON-LCD1 - LCD1/LVDS1
TCON-TOP
/ \ [2] TCON-TV0 [0] - TVE0/RGB
mixer1 | \
| TCON-TOP - HDMI
| /
\ [3] TCON-TV1 [1] - TVE1/RGB
Note that both TCON TOP references same physical unit. Both mixers can be
connected to any TCON.
Required properties:
- compatible: value must be one of:
* allwinner,sun8i-r40-tcon-top
- reg: base address and size of the memory-mapped region.
- clocks: phandle to the clocks feeding the TCON TOP
* bus: TCON TOP interface clock
* tcon-tv0: TCON TV0 clock
* tve0: TVE0 clock
* tcon-tv1: TCON TV1 clock
* tve1: TVE0 clock
* dsi: MIPI DSI clock
- clock-names: clock name mentioned above
- resets: phandle to the reset line driving the TCON TOP
- #clock-cells : must contain 1
- clock-output-names: Names of clocks created for TCON TV0 channel clock,
TCON TV1 channel clock and DSI channel clock, in that order.
- ports: A ports node with endpoint definitions as defined in
Documentation/devicetree/bindings/media/video-interfaces.txt. 6 ports should
be defined:
* port 0 is input for mixer0 mux
* port 1 is output for mixer0 mux
* port 2 is input for mixer1 mux
* port 3 is output for mixer1 mux
* port 4 is input for HDMI mux
* port 5 is output for HDMI mux
All output endpoints for mixer muxes and input endpoints for HDMI mux should
have reg property with the id of the target TCON, as shown in above graph
(0-3 for mixer muxes and 0-1 for HDMI mux). All ports should have only one
endpoint connected to remote endpoint.
DRC
---

View File

@ -8,6 +8,7 @@ abracon Abracon Corporation
actions Actions Semiconductor Co., Ltd.
active-semi Active-Semi International Inc
ad Avionic Design GmbH
adafruit Adafruit Industries, LLC
adapteva Adapteva, Inc.
adaptrum Adaptrum, Inc.
adh AD Holdings Plc.

View File

@ -65,6 +65,12 @@ int drm_mode_getresources(struct drm_device *dev,
/* drm_dumb_buffers.c */
int drm_mode_create_dumb(struct drm_device *dev,
struct drm_mode_create_dumb *args,
struct drm_file *file_priv);
int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle,
struct drm_file *file_priv);
/* IOCTLs */
int drm_mode_create_dumb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv);
@ -166,14 +172,19 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y,
const struct drm_framebuffer *fb);
void drm_fb_release(struct drm_file *file_priv);
int drm_mode_addfb(struct drm_device *dev, struct drm_mode_fb_cmd *or,
struct drm_file *file_priv);
int drm_mode_rmfb(struct drm_device *dev, u32 fb_id,
struct drm_file *file_priv);
/* IOCTL */
int drm_mode_addfb(struct drm_device *dev,
void *data, struct drm_file *file_priv);
int drm_mode_addfb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv);
int drm_mode_addfb2(struct drm_device *dev,
void *data, struct drm_file *file_priv);
int drm_mode_rmfb(struct drm_device *dev,
void *data, struct drm_file *file_priv);
int drm_mode_rmfb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv);
int drm_mode_getfb(struct drm_device *dev,
void *data, struct drm_file *file_priv);
int drm_mode_dirtyfb_ioctl(struct drm_device *dev,

View File

@ -53,10 +53,10 @@
* a hardware-specific ioctl to allocate suitable buffer objects.
*/
int drm_mode_create_dumb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
int drm_mode_create_dumb(struct drm_device *dev,
struct drm_mode_create_dumb *args,
struct drm_file *file_priv)
{
struct drm_mode_create_dumb *args = data;
u32 cpp, stride, size;
if (!dev->driver->dumb_create)
@ -92,6 +92,12 @@ int drm_mode_create_dumb_ioctl(struct drm_device *dev,
return dev->driver->dumb_create(file_priv, dev, args);
}
int drm_mode_create_dumb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
return drm_mode_create_dumb(dev, data, file_priv);
}
/**
* drm_mode_mmap_dumb_ioctl - create an mmap offset for a dumb backing storage buffer
* @dev: DRM device
@ -123,17 +129,22 @@ int drm_mode_mmap_dumb_ioctl(struct drm_device *dev,
&args->offset);
}
int drm_mode_destroy_dumb(struct drm_device *dev, u32 handle,
struct drm_file *file_priv)
{
if (!dev->driver->dumb_create)
return -ENOSYS;
if (dev->driver->dumb_destroy)
return dev->driver->dumb_destroy(file_priv, dev, handle);
else
return drm_gem_dumb_destroy(file_priv, dev, handle);
}
int drm_mode_destroy_dumb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
struct drm_mode_destroy_dumb *args = data;
if (!dev->driver->dumb_create)
return -ENOSYS;
if (dev->driver->dumb_destroy)
return dev->driver->dumb_destroy(file_priv, dev, args->handle);
else
return drm_gem_dumb_destroy(file_priv, dev, args->handle);
return drm_mode_destroy_dumb(dev, args->handle, file_priv);
}

View File

@ -101,6 +101,166 @@ DEFINE_MUTEX(drm_global_mutex);
static int drm_open_helper(struct file *filp, struct drm_minor *minor);
/**
* drm_file_alloc - allocate file context
* @minor: minor to allocate on
*
* This allocates a new DRM file context. It is not linked into any context and
* can be used by the caller freely. Note that the context keeps a pointer to
* @minor, so it must be freed before @minor is.
*
* RETURNS:
* Pointer to newly allocated context, ERR_PTR on failure.
*/
struct drm_file *drm_file_alloc(struct drm_minor *minor)
{
struct drm_device *dev = minor->dev;
struct drm_file *file;
int ret;
file = kzalloc(sizeof(*file), GFP_KERNEL);
if (!file)
return ERR_PTR(-ENOMEM);
file->pid = get_pid(task_pid(current));
file->minor = minor;
/* for compatibility root is always authenticated */
file->authenticated = capable(CAP_SYS_ADMIN);
file->lock_count = 0;
INIT_LIST_HEAD(&file->lhead);
INIT_LIST_HEAD(&file->fbs);
mutex_init(&file->fbs_lock);
INIT_LIST_HEAD(&file->blobs);
INIT_LIST_HEAD(&file->pending_event_list);
INIT_LIST_HEAD(&file->event_list);
init_waitqueue_head(&file->event_wait);
file->event_space = 4096; /* set aside 4k for event buffer */
mutex_init(&file->event_read_lock);
if (drm_core_check_feature(dev, DRIVER_GEM))
drm_gem_open(dev, file);
if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
drm_syncobj_open(file);
if (drm_core_check_feature(dev, DRIVER_PRIME))
drm_prime_init_file_private(&file->prime);
if (dev->driver->open) {
ret = dev->driver->open(dev, file);
if (ret < 0)
goto out_prime_destroy;
}
return file;
out_prime_destroy:
if (drm_core_check_feature(dev, DRIVER_PRIME))
drm_prime_destroy_file_private(&file->prime);
if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
drm_syncobj_release(file);
if (drm_core_check_feature(dev, DRIVER_GEM))
drm_gem_release(dev, file);
put_pid(file->pid);
kfree(file);
return ERR_PTR(ret);
}
static void drm_events_release(struct drm_file *file_priv)
{
struct drm_device *dev = file_priv->minor->dev;
struct drm_pending_event *e, *et;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
/* Unlink pending events */
list_for_each_entry_safe(e, et, &file_priv->pending_event_list,
pending_link) {
list_del(&e->pending_link);
e->file_priv = NULL;
}
/* Remove unconsumed events */
list_for_each_entry_safe(e, et, &file_priv->event_list, link) {
list_del(&e->link);
kfree(e);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
/**
* drm_file_free - free file context
* @file: context to free, or NULL
*
* This destroys and deallocates a DRM file context previously allocated via
* drm_file_alloc(). The caller must make sure to unlink it from any contexts
* before calling this.
*
* If NULL is passed, this is a no-op.
*
* RETURNS:
* 0 on success, or error code on failure.
*/
void drm_file_free(struct drm_file *file)
{
struct drm_device *dev;
if (!file)
return;
dev = file->minor->dev;
DRM_DEBUG("pid = %d, device = 0x%lx, open_count = %d\n",
task_pid_nr(current),
(long)old_encode_dev(file->minor->kdev->devt),
dev->open_count);
if (drm_core_check_feature(dev, DRIVER_LEGACY) &&
dev->driver->preclose)
dev->driver->preclose(dev, file);
if (drm_core_check_feature(dev, DRIVER_LEGACY))
drm_legacy_lock_release(dev, file->filp);
if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
drm_legacy_reclaim_buffers(dev, file);
drm_events_release(file);
if (drm_core_check_feature(dev, DRIVER_MODESET)) {
drm_fb_release(file);
drm_property_destroy_user_blobs(dev, file);
}
if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
drm_syncobj_release(file);
if (drm_core_check_feature(dev, DRIVER_GEM))
drm_gem_release(dev, file);
drm_legacy_ctxbitmap_flush(dev, file);
if (drm_is_primary_client(file))
drm_master_release(file);
if (dev->driver->postclose)
dev->driver->postclose(dev, file);
if (drm_core_check_feature(dev, DRIVER_PRIME))
drm_prime_destroy_file_private(&file->prime);
WARN_ON(!list_empty(&file->event_list));
put_pid(file->pid);
kfree(file);
}
static int drm_setup(struct drm_device * dev)
{
int ret;
@ -207,51 +367,21 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
DRM_DEBUG("pid = %d, minor = %d\n", task_pid_nr(current), minor->index);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv = drm_file_alloc(minor);
if (IS_ERR(priv))
return PTR_ERR(priv);
if (drm_is_primary_client(priv)) {
ret = drm_master_open(priv);
if (ret) {
drm_file_free(priv);
return ret;
}
}
filp->private_data = priv;
filp->f_mode |= FMODE_UNSIGNED_OFFSET;
priv->filp = filp;
priv->pid = get_pid(task_pid(current));
priv->minor = minor;
/* for compatibility root is always authenticated */
priv->authenticated = capable(CAP_SYS_ADMIN);
priv->lock_count = 0;
INIT_LIST_HEAD(&priv->lhead);
INIT_LIST_HEAD(&priv->fbs);
mutex_init(&priv->fbs_lock);
INIT_LIST_HEAD(&priv->blobs);
INIT_LIST_HEAD(&priv->pending_event_list);
INIT_LIST_HEAD(&priv->event_list);
init_waitqueue_head(&priv->event_wait);
priv->event_space = 4096; /* set aside 4k for event buffer */
mutex_init(&priv->event_read_lock);
if (drm_core_check_feature(dev, DRIVER_GEM))
drm_gem_open(dev, priv);
if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
drm_syncobj_open(priv);
if (drm_core_check_feature(dev, DRIVER_PRIME))
drm_prime_init_file_private(&priv->prime);
if (dev->driver->open) {
ret = dev->driver->open(dev, priv);
if (ret < 0)
goto out_prime_destroy;
}
if (drm_is_primary_client(priv)) {
ret = drm_master_open(priv);
if (ret)
goto out_close;
}
mutex_lock(&dev->filelist_mutex);
list_add(&priv->lhead, &dev->filelist);
@ -278,45 +408,6 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
#endif
return 0;
out_close:
if (dev->driver->postclose)
dev->driver->postclose(dev, priv);
out_prime_destroy:
if (drm_core_check_feature(dev, DRIVER_PRIME))
drm_prime_destroy_file_private(&priv->prime);
if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
drm_syncobj_release(priv);
if (drm_core_check_feature(dev, DRIVER_GEM))
drm_gem_release(dev, priv);
put_pid(priv->pid);
kfree(priv);
filp->private_data = NULL;
return ret;
}
static void drm_events_release(struct drm_file *file_priv)
{
struct drm_device *dev = file_priv->minor->dev;
struct drm_pending_event *e, *et;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
/* Unlink pending events */
list_for_each_entry_safe(e, et, &file_priv->pending_event_list,
pending_link) {
list_del(&e->pending_link);
e->file_priv = NULL;
}
/* Remove unconsumed events */
list_for_each_entry_safe(e, et, &file_priv->event_list, link) {
list_del(&e->link);
kfree(e);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
static void drm_legacy_dev_reinit(struct drm_device *dev)
@ -383,57 +474,7 @@ int drm_release(struct inode *inode, struct file *filp)
list_del(&file_priv->lhead);
mutex_unlock(&dev->filelist_mutex);
if (drm_core_check_feature(dev, DRIVER_LEGACY) &&
dev->driver->preclose)
dev->driver->preclose(dev, file_priv);
/* ========================================================
* Begin inline drm_release
*/
DRM_DEBUG("pid = %d, device = 0x%lx, open_count = %d\n",
task_pid_nr(current),
(long)old_encode_dev(file_priv->minor->kdev->devt),
dev->open_count);
if (drm_core_check_feature(dev, DRIVER_LEGACY))
drm_legacy_lock_release(dev, filp);
if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
drm_legacy_reclaim_buffers(dev, file_priv);
drm_events_release(file_priv);
if (drm_core_check_feature(dev, DRIVER_MODESET)) {
drm_fb_release(file_priv);
drm_property_destroy_user_blobs(dev, file_priv);
}
if (drm_core_check_feature(dev, DRIVER_SYNCOBJ))
drm_syncobj_release(file_priv);
if (drm_core_check_feature(dev, DRIVER_GEM))
drm_gem_release(dev, file_priv);
drm_legacy_ctxbitmap_flush(dev, file_priv);
if (drm_is_primary_client(file_priv))
drm_master_release(file_priv);
if (dev->driver->postclose)
dev->driver->postclose(dev, file_priv);
if (drm_core_check_feature(dev, DRIVER_PRIME))
drm_prime_destroy_file_private(&file_priv->prime);
WARN_ON(!list_empty(&file_priv->event_list));
put_pid(file_priv->pid);
kfree(file_priv);
/* ========================================================
* End inline drm_release
*/
drm_file_free(file_priv);
if (!--dev->open_count) {
drm_lastclose(dev);

View File

@ -95,21 +95,20 @@ int drm_framebuffer_check_src_coords(uint32_t src_x, uint32_t src_y,
/**
* drm_mode_addfb - add an FB to the graphics configuration
* @dev: drm device for the ioctl
* @data: data pointer for the ioctl
* @file_priv: drm file for the ioctl call
* @or: pointer to request structure
* @file_priv: drm file
*
* Add a new FB to the specified CRTC, given a user request. This is the
* original addfb ioctl which only supported RGB formats.
*
* Called by the user via ioctl.
* Called by the user via ioctl, or by an in-kernel client.
*
* Returns:
* Zero on success, negative errno on failure.
*/
int drm_mode_addfb(struct drm_device *dev,
void *data, struct drm_file *file_priv)
int drm_mode_addfb(struct drm_device *dev, struct drm_mode_fb_cmd *or,
struct drm_file *file_priv)
{
struct drm_mode_fb_cmd *or = data;
struct drm_mode_fb_cmd2 r = {};
int ret;
@ -134,6 +133,12 @@ int drm_mode_addfb(struct drm_device *dev,
return 0;
}
int drm_mode_addfb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
return drm_mode_addfb(dev, data, file_priv);
}
static int fb_plane_width(int width,
const struct drm_format_info *format, int plane)
{
@ -367,29 +372,28 @@ static void drm_mode_rmfb_work_fn(struct work_struct *w)
/**
* drm_mode_rmfb - remove an FB from the configuration
* @dev: drm device for the ioctl
* @data: data pointer for the ioctl
* @file_priv: drm file for the ioctl call
* @dev: drm device
* @fb_id: id of framebuffer to remove
* @file_priv: drm file
*
* Remove the FB specified by the user.
* Remove the specified FB.
*
* Called by the user via ioctl.
* Called by the user via ioctl, or by an in-kernel client.
*
* Returns:
* Zero on success, negative errno on failure.
*/
int drm_mode_rmfb(struct drm_device *dev,
void *data, struct drm_file *file_priv)
int drm_mode_rmfb(struct drm_device *dev, u32 fb_id,
struct drm_file *file_priv)
{
struct drm_framebuffer *fb = NULL;
struct drm_framebuffer *fbl = NULL;
uint32_t *id = data;
int found = 0;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EINVAL;
fb = drm_framebuffer_lookup(dev, file_priv, *id);
fb = drm_framebuffer_lookup(dev, file_priv, fb_id);
if (!fb)
return -ENOENT;
@ -435,6 +439,14 @@ int drm_mode_rmfb(struct drm_device *dev,
return -ENOENT;
}
int drm_mode_rmfb_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
uint32_t *fb_id = data;
return drm_mode_rmfb(dev, *fb_id, file_priv);
}
/**
* drm_mode_getfb - get FB info
* @dev: drm device for the ioctl

View File

@ -26,6 +26,8 @@
/* drm_file.c */
extern struct mutex drm_global_mutex;
struct drm_file *drm_file_alloc(struct drm_minor *minor);
void drm_file_free(struct drm_file *file);
void drm_lastclose(struct drm_device *dev);
/* drm_pci.c */

View File

@ -644,9 +644,9 @@ static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETPROPERTY, drm_mode_connector_property_set_ioctl, DRM_MASTER|DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPBLOB, drm_mode_getblob_ioctl, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETFB, drm_mode_getfb, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb_ioctl, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_RMFB, drm_mode_rmfb_ioctl, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_PAGE_FLIP, drm_mode_page_flip_ioctl, DRM_MASTER|DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_DIRTYFB, drm_mode_dirtyfb_ioctl, DRM_MASTER|DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_MODE_CREATE_DUMB, drm_mode_create_dumb_ioctl, DRM_UNLOCKED),

View File

@ -15,15 +15,15 @@ static void drm_release_of(struct device *dev, void *data)
}
/**
* drm_crtc_port_mask - find the mask of a registered CRTC by port OF node
* drm_of_crtc_port_mask - find the mask of a registered CRTC by port OF node
* @dev: DRM device
* @port: port OF node
*
* Given a port OF node, return the possible mask of the corresponding
* CRTC within a device's list of CRTCs. Returns zero if not found.
*/
static uint32_t drm_crtc_port_mask(struct drm_device *dev,
struct device_node *port)
uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port)
{
unsigned int index = 0;
struct drm_crtc *tmp;
@ -37,6 +37,7 @@ static uint32_t drm_crtc_port_mask(struct drm_device *dev,
return 0;
}
EXPORT_SYMBOL(drm_of_crtc_port_mask);
/**
* drm_of_find_possible_crtcs - find the possible CRTCs for an encoder port
@ -62,7 +63,7 @@ uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
return 0;
}
possible_crtcs |= drm_crtc_port_mask(dev, remote_port);
possible_crtcs |= drm_of_crtc_port_mask(dev, remote_port);
of_node_put(remote_port);
}

View File

@ -517,7 +517,7 @@ static int psb_fbdev_destroy(struct drm_device *dev, struct psb_fbdev *fbdev)
drm_framebuffer_cleanup(&psbfb->base);
if (psbfb->base.obj[0])
drm_gem_object_unreference_unlocked(psbfb->base.obj[0]);
drm_gem_object_put_unlocked(psbfb->base.obj[0]);
return 0;
}

View File

@ -93,7 +93,7 @@ int psb_gem_create(struct drm_file *file, struct drm_device *dev, u64 size,
return ret;
}
/* We have the initial and handle reference but need only one now */
drm_gem_object_unreference_unlocked(&r->gem);
drm_gem_object_put_unlocked(&r->gem);
*handlep = handle;
return 0;
}

View File

@ -60,7 +60,7 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_psb_private *dev_priv = dev->dev_private;
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
struct drm_framebuffer *fb = crtc->primary->fb;
struct gtt_range *gtt = to_gtt_range(fb->obj[0]);
struct gtt_range *gtt;
int pipe = gma_crtc->pipe;
const struct psb_offset *map = &dev_priv->regmap[pipe];
unsigned long start, offset;
@ -76,6 +76,8 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
goto gma_pipe_cleaner;
}
gtt = to_gtt_range(fb->obj[0]);
/* We are displaying this buffer, make sure it is actually loaded
into the GTT */
ret = psb_gtt_pin(gtt);
@ -353,7 +355,7 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
gt = container_of(gma_crtc->cursor_obj,
struct gtt_range, gem);
psb_gtt_unpin(gt);
drm_gem_object_unreference_unlocked(gma_crtc->cursor_obj);
drm_gem_object_put_unlocked(gma_crtc->cursor_obj);
gma_crtc->cursor_obj = NULL;
}
return 0;
@ -429,7 +431,7 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
if (gma_crtc->cursor_obj) {
gt = container_of(gma_crtc->cursor_obj, struct gtt_range, gem);
psb_gtt_unpin(gt);
drm_gem_object_unreference_unlocked(gma_crtc->cursor_obj);
drm_gem_object_put_unlocked(gma_crtc->cursor_obj);
}
gma_crtc->cursor_obj = obj;
@ -437,7 +439,7 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
return ret;
unref_cursor:
drm_gem_object_unreference_unlocked(obj);
drm_gem_object_put_unlocked(obj);
return ret;
}

View File

@ -167,7 +167,6 @@ static int mdfld__intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_psb_private *dev_priv = dev->dev_private;
struct drm_framebuffer *fb = crtc->primary->fb;
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
struct psb_framebuffer *psbfb = to_psb_fb(fb);
int pipe = gma_crtc->pipe;
const struct psb_offset *map = &dev_priv->regmap[pipe];
unsigned long start, offset;

View File

@ -859,7 +859,6 @@ static int ade_plane_atomic_check(struct drm_plane *plane,
return PTR_ERR(crtc_state);
if (src_w != crtc_w || src_h != crtc_h) {
DRM_ERROR("Scale not support!!!\n");
return -EINVAL;
}

View File

@ -46,6 +46,15 @@ config DRM_PANEL_ILITEK_IL9322
Say Y here if you want to enable support for Ilitek IL9322
QVGA (320x240) RGB, YUV and ITU-T BT.656 panels.
config DRM_PANEL_ILITEK_ILI9881C
tristate "Ilitek ILI9881C-based panels"
depends on OF
depends on DRM_MIPI_DSI
depends on BACKLIGHT_CLASS_DEVICE
help
Say Y if you want to enable support for panels based on the
Ilitek ILI9881c controller.
config DRM_PANEL_INNOLUX_P079ZCA
tristate "Innolux P079ZCA panel"
depends on OF

View File

@ -3,6 +3,7 @@ obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o
obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o
obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o

View File

@ -0,0 +1,503 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2017-2018, Bootlin
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fb.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#include <video/mipi_display.h>
struct ili9881c {
struct drm_panel panel;
struct mipi_dsi_device *dsi;
struct backlight_device *backlight;
struct regulator *power;
struct gpio_desc *reset;
};
enum ili9881c_op {
ILI9881C_SWITCH_PAGE,
ILI9881C_COMMAND,
};
struct ili9881c_instr {
enum ili9881c_op op;
union arg {
struct cmd {
u8 cmd;
u8 data;
} cmd;
u8 page;
} arg;
};
#define ILI9881C_SWITCH_PAGE_INSTR(_page) \
{ \
.op = ILI9881C_SWITCH_PAGE, \
.arg = { \
.page = (_page), \
}, \
}
#define ILI9881C_COMMAND_INSTR(_cmd, _data) \
{ \
.op = ILI9881C_COMMAND, \
.arg = { \
.cmd = { \
.cmd = (_cmd), \
.data = (_data), \
}, \
}, \
}
static const struct ili9881c_instr ili9881c_init[] = {
ILI9881C_SWITCH_PAGE_INSTR(3),
ILI9881C_COMMAND_INSTR(0x01, 0x00),
ILI9881C_COMMAND_INSTR(0x02, 0x00),
ILI9881C_COMMAND_INSTR(0x03, 0x73),
ILI9881C_COMMAND_INSTR(0x04, 0x03),
ILI9881C_COMMAND_INSTR(0x05, 0x00),
ILI9881C_COMMAND_INSTR(0x06, 0x06),
ILI9881C_COMMAND_INSTR(0x07, 0x06),
ILI9881C_COMMAND_INSTR(0x08, 0x00),
ILI9881C_COMMAND_INSTR(0x09, 0x18),
ILI9881C_COMMAND_INSTR(0x0a, 0x04),
ILI9881C_COMMAND_INSTR(0x0b, 0x00),
ILI9881C_COMMAND_INSTR(0x0c, 0x02),
ILI9881C_COMMAND_INSTR(0x0d, 0x03),
ILI9881C_COMMAND_INSTR(0x0e, 0x00),
ILI9881C_COMMAND_INSTR(0x0f, 0x25),
ILI9881C_COMMAND_INSTR(0x10, 0x25),
ILI9881C_COMMAND_INSTR(0x11, 0x00),
ILI9881C_COMMAND_INSTR(0x12, 0x00),
ILI9881C_COMMAND_INSTR(0x13, 0x00),
ILI9881C_COMMAND_INSTR(0x14, 0x00),
ILI9881C_COMMAND_INSTR(0x15, 0x00),
ILI9881C_COMMAND_INSTR(0x16, 0x0C),
ILI9881C_COMMAND_INSTR(0x17, 0x00),
ILI9881C_COMMAND_INSTR(0x18, 0x00),
ILI9881C_COMMAND_INSTR(0x19, 0x00),
ILI9881C_COMMAND_INSTR(0x1a, 0x00),
ILI9881C_COMMAND_INSTR(0x1b, 0x00),
ILI9881C_COMMAND_INSTR(0x1c, 0x00),
ILI9881C_COMMAND_INSTR(0x1d, 0x00),
ILI9881C_COMMAND_INSTR(0x1e, 0xC0),
ILI9881C_COMMAND_INSTR(0x1f, 0x80),
ILI9881C_COMMAND_INSTR(0x20, 0x04),
ILI9881C_COMMAND_INSTR(0x21, 0x01),
ILI9881C_COMMAND_INSTR(0x22, 0x00),
ILI9881C_COMMAND_INSTR(0x23, 0x00),
ILI9881C_COMMAND_INSTR(0x24, 0x00),
ILI9881C_COMMAND_INSTR(0x25, 0x00),
ILI9881C_COMMAND_INSTR(0x26, 0x00),
ILI9881C_COMMAND_INSTR(0x27, 0x00),
ILI9881C_COMMAND_INSTR(0x28, 0x33),
ILI9881C_COMMAND_INSTR(0x29, 0x03),
ILI9881C_COMMAND_INSTR(0x2a, 0x00),
ILI9881C_COMMAND_INSTR(0x2b, 0x00),
ILI9881C_COMMAND_INSTR(0x2c, 0x00),
ILI9881C_COMMAND_INSTR(0x2d, 0x00),
ILI9881C_COMMAND_INSTR(0x2e, 0x00),
ILI9881C_COMMAND_INSTR(0x2f, 0x00),
ILI9881C_COMMAND_INSTR(0x30, 0x00),
ILI9881C_COMMAND_INSTR(0x31, 0x00),
ILI9881C_COMMAND_INSTR(0x32, 0x00),
ILI9881C_COMMAND_INSTR(0x33, 0x00),
ILI9881C_COMMAND_INSTR(0x34, 0x04),
ILI9881C_COMMAND_INSTR(0x35, 0x00),
ILI9881C_COMMAND_INSTR(0x36, 0x00),
ILI9881C_COMMAND_INSTR(0x37, 0x00),
ILI9881C_COMMAND_INSTR(0x38, 0x3C),
ILI9881C_COMMAND_INSTR(0x39, 0x00),
ILI9881C_COMMAND_INSTR(0x3a, 0x00),
ILI9881C_COMMAND_INSTR(0x3b, 0x00),
ILI9881C_COMMAND_INSTR(0x3c, 0x00),
ILI9881C_COMMAND_INSTR(0x3d, 0x00),
ILI9881C_COMMAND_INSTR(0x3e, 0x00),
ILI9881C_COMMAND_INSTR(0x3f, 0x00),
ILI9881C_COMMAND_INSTR(0x40, 0x00),
ILI9881C_COMMAND_INSTR(0x41, 0x00),
ILI9881C_COMMAND_INSTR(0x42, 0x00),
ILI9881C_COMMAND_INSTR(0x43, 0x00),
ILI9881C_COMMAND_INSTR(0x44, 0x00),
ILI9881C_COMMAND_INSTR(0x50, 0x01),
ILI9881C_COMMAND_INSTR(0x51, 0x23),
ILI9881C_COMMAND_INSTR(0x52, 0x45),
ILI9881C_COMMAND_INSTR(0x53, 0x67),
ILI9881C_COMMAND_INSTR(0x54, 0x89),
ILI9881C_COMMAND_INSTR(0x55, 0xab),
ILI9881C_COMMAND_INSTR(0x56, 0x01),
ILI9881C_COMMAND_INSTR(0x57, 0x23),
ILI9881C_COMMAND_INSTR(0x58, 0x45),
ILI9881C_COMMAND_INSTR(0x59, 0x67),
ILI9881C_COMMAND_INSTR(0x5a, 0x89),
ILI9881C_COMMAND_INSTR(0x5b, 0xab),
ILI9881C_COMMAND_INSTR(0x5c, 0xcd),
ILI9881C_COMMAND_INSTR(0x5d, 0xef),
ILI9881C_COMMAND_INSTR(0x5e, 0x11),
ILI9881C_COMMAND_INSTR(0x5f, 0x02),
ILI9881C_COMMAND_INSTR(0x60, 0x02),
ILI9881C_COMMAND_INSTR(0x61, 0x02),
ILI9881C_COMMAND_INSTR(0x62, 0x02),
ILI9881C_COMMAND_INSTR(0x63, 0x02),
ILI9881C_COMMAND_INSTR(0x64, 0x02),
ILI9881C_COMMAND_INSTR(0x65, 0x02),
ILI9881C_COMMAND_INSTR(0x66, 0x02),
ILI9881C_COMMAND_INSTR(0x67, 0x02),
ILI9881C_COMMAND_INSTR(0x68, 0x02),
ILI9881C_COMMAND_INSTR(0x69, 0x02),
ILI9881C_COMMAND_INSTR(0x6a, 0x0C),
ILI9881C_COMMAND_INSTR(0x6b, 0x02),
ILI9881C_COMMAND_INSTR(0x6c, 0x0F),
ILI9881C_COMMAND_INSTR(0x6d, 0x0E),
ILI9881C_COMMAND_INSTR(0x6e, 0x0D),
ILI9881C_COMMAND_INSTR(0x6f, 0x06),
ILI9881C_COMMAND_INSTR(0x70, 0x07),
ILI9881C_COMMAND_INSTR(0x71, 0x02),
ILI9881C_COMMAND_INSTR(0x72, 0x02),
ILI9881C_COMMAND_INSTR(0x73, 0x02),
ILI9881C_COMMAND_INSTR(0x74, 0x02),
ILI9881C_COMMAND_INSTR(0x75, 0x02),
ILI9881C_COMMAND_INSTR(0x76, 0x02),
ILI9881C_COMMAND_INSTR(0x77, 0x02),
ILI9881C_COMMAND_INSTR(0x78, 0x02),
ILI9881C_COMMAND_INSTR(0x79, 0x02),
ILI9881C_COMMAND_INSTR(0x7a, 0x02),
ILI9881C_COMMAND_INSTR(0x7b, 0x02),
ILI9881C_COMMAND_INSTR(0x7c, 0x02),
ILI9881C_COMMAND_INSTR(0x7d, 0x02),
ILI9881C_COMMAND_INSTR(0x7e, 0x02),
ILI9881C_COMMAND_INSTR(0x7f, 0x02),
ILI9881C_COMMAND_INSTR(0x80, 0x0C),
ILI9881C_COMMAND_INSTR(0x81, 0x02),
ILI9881C_COMMAND_INSTR(0x82, 0x0F),
ILI9881C_COMMAND_INSTR(0x83, 0x0E),
ILI9881C_COMMAND_INSTR(0x84, 0x0D),
ILI9881C_COMMAND_INSTR(0x85, 0x06),
ILI9881C_COMMAND_INSTR(0x86, 0x07),
ILI9881C_COMMAND_INSTR(0x87, 0x02),
ILI9881C_COMMAND_INSTR(0x88, 0x02),
ILI9881C_COMMAND_INSTR(0x89, 0x02),
ILI9881C_COMMAND_INSTR(0x8A, 0x02),
ILI9881C_SWITCH_PAGE_INSTR(4),
ILI9881C_COMMAND_INSTR(0x6C, 0x15),
ILI9881C_COMMAND_INSTR(0x6E, 0x22),
ILI9881C_COMMAND_INSTR(0x6F, 0x33),
ILI9881C_COMMAND_INSTR(0x3A, 0xA4),
ILI9881C_COMMAND_INSTR(0x8D, 0x0D),
ILI9881C_COMMAND_INSTR(0x87, 0xBA),
ILI9881C_COMMAND_INSTR(0x26, 0x76),
ILI9881C_COMMAND_INSTR(0xB2, 0xD1),
ILI9881C_SWITCH_PAGE_INSTR(1),
ILI9881C_COMMAND_INSTR(0x22, 0x0A),
ILI9881C_COMMAND_INSTR(0x53, 0xDC),
ILI9881C_COMMAND_INSTR(0x55, 0xA7),
ILI9881C_COMMAND_INSTR(0x50, 0x78),
ILI9881C_COMMAND_INSTR(0x51, 0x78),
ILI9881C_COMMAND_INSTR(0x31, 0x02),
ILI9881C_COMMAND_INSTR(0x60, 0x14),
ILI9881C_COMMAND_INSTR(0xA0, 0x2A),
ILI9881C_COMMAND_INSTR(0xA1, 0x39),
ILI9881C_COMMAND_INSTR(0xA2, 0x46),
ILI9881C_COMMAND_INSTR(0xA3, 0x0e),
ILI9881C_COMMAND_INSTR(0xA4, 0x12),
ILI9881C_COMMAND_INSTR(0xA5, 0x25),
ILI9881C_COMMAND_INSTR(0xA6, 0x19),
ILI9881C_COMMAND_INSTR(0xA7, 0x1d),
ILI9881C_COMMAND_INSTR(0xA8, 0xa6),
ILI9881C_COMMAND_INSTR(0xA9, 0x1C),
ILI9881C_COMMAND_INSTR(0xAA, 0x29),
ILI9881C_COMMAND_INSTR(0xAB, 0x85),
ILI9881C_COMMAND_INSTR(0xAC, 0x1C),
ILI9881C_COMMAND_INSTR(0xAD, 0x1B),
ILI9881C_COMMAND_INSTR(0xAE, 0x51),
ILI9881C_COMMAND_INSTR(0xAF, 0x22),
ILI9881C_COMMAND_INSTR(0xB0, 0x2d),
ILI9881C_COMMAND_INSTR(0xB1, 0x4f),
ILI9881C_COMMAND_INSTR(0xB2, 0x59),
ILI9881C_COMMAND_INSTR(0xB3, 0x3F),
ILI9881C_COMMAND_INSTR(0xC0, 0x2A),
ILI9881C_COMMAND_INSTR(0xC1, 0x3a),
ILI9881C_COMMAND_INSTR(0xC2, 0x45),
ILI9881C_COMMAND_INSTR(0xC3, 0x0e),
ILI9881C_COMMAND_INSTR(0xC4, 0x11),
ILI9881C_COMMAND_INSTR(0xC5, 0x24),
ILI9881C_COMMAND_INSTR(0xC6, 0x1a),
ILI9881C_COMMAND_INSTR(0xC7, 0x1c),
ILI9881C_COMMAND_INSTR(0xC8, 0xaa),
ILI9881C_COMMAND_INSTR(0xC9, 0x1C),
ILI9881C_COMMAND_INSTR(0xCA, 0x29),
ILI9881C_COMMAND_INSTR(0xCB, 0x96),
ILI9881C_COMMAND_INSTR(0xCC, 0x1C),
ILI9881C_COMMAND_INSTR(0xCD, 0x1B),
ILI9881C_COMMAND_INSTR(0xCE, 0x51),
ILI9881C_COMMAND_INSTR(0xCF, 0x22),
ILI9881C_COMMAND_INSTR(0xD0, 0x2b),
ILI9881C_COMMAND_INSTR(0xD1, 0x4b),
ILI9881C_COMMAND_INSTR(0xD2, 0x59),
ILI9881C_COMMAND_INSTR(0xD3, 0x3F),
};
static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel)
{
return container_of(panel, struct ili9881c, panel);
}
/*
* The panel seems to accept some private DCS commands that map
* directly to registers.
*
* It is organised by page, with each page having its own set of
* registers, and the first page looks like it's holding the standard
* DCS commands.
*
* So before any attempt at sending a command or data, we have to be
* sure if we're in the right page or not.
*/
static int ili9881c_switch_page(struct ili9881c *ctx, u8 page)
{
u8 buf[4] = { 0xff, 0x98, 0x81, page };
int ret;
ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf));
if (ret < 0)
return ret;
return 0;
}
static int ili9881c_send_cmd_data(struct ili9881c *ctx, u8 cmd, u8 data)
{
u8 buf[2] = { cmd, data };
int ret;
ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf));
if (ret < 0)
return ret;
return 0;
}
static int ili9881c_prepare(struct drm_panel *panel)
{
struct ili9881c *ctx = panel_to_ili9881c(panel);
unsigned int i;
int ret;
/* Power the panel */
ret = regulator_enable(ctx->power);
if (ret)
return ret;
msleep(5);
/* And reset it */
gpiod_set_value(ctx->reset, 1);
msleep(20);
gpiod_set_value(ctx->reset, 0);
msleep(20);
for (i = 0; i < ARRAY_SIZE(ili9881c_init); i++) {
const struct ili9881c_instr *instr = &ili9881c_init[i];
if (instr->op == ILI9881C_SWITCH_PAGE)
ret = ili9881c_switch_page(ctx, instr->arg.page);
else if (instr->op == ILI9881C_COMMAND)
ret = ili9881c_send_cmd_data(ctx, instr->arg.cmd.cmd,
instr->arg.cmd.data);
if (ret)
return ret;
}
ret = ili9881c_switch_page(ctx, 0);
if (ret)
return ret;
ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
if (ret)
return ret;
mipi_dsi_dcs_exit_sleep_mode(ctx->dsi);
if (ret)
return ret;
return 0;
}
static int ili9881c_enable(struct drm_panel *panel)
{
struct ili9881c *ctx = panel_to_ili9881c(panel);
msleep(120);
mipi_dsi_dcs_set_display_on(ctx->dsi);
backlight_enable(ctx->backlight);
return 0;
}
static int ili9881c_disable(struct drm_panel *panel)
{
struct ili9881c *ctx = panel_to_ili9881c(panel);
backlight_disable(ctx->backlight);
return mipi_dsi_dcs_set_display_off(ctx->dsi);
}
static int ili9881c_unprepare(struct drm_panel *panel)
{
struct ili9881c *ctx = panel_to_ili9881c(panel);
mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
regulator_disable(ctx->power);
gpiod_set_value(ctx->reset, 1);
return 0;
}
static const struct drm_display_mode bananapi_default_mode = {
.clock = 62000,
.vrefresh = 60,
.hdisplay = 720,
.hsync_start = 720 + 10,
.hsync_end = 720 + 10 + 20,
.htotal = 720 + 10 + 20 + 30,
.vdisplay = 1280,
.vsync_start = 1280 + 10,
.vsync_end = 1280 + 10 + 10,
.vtotal = 1280 + 10 + 10 + 20,
};
static int ili9881c_get_modes(struct drm_panel *panel)
{
struct drm_connector *connector = panel->connector;
struct ili9881c *ctx = panel_to_ili9881c(panel);
struct drm_display_mode *mode;
mode = drm_mode_duplicate(panel->drm, &bananapi_default_mode);
if (!mode) {
dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n",
bananapi_default_mode.hdisplay,
bananapi_default_mode.vdisplay,
bananapi_default_mode.vrefresh);
return -ENOMEM;
}
drm_mode_set_name(mode);
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
panel->connector->display_info.width_mm = 62;
panel->connector->display_info.height_mm = 110;
return 1;
}
static const struct drm_panel_funcs ili9881c_funcs = {
.prepare = ili9881c_prepare,
.unprepare = ili9881c_unprepare,
.enable = ili9881c_enable,
.disable = ili9881c_disable,
.get_modes = ili9881c_get_modes,
};
static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi)
{
struct device_node *np;
struct ili9881c *ctx;
int ret;
ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mipi_dsi_set_drvdata(dsi, ctx);
ctx->dsi = dsi;
drm_panel_init(&ctx->panel);
ctx->panel.dev = &dsi->dev;
ctx->panel.funcs = &ili9881c_funcs;
ctx->power = devm_regulator_get(&dsi->dev, "power");
if (IS_ERR(ctx->power)) {
dev_err(&dsi->dev, "Couldn't get our power regulator\n");
return PTR_ERR(ctx->power);
}
ctx->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(ctx->reset)) {
dev_err(&dsi->dev, "Couldn't get our reset GPIO\n");
return PTR_ERR(ctx->reset);
}
np = of_parse_phandle(dsi->dev.of_node, "backlight", 0);
if (np) {
ctx->backlight = of_find_backlight_by_node(np);
of_node_put(np);
if (!ctx->backlight)
return -EPROBE_DEFER;
}
ret = drm_panel_add(&ctx->panel);
if (ret < 0)
return ret;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->lanes = 4;
return mipi_dsi_attach(dsi);
}
static int ili9881c_dsi_remove(struct mipi_dsi_device *dsi)
{
struct ili9881c *ctx = mipi_dsi_get_drvdata(dsi);
mipi_dsi_detach(dsi);
drm_panel_remove(&ctx->panel);
if (ctx->backlight)
put_device(&ctx->backlight->dev);
return 0;
}
static const struct of_device_id ili9881c_of_match[] = {
{ .compatible = "bananapi,lhr050h41" },
{ }
};
MODULE_DEVICE_TABLE(of, ili9881c_of_match);
static struct mipi_dsi_driver ili9881c_dsi_driver = {
.probe = ili9881c_dsi_probe,
.remove = ili9881c_dsi_remove,
.driver = {
.name = "ili9881c-dsi",
.of_match_table = ili9881c_of_match,
},
};
module_mipi_dsi_driver(ili9881c_dsi_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Ilitek ILI9881C Controller Driver");
MODULE_LICENSE("GPL v2");

View File

@ -1308,7 +1308,7 @@ static int vop_create_crtc(struct vop *vop)
for (i = 0; i < vop_data->win_size; i++) {
struct vop_win *vop_win = &vop->win[i];
const struct vop_win_data *win_data = vop_win->data;
unsigned long possible_crtcs = 1 << drm_crtc_index(crtc);
unsigned long possible_crtcs = drm_crtc_mask(crtc);
if (win_data->type != DRM_PLANE_TYPE_OVERLAY)
continue;

View File

@ -331,16 +331,19 @@ static inline int scl_vop_cal_lb_mode(int width, bool is_yuv)
{
int lb_mode;
if (width > 2560)
lb_mode = LB_RGB_3840X2;
else if (width > 1920)
lb_mode = LB_RGB_2560X4;
else if (!is_yuv)
lb_mode = LB_RGB_1920X5;
else if (width > 1280)
lb_mode = LB_YUV_3840X5;
else
lb_mode = LB_YUV_2560X8;
if (is_yuv) {
if (width > 1280)
lb_mode = LB_YUV_3840X5;
else
lb_mode = LB_YUV_2560X8;
} else {
if (width > 2560)
lb_mode = LB_RGB_3840X2;
else if (width > 1920)
lb_mode = LB_RGB_2560X4;
else
lb_mode = LB_RGB_1920X5;
}
return lb_mode;
}

View File

@ -36,4 +36,4 @@ obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o sun4i-frontend.o
obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o
obj-$(CONFIG_DRM_SUN6I_DSI) += sun6i-dsi.o
obj-$(CONFIG_DRM_SUN8I_DW_HDMI) += sun8i-drm-hdmi.o
obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o
obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o sun8i_tcon_top.o

View File

@ -26,6 +26,7 @@
#include "sun4i_frontend.h"
#include "sun4i_framebuffer.h"
#include "sun4i_tcon.h"
#include "sun8i_tcon_top.h"
DEFINE_DRM_GEM_CMA_FOPS(sun4i_drv_fops);
@ -197,6 +198,27 @@ static bool sun4i_drv_node_is_tcon(struct device_node *node)
return !!of_match_node(sun4i_tcon_of_table, node);
}
static bool sun4i_drv_node_is_tcon_with_ch0(struct device_node *node)
{
const struct of_device_id *match;
match = of_match_node(sun4i_tcon_of_table, node);
if (match) {
struct sun4i_tcon_quirks *quirks;
quirks = (struct sun4i_tcon_quirks *)match->data;
return quirks->has_channel_0;
}
return false;
}
static bool sun4i_drv_node_is_tcon_top(struct device_node *node)
{
return !!of_match_node(sun8i_tcon_top_of_table, node);
}
static int compare_of(struct device *dev, void *data)
{
DRM_DEBUG_DRIVER("Comparing of node %pOF with %pOF\n",
@ -231,12 +253,69 @@ struct endpoint_list {
DECLARE_KFIFO(fifo, struct device_node *, 16);
};
static void sun4i_drv_traverse_endpoints(struct endpoint_list *list,
struct device_node *node,
int port_id)
{
struct device_node *ep, *remote, *port;
port = of_graph_get_port_by_id(node, port_id);
if (!port) {
DRM_DEBUG_DRIVER("No output to bind on port %d\n", port_id);
return;
}
for_each_available_child_of_node(port, ep) {
remote = of_graph_get_remote_port_parent(ep);
if (!remote) {
DRM_DEBUG_DRIVER("Error retrieving the output node\n");
continue;
}
if (sun4i_drv_node_is_tcon(node)) {
/*
* TCON TOP is always probed before TCON. However, TCON
* points back to TCON TOP when it is source for HDMI.
* We have to skip it here to prevent infinite looping
* between TCON TOP and TCON.
*/
if (sun4i_drv_node_is_tcon_top(remote)) {
DRM_DEBUG_DRIVER("TCON output endpoint is TCON TOP... skipping\n");
of_node_put(remote);
continue;
}
/*
* If the node is our TCON with channel 0, the first
* port is used for panel or bridges, and will not be
* part of the component framework.
*/
if (sun4i_drv_node_is_tcon_with_ch0(node)) {
struct of_endpoint endpoint;
if (of_graph_parse_endpoint(ep, &endpoint)) {
DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
of_node_put(remote);
continue;
}
if (!endpoint.id) {
DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
of_node_put(remote);
continue;
}
}
}
kfifo_put(&list->fifo, remote);
}
}
static int sun4i_drv_add_endpoints(struct device *dev,
struct endpoint_list *list,
struct component_match **match,
struct device_node *node)
{
struct device_node *port, *ep, *remote;
int count = 0;
/*
@ -272,41 +351,13 @@ static int sun4i_drv_add_endpoints(struct device *dev,
count++;
}
/* Inputs are listed first, then outputs */
port = of_graph_get_port_by_id(node, 1);
if (!port) {
DRM_DEBUG_DRIVER("No output to bind\n");
return count;
}
/* each node has at least one output */
sun4i_drv_traverse_endpoints(list, node, 1);
for_each_available_child_of_node(port, ep) {
remote = of_graph_get_remote_port_parent(ep);
if (!remote) {
DRM_DEBUG_DRIVER("Error retrieving the output node\n");
of_node_put(remote);
continue;
}
/*
* If the node is our TCON, the first port is used for
* panel or bridges, and will not be part of the
* component framework.
*/
if (sun4i_drv_node_is_tcon(node)) {
struct of_endpoint endpoint;
if (of_graph_parse_endpoint(ep, &endpoint)) {
DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
continue;
}
if (!endpoint.id) {
DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
continue;
}
}
kfifo_put(&list->fifo, remote);
/* TCON TOP has second and third output */
if (sun4i_drv_node_is_tcon_top(node)) {
sun4i_drv_traverse_endpoints(list, node, 3);
sun4i_drv_traverse_endpoints(list, node, 5);
}
return count;

View File

@ -791,12 +791,14 @@ static int sun4i_tcon_init_regmap(struct device *dev,
*/
static struct sunxi_engine *
sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv,
struct device_node *node)
struct device_node *node,
u32 port_id)
{
struct device_node *port, *ep, *remote;
struct sunxi_engine *engine = ERR_PTR(-EINVAL);
u32 reg = 0;
port = of_graph_get_port_by_id(node, 0);
port = of_graph_get_port_by_id(node, port_id);
if (!port)
return ERR_PTR(-EINVAL);
@ -826,8 +828,20 @@ sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv,
if (remote == engine->node)
goto out_put_remote;
/*
* According to device tree binding input ports have even id
* number and output ports have odd id. Since component with
* more than one input and one output (TCON TOP) exits, correct
* remote input id has to be calculated by subtracting 1 from
* remote output id. If this for some reason can't be done, 0
* is used as input port id.
*/
port = of_graph_get_remote_port(ep);
if (!of_property_read_u32(port, "reg", &reg) && reg > 0)
reg -= 1;
/* keep looking through upstream ports */
engine = sun4i_tcon_find_engine_traverse(drv, remote);
engine = sun4i_tcon_find_engine_traverse(drv, remote, reg);
out_put_remote:
of_node_put(remote);
@ -950,7 +964,7 @@ static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv,
/* Fallback to old method by traversing input endpoints */
of_node_put(port);
return sun4i_tcon_find_engine_traverse(drv, node);
return sun4i_tcon_find_engine_traverse(drv, node, 0);
}
static int sun4i_tcon_bind(struct device *dev, struct device *master,
@ -1092,23 +1106,25 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
goto err_free_dotclock;
}
/*
* If we have an LVDS panel connected to the TCON, we should
* just probe the LVDS connector. Otherwise, just probe RGB as
* we used to.
*/
remote = of_graph_get_remote_node(dev->of_node, 1, 0);
if (of_device_is_compatible(remote, "panel-lvds"))
if (can_lvds)
ret = sun4i_lvds_init(drm, tcon);
if (tcon->quirks->has_channel_0) {
/*
* If we have an LVDS panel connected to the TCON, we should
* just probe the LVDS connector. Otherwise, just probe RGB as
* we used to.
*/
remote = of_graph_get_remote_node(dev->of_node, 1, 0);
if (of_device_is_compatible(remote, "panel-lvds"))
if (can_lvds)
ret = sun4i_lvds_init(drm, tcon);
else
ret = -EINVAL;
else
ret = -EINVAL;
else
ret = sun4i_rgb_init(drm, tcon);
of_node_put(remote);
ret = sun4i_rgb_init(drm, tcon);
of_node_put(remote);
if (ret < 0)
goto err_free_dotclock;
if (ret < 0)
goto err_free_dotclock;
}
if (tcon->quirks->needs_de_be_mux) {
/*
@ -1162,13 +1178,19 @@ static const struct component_ops sun4i_tcon_ops = {
static int sun4i_tcon_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
const struct sun4i_tcon_quirks *quirks;
struct drm_bridge *bridge;
struct drm_panel *panel;
int ret;
ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge);
if (ret == -EPROBE_DEFER)
return ret;
quirks = of_device_get_match_data(&pdev->dev);
/* panels and bridges are present only on TCONs with channel 0 */
if (quirks->has_channel_0) {
ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge);
if (ret == -EPROBE_DEFER)
return ret;
}
return component_add(&pdev->dev, &sun4i_tcon_ops);
}

View File

@ -12,6 +12,7 @@
#include <drm/drm_crtc_helper.h>
#include "sun8i_dw_hdmi.h"
#include "sun8i_tcon_top.h"
static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
@ -41,6 +42,48 @@ sun8i_dw_hdmi_mode_valid(struct drm_connector *connector,
return MODE_OK;
}
static bool sun8i_dw_hdmi_node_is_tcon_top(struct device_node *node)
{
return !!of_match_node(sun8i_tcon_top_of_table, node);
}
static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm,
struct device_node *node)
{
struct device_node *port, *ep, *remote, *remote_port;
u32 crtcs = 0;
port = of_graph_get_port_by_id(node, 0);
if (!port)
return 0;
ep = of_get_next_available_child(port, NULL);
if (!ep)
return 0;
remote = of_graph_get_remote_port_parent(ep);
if (!remote)
return 0;
if (sun8i_dw_hdmi_node_is_tcon_top(remote)) {
port = of_graph_get_port_by_id(remote, 4);
if (!port)
return 0;
for_each_child_of_node(port, ep) {
remote_port = of_graph_get_remote_port(ep);
if (remote_port) {
crtcs |= drm_of_crtc_port_mask(drm, remote_port);
of_node_put(remote_port);
}
}
} else {
crtcs = drm_of_find_possible_crtcs(drm, node);
}
return crtcs;
}
static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
@ -63,7 +106,8 @@ static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
hdmi->dev = &pdev->dev;
encoder = &hdmi->encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
encoder->possible_crtcs =
sun8i_dw_hdmi_find_possible_crtcs(drm, dev->of_node);
/*
* If we failed to find the CRTC(s) which this encoder is
* supposed to be connected to, it's because the CRTC has

View File

@ -98,7 +98,8 @@
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN BIT(29)
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN BIT(28)
#define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 BIT(27)
#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL BIT(26)
#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK BIT(26)
#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT 26
#define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN BIT(25)
#define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x) ((x) << 22)
#define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x) ((x) << 20)
@ -147,6 +148,7 @@ struct sun8i_hdmi_phy;
struct sun8i_hdmi_phy_variant {
bool has_phy_clk;
bool has_second_pll;
void (*phy_init)(struct sun8i_hdmi_phy *phy);
void (*phy_disable)(struct dw_hdmi *hdmi,
struct sun8i_hdmi_phy *phy);
@ -160,6 +162,7 @@ struct sun8i_hdmi_phy {
struct clk *clk_mod;
struct clk *clk_phy;
struct clk *clk_pll0;
struct clk *clk_pll1;
unsigned int rcal;
struct regmap *regs;
struct reset_control *rst_phy;
@ -188,6 +191,7 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi);
void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
bool second_parent);
#endif /* _SUN8I_DW_HDMI_H_ */

View File

@ -183,7 +183,13 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
/*
* NOTE: We have to be careful not to overwrite PHY parent
* clock selection bit and clock divider.
*/
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
(u32)~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
pll_cfg1_init);
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
(u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
pll_cfg2_init);
@ -352,6 +358,10 @@ static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy)
SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
SUN8I_HDMI_PHY_ANA_CFG3_SDAEN);
/* reset PHY PLL clock parent */
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, 0);
/* set HW control of CEC pins */
regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG, 0);
@ -386,6 +396,14 @@ static struct regmap_config sun8i_hdmi_phy_regmap_config = {
.name = "phy"
};
static const struct sun8i_hdmi_phy_variant sun50i_a64_hdmi_phy = {
.has_phy_clk = true,
.has_second_pll = true,
.phy_init = &sun8i_hdmi_phy_init_h3,
.phy_disable = &sun8i_hdmi_phy_disable_h3,
.phy_config = &sun8i_hdmi_phy_config_h3,
};
static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
.phy_init = &sun8i_hdmi_phy_init_a83t,
.phy_disable = &sun8i_hdmi_phy_disable_a83t,
@ -400,6 +418,10 @@ static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
};
static const struct of_device_id sun8i_hdmi_phy_of_table[] = {
{
.compatible = "allwinner,sun50i-a64-hdmi-phy",
.data = &sun50i_a64_hdmi_phy,
},
{
.compatible = "allwinner,sun8i-a83t-hdmi-phy",
.data = &sun8i_a83t_hdmi_phy,
@ -472,18 +494,30 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
goto err_put_clk_mod;
}
ret = sun8i_phy_clk_create(phy, dev);
if (phy->variant->has_second_pll) {
phy->clk_pll1 = of_clk_get_by_name(node, "pll-1");
if (IS_ERR(phy->clk_pll1)) {
dev_err(dev, "Could not get pll-1 clock\n");
ret = PTR_ERR(phy->clk_pll1);
goto err_put_clk_pll0;
}
}
ret = sun8i_phy_clk_create(phy, dev,
phy->variant->has_second_pll);
if (ret) {
dev_err(dev, "Couldn't create the PHY clock\n");
goto err_put_clk_pll0;
goto err_put_clk_pll1;
}
clk_prepare_enable(phy->clk_phy);
}
phy->rst_phy = of_reset_control_get_shared(node, "phy");
if (IS_ERR(phy->rst_phy)) {
dev_err(dev, "Could not get phy reset control\n");
ret = PTR_ERR(phy->rst_phy);
goto err_put_clk_pll0;
goto err_disable_clk_phy;
}
ret = reset_control_deassert(phy->rst_phy);
@ -514,9 +548,12 @@ int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
reset_control_assert(phy->rst_phy);
err_put_rst_phy:
reset_control_put(phy->rst_phy);
err_disable_clk_phy:
clk_disable_unprepare(phy->clk_phy);
err_put_clk_pll1:
clk_put(phy->clk_pll1);
err_put_clk_pll0:
if (phy->variant->has_phy_clk)
clk_put(phy->clk_pll0);
clk_put(phy->clk_pll0);
err_put_clk_mod:
clk_put(phy->clk_mod);
err_put_clk_bus:
@ -531,13 +568,14 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi)
clk_disable_unprepare(phy->clk_mod);
clk_disable_unprepare(phy->clk_bus);
clk_disable_unprepare(phy->clk_phy);
reset_control_assert(phy->rst_phy);
reset_control_put(phy->rst_phy);
if (phy->variant->has_phy_clk)
clk_put(phy->clk_pll0);
clk_put(phy->clk_pll0);
clk_put(phy->clk_pll1);
clk_put(phy->clk_mod);
clk_put(phy->clk_bus);
}

View File

@ -22,35 +22,45 @@ static int sun8i_phy_clk_determine_rate(struct clk_hw *hw,
{
unsigned long rate = req->rate;
unsigned long best_rate = 0;
struct clk_hw *best_parent = NULL;
struct clk_hw *parent;
int best_div = 1;
int i;
int i, p;
parent = clk_hw_get_parent(hw);
for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
parent = clk_hw_get_parent_by_index(hw, p);
if (!parent)
continue;
for (i = 1; i <= 16; i++) {
unsigned long ideal = rate * i;
unsigned long rounded;
for (i = 1; i <= 16; i++) {
unsigned long ideal = rate * i;
unsigned long rounded;
rounded = clk_hw_round_rate(parent, ideal);
rounded = clk_hw_round_rate(parent, ideal);
if (rounded == ideal) {
best_rate = rounded;
best_div = i;
if (rounded == ideal) {
best_rate = rounded;
best_div = i;
best_parent = parent;
break;
}
if (!best_rate ||
abs(rate - rounded / i) <
abs(rate - best_rate / best_div)) {
best_rate = rounded;
best_div = i;
best_parent = parent;
}
}
if (best_rate / best_div == rate)
break;
}
if (!best_rate ||
abs(rate - rounded / i) <
abs(rate - best_rate / best_div)) {
best_rate = rounded;
best_div = i;
}
}
req->rate = best_rate / best_div;
req->best_parent_rate = best_rate;
req->best_parent_hw = parent;
req->best_parent_hw = best_parent;
return 0;
}
@ -95,22 +105,58 @@ static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
return 0;
}
static u8 sun8i_phy_clk_get_parent(struct clk_hw *hw)
{
struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
u32 reg;
regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, &reg);
reg = (reg & SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK) >>
SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT;
return reg;
}
static int sun8i_phy_clk_set_parent(struct clk_hw *hw, u8 index)
{
struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
if (index > 1)
return -EINVAL;
regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
index << SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT);
return 0;
}
static const struct clk_ops sun8i_phy_clk_ops = {
.determine_rate = sun8i_phy_clk_determine_rate,
.recalc_rate = sun8i_phy_clk_recalc_rate,
.set_rate = sun8i_phy_clk_set_rate,
.get_parent = sun8i_phy_clk_get_parent,
.set_parent = sun8i_phy_clk_set_parent,
};
int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
bool second_parent)
{
struct clk_init_data init;
struct sun8i_phy_clk *priv;
const char *parents[1];
const char *parents[2];
parents[0] = __clk_get_name(phy->clk_pll0);
if (!parents[0])
return -ENODEV;
if (second_parent) {
parents[1] = __clk_get_name(phy->clk_pll1);
if (!parents[1])
return -ENODEV;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
@ -118,7 +164,7 @@ int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
init.name = "hdmi-phy-clk";
init.ops = &sun8i_phy_clk_ops;
init.parent_names = parents;
init.num_parents = 1;
init.num_parents = second_parent ? 2 : 1;
init.flags = CLK_SET_RATE_PARENT;
priv->phy = phy;

View File

@ -500,6 +500,22 @@ static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = {
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_r40_mixer0_cfg = {
.ccsc = 0,
.mod_rate = 297000000,
.scaler_mask = 0xf,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_r40_mixer1_cfg = {
.ccsc = 1,
.mod_rate = 297000000,
.scaler_mask = 0x3,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
.vi_num = 2,
.ui_num = 1,
@ -521,6 +537,14 @@ static const struct of_device_id sun8i_mixer_of_table[] = {
.compatible = "allwinner,sun8i-h3-de2-mixer-0",
.data = &sun8i_h3_mixer0_cfg,
},
{
.compatible = "allwinner,sun8i-r40-de2-mixer-0",
.data = &sun8i_r40_mixer0_cfg,
},
{
.compatible = "allwinner,sun8i-r40-de2-mixer-1",
.data = &sun8i_r40_mixer1_cfg,
},
{
.compatible = "allwinner,sun8i-v3s-de2-mixer",
.data = &sun8i_v3s_mixer_cfg,

View File

@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
#include <drm/drmP.h>
#include <dt-bindings/clock/sun8i-tcon-top.h>
#include <linux/bitfield.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include "sun8i_tcon_top.h"
static int sun8i_tcon_top_get_connected_ep_id(struct device_node *node,
int port_id)
{
struct device_node *ep, *remote, *port;
struct of_endpoint endpoint;
port = of_graph_get_port_by_id(node, port_id);
if (!port)
return -ENOENT;
for_each_available_child_of_node(port, ep) {
remote = of_graph_get_remote_port_parent(ep);
if (!remote)
continue;
if (of_device_is_available(remote)) {
of_graph_parse_endpoint(ep, &endpoint);
of_node_put(remote);
return endpoint.id;
}
of_node_put(remote);
}
return -ENOENT;
}
static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev,
struct clk *parent,
void __iomem *regs,
spinlock_t *lock,
u8 bit, int name_index)
{
const char *clk_name, *parent_name;
int ret;
parent_name = __clk_get_name(parent);
ret = of_property_read_string_index(dev->of_node,
"clock-output-names", name_index,
&clk_name);
if (ret)
return ERR_PTR(ret);
return clk_hw_register_gate(dev, clk_name, parent_name,
CLK_SET_RATE_PARENT,
regs + TCON_TOP_GATE_SRC_REG,
bit, 0, lock);
};
static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct clk *dsi, *tcon_tv0, *tcon_tv1, *tve0, *tve1;
struct clk_hw_onecell_data *clk_data;
struct sun8i_tcon_top *tcon_top;
bool mixer0_unused = false;
struct resource *res;
void __iomem *regs;
int ret, i, id;
u32 val;
tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL);
if (!tcon_top)
return -ENOMEM;
clk_data = devm_kzalloc(dev, sizeof(*clk_data) +
sizeof(*clk_data->hws) * CLK_NUM,
GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
tcon_top->clk_data = clk_data;
spin_lock_init(&tcon_top->reg_lock);
tcon_top->rst = devm_reset_control_get(dev, NULL);
if (IS_ERR(tcon_top->rst)) {
dev_err(dev, "Couldn't get our reset line\n");
return PTR_ERR(tcon_top->rst);
}
tcon_top->bus = devm_clk_get(dev, "bus");
if (IS_ERR(tcon_top->bus)) {
dev_err(dev, "Couldn't get the bus clock\n");
return PTR_ERR(tcon_top->bus);
}
dsi = devm_clk_get(dev, "dsi");
if (IS_ERR(dsi)) {
dev_err(dev, "Couldn't get the dsi clock\n");
return PTR_ERR(dsi);
}
tcon_tv0 = devm_clk_get(dev, "tcon-tv0");
if (IS_ERR(tcon_tv0)) {
dev_err(dev, "Couldn't get the tcon-tv0 clock\n");
return PTR_ERR(tcon_tv0);
}
tcon_tv1 = devm_clk_get(dev, "tcon-tv1");
if (IS_ERR(tcon_tv1)) {
dev_err(dev, "Couldn't get the tcon-tv1 clock\n");
return PTR_ERR(tcon_tv1);
}
tve0 = devm_clk_get(dev, "tve0");
if (IS_ERR(tve0)) {
dev_err(dev, "Couldn't get the tve0 clock\n");
return PTR_ERR(tve0);
}
tve1 = devm_clk_get(dev, "tve1");
if (IS_ERR(tve1)) {
dev_err(dev, "Couldn't get the tve1 clock\n");
return PTR_ERR(tve1);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
ret = reset_control_deassert(tcon_top->rst);
if (ret) {
dev_err(dev, "Could not deassert ctrl reset control\n");
return ret;
}
ret = clk_prepare_enable(tcon_top->bus);
if (ret) {
dev_err(dev, "Could not enable bus clock\n");
goto err_assert_reset;
}
val = 0;
/* check if HDMI mux output is connected */
if (sun8i_tcon_top_get_connected_ep_id(dev->of_node, 5) >= 0) {
/* find HDMI input endpoint id, if it is connected at all*/
id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 4);
if (id >= 0)
val = FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, id + 1);
else
DRM_DEBUG_DRIVER("TCON TOP HDMI input is not connected\n");
} else {
DRM_DEBUG_DRIVER("TCON TOP HDMI output is not connected\n");
}
writel(val, regs + TCON_TOP_GATE_SRC_REG);
val = 0;
/* process mixer0 mux output */
id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 1);
if (id >= 0) {
val = FIELD_PREP(TCON_TOP_PORT_DE0_MSK, id);
} else {
DRM_DEBUG_DRIVER("TCON TOP mixer0 output is not connected\n");
mixer0_unused = true;
}
/* process mixer1 mux output */
id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 3);
if (id >= 0) {
val |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, id);
/*
* mixer0 mux has priority over mixer1 mux. We have to
* make sure mixer0 doesn't overtake TCON from mixer1.
*/
if (mixer0_unused && id == 0)
val |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, 1);
} else {
DRM_DEBUG_DRIVER("TCON TOP mixer1 output is not connected\n");
}
writel(val, regs + TCON_TOP_PORT_SEL_REG);
/*
* TCON TOP has two muxes, which select parent clock for each TCON TV
* channel clock. Parent could be either TCON TV or TVE clock. For now
* we leave this fixed to TCON TV, since TVE driver for R40 is not yet
* implemented. Once it is, graph needs to be traversed to determine
* if TVE is active on each TCON TV. If it is, mux should be switched
* to TVE clock parent.
*/
clk_data->hws[CLK_TCON_TOP_TV0] =
sun8i_tcon_top_register_gate(dev, tcon_tv0, regs,
&tcon_top->reg_lock,
TCON_TOP_TCON_TV0_GATE, 0);
clk_data->hws[CLK_TCON_TOP_TV1] =
sun8i_tcon_top_register_gate(dev, tcon_tv1, regs,
&tcon_top->reg_lock,
TCON_TOP_TCON_TV1_GATE, 1);
clk_data->hws[CLK_TCON_TOP_DSI] =
sun8i_tcon_top_register_gate(dev, dsi, regs,
&tcon_top->reg_lock,
TCON_TOP_TCON_DSI_GATE, 2);
for (i = 0; i < CLK_NUM; i++)
if (IS_ERR(clk_data->hws[i])) {
ret = PTR_ERR(clk_data->hws[i]);
goto err_unregister_gates;
}
clk_data->num = CLK_NUM;
ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
clk_data);
if (ret)
goto err_unregister_gates;
dev_set_drvdata(dev, tcon_top);
return 0;
err_unregister_gates:
for (i = 0; i < CLK_NUM; i++)
if (clk_data->hws[i])
clk_hw_unregister_gate(clk_data->hws[i]);
clk_disable_unprepare(tcon_top->bus);
err_assert_reset:
reset_control_assert(tcon_top->rst);
return ret;
}
static void sun8i_tcon_top_unbind(struct device *dev, struct device *master,
void *data)
{
struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
struct clk_hw_onecell_data *clk_data = tcon_top->clk_data;
int i;
of_clk_del_provider(dev->of_node);
for (i = 0; i < CLK_NUM; i++)
clk_hw_unregister_gate(clk_data->hws[i]);
clk_disable_unprepare(tcon_top->bus);
reset_control_assert(tcon_top->rst);
}
static const struct component_ops sun8i_tcon_top_ops = {
.bind = sun8i_tcon_top_bind,
.unbind = sun8i_tcon_top_unbind,
};
static int sun8i_tcon_top_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &sun8i_tcon_top_ops);
}
static int sun8i_tcon_top_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun8i_tcon_top_ops);
return 0;
}
/* sun4i_drv uses this list to check if a device node is a TCON TOP */
const struct of_device_id sun8i_tcon_top_of_table[] = {
{ .compatible = "allwinner,sun8i-r40-tcon-top" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table);
EXPORT_SYMBOL(sun8i_tcon_top_of_table);
static struct platform_driver sun8i_tcon_top_platform_driver = {
.probe = sun8i_tcon_top_probe,
.remove = sun8i_tcon_top_remove,
.driver = {
.name = "sun8i-tcon-top",
.of_match_table = sun8i_tcon_top_of_table,
},
};
module_platform_driver(sun8i_tcon_top_platform_driver);
MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,40 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
#ifndef _SUN8I_TCON_TOP_H_
#define _SUN8I_TCON_TOP_H_
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/reset.h>
#include <linux/spinlock.h>
#define TCON_TOP_TCON_TV_SETUP_REG 0x00
#define TCON_TOP_PORT_SEL_REG 0x1C
#define TCON_TOP_PORT_DE0_MSK GENMASK(1, 0)
#define TCON_TOP_PORT_DE1_MSK GENMASK(5, 4)
#define TCON_TOP_GATE_SRC_REG 0x20
#define TCON_TOP_HDMI_SRC_MSK GENMASK(29, 28)
#define TCON_TOP_TCON_TV1_GATE 24
#define TCON_TOP_TCON_TV0_GATE 20
#define TCON_TOP_TCON_DSI_GATE 16
#define CLK_NUM 3
struct sun8i_tcon_top {
struct clk *bus;
struct clk_hw_onecell_data *clk_data;
struct reset_control *rst;
/*
* spinlock is used to synchronize access to same
* register where multiple clock gates can be set.
*/
spinlock_t reg_lock;
};
extern const struct of_device_id sun8i_tcon_top_of_table[];
#endif /* _SUN8I_TCON_TOP_H_ */

View File

@ -20,6 +20,16 @@ config TINYDRM_ILI9225
If M is selected the module will be called ili9225.
config TINYDRM_ILI9341
tristate "DRM support for ILI9341 display panels"
depends on DRM_TINYDRM && SPI
select TINYDRM_MIPI_DBI
help
DRM driver for the following Ilitek ILI9341 panels:
* YX240QV29-T 2.4" 240x320 TFT (Adafruit 2.4")
If M is selected the module will be called ili9341.
config TINYDRM_MI0283QT
tristate "DRM support for MI0283QT"
depends on DRM_TINYDRM && SPI

View File

@ -5,6 +5,7 @@ obj-$(CONFIG_TINYDRM_MIPI_DBI) += mipi-dbi.o
# Displays
obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o
obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o
obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o
obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o
obj-$(CONFIG_TINYDRM_ST7586) += st7586.o

View File

@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* DRM driver for Ilitek ILI9341 panels
*
* Copyright 2018 David Lechner <david@lechnology.com>
*
* Based on mi0283qt.c:
* Copyright 2016 Noralf Trønnes
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/spi/spi.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_modeset_helper.h>
#include <drm/tinydrm/mipi-dbi.h>
#include <drm/tinydrm/tinydrm-helpers.h>
#include <video/mipi_display.h>
#define ILI9341_FRMCTR1 0xb1
#define ILI9341_DISCTRL 0xb6
#define ILI9341_ETMOD 0xb7
#define ILI9341_PWCTRL1 0xc0
#define ILI9341_PWCTRL2 0xc1
#define ILI9341_VMCTRL1 0xc5
#define ILI9341_VMCTRL2 0xc7
#define ILI9341_PWCTRLA 0xcb
#define ILI9341_PWCTRLB 0xcf
#define ILI9341_PGAMCTRL 0xe0
#define ILI9341_NGAMCTRL 0xe1
#define ILI9341_DTCTRLA 0xe8
#define ILI9341_DTCTRLB 0xea
#define ILI9341_PWRSEQ 0xed
#define ILI9341_EN3GAM 0xf2
#define ILI9341_PUMPCTRL 0xf7
#define ILI9341_MADCTL_BGR BIT(3)
#define ILI9341_MADCTL_MV BIT(5)
#define ILI9341_MADCTL_MX BIT(6)
#define ILI9341_MADCTL_MY BIT(7)
static void yx240qv29_enable(struct drm_simple_display_pipe *pipe,
struct drm_crtc_state *crtc_state,
struct drm_plane_state *plane_state)
{
struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
u8 addr_mode;
int ret;
DRM_DEBUG_KMS("\n");
ret = mipi_dbi_poweron_conditional_reset(mipi);
if (ret < 0)
return;
if (ret == 1)
goto out_enable;
mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF);
mipi_dbi_command(mipi, ILI9341_PWCTRLB, 0x00, 0xc1, 0x30);
mipi_dbi_command(mipi, ILI9341_PWRSEQ, 0x64, 0x03, 0x12, 0x81);
mipi_dbi_command(mipi, ILI9341_DTCTRLA, 0x85, 0x00, 0x78);
mipi_dbi_command(mipi, ILI9341_PWCTRLA, 0x39, 0x2c, 0x00, 0x34, 0x02);
mipi_dbi_command(mipi, ILI9341_PUMPCTRL, 0x20);
mipi_dbi_command(mipi, ILI9341_DTCTRLB, 0x00, 0x00);
/* Power Control */
mipi_dbi_command(mipi, ILI9341_PWCTRL1, 0x23);
mipi_dbi_command(mipi, ILI9341_PWCTRL2, 0x10);
/* VCOM */
mipi_dbi_command(mipi, ILI9341_VMCTRL1, 0x3e, 0x28);
mipi_dbi_command(mipi, ILI9341_VMCTRL2, 0x86);
/* Memory Access Control */
mipi_dbi_command(mipi, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
/* Frame Rate */
mipi_dbi_command(mipi, ILI9341_FRMCTR1, 0x00, 0x1b);
/* Gamma */
mipi_dbi_command(mipi, ILI9341_EN3GAM, 0x00);
mipi_dbi_command(mipi, MIPI_DCS_SET_GAMMA_CURVE, 0x01);
mipi_dbi_command(mipi, ILI9341_PGAMCTRL,
0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1,
0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00);
mipi_dbi_command(mipi, ILI9341_NGAMCTRL,
0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1,
0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f);
/* DDRAM */
mipi_dbi_command(mipi, ILI9341_ETMOD, 0x07);
/* Display */
mipi_dbi_command(mipi, ILI9341_DISCTRL, 0x08, 0x82, 0x27, 0x00);
mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE);
msleep(100);
mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON);
msleep(100);
out_enable:
switch (mipi->rotation) {
default:
addr_mode = ILI9341_MADCTL_MX;
break;
case 90:
addr_mode = ILI9341_MADCTL_MV;
break;
case 180:
addr_mode = ILI9341_MADCTL_MY;
break;
case 270:
addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY |
ILI9341_MADCTL_MX;
break;
}
addr_mode |= ILI9341_MADCTL_BGR;
mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
mipi_dbi_enable_flush(mipi, crtc_state, plane_state);
}
static const struct drm_simple_display_pipe_funcs ili9341_pipe_funcs = {
.enable = yx240qv29_enable,
.disable = mipi_dbi_pipe_disable,
.update = tinydrm_display_pipe_update,
.prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
};
static const struct drm_display_mode yx240qv29_mode = {
TINYDRM_MODE(240, 320, 37, 49),
};
DEFINE_DRM_GEM_CMA_FOPS(ili9341_fops);
static struct drm_driver ili9341_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC,
.fops = &ili9341_fops,
TINYDRM_GEM_DRIVER_OPS,
.lastclose = drm_fb_helper_lastclose,
.debugfs_init = mipi_dbi_debugfs_init,
.name = "ili9341",
.desc = "Ilitek ILI9341",
.date = "20180514",
.major = 1,
.minor = 0,
};
static const struct of_device_id ili9341_of_match[] = {
{ .compatible = "adafruit,yx240qv29" },
{ }
};
MODULE_DEVICE_TABLE(of, ili9341_of_match);
static const struct spi_device_id ili9341_id[] = {
{ "yx240qv29", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, ili9341_id);
static int ili9341_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct mipi_dbi *mipi;
struct gpio_desc *dc;
u32 rotation = 0;
int ret;
mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
if (!mipi)
return -ENOMEM;
mipi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(mipi->reset)) {
DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
return PTR_ERR(mipi->reset);
}
dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
if (IS_ERR(dc)) {
DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n");
return PTR_ERR(dc);
}
mipi->backlight = devm_of_find_backlight(dev);
if (IS_ERR(mipi->backlight))
return PTR_ERR(mipi->backlight);
device_property_read_u32(dev, "rotation", &rotation);
ret = mipi_dbi_spi_init(spi, mipi, dc);
if (ret)
return ret;
ret = mipi_dbi_init(&spi->dev, mipi, &ili9341_pipe_funcs,
&ili9341_driver, &yx240qv29_mode, rotation);
if (ret)
return ret;
spi_set_drvdata(spi, mipi);
return devm_tinydrm_register(&mipi->tinydrm);
}
static void ili9341_shutdown(struct spi_device *spi)
{
struct mipi_dbi *mipi = spi_get_drvdata(spi);
tinydrm_shutdown(&mipi->tinydrm);
}
static struct spi_driver ili9341_spi_driver = {
.driver = {
.name = "ili9341",
.of_match_table = ili9341_of_match,
},
.id_table = ili9341_id,
.probe = ili9341_probe,
.shutdown = ili9341_shutdown,
};
module_spi_driver(ili9341_spi_driver);
MODULE_DESCRIPTION("Ilitek ILI9341 DRM driver");
MODULE_AUTHOR("David Lechner <david@lechnology.com>");
MODULE_LICENSE("GPL");

View File

@ -25,7 +25,6 @@ struct v3d_queue_state {
u64 fence_context;
u64 emit_seqno;
u64 finished_seqno;
};
struct v3d_dev {
@ -85,6 +84,11 @@ struct v3d_dev {
*/
struct mutex reset_lock;
/* Lock taken when creating and pushing the GPU scheduler
* jobs, to keep the sched-fence seqnos in order.
*/
struct mutex sched_lock;
struct {
u32 num_allocated;
u32 pages_allocated;

View File

@ -40,19 +40,14 @@ static bool v3d_fence_enable_signaling(struct dma_fence *fence)
return true;
}
static bool v3d_fence_signaled(struct dma_fence *fence)
{
struct v3d_fence *f = to_v3d_fence(fence);
struct v3d_dev *v3d = to_v3d_dev(f->dev);
return v3d->queue[f->queue].finished_seqno >= f->seqno;
}
const struct dma_fence_ops v3d_fence_ops = {
.get_driver_name = v3d_fence_get_driver_name,
.get_timeline_name = v3d_fence_get_timeline_name,
.enable_signaling = v3d_fence_enable_signaling,
.signaled = v3d_fence_signaled,
/* Each of our fences gets signaled as complete by the IRQ
* handler, so we rely on the core's tracking of signaling.
*/
.signaled = NULL,
.wait = dma_fence_default_wait,
.release = dma_fence_free,
};

View File

@ -550,6 +550,7 @@ v3d_submit_cl_ioctl(struct drm_device *dev, void *data,
if (ret)
goto fail;
mutex_lock(&v3d->sched_lock);
if (exec->bin.start != exec->bin.end) {
ret = drm_sched_job_init(&exec->bin.base,
&v3d->queue[V3D_BIN].sched,
@ -576,6 +577,7 @@ v3d_submit_cl_ioctl(struct drm_device *dev, void *data,
kref_get(&exec->refcount); /* put by scheduler job completion */
drm_sched_entity_push_job(&exec->render.base,
&v3d_priv->sched_entity[V3D_RENDER]);
mutex_unlock(&v3d->sched_lock);
v3d_attach_object_fences(exec);
@ -594,6 +596,7 @@ v3d_submit_cl_ioctl(struct drm_device *dev, void *data,
return 0;
fail_unreserve:
mutex_unlock(&v3d->sched_lock);
v3d_unlock_bo_reservations(dev, exec, &acquire_ctx);
fail:
v3d_exec_put(exec);
@ -615,6 +618,7 @@ v3d_gem_init(struct drm_device *dev)
spin_lock_init(&v3d->job_lock);
mutex_init(&v3d->bo_lock);
mutex_init(&v3d->reset_lock);
mutex_init(&v3d->sched_lock);
/* Note: We don't allocate address 0. Various bits of HW
* treat 0 as special, such as the occlusion query counters
@ -650,17 +654,14 @@ void
v3d_gem_destroy(struct drm_device *dev)
{
struct v3d_dev *v3d = to_v3d_dev(dev);
enum v3d_queue q;
v3d_sched_fini(v3d);
/* Waiting for exec to finish would need to be done before
* unregistering V3D.
*/
for (q = 0; q < V3D_MAX_QUEUES; q++) {
WARN_ON(v3d->queue[q].emit_seqno !=
v3d->queue[q].finished_seqno);
}
WARN_ON(v3d->bin_job);
WARN_ON(v3d->render_job);
drm_mm_takedown(&v3d->mm);

View File

@ -87,15 +87,12 @@ v3d_irq(int irq, void *arg)
}
if (intsts & V3D_INT_FLDONE) {
v3d->queue[V3D_BIN].finished_seqno++;
dma_fence_signal(v3d->bin_job->bin.done_fence);
status = IRQ_HANDLED;
}
if (intsts & V3D_INT_FRDONE) {
v3d->queue[V3D_RENDER].finished_seqno++;
dma_fence_signal(v3d->render_job->render.done_fence);
status = IRQ_HANDLED;
}

View File

@ -721,7 +721,7 @@ vc4_prime_export(struct drm_device *dev, struct drm_gem_object *obj, int flags)
return dmabuf;
}
int vc4_fault(struct vm_fault *vmf)
vm_fault_t vc4_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct drm_gem_object *obj = vma->vm_private_data;

View File

@ -6,6 +6,7 @@
* published by the Free Software Foundation.
*/
#include <linux/mm_types.h>
#include <linux/reservation.h>
#include <drm/drmP.h>
#include <drm/drm_encoder.h>
@ -674,7 +675,7 @@ int vc4_get_hang_state_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv);
int vc4_label_bo_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv);
int vc4_fault(struct vm_fault *vmf);
vm_fault_t vc4_fault(struct vm_fault *vmf);
int vc4_mmap(struct file *filp, struct vm_area_struct *vma);
struct reservation_object *vc4_prime_res_obj(struct drm_gem_object *obj);
int vc4_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);

View File

@ -97,6 +97,16 @@ struct pci_controller;
#define DRM_IF_VERSION(maj, min) (maj << 16 | min)
#define DRM_SWITCH_POWER_ON 0
#define DRM_SWITCH_POWER_OFF 1
#define DRM_SWITCH_POWER_CHANGING 2
#define DRM_SWITCH_POWER_DYNAMIC_OFF 3
static inline bool drm_core_check_feature(struct drm_device *dev, int feature)
{
return dev->driver->driver_features & feature;
}
/**
* drm_drv_uses_atomic_modeset - check if the driver implements
* atomic_commit()
@ -107,17 +117,8 @@ struct pci_controller;
*/
static inline bool drm_drv_uses_atomic_modeset(struct drm_device *dev)
{
return dev->mode_config.funcs->atomic_commit != NULL;
}
#define DRM_SWITCH_POWER_ON 0
#define DRM_SWITCH_POWER_OFF 1
#define DRM_SWITCH_POWER_CHANGING 2
#define DRM_SWITCH_POWER_DYNAMIC_OFF 3
static inline bool drm_core_check_feature(struct drm_device *dev, int feature)
{
return dev->driver->driver_features & feature;
return drm_core_check_feature(dev, DRIVER_ATOMIC) ||
dev->mode_config.funcs->atomic_commit != NULL;
}
/* returns true if currently okay to sleep */

View File

@ -270,27 +270,29 @@ struct drm_bridge_timings {
/**
* struct drm_bridge - central DRM bridge control structure
* @dev: DRM device this bridge belongs to
* @encoder: encoder to which this bridge is connected
* @next: the next bridge in the encoder chain
* @of_node: device node pointer to the bridge
* @list: to keep track of all added bridges
* @timings: the timing specification for the bridge, if any (may
* be NULL)
* @funcs: control functions
* @driver_private: pointer to the bridge driver's internal context
*/
struct drm_bridge {
/** @dev: DRM device this bridge belongs to */
struct drm_device *dev;
/** @encoder: encoder to which this bridge is connected */
struct drm_encoder *encoder;
/** @next: the next bridge in the encoder chain */
struct drm_bridge *next;
#ifdef CONFIG_OF
/** @of_node: device node pointer to the bridge */
struct device_node *of_node;
#endif
/** @list: to keep track of all added bridges */
struct list_head list;
/**
* @timings:
*
* the timing specification for the bridge, if any (may be NULL)
*/
const struct drm_bridge_timings *timings;
/** @funcs: control functions */
const struct drm_bridge_funcs *funcs;
/** @driver_private: pointer to the bridge driver's internal context */
void *driver_private;
};

View File

@ -329,10 +329,10 @@ struct drm_mode_config_funcs {
/**
* struct drm_mode_config - Mode configuration control structure
* @min_width: minimum pixel width on this device
* @min_height: minimum pixel height on this device
* @max_width: maximum pixel width on this device
* @max_height: maximum pixel height on this device
* @min_width: minimum fb pixel width on this device
* @min_height: minimum fb pixel height on this device
* @max_width: maximum fb pixel width on this device
* @max_height: maximum fb pixel height on this device
* @funcs: core driver provided mode setting functions
* @fb_base: base address of the framebuffer
* @poll_enabled: track polling support for this device

View File

@ -17,6 +17,8 @@ struct drm_bridge;
struct device_node;
#ifdef CONFIG_OF
uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port);
uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
struct device_node *port);
void drm_of_component_match_add(struct device *master,
@ -34,6 +36,12 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
struct drm_panel **panel,
struct drm_bridge **bridge);
#else
static inline uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port)
{
return 0;
}
static inline uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
struct device_node *port)
{

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/* Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
#ifndef _DT_BINDINGS_CLOCK_SUN8I_TCON_TOP_H_
#define _DT_BINDINGS_CLOCK_SUN8I_TCON_TOP_H_
#define CLK_TCON_TOP_TV0 0
#define CLK_TCON_TOP_TV1 1
#define CLK_TCON_TOP_DSI 2
#endif /* _DT_BINDINGS_CLOCK_SUN8I_TCON_TOP_H_ */