mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-13 23:34:05 +08:00
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:
commit
eab9766931
27
Documentation/devicetree/bindings/display/ilitek,ili9341.txt
Normal file
27
Documentation/devicetree/bindings/display/ilitek,ili9341.txt
Normal 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>;
|
||||
};
|
@ -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 = <®_display>;
|
||||
reset-gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL05 */
|
||||
backlight = <&pwm_bl>;
|
||||
};
|
@ -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
|
||||
---
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 @@ fail_unref:
|
||||
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
|
||||
|
@ -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 */
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 @@ unlock:
|
||||
return ret;
|
||||
|
||||
unref_cursor:
|
||||
drm_gem_object_unreference_unlocked(obj);
|
||||
drm_gem_object_put_unlocked(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
503
drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
Normal file
503
drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
Normal 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");
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 > 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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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_ */
|
||||
|
@ -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 @@ err_deassert_rst_phy:
|
||||
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);
|
||||
}
|
||||
|
@ -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 & 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;
|
||||
|
@ -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,
|
||||
|
300
drivers/gpu/drm/sun4i/sun8i_tcon_top.c
Normal file
300
drivers/gpu/drm/sun4i/sun8i_tcon_top.c
Normal 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");
|
40
drivers/gpu/drm/sun4i/sun8i_tcon_top.h
Normal file
40
drivers/gpu/drm/sun4i/sun8i_tcon_top.h
Normal 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_ */
|
@ -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
|
||||
|
@ -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
|
||||
|
233
drivers/gpu/drm/tinydrm/ili9341.c
Normal file
233
drivers/gpu/drm/tinydrm/ili9341.c
Normal 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");
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
11
include/dt-bindings/clock/sun8i-tcon-top.h
Normal file
11
include/dt-bindings/clock/sun8i-tcon-top.h
Normal 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_ */
|
Loading…
Reference in New Issue
Block a user