drm-misc-next for v5.20:

UAPI Changes:
 
 Cross-subsystem Changes:
 
  * dma-buf: Add sync-file API; Set DMA mask for udmabuf devices
 
  * fbcon: Cleanups
 
  * fbdev: Disable firmware-device registration when first native driver loads
 
  * iosys-map: Documentation fixes
 
 Core Changes:
 
  * edid: Use struct drm_edid in more places
 
  * gem-cma-helper: Improve documentation
 
  * of: Add data-lane helpers and convert drivers
 
  * syncobj: Fixes
 
 Driver Changes:
 
  * amdgpu: Build fixes
 
  * ast: Support multiple outputs
 
  * bochs: Include <linux/module.h>
 
  * bridge: adv7511: I2C fixes; anx7625: Fix error handling; lt6505: Kconfig fixes
 
  * display/dp: Documentation fixes
 
  * display/dp-mst: Read extended DPCD capabilities during system resume
 
  * logicvc: Add new driver
 
  * magag200: Build fixes
 
  * nouveau: Cleanups
 
  * panel: Add backlight support; nt36672a: DT backlight support
 
  * qxl: Cleanups
 
  * sun4i: HDMI PHY cleanups
 
  * vc4: Add support for BCM2711
 
  * virt-gpu: Avoid NULL dereference; Fix error checks; Cleanups
 
  * vkms: Allocate output buffer with vmalloc(); Fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEchf7rIzpz2NEoWjlaA3BHVMLeiMFAmKsKwAACgkQaA3BHVML
 eiPnJAgArdU7DOVUuRm0clRBHNndob37pICGbvdh84jNEpHq6rdfpi7HGqvNYvSy
 KyVhBPKFxygDyXNrgFh5YnMLX8Gr+S73VRKXc19k5tc3KYG9W36Vg/aBL7fXqs1F
 VDGgC0uksMIA2X0J03a+cZuQV8drYoWLUO7Pz0TcqFE+9cSSBe8gJb6lSt9rPIYx
 0YTFWsHbL9VeW/VuhyG11KLL3s2SGofP4lw8i7H01RQJRGYo/Ak95ysA9Uo/4AXA
 nwOoK46MqPtsqs+dxGU/Ix6jP8KHSGMPKb+WEfv8WDQvdhOnOyUsza3V2Uma2H+M
 2wlAkJnhyUXletzTRQBUnGFBHcccpA==
 =8nrd
 -----END PGP SIGNATURE-----

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

drm-misc-next for v5.20:

UAPI Changes:

Cross-subsystem Changes:

 * dma-buf: Add sync-file API; Set DMA mask for udmabuf devices

 * fbcon: Cleanups

 * fbdev: Disable firmware-device registration when first native driver loads

 * iosys-map: Documentation fixes

Core Changes:

 * edid: Use struct drm_edid in more places

 * gem-cma-helper: Improve documentation

 * of: Add data-lane helpers and convert drivers

 * syncobj: Fixes

Driver Changes:

 * amdgpu: Build fixes

 * ast: Support multiple outputs

 * bochs: Include <linux/module.h>

 * bridge: adv7511: I2C fixes; anx7625: Fix error handling; lt6505: Kconfig fixes

 * display/dp: Documentation fixes

 * display/dp-mst: Read extended DPCD capabilities during system resume

 * logicvc: Add new driver

 * magag200: Build fixes

 * nouveau: Cleanups

 * panel: Add backlight support; nt36672a: DT backlight support

 * qxl: Cleanups

 * sun4i: HDMI PHY cleanups

 * vc4: Add support for BCM2711

 * virt-gpu: Avoid NULL dereference; Fix error checks; Cleanups

 * vkms: Allocate output buffer with vmalloc(); Fixes

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Thomas Zimmermann <tzimmermann@suse.de>
Link: https://patchwork.freedesktop.org/patch/msgid/YqwriEhn0l4uO+Gn@linux-uq9g
This commit is contained in:
Dave Airlie 2022-06-22 10:32:55 +10:00
commit 0a2af0bd6e
76 changed files with 3181 additions and 457 deletions

View File

@ -46,6 +46,7 @@ properties:
reg: true
port: true
backlight: true
required:
- compatible
@ -73,6 +74,7 @@ examples:
vddpos-supply = <&lab>;
vddneg-supply = <&ibb>;
backlight = <&pmi8998_wled>;
reset-gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>;
port {

View File

@ -16,6 +16,7 @@ properties:
compatible:
enum:
- brcm,2711-v3d
- brcm,7268-v3d
- brcm,7278-v3d

View File

@ -13,6 +13,12 @@ EDD Interfaces
.. kernel-doc:: drivers/firmware/edd.c
:internal:
Generic System Framebuffers Interface
-------------------------------------
.. kernel-doc:: drivers/firmware/sysfb.c
:export:
Intel Stratix10 SoC Service Layer
---------------------------------
Some features of the Intel Stratix10 SoC require a level of privilege

View File

@ -102,12 +102,6 @@ Debugging:
- kms_plane: some test cases are failing due to timeout on capturing CRC;
- kms_flip: when running test cases in sequence, some successful individual
test cases are failing randomly; when individually, some successful test
cases display in the log the following error::
[drm:vkms_prepare_fb [vkms]] ERROR vmap failed: -4
Virtual hardware (vblank-less) mode:
- VKMS already has support for vblanks simulated via hrtimers, which can be

View File

@ -6284,6 +6284,12 @@ S: Orphan / Obsolete
F: drivers/gpu/drm/i810/
F: include/uapi/drm/i810_drm.h
DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
M: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
S: Supported
T: git git://anongit.freedesktop.org/drm/drm-misc
F: drivers/gpu/drm/logicvc/
DRM DRIVER FOR LVDS PANELS
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
L: dri-devel@lists.freedesktop.org

View File

@ -20,6 +20,7 @@
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/sync_file.h>
#include <linux/poll.h>
#include <linux/dma-resv.h>
#include <linux/mm.h>
@ -192,6 +193,9 @@ static loff_t dma_buf_llseek(struct file *file, loff_t offset, int whence)
* Note that this only signals the completion of the respective fences, i.e. the
* DMA transfers are complete. Cache flushing and any other necessary
* preparations before CPU access can begin still need to happen.
*
* As an alternative to poll(), the set of fences on DMA buffer can be
* exported as a &sync_file using &dma_buf_sync_file_export.
*/
static void dma_buf_poll_cb(struct dma_fence *fence, struct dma_fence_cb *cb)
@ -326,6 +330,101 @@ static long dma_buf_set_name(struct dma_buf *dmabuf, const char __user *buf)
return 0;
}
#if IS_ENABLED(CONFIG_SYNC_FILE)
static long dma_buf_export_sync_file(struct dma_buf *dmabuf,
void __user *user_data)
{
struct dma_buf_export_sync_file arg;
enum dma_resv_usage usage;
struct dma_fence *fence = NULL;
struct sync_file *sync_file;
int fd, ret;
if (copy_from_user(&arg, user_data, sizeof(arg)))
return -EFAULT;
if (arg.flags & ~DMA_BUF_SYNC_RW)
return -EINVAL;
if ((arg.flags & DMA_BUF_SYNC_RW) == 0)
return -EINVAL;
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0)
return fd;
usage = dma_resv_usage_rw(arg.flags & DMA_BUF_SYNC_WRITE);
ret = dma_resv_get_singleton(dmabuf->resv, usage, &fence);
if (ret)
goto err_put_fd;
if (!fence)
fence = dma_fence_get_stub();
sync_file = sync_file_create(fence);
dma_fence_put(fence);
if (!sync_file) {
ret = -ENOMEM;
goto err_put_fd;
}
arg.fd = fd;
if (copy_to_user(user_data, &arg, sizeof(arg))) {
ret = -EFAULT;
goto err_put_file;
}
fd_install(fd, sync_file->file);
return 0;
err_put_file:
fput(sync_file->file);
err_put_fd:
put_unused_fd(fd);
return ret;
}
static long dma_buf_import_sync_file(struct dma_buf *dmabuf,
const void __user *user_data)
{
struct dma_buf_import_sync_file arg;
struct dma_fence *fence;
enum dma_resv_usage usage;
int ret = 0;
if (copy_from_user(&arg, user_data, sizeof(arg)))
return -EFAULT;
if (arg.flags & ~DMA_BUF_SYNC_RW)
return -EINVAL;
if ((arg.flags & DMA_BUF_SYNC_RW) == 0)
return -EINVAL;
fence = sync_file_get_fence(arg.fd);
if (!fence)
return -EINVAL;
usage = (arg.flags & DMA_BUF_SYNC_WRITE) ? DMA_RESV_USAGE_WRITE :
DMA_RESV_USAGE_READ;
dma_resv_lock(dmabuf->resv, NULL);
ret = dma_resv_reserve_fences(dmabuf->resv, 1);
if (!ret)
dma_resv_add_fence(dmabuf->resv, fence, usage);
dma_resv_unlock(dmabuf->resv);
dma_fence_put(fence);
return ret;
}
#endif
static long dma_buf_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
@ -369,6 +468,13 @@ static long dma_buf_ioctl(struct file *file,
case DMA_BUF_SET_NAME_B:
return dma_buf_set_name(dmabuf, (const char __user *)arg);
#if IS_ENABLED(CONFIG_SYNC_FILE)
case DMA_BUF_IOCTL_EXPORT_SYNC_FILE:
return dma_buf_export_sync_file(dmabuf, (void __user *)arg);
case DMA_BUF_IOCTL_IMPORT_SYNC_FILE:
return dma_buf_import_sync_file(dmabuf, (const void __user *)arg);
#endif
default:
return -ENOTTY;
}

View File

@ -365,7 +365,23 @@ static struct miscdevice udmabuf_misc = {
static int __init udmabuf_dev_init(void)
{
return misc_register(&udmabuf_misc);
int ret;
ret = misc_register(&udmabuf_misc);
if (ret < 0) {
pr_err("Could not initialize udmabuf device\n");
return ret;
}
ret = dma_coerce_mask_and_coherent(udmabuf_misc.this_device,
DMA_BIT_MASK(64));
if (ret < 0) {
pr_err("Could not setup DMA mask for udmabuf device\n");
misc_deregister(&udmabuf_misc);
return ret;
}
return 0;
}
static void __exit udmabuf_dev_exit(void)

View File

@ -34,21 +34,59 @@
#include <linux/screen_info.h>
#include <linux/sysfb.h>
static struct platform_device *pd;
static DEFINE_MUTEX(disable_lock);
static bool disabled;
static bool sysfb_unregister(void)
{
if (IS_ERR_OR_NULL(pd))
return false;
platform_device_unregister(pd);
pd = NULL;
return true;
}
/**
* sysfb_disable() - disable the Generic System Framebuffers support
*
* This disables the registration of system framebuffer devices that match the
* generic drivers that make use of the system framebuffer set up by firmware.
*
* It also unregisters a device if this was already registered by sysfb_init().
*
* Context: The function can sleep. A @disable_lock mutex is acquired to serialize
* against sysfb_init(), that registers a system framebuffer device.
*/
void sysfb_disable(void)
{
mutex_lock(&disable_lock);
sysfb_unregister();
disabled = true;
mutex_unlock(&disable_lock);
}
EXPORT_SYMBOL_GPL(sysfb_disable);
static __init int sysfb_init(void)
{
struct screen_info *si = &screen_info;
struct simplefb_platform_data mode;
struct platform_device *pd;
const char *name;
bool compatible;
int ret;
int ret = 0;
mutex_lock(&disable_lock);
if (disabled)
goto unlock_mutex;
/* try to create a simple-framebuffer device */
compatible = sysfb_parse_mode(si, &mode);
if (compatible) {
ret = sysfb_create_simplefb(si, &mode);
if (!ret)
return 0;
pd = sysfb_create_simplefb(si, &mode);
if (!IS_ERR(pd))
goto unlock_mutex;
}
/* if the FB is incompatible, create a legacy framebuffer device */
@ -60,8 +98,10 @@ static __init int sysfb_init(void)
name = "platform-framebuffer";
pd = platform_device_alloc(name, 0);
if (!pd)
return -ENOMEM;
if (!pd) {
ret = -ENOMEM;
goto unlock_mutex;
}
sysfb_apply_efi_quirks(pd);
@ -73,9 +113,11 @@ static __init int sysfb_init(void)
if (ret)
goto err;
return 0;
goto unlock_mutex;
err:
platform_device_put(pd);
unlock_mutex:
mutex_unlock(&disable_lock);
return ret;
}

View File

@ -57,8 +57,8 @@ __init bool sysfb_parse_mode(const struct screen_info *si,
return false;
}
__init int sysfb_create_simplefb(const struct screen_info *si,
const struct simplefb_platform_data *mode)
__init struct platform_device *sysfb_create_simplefb(const struct screen_info *si,
const struct simplefb_platform_data *mode)
{
struct platform_device *pd;
struct resource res;
@ -76,7 +76,7 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
base |= (u64)si->ext_lfb_base << 32;
if (!base || (u64)(resource_size_t)base != base) {
printk(KERN_DEBUG "sysfb: inaccessible VRAM base\n");
return -EINVAL;
return ERR_PTR(-EINVAL);
}
/*
@ -93,7 +93,7 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
length = mode->height * mode->stride;
if (length > size) {
printk(KERN_WARNING "sysfb: VRAM smaller than advertised\n");
return -EINVAL;
return ERR_PTR(-EINVAL);
}
length = PAGE_ALIGN(length);
@ -104,11 +104,11 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
res.start = base;
res.end = res.start + length - 1;
if (res.end <= res.start)
return -EINVAL;
return ERR_PTR(-EINVAL);
pd = platform_device_alloc("simple-framebuffer", 0);
if (!pd)
return -ENOMEM;
return ERR_PTR(-ENOMEM);
sysfb_apply_efi_quirks(pd);
@ -124,10 +124,10 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
if (ret)
goto err_put_device;
return 0;
return pd;
err_put_device:
platform_device_put(pd);
return ret;
return ERR_PTR(ret);
}

View File

@ -351,6 +351,8 @@ source "drivers/gpu/drm/etnaviv/Kconfig"
source "drivers/gpu/drm/hisilicon/Kconfig"
source "drivers/gpu/drm/logicvc/Kconfig"
source "drivers/gpu/drm/mediatek/Kconfig"
source "drivers/gpu/drm/mxsfb/Kconfig"

View File

@ -121,6 +121,7 @@ obj-$(CONFIG_DRM_STM) += stm/
obj-$(CONFIG_DRM_STI) += sti/
obj-y += imx/
obj-$(CONFIG_DRM_INGENIC) += ingenic/
obj-$(CONFIG_DRM_LOGICVC) += logicvc/
obj-$(CONFIG_DRM_MEDIATEK) += mediatek/
obj-$(CONFIG_DRM_MESON) += meson/
obj-y += i2c/

View File

@ -6592,12 +6592,14 @@ dm_crtc_duplicate_state(struct drm_crtc *crtc)
return &state->base;
}
#ifdef CONFIG_DEBUG_FS
static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc)
{
crtc_debugfs_init(crtc);
return 0;
}
#endif
static inline int dm_set_vupdate_irq(struct drm_crtc *crtc, bool enable)
{
@ -6691,7 +6693,9 @@ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = {
.enable_vblank = dm_enable_vblank,
.disable_vblank = dm_disable_vblank,
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
#if defined(CONFIG_DEBUG_FS)
.late_register = amdgpu_dm_crtc_late_register,
#endif
};
static enum drm_connector_status

View File

@ -94,6 +94,8 @@ config DRM_ITE_IT6505
select DRM_KMS_HELPER
select DRM_DP_HELPER
select EXTCON
select CRYPTO
select CRYPTO_HASH
help
ITE IT6505 DisplayPort bridge chip driver.

View File

@ -1065,6 +1065,10 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv)
ADV7511_CEC_I2C_ADDR_DEFAULT);
if (IS_ERR(adv->i2c_cec))
return PTR_ERR(adv->i2c_cec);
regmap_write(adv->regmap, ADV7511_REG_CEC_I2C_ADDR,
adv->i2c_cec->addr << 1);
i2c_set_clientdata(adv->i2c_cec, adv);
adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
@ -1271,9 +1275,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
if (ret)
goto err_i2c_unregister_packet;
regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR,
adv7511->i2c_cec->addr << 1);
INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work);
if (i2c->irq) {

View File

@ -1637,16 +1637,16 @@ static int anx7625_parse_dt(struct device *dev,
if (of_property_read_u32(ep0, "bus-type", &bus_type))
bus_type = 0;
mipi_lanes = of_property_count_u32_elems(ep0, "data-lanes");
mipi_lanes = drm_of_get_data_lanes_count(ep0, 1, MAX_LANES_SUPPORT);
of_node_put(ep0);
}
if (bus_type == V4L2_FWNODE_BUS_TYPE_PARALLEL) /* bus type is Parallel(DSI) */
pdata->is_dpi = 0;
pdata->mipi_lanes = mipi_lanes;
if (pdata->mipi_lanes > MAX_LANES_SUPPORT || pdata->mipi_lanes <= 0)
pdata->mipi_lanes = MAX_LANES_SUPPORT;
pdata->mipi_lanes = MAX_LANES_SUPPORT;
if (mipi_lanes > 0)
pdata->mipi_lanes = mipi_lanes;
if (pdata->is_dpi)
DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DPI host node.\n");
@ -1658,8 +1658,10 @@ static int anx7625_parse_dt(struct device *dev,
pdata->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
if (IS_ERR(pdata->panel_bridge)) {
if (PTR_ERR(pdata->panel_bridge) == -ENODEV)
if (PTR_ERR(pdata->panel_bridge) == -ENODEV) {
pdata->panel_bridge = NULL;
return 0;
}
return PTR_ERR(pdata->panel_bridge);
}

View File

@ -498,21 +498,18 @@ static int chipone_dsi_attach(struct chipone *icn)
{
struct mipi_dsi_device *dsi = icn->dsi;
struct device *dev = icn->dev;
struct device_node *endpoint;
int dsi_lanes, ret;
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
of_node_put(endpoint);
dsi_lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, 0, 1, 4);
/*
* If the 'data-lanes' property does not exist in DT or is invalid,
* default to previously hard-coded behavior, which was 4 data lanes.
*/
if (dsi_lanes >= 1 && dsi_lanes <= 4)
icn->dsi->lanes = dsi_lanes;
else
if (dsi_lanes < 0)
icn->dsi->lanes = 4;
else
icn->dsi->lanes = dsi_lanes;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |

View File

@ -607,7 +607,6 @@ static int lt8912_parse_dt(struct lt8912 *lt)
int ret;
int data_lanes;
struct device_node *port_node;
struct device_node *endpoint;
gp_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(gp_reset)) {
@ -618,16 +617,12 @@ static int lt8912_parse_dt(struct lt8912 *lt)
}
lt->gp_reset = gp_reset;
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
if (!endpoint)
return -ENODEV;
data_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
of_node_put(endpoint);
data_lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, -1, 1, 4);
if (data_lanes < 0) {
dev_err(lt->dev, "%s: Bad data-lanes property\n", __func__);
return data_lanes;
}
lt->data_lanes = data_lanes;
lt->host_node = of_graph_get_remote_node(dev->of_node, 0, -1);

View File

@ -686,7 +686,7 @@ static int lt9211_host_attach(struct lt9211 *ctx)
int ret;
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
host_node = of_graph_get_remote_port_parent(endpoint);
host = of_find_mipi_dsi_host_by_node(host_node);
of_node_put(host_node);
@ -695,8 +695,8 @@ static int lt9211_host_attach(struct lt9211 *ctx)
if (!host)
return -EPROBE_DEFER;
if (dsi_lanes < 0 || dsi_lanes > 4)
return -EINVAL;
if (dsi_lanes < 0)
return dsi_lanes;
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi))

View File

@ -1890,18 +1890,18 @@ static int tc_mipi_dsi_host_attach(struct tc_data *tc)
int dsi_lanes, ret;
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
host_node = of_graph_get_remote_port_parent(endpoint);
host = of_find_mipi_dsi_host_by_node(host_node);
of_node_put(host_node);
of_node_put(endpoint);
if (dsi_lanes <= 0 || dsi_lanes > 4)
return -EINVAL;
if (!host)
return -EPROBE_DEFER;
if (dsi_lanes < 0)
return dsi_lanes;
dsi = mipi_dsi_device_register_full(host, &info);
if (IS_ERR(dsi))
return dev_err_probe(dev, PTR_ERR(dsi),

View File

@ -529,8 +529,7 @@ static int tc358775_parse_dt(struct device_node *np, struct tc_data *tc)
struct device_node *endpoint;
struct device_node *parent;
struct device_node *remote;
struct property *prop;
int len = 0;
int dsi_lanes = -1;
/*
* To get the data-lanes of dsi, we need to access the dsi0_out of port1
@ -544,25 +543,15 @@ static int tc358775_parse_dt(struct device_node *np, struct tc_data *tc)
of_node_put(endpoint);
if (parent) {
/* dsi0 port 1 */
endpoint = of_graph_get_endpoint_by_regs(parent, 1, -1);
dsi_lanes = drm_of_get_data_lanes_count_ep(parent, 1, -1, 1, 4);
of_node_put(parent);
if (endpoint) {
prop = of_find_property(endpoint, "data-lanes",
&len);
of_node_put(endpoint);
if (!prop) {
dev_err(tc->dev,
"failed to find data lane\n");
return -EPROBE_DEFER;
}
}
}
}
tc->num_dsi_lanes = len / sizeof(u32);
if (dsi_lanes < 0)
return dsi_lanes;
if (tc->num_dsi_lanes < 1 || tc->num_dsi_lanes > 4)
return -EINVAL;
tc->num_dsi_lanes = dsi_lanes;
tc->host_node = of_graph_get_remote_node(np, 0, 0);
if (!tc->host_node)

View File

@ -140,12 +140,10 @@ struct sn65dsi83 {
struct drm_bridge bridge;
struct device *dev;
struct regmap *regmap;
struct device_node *host_node;
struct mipi_dsi_device *dsi;
struct drm_bridge *panel_bridge;
struct gpio_desc *enable_gpio;
struct regulator *vcc;
int dsi_lanes;
bool lvds_dual_link;
bool lvds_dual_link_even_odd_swap;
};
@ -306,7 +304,7 @@ static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx,
*/
return DIV_ROUND_UP(clamp((unsigned int)mode->clock *
mipi_dsi_pixel_format_to_bpp(ctx->dsi->format) /
ctx->dsi_lanes / 2, 40000U, 500000U), 5000U);
ctx->dsi->lanes / 2, 40000U, 500000U), 5000U);
}
static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx)
@ -314,7 +312,7 @@ static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx)
/* The divider is (DSI_CLK / LVDS_CLK) - 1, which really is: */
unsigned int dsi_div = mipi_dsi_pixel_format_to_bpp(ctx->dsi->format);
dsi_div /= ctx->dsi_lanes;
dsi_div /= ctx->dsi->lanes;
if (!ctx->lvds_dual_link)
dsi_div /= 2;
@ -405,7 +403,7 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
/* Set number of DSI lanes and LVDS link config. */
regmap_write(ctx->regmap, REG_DSI_LANE,
REG_DSI_LANE_DSI_CHANNEL_MODE_SINGLE |
REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi_lanes - 1)) |
REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi->lanes - 1)) |
/* CHB is DSI85-only, set to default on DSI83/DSI84 */
REG_DSI_LANE_CHB_DSI_LANES(3));
/* No equalization. */
@ -569,22 +567,6 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
{
struct drm_bridge *panel_bridge;
struct device *dev = ctx->dev;
struct device_node *endpoint;
int ret;
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
ctx->dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
ctx->host_node = of_graph_get_remote_port_parent(endpoint);
of_node_put(endpoint);
if (ctx->dsi_lanes <= 0 || ctx->dsi_lanes > 4) {
ret = -EINVAL;
goto err_put_node;
}
if (!ctx->host_node) {
ret = -ENODEV;
goto err_put_node;
}
ctx->lvds_dual_link = false;
ctx->lvds_dual_link_even_odd_swap = false;
@ -610,10 +592,8 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
}
panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0);
if (IS_ERR(panel_bridge)) {
ret = PTR_ERR(panel_bridge);
goto err_put_node;
}
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
ctx->panel_bridge = panel_bridge;
@ -623,15 +603,13 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
"Failed to get supply 'vcc'\n");
return 0;
err_put_node:
of_node_put(ctx->host_node);
return ret;
}
static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
{
struct device *dev = ctx->dev;
struct device_node *host_node;
struct device_node *endpoint;
struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host;
const struct mipi_dsi_device_info info = {
@ -639,13 +617,20 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
.channel = 0,
.node = NULL,
};
int ret;
int dsi_lanes, ret;
host = of_find_mipi_dsi_host_by_node(ctx->host_node);
if (!host) {
dev_err(dev, "failed to find dsi host\n");
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
host_node = of_graph_get_remote_port_parent(endpoint);
host = of_find_mipi_dsi_host_by_node(host_node);
of_node_put(host_node);
of_node_put(endpoint);
if (!host)
return -EPROBE_DEFER;
}
if (dsi_lanes < 0)
return dsi_lanes;
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi))
@ -654,7 +639,7 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
ctx->dsi = dsi;
dsi->lanes = ctx->dsi_lanes;
dsi->lanes = dsi_lanes;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
@ -701,10 +686,8 @@ static int sn65dsi83_probe(struct i2c_client *client,
return ret;
ctx->regmap = devm_regmap_init_i2c(client, &sn65dsi83_regmap_config);
if (IS_ERR(ctx->regmap)) {
ret = PTR_ERR(ctx->regmap);
goto err_put_node;
}
if (IS_ERR(ctx->regmap))
return PTR_ERR(ctx->regmap);
dev_set_drvdata(dev, ctx);
i2c_set_clientdata(client, ctx);
@ -721,8 +704,6 @@ static int sn65dsi83_probe(struct i2c_client *client,
err_remove_bridge:
drm_bridge_remove(&ctx->bridge);
err_put_node:
of_node_put(ctx->host_node);
return ret;
}
@ -731,7 +712,6 @@ static int sn65dsi83_remove(struct i2c_client *client)
struct sn65dsi83 *ctx = i2c_get_clientdata(client);
drm_bridge_remove(&ctx->bridge);
of_node_put(ctx->host_node);
return 0;
}

View File

@ -1142,8 +1142,8 @@ static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata,
* mappings that the hardware supports.
*/
endpoint = of_graph_get_endpoint_by_regs(np, 1, -1);
dp_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
if (dp_lanes > 0 && dp_lanes <= SN_MAX_DP_LANES) {
dp_lanes = drm_of_get_data_lanes_count(endpoint, 1, SN_MAX_DP_LANES);
if (dp_lanes > 0) {
of_property_read_u32_array(endpoint, "data-lanes",
lane_assignments, dp_lanes);
of_property_read_u32_array(endpoint, "lane-polarities",

View File

@ -3860,9 +3860,7 @@ int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr,
if (!mgr->mst_primary)
goto out_fail;
ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd,
DP_RECEIVER_CAP_SIZE);
if (ret != DP_RECEIVER_CAP_SIZE) {
if (drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd) < 0) {
drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n");
goto out_fail;
}
@ -4911,8 +4909,7 @@ void drm_dp_mst_dump_topology(struct seq_file *m,
u8 buf[DP_PAYLOAD_TABLE_SIZE];
int ret;
ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE);
if (ret) {
if (drm_dp_read_dpcd_caps(mgr->aux, buf) < 0) {
seq_printf(m, "dpcd read failed\n");
goto out;
}

View File

@ -1613,6 +1613,35 @@ static const void *edid_extension_block_data(const struct edid *edid, int index)
return edid_block_data(edid, index + 1);
}
static int drm_edid_block_count(const struct drm_edid *drm_edid)
{
int num_blocks;
/* Starting point */
num_blocks = edid_block_count(drm_edid->edid);
/* Limit by allocated size */
num_blocks = min(num_blocks, (int)drm_edid->size / EDID_LENGTH);
return num_blocks;
}
static int drm_edid_extension_block_count(const struct drm_edid *drm_edid)
{
return drm_edid_block_count(drm_edid) - 1;
}
static const void *drm_edid_block_data(const struct drm_edid *drm_edid, int index)
{
return edid_block_data(drm_edid->edid, index);
}
static const void *drm_edid_extension_block_data(const struct drm_edid *drm_edid,
int index)
{
return edid_extension_block_data(drm_edid->edid, index);
}
/*
* Initializer helper for legacy interfaces, where we have no choice but to
* trust edid size. Not for general purpose use.
@ -1665,8 +1694,8 @@ static const void *__drm_edid_iter_next(struct drm_edid_iter *iter)
if (!iter->drm_edid)
return NULL;
if (iter->index < edid_block_count(iter->drm_edid->edid))
block = edid_block_data(iter->drm_edid->edid, iter->index++);
if (iter->index < drm_edid_block_count(iter->drm_edid))
block = drm_edid_block_data(iter->drm_edid, iter->index++);
return block;
}
@ -1992,13 +2021,16 @@ bool drm_edid_is_valid(struct edid *edid)
EXPORT_SYMBOL(drm_edid_is_valid);
static struct edid *edid_filter_invalid_blocks(const struct edid *edid,
int invalid_blocks)
int invalid_blocks,
size_t *alloc_size)
{
struct edid *new, *dest_block;
int valid_extensions = edid->extensions - invalid_blocks;
int i;
new = kmalloc(edid_size_by_blocks(valid_extensions + 1), GFP_KERNEL);
*alloc_size = edid_size_by_blocks(valid_extensions + 1);
new = kmalloc(*alloc_size, GFP_KERNEL);
if (!new)
goto out;
@ -2111,7 +2143,8 @@ static void connector_bad_edid(struct drm_connector *connector,
}
/* Get override or firmware EDID */
static struct edid *drm_get_override_edid(struct drm_connector *connector)
static struct edid *drm_get_override_edid(struct drm_connector *connector,
size_t *alloc_size)
{
struct edid *override = NULL;
@ -2121,6 +2154,10 @@ static struct edid *drm_get_override_edid(struct drm_connector *connector)
if (!override)
override = drm_load_edid_firmware(connector);
/* FIXME: Get alloc size from deeper down the stack */
if (!IS_ERR_OR_NULL(override) && alloc_size)
*alloc_size = edid_size(override);
return IS_ERR(override) ? NULL : override;
}
@ -2140,7 +2177,7 @@ int drm_add_override_edid_modes(struct drm_connector *connector)
struct edid *override;
int num_modes = 0;
override = drm_get_override_edid(connector);
override = drm_get_override_edid(connector, NULL);
if (override) {
drm_connector_update_edid_property(connector, override);
num_modes = drm_add_edid_modes(connector, override);
@ -2189,39 +2226,20 @@ static enum edid_block_status edid_block_read(void *block, unsigned int block_nu
return status;
}
/**
* drm_do_get_edid - get EDID data using a custom EDID block read function
* @connector: connector we're probing
* @read_block: EDID block read function
* @context: private data passed to the block read function
*
* When the I2C adapter connected to the DDC bus is hidden behind a device that
* exposes a different interface to read EDID blocks this function can be used
* to get EDID data using a custom block read function.
*
* As in the general case the DDC bus is accessible by the kernel at the I2C
* level, drivers must make all reasonable efforts to expose it as an I2C
* adapter and use drm_get_edid() instead of abusing this function.
*
* The EDID may be overridden using debugfs override_edid or firmware EDID
* (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
* order. Having either of them bypasses actual EDID reads.
*
* Return: Pointer to valid EDID or NULL if we couldn't find any.
*/
struct edid *drm_do_get_edid(struct drm_connector *connector,
read_block_fn read_block,
void *context)
static struct edid *_drm_do_get_edid(struct drm_connector *connector,
read_block_fn read_block, void *context,
size_t *size)
{
enum edid_block_status status;
int i, invalid_blocks = 0;
struct edid *edid, *new;
size_t alloc_size = EDID_LENGTH;
edid = drm_get_override_edid(connector);
edid = drm_get_override_edid(connector, &alloc_size);
if (edid)
goto ok;
edid = kmalloc(EDID_LENGTH, GFP_KERNEL);
edid = kmalloc(alloc_size, GFP_KERNEL);
if (!edid)
return NULL;
@ -2249,7 +2267,8 @@ struct edid *drm_do_get_edid(struct drm_connector *connector,
if (!edid_extension_block_count(edid))
goto ok;
new = krealloc(edid, edid_size(edid), GFP_KERNEL);
alloc_size = edid_size(edid);
new = krealloc(edid, alloc_size, GFP_KERNEL);
if (!new)
goto fail;
edid = new;
@ -2271,18 +2290,130 @@ struct edid *drm_do_get_edid(struct drm_connector *connector,
if (invalid_blocks) {
connector_bad_edid(connector, edid, edid_block_count(edid));
edid = edid_filter_invalid_blocks(edid, invalid_blocks);
edid = edid_filter_invalid_blocks(edid, invalid_blocks,
&alloc_size);
}
ok:
if (size)
*size = alloc_size;
return edid;
fail:
kfree(edid);
return NULL;
}
/**
* drm_do_get_edid - get EDID data using a custom EDID block read function
* @connector: connector we're probing
* @read_block: EDID block read function
* @context: private data passed to the block read function
*
* When the I2C adapter connected to the DDC bus is hidden behind a device that
* exposes a different interface to read EDID blocks this function can be used
* to get EDID data using a custom block read function.
*
* As in the general case the DDC bus is accessible by the kernel at the I2C
* level, drivers must make all reasonable efforts to expose it as an I2C
* adapter and use drm_get_edid() instead of abusing this function.
*
* The EDID may be overridden using debugfs override_edid or firmware EDID
* (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
* order. Having either of them bypasses actual EDID reads.
*
* Return: Pointer to valid EDID or NULL if we couldn't find any.
*/
struct edid *drm_do_get_edid(struct drm_connector *connector,
read_block_fn read_block,
void *context)
{
return _drm_do_get_edid(connector, read_block, context, NULL);
}
EXPORT_SYMBOL_GPL(drm_do_get_edid);
/* Allocate struct drm_edid container *without* duplicating the edid data */
static const struct drm_edid *_drm_edid_alloc(const void *edid, size_t size)
{
struct drm_edid *drm_edid;
if (!edid || !size || size < EDID_LENGTH)
return NULL;
drm_edid = kzalloc(sizeof(*drm_edid), GFP_KERNEL);
if (drm_edid) {
drm_edid->edid = edid;
drm_edid->size = size;
}
return drm_edid;
}
/**
* drm_edid_alloc - Allocate a new drm_edid container
* @edid: Pointer to raw EDID data
* @size: Size of memory allocated for EDID
*
* Allocate a new drm_edid container. Do not calculate edid size from edid, pass
* the actual size that has been allocated for the data. There is no validation
* of the raw EDID data against the size, but at least the EDID base block must
* fit in the buffer.
*
* The returned pointer must be freed using drm_edid_free().
*
* Return: drm_edid container, or NULL on errors
*/
const struct drm_edid *drm_edid_alloc(const void *edid, size_t size)
{
const struct drm_edid *drm_edid;
if (!edid || !size || size < EDID_LENGTH)
return NULL;
edid = kmemdup(edid, size, GFP_KERNEL);
if (!edid)
return NULL;
drm_edid = _drm_edid_alloc(edid, size);
if (!drm_edid)
kfree(edid);
return drm_edid;
}
EXPORT_SYMBOL(drm_edid_alloc);
/**
* drm_edid_dup - Duplicate a drm_edid container
* @drm_edid: EDID to duplicate
*
* The returned pointer must be freed using drm_edid_free().
*
* Returns: drm_edid container copy, or NULL on errors
*/
const struct drm_edid *drm_edid_dup(const struct drm_edid *drm_edid)
{
if (!drm_edid)
return NULL;
return drm_edid_alloc(drm_edid->edid, drm_edid->size);
}
EXPORT_SYMBOL(drm_edid_dup);
/**
* drm_edid_free - Free the drm_edid container
* @drm_edid: EDID to free
*/
void drm_edid_free(const struct drm_edid *drm_edid)
{
if (!drm_edid)
return;
kfree(drm_edid->edid);
kfree(drm_edid);
}
EXPORT_SYMBOL(drm_edid_free);
/**
* drm_probe_ddc() - probe DDC presence
* @adapter: I2C adapter to probe
@ -2319,12 +2450,118 @@ struct edid *drm_get_edid(struct drm_connector *connector,
if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
return NULL;
edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
edid = _drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter, NULL);
drm_connector_update_edid_property(connector, edid);
return edid;
}
EXPORT_SYMBOL(drm_get_edid);
/**
* drm_edid_read_custom - Read EDID data using given EDID block read function
* @connector: Connector to use
* @read_block: EDID block read function
* @context: Private data passed to the block read function
*
* When the I2C adapter connected to the DDC bus is hidden behind a device that
* exposes a different interface to read EDID blocks this function can be used
* to get EDID data using a custom block read function.
*
* As in the general case the DDC bus is accessible by the kernel at the I2C
* level, drivers must make all reasonable efforts to expose it as an I2C
* adapter and use drm_edid_read() or drm_edid_read_ddc() instead of abusing
* this function.
*
* The EDID may be overridden using debugfs override_edid or firmware EDID
* (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
* order. Having either of them bypasses actual EDID reads.
*
* The returned pointer must be freed using drm_edid_free().
*
* Return: Pointer to EDID, or NULL if probe/read failed.
*/
const struct drm_edid *drm_edid_read_custom(struct drm_connector *connector,
read_block_fn read_block,
void *context)
{
const struct drm_edid *drm_edid;
struct edid *edid;
size_t size = 0;
edid = _drm_do_get_edid(connector, read_block, context, &size);
if (!edid)
return NULL;
/* Sanity check for now */
drm_WARN_ON(connector->dev, !size);
drm_edid = _drm_edid_alloc(edid, size);
if (!drm_edid)
kfree(edid);
return drm_edid;
}
EXPORT_SYMBOL(drm_edid_read_custom);
/**
* drm_edid_read_ddc - Read EDID data using given I2C adapter
* @connector: Connector to use
* @adapter: I2C adapter to use for DDC
*
* Read EDID using the given I2C adapter.
*
* The EDID may be overridden using debugfs override_edid or firmware EDID
* (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
* order. Having either of them bypasses actual EDID reads.
*
* Prefer initializing connector->ddc with drm_connector_init_with_ddc() and
* using drm_edid_read() instead of this function.
*
* The returned pointer must be freed using drm_edid_free().
*
* Return: Pointer to EDID, or NULL if probe/read failed.
*/
const struct drm_edid *drm_edid_read_ddc(struct drm_connector *connector,
struct i2c_adapter *adapter)
{
const struct drm_edid *drm_edid;
if (connector->force == DRM_FORCE_OFF)
return NULL;
if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
return NULL;
drm_edid = drm_edid_read_custom(connector, drm_do_probe_ddc_edid, adapter);
/* Note: Do *not* call connector updates here. */
return drm_edid;
}
EXPORT_SYMBOL(drm_edid_read_ddc);
/**
* drm_edid_read - Read EDID data using connector's I2C adapter
* @connector: Connector to use
*
* Read EDID using the connector's I2C adapter.
*
* The EDID may be overridden using debugfs override_edid or firmware EDID
* (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
* order. Having either of them bypasses actual EDID reads.
*
* The returned pointer must be freed using drm_edid_free().
*
* Return: Pointer to EDID, or NULL if probe/read failed.
*/
const struct drm_edid *drm_edid_read(struct drm_connector *connector)
{
if (drm_WARN_ON(connector->dev, !connector->ddc))
return NULL;
return drm_edid_read_ddc(connector, connector->ddc);
}
EXPORT_SYMBOL(drm_edid_read);
static u32 edid_extract_panel_id(const struct edid *edid)
{
/*
@ -3574,22 +3811,21 @@ static int add_detailed_modes(struct drm_connector *connector,
const u8 *drm_find_edid_extension(const struct drm_edid *drm_edid,
int ext_id, int *ext_index)
{
const struct edid *edid = drm_edid ? drm_edid->edid : NULL;
const u8 *edid_ext = NULL;
int i;
/* No EDID or EDID extensions */
if (!edid || !edid_extension_block_count(edid))
if (!drm_edid || !drm_edid_extension_block_count(drm_edid))
return NULL;
/* Find CEA extension */
for (i = *ext_index; i < edid_extension_block_count(edid); i++) {
edid_ext = edid_extension_block_data(edid, i);
for (i = *ext_index; i < drm_edid_extension_block_count(drm_edid); i++) {
edid_ext = drm_edid_extension_block_data(drm_edid, i);
if (edid_block_tag(edid_ext) == ext_id)
break;
}
if (i >= edid_extension_block_count(edid))
if (i >= drm_edid_extension_block_count(drm_edid))
return NULL;
*ext_index = i + 1;
@ -4482,6 +4718,20 @@ __cea_db_iter_current_block(const struct cea_db_iter *iter)
return NULL;
}
/*
* References:
* - CTA-861-H section 7.3.3 CTA Extension Version 3
*/
static int cea_db_collection_size(const u8 *cta)
{
u8 d = cta[2];
if (d < 4 || d > 127)
return 0;
return d - 4;
}
/*
* References:
* - VESA E-EDID v1.4
@ -4492,17 +4742,19 @@ static const void *__cea_db_iter_edid_next(struct cea_db_iter *iter)
const u8 *ext;
drm_edid_iter_for_each(ext, &iter->edid_iter) {
int size;
/* Only support CTA Extension revision 3+ */
if (ext[0] != CEA_EXT || cea_revision(ext) < 3)
continue;
iter->index = 4;
iter->end = ext[2];
if (iter->end == 0)
iter->end = 127;
if (iter->end < 4 || iter->end > 127)
size = cea_db_collection_size(ext);
if (!size)
continue;
iter->index = 4;
iter->end = iter->index + size;
return ext;
}

View File

@ -26,12 +26,22 @@
/**
* DOC: cma helpers
*
* The Contiguous Memory Allocator reserves a pool of memory at early boot
* that is used to service requests for large blocks of contiguous memory.
* The DRM GEM/CMA helpers are a means to provide buffer objects that are
* presented to the device as a contiguous chunk of memory. This is useful
* for devices that do not support scatter-gather DMA (either directly or
* by using an intimately attached IOMMU).
*
* The DRM GEM/CMA helpers use this allocator as a means to provide buffer
* objects that are physically contiguous in memory. This is useful for
* display drivers that are unable to map scattered buffers via an IOMMU.
* Despite the name, the DRM GEM/CMA helpers are not hardwired to use the
* Contiguous Memory Allocator (CMA).
*
* For devices that access the memory bus through an (external) IOMMU then
* the buffer objects are allocated using a traditional page-based
* allocator and may be scattered through physical memory. However they
* are contiguous in the IOVA space so appear contiguous to devices using
* them.
*
* For other devices then the helpers rely on CMA to provide buffer
* objects that are physically contiguous in memory.
*
* For GEM callback helpers in struct &drm_gem_object functions, see likewise
* named functions with an _object_ infix (e.g., drm_gem_cma_object_vmap() wraps
@ -111,8 +121,14 @@ error:
* @drm: DRM device
* @size: size of the object to allocate
*
* This function creates a CMA GEM object and allocates a contiguous chunk of
* memory as backing store.
* This function creates a CMA GEM object and allocates memory as backing store.
* The allocated memory will occupy a contiguous chunk of bus address space.
*
* For devices that are directly connected to the memory bus then the allocated
* memory will be physically contiguous. For devices that access through an
* IOMMU, then the allocated memory is not expected to be physically contiguous
* because having contiguous IOVAs is sufficient to meet a devices DMA
* requirements.
*
* Returns:
* A struct drm_gem_cma_object * on success or an ERR_PTR()-encoded negative
@ -162,9 +178,12 @@ EXPORT_SYMBOL_GPL(drm_gem_cma_create);
* @size: size of the object to allocate
* @handle: return location for the GEM handle
*
* This function creates a CMA GEM object, allocating a physically contiguous
* chunk of memory as backing store. The GEM object is then added to the list
* of object associated with the given file and a handle to it is returned.
* This function creates a CMA GEM object, allocating a chunk of memory as
* backing store. The GEM object is then added to the list of object associated
* with the given file and a handle to it is returned.
*
* The allocated memory will occupy a contiguous chunk of bus address space.
* See drm_gem_cma_create() for more details.
*
* Returns:
* A struct drm_gem_cma_object * on success or an ERR_PTR()-encoded negative

View File

@ -430,3 +430,64 @@ int drm_of_lvds_get_data_mapping(const struct device_node *port)
return -EINVAL;
}
EXPORT_SYMBOL_GPL(drm_of_lvds_get_data_mapping);
/**
* drm_of_get_data_lanes_count - Get DSI/(e)DP data lane count
* @endpoint: DT endpoint node of the DSI/(e)DP source or sink
* @min: minimum supported number of data lanes
* @max: maximum supported number of data lanes
*
* Count DT "data-lanes" property elements and check for validity.
*
* Return:
* * min..max - positive integer count of "data-lanes" elements
* * -ve - the "data-lanes" property is missing or invalid
* * -EINVAL - the "data-lanes" property is unsupported
*/
int drm_of_get_data_lanes_count(const struct device_node *endpoint,
const unsigned int min, const unsigned int max)
{
int ret;
ret = of_property_count_u32_elems(endpoint, "data-lanes");
if (ret < 0)
return ret;
if (ret < min || ret > max)
return -EINVAL;
return ret;
}
EXPORT_SYMBOL_GPL(drm_of_get_data_lanes_count);
/**
* drm_of_get_data_lanes_count_ep - Get DSI/(e)DP data lane count by endpoint
* @port: DT port node of the DSI/(e)DP source or sink
* @port_reg: identifier (value of reg property) of the parent port node
* @reg: identifier (value of reg property) of the endpoint node
* @min: minimum supported number of data lanes
* @max: maximum supported number of data lanes
*
* Count DT "data-lanes" property elements and check for validity.
* This variant uses endpoint specifier.
*
* Return:
* * min..max - positive integer count of "data-lanes" elements
* * -EINVAL - the "data-mapping" property is unsupported
* * -ENODEV - the "data-mapping" property is missing
*/
int drm_of_get_data_lanes_count_ep(const struct device_node *port,
int port_reg, int reg,
const unsigned int min,
const unsigned int max)
{
struct device_node *endpoint;
int ret;
endpoint = of_graph_get_endpoint_by_regs(port, port_reg, reg);
ret = drm_of_get_data_lanes_count(endpoint, min, max);
of_node_put(endpoint);
return ret;
}
EXPORT_SYMBOL_GPL(drm_of_get_data_lanes_count_ep);

View File

@ -354,6 +354,24 @@ drm_helper_probe_detect(struct drm_connector *connector,
}
EXPORT_SYMBOL(drm_helper_probe_detect);
static int drm_helper_probe_get_modes(struct drm_connector *connector)
{
const struct drm_connector_helper_funcs *connector_funcs =
connector->helper_private;
int count;
count = connector_funcs->get_modes(connector);
/*
* Fallback for when DDC probe failed in drm_get_edid() and thus skipped
* override/firmware EDID.
*/
if (count == 0 && connector->status == connector_status_connected)
count = drm_add_override_edid_modes(connector);
return count;
}
static int __drm_helper_update_and_validate(struct drm_connector *connector,
uint32_t maxX, uint32_t maxY,
struct drm_modeset_acquire_ctx *ctx)
@ -473,8 +491,6 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
{
struct drm_device *dev = connector->dev;
struct drm_display_mode *mode;
const struct drm_connector_helper_funcs *connector_funcs =
connector->helper_private;
int count = 0, ret;
enum drm_connector_status old_status;
struct drm_modeset_acquire_ctx ctx;
@ -559,14 +575,7 @@ retry:
goto exit;
}
count = (*connector_funcs->get_modes)(connector);
/*
* Fallback for when DDC probe failed in drm_get_edid() and thus skipped
* override/firmware EDID.
*/
if (count == 0 && connector->status == connector_status_connected)
count = drm_add_override_edid_modes(connector);
count = drm_helper_probe_get_modes(connector);
if (count == 0 && (connector->status == connector_status_connected ||
connector->status == connector_status_unknown)) {

View File

@ -874,8 +874,10 @@ static int drm_syncobj_transfer_to_timeline(struct drm_file *file_private,
fence = dma_fence_unwrap_merge(tmp);
dma_fence_put(tmp);
if (!fence)
if (!fence) {
ret = -ENOMEM;
goto err_put_timeline;
}
chain = dma_fence_chain_alloc();
if (!chain) {

View File

@ -0,0 +1,9 @@
config DRM_LOGICVC
tristate "LogiCVC DRM"
depends on DRM
depends on OF || COMPILE_TEST
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER
help
DRM display driver for the logiCVC programmable logic block from Xylon

View File

@ -0,0 +1,9 @@
logicvc-drm-y += \
logicvc_crtc.o \
logicvc_drm.o \
logicvc_interface.o \
logicvc_layer.o \
logicvc_mode.o \
logicvc_of.o
obj-$(CONFIG_DRM_LOGICVC) += logicvc-drm.o

View File

@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include "logicvc_crtc.h"
#include "logicvc_drm.h"
#include "logicvc_interface.h"
#include "logicvc_layer.h"
#include "logicvc_regs.h"
#define logicvc_crtc(c) \
container_of(c, struct logicvc_crtc, drm_crtc)
static enum drm_mode_status
logicvc_crtc_mode_valid(struct drm_crtc *drm_crtc,
const struct drm_display_mode *mode)
{
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
return -EINVAL;
return 0;
}
static void logicvc_crtc_atomic_begin(struct drm_crtc *drm_crtc,
struct drm_atomic_state *state)
{
struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc);
struct drm_crtc_state *old_state =
drm_atomic_get_old_crtc_state(state, drm_crtc);
struct drm_device *drm_dev = drm_crtc->dev;
unsigned long flags;
/*
* We need to grab the pending event here if vblank was already enabled
* since we won't get a call to atomic_enable to grab it.
*/
if (drm_crtc->state->event && old_state->active) {
spin_lock_irqsave(&drm_dev->event_lock, flags);
WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0);
crtc->event = drm_crtc->state->event;
drm_crtc->state->event = NULL;
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
}
}
static void logicvc_crtc_atomic_enable(struct drm_crtc *drm_crtc,
struct drm_atomic_state *state)
{
struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc);
struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
struct drm_crtc_state *old_state =
drm_atomic_get_old_crtc_state(state, drm_crtc);
struct drm_crtc_state *new_state =
drm_atomic_get_new_crtc_state(state, drm_crtc);
struct drm_display_mode *mode = &new_state->adjusted_mode;
struct drm_device *drm_dev = drm_crtc->dev;
unsigned int hact, hfp, hsl, hbp;
unsigned int vact, vfp, vsl, vbp;
unsigned long flags;
u32 ctrl;
/* Timings */
hact = mode->hdisplay;
hfp = mode->hsync_start - mode->hdisplay;
hsl = mode->hsync_end - mode->hsync_start;
hbp = mode->htotal - mode->hsync_end;
vact = mode->vdisplay;
vfp = mode->vsync_start - mode->vdisplay;
vsl = mode->vsync_end - mode->vsync_start;
vbp = mode->vtotal - mode->vsync_end;
regmap_write(logicvc->regmap, LOGICVC_HSYNC_FRONT_PORCH_REG, hfp - 1);
regmap_write(logicvc->regmap, LOGICVC_HSYNC_REG, hsl - 1);
regmap_write(logicvc->regmap, LOGICVC_HSYNC_BACK_PORCH_REG, hbp - 1);
regmap_write(logicvc->regmap, LOGICVC_HRES_REG, hact - 1);
regmap_write(logicvc->regmap, LOGICVC_VSYNC_FRONT_PORCH_REG, vfp - 1);
regmap_write(logicvc->regmap, LOGICVC_VSYNC_REG, vsl - 1);
regmap_write(logicvc->regmap, LOGICVC_VSYNC_BACK_PORCH_REG, vbp - 1);
regmap_write(logicvc->regmap, LOGICVC_VRES_REG, vact - 1);
/* Signals */
ctrl = LOGICVC_CTRL_HSYNC_ENABLE | LOGICVC_CTRL_VSYNC_ENABLE |
LOGICVC_CTRL_DE_ENABLE;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
ctrl |= LOGICVC_CTRL_HSYNC_INVERT;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
ctrl |= LOGICVC_CTRL_VSYNC_INVERT;
if (logicvc->interface) {
struct drm_connector *connector =
&logicvc->interface->drm_connector;
struct drm_display_info *display_info =
&connector->display_info;
if (display_info->bus_flags & DRM_BUS_FLAG_DE_LOW)
ctrl |= LOGICVC_CTRL_DE_INVERT;
if (display_info->bus_flags &
DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
ctrl |= LOGICVC_CTRL_CLOCK_INVERT;
}
regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG,
LOGICVC_CTRL_HSYNC_ENABLE |
LOGICVC_CTRL_HSYNC_INVERT |
LOGICVC_CTRL_VSYNC_ENABLE |
LOGICVC_CTRL_VSYNC_INVERT |
LOGICVC_CTRL_DE_ENABLE |
LOGICVC_CTRL_DE_INVERT |
LOGICVC_CTRL_PIXEL_INVERT |
LOGICVC_CTRL_CLOCK_INVERT, ctrl);
/* Generate internal state reset. */
regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0);
drm_crtc_vblank_on(drm_crtc);
/* Register our event after vblank is enabled. */
if (drm_crtc->state->event && !old_state->active) {
spin_lock_irqsave(&drm_dev->event_lock, flags);
WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0);
crtc->event = drm_crtc->state->event;
drm_crtc->state->event = NULL;
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
}
}
static void logicvc_crtc_atomic_disable(struct drm_crtc *drm_crtc,
struct drm_atomic_state *state)
{
struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
struct drm_device *drm_dev = drm_crtc->dev;
drm_crtc_vblank_off(drm_crtc);
/* Disable and clear CRTC bits. */
regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG,
LOGICVC_CTRL_HSYNC_ENABLE |
LOGICVC_CTRL_HSYNC_INVERT |
LOGICVC_CTRL_VSYNC_ENABLE |
LOGICVC_CTRL_VSYNC_INVERT |
LOGICVC_CTRL_DE_ENABLE |
LOGICVC_CTRL_DE_INVERT |
LOGICVC_CTRL_PIXEL_INVERT |
LOGICVC_CTRL_CLOCK_INVERT, 0);
/* Generate internal state reset. */
regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0);
/* Consume any leftover event since vblank is now disabled. */
if (drm_crtc->state->event && !drm_crtc->state->active) {
spin_lock_irq(&drm_dev->event_lock);
drm_crtc_send_vblank_event(drm_crtc, drm_crtc->state->event);
drm_crtc->state->event = NULL;
spin_unlock_irq(&drm_dev->event_lock);
}
}
static const struct drm_crtc_helper_funcs logicvc_crtc_helper_funcs = {
.mode_valid = logicvc_crtc_mode_valid,
.atomic_begin = logicvc_crtc_atomic_begin,
.atomic_enable = logicvc_crtc_atomic_enable,
.atomic_disable = logicvc_crtc_atomic_disable,
};
static int logicvc_crtc_enable_vblank(struct drm_crtc *drm_crtc)
{
struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
/* Clear any pending V_SYNC interrupt. */
regmap_write_bits(logicvc->regmap, LOGICVC_INT_STAT_REG,
LOGICVC_INT_STAT_V_SYNC, LOGICVC_INT_STAT_V_SYNC);
/* Unmask V_SYNC interrupt. */
regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG,
LOGICVC_INT_MASK_V_SYNC, 0);
return 0;
}
static void logicvc_crtc_disable_vblank(struct drm_crtc *drm_crtc)
{
struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
/* Mask V_SYNC interrupt. */
regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG,
LOGICVC_INT_MASK_V_SYNC, LOGICVC_INT_MASK_V_SYNC);
}
static const struct drm_crtc_funcs logicvc_crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = logicvc_crtc_enable_vblank,
.disable_vblank = logicvc_crtc_disable_vblank,
};
void logicvc_crtc_vblank_handler(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct logicvc_crtc *crtc = logicvc->crtc;
unsigned long flags;
if (!crtc)
return;
drm_crtc_handle_vblank(&crtc->drm_crtc);
if (crtc->event) {
spin_lock_irqsave(&drm_dev->event_lock, flags);
drm_crtc_send_vblank_event(&crtc->drm_crtc, crtc->event);
drm_crtc_vblank_put(&crtc->drm_crtc);
crtc->event = NULL;
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
}
}
int logicvc_crtc_init(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct device_node *of_node = dev->of_node;
struct logicvc_crtc *crtc;
struct logicvc_layer *layer_primary;
int ret;
crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
if (!crtc)
return -ENOMEM;
layer_primary = logicvc_layer_get_primary(logicvc);
if (!layer_primary) {
drm_err(drm_dev, "Failed to get primary layer\n");
return -EINVAL;
}
ret = drm_crtc_init_with_planes(drm_dev, &crtc->drm_crtc,
&layer_primary->drm_plane, NULL,
&logicvc_crtc_funcs, NULL);
if (ret) {
drm_err(drm_dev, "Failed to initialize CRTC\n");
return ret;
}
drm_crtc_helper_add(&crtc->drm_crtc, &logicvc_crtc_helper_funcs);
crtc->drm_crtc.port = of_graph_get_port_by_id(of_node, 1);
logicvc->crtc = crtc;
return 0;
}

View File

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#ifndef _LOGICVC_CRTC_H_
#define _LOGICVC_CRTC_H_
struct drm_pending_vblank_event;
struct logicvc_drm;
struct logicvc_crtc {
struct drm_crtc drm_crtc;
struct drm_pending_vblank_event *event;
};
void logicvc_crtc_vblank_handler(struct logicvc_drm *logicvc);
int logicvc_crtc_init(struct logicvc_drm *logicvc);
#endif

View File

@ -0,0 +1,497 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_reserved_mem.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_print.h>
#include "logicvc_crtc.h"
#include "logicvc_drm.h"
#include "logicvc_interface.h"
#include "logicvc_mode.h"
#include "logicvc_layer.h"
#include "logicvc_of.h"
#include "logicvc_regs.h"
DEFINE_DRM_GEM_CMA_FOPS(logicvc_drm_fops);
static int logicvc_drm_gem_cma_dumb_create(struct drm_file *file_priv,
struct drm_device *drm_dev,
struct drm_mode_create_dumb *args)
{
struct logicvc_drm *logicvc = logicvc_drm(drm_dev);
/* Stride is always fixed to its configuration value. */
args->pitch = logicvc->config.row_stride * DIV_ROUND_UP(args->bpp, 8);
return drm_gem_cma_dumb_create_internal(file_priv, drm_dev, args);
}
static struct drm_driver logicvc_drm_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET |
DRIVER_ATOMIC,
.fops = &logicvc_drm_fops,
.name = "logicvc-drm",
.desc = "Xylon LogiCVC DRM driver",
.date = "20200403",
.major = 1,
.minor = 0,
DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_cma_dumb_create),
};
static struct regmap_config logicvc_drm_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.name = "logicvc-drm",
};
static irqreturn_t logicvc_drm_irq_handler(int irq, void *data)
{
struct logicvc_drm *logicvc = data;
irqreturn_t ret = IRQ_NONE;
u32 stat = 0;
/* Get pending interrupt sources. */
regmap_read(logicvc->regmap, LOGICVC_INT_STAT_REG, &stat);
/* Clear all pending interrupt sources. */
regmap_write(logicvc->regmap, LOGICVC_INT_STAT_REG, stat);
if (stat & LOGICVC_INT_STAT_V_SYNC) {
logicvc_crtc_vblank_handler(logicvc);
ret = IRQ_HANDLED;
}
return ret;
}
static int logicvc_drm_config_parse(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct device_node *of_node = dev->of_node;
struct logicvc_drm_config *config = &logicvc->config;
struct device_node *layers_node;
int ret;
logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_DITHERING,
&config->dithering);
logicvc_of_property_parse_bool(of_node,
LOGICVC_OF_PROPERTY_BACKGROUND_LAYER,
&config->background_layer);
logicvc_of_property_parse_bool(of_node,
LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE,
&config->layers_configurable);
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE,
&config->display_interface);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE,
&config->display_colorspace);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_DISPLAY_DEPTH,
&config->display_depth);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_ROW_STRIDE,
&config->row_stride);
if (ret)
return ret;
layers_node = of_get_child_by_name(of_node, "layers");
if (!layers_node) {
drm_err(drm_dev, "Missing non-optional layers node\n");
return -EINVAL;
}
config->layers_count = of_get_child_count(layers_node);
if (!config->layers_count) {
drm_err(drm_dev,
"Missing a non-optional layers children node\n");
return -EINVAL;
}
return 0;
}
static int logicvc_clocks_prepare(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct {
struct clk **clk;
char *name;
bool optional;
} clocks_map[] = {
{
.clk = &logicvc->vclk,
.name = "vclk",
.optional = false,
},
{
.clk = &logicvc->vclk2,
.name = "vclk2",
.optional = true,
},
{
.clk = &logicvc->lvdsclk,
.name = "lvdsclk",
.optional = true,
},
{
.clk = &logicvc->lvdsclkn,
.name = "lvdsclkn",
.optional = true,
},
};
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(clocks_map); i++) {
struct clk *clk;
clk = devm_clk_get(dev, clocks_map[i].name);
if (IS_ERR(clk)) {
if (PTR_ERR(clk) == -ENOENT && clocks_map[i].optional)
continue;
drm_err(drm_dev, "Missing non-optional clock %s\n",
clocks_map[i].name);
ret = PTR_ERR(clk);
goto error;
}
ret = clk_prepare_enable(clk);
if (ret) {
drm_err(drm_dev,
"Failed to prepare and enable clock %s\n",
clocks_map[i].name);
goto error;
}
*clocks_map[i].clk = clk;
}
return 0;
error:
for (i = 0; i < ARRAY_SIZE(clocks_map); i++) {
if (!*clocks_map[i].clk)
continue;
clk_disable_unprepare(*clocks_map[i].clk);
*clocks_map[i].clk = NULL;
}
return ret;
}
static int logicvc_clocks_unprepare(struct logicvc_drm *logicvc)
{
struct clk **clocks[] = {
&logicvc->vclk,
&logicvc->vclk2,
&logicvc->lvdsclk,
&logicvc->lvdsclkn,
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(clocks); i++) {
if (!*clocks[i])
continue;
clk_disable_unprepare(*clocks[i]);
*clocks[i] = NULL;
}
return 0;
}
static const struct logicvc_drm_caps logicvc_drm_caps[] = {
{
.major = 3,
.layer_address = false,
},
{
.major = 4,
.layer_address = true,
},
{
.major = 5,
.layer_address = true,
},
};
static const struct logicvc_drm_caps *
logicvc_drm_caps_match(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
const struct logicvc_drm_caps *caps = NULL;
unsigned int major, minor;
char level;
unsigned int i;
u32 version;
regmap_read(logicvc->regmap, LOGICVC_IP_VERSION_REG, &version);
major = FIELD_GET(LOGICVC_IP_VERSION_MAJOR_MASK, version);
minor = FIELD_GET(LOGICVC_IP_VERSION_MINOR_MASK, version);
level = FIELD_GET(LOGICVC_IP_VERSION_LEVEL_MASK, version) + 'a';
for (i = 0; i < ARRAY_SIZE(logicvc_drm_caps); i++) {
if (logicvc_drm_caps[i].major &&
logicvc_drm_caps[i].major != major)
continue;
if (logicvc_drm_caps[i].minor &&
logicvc_drm_caps[i].minor != minor)
continue;
if (logicvc_drm_caps[i].level &&
logicvc_drm_caps[i].level != level)
continue;
caps = &logicvc_drm_caps[i];
}
drm_info(drm_dev, "LogiCVC version %d.%02d.%c\n", major, minor, level);
return caps;
}
static int logicvc_drm_probe(struct platform_device *pdev)
{
struct device_node *of_node = pdev->dev.of_node;
struct device_node *reserved_mem_node;
struct reserved_mem *reserved_mem = NULL;
const struct logicvc_drm_caps *caps;
struct logicvc_drm *logicvc;
struct device *dev = &pdev->dev;
struct drm_device *drm_dev;
struct regmap *regmap;
struct resource res;
void __iomem *base;
int irq;
int ret;
ret = of_reserved_mem_device_init(dev);
if (ret && ret != -ENODEV) {
dev_err(dev, "Failed to init memory region\n");
goto error_early;
}
reserved_mem_node = of_parse_phandle(of_node, "memory-region", 0);
if (reserved_mem_node) {
reserved_mem = of_reserved_mem_lookup(reserved_mem_node);
of_node_put(reserved_mem_node);
}
/* Get regmap from parent if available. */
if (of_node->parent)
regmap = syscon_node_to_regmap(of_node->parent);
/* Register our own regmap otherwise. */
if (IS_ERR_OR_NULL(regmap)) {
ret = of_address_to_resource(of_node, 0, &res);
if (ret) {
dev_err(dev, "Failed to get resource from address\n");
goto error_reserved_mem;
}
base = devm_ioremap_resource(dev, &res);
if (IS_ERR(base)) {
dev_err(dev, "Failed to map I/O base\n");
ret = PTR_ERR(base);
goto error_reserved_mem;
}
logicvc_drm_regmap_config.max_register = resource_size(&res) -
4;
regmap = devm_regmap_init_mmio(dev, base,
&logicvc_drm_regmap_config);
if (IS_ERR(regmap)) {
dev_err(dev, "Failed to create regmap for I/O\n");
ret = PTR_ERR(regmap);
goto error_reserved_mem;
}
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "Failed to get IRQ\n");
ret = -ENODEV;
goto error_reserved_mem;
}
logicvc = devm_drm_dev_alloc(dev, &logicvc_drm_driver,
struct logicvc_drm, drm_dev);
if (IS_ERR(logicvc)) {
ret = PTR_ERR(logicvc);
goto error_reserved_mem;
}
platform_set_drvdata(pdev, logicvc);
drm_dev = &logicvc->drm_dev;
logicvc->regmap = regmap;
INIT_LIST_HEAD(&logicvc->layers_list);
caps = logicvc_drm_caps_match(logicvc);
if (!caps) {
ret = -EINVAL;
goto error_reserved_mem;
}
logicvc->caps = caps;
if (reserved_mem)
logicvc->reserved_mem_base = reserved_mem->base;
ret = logicvc_clocks_prepare(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to prepare clocks\n");
goto error_reserved_mem;
}
ret = devm_request_irq(dev, irq, logicvc_drm_irq_handler, 0,
dev_name(dev), logicvc);
if (ret) {
drm_err(drm_dev, "Failed to request IRQ\n");
goto error_clocks;
}
ret = logicvc_drm_config_parse(logicvc);
if (ret && ret != -ENODEV) {
drm_err(drm_dev, "Failed to parse config\n");
goto error_clocks;
}
ret = drmm_mode_config_init(drm_dev);
if (ret) {
drm_err(drm_dev, "Failed to init mode config\n");
goto error_clocks;
}
ret = logicvc_layers_init(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to initialize layers\n");
goto error_clocks;
}
ret = logicvc_crtc_init(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to initialize CRTC\n");
goto error_clocks;
}
logicvc_layers_attach_crtc(logicvc);
ret = logicvc_interface_init(logicvc);
if (ret) {
if (ret != -EPROBE_DEFER)
drm_err(drm_dev, "Failed to initialize interface\n");
goto error_clocks;
}
logicvc_interface_attach_crtc(logicvc);
ret = logicvc_mode_init(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to initialize KMS\n");
goto error_clocks;
}
ret = drm_dev_register(drm_dev, 0);
if (ret) {
drm_err(drm_dev, "Failed to register DRM device\n");
goto error_mode;
}
drm_fbdev_generic_setup(drm_dev, drm_dev->mode_config.preferred_depth);
return 0;
error_mode:
logicvc_mode_fini(logicvc);
error_clocks:
logicvc_clocks_unprepare(logicvc);
error_reserved_mem:
of_reserved_mem_device_release(dev);
error_early:
return ret;
}
static int logicvc_drm_remove(struct platform_device *pdev)
{
struct logicvc_drm *logicvc = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct drm_device *drm_dev = &logicvc->drm_dev;
drm_dev_unregister(drm_dev);
drm_atomic_helper_shutdown(drm_dev);
logicvc_mode_fini(logicvc);
logicvc_clocks_unprepare(logicvc);
of_reserved_mem_device_release(dev);
return 0;
}
static const struct of_device_id logicvc_drm_of_table[] = {
{ .compatible = "xylon,logicvc-3.02.a-display" },
{ .compatible = "xylon,logicvc-4.01.a-display" },
{},
};
MODULE_DEVICE_TABLE(of, logicvc_drm_of_table);
static struct platform_driver logicvc_drm_platform_driver = {
.probe = logicvc_drm_probe,
.remove = logicvc_drm_remove,
.driver = {
.name = "logicvc-drm",
.of_match_table = logicvc_drm_of_table,
},
};
module_platform_driver(logicvc_drm_platform_driver);
MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
MODULE_DESCRIPTION("Xylon LogiCVC DRM driver");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,67 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#ifndef _LOGICVC_DRM_H_
#define _LOGICVC_DRM_H_
#include <linux/regmap.h>
#include <linux/types.h>
#include <drm/drm_device.h>
#define LOGICVC_DISPLAY_INTERFACE_RGB 0
#define LOGICVC_DISPLAY_INTERFACE_ITU656 1
#define LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS 2
#define LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA 3
#define LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS 4
#define LOGICVC_DISPLAY_INTERFACE_DVI 5
#define LOGICVC_DISPLAY_COLORSPACE_RGB 0
#define LOGICVC_DISPLAY_COLORSPACE_YUV422 1
#define LOGICVC_DISPLAY_COLORSPACE_YUV444 2
#define logicvc_drm(d) \
container_of(d, struct logicvc_drm, drm_dev)
struct logicvc_crtc;
struct logicvc_interface;
struct logicvc_drm_config {
u32 display_interface;
u32 display_colorspace;
u32 display_depth;
u32 row_stride;
bool dithering;
bool background_layer;
bool layers_configurable;
u32 layers_count;
};
struct logicvc_drm_caps {
unsigned int major;
unsigned int minor;
char level;
bool layer_address;
};
struct logicvc_drm {
const struct logicvc_drm_caps *caps;
struct logicvc_drm_config config;
struct drm_device drm_dev;
phys_addr_t reserved_mem_base;
struct regmap *regmap;
struct clk *vclk;
struct clk *vclk2;
struct clk *lvdsclk;
struct clk *lvdsclkn;
struct list_head layers_list;
struct logicvc_crtc *crtc;
struct logicvc_interface *interface;
};
#endif

View File

@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#include <linux/types.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_encoder.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "logicvc_crtc.h"
#include "logicvc_drm.h"
#include "logicvc_interface.h"
#include "logicvc_regs.h"
#define logicvc_interface_from_drm_encoder(c) \
container_of(c, struct logicvc_interface, drm_encoder)
#define logicvc_interface_from_drm_connector(c) \
container_of(c, struct logicvc_interface, drm_connector)
static void logicvc_encoder_enable(struct drm_encoder *drm_encoder)
{
struct logicvc_drm *logicvc = logicvc_drm(drm_encoder->dev);
struct logicvc_interface *interface =
logicvc_interface_from_drm_encoder(drm_encoder);
regmap_update_bits(logicvc->regmap, LOGICVC_POWER_CTRL_REG,
LOGICVC_POWER_CTRL_VIDEO_ENABLE,
LOGICVC_POWER_CTRL_VIDEO_ENABLE);
if (interface->drm_panel) {
drm_panel_prepare(interface->drm_panel);
drm_panel_enable(interface->drm_panel);
}
}
static void logicvc_encoder_disable(struct drm_encoder *drm_encoder)
{
struct logicvc_interface *interface =
logicvc_interface_from_drm_encoder(drm_encoder);
if (interface->drm_panel) {
drm_panel_disable(interface->drm_panel);
drm_panel_unprepare(interface->drm_panel);
}
}
static const struct drm_encoder_helper_funcs logicvc_encoder_helper_funcs = {
.enable = logicvc_encoder_enable,
.disable = logicvc_encoder_disable,
};
static const struct drm_encoder_funcs logicvc_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
static int logicvc_connector_get_modes(struct drm_connector *drm_connector)
{
struct logicvc_interface *interface =
logicvc_interface_from_drm_connector(drm_connector);
if (interface->drm_panel)
return drm_panel_get_modes(interface->drm_panel, drm_connector);
WARN_ONCE(1, "Retrieving modes from a native connector is not implemented.");
return 0;
}
static const struct drm_connector_helper_funcs logicvc_connector_helper_funcs = {
.get_modes = logicvc_connector_get_modes,
};
static const struct drm_connector_funcs logicvc_connector_funcs = {
.reset = drm_atomic_helper_connector_reset,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int logicvc_interface_encoder_type(struct logicvc_drm *logicvc)
{
switch (logicvc->config.display_interface) {
case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS:
case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA:
case LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS:
return DRM_MODE_ENCODER_LVDS;
case LOGICVC_DISPLAY_INTERFACE_DVI:
return DRM_MODE_ENCODER_TMDS;
case LOGICVC_DISPLAY_INTERFACE_RGB:
return DRM_MODE_ENCODER_DPI;
default:
return DRM_MODE_ENCODER_NONE;
}
}
static int logicvc_interface_connector_type(struct logicvc_drm *logicvc)
{
switch (logicvc->config.display_interface) {
case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS:
case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA:
case LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS:
return DRM_MODE_CONNECTOR_LVDS;
case LOGICVC_DISPLAY_INTERFACE_DVI:
return DRM_MODE_CONNECTOR_DVID;
case LOGICVC_DISPLAY_INTERFACE_RGB:
return DRM_MODE_CONNECTOR_DPI;
default:
return DRM_MODE_CONNECTOR_Unknown;
}
}
static bool logicvc_interface_native_connector(struct logicvc_drm *logicvc)
{
switch (logicvc->config.display_interface) {
case LOGICVC_DISPLAY_INTERFACE_DVI:
return true;
default:
return false;
}
}
void logicvc_interface_attach_crtc(struct logicvc_drm *logicvc)
{
uint32_t possible_crtcs = drm_crtc_mask(&logicvc->crtc->drm_crtc);
logicvc->interface->drm_encoder.possible_crtcs = possible_crtcs;
}
int logicvc_interface_init(struct logicvc_drm *logicvc)
{
struct logicvc_interface *interface;
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct device_node *of_node = dev->of_node;
int encoder_type = logicvc_interface_encoder_type(logicvc);
int connector_type = logicvc_interface_connector_type(logicvc);
bool native_connector = logicvc_interface_native_connector(logicvc);
int ret;
interface = devm_kzalloc(dev, sizeof(*interface), GFP_KERNEL);
if (!interface) {
ret = -ENOMEM;
goto error_early;
}
ret = drm_of_find_panel_or_bridge(of_node, 0, 0, &interface->drm_panel,
&interface->drm_bridge);
if (ret == -EPROBE_DEFER)
goto error_early;
ret = drm_encoder_init(drm_dev, &interface->drm_encoder,
&logicvc_encoder_funcs, encoder_type, NULL);
if (ret) {
drm_err(drm_dev, "Failed to initialize encoder\n");
goto error_early;
}
drm_encoder_helper_add(&interface->drm_encoder,
&logicvc_encoder_helper_funcs);
if (native_connector || interface->drm_panel) {
ret = drm_connector_init(drm_dev, &interface->drm_connector,
&logicvc_connector_funcs,
connector_type);
if (ret) {
drm_err(drm_dev, "Failed to initialize connector\n");
goto error_encoder;
}
drm_connector_helper_add(&interface->drm_connector,
&logicvc_connector_helper_funcs);
ret = drm_connector_attach_encoder(&interface->drm_connector,
&interface->drm_encoder);
if (ret) {
drm_err(drm_dev,
"Failed to attach connector to encoder\n");
goto error_encoder;
}
}
if (interface->drm_bridge) {
ret = drm_bridge_attach(&interface->drm_encoder,
interface->drm_bridge, NULL, 0);
if (ret) {
drm_err(drm_dev,
"Failed to attach bridge to encoder\n");
goto error_encoder;
}
}
logicvc->interface = interface;
return 0;
error_encoder:
drm_encoder_cleanup(&interface->drm_encoder);
error_early:
return ret;
}

View File

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#ifndef _LOGICVC_INTERFACE_H_
#define _LOGICVC_INTERFACE_H_
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
#include <drm/drm_panel.h>
struct logicvc_drm;
struct logicvc_interface {
struct drm_encoder drm_encoder;
struct drm_connector drm_connector;
struct drm_panel *drm_panel;
struct drm_bridge *drm_bridge;
};
void logicvc_interface_attach_crtc(struct logicvc_drm *logicvc);
int logicvc_interface_init(struct logicvc_drm *logicvc);
#endif

View File

@ -0,0 +1,628 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#include <linux/of.h>
#include <linux/types.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_plane.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_print.h>
#include "logicvc_crtc.h"
#include "logicvc_drm.h"
#include "logicvc_layer.h"
#include "logicvc_of.h"
#include "logicvc_regs.h"
#define logicvc_layer(p) \
container_of(p, struct logicvc_layer, drm_plane)
static uint32_t logicvc_layer_formats_rgb16[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_BGR565,
DRM_FORMAT_INVALID,
};
static uint32_t logicvc_layer_formats_rgb24[] = {
DRM_FORMAT_XRGB8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_INVALID,
};
/*
* What we call depth in this driver only counts color components, not alpha.
* This allows us to stay compatible with the LogiCVC bistream definitions.
*/
static uint32_t logicvc_layer_formats_rgb24_alpha[] = {
DRM_FORMAT_ARGB8888,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_INVALID,
};
static struct logicvc_layer_formats logicvc_layer_formats[] = {
{
.colorspace = LOGICVC_LAYER_COLORSPACE_RGB,
.depth = 16,
.formats = logicvc_layer_formats_rgb16,
},
{
.colorspace = LOGICVC_LAYER_COLORSPACE_RGB,
.depth = 24,
.formats = logicvc_layer_formats_rgb24,
},
{
.colorspace = LOGICVC_LAYER_COLORSPACE_RGB,
.depth = 24,
.alpha = true,
.formats = logicvc_layer_formats_rgb24_alpha,
},
{ }
};
static bool logicvc_layer_format_inverted(uint32_t format)
{
switch (format) {
case DRM_FORMAT_BGR565:
case DRM_FORMAT_BGR888:
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
return true;
default:
return false;
}
}
static int logicvc_plane_atomic_check(struct drm_plane *drm_plane,
struct drm_atomic_state *state)
{
struct drm_device *drm_dev = drm_plane->dev;
struct logicvc_layer *layer = logicvc_layer(drm_plane);
struct logicvc_drm *logicvc = logicvc_drm(drm_dev);
struct drm_plane_state *new_state =
drm_atomic_get_new_plane_state(state, drm_plane);
struct drm_crtc_state *crtc_state;
int min_scale, max_scale;
bool can_position;
int ret;
if (!new_state->crtc)
return 0;
crtc_state = drm_atomic_get_existing_crtc_state(new_state->state,
new_state->crtc);
if (WARN_ON(!crtc_state))
return -EINVAL;
if (new_state->crtc_x < 0 || new_state->crtc_y < 0) {
drm_err(drm_dev,
"Negative on-CRTC positions are not supported.\n");
return -EINVAL;
}
if (!logicvc->caps->layer_address) {
ret = logicvc_layer_buffer_find_setup(logicvc, layer, new_state,
NULL);
if (ret) {
drm_err(drm_dev, "No viable setup for buffer found.\n");
return ret;
}
}
min_scale = DRM_PLANE_HELPER_NO_SCALING;
max_scale = DRM_PLANE_HELPER_NO_SCALING;
can_position = (drm_plane->type == DRM_PLANE_TYPE_OVERLAY &&
layer->index != (logicvc->config.layers_count - 1) &&
logicvc->config.layers_configurable);
ret = drm_atomic_helper_check_plane_state(new_state, crtc_state,
min_scale, max_scale,
can_position, true);
if (ret) {
drm_err(drm_dev, "Invalid plane state\n\n");
return ret;
}
return 0;
}
static void logicvc_plane_atomic_update(struct drm_plane *drm_plane,
struct drm_atomic_state *state)
{
struct logicvc_layer *layer = logicvc_layer(drm_plane);
struct logicvc_drm *logicvc = logicvc_drm(drm_plane->dev);
struct drm_device *drm_dev = &logicvc->drm_dev;
struct drm_plane_state *new_state =
drm_atomic_get_new_plane_state(state, drm_plane);
struct drm_crtc *drm_crtc = &logicvc->crtc->drm_crtc;
struct drm_display_mode *mode = &drm_crtc->state->adjusted_mode;
struct drm_framebuffer *fb = new_state->fb;
struct logicvc_layer_buffer_setup setup = {};
u32 index = layer->index;
u32 reg;
/* Layer dimensions */
regmap_write(logicvc->regmap, LOGICVC_LAYER_WIDTH_REG(index),
new_state->crtc_w - 1);
regmap_write(logicvc->regmap, LOGICVC_LAYER_HEIGHT_REG(index),
new_state->crtc_h - 1);
if (logicvc->caps->layer_address) {
phys_addr_t fb_addr = drm_fb_cma_get_gem_addr(fb, new_state, 0);
regmap_write(logicvc->regmap, LOGICVC_LAYER_ADDRESS_REG(index),
fb_addr);
} else {
/* Rely on offsets to configure the address. */
logicvc_layer_buffer_find_setup(logicvc, layer, new_state,
&setup);
/* Layer memory offsets */
regmap_write(logicvc->regmap, LOGICVC_BUFFER_SEL_REG,
LOGICVC_BUFFER_SEL_VALUE(index, setup.buffer_sel));
regmap_write(logicvc->regmap, LOGICVC_LAYER_HOFFSET_REG(index),
setup.hoffset);
regmap_write(logicvc->regmap, LOGICVC_LAYER_VOFFSET_REG(index),
setup.voffset);
}
/* Layer position */
regmap_write(logicvc->regmap, LOGICVC_LAYER_HPOSITION_REG(index),
mode->hdisplay - 1 - new_state->crtc_x);
/* Vertical position must be set last to sync layer register changes. */
regmap_write(logicvc->regmap, LOGICVC_LAYER_VPOSITION_REG(index),
mode->vdisplay - 1 - new_state->crtc_y);
/* Layer alpha */
if (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_LAYER) {
u32 alpha_bits;
u32 alpha_max;
u32 alpha;
switch (layer->config.depth) {
case 8:
alpha_bits = 3;
break;
case 16:
if (layer->config.colorspace ==
LOGICVC_LAYER_COLORSPACE_YUV)
alpha_bits = 8;
else
alpha_bits = 6;
break;
default:
alpha_bits = 8;
break;
}
alpha_max = BIT(alpha_bits) - 1;
alpha = new_state->alpha * alpha_max / DRM_BLEND_ALPHA_OPAQUE;
drm_dbg_kms(drm_dev, "Setting layer %d alpha to %d/%d\n", index,
alpha, alpha_max);
regmap_write(logicvc->regmap, LOGICVC_LAYER_ALPHA_REG(index),
alpha);
}
/* Layer control */
reg = LOGICVC_LAYER_CTRL_ENABLE;
if (logicvc_layer_format_inverted(fb->format->format))
reg |= LOGICVC_LAYER_CTRL_PIXEL_FORMAT_INVERT;
reg |= LOGICVC_LAYER_CTRL_COLOR_KEY_DISABLE;
regmap_write(logicvc->regmap, LOGICVC_LAYER_CTRL_REG(index), reg);
}
static void logicvc_plane_atomic_disable(struct drm_plane *drm_plane,
struct drm_atomic_state *state)
{
struct logicvc_layer *layer = logicvc_layer(drm_plane);
struct logicvc_drm *logicvc = logicvc_drm(drm_plane->dev);
u32 index = layer->index;
regmap_write(logicvc->regmap, LOGICVC_LAYER_CTRL_REG(index), 0);
}
static struct drm_plane_helper_funcs logicvc_plane_helper_funcs = {
.atomic_check = logicvc_plane_atomic_check,
.atomic_update = logicvc_plane_atomic_update,
.atomic_disable = logicvc_plane_atomic_disable,
};
static const struct drm_plane_funcs logicvc_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
int logicvc_layer_buffer_find_setup(struct logicvc_drm *logicvc,
struct logicvc_layer *layer,
struct drm_plane_state *state,
struct logicvc_layer_buffer_setup *setup)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct drm_framebuffer *fb = state->fb;
/* All the supported formats have a single data plane. */
u32 layer_bytespp = fb->format->cpp[0];
u32 layer_stride = layer_bytespp * logicvc->config.row_stride;
u32 base_offset = layer->config.base_offset * layer_stride;
u32 buffer_offset = layer->config.buffer_offset * layer_stride;
u8 buffer_sel = 0;
u16 voffset = 0;
u16 hoffset = 0;
phys_addr_t fb_addr;
u32 fb_offset;
u32 gap;
if (!logicvc->reserved_mem_base) {
drm_err(drm_dev, "No reserved memory base was registered!\n");
return -ENOMEM;
}
fb_addr = drm_fb_cma_get_gem_addr(fb, state, 0);
if (fb_addr < logicvc->reserved_mem_base) {
drm_err(drm_dev,
"Framebuffer memory below reserved memory base!\n");
return -EINVAL;
}
fb_offset = (u32) (fb_addr - logicvc->reserved_mem_base);
if (fb_offset < base_offset) {
drm_err(drm_dev,
"Framebuffer offset below layer base offset!\n");
return -EINVAL;
}
gap = fb_offset - base_offset;
/* Use the possible video buffers selection. */
if (gap && buffer_offset) {
buffer_sel = gap / buffer_offset;
if (buffer_sel > LOGICVC_BUFFER_SEL_MAX)
buffer_sel = LOGICVC_BUFFER_SEL_MAX;
gap -= buffer_sel * buffer_offset;
}
/* Use the vertical offset. */
if (gap && layer_stride && logicvc->config.layers_configurable) {
voffset = gap / layer_stride;
if (voffset > LOGICVC_LAYER_VOFFSET_MAX)
voffset = LOGICVC_LAYER_VOFFSET_MAX;
gap -= voffset * layer_stride;
}
/* Use the horizontal offset. */
if (gap && layer_bytespp && logicvc->config.layers_configurable) {
hoffset = gap / layer_bytespp;
if (hoffset > LOGICVC_DIMENSIONS_MAX)
hoffset = LOGICVC_DIMENSIONS_MAX;
gap -= hoffset * layer_bytespp;
}
if (gap) {
drm_err(drm_dev,
"Unable to find layer %d buffer setup for 0x%x byte gap\n",
layer->index, fb_offset - base_offset);
return -EINVAL;
}
drm_dbg_kms(drm_dev, "Found layer %d buffer setup for 0x%x byte gap:\n",
layer->index, fb_offset - base_offset);
drm_dbg_kms(drm_dev, "- buffer_sel = 0x%x chunks of 0x%x bytes\n",
buffer_sel, buffer_offset);
drm_dbg_kms(drm_dev, "- voffset = 0x%x chunks of 0x%x bytes\n", voffset,
layer_stride);
drm_dbg_kms(drm_dev, "- hoffset = 0x%x chunks of 0x%x bytes\n", hoffset,
layer_bytespp);
if (setup) {
setup->buffer_sel = buffer_sel;
setup->voffset = voffset;
setup->hoffset = hoffset;
}
return 0;
}
static struct logicvc_layer_formats *logicvc_layer_formats_lookup(struct logicvc_layer *layer)
{
bool alpha;
unsigned int i = 0;
alpha = (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_PIXEL);
while (logicvc_layer_formats[i].formats) {
if (logicvc_layer_formats[i].colorspace == layer->config.colorspace &&
logicvc_layer_formats[i].depth == layer->config.depth &&
logicvc_layer_formats[i].alpha == alpha)
return &logicvc_layer_formats[i];
i++;
}
return NULL;
}
static unsigned int logicvc_layer_formats_count(struct logicvc_layer_formats *formats)
{
unsigned int count = 0;
while (formats->formats[count] != DRM_FORMAT_INVALID)
count++;
return count;
}
static int logicvc_layer_config_parse(struct logicvc_drm *logicvc,
struct logicvc_layer *layer)
{
struct device_node *of_node = layer->of_node;
struct logicvc_layer_config *config = &layer->config;
int ret;
logicvc_of_property_parse_bool(of_node,
LOGICVC_OF_PROPERTY_LAYER_PRIMARY,
&config->primary);
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_LAYER_COLORSPACE,
&config->colorspace);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_LAYER_DEPTH,
&config->depth);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE,
&config->alpha_mode);
if (ret)
return ret;
/*
* Memory offset is only relevant without layer address configuration.
*/
if (logicvc->caps->layer_address)
return 0;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET,
&config->base_offset);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET,
&config->buffer_offset);
if (ret)
return ret;
return 0;
}
struct logicvc_layer *logicvc_layer_get_from_index(struct logicvc_drm *logicvc,
u32 index)
{
struct logicvc_layer *layer;
list_for_each_entry(layer, &logicvc->layers_list, list)
if (layer->index == index)
return layer;
return NULL;
}
struct logicvc_layer *logicvc_layer_get_from_type(struct logicvc_drm *logicvc,
enum drm_plane_type type)
{
struct logicvc_layer *layer;
list_for_each_entry(layer, &logicvc->layers_list, list)
if (layer->drm_plane.type == type)
return layer;
return NULL;
}
struct logicvc_layer *logicvc_layer_get_primary(struct logicvc_drm *logicvc)
{
return logicvc_layer_get_from_type(logicvc, DRM_PLANE_TYPE_PRIMARY);
}
static int logicvc_layer_init(struct logicvc_drm *logicvc,
struct device_node *of_node, u32 index)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct logicvc_layer *layer = NULL;
struct logicvc_layer_formats *formats;
unsigned int formats_count;
enum drm_plane_type type;
unsigned int zpos;
int ret;
layer = devm_kzalloc(dev, sizeof(*layer), GFP_KERNEL);
if (!layer) {
ret = -ENOMEM;
goto error;
}
layer->of_node = of_node;
layer->index = index;
ret = logicvc_layer_config_parse(logicvc, layer);
if (ret) {
drm_err(drm_dev, "Failed to parse config for layer #%d\n",
index);
goto error;
}
formats = logicvc_layer_formats_lookup(layer);
if (!formats) {
drm_err(drm_dev, "Failed to lookup formats for layer #%d\n",
index);
goto error;
}
formats_count = logicvc_layer_formats_count(formats);
/* The final layer can be configured as a background layer. */
if (logicvc->config.background_layer &&
index == (logicvc->config.layers_count - 1)) {
/*
* A zero value for black is only valid for RGB, not for YUV,
* so this will need to take the format in account for YUV.
*/
u32 background = 0;
drm_dbg_kms(drm_dev, "Using layer #%d as background layer\n",
index);
regmap_write(logicvc->regmap, LOGICVC_BACKGROUND_COLOR_REG,
background);
devm_kfree(dev, layer);
return 0;
}
if (layer->config.primary)
type = DRM_PLANE_TYPE_PRIMARY;
else
type = DRM_PLANE_TYPE_OVERLAY;
ret = drm_universal_plane_init(drm_dev, &layer->drm_plane, 0,
&logicvc_plane_funcs, formats->formats,
formats_count, NULL, type, NULL);
if (ret) {
drm_err(drm_dev, "Failed to initialize layer plane\n");
return ret;
}
drm_plane_helper_add(&layer->drm_plane, &logicvc_plane_helper_funcs);
zpos = logicvc->config.layers_count - index - 1;
drm_dbg_kms(drm_dev, "Giving layer #%d zpos %d\n", index, zpos);
if (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_LAYER)
drm_plane_create_alpha_property(&layer->drm_plane);
drm_plane_create_zpos_immutable_property(&layer->drm_plane, zpos);
drm_dbg_kms(drm_dev, "Registering layer #%d\n", index);
layer->formats = formats;
list_add_tail(&layer->list, &logicvc->layers_list);
return 0;
error:
if (layer)
devm_kfree(dev, layer);
return ret;
}
static void logicvc_layer_fini(struct logicvc_drm *logicvc,
struct logicvc_layer *layer)
{
struct device *dev = logicvc->drm_dev.dev;
list_del(&layer->list);
devm_kfree(dev, layer);
}
void logicvc_layers_attach_crtc(struct logicvc_drm *logicvc)
{
uint32_t possible_crtcs = drm_crtc_mask(&logicvc->crtc->drm_crtc);
struct logicvc_layer *layer;
list_for_each_entry(layer, &logicvc->layers_list, list) {
if (layer->drm_plane.type != DRM_PLANE_TYPE_OVERLAY)
continue;
layer->drm_plane.possible_crtcs = possible_crtcs;
}
}
int logicvc_layers_init(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct device_node *of_node = dev->of_node;
struct device_node *layer_node = NULL;
struct device_node *layers_node;
struct logicvc_layer *layer;
struct logicvc_layer *next;
int ret = 0;
layers_node = of_get_child_by_name(of_node, "layers");
if (!layers_node) {
drm_err(drm_dev, "No layers node found in the description\n");
ret = -ENODEV;
goto error;
}
for_each_child_of_node(layers_node, layer_node) {
u32 index = 0;
if (!logicvc_of_node_is_layer(layer_node))
continue;
ret = of_property_read_u32(layer_node, "reg", &index);
if (ret)
continue;
layer = logicvc_layer_get_from_index(logicvc, index);
if (layer) {
drm_err(drm_dev, "Duplicated entry for layer #%d\n",
index);
continue;
}
ret = logicvc_layer_init(logicvc, layer_node, index);
if (ret)
goto error;
of_node_put(layer_node);
}
of_node_put(layers_node);
return 0;
error:
list_for_each_entry_safe(layer, next, &logicvc->layers_list, list)
logicvc_layer_fini(logicvc, layer);
return ret;
}

View File

@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#ifndef _LOGICVC_LAYER_H_
#define _LOGICVC_LAYER_H_
#include <linux/of.h>
#include <linux/types.h>
#include <drm/drm_plane.h>
#define LOGICVC_LAYER_COLORSPACE_RGB 0
#define LOGICVC_LAYER_COLORSPACE_YUV 1
#define LOGICVC_LAYER_ALPHA_LAYER 0
#define LOGICVC_LAYER_ALPHA_PIXEL 1
struct logicvc_layer_buffer_setup {
u8 buffer_sel;
u16 voffset;
u16 hoffset;
};
struct logicvc_layer_config {
u32 colorspace;
u32 depth;
u32 alpha_mode;
u32 base_offset;
u32 buffer_offset;
bool primary;
};
struct logicvc_layer_formats {
u32 colorspace;
u32 depth;
bool alpha;
uint32_t *formats;
};
struct logicvc_layer {
struct logicvc_layer_config config;
struct logicvc_layer_formats *formats;
struct device_node *of_node;
struct drm_plane drm_plane;
struct list_head list;
u32 index;
};
int logicvc_layer_buffer_find_setup(struct logicvc_drm *logicvc,
struct logicvc_layer *layer,
struct drm_plane_state *state,
struct logicvc_layer_buffer_setup *setup);
struct logicvc_layer *logicvc_layer_get_from_index(struct logicvc_drm *logicvc,
u32 index);
struct logicvc_layer *logicvc_layer_get_from_type(struct logicvc_drm *logicvc,
enum drm_plane_type type);
struct logicvc_layer *logicvc_layer_get_primary(struct logicvc_drm *logicvc);
void logicvc_layers_attach_crtc(struct logicvc_drm *logicvc);
int logicvc_layers_init(struct logicvc_drm *logicvc);
#endif

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#include <linux/types.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_mode_config.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "logicvc_drm.h"
#include "logicvc_interface.h"
#include "logicvc_layer.h"
#include "logicvc_mode.h"
static const struct drm_mode_config_funcs logicvc_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.output_poll_changed = drm_fb_helper_output_poll_changed,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
int logicvc_mode_init(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct drm_mode_config *mode_config = &drm_dev->mode_config;
struct logicvc_layer *layer_primary;
uint32_t preferred_depth;
int ret;
ret = drm_vblank_init(drm_dev, mode_config->num_crtc);
if (ret) {
drm_err(drm_dev, "Failed to initialize vblank\n");
return ret;
}
layer_primary = logicvc_layer_get_primary(logicvc);
if (!layer_primary) {
drm_err(drm_dev, "Failed to get primary layer\n");
return -EINVAL;
}
preferred_depth = layer_primary->formats->depth;
/* DRM counts alpha in depth, our driver doesn't. */
if (layer_primary->formats->alpha)
preferred_depth += 8;
mode_config->min_width = 64;
mode_config->max_width = 2048;
mode_config->min_height = 1;
mode_config->max_height = 2048;
mode_config->preferred_depth = preferred_depth;
mode_config->funcs = &logicvc_mode_config_funcs;
drm_mode_config_reset(drm_dev);
drm_kms_helper_poll_init(drm_dev);
return 0;
}
void logicvc_mode_fini(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
drm_kms_helper_poll_fini(drm_dev);
}

View File

@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#ifndef _LOGICVC_MODE_H_
#define _LOGICVC_MODE_H_
struct logicvc_drm;
int logicvc_mode_init(struct logicvc_drm *logicvc);
void logicvc_mode_fini(struct logicvc_drm *logicvc);
#endif

View File

@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#include <drm/drm_print.h>
#include "logicvc_drm.h"
#include "logicvc_layer.h"
#include "logicvc_of.h"
static struct logicvc_of_property_sv logicvc_of_display_interface_sv[] = {
{ "lvds-4bits", LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS },
{ "lvds-3bits", LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS },
{ },
};
static struct logicvc_of_property_sv logicvc_of_display_colorspace_sv[] = {
{ "rgb", LOGICVC_DISPLAY_COLORSPACE_RGB },
{ "yuv422", LOGICVC_DISPLAY_COLORSPACE_YUV422 },
{ "yuv444", LOGICVC_DISPLAY_COLORSPACE_YUV444 },
{ },
};
static struct logicvc_of_property_sv logicvc_of_layer_colorspace_sv[] = {
{ "rgb", LOGICVC_LAYER_COLORSPACE_RGB },
{ "yuv", LOGICVC_LAYER_COLORSPACE_YUV },
{ },
};
static struct logicvc_of_property_sv logicvc_of_layer_alpha_mode_sv[] = {
{ "layer", LOGICVC_LAYER_ALPHA_LAYER },
{ "pixel", LOGICVC_LAYER_ALPHA_PIXEL },
{ },
};
static struct logicvc_of_property logicvc_of_properties[] = {
[LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE] = {
.name = "xylon,display-interface",
.sv = logicvc_of_display_interface_sv,
.range = {
LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS,
LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS,
},
},
[LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE] = {
.name = "xylon,display-colorspace",
.sv = logicvc_of_display_colorspace_sv,
.range = {
LOGICVC_DISPLAY_COLORSPACE_RGB,
LOGICVC_DISPLAY_COLORSPACE_YUV444,
},
},
[LOGICVC_OF_PROPERTY_DISPLAY_DEPTH] = {
.name = "xylon,display-depth",
.range = { 8, 24 },
},
[LOGICVC_OF_PROPERTY_ROW_STRIDE] = {
.name = "xylon,row-stride",
},
[LOGICVC_OF_PROPERTY_DITHERING] = {
.name = "xylon,dithering",
.optional = true,
},
[LOGICVC_OF_PROPERTY_BACKGROUND_LAYER] = {
.name = "xylon,background-layer",
.optional = true,
},
[LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE] = {
.name = "xylon,layers-configurable",
.optional = true,
},
[LOGICVC_OF_PROPERTY_LAYERS_COUNT] = {
.name = "xylon,layers-count",
},
[LOGICVC_OF_PROPERTY_LAYER_DEPTH] = {
.name = "xylon,layer-depth",
.range = { 8, 24 },
},
[LOGICVC_OF_PROPERTY_LAYER_COLORSPACE] = {
.name = "xylon,layer-colorspace",
.sv = logicvc_of_layer_colorspace_sv,
.range = {
LOGICVC_LAYER_COLORSPACE_RGB,
LOGICVC_LAYER_COLORSPACE_RGB,
},
},
[LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE] = {
.name = "xylon,layer-alpha-mode",
.sv = logicvc_of_layer_alpha_mode_sv,
.range = {
LOGICVC_LAYER_ALPHA_LAYER,
LOGICVC_LAYER_ALPHA_PIXEL,
},
},
[LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET] = {
.name = "xylon,layer-base-offset",
},
[LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET] = {
.name = "xylon,layer-buffer-offset",
},
[LOGICVC_OF_PROPERTY_LAYER_PRIMARY] = {
.name = "xylon,layer-primary",
.optional = true,
},
};
static int logicvc_of_property_sv_value(struct logicvc_of_property_sv *sv,
const char *string, u32 *value)
{
unsigned int i = 0;
while (sv[i].string) {
if (!strcmp(sv[i].string, string)) {
*value = sv[i].value;
return 0;
}
i++;
}
return -EINVAL;
}
int logicvc_of_property_parse_u32(struct device_node *of_node,
unsigned int index, u32 *target)
{
struct logicvc_of_property *property;
const char *string;
u32 value;
int ret;
if (index >= LOGICVC_OF_PROPERTY_MAXIMUM)
return -EINVAL;
property = &logicvc_of_properties[index];
if (!property->optional &&
!of_property_read_bool(of_node, property->name))
return -ENODEV;
if (property->sv) {
ret = of_property_read_string(of_node, property->name, &string);
if (ret)
return ret;
ret = logicvc_of_property_sv_value(property->sv, string,
&value);
if (ret)
return ret;
} else {
ret = of_property_read_u32(of_node, property->name, &value);
if (ret)
return ret;
}
if (property->range[0] || property->range[1])
if (value < property->range[0] || value > property->range[1])
return -ERANGE;
*target = value;
return 0;
}
void logicvc_of_property_parse_bool(struct device_node *of_node,
unsigned int index, bool *target)
{
struct logicvc_of_property *property;
if (index >= LOGICVC_OF_PROPERTY_MAXIMUM) {
/* Fallback. */
*target = false;
return;
}
property = &logicvc_of_properties[index];
*target = of_property_read_bool(of_node, property->name);
}
bool logicvc_of_node_is_layer(struct device_node *of_node)
{
return !of_node_cmp(of_node->name, "layer");
}

View File

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#ifndef _LOGICVC_OF_H_
#define _LOGICVC_OF_H_
enum logicvc_of_property_index {
LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE = 0,
LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE,
LOGICVC_OF_PROPERTY_DISPLAY_DEPTH,
LOGICVC_OF_PROPERTY_ROW_STRIDE,
LOGICVC_OF_PROPERTY_DITHERING,
LOGICVC_OF_PROPERTY_BACKGROUND_LAYER,
LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE,
LOGICVC_OF_PROPERTY_LAYERS_COUNT,
LOGICVC_OF_PROPERTY_LAYER_DEPTH,
LOGICVC_OF_PROPERTY_LAYER_COLORSPACE,
LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE,
LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET,
LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET,
LOGICVC_OF_PROPERTY_LAYER_PRIMARY,
LOGICVC_OF_PROPERTY_MAXIMUM,
};
struct logicvc_of_property_sv {
const char *string;
u32 value;
};
struct logicvc_of_property {
char *name;
bool optional;
struct logicvc_of_property_sv *sv;
u32 range[2];
};
int logicvc_of_property_parse_u32(struct device_node *of_node,
unsigned int index, u32 *target);
void logicvc_of_property_parse_bool(struct device_node *of_node,
unsigned int index, bool *target);
bool logicvc_of_node_is_layer(struct device_node *of_node);
#endif

View File

@ -0,0 +1,80 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*
* Copyright (C) 2014 Xylon d.o.o.
* Author: Davor Joja <davor.joja@logicbricks.com>
*/
#ifndef _LOGICVC_REGS_H_
#define _LOGICVC_REGS_H_
#define LOGICVC_DIMENSIONS_MAX (BIT(16) - 1)
#define LOGICVC_HSYNC_FRONT_PORCH_REG 0x00
#define LOGICVC_HSYNC_REG 0x08
#define LOGICVC_HSYNC_BACK_PORCH_REG 0x10
#define LOGICVC_HRES_REG 0x18
#define LOGICVC_VSYNC_FRONT_PORCH_REG 0x20
#define LOGICVC_VSYNC_REG 0x28
#define LOGICVC_VSYNC_BACK_PORCH_REG 0x30
#define LOGICVC_VRES_REG 0x38
#define LOGICVC_CTRL_REG 0x40
#define LOGICVC_CTRL_CLOCK_INVERT BIT(8)
#define LOGICVC_CTRL_PIXEL_INVERT BIT(7)
#define LOGICVC_CTRL_DE_INVERT BIT(5)
#define LOGICVC_CTRL_DE_ENABLE BIT(4)
#define LOGICVC_CTRL_VSYNC_INVERT BIT(3)
#define LOGICVC_CTRL_VSYNC_ENABLE BIT(2)
#define LOGICVC_CTRL_HSYNC_INVERT BIT(1)
#define LOGICVC_CTRL_HSYNC_ENABLE BIT(0)
#define LOGICVC_DTYPE_REG 0x48
#define LOGICVC_BACKGROUND_COLOR_REG 0x50
#define LOGICVC_BUFFER_SEL_REG 0x58
#define LOGICVC_BUFFER_SEL_VALUE(i, v) \
(BIT(10 + (i)) | ((v) << (2 * (i))))
#define LOGICVC_BUFFER_SEL_MAX 2
#define LOGICVC_DOUBLE_CLUT_REG 0x60
#define LOGICVC_INT_STAT_REG 0x68
#define LOGICVC_INT_STAT_V_SYNC BIT(5)
#define LOGICVC_INT_MASK_REG 0x70
#define LOGICVC_INT_MASK_V_SYNC BIT(5)
#define LOGICVC_POWER_CTRL_REG 0x78
#define LOGICVC_POWER_CTRL_BACKLIGHT_ENABLE BIT(0)
#define LOGICVC_POWER_CTRL_VDD_ENABLE BIT(1)
#define LOGICVC_POWER_CTRL_VEE_ENABLE BIT(2)
#define LOGICVC_POWER_CTRL_VIDEO_ENABLE BIT(3)
#define LOGICVC_IP_VERSION_REG 0xf8
#define LOGICVC_IP_VERSION_MAJOR_MASK GENMASK(16, 11)
#define LOGICVC_IP_VERSION_MINOR_MASK GENMASK(10, 5)
#define LOGICVC_IP_VERSION_LEVEL_MASK GENMASK(4, 0)
#define LOGICVC_LAYER_ADDRESS_REG(i) (0x100 + (i) * 0x80)
#define LOGICVC_LAYER_HOFFSET_REG(i) (0x100 + (i) * 0x80)
#define LOGICVC_LAYER_VOFFSET_REG(i) (0x108 + (i) * 0x80)
#define LOGICVC_LAYER_VOFFSET_MAX 4095
#define LOGICVC_LAYER_HPOSITION_REG(i) (0x110 + (i) * 0x80)
#define LOGICVC_LAYER_VPOSITION_REG(i) (0x118 + (i) * 0x80)
#define LOGICVC_LAYER_WIDTH_REG(i) (0x120 + (i) * 0x80)
#define LOGICVC_LAYER_HEIGHT_REG(i) (0x128 + (i) * 0x80)
#define LOGICVC_LAYER_ALPHA_REG(i) (0x130 + (i) * 0x80)
#define LOGICVC_LAYER_CTRL_REG(i) (0x138 + (i) * 0x80)
#define LOGICVC_LAYER_CTRL_ENABLE BIT(0)
#define LOGICVC_LAYER_CTRL_COLOR_KEY_DISABLE BIT(1)
#define LOGICVC_LAYER_CTRL_PIXEL_FORMAT_INVERT BIT(4)
#define LOGICVC_LAYER_COLOR_KEY_REG(i) (0x140 + (i) * 0x80)
#endif

View File

@ -8,7 +8,6 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include <drm/drm_aperture.h>
#include <drm/drm_drv.h>

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include <drm/drm_drv.h>

View File

@ -102,14 +102,12 @@ static int dp_parser_ctrl_res(struct dp_parser *parser)
static int dp_parser_misc(struct dp_parser *parser)
{
struct device_node *of_node = parser->pdev->dev.of_node;
int len = 0;
const char *data_lane_property = "data-lanes";
int len;
len = of_property_count_elems_of_size(of_node,
data_lane_property, sizeof(u32));
len = drm_of_get_data_lanes_count(of_node, 1, DP_MAX_NUM_DP_LANES);
if (len < 0) {
DRM_WARN("Invalid property %s, default max DP lanes = %d\n",
data_lane_property, DP_MAX_NUM_DP_LANES);
DRM_WARN("Invalid property \"data-lanes\", default max DP lanes = %d\n",
DP_MAX_NUM_DP_LANES);
len = DP_MAX_NUM_DP_LANES;
}

View File

@ -21,6 +21,8 @@
#include <video/mipi_display.h>
#include <drm/drm_of.h>
#include "dsi.h"
#include "dsi.xml.h"
#include "sfpb.xml.h"
@ -1779,11 +1781,10 @@ static int dsi_host_parse_lane_data(struct msm_dsi_host *msm_host,
return 0;
}
num_lanes = len / sizeof(u32);
if (num_lanes < 1 || num_lanes > 4) {
num_lanes = drm_of_get_data_lanes_count(ep, 1, 4);
if (num_lanes < 0) {
DRM_DEV_ERROR(dev, "bad number of data lanes\n");
return -EINVAL;
return num_lanes;
}
msm_host->num_data_lanes = num_lanes;

View File

@ -190,7 +190,7 @@
#define DRF_MD_(X,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,IMPL,...) IMPL
#define DRF_MD(A...) DRF_MD_(X, ##A, DRF_MD_I, DRF_MD_N)(X, ##A)
/* Helper for testing against field value in aribtrary object */
/* Helper for testing against field value in arbitrary object */
#define DRF_TV_N(X,e,p,o,d,r, f,cmp,v) \
NVVAL_TEST_X(DRF_RD_X(e, (p), (o), d##_##r ), d##_##r##_##f, cmp, (v))
#define DRF_TV_I(X,e,p,o,d,r,i,f,cmp,v) \
@ -198,7 +198,7 @@
#define DRF_TV_(X,_1,_2,_3,_4,_5,_6,_7,_8,_9,IMPL,...) IMPL
#define DRF_TV(A...) DRF_TV_(X, ##A, DRF_TV_I, DRF_TV_N)(X, ##A)
/* Helper for testing against field definition in aribtrary object */
/* Helper for testing against field definition in arbitrary object */
#define DRF_TD_N(X,e,p,o,d,r, f,cmp,v) \
NVVAL_TEST_X(DRF_RD_X(e, (p), (o), d##_##r ), d##_##r##_##f, cmp, d##_##r##_##f##_##v)
#define DRF_TD_I(X,e,p,o,d,r,i,f,cmp,v) \

View File

@ -19,7 +19,7 @@ struct nvbios_source {
int nvbios_extend(struct nvkm_bios *, u32 length);
int nvbios_shadow(struct nvkm_bios *);
extern const struct nvbios_source nvbios_rom;
extern const struct nvbios_source nvbios_prom;
extern const struct nvbios_source nvbios_ramin;
extern const struct nvbios_source nvbios_acpi_fast;
extern const struct nvbios_source nvbios_acpi_slow;

View File

@ -171,7 +171,7 @@ nvbios_shadow(struct nvkm_bios *bios)
struct shadow mthds[] = {
{ 0, &nvbios_of },
{ 0, &nvbios_ramin },
{ 0, &nvbios_rom },
{ 0, &nvbios_prom },
{ 0, &nvbios_acpi_fast },
{ 4, &nvbios_acpi_slow },
{ 1, &nvbios_pcirom },

View File

@ -25,7 +25,7 @@
#include <subdev/pci.h>
static u32
prom_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
nvbios_prom_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
{
struct nvkm_device *device = data;
u32 i;
@ -38,14 +38,14 @@ prom_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
}
static void
prom_fini(void *data)
nvbios_prom_fini(void *data)
{
struct nvkm_device *device = data;
nvkm_pci_rom_shadow(device->pci, true);
}
static void *
prom_init(struct nvkm_bios *bios, const char *name)
nvbios_prom_init(struct nvkm_bios *bios, const char *name)
{
struct nvkm_device *device = bios->subdev.device;
if (device->card_type == NV_40 && device->chipset >= 0x4c)
@ -55,10 +55,10 @@ prom_init(struct nvkm_bios *bios, const char *name)
}
const struct nvbios_source
nvbios_rom = {
nvbios_prom = {
.name = "PROM",
.init = prom_init,
.fini = prom_fini,
.read = prom_read,
.init = nvbios_prom_init,
.fini = nvbios_prom_fini,
.read = nvbios_prom_read,
.rw = false,
};

View File

@ -628,6 +628,10 @@ static int nt36672a_panel_add(struct nt36672a_panel *pinfo)
drm_panel_init(&pinfo->base, dev, &panel_funcs, DRM_MODE_CONNECTOR_DSI);
ret = drm_panel_of_backlight(&pinfo->base);
if (ret)
return dev_err_probe(dev, ret, "Failed to get backlight\n");
drm_panel_add(&pinfo->base);
return 0;

View File

@ -33,8 +33,6 @@
#include "qxl_drv.h"
#include "qxl_object.h"
int qxl_log_level;
static bool qxl_check_device(struct qxl_device *qdev)
{
struct qxl_rom *rom = qdev->rom;

View File

@ -679,23 +679,12 @@ static const struct mipi_dsi_host_ops rcar_mipi_dsi_host_ops = {
static int rcar_mipi_dsi_parse_dt(struct rcar_mipi_dsi *dsi)
{
struct device_node *ep;
u32 data_lanes[4];
int ret;
ep = of_graph_get_endpoint_by_regs(dsi->dev->of_node, 1, 0);
if (!ep) {
dev_dbg(dsi->dev, "unconnected port@1\n");
return -ENODEV;
}
ret = of_property_read_variable_u32_array(ep, "data-lanes", data_lanes,
1, 4);
of_node_put(ep);
ret = drm_of_get_data_lanes_count_ep(dsi->dev->of_node, 1, 0, 1, 4);
if (ret < 0) {
dev_err(dsi->dev, "missing or invalid data-lanes property\n");
return -ENODEV;
return ret;
}
dsi->num_data_lanes = ret;

View File

@ -151,16 +151,11 @@ struct sun8i_hdmi_phy;
struct sun8i_hdmi_phy_variant {
bool has_phy_clk;
bool has_second_pll;
unsigned int is_custom_phy : 1;
const struct dw_hdmi_curr_ctrl *cur_ctr;
const struct dw_hdmi_mpll_config *mpll_cfg;
const struct dw_hdmi_phy_config *phy_cfg;
const struct dw_hdmi_phy_ops *phy_ops;
void (*phy_init)(struct sun8i_hdmi_phy *phy);
void (*phy_disable)(struct dw_hdmi *hdmi,
struct sun8i_hdmi_phy *phy);
int (*phy_config)(struct dw_hdmi *hdmi,
struct sun8i_hdmi_phy *phy,
unsigned int clk_rate);
};
struct sun8i_hdmi_phy {
@ -173,7 +168,7 @@ struct sun8i_hdmi_phy {
unsigned int rcal;
struct regmap *regs;
struct reset_control *rst_phy;
struct sun8i_hdmi_phy_variant *variant;
const struct sun8i_hdmi_phy_variant *variant;
};
struct sun8i_dw_hdmi_quirks {

View File

@ -123,10 +123,30 @@ static const struct dw_hdmi_phy_config sun50i_h6_phy_config[] = {
{ ~0UL, 0x0000, 0x0000, 0x0000}
};
static int sun8i_hdmi_phy_config_a83t(struct dw_hdmi *hdmi,
struct sun8i_hdmi_phy *phy,
unsigned int clk_rate)
static void sun8i_hdmi_phy_set_polarity(struct sun8i_hdmi_phy *phy,
const struct drm_display_mode *mode)
{
u32 val = 0;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC;
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val);
};
static int sun8i_a83t_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *display,
const struct drm_display_mode *mode)
{
unsigned int clk_rate = mode->crtc_clock * 1000;
struct sun8i_hdmi_phy *phy = data;
sun8i_hdmi_phy_set_polarity(phy, mode);
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN,
SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN);
@ -185,10 +205,31 @@ static int sun8i_hdmi_phy_config_a83t(struct dw_hdmi *hdmi,
return 0;
}
static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
struct sun8i_hdmi_phy *phy,
unsigned int clk_rate)
static void sun8i_a83t_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
{
struct sun8i_hdmi_phy *phy = data;
dw_hdmi_phy_gen2_txpwron(hdmi, 0);
dw_hdmi_phy_gen2_pddq(hdmi, 1);
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0);
}
static const struct dw_hdmi_phy_ops sun8i_a83t_hdmi_phy_ops = {
.init = sun8i_a83t_hdmi_phy_config,
.disable = sun8i_a83t_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
.update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
static int sun8i_h3_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *display,
const struct drm_display_mode *mode)
{
unsigned int clk_rate = mode->crtc_clock * 1000;
struct sun8i_hdmi_phy *phy = data;
u32 pll_cfg1_init;
u32 pll_cfg2_init;
u32 ana_cfg1_end;
@ -197,6 +238,11 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
u32 b_offset = 0;
u32 val;
if (phy->variant->has_phy_clk)
clk_set_rate(phy->clk_phy, clk_rate);
sun8i_hdmi_phy_set_polarity(phy, mode);
/* bandwidth / frequency independent settings */
pll_cfg1_init = SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN |
@ -333,41 +379,10 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
return 0;
}
static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *display,
const struct drm_display_mode *mode)
static void sun8i_h3_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
{
struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data;
u32 val = 0;
struct sun8i_hdmi_phy *phy = data;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC;
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val);
if (phy->variant->has_phy_clk)
clk_set_rate(phy->clk_phy, mode->crtc_clock * 1000);
return phy->variant->phy_config(hdmi, phy, mode->crtc_clock * 1000);
};
static void sun8i_hdmi_phy_disable_a83t(struct dw_hdmi *hdmi,
struct sun8i_hdmi_phy *phy)
{
dw_hdmi_phy_gen2_txpwron(hdmi, 0);
dw_hdmi_phy_gen2_pddq(hdmi, 1);
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0);
}
static void sun8i_hdmi_phy_disable_h3(struct dw_hdmi *hdmi,
struct sun8i_hdmi_phy *phy)
{
regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
SUN8I_HDMI_PHY_ANA_CFG1_LDOEN |
SUN8I_HDMI_PHY_ANA_CFG1_ENVBS |
@ -375,19 +390,12 @@ static void sun8i_hdmi_phy_disable_h3(struct dw_hdmi *hdmi,
regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0);
}
static void sun8i_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
{
struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data;
phy->variant->phy_disable(hdmi, phy);
}
static const struct dw_hdmi_phy_ops sun8i_hdmi_phy_ops = {
.init = &sun8i_hdmi_phy_config,
.disable = &sun8i_hdmi_phy_disable,
.read_hpd = &dw_hdmi_phy_read_hpd,
.update_hpd = &dw_hdmi_phy_update_hpd,
.setup_hpd = &dw_hdmi_phy_setup_hpd,
static const struct dw_hdmi_phy_ops sun8i_h3_hdmi_phy_ops = {
.init = sun8i_h3_hdmi_phy_config,
.disable = sun8i_h3_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
.update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
static void sun8i_hdmi_phy_unlock(struct sun8i_hdmi_phy *phy)
@ -565,10 +573,10 @@ void sun8i_hdmi_phy_deinit(struct sun8i_hdmi_phy *phy)
void sun8i_hdmi_phy_set_ops(struct sun8i_hdmi_phy *phy,
struct dw_hdmi_plat_data *plat_data)
{
struct sun8i_hdmi_phy_variant *variant = phy->variant;
const struct sun8i_hdmi_phy_variant *variant = phy->variant;
if (variant->is_custom_phy) {
plat_data->phy_ops = &sun8i_hdmi_phy_ops;
if (variant->phy_ops) {
plat_data->phy_ops = variant->phy_ops;
plat_data->phy_name = "sun8i_dw_hdmi_phy";
plat_data->phy_data = phy;
} else {
@ -587,35 +595,27 @@ static const struct regmap_config sun8i_hdmi_phy_regmap_config = {
};
static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
.is_custom_phy = true,
.phy_ops = &sun8i_a83t_hdmi_phy_ops,
.phy_init = &sun8i_hdmi_phy_init_a83t,
.phy_disable = &sun8i_hdmi_phy_disable_a83t,
.phy_config = &sun8i_hdmi_phy_config_a83t,
};
static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
.has_phy_clk = true,
.is_custom_phy = true,
.phy_ops = &sun8i_h3_hdmi_phy_ops,
.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_r40_hdmi_phy = {
.has_phy_clk = true,
.has_second_pll = true,
.is_custom_phy = true,
.phy_ops = &sun8i_h3_hdmi_phy_ops,
.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 sun50i_a64_hdmi_phy = {
.has_phy_clk = true,
.is_custom_phy = true,
.phy_ops = &sun8i_h3_hdmi_phy_ops,
.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 sun50i_h6_hdmi_phy = {
@ -672,116 +672,64 @@ int sun8i_hdmi_phy_get(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
static int sun8i_hdmi_phy_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct sun8i_hdmi_phy *phy;
struct resource res;
void __iomem *regs;
int ret;
match = of_match_node(sun8i_hdmi_phy_of_table, node);
if (!match) {
dev_err(dev, "Incompatible HDMI PHY\n");
return -EINVAL;
}
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->variant = (struct sun8i_hdmi_phy_variant *)match->data;
phy->variant = of_device_get_match_data(dev);
phy->dev = dev;
ret = of_address_to_resource(node, 0, &res);
if (ret) {
dev_err(dev, "phy: Couldn't get our resources\n");
return ret;
}
regs = devm_ioremap_resource(dev, &res);
if (IS_ERR(regs)) {
dev_err(dev, "Couldn't map the HDMI PHY registers\n");
return PTR_ERR(regs);
}
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return dev_err_probe(dev, PTR_ERR(regs),
"Couldn't map the HDMI PHY registers\n");
phy->regs = devm_regmap_init_mmio(dev, regs,
&sun8i_hdmi_phy_regmap_config);
if (IS_ERR(phy->regs)) {
dev_err(dev, "Couldn't create the HDMI PHY regmap\n");
return PTR_ERR(phy->regs);
}
if (IS_ERR(phy->regs))
return dev_err_probe(dev, PTR_ERR(phy->regs),
"Couldn't create the HDMI PHY regmap\n");
phy->clk_bus = of_clk_get_by_name(node, "bus");
if (IS_ERR(phy->clk_bus)) {
dev_err(dev, "Could not get bus clock\n");
return PTR_ERR(phy->clk_bus);
}
phy->clk_bus = devm_clk_get(dev, "bus");
if (IS_ERR(phy->clk_bus))
return dev_err_probe(dev, PTR_ERR(phy->clk_bus),
"Could not get bus clock\n");
phy->clk_mod = of_clk_get_by_name(node, "mod");
if (IS_ERR(phy->clk_mod)) {
dev_err(dev, "Could not get mod clock\n");
ret = PTR_ERR(phy->clk_mod);
goto err_put_clk_bus;
}
phy->clk_mod = devm_clk_get(dev, "mod");
if (IS_ERR(phy->clk_mod))
return dev_err_probe(dev, PTR_ERR(phy->clk_mod),
"Could not get mod clock\n");
if (phy->variant->has_phy_clk) {
phy->clk_pll0 = of_clk_get_by_name(node, "pll-0");
if (IS_ERR(phy->clk_pll0)) {
dev_err(dev, "Could not get pll-0 clock\n");
ret = PTR_ERR(phy->clk_pll0);
goto err_put_clk_mod;
}
phy->clk_pll0 = devm_clk_get(dev, "pll-0");
if (IS_ERR(phy->clk_pll0))
return dev_err_probe(dev, PTR_ERR(phy->clk_pll0),
"Could not get pll-0 clock\n");
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;
}
phy->clk_pll1 = devm_clk_get(dev, "pll-1");
if (IS_ERR(phy->clk_pll1))
return dev_err_probe(dev, PTR_ERR(phy->clk_pll1),
"Could not get pll-1 clock\n");
}
}
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_pll1;
}
phy->rst_phy = devm_reset_control_get_shared(dev, "phy");
if (IS_ERR(phy->rst_phy))
return dev_err_probe(dev, PTR_ERR(phy->rst_phy),
"Could not get phy reset control\n");
platform_set_drvdata(pdev, phy);
return 0;
err_put_clk_pll1:
clk_put(phy->clk_pll1);
err_put_clk_pll0:
clk_put(phy->clk_pll0);
err_put_clk_mod:
clk_put(phy->clk_mod);
err_put_clk_bus:
clk_put(phy->clk_bus);
return ret;
}
static int sun8i_hdmi_phy_remove(struct platform_device *pdev)
{
struct sun8i_hdmi_phy *phy = platform_get_drvdata(pdev);
reset_control_put(phy->rst_phy);
clk_put(phy->clk_pll0);
clk_put(phy->clk_pll1);
clk_put(phy->clk_mod);
clk_put(phy->clk_bus);
return 0;
}
struct platform_driver sun8i_hdmi_phy_driver = {
.probe = sun8i_hdmi_phy_probe,
.remove = sun8i_hdmi_phy_remove,
.driver = {
.name = "sun8i-hdmi-phy",
.of_match_table = sun8i_hdmi_phy_of_table,

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/module.h>
#include <linux/pci.h>
#include <drm/drm_aperture.h>

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_V3D
tristate "Broadcom V3D 3.x and newer"
depends on ARCH_BCM || ARCH_BRCMSTB || COMPILE_TEST
depends on ARCH_BCM || ARCH_BRCMSTB || ARCH_BCM2835 || COMPILE_TEST
depends on DRM
depends on COMMON_CLK
depends on MMU
@ -9,4 +9,5 @@ config DRM_V3D
select DRM_GEM_SHMEM_HELPER
help
Choose this option if you have a system that has a Broadcom
V3D 3.x or newer GPU, such as BCM7268.
V3D 3.x or newer GPUs. SoCs supported include the BCM2711,
BCM7268 and BCM7278.

View File

@ -4,7 +4,6 @@
#include <linux/circ_buf.h>
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/pm_runtime.h>
#include <linux/seq_file.h>
#include <linux/string_helpers.h>
@ -131,11 +130,7 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
struct drm_device *dev = node->minor->dev;
struct v3d_dev *v3d = to_v3d_dev(dev);
u32 ident0, ident1, ident2, ident3, cores;
int ret, core;
ret = pm_runtime_get_sync(v3d->drm.dev);
if (ret < 0)
return ret;
int core;
ident0 = V3D_READ(V3D_HUB_IDENT0);
ident1 = V3D_READ(V3D_HUB_IDENT1);
@ -188,9 +183,6 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
(misccfg & V3D_MISCCFG_OVRTMUOUT) != 0);
}
pm_runtime_mark_last_busy(v3d->drm.dev);
pm_runtime_put_autosuspend(v3d->drm.dev);
return 0;
}
@ -218,11 +210,6 @@ static int v3d_measure_clock(struct seq_file *m, void *unused)
uint32_t cycles;
int core = 0;
int measure_ms = 1000;
int ret;
ret = pm_runtime_get_sync(v3d->drm.dev);
if (ret < 0)
return ret;
if (v3d->ver >= 40) {
V3D_CORE_WRITE(core, V3D_V4_PCTR_0_SRC_0_3,
@ -246,9 +233,6 @@ static int v3d_measure_clock(struct seq_file *m, void *unused)
cycles / (measure_ms * 1000),
(cycles / (measure_ms * 100)) % 10);
pm_runtime_mark_last_busy(v3d->drm.dev);
pm_runtime_put_autosuspend(v3d->drm.dev);
return 0;
}

View File

@ -19,7 +19,6 @@
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <drm/drm_drv.h>
@ -43,7 +42,6 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
{
struct v3d_dev *v3d = to_v3d_dev(dev);
struct drm_v3d_get_param *args = data;
int ret;
static const u32 reg_map[] = {
[DRM_V3D_PARAM_V3D_UIFCFG] = V3D_HUB_UIFCFG,
[DRM_V3D_PARAM_V3D_HUB_IDENT1] = V3D_HUB_IDENT1,
@ -69,17 +67,12 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
if (args->value != 0)
return -EINVAL;
ret = pm_runtime_get_sync(v3d->drm.dev);
if (ret < 0)
return ret;
if (args->param >= DRM_V3D_PARAM_V3D_CORE0_IDENT0 &&
args->param <= DRM_V3D_PARAM_V3D_CORE0_IDENT2) {
args->value = V3D_CORE_READ(0, offset);
} else {
args->value = V3D_READ(offset);
}
pm_runtime_mark_last_busy(v3d->drm.dev);
pm_runtime_put_autosuspend(v3d->drm.dev);
return 0;
}
@ -198,6 +191,7 @@ static const struct drm_driver v3d_drm_driver = {
};
static const struct of_device_id v3d_of_match[] = {
{ .compatible = "brcm,2711-v3d" },
{ .compatible = "brcm,7268-v3d" },
{ .compatible = "brcm,7278-v3d" },
{},
@ -280,10 +274,6 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
return -ENOMEM;
}
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 50);
pm_runtime_enable(dev);
ret = v3d_gem_init(drm);
if (ret)
goto dma_free;

View File

@ -6,7 +6,6 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/sched/signal.h>
#include <linux/uaccess.h>
@ -372,9 +371,6 @@ v3d_job_free(struct kref *ref)
dma_fence_put(job->irq_fence);
dma_fence_put(job->done_fence);
pm_runtime_mark_last_busy(job->v3d->drm.dev);
pm_runtime_put_autosuspend(job->v3d->drm.dev);
if (job->perfmon)
v3d_perfmon_put(job->perfmon);
@ -476,14 +472,10 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
job->v3d = v3d;
job->free = free;
ret = pm_runtime_get_sync(v3d->drm.dev);
if (ret < 0)
goto fail;
ret = drm_sched_job_init(&job->base, &v3d_priv->sched_entity[queue],
v3d_priv);
if (ret)
goto fail_job;
goto fail;
if (has_multisync) {
if (se->in_sync_count && se->wait_stage == queue) {
@ -514,8 +506,6 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
fail_deps:
drm_sched_job_cleanup(&job->base);
fail_job:
pm_runtime_put_autosuspend(v3d->drm.dev);
fail:
kfree(*container);
*container = NULL;

View File

@ -580,8 +580,10 @@ static int virtio_gpu_get_caps_ioctl(struct drm_device *dev,
spin_unlock(&vgdev->display_info_lock);
/* not in cache - need to talk to hw */
virtio_gpu_cmd_get_capset(vgdev, found_valid, args->cap_set_ver,
&cache_ent);
ret = virtio_gpu_cmd_get_capset(vgdev, found_valid, args->cap_set_ver,
&cache_ent);
if (ret)
return ret;
virtio_gpu_notify(vgdev);
copy_exit:

View File

@ -168,9 +168,9 @@ static int virtio_gpu_object_shmem_init(struct virtio_gpu_device *vgdev,
* since virtio_gpu doesn't support dma-buf import from other devices.
*/
shmem->pages = drm_gem_shmem_get_sg_table(&bo->base);
if (!shmem->pages) {
if (IS_ERR(shmem->pages)) {
drm_gem_shmem_unpin(&bo->base);
return -EINVAL;
return PTR_ERR(shmem->pages);
}
if (use_dma_api) {

View File

@ -90,7 +90,6 @@ static const struct virtio_dma_buf_ops virtgpu_dmabuf_ops = {
int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo)
{
int ret;
struct virtio_gpu_object_array *objs;
objs = virtio_gpu_array_alloc(1);
@ -98,11 +97,8 @@ int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev,
return -ENOMEM;
virtio_gpu_array_add_obj(objs, &bo->base.base);
ret = virtio_gpu_cmd_resource_assign_uuid(vgdev, objs);
if (ret)
return ret;
return 0;
return virtio_gpu_cmd_resource_assign_uuid(vgdev, objs);
}
struct dma_buf *virtgpu_gem_prime_export(struct drm_gem_object *obj,

View File

@ -157,7 +157,7 @@ static void compose_plane(struct vkms_composer *primary_composer,
void *vaddr;
void (*pixel_blend)(const u8 *p_src, u8 *p_dst);
if (WARN_ON(iosys_map_is_null(&primary_composer->map[0])))
if (WARN_ON(iosys_map_is_null(&plane_composer->map[0])))
return;
vaddr = plane_composer->map[0].vaddr;
@ -180,7 +180,7 @@ static int compose_active_planes(void **vaddr_out,
int i;
if (!*vaddr_out) {
*vaddr_out = kzalloc(gem_obj->size, GFP_KERNEL);
*vaddr_out = kvzalloc(gem_obj->size, GFP_KERNEL);
if (!*vaddr_out) {
DRM_ERROR("Cannot allocate memory for output frame.");
return -ENOMEM;
@ -263,7 +263,7 @@ void vkms_composer_worker(struct work_struct *work)
crtc_state);
if (ret) {
if (ret == -EINVAL && !wb_pending)
kfree(vaddr_out);
kvfree(vaddr_out);
return;
}
@ -275,7 +275,7 @@ void vkms_composer_worker(struct work_struct *work)
crtc_state->wb_pending = false;
spin_unlock_irq(&out->composer_lock);
} else {
kfree(vaddr_out);
kvfree(vaddr_out);
}
/*

View File

@ -616,7 +616,7 @@ config FB_UVESA
config FB_VESA
bool "VESA VGA graphics support"
depends on (FB = y) && (X86 || COMPILE_TEST)
depends on (FB = y) && X86
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT

View File

@ -2182,7 +2182,6 @@ static int fbcon_switch(struct vc_data *vc)
if (logo_shown == FBCON_LOGO_DRAW) {
logo_shown = fg_console;
/* This is protected above by initmem_freed */
fb_show_logo(info, ops->rotate);
update_region(vc,
vc->vc_origin + vc->vc_size_row * vc->vc_top,

View File

@ -19,6 +19,7 @@
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/sysfb.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/vt.h>
@ -1764,6 +1765,17 @@ int remove_conflicting_framebuffers(struct apertures_struct *a,
do_free = true;
}
/*
* If a driver asked to unregister a platform device registered by
* sysfb, then can be assumed that this is a driver for a display
* that is set up by the system firmware and has a generic driver.
*
* Drivers for devices that don't have a generic driver will never
* ask for this, so let's assume that a real driver for the display
* was already probed and prevent sysfb to register devices later.
*/
sysfb_disable();
mutex_lock(&registration_lock);
do_remove_conflicting_framebuffers(a, name, primary);
mutex_unlock(&registration_lock);

View File

@ -358,17 +358,6 @@ static int efifb_probe(struct platform_device *dev)
char *option = NULL;
efi_memory_desc_t md;
/*
* Generic drivers must not be registered if a framebuffer exists.
* If a native driver was probed, the display hardware was already
* taken and attempting to use the system framebuffer is dangerous.
*/
if (num_registered_fb > 0) {
dev_err(&dev->dev,
"efifb: a framebuffer is already registered\n");
return -EINVAL;
}
if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI || pci_dev_disabled)
return -ENODEV;

View File

@ -419,17 +419,6 @@ static int simplefb_probe(struct platform_device *pdev)
struct simplefb_par *par;
struct resource *res, *mem;
/*
* Generic drivers must not be registered if a framebuffer exists.
* If a native driver was probed, the display hardware was already
* taken and attempting to use the system framebuffer is dangerous.
*/
if (num_registered_fb > 0) {
dev_err(&pdev->dev,
"simplefb: a framebuffer is already registered\n");
return -EINVAL;
}
if (fb_get_options("simplefb", NULL))
return -ENODEV;

View File

@ -372,8 +372,10 @@ struct drm_dp_aux {
* Also note that this callback can be called no matter the
* state @dev is in and also no matter what state the panel is
* in. It's expected:
*
* - If the @dev providing the AUX bus is currently unpowered then
* it will power itself up for the transfer.
*
* - If we're on eDP (using a drm_panel) and the panel is not in a
* state where it can respond (it's not powered or it's in a
* low power state) then this function may return an error, but

View File

@ -594,6 +594,15 @@ drm_display_mode_from_cea_vic(struct drm_device *dev,
u8 video_code);
/* Interface based on struct drm_edid */
const struct drm_edid *drm_edid_alloc(const void *edid, size_t size);
const struct drm_edid *drm_edid_dup(const struct drm_edid *drm_edid);
void drm_edid_free(const struct drm_edid *drm_edid);
const struct drm_edid *drm_edid_read(struct drm_connector *connector);
const struct drm_edid *drm_edid_read_ddc(struct drm_connector *connector,
struct i2c_adapter *adapter);
const struct drm_edid *drm_edid_read_custom(struct drm_connector *connector,
int (*read_block)(void *context, u8 *buf, unsigned int block, size_t len),
void *context);
const u8 *drm_find_edid_extension(const struct drm_edid *drm_edid,
int ext_id, int *ext_index);

View File

@ -50,6 +50,12 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
const struct device_node *port2);
int drm_of_lvds_get_data_mapping(const struct device_node *port);
int drm_of_get_data_lanes_count(const struct device_node *endpoint,
const unsigned int min, const unsigned int max);
int drm_of_get_data_lanes_count_ep(const struct device_node *port,
int port_reg, int reg,
const unsigned int min,
const unsigned int max);
#else
static inline uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port)
@ -105,6 +111,22 @@ drm_of_lvds_get_data_mapping(const struct device_node *port)
{
return -EINVAL;
}
static inline int
drm_of_get_data_lanes_count(const struct device_node *endpoint,
const unsigned int min, const unsigned int max)
{
return -EINVAL;
}
static inline int
drm_of_get_data_lanes_count_ep(const struct device_node *port,
int port_reg, int reg,
const unsigned int min,
const unsigned int max)
{
return -EINVAL;
}
#endif
/*

View File

@ -23,7 +23,7 @@
* memcpy(vaddr, src, len);
*
* void *vaddr_iomem = ...; // pointer to I/O memory
* memcpy_toio(vaddr, _iomem, src, len);
* memcpy_toio(vaddr_iomem, src, len);
*
* The user of such pointer may not have information about the mapping of that
* region or may want to have a single code path to handle operations on that

View File

@ -55,6 +55,18 @@ struct efifb_dmi_info {
int flags;
};
#ifdef CONFIG_SYSFB
void sysfb_disable(void);
#else /* CONFIG_SYSFB */
static inline void sysfb_disable(void)
{
}
#endif /* CONFIG_SYSFB */
#ifdef CONFIG_EFI
extern struct efifb_dmi_info efifb_dmi_list[];
@ -72,8 +84,8 @@ static inline void sysfb_apply_efi_quirks(struct platform_device *pd)
bool sysfb_parse_mode(const struct screen_info *si,
struct simplefb_platform_data *mode);
int sysfb_create_simplefb(const struct screen_info *si,
const struct simplefb_platform_data *mode);
struct platform_device *sysfb_create_simplefb(const struct screen_info *si,
const struct simplefb_platform_data *mode);
#else /* CONFIG_SYSFB_SIMPLE */
@ -83,10 +95,10 @@ static inline bool sysfb_parse_mode(const struct screen_info *si,
return false;
}
static inline int sysfb_create_simplefb(const struct screen_info *si,
const struct simplefb_platform_data *mode)
static inline struct platform_device *sysfb_create_simplefb(const struct screen_info *si,
const struct simplefb_platform_data *mode)
{
return -EINVAL;
return ERR_PTR(-EINVAL);
}
#endif /* CONFIG_SYSFB_SIMPLE */

View File

@ -85,6 +85,88 @@ struct dma_buf_sync {
#define DMA_BUF_NAME_LEN 32
/**
* struct dma_buf_export_sync_file - Get a sync_file from a dma-buf
*
* Userspace can perform a DMA_BUF_IOCTL_EXPORT_SYNC_FILE to retrieve the
* current set of fences on a dma-buf file descriptor as a sync_file. CPU
* waits via poll() or other driver-specific mechanisms typically wait on
* whatever fences are on the dma-buf at the time the wait begins. This
* is similar except that it takes a snapshot of the current fences on the
* dma-buf for waiting later instead of waiting immediately. This is
* useful for modern graphics APIs such as Vulkan which assume an explicit
* synchronization model but still need to inter-operate with dma-buf.
*
* The intended usage pattern is the following:
*
* 1. Export a sync_file with flags corresponding to the expected GPU usage
* via DMA_BUF_IOCTL_EXPORT_SYNC_FILE.
*
* 2. Submit rendering work which uses the dma-buf. The work should wait on
* the exported sync file before rendering and produce another sync_file
* when complete.
*
* 3. Import the rendering-complete sync_file into the dma-buf with flags
* corresponding to the GPU usage via DMA_BUF_IOCTL_IMPORT_SYNC_FILE.
*
* Unlike doing implicit synchronization via a GPU kernel driver's exec ioctl,
* the above is not a single atomic operation. If userspace wants to ensure
* ordering via these fences, it is the respnosibility of userspace to use
* locks or other mechanisms to ensure that no other context adds fences or
* submits work between steps 1 and 3 above.
*/
struct dma_buf_export_sync_file {
/**
* @flags: Read/write flags
*
* Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both.
*
* If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set,
* the returned sync file waits on any writers of the dma-buf to
* complete. Waiting on the returned sync file is equivalent to
* poll() with POLLIN.
*
* If DMA_BUF_SYNC_WRITE is set, the returned sync file waits on
* any users of the dma-buf (read or write) to complete. Waiting
* on the returned sync file is equivalent to poll() with POLLOUT.
* If both DMA_BUF_SYNC_WRITE and DMA_BUF_SYNC_READ are set, this
* is equivalent to just DMA_BUF_SYNC_WRITE.
*/
__u32 flags;
/** @fd: Returned sync file descriptor */
__s32 fd;
};
/**
* struct dma_buf_import_sync_file - Insert a sync_file into a dma-buf
*
* Userspace can perform a DMA_BUF_IOCTL_IMPORT_SYNC_FILE to insert a
* sync_file into a dma-buf for the purposes of implicit synchronization
* with other dma-buf consumers. This allows clients using explicitly
* synchronized APIs such as Vulkan to inter-op with dma-buf consumers
* which expect implicit synchronization such as OpenGL or most media
* drivers/video.
*/
struct dma_buf_import_sync_file {
/**
* @flags: Read/write flags
*
* Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both.
*
* If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set,
* this inserts the sync_file as a read-only fence. Any subsequent
* implicitly synchronized writes to this dma-buf will wait on this
* fence but reads will not.
*
* If DMA_BUF_SYNC_WRITE is set, this inserts the sync_file as a
* write fence. All subsequent implicitly synchronized access to
* this dma-buf will wait on this fence.
*/
__u32 flags;
/** @fd: Sync file descriptor */
__s32 fd;
};
#define DMA_BUF_BASE 'b'
#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
@ -94,5 +176,7 @@ struct dma_buf_sync {
#define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 1, const char *)
#define DMA_BUF_SET_NAME_A _IOW(DMA_BUF_BASE, 1, __u32)
#define DMA_BUF_SET_NAME_B _IOW(DMA_BUF_BASE, 1, __u64)
#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file)
#define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file)
#endif