Hi,
ADF is an experimental display framework that I designed after experimenting with a KMS-based hardware composer for Android. ADF started as an proof-of-concept implemented from scratch, so right now it's a separate framework rather than a patchstack on top of KMS. If there's community interest, moving forward I'd like to merge its functionality into KMS rather than keep it as a separate thing.
I'm going to talk about ADF at the Android and Graphics session at Linux Plumbers. The documentation's not done but I wanted to post these patches to people a heads-up about ADF. If there's interest I can write up some more formal documentation ahead of Plumbers.
I designed ADF to deal with some serious issues I had working with KMS:
1. The API is geared toward updating one object at a time. Android's graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
2. Some SoCs don't map well to KMS's CRTC + planes + encoders + connector model. At the time I was dealing with hardware where the blending engines didn't have their own framebuffer (they could only scan out constant colors or mix the output of other blocks), and you needed to gang several mixers together to drive high-res displays.
3. KMS doesn't support custom pixel formats, which a lot of newer SoCs use internally to cut down on bandwidth between hardware blocks.
4. KMS doesn't have a way to exchange sync fences. As a hack I managed to pass sync fences into the kernel as properties of the atomic pageflip, but there was no good way to get them back out of the kernel without a side channel.
ADF represents display devices as collections of overlay engines and interfaces. Overlay engines (struct adf_overlay_engine) scan out images and interfaces (struct adf_interface) display those images. Overlay engines and interfaces can be connected in any n-to-n configuration that the hardware supports.
Clients issue atomic updates to the screen by passing in a list of buffers (struct adf_buffer) consisting of dma-buf handles, sync fences, and basic metadata like format and size. If this involves composing multiple buffers, clients include a block of custom data describing the actual composition (scaling, z-order, blending, etc.) in a driver-specific format.
Drivers provide hooks to validate these custom data blocks and commit the new configuration to hardware. ADF handles importing the dma-bufs and fences, waiting on incoming sync fences before committing, advancing the display's sync timeline, and releasing dma-bufs once they're removed from the screen.
ADF represents pixel formats using DRM-style fourccs, and automatically sanity-checks buffer sizes when using one of the formats listed in drm_fourcc.h. Drivers can support custom fourccs if they provide hooks to validate buffers that use them.
ADF also provides driver hooks for modesetting, managing and reporting hardware events like vsync, and changing DPMS state. These are documented in struct adf_{obj,overlay_engine,interface,device}_ops, and are similar to the equivalent DRM ops.
Greg Hackmann (3): video: add atomic display framework video: adf: add display core helper video: adf: add memblock helper
Laurent Pinchart (1): video: Add generic display entity core
drivers/video/Kconfig | 2 + drivers/video/Makefile | 2 + drivers/video/adf/Kconfig | 15 + drivers/video/adf/Makefile | 14 + drivers/video/adf/adf.c | 1013 ++++++++++++++++++++++++++++++++++ drivers/video/adf/adf.h | 49 ++ drivers/video/adf/adf_client.c | 853 ++++++++++++++++++++++++++++ drivers/video/adf/adf_display.c | 123 +++++ drivers/video/adf/adf_fops.c | 982 ++++++++++++++++++++++++++++++++ drivers/video/adf/adf_fops.h | 37 ++ drivers/video/adf/adf_fops32.c | 217 ++++++++ drivers/video/adf/adf_fops32.h | 78 +++ drivers/video/adf/adf_memblock.c | 150 +++++ drivers/video/adf/adf_sysfs.c | 291 ++++++++++ drivers/video/adf/adf_sysfs.h | 33 ++ drivers/video/adf/adf_trace.h | 93 ++++ drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/display-core.c | 362 ++++++++++++ include/video/adf.h | 743 +++++++++++++++++++++++++ include/video/adf_client.h | 61 ++ include/video/adf_display.h | 31 ++ include/video/adf_format.h | 282 ++++++++++ include/video/adf_memblock.h | 20 + include/video/display.h | 150 +++++ 25 files changed, 5606 insertions(+) create mode 100644 drivers/video/adf/Kconfig create mode 100644 drivers/video/adf/Makefile create mode 100644 drivers/video/adf/adf.c create mode 100644 drivers/video/adf/adf.h create mode 100644 drivers/video/adf/adf_client.c create mode 100644 drivers/video/adf/adf_display.c create mode 100644 drivers/video/adf/adf_fops.c create mode 100644 drivers/video/adf/adf_fops.h create mode 100644 drivers/video/adf/adf_fops32.c create mode 100644 drivers/video/adf/adf_fops32.h create mode 100644 drivers/video/adf/adf_memblock.c create mode 100644 drivers/video/adf/adf_sysfs.c create mode 100644 drivers/video/adf/adf_sysfs.h create mode 100644 drivers/video/adf/adf_trace.h create mode 100644 drivers/video/display/Kconfig create mode 100644 drivers/video/display/Makefile create mode 100644 drivers/video/display/display-core.c create mode 100644 include/video/adf.h create mode 100644 include/video/adf_client.h create mode 100644 include/video/adf_display.h create mode 100644 include/video/adf_format.h create mode 100644 include/video/adf_memblock.h create mode 100644 include/video/display.h
From: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/display-core.c | 362 +++++++++++++++++++++++++++++++++++ include/video/display.h | 150 +++++++++++++++ 6 files changed, 519 insertions(+) create mode 100644 drivers/video/display/Kconfig create mode 100644 drivers/video/display/Makefile create mode 100644 drivers/video/display/display-core.c create mode 100644 include/video/display.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 2e937bd..6d9788d 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2475,6 +2475,7 @@ source "drivers/video/omap2/Kconfig" source "drivers/video/exynos/Kconfig" source "drivers/video/mmp/Kconfig" source "drivers/video/backlight/Kconfig" +source "drivers/video/display/Kconfig"
if VT source "drivers/video/console/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index e8bae8d..d7fd4a2 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -15,6 +15,7 @@ fb-objs := $(fb-y) obj-$(CONFIG_VT) += console/ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ +obj-y += display/
obj-$(CONFIG_EXYNOS_VIDEO) += exynos/
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig new file mode 100644 index 0000000..1d533e7 --- /dev/null +++ b/drivers/video/display/Kconfig @@ -0,0 +1,4 @@ +menuconfig DISPLAY_CORE + tristate "Display Core" + ---help--- + Support common display framework for graphics devices. diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile new file mode 100644 index 0000000..bd93496 --- /dev/null +++ b/drivers/video/display/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_DISPLAY_CORE) += display-core.o diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c new file mode 100644 index 0000000..d2daa15 --- /dev/null +++ b/drivers/video/display/display-core.c @@ -0,0 +1,362 @@ +/* + * Display Core + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> + +#include <video/display.h> +#include <video/videomode.h> + +static LIST_HEAD(display_entity_list); +static LIST_HEAD(display_entity_notifiers); +static DEFINE_MUTEX(display_entity_mutex); + +/* ----------------------------------------------------------------------------- + * Control operations + */ + +/** + * display_entity_set_state - Set the display entity operation state + * @entity: The display entity + * @state: Display entity operation state + * + * See &enum display_entity_state for information regarding the entity states. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_set_state(struct display_entity *entity, + enum display_entity_state state) +{ + int ret; + + if (entity->state == state) + return 0; + + if (!entity->ops.ctrl || !entity->ops.ctrl->set_state) + return 0; + + ret = entity->ops.ctrl->set_state(entity, state); + if (ret < 0) + return ret; + + entity->state = state; + return 0; +} +EXPORT_SYMBOL_GPL(display_entity_set_state); + +/** + * display_entity_update - Update the display + * @entity: The display entity + * + * Make the display entity ready to receive pixel data and start frame transfer. + * This operation can only be called if the display entity is in STANDBY or ON + * state. + * + * The display entity will call the upstream entity in the video chain to start + * the video stream. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_update(struct display_entity *entity) +{ + if (!entity->ops.ctrl || !entity->ops.ctrl->update) + return 0; + + return entity->ops.ctrl->update(entity); +} +EXPORT_SYMBOL_GPL(display_entity_update); + +/** + * display_entity_get_modes - Get video modes supported by the display entity + * @entity The display entity + * @modes: Pointer to an array of modes + * + * Fill the modes argument with a pointer to an array of video modes. The array + * is owned by the display entity. + * + * Return the number of supported modes on success (including 0 if no mode is + * supported) or a negative error code otherwise. + */ +int display_entity_get_modes(struct display_entity *entity, + const struct videomode **modes) +{ + if (!entity->ops.ctrl || !entity->ops.ctrl->get_modes) + return 0; + + return entity->ops.ctrl->get_modes(entity, modes); +} +EXPORT_SYMBOL_GPL(display_entity_get_modes); + +/** + * display_entity_get_size - Get display entity physical size + * @entity: The display entity + * @width: Physical width in millimeters + * @height: Physical height in millimeters + * + * When applicable, for instance for display panels, retrieve the display + * physical size in millimeters. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_get_size(struct display_entity *entity, + unsigned int *width, unsigned int *height) +{ + if (!entity->ops.ctrl || !entity->ops.ctrl->get_size) + return -EOPNOTSUPP; + + return entity->ops.ctrl->get_size(entity, width, height); +} +EXPORT_SYMBOL_GPL(display_entity_get_size); + +/** + * display_entity_get_params - Get display entity interface parameters + * @entity: The display entity + * @params: Pointer to interface parameters + * + * Fill the parameters structure pointed to by the params argument with display + * entity interface parameters. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_get_params(struct display_entity *entity, + struct display_entity_interface_params *params) +{ + if (!entity->ops.ctrl || !entity->ops.ctrl->get_modes) + return -EOPNOTSUPP; + + return entity->ops.ctrl->get_params(entity, params); +} +EXPORT_SYMBOL_GPL(display_entity_get_params); + +/* ----------------------------------------------------------------------------- + * Video operations + */ + +/** + * display_entity_set_stream - Control the video stream state + * @entity: The display entity + * @state: Display video stream state + * + * Control the video stream state at the entity video output. + * + * See &enum display_entity_stream_state for information regarding the stream + * states. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_set_stream(struct display_entity *entity, + enum display_entity_stream_state state) +{ + if (!entity->ops.video || !entity->ops.video->set_stream) + return 0; + + return entity->ops.video->set_stream(entity, state); +} +EXPORT_SYMBOL_GPL(display_entity_set_stream); + +/* ----------------------------------------------------------------------------- + * Connections + */ + +/** + * display_entity_connect - Connect two entities through a video stream + * @source: The video stream source + * @sink: The video stream sink + * + * Set the sink entity source field to the source entity. + */ + +/** + * display_entity_disconnect - Disconnect two previously connected entities + * @source: The video stream source + * @sink: The video stream sink + * + * Break a connection between two previously connected entities. The source + * entity source field is reset to NULL. + */ + +/* ----------------------------------------------------------------------------- + * Registration and notification + */ + +static void display_entity_release(struct kref *ref) +{ + struct display_entity *entity = + container_of(ref, struct display_entity, ref); + + if (entity->release) + entity->release(entity); +} + +/** + * display_entity_get - get a reference to a display entity + * @display_entity: the display entity + * + * Return the display entity pointer. + */ +struct display_entity *display_entity_get(struct display_entity *entity) +{ + if (entity == NULL) + return NULL; + + kref_get(&entity->ref); + return entity; +} +EXPORT_SYMBOL_GPL(display_entity_get); + +/** + * display_entity_put - release a reference to a display entity + * @display_entity: the display entity + * + * Releasing the last reference to a display entity releases the display entity + * itself. + */ +void display_entity_put(struct display_entity *entity) +{ + kref_put(&entity->ref, display_entity_release); +} +EXPORT_SYMBOL_GPL(display_entity_put); + +static int display_entity_notifier_match(struct display_entity *entity, + struct display_entity_notifier *notifier) +{ + return notifier->dev == NULL || notifier->dev == entity->dev; +} + +/** + * display_entity_register_notifier - register a display entity notifier + * @notifier: display entity notifier structure we want to register + * + * Display entity notifiers are called to notify drivers of display + * entity-related events for matching display_entitys. + * + * Notifiers and display_entitys are matched through the device they correspond + * to. If the notifier dev field is equal to the display entity dev field the + * notifier will be called when an event is reported. Notifiers with a NULL dev + * field act as catch-all and will be called for all display_entitys. + * + * Supported events are + * + * - DISPLAY_ENTITY_NOTIFIER_CONNECT reports display entity connection and is + * sent at display entity or notifier registration time + * - DISPLAY_ENTITY_NOTIFIER_DISCONNECT reports display entity disconnection and + * is sent at display entity unregistration time + * + * Registering a notifier sends DISPLAY_ENTITY_NOTIFIER_CONNECT events for all + * previously registered display_entitys that match the notifiers. + * + * Return 0 on success. + */ +int display_entity_register_notifier(struct display_entity_notifier *notifier) +{ + struct display_entity *entity; + + mutex_lock(&display_entity_mutex); + list_add_tail(¬ifier->list, &display_entity_notifiers); + + list_for_each_entry(entity, &display_entity_list, list) { + if (!display_entity_notifier_match(entity, notifier)) + continue; + + if (notifier->notify(notifier, entity, + DISPLAY_ENTITY_NOTIFIER_CONNECT)) + break; + } + mutex_unlock(&display_entity_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(display_entity_register_notifier); + +/** + * display_entity_unregister_notifier - unregister a display entity notifier + * @notifier: display entity notifier to be unregistered + * + * Unregistration guarantees that the notifier will never be called upon return + * of this function. + */ +void display_entity_unregister_notifier(struct display_entity_notifier *notifier) +{ + mutex_lock(&display_entity_mutex); + list_del(¬ifier->list); + mutex_unlock(&display_entity_mutex); +} +EXPORT_SYMBOL_GPL(display_entity_unregister_notifier); + +/** + * display_entity_register - register a display entity + * @display_entity: display entity to be registered + * + * Register the display entity and send the DISPLAY_ENTITY_NOTIFIER_CONNECT + * event to all matching registered notifiers. + * + * Return 0 on success. + */ +int __must_check __display_entity_register(struct display_entity *entity, + struct module *owner) +{ + struct display_entity_notifier *notifier; + + kref_init(&entity->ref); + entity->owner = owner; + entity->state = DISPLAY_ENTITY_STATE_OFF; + + mutex_lock(&display_entity_mutex); + list_add(&entity->list, &display_entity_list); + + list_for_each_entry(notifier, &display_entity_notifiers, list) { + if (!display_entity_notifier_match(entity, notifier)) + continue; + + if (notifier->notify(notifier, entity, + DISPLAY_ENTITY_NOTIFIER_CONNECT)) + break; + } + mutex_unlock(&display_entity_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(__display_entity_register); + +/** + * display_entity_unregister - unregister a display entity + * @display_entity: display entity to be unregistered + * + * Unregister the display entity and send the DISPLAY_ENTITY_NOTIFIER_DISCONNECT + * event to all matching registered notifiers. + */ +void display_entity_unregister(struct display_entity *entity) +{ + struct display_entity_notifier *notifier; + + mutex_lock(&display_entity_mutex); + list_for_each_entry(notifier, &display_entity_notifiers, list) { + if (!display_entity_notifier_match(entity, notifier)) + continue; + + notifier->notify(notifier, entity, + DISPLAY_ENTITY_NOTIFIER_DISCONNECT); + } + + list_del(&entity->list); + mutex_unlock(&display_entity_mutex); + + display_entity_put(entity); +} +EXPORT_SYMBOL_GPL(display_entity_unregister); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Display Core"); +MODULE_LICENSE("GPL"); diff --git a/include/video/display.h b/include/video/display.h new file mode 100644 index 0000000..90d18ca --- /dev/null +++ b/include/video/display.h @@ -0,0 +1,150 @@ +/* + * Display Core + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DISPLAY_H__ +#define __DISPLAY_H__ + +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/module.h> + +/* ----------------------------------------------------------------------------- + * Display Entity + */ + +struct display_entity; +struct videomode; + +#define DISPLAY_ENTITY_NOTIFIER_CONNECT 1 +#define DISPLAY_ENTITY_NOTIFIER_DISCONNECT 2 + +struct display_entity_notifier { + int (*notify)(struct display_entity_notifier *, struct display_entity *, + int); + struct device *dev; + struct list_head list; +}; + +/** + * enum display_entity_state - State of a display entity + * @DISPLAY_ENTITY_STATE_OFF: The entity is turned off completely, possibly + * including its power supplies. Communication with a display entity in + * that state is not possible. + * @DISPLAY_ENTITY_STATE_STANDBY: The entity is in a low-power state. Full + * communication with the display entity is supported, including pixel data + * transfer, but the output is kept blanked. + * @DISPLAY_ENTITY_STATE_ON: The entity is fully operational. + */ +enum display_entity_state { + DISPLAY_ENTITY_STATE_OFF, + DISPLAY_ENTITY_STATE_STANDBY, + DISPLAY_ENTITY_STATE_ON, +}; + +/** + * enum display_entity_stream_state - State of a video stream + * @DISPLAY_ENTITY_STREAM_STOPPED: The video stream is stopped, no frames are + * transferred. + * @DISPLAY_ENTITY_STREAM_SINGLE_SHOT: The video stream has been started for + * single shot operation. The source entity will transfer a single frame + * and then stop. + * @DISPLAY_ENTITY_STREAM_CONTINUOUS: The video stream is running, frames are + * transferred continuously by the source entity. + */ +enum display_entity_stream_state { + DISPLAY_ENTITY_STREAM_STOPPED, + DISPLAY_ENTITY_STREAM_SINGLE_SHOT, + DISPLAY_ENTITY_STREAM_CONTINUOUS, +}; + +enum display_entity_interface_type { + DISPLAY_ENTITY_INTERFACE_DPI, +}; + +struct display_entity_interface_params { + enum display_entity_interface_type type; +}; + +struct display_entity_control_ops { + int (*set_state)(struct display_entity *ent, + enum display_entity_state state); + int (*update)(struct display_entity *ent); + int (*get_modes)(struct display_entity *ent, + const struct videomode **modes); + int (*get_params)(struct display_entity *ent, + struct display_entity_interface_params *params); + int (*get_size)(struct display_entity *ent, + unsigned int *width, unsigned int *height); +}; + +struct display_entity_video_ops { + int (*set_stream)(struct display_entity *ent, + enum display_entity_stream_state state); +}; + +struct display_entity { + struct list_head list; + struct device *dev; + struct module *owner; + struct kref ref; + + struct display_entity *source; + + struct { + const struct display_entity_control_ops *ctrl; + const struct display_entity_video_ops *video; + } ops; + + void(*release)(struct display_entity *ent); + + enum display_entity_state state; +}; + +int display_entity_set_state(struct display_entity *entity, + enum display_entity_state state); +int display_entity_update(struct display_entity *entity); +int display_entity_get_modes(struct display_entity *entity, + const struct videomode **modes); +int display_entity_get_params(struct display_entity *entity, + struct display_entity_interface_params *params); +int display_entity_get_size(struct display_entity *entity, + unsigned int *width, unsigned int *height); + +int display_entity_set_stream(struct display_entity *entity, + enum display_entity_stream_state state); + +static inline void display_entity_connect(struct display_entity *source, + struct display_entity *sink) +{ + sink->source = source; +} + +static inline void display_entity_disconnect(struct display_entity *source, + struct display_entity *sink) +{ + sink->source = NULL; +} + +struct display_entity *display_entity_get(struct display_entity *entity); +void display_entity_put(struct display_entity *entity); + +int __must_check __display_entity_register(struct display_entity *entity, + struct module *owner); +void display_entity_unregister(struct display_entity *entity); + +int display_entity_register_notifier(struct display_entity_notifier *notifier); +void display_entity_unregister_notifier(struct display_entity_notifier *notifier); + +#define display_entity_register(display_entity) \ + __display_entity_register(display_entity, THIS_MODULE) + +#endif /* __DISPLAY_H__ */
Signed-off-by: Greg Hackmann ghackmann@google.com --- drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/adf/Kconfig | 5 + drivers/video/adf/Makefile | 10 + drivers/video/adf/adf.c | 987 +++++++++++++++++++++++++++++++++++++++++ drivers/video/adf/adf.h | 48 ++ drivers/video/adf/adf_client.c | 853 +++++++++++++++++++++++++++++++++++ drivers/video/adf/adf_fops.c | 982 ++++++++++++++++++++++++++++++++++++++++ drivers/video/adf/adf_fops.h | 37 ++ drivers/video/adf/adf_fops32.c | 217 +++++++++ drivers/video/adf/adf_fops32.h | 78 ++++ drivers/video/adf/adf_sysfs.c | 291 ++++++++++++ drivers/video/adf/adf_sysfs.h | 33 ++ drivers/video/adf/adf_trace.h | 93 ++++ include/video/adf.h | 743 +++++++++++++++++++++++++++++++ include/video/adf_client.h | 61 +++ include/video/adf_format.h | 282 ++++++++++++ 17 files changed, 4722 insertions(+) create mode 100644 drivers/video/adf/Kconfig create mode 100644 drivers/video/adf/Makefile create mode 100644 drivers/video/adf/adf.c create mode 100644 drivers/video/adf/adf.h create mode 100644 drivers/video/adf/adf_client.c create mode 100644 drivers/video/adf/adf_fops.c create mode 100644 drivers/video/adf/adf_fops.h create mode 100644 drivers/video/adf/adf_fops32.c create mode 100644 drivers/video/adf/adf_fops32.h create mode 100644 drivers/video/adf/adf_sysfs.c create mode 100644 drivers/video/adf/adf_sysfs.h create mode 100644 drivers/video/adf/adf_trace.h create mode 100644 include/video/adf.h create mode 100644 include/video/adf_client.h create mode 100644 include/video/adf_format.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 6d9788d..a77df10 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2476,6 +2476,7 @@ source "drivers/video/exynos/Kconfig" source "drivers/video/mmp/Kconfig" source "drivers/video/backlight/Kconfig" source "drivers/video/display/Kconfig" +source "drivers/video/adf/Kconfig"
if VT source "drivers/video/console/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index d7fd4a2..aa6a247 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -12,6 +12,7 @@ fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ modedb.o fbcvt.o fb-objs := $(fb-y)
+obj-$(CONFIG_ADF) += adf/ obj-$(CONFIG_VT) += console/ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ diff --git a/drivers/video/adf/Kconfig b/drivers/video/adf/Kconfig new file mode 100644 index 0000000..0b64408 --- /dev/null +++ b/drivers/video/adf/Kconfig @@ -0,0 +1,5 @@ +menuconfig ADF + depends on SYNC + depends on SW_SYNC + depends on DMA_SHARED_BUFFER + tristate "Atomic Display Framework" diff --git a/drivers/video/adf/Makefile b/drivers/video/adf/Makefile new file mode 100644 index 0000000..2af5f79 --- /dev/null +++ b/drivers/video/adf/Makefile @@ -0,0 +1,10 @@ +ccflags-y := -Idrivers/staging/android -Idrivers/staging/android/sync + +CFLAGS_adf.o := -I$(src) + +obj-$(CONFIG_ADF) += adf.o \ + adf_client.o \ + adf_fops.o \ + adf_sysfs.o + +obj-$(CONFIG_COMPAT) += adf_fops32.o diff --git a/drivers/video/adf/adf.c b/drivers/video/adf/adf.c new file mode 100644 index 0000000..5dc04af --- /dev/null +++ b/drivers/video/adf/adf.c @@ -0,0 +1,987 @@ +/* + * Copyright (C) 2013 Google, Inc. + * adf_modeinfo_set_name modified from drm_mode_set_name in + * drivers/gpu/drm/drm_modes.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/device.h> +#include <linux/idr.h> +#include <linux/highmem.h> +#include <linux/memblock.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "sw_sync.h" +#include "sync.h" + +#include "adf.h" +#include "adf_fops.h" +#include "adf_sysfs.h" + +#define CREATE_TRACE_POINTS +#include "adf_trace.h" + +#define ADF_SHORT_FENCE_TIMEOUT (1 * MSEC_PER_SEC) +#define ADF_LONG_FENCE_TIMEOUT (10 * MSEC_PER_SEC) + +static void adf_fence_wait(struct adf_device *dev, struct sync_fence *fence) +{ + /* sync_fence_wait() dumps debug information on timeout. Experience + has shown that if the pipeline gets stuck, a short timeout followed + by a longer one provides useful information for debugging. */ + int err = sync_fence_wait(fence, ADF_SHORT_FENCE_TIMEOUT); + if (err >= 0) + return; + + if (err == -ETIME) + err = sync_fence_wait(fence, ADF_LONG_FENCE_TIMEOUT); + + if (err < 0) + dev_warn(&dev->base.dev, "error waiting on fence: %d\n", err); +} + +void adf_buffer_cleanup(struct adf_buffer *buf) +{ + size_t i; + for (i = 0; i < ARRAY_SIZE(buf->dma_bufs); i++) + if (buf->dma_bufs[i]) + dma_buf_put(buf->dma_bufs[i]); + + if (buf->acquire_fence) + sync_fence_put(buf->acquire_fence); +} + +void adf_buffer_mapping_cleanup(struct adf_buffer_mapping *mapping, + struct adf_buffer *buf) +{ + /* calling adf_buffer_mapping_cleanup() is safe even if mapping is + uninitialized or partially-initialized, as long as it was + zeroed on allocation */ + size_t i; + for (i = 0; i < ARRAY_SIZE(mapping->sg_tables); i++) { + if (mapping->sg_tables[i]) + dma_buf_unmap_attachment(mapping->attachments[i], + mapping->sg_tables[i], DMA_TO_DEVICE); + if (mapping->attachments[i]) + dma_buf_detach(buf->dma_bufs[i], + mapping->attachments[i]); + } +} + +void adf_post_cleanup(struct adf_device *dev, struct adf_pending_post *post) +{ + size_t i; + + if (post->state) + dev->ops->state_free(dev, post->state); + + for (i = 0; i < post->config.n_bufs; i++) { + adf_buffer_mapping_cleanup(&post->config.mappings[i], + &post->config.bufs[i]); + adf_buffer_cleanup(&post->config.bufs[i]); + } + + kfree(post->config.custom_data); + kfree(post->config.mappings); + kfree(post->config.bufs); + kfree(post); +} + +static void adf_post_work_func(struct kthread_work *work) +{ + struct adf_device *dev = + container_of(work, struct adf_device, post_work); + struct adf_pending_post *post, *next; + struct list_head saved_list; + + mutex_lock(&dev->post_lock); + saved_list = dev->post_list; + list_replace_init(&dev->post_list, &saved_list); + mutex_unlock(&dev->post_lock); + + list_for_each_entry_safe(post, next, &saved_list, head) { + int i; + + for (i = 0; i < post->config.n_bufs; i++) { + struct sync_fence *fence = + post->config.bufs[i].acquire_fence; + if (fence) + adf_fence_wait(dev, fence); + } + + dev->ops->post(dev, &post->config, post->state); + + if (dev->ops->advance_timeline) { + dev->ops->advance_timeline(dev, &post->config, + post->state); + } else { + sw_sync_timeline_inc(dev->timeline, 1); + } + + list_del(&post->head); + if (dev->onscreen) + adf_post_cleanup(dev, dev->onscreen); + dev->onscreen = post; + } +} + +void adf_attachment_free(struct adf_attachment_list *attachment) +{ + list_del(&attachment->head); + kfree(attachment); +} + +struct adf_event_refcount *adf_obj_find_refcount(struct adf_obj *obj, + enum adf_event_type type) +{ + struct rb_root *root = &obj->event_refcount; + struct rb_node **new = &(root->rb_node), *parent = NULL; + struct adf_event_refcount *refcount; + + while (*new) { + refcount = container_of(*new, struct adf_event_refcount, node); + parent = *new; + + if (refcount->type > type) + new = &(*new)->rb_left; + else if (refcount->type < type) + new = &(*new)->rb_right; + else + return refcount; + } + + refcount = kzalloc(sizeof(*refcount), GFP_KERNEL); + if (!refcount) + return NULL; + + rb_link_node(&refcount->node, parent, new); + rb_insert_color(&refcount->node, root); + return refcount; +} + +/** + * adf_event_get - increase the refcount for an event + * + * @obj: the object that produces the event + * @type: the event type + * + * ADF will call the object's set_event() op if needed. ops are allowed + * to sleep, so adf_event_get() must NOT be called from an atomic context. + * + * Returns 0 if successful, or -%EINVAL if the object does not support the + * requested event type. + */ +int adf_event_get(struct adf_obj *obj, enum adf_event_type type) +{ + struct adf_event_refcount *refcount = adf_obj_find_refcount(obj, type); + int old_refcount; + + if (!refcount) + return -ENOMEM; + + if (!obj->ops || + !obj->ops->supports_event || + !obj->ops->set_event) + return -EINVAL; + + if (!obj->ops->supports_event(obj, type)) + return -EINVAL; + + mutex_lock(&obj->event_lock); + old_refcount = refcount->refcount++; + + if (old_refcount == 0) { + obj->ops->set_event(obj, type, true); + trace_adf_event_enable(obj, type); + } + mutex_unlock(&obj->event_lock); + + return 0; +} +EXPORT_SYMBOL(adf_event_get); + +/** + * adf_event_put - decrease the refcount for an event + * + * @obj: the object that produces the event + * @type: the event type + * + * ADF will call the object's set_event() op if needed. ops are allowed + * to sleep, so adf_event_put() must NOT be called from an atomic context. + * + * Returns 0 if successful, -%EINVAL if the object does not support the + * requested event type, or -%EALREADY if the refcount is already 0. + */ +int adf_event_put(struct adf_obj *obj, enum adf_event_type type) +{ + struct adf_event_refcount *refcount = adf_obj_find_refcount(obj, type); + int old_refcount; + int ret = 0; + + if (!refcount) + return -ENOMEM; + + if (!obj->ops || + !obj->ops->supports_event || + !obj->ops->set_event) + return -EINVAL; + + if (!obj->ops->supports_event(obj, type)) + return -EINVAL; + + mutex_lock(&obj->event_lock); + old_refcount = refcount->refcount--; + + if (WARN_ON(old_refcount == 0)) { + refcount->refcount++; + ret = -EALREADY; + } else if (old_refcount == 1) { + obj->ops->set_event(obj, type, false); + trace_adf_event_disable(obj, type); + } + mutex_unlock(&obj->event_lock); + + return ret; +} +EXPORT_SYMBOL(adf_event_put); + +/** + * adf_vsync_wait - wait for a vsync event on a display interface + * + * @intf: the display interface + * @timeout: timeout in jiffies (0 = wait indefinitely) + * + * adf_vsync_wait() may sleep, so it must NOT be called from an atomic context. + * + * This function returns -%ERESTARTSYS if it is interrupted by a signal. + * If @timeout == 0 then this function returns 0 on vsync. If @timeout > 0 then + * this function returns the number of remaining jiffies or -%ETIMEDOUT on + * timeout. + */ +int adf_vsync_wait(struct adf_interface *intf, long timeout) +{ + ktime_t timestamp; + int ret; + unsigned long flags; + + read_lock_irqsave(&intf->vsync_lock, flags); + timestamp = intf->vsync_timestamp; + read_unlock_irqrestore(&intf->vsync_lock, flags); + + adf_vsync_get(intf); + if (timeout) { + ret = wait_event_interruptible_timeout(intf->vsync_wait, + !ktime_equal(timestamp, + intf->vsync_timestamp), + msecs_to_jiffies(timeout)); + if (ret == 0 && ktime_equal(timestamp, intf->vsync_timestamp)) + ret = -ETIMEDOUT; + } else { + ret = wait_event_interruptible(intf->vsync_wait, + !ktime_equal(timestamp, + intf->vsync_timestamp)); + } + adf_vsync_put(intf); + + return ret; +} +EXPORT_SYMBOL(adf_vsync_wait); + +static void adf_event_queue(struct adf_obj *obj, struct adf_event *event) +{ + struct adf_file *file; + unsigned long flags; + + trace_adf_event(obj, event->type); + + spin_lock_irqsave(&obj->file_lock, flags); + + list_for_each_entry(file, &obj->file_list, head) + if (test_bit(event->type, file->event_subscriptions)) + adf_file_queue_event(file, event); + + spin_unlock_irqrestore(&obj->file_lock, flags); +} + +/** + * adf_event_notify - notify userspace of a driver-private event + * + * @obj: the ADF object that produced the event + * @event: the event + * + * adf_event_notify() may be called safely from an atomic context. It will + * copy @event if needed, so @event may point to a variable on the stack. + * + * Drivers must NOT call adf_event_notify() for vsync and hotplug events. + * ADF provides adf_vsync_notify() and + * adf_hotplug_notify_{connected,disconnected}() for these events. + */ +int adf_event_notify(struct adf_obj *obj, struct adf_event *event) +{ + if (WARN_ON(event->type == ADF_EVENT_VSYNC || + event->type == ADF_EVENT_HOTPLUG)) { + return -EINVAL; + } else { + adf_event_queue(obj, event); + return 0; + } +} +EXPORT_SYMBOL(adf_event_notify); + +/** + * adf_vsync_notify - notify ADF of a display interface's vsync event + * + * @intf: the display interface + * @timestamp: the time the vsync occurred + * + * adf_vsync_notify() may be called safely from an atomic context. + */ +void adf_vsync_notify(struct adf_interface *intf, ktime_t timestamp) +{ + unsigned long flags; + struct adf_vsync_event event; + + write_lock_irqsave(&intf->vsync_lock, flags); + intf->vsync_timestamp = timestamp; + write_unlock_irqrestore(&intf->vsync_lock, flags); + + wake_up_interruptible_all(&intf->vsync_wait); + + event.base.type = ADF_EVENT_VSYNC; + event.base.length = sizeof(event); + event.timestamp = ktime_to_ns(timestamp); + adf_event_queue(&intf->base, &event.base); +} +EXPORT_SYMBOL(adf_vsync_notify); + +void adf_hotplug_notify(struct adf_interface *intf, bool connected, + struct drm_mode_modeinfo *modelist, size_t n_modes) +{ + unsigned long flags; + struct adf_hotplug_event event; + struct drm_mode_modeinfo *old_modelist; + + write_lock_irqsave(&intf->hotplug_modelist_lock, flags); + old_modelist = intf->modelist; + intf->hotplug_detect = connected; + intf->modelist = modelist; + intf->n_modes = n_modes; + write_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + + kfree(old_modelist); + + event.base.length = sizeof(event); + event.base.type = ADF_EVENT_HOTPLUG; + event.connected = connected; + adf_event_queue(&intf->base, &event.base); +} + +/** + * adf_hotplug_notify_connected - notify ADF of a display interface being + * connected to a display + * + * @intf: the display interface + * @modelist: hardware modes supported by display + * @n_modes: length of modelist + * + * @modelist is copied as needed, so it may point to a variable on the stack. + * + * adf_hotplug_notify_connected() may NOT be called safely from an atomic + * context. + * + * Returns 0 on success or error code (<0) on error. + */ +int adf_hotplug_notify_connected(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes) +{ + struct drm_mode_modeinfo *modelist_copy; + + if (n_modes > ADF_MAX_MODES) + return -ENOMEM; + + modelist_copy = kzalloc(sizeof(modelist_copy[0]) * n_modes, + GFP_KERNEL); + if (!modelist_copy) + return -ENOMEM; + memcpy(modelist_copy, modelist, sizeof(modelist_copy[0]) * n_modes); + + adf_hotplug_notify(intf, true, modelist_copy, n_modes); + return 0; +} +EXPORT_SYMBOL(adf_hotplug_notify_connected); + +/** + * adf_hotplug_notify_disconnected - notify ADF of a display interface being + * disconnected from a display + * + * @intf: the display interface + * + * adf_hotplug_notify_disconnected() may be called safely from an atomic + * context. + */ +void adf_hotplug_notify_disconnected(struct adf_interface *intf) +{ + adf_hotplug_notify(intf, false, NULL, 0); +} +EXPORT_SYMBOL(adf_hotplug_notify_disconnected); + +int adf_new_id(void *ptr, struct idr *idr) +{ + while (true) { + int id; + + int err = idr_pre_get(idr, GFP_KERNEL); + if (!err) + return -ENOMEM; + + err = idr_get_new(idr, ptr, &id); + if (err == -EAGAIN) + continue; + else if (err == 0) + return id; + else + return err; + } +} + +static int adf_obj_init(struct adf_obj *obj, enum adf_obj_type type, + struct idr *idr, struct adf_device *parent, + const struct adf_obj_ops *ops, const char *fmt, va_list args) +{ + if (idr) { + int ret = adf_new_id(obj, idr); + if (ret < 0) { + pr_err("%s: allocating object id failed: %d\n", + __func__, ret); + return ret; + } + obj->id = ret; + } else { + obj->id = -1; + } + + vscnprintf(obj->name, sizeof(obj->name), fmt, args); + + obj->type = type; + obj->ops = ops; + obj->parent = parent; + mutex_init(&obj->event_lock); + obj->event_refcount = RB_ROOT; + spin_lock_init(&obj->file_lock); + INIT_LIST_HEAD(&obj->file_list); + return 0; +} + +static void adf_obj_destroy(struct adf_obj *obj) +{ + struct rb_node *node = rb_first(&obj->event_refcount); + + while (node) { + struct adf_event_refcount *refcount = + container_of(node, struct adf_event_refcount, + node); + kfree(refcount); + node = rb_first(&obj->event_refcount); + } +} + +/** + * adf_device_init - initialize ADF-internal data for a display device + * and create sysfs entries + * + * @dev: the display device + * @parent: the device's parent device + * @ops: the device's associated ops + * @fmt: formatting string for the display device's name + * + * @fmt specifies the device's sysfs filename and the name returned to + * userspace through the %ADF_GET_DEVICE_DATA ioctl. + * + * Returns 0 on success or error code (<0) on failure. + */ +int adf_device_init(struct adf_device *dev, struct device *parent, + const struct adf_device_ops *ops, const char *fmt, ...) +{ + int ret; + va_list args; + + dev->dev = parent; + idr_init(&dev->overlay_engines); + idr_init(&dev->interfaces); + + va_start(args, fmt); + ret = adf_obj_init(&dev->base, ADF_OBJ_DEVICE, NULL, dev, &ops->base, + fmt, args); + va_end(args); + if (ret < 0) + return ret; + + ret = adf_device_sysfs_init(dev); + if (ret < 0) + return ret; + + mutex_init(&dev->client_lock); + INIT_LIST_HEAD(&dev->post_list); + mutex_init(&dev->post_lock); + init_kthread_worker(&dev->post_worker); + INIT_LIST_HEAD(&dev->attached); + INIT_LIST_HEAD(&dev->attach_allowed); + + dev->post_thread = kthread_run(kthread_worker_fn, + &dev->post_worker, dev->base.name); + if (IS_ERR(dev->post_thread)) { + ret = PTR_ERR(dev->post_thread); + dev->post_thread = NULL; + + dev_err(&dev->base.dev, "failed to run config posting thread: %d\n", + ret); + goto err; + } + init_kthread_work(&dev->post_work, adf_post_work_func); + + dev->ops = ops; + return 0; + +err: + adf_device_sysfs_destroy(dev); + return ret; +} +EXPORT_SYMBOL(adf_device_init); + +/** + * adf_device_destroy - clean up ADF-internal data for a display device + * + * @dev: the display device + */ +void adf_device_destroy(struct adf_device *dev) +{ + struct adf_attachment_list *entry, *next; + + idr_remove_all(&dev->interfaces); + idr_remove_all(&dev->overlay_engines); + idr_destroy(&dev->interfaces); + idr_destroy(&dev->overlay_engines); + + flush_kthread_worker(&dev->post_worker); + + if (dev->onscreen) + adf_post_cleanup(dev, dev->onscreen); + adf_device_sysfs_destroy(dev); + list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) { + adf_attachment_free(entry); + } + list_for_each_entry_safe(entry, next, &dev->attached, head) { + adf_attachment_free(entry); + } + adf_obj_destroy(&dev->base); +} +EXPORT_SYMBOL(adf_device_destroy); + +/** + * adf_interface_init - initialize ADF-internal data for a display interface + * and create sysfs entries + * + * @intf: the display interface + * @dev: the interface's "parent" display device + * @type: interface type (see enum @adf_interface_type) + * @idx: which interface of type @type; + * e.g. interface DSI.1 -> @type=%ADF_INTF_TYPE_DSI, @idx=1 + * @flags: informational flags (bitmask of %ADF_INTF_FLAG_* values) + * @ops: the interface's associated ops + * @fmt: formatting string for the display interface's name + * + * @dev must have previously been initialized with adf_device_init(). + * + * @fmt affects the name returned to userspace through the + * %ADF_GET_INTERFACE_DATA ioctl. It does not affect the sysfs filename, + * which is derived from @dev's name. + * + * Returns 0 on success or error code (<0) on failure. + */ +int adf_interface_init(struct adf_interface *intf, struct adf_device *dev, + enum adf_interface_type type, u32 idx, u32 flags, + const struct adf_interface_ops *ops, const char *fmt, ...) +{ + int ret; + va_list args; + + if (dev->n_interfaces == ADF_MAX_INTERFACES) + return -ENOMEM; + + if (type >= ADF_INTF_MEMORY && + type <= ADF_INTF_TYPE_DEVICE_CUSTOM) + return -EINVAL; + + va_start(args, fmt); + ret = adf_obj_init(&intf->base, ADF_OBJ_INTERFACE, &dev->interfaces, + dev, ops ? &ops->base : NULL, fmt, args); + va_end(args); + if (ret < 0) + goto err; + + ret = adf_interface_sysfs_init(intf); + if (ret < 0) + goto err; + + intf->type = type; + intf->idx = idx; + intf->flags = flags; + intf->ops = ops; + init_waitqueue_head(&intf->vsync_wait); + rwlock_init(&intf->vsync_lock); + rwlock_init(&intf->hotplug_modelist_lock); + dev->n_interfaces++; + + return 0; + +err: + idr_remove(&dev->interfaces, intf->base.id); + return ret; +} +EXPORT_SYMBOL(adf_interface_init); + +/** + * adf_interface_destroy - clean up ADF-internal data for a display interface + * + * @intf: the display interface + */ +void adf_interface_destroy(struct adf_interface *intf) +{ + struct adf_device *dev = intf->base.parent; + struct adf_attachment_list *entry, *next; + + mutex_lock(&dev->client_lock); + list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) { + if (entry->attachment.interface == intf) { + adf_attachment_free(entry); + dev->n_attach_allowed--; + } + } + list_for_each_entry_safe(entry, next, &dev->attached, head) { + if (entry->attachment.interface == intf) { + if (dev->ops && dev->ops->detach) + dev->ops->detach(dev, + entry->attachment.overlay_engine, intf); + adf_attachment_free(entry); + dev->n_attached--; + } + } + kfree(intf->modelist); + adf_interface_sysfs_destroy(intf); + idr_remove(&intf->base.parent->interfaces, intf->base.id); + adf_obj_destroy(&intf->base); + dev->n_interfaces--; + mutex_unlock(&dev->client_lock); +} +EXPORT_SYMBOL(adf_interface_destroy); + +/** + * adf_overlay_engine_init - initialize ADF-internal data for an + * overlay engine and create sysfs entries + * + * @eng: the overlay engine + * @dev: the overlay engine's "parent" display device + * @ops: the overlay engine's associated ops + * @fmt: formatting string for the overlay engine's name + * + * @dev must have previously been initialized with adf_device_init(). + * + * @fmt affects the name returned to userspace through the + * %ADF_GET_OVERLAY_ENGINE_DATA ioctl. It does not affect the sysfs filename, + * which is derived from @dev's name. + * + * Returns 0 on success or error code (<0) on failure. + */ +int adf_overlay_engine_init(struct adf_overlay_engine *eng, + struct adf_device *dev, + const struct adf_overlay_engine_ops *ops, const char *fmt, ...) +{ + int ret; + va_list args; + + if (ops->n_supported_formats > ADF_MAX_SUPPORTED_FORMATS) + return -EINVAL; + + va_start(args, fmt); + ret = adf_obj_init(&eng->base, ADF_OBJ_OVERLAY_ENGINE, + &dev->overlay_engines, dev, &ops->base, fmt, args); + va_end(args); + if (ret < 0) + goto err; + + ret = adf_overlay_engine_sysfs_init(eng); + if (ret < 0) + goto err; + + eng->ops = ops; + return 0; + +err: + idr_remove(&dev->overlay_engines, eng->base.id); + return ret; +} +EXPORT_SYMBOL(adf_overlay_engine_init); + +/** + * adf_interface_destroy - clean up ADF-internal data for an overlay engine + * + * @eng: the overlay engine + */ +void adf_overlay_engine_destroy(struct adf_overlay_engine *eng) +{ + struct adf_device *dev = eng->base.parent; + struct adf_attachment_list *entry, *next; + + mutex_lock(&dev->client_lock); + list_for_each_entry_safe(entry, next, &dev->attach_allowed, head) { + if (entry->attachment.overlay_engine == eng) { + adf_attachment_free(entry); + dev->n_attach_allowed--; + } + } + list_for_each_entry_safe(entry, next, &dev->attached, head) { + if (entry->attachment.overlay_engine == eng) { + if (dev->ops && dev->ops->detach) + dev->ops->detach(dev, eng, + entry->attachment.interface); + adf_attachment_free(entry); + dev->n_attached--; + } + } + adf_overlay_engine_sysfs_destroy(eng); + idr_remove(&eng->base.parent->overlay_engines, eng->base.id); + adf_obj_destroy(&eng->base); + mutex_unlock(&dev->client_lock); +} +EXPORT_SYMBOL(adf_overlay_engine_destroy); + +struct adf_attachment_list *adf_attachment_find(struct list_head *list, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + struct adf_attachment_list *entry; + list_for_each_entry(entry, list, head) { + if (entry->attachment.interface == intf && + entry->attachment.overlay_engine == eng) + return entry; + } + return NULL; +} + +int adf_attachment_validate(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + if (intf->base.parent != dev) { + dev_err(&dev->base.dev, "can't attach interface %s belonging to device %s\n", + intf->base.name, intf->base.parent->base.name); + return -EINVAL; + } + + if (eng->base.parent != dev) { + dev_err(&dev->base.dev, "can't attach overlay engine %s belonging to device %s\n", + eng->base.name, eng->base.parent->base.name); + return -EINVAL; + } + + return 0; +} + +/** + * adf_attachment_allow - add a new entry to the list of allowed + * attachments + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + * + * adf_attachment_allow() indicates that the underlying display hardware allows + * @intf to scan out @eng's output. It is intended to be called at + * driver initialization for each supported overlay engine + interface pair. + * + * Returns 0 on success, -%EALREADY if the entry already exists, or -errno on + * any other failure. + */ +int adf_attachment_allow(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + int ret; + struct adf_attachment_list *entry = NULL; + + ret = adf_attachment_validate(dev, eng, intf); + if (ret < 0) + return ret; + + mutex_lock(&dev->client_lock); + + if (dev->n_attach_allowed == ADF_MAX_ATTACHMENTS) { + ret = -ENOMEM; + goto done; + } + + if (adf_attachment_find(&dev->attach_allowed, eng, intf)) { + ret = -EALREADY; + goto done; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto done; + } + + entry->attachment.interface = intf; + entry->attachment.overlay_engine = eng; + list_add_tail(&entry->head, &dev->attach_allowed); + dev->n_attach_allowed++; + +done: + mutex_unlock(&dev->client_lock); + if (ret < 0) + kfree(entry); + + return ret; +} + +/** + * adf_obj_type_str - string representation of an adf_obj_type + * + * @type: the object type + */ +const char *adf_obj_type_str(enum adf_obj_type type) +{ + switch (type) { + case ADF_OBJ_OVERLAY_ENGINE: + return "overlay engine"; + + case ADF_OBJ_INTERFACE: + return "interface"; + + case ADF_OBJ_DEVICE: + return "device"; + + default: + return "unknown"; + } +} +EXPORT_SYMBOL(adf_obj_type_str); + +/** + * adf_interface_type_str - string representation of an adf_interface's type + * + * @intf: the interface + */ +const char *adf_interface_type_str(struct adf_interface *intf) +{ + switch (intf->type) { + case ADF_INTF_DSI: + return "DSI"; + + case ADF_INTF_eDP: + return "eDP"; + + case ADF_INTF_DPI: + return "DPI"; + + case ADF_INTF_VGA: + return "VGA"; + + case ADF_INTF_DVI: + return "DVI"; + + case ADF_INTF_HDMI: + return "HDMI"; + + case ADF_INTF_MEMORY: + return "memory"; + + default: + if (intf->type >= ADF_INTF_TYPE_DEVICE_CUSTOM) { + if (intf->ops && intf->ops->type_str) + return intf->ops->type_str(intf); + return "custom"; + } + return "unknown"; + } +} +EXPORT_SYMBOL(adf_interface_type_str); + +/** + * adf_event_type_str - string representation of an adf_event_type + * + * @obj: ADF object that produced the event + * @type: event type + */ +const char *adf_event_type_str(struct adf_obj *obj, enum adf_event_type type) +{ + switch (type) { + case ADF_EVENT_VSYNC: + return "vsync"; + + case ADF_EVENT_HOTPLUG: + return "hotplug"; + + default: + if (type >= ADF_EVENT_DEVICE_CUSTOM) { + if (obj->ops && obj->ops->event_type_str) + return obj->ops->event_type_str(obj, type); + return "custom"; + } + return "unknown"; + } +} +EXPORT_SYMBOL(adf_event_type_str); + +/** + * adf_format_str - string representation of an ADF/DRM fourcc format + * + * @format: format fourcc + * @buf: target buffer for the format's string representation + */ +void adf_format_str(u32 format, char buf[5]) +{ + buf[0] = format & 0xFF; + buf[1] = (format >> 8) & 0xFF; + buf[2] = (format >> 16) & 0xFF; + buf[3] = (format >> 24) & 0xFF; + buf[4] = '\0'; +} +EXPORT_SYMBOL(adf_format_str); + +void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode) +{ + bool interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + + snprintf(mode->name, DRM_DISPLAY_MODE_LEN, "%dx%d%s", + mode->hdisplay, mode->vdisplay, + interlaced ? "i" : ""); +} + +static void __exit adf_exit(void); +static int __init adf_init(void) +{ + int err; + + err = adf_sysfs_init(); + if (err < 0) + return err; + + return 0; +} + +static void __exit adf_exit(void) +{ + adf_sysfs_destroy(); +} + +module_init(adf_init); +module_exit(adf_exit); diff --git a/drivers/video/adf/adf.h b/drivers/video/adf/adf.h new file mode 100644 index 0000000..acad631 --- /dev/null +++ b/drivers/video/adf/adf.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ADF_H +#define __ADF_H + +#include <linux/idr.h> +#include <linux/list.h> +#include <video/adf.h> + +#include "sync.h" + +int adf_new_id(void *ptr, struct idr *idr); + +struct adf_event_refcount { + struct rb_node node; + enum adf_event_type type; + int refcount; +}; + +void adf_buffer_cleanup(struct adf_buffer *buf); +void adf_buffer_mapping_cleanup(struct adf_buffer_mapping *mapping, + struct adf_buffer *buf); +void adf_post_cleanup(struct adf_device *dev, struct adf_pending_post *post); + +struct adf_attachment_list *adf_attachment_find(struct list_head *list, + struct adf_overlay_engine *eng, struct adf_interface *intf); +int adf_attachment_validate(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf); +void adf_attachment_free(struct adf_attachment_list *attachment); + +struct adf_event_refcount *adf_obj_find_refcount(struct adf_obj *obj, + enum adf_event_type type); + +void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode); + +#endif /* __ADF_H */ diff --git a/drivers/video/adf/adf_client.c b/drivers/video/adf/adf_client.c new file mode 100644 index 0000000..19f496b --- /dev/null +++ b/drivers/video/adf/adf_client.c @@ -0,0 +1,853 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/kthread.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include "sw_sync.h" + +#include <video/adf.h> +#include <video/adf_client.h> +#include <video/adf_format.h> + +#include "adf.h" + +static inline bool vsync_active(u8 state) +{ + return state == DRM_MODE_DPMS_ON || state == DRM_MODE_DPMS_STANDBY; +} + +/** + * adf_interface_blank - set interface's DPMS state + * + * @intf: the interface + * @state: one of %DRM_MODE_DPMS_* + * + * Returns 0 on success or -errno on failure. + */ +int adf_interface_blank(struct adf_interface *intf, u8 state) +{ + struct adf_device *dev = intf->base.parent; + u8 prev_state; + bool disable_vsync; + bool enable_vsync; + int ret = 0; + struct adf_event_refcount *vsync_refcount; + + if (!intf->ops || !intf->ops->blank) + return -ENOTTY; + + mutex_lock(&dev->client_lock); + if (state != DRM_MODE_DPMS_ON) + flush_kthread_worker(&dev->post_worker); + mutex_lock(&intf->base.event_lock); + + vsync_refcount = adf_obj_find_refcount(&intf->base, ADF_EVENT_VSYNC); + if (!vsync_refcount) { + ret = -ENOMEM; + goto done; + } + + prev_state = intf->dpms_state; + disable_vsync = vsync_active(prev_state) && + !vsync_active(state) && + vsync_refcount->refcount; + enable_vsync = !vsync_active(prev_state) && + vsync_active(state) && + vsync_refcount->refcount; + + if (prev_state == state) { + ret = -EBUSY; + goto done; + } + + if (disable_vsync) + intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC, + false); + + ret = intf->ops->blank(intf, state); + if (ret < 0) { + if (disable_vsync) + intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC, + true); + goto done; + } + + if (enable_vsync) + intf->base.ops->set_event(&intf->base, ADF_EVENT_VSYNC, + true); + + intf->dpms_state = state; +done: + mutex_unlock(&intf->base.event_lock); + mutex_unlock(&dev->client_lock); + return ret; +} +EXPORT_SYMBOL(adf_interface_blank); + +/** + * adf_interface_blank - get interface's current DPMS state + * + * @intf: the interface + * + * Returns one of %DRM_MODE_DPMS_*. + */ +u8 adf_interface_dpms_state(struct adf_interface *intf) +{ + struct adf_device *dev = intf->base.parent; + u8 dpms_state; + + mutex_lock(&dev->client_lock); + dpms_state = intf->dpms_state; + mutex_unlock(&dev->client_lock); + + return dpms_state; +} +EXPORT_SYMBOL(adf_interface_dpms_state); + +/** + * adf_interface_current_mode - get interface's current display mode + * + * @intf: the interface + * @mode: returns the current mode + */ +void adf_interface_current_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode) +{ + struct adf_device *dev = intf->base.parent; + + mutex_lock(&dev->client_lock); + memcpy(mode, &intf->current_mode, sizeof(*mode)); + mutex_unlock(&dev->client_lock); +} +EXPORT_SYMBOL(adf_interface_current_mode); + +/** + * adf_interface_modelist - get interface's modelist + * + * @intf: the interface + * @modelist: storage for the modelist (optional) + * @n_modes: length of @modelist + * + * If @modelist is not NULL, adf_interface_modelist() will copy up to @n_modes + * modelist entries into @modelist. + * + * Returns the length of the modelist. + */ +size_t adf_interface_modelist(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes) +{ + unsigned long flags; + size_t retval; + + read_lock_irqsave(&intf->hotplug_modelist_lock, flags); + if (modelist) + memcpy(modelist, intf->modelist, sizeof(modelist[0]) * + min(n_modes, intf->n_modes)); + retval = intf->n_modes; + read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + + return retval; +} +EXPORT_SYMBOL(adf_interface_modelist); + +/** + * adf_interface_set_mode - set interface's display mode + * + * @intf: the interface + * @mode: the new mode + * + * Returns 0 on success or -errno on failure. + */ +int adf_interface_set_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode) +{ + struct adf_device *dev = intf->base.parent; + int ret = 0; + + if (!intf->ops || !intf->ops->modeset) + return -ENOTTY; + + mutex_lock(&dev->client_lock); + flush_kthread_worker(&dev->post_worker); + + ret = intf->ops->modeset(intf, mode); + if (ret < 0) + goto done; + + memcpy(&intf->current_mode, mode, sizeof(*mode)); +done: + mutex_unlock(&dev->client_lock); + return ret; +} +EXPORT_SYMBOL(adf_interface_set_mode); + +/** + * adf_interface_screen_size - get size of screen connected to interface + * + * @intf: the interface + * @width_mm: returns the screen width in mm + * @height_mm: returns the screen width in mm + * + * Returns 0 on success or -errno on failure. On failure, @width_mm and + * @height_mm are both set to 0. + */ +int adf_interface_screen_size(struct adf_interface *intf, u16 *width_mm, + u16 *height_mm) +{ + int ret = intf->ops->screen_size(intf, width_mm, height_mm); + if (ret < 0) + *width_mm = *height_mm * 0; + return ret; +} +EXPORT_SYMBOL(adf_interface_screen_size); + +/** + * adf_overlay_engine_supports_format - returns whether a format is in an + * overlay engine's supported list + * + * @eng: the overlay engine + * @format: format fourcc + */ +bool adf_overlay_engine_supports_format(struct adf_overlay_engine *eng, + u32 format) +{ + size_t i; + for (i = 0; i < eng->ops->n_supported_formats; i++) + if (format == eng->ops->supported_formats[i]) + return true; + + return false; +} +EXPORT_SYMBOL(adf_overlay_engine_supports_format); + +static int adf_buffer_validate(struct adf_buffer *buf) +{ + struct adf_overlay_engine *eng = buf->overlay_engine; + struct device *dev = &eng->base.dev; + int hsub, vsub, num_planes, i; + + if (!adf_overlay_engine_supports_format(eng, buf->format)) { + char format_str[5]; + adf_format_str(buf->format, format_str); + dev_err(dev, "unsupported format %s\n", format_str); + return -EINVAL; + } + + if (!adf_format_is_standard(buf->format)) { + struct adf_device *parent = eng->base.parent; + return parent->ops->validate_custom_format(parent, buf); + } + + hsub = adf_format_horz_chroma_subsampling(buf->format); + vsub = adf_format_vert_chroma_subsampling(buf->format); + num_planes = adf_format_num_planes(buf->format); + + if (num_planes != buf->n_planes) { + char format_str[5]; + adf_format_str(buf->format, format_str); + dev_err(dev, "%u planes expected for format %s but %u planes provided\n", + num_planes, format_str, buf->n_planes); + return -EINVAL; + } + + if (buf->w == 0 || buf->w % hsub) { + dev_err(dev, "bad buffer width %u\n", buf->w); + return -EINVAL; + } + + if (buf->h == 0 || buf->w % vsub) { + dev_err(dev, "bad buffer height %u\n", buf->h); + return -EINVAL; + } + + for (i = 0; i < num_planes; i++) { + unsigned int width = buf->w / (i != 0 ? hsub : 1); + unsigned int height = buf->h / (i != 0 ? vsub : 1); + unsigned int cpp = adf_format_plane_cpp(buf->format, i); + + if ((u64) width * cpp > UINT_MAX) { + dev_err(dev, "plane %u stride %llu > UINT_MAX (width = %u, bpp = %u)\n", + i, (u64) width * cpp, width, cpp * 8); + return -ERANGE; + } + + if ((u64) height * buf->pitch[i] + buf->offset[i] > UINT_MAX) { + dev_err(dev, "plane %u size %llu > UINT_MAX (height = %u, pitch = %u, offset = %u)\n", + i, (u64) height * buf->pitch[i] + + buf->offset[i], + height, buf->pitch[i], buf->offset[i]); + return -ERANGE; + } + + if (buf->pitch[i] < buf->w * cpp) { + dev_err(dev, "plane %u pitch is shorter than buffer width (pitch = %u, width = %u, bpp = %u)\n", + i, buf->pitch[i], buf->w, cpp * 8); + return -EINVAL; + } + } + + return 0; +} + +static int adf_buffer_map(struct adf_device *dev, struct adf_buffer *buf, + struct adf_buffer_mapping *mapping) +{ + int ret = 0; + size_t i; + + for (i = 0; i < buf->n_planes; i++) { + struct dma_buf_attachment *attachment; + struct sg_table *sg_table; + + attachment = dma_buf_attach(buf->dma_bufs[i], dev->dev); + if (IS_ERR(attachment)) { + ret = PTR_ERR(attachment); + dev_err(&dev->base.dev, "attaching plane %u failed: %d\n", + i, ret); + goto done; + } + mapping->attachments[i] = attachment; + + sg_table = dma_buf_map_attachment(attachment, DMA_TO_DEVICE); + if (IS_ERR(sg_table)) { + ret = PTR_ERR(sg_table); + dev_err(&dev->base.dev, "mapping plane %u failed: %d", + i, ret); + goto done; + } else if (!sg_table) { + ret = -ENOMEM; + dev_err(&dev->base.dev, "mapping plane %u failed\n", + i); + goto done; + } + mapping->sg_tables[i] = sg_table; + } + +done: + if (ret < 0) + adf_buffer_mapping_cleanup(mapping, buf); + + return ret; +} + +static struct sync_fence *adf_sw_complete_fence(struct adf_device *dev) +{ + struct sync_pt *pt; + struct sync_fence *complete_fence; + + if (!dev->timeline) { + dev->timeline = sw_sync_timeline_create(dev->base.name); + dev->timeline_max = 1; + } + + dev->timeline_max++; + pt = sw_sync_pt_create(dev->timeline, dev->timeline_max); + complete_fence = sync_fence_create(dev->base.name, pt); + if (!complete_fence) + complete_fence = ERR_PTR(-ENOMEM); + + return complete_fence; +} + +/** + * adf_device_post - flip to a new set of buffers + * + * @dev: device targeted by the flip + * @intfs: interfaces targeted by the flip + * @n_intfs: number of targeted interfaces + * @bufs: description of buffers displayed + * @n_bufs: number of buffers displayed + * @custom_data: driver-private data + * @custom_data_size: size of driver-private data + * + * adf_device_post() will copy @intfs, @bufs, and @custom_data, so they may + * point to variables on the stack. adf_device_post() also takes its own + * reference on each of the dma-bufs in @bufs. The adf_device_post_nocopy() + * variant transfers ownership of these resources to ADF instead. + * + * On success, returns a sync fence which signals when the buffers are removed + * from the screen. On failure, returns ERR_PTR(-errno). + */ +struct sync_fence *adf_device_post(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, void *custom_data, + size_t custom_data_size) +{ + struct adf_interface **intfs_copy = NULL; + struct adf_buffer *bufs_copy = NULL; + void *custom_data_copy = NULL; + struct sync_fence *ret; + size_t i; + + intfs_copy = kzalloc(sizeof(intfs_copy[0]) * n_intfs, GFP_KERNEL); + if (!intfs_copy) + return ERR_PTR(-ENOMEM); + + bufs_copy = kzalloc(sizeof(bufs_copy[0]) * n_bufs, GFP_KERNEL); + if (!bufs_copy) { + ret = ERR_PTR(-ENOMEM); + goto err_alloc; + } + + custom_data_copy = kzalloc(custom_data_size, GFP_KERNEL); + if (!custom_data_copy) { + ret = ERR_PTR(-ENOMEM); + goto err_alloc; + } + + for (i = 0; i < n_bufs; i++) { + size_t j; + for (j = 0; j < bufs[i].n_planes; j++) + get_dma_buf(bufs[i].dma_bufs[j]); + } + + memcpy(intfs_copy, intfs, sizeof(intfs_copy[0]) * n_intfs); + memcpy(bufs_copy, bufs, sizeof(bufs_copy[0]) * n_bufs); + memcpy(custom_data_copy, custom_data, custom_data_size); + + ret = adf_device_post_nocopy(dev, intfs_copy, n_intfs, bufs_copy, + n_bufs, custom_data_copy, custom_data_size); + if (IS_ERR(ret)) + goto err_post; + + return ret; + +err_post: + for (i = 0; i < n_bufs; i++) { + size_t j; + for (j = 0; j < bufs[i].n_planes; j++) + dma_buf_put(bufs[i].dma_bufs[j]); + } +err_alloc: + kfree(custom_data_copy); + kfree(bufs_copy); + kfree(intfs_copy); + return ret; +} +EXPORT_SYMBOL(adf_device_post); + +/** + * adf_device_post_nocopy - flip to a new set of buffers + * + * adf_device_post_nocopy() has the same behavior as adf_device_post(), + * except ADF does not copy @intfs, @bufs, or @custom_data, and it does + * not take an extra reference on the dma-bufs in @bufs. + * + * @intfs, @bufs, and @custom_data must point to buffers allocated by + * kmalloc(). On success, ADF takes ownership of these buffers and the dma-bufs + * in @bufs, and will kfree()/dma_buf_put() them when they are no longer needed. + * On failure, adf_device_post_nocopy() does NOT take ownership of these + * buffers or the dma-bufs, and the caller must clean them up. + * + * adf_device_post_nocopy() is mainly intended for implementing ADF's ioctls. + * Clients may find the nocopy variant useful in limited cases, but most should + * call adf_device_post() instead. + */ +struct sync_fence *adf_device_post_nocopy(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, + void *custom_data, size_t custom_data_size) +{ + struct adf_pending_post *cfg; + struct adf_buffer_mapping *mappings; + struct sync_fence *ret; + size_t i; + int err; + + if (!dev->ops || !dev->ops->validate || !dev->ops->post) + return ERR_PTR(-ENOTTY); + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return ERR_PTR(-ENOMEM); + + mappings = kzalloc(sizeof(mappings[0]) * n_bufs, GFP_KERNEL); + if (!mappings) { + ret = ERR_PTR(-ENOMEM); + goto err_alloc; + } + + mutex_lock(&dev->client_lock); + for (i = 0; i < n_intfs; i++) { + if (intfs[i]->dpms_state != DRM_MODE_DPMS_ON) { + dev_dbg(&dev->base.dev, "skipping post because interface %s is off\n", + intfs[i]->base.name); + /* If the display is off, proceed as if the config was + posted and immediately cleared from the screen */ + ret = adf_sw_complete_fence(dev); + if (!IS_ERR(ret)) + sw_sync_timeline_inc(dev->timeline, 1); + goto err_display_off; + } + } + + for (i = 0; i < n_bufs; i++) { + err = adf_buffer_validate(&bufs[i]); + if (err < 0) { + ret = ERR_PTR(err); + goto err_buf; + } + + err = adf_buffer_map(dev, &bufs[i], &mappings[i]); + if (err < 0) { + ret = ERR_PTR(err); + goto err_buf; + } + } + + INIT_LIST_HEAD(&cfg->head); + cfg->config.n_bufs = n_bufs; + cfg->config.bufs = bufs; + cfg->config.mappings = mappings; + cfg->config.custom_data = custom_data; + cfg->config.custom_data_size = custom_data_size; + + err = dev->ops->validate(dev, &cfg->config, &cfg->state); + if (err < 0) { + ret = ERR_PTR(err); + goto err_buf; + } + + mutex_lock(&dev->post_lock); + + if (dev->ops->complete_fence) + ret = dev->ops->complete_fence(dev, &cfg->config, + cfg->state); + else + ret = adf_sw_complete_fence(dev); + + if (IS_ERR(ret)) + goto err_fence; + + list_add_tail(&cfg->head, &dev->post_list); + queue_kthread_work(&dev->post_worker, &dev->post_work); + mutex_unlock(&dev->post_lock); + mutex_unlock(&dev->client_lock); + kfree(intfs); + return ret; + +err_fence: + mutex_unlock(&dev->post_lock); + +err_buf: + for (i = 0; i < n_bufs; i++) + adf_buffer_mapping_cleanup(&mappings[i], &bufs[i]); + +err_display_off: + mutex_unlock(&dev->client_lock); + kfree(mappings); + +err_alloc: + kfree(cfg); + return ret; +} +EXPORT_SYMBOL(adf_device_post_nocopy); + +static void adf_attachment_list_to_array(struct adf_device *dev, + struct list_head *src, struct adf_attachment *dst, size_t size) +{ + struct adf_attachment_list *entry; + size_t i = 0; + + if (!dst) + return; + + list_for_each_entry(entry, src, head) { + if (i == size) + return; + dst[i] = entry->attachment; + i++; + } +} + +/** + * adf_device_attachments_allowed - get device's list of active attachments + * + * @dev: the device + * @attachments: storage for the attachment list (optional) + * @n_attachments: length of @attachments + * + * If @attachments is not NULL, adf_device_attachments() will copy up to + * @n_attachments entries into @attachments. + * + * Returns the length of the active attachment list. + */ +size_t adf_device_attachments(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments) +{ + size_t retval; + + mutex_lock(&dev->client_lock); + adf_attachment_list_to_array(dev, &dev->attached, attachments, + n_attachments); + retval = dev->n_attached; + mutex_unlock(&dev->client_lock); + + return retval; +} +EXPORT_SYMBOL(adf_device_attachments); + +/** + * adf_device_attachments_allowed - get device's list of allowed attachments + * + * @dev: the device + * @attachments: storage for the attachment list (optional) + * @n_attachments: length of @attachments + * + * If @attachments is not NULL, adf_device_attachments_allowed() will copy up to + * @n_attachments entries into @attachments. + * + * Returns the length of the allowed attachment list. + */ +size_t adf_device_attachments_allowed(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments) +{ + size_t retval; + + mutex_lock(&dev->client_lock); + adf_attachment_list_to_array(dev, &dev->attach_allowed, attachments, + n_attachments); + retval = dev->n_attach_allowed; + mutex_unlock(&dev->client_lock); + + return retval; +} +EXPORT_SYMBOL(adf_device_attachments_allowed); + +/** + * adf_device_attached - return whether an overlay engine and interface are + * attached + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + */ +bool adf_device_attached(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf) +{ + bool ret; + + mutex_lock(&dev->client_lock); + ret = adf_attachment_find(&dev->attached, eng, intf) != NULL; + mutex_unlock(&dev->client_lock); + + return ret; +} +EXPORT_SYMBOL(adf_device_attached); + +/** + * adf_device_attach_allowed - return whether the ADF device supports attaching + * an overlay engine and interface + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + */ +bool adf_device_attach_allowed(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf) +{ + bool ret; + + mutex_lock(&dev->client_lock); + ret = adf_attachment_find(&dev->attach_allowed, eng, intf) != NULL; + mutex_unlock(&dev->client_lock); + + return ret; +} +EXPORT_SYMBOL(adf_device_attach_allowed); + +/** + * adf_device_attach - attach an overlay engine to an interface + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + * + * Returns 0 on success, -%EINVAL if attaching @intf and @eng is not allowed, + * -%EALREADY if @intf and @eng are already attached, or -errno on any other + * failure. + */ +int adf_device_attach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf) +{ + int ret; + struct adf_attachment_list *attachment = NULL; + + ret = adf_attachment_validate(dev, eng, intf); + if (ret < 0) + return ret; + + mutex_lock(&dev->client_lock); + + if (dev->n_attached == ADF_MAX_ATTACHMENTS) { + ret = -ENOMEM; + goto done; + } + + if (adf_attachment_find(&dev->attach_allowed, eng, intf) == NULL) { + ret = -EINVAL; + goto done; + } + + if (adf_attachment_find(&dev->attached, eng, intf)) { + ret = -EALREADY; + goto done; + } + + if (dev->ops && dev->ops->attach) { + ret = dev->ops->attach(dev, eng, intf); + if (ret < 0) + goto done; + } + + attachment = kzalloc(sizeof(*attachment), GFP_KERNEL); + if (!attachment) { + ret = -ENOMEM; + goto done; + } + + attachment->attachment.interface = intf; + attachment->attachment.overlay_engine = eng; + list_add_tail(&attachment->head, &dev->attached); + dev->n_attached++; + +done: + mutex_unlock(&dev->client_lock); + if (ret < 0) + kfree(attachment); + + return ret; +} +EXPORT_SYMBOL(adf_device_attach); + +/** + * adf_device_detach - detach an overlay engine from an interface + * + * @dev: the parent device + * @eng: the overlay engine + * @intf: the interface + * + * Returns 0 on success, -%EINVAL if @intf and @eng are not attached, + * or -errno on any other failure. + */ +int adf_device_detach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf) +{ + int ret; + struct adf_attachment_list *attachment; + + ret = adf_attachment_validate(dev, eng, intf); + if (ret < 0) + return ret; + + mutex_lock(&dev->client_lock); + + attachment = adf_attachment_find(&dev->attached, eng, intf); + if (!attachment) { + ret = -EINVAL; + goto done; + } + + if (dev->ops && dev->ops->detach) { + ret = dev->ops->detach(dev, eng, intf); + if (ret < 0) + goto done; + } + + adf_attachment_free(attachment); + dev->n_attached--; +done: + mutex_unlock(&dev->client_lock); + return ret; +} +EXPORT_SYMBOL(adf_device_detach); + +/** + * adf_interface_simple_buffer_alloc - allocate a simple buffer + * + * @intf: target interface + * @w: width in pixels + * @h: height in pixels + * @format: format fourcc + * @dma_buf: returns the allocated buffer + * @offset: returns the byte offset of the allocated buffer's first pixel + * @pitch: returns the allocated buffer's pitch + * + * See &struct adf_simple_buffer_alloc for a description of simple buffers and + * their limitations. + * + * Returns 0 on success or -errno on failure. + */ +int adf_interface_simple_buffer_alloc(struct adf_interface *intf, u16 w, u16 h, + u32 format, struct dma_buf **dma_buf, u32 *offset, u32 *pitch) +{ + if (!intf->ops || !intf->ops->alloc_simple_buffer) + return -ENOTTY; + + if (!adf_format_is_rgb(format)) + return -EINVAL; + + return intf->ops->alloc_simple_buffer(intf, w, h, format, dma_buf, + offset, pitch); +} +EXPORT_SYMBOL(adf_interface_simple_buffer_alloc); + +/** + * adf_interface_simple_post - flip to a single buffer + * + * @intf: interface targeted by the flip + * @buf: buffer to display + * + * adf_interface_simple_post() can be used generically for simple display + * configurations, since the client does not need to provide any driver-private + * configuration data. + * + * adf_interface_simple_post() has the same copying semantics as + * adf_device_post(). + * + * On success, returns a sync fence which signals when the buffer is removed + * from the screen. On failure, returns ERR_PTR(-errno). + */ +struct sync_fence *adf_interface_simple_post(struct adf_interface *intf, + struct adf_buffer *buf) +{ + size_t custom_data_size = 0; + void *custom_data = NULL; + struct sync_fence *ret; + + if (intf->ops && intf->ops->describe_simple_post) { + int err; + + custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL); + if (!custom_data) { + ret = ERR_PTR(-ENOMEM); + goto done; + } + + err = intf->ops->describe_simple_post(intf, buf, custom_data, + &custom_data_size); + if (err < 0) { + ret = ERR_PTR(err); + goto done; + } + } + + ret = adf_device_post(intf->base.parent, &intf, 1, buf, 1, custom_data, + custom_data_size); +done: + kfree(custom_data); + return ret; +} +EXPORT_SYMBOL(adf_interface_simple_post); diff --git a/drivers/video/adf/adf_fops.c b/drivers/video/adf/adf_fops.c new file mode 100644 index 0000000..361ed42 --- /dev/null +++ b/drivers/video/adf/adf_fops.c @@ -0,0 +1,982 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/bitops.h> +#include <linux/circ_buf.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <video/adf_client.h> +#include <video/adf_format.h> + +#include "sync.h" +#include "sw_sync.h" + +#include "adf.h" +#include "adf_fops.h" +#include "adf_sysfs.h" + +#ifdef CONFIG_COMPAT +#include "adf_fops32.h" +#endif + +static int adf_obj_set_event(struct adf_obj *obj, struct adf_file *file, + struct adf_set_event __user *arg) +{ + struct adf_set_event data; + bool enabled; + unsigned long flags; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + if (!obj->ops || + !obj->ops->supports_event || + !obj->ops->set_event) + return -ENOTTY; + + if (!obj->ops->supports_event(obj, data.type)) + return -EINVAL; + + spin_lock_irqsave(&obj->file_lock, flags); + if (data.enabled) + enabled = test_and_set_bit(data.type, + file->event_subscriptions); + else + enabled = test_and_clear_bit(data.type, + file->event_subscriptions); + spin_unlock_irqrestore(&obj->file_lock, flags); + + if (data.enabled == enabled) + return -EALREADY; + + if (data.enabled) + adf_event_get(obj, data.type); + else + adf_event_put(obj, data.type); + + return 0; +} + +static int adf_obj_copy_custom_data_to_user(struct adf_obj *obj, + void __user *dst, size_t *dst_size) +{ + void *custom_data; + size_t custom_data_size; + int ret; + + if (!obj->ops || !obj->ops->custom_data) { + dev_dbg(&obj->dev, "%s: no custom_data op\n", __func__); + return 0; + } + + custom_data = kzalloc(ADF_MAX_CUSTOM_DATA_SIZE, GFP_KERNEL); + if (!custom_data) + return -ENOMEM; + + ret = obj->ops->custom_data(obj, custom_data, &custom_data_size); + if (ret < 0) + goto done; + + if (copy_to_user(dst, custom_data, min(*dst_size, custom_data_size))) { + ret = -EFAULT; + goto done; + } + *dst_size = custom_data_size; + +done: + kfree(custom_data); + return ret; +} + +static int adf_eng_get_data(struct adf_overlay_engine *eng, + struct adf_overlay_engine_data __user *arg) +{ + struct adf_device *dev = eng->base.parent; + struct adf_overlay_engine_data data; + size_t n_supported_formats; + u32 *supported_formats = NULL; + int ret = 0; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + strlcpy(data.name, eng->base.name, sizeof(data.name)); + + if (data.n_supported_formats > ADF_MAX_SUPPORTED_FORMATS) + return -ENOMEM; + + n_supported_formats = data.n_supported_formats; + data.n_supported_formats = eng->ops->n_supported_formats; + + if (n_supported_formats) { + supported_formats = kzalloc(n_supported_formats * + sizeof(supported_formats[0]), GFP_KERNEL); + if (!supported_formats) + return -ENOMEM; + } + + memcpy(supported_formats, eng->ops->supported_formats, + sizeof(u32) * min(n_supported_formats, + eng->ops->n_supported_formats)); + + mutex_lock(&dev->client_lock); + ret = adf_obj_copy_custom_data_to_user(&eng->base, arg->custom_data, + &data.custom_data_size); + mutex_unlock(&dev->client_lock); + + if (ret < 0) + goto done; + + if (copy_to_user(arg, &data, sizeof(data))) { + ret = -EFAULT; + goto done; + } + + if (supported_formats && copy_to_user(arg->supported_formats, + supported_formats, + n_supported_formats * sizeof(supported_formats[0]))) + ret = -EFAULT; + +done: + kfree(supported_formats); + return ret; +} + +static int adf_buffer_import(struct adf_device *dev, + struct adf_buffer_config __user *cfg, struct adf_buffer *buf) +{ + struct adf_buffer_config user_buf; + size_t i; + int ret = 0; + + if (copy_from_user(&user_buf, cfg, sizeof(user_buf))) + return -EFAULT; + + memset(buf, 0, sizeof(*buf)); + + if (user_buf.n_planes > ADF_MAX_PLANES) { + dev_err(&dev->base.dev, "invalid plane count %u\n", + user_buf.n_planes); + return -EINVAL; + } + + buf->overlay_engine = idr_find(&dev->overlay_engines, + user_buf.overlay_engine); + if (!buf->overlay_engine) { + dev_err(&dev->base.dev, "invalid overlay engine id %u\n", + user_buf.overlay_engine); + return -ENOENT; + } + + buf->w = user_buf.w; + buf->h = user_buf.h; + buf->format = user_buf.format; + for (i = 0; i < user_buf.n_planes; i++) { + buf->dma_bufs[i] = dma_buf_get(user_buf.fd[i]); + if (IS_ERR(buf->dma_bufs[i])) { + ret = PTR_ERR(buf->dma_bufs[i]); + dev_err(&dev->base.dev, "importing dma_buf fd %llu failed: %d\n", + user_buf.fd[i], ret); + buf->dma_bufs[i] = NULL; + goto done; + } + buf->offset[i] = user_buf.offset[i]; + buf->pitch[i] = user_buf.pitch[i]; + } + buf->n_planes = user_buf.n_planes; + + if (user_buf.acquire_fence >= 0) { + buf->acquire_fence = sync_fence_fdget(user_buf.acquire_fence); + if (!buf->acquire_fence) { + dev_err(&dev->base.dev, "getting fence fd %lld failed\n", + user_buf.acquire_fence); + ret = -EINVAL; + goto done; + } + } + +done: + if (ret < 0) + adf_buffer_cleanup(buf); + return ret; +} + +static int adf_device_post_config(struct adf_device *dev, + struct adf_post_config __user *arg) +{ + struct sync_fence *complete_fence; + int complete_fence_fd; + struct adf_buffer *bufs = NULL; + struct adf_interface **intfs = NULL; + size_t n_intfs, n_bufs, i; + void *custom_data = NULL; + size_t custom_data_size; + int ret = 0; + + complete_fence_fd = get_unused_fd(); + if (complete_fence_fd < 0) + return complete_fence_fd; + + if (get_user(n_intfs, &arg->n_interfaces)) { + ret = -EFAULT; + goto err_get_user; + } + + if (n_intfs > ADF_MAX_INTERFACES) { + ret = -ENOMEM; + goto err_get_user; + } + + if (get_user(n_bufs, &arg->n_bufs)) { + ret = -EFAULT; + goto err_get_user; + } + + if (n_bufs > ADF_MAX_BUFFERS) { + ret = -ENOMEM; + goto err_get_user; + } + + if (get_user(custom_data_size, &arg->custom_data_size)) { + ret = -EFAULT; + goto err_get_user; + } + + if (custom_data_size > ADF_MAX_CUSTOM_DATA_SIZE) { + ret = -ENOMEM; + goto err_get_user; + } + + if (n_intfs) { + intfs = kzalloc(sizeof(intfs[0]) * n_intfs, GFP_KERNEL); + if (!intfs) { + ret = -ENOMEM; + goto err_get_user; + } + } + + for (i = 0; i < n_intfs; i++) { + u32 intf_id; + if (get_user(intf_id, &arg->interfaces[i])) { + ret = -EFAULT; + goto err_get_user; + } + + intfs[i] = idr_find(&dev->interfaces, intf_id); + if (!intfs[i]) { + ret = -EINVAL; + goto err_get_user; + } + } + + if (n_bufs) { + bufs = kzalloc(sizeof(bufs[0]) * n_bufs, GFP_KERNEL); + if (!bufs) { + ret = -ENOMEM; + goto err_get_user; + } + } + + for (i = 0; i < n_bufs; i++) { + ret = adf_buffer_import(dev, &arg->bufs[i], &bufs[i]); + if (ret < 0) { + memset(&bufs[i], 0, sizeof(bufs[i])); + goto err_import; + } + } + + if (custom_data_size) { + custom_data = kzalloc(custom_data_size, GFP_KERNEL); + if (!custom_data) { + ret = -ENOMEM; + goto err_import; + } + + if (copy_from_user(custom_data, arg->custom_data, + custom_data_size)) { + ret = -EFAULT; + goto err_import; + } + } + + if (put_user(complete_fence_fd, &arg->complete_fence)) { + ret = -EFAULT; + goto err_import; + } + + complete_fence = adf_device_post_nocopy(dev, intfs, n_intfs, bufs, + n_bufs, custom_data, custom_data_size); + if (IS_ERR(complete_fence)) { + ret = PTR_ERR(complete_fence); + goto err_import; + } + + sync_fence_install(complete_fence, complete_fence_fd); + return 0; + +err_import: + for (i = 0; i < n_bufs; i++) + adf_buffer_cleanup(&bufs[i]); + +err_get_user: + kfree(custom_data); + kfree(bufs); + kfree(intfs); + put_unused_fd(complete_fence_fd); + return ret; +} + +static int adf_intf_simple_post_config(struct adf_interface *intf, + struct adf_simple_post_config __user *arg) +{ + struct adf_device *dev = intf->base.parent; + struct sync_fence *complete_fence; + int complete_fence_fd; + struct adf_buffer buf; + int ret = 0; + + complete_fence_fd = get_unused_fd(); + if (complete_fence_fd < 0) + return complete_fence_fd; + + ret = adf_buffer_import(dev, &arg->buf, &buf); + if (ret < 0) + goto err_import; + + if (put_user(complete_fence_fd, &arg->complete_fence)) { + ret = -EFAULT; + goto err_put_user; + } + + complete_fence = adf_interface_simple_post(intf, &buf); + if (IS_ERR(complete_fence)) { + ret = PTR_ERR(complete_fence); + goto err_put_user; + } + + sync_fence_install(complete_fence, complete_fence_fd); + return 0; + +err_put_user: + adf_buffer_cleanup(&buf); +err_import: + put_unused_fd(complete_fence_fd); + return ret; +} + +static int adf_intf_simple_buffer_alloc(struct adf_interface *intf, + struct adf_simple_buffer_alloc __user *arg) +{ + struct adf_simple_buffer_alloc data; + struct dma_buf *dma_buf; + int ret = 0; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + data.fd = get_unused_fd_flags(O_CLOEXEC); + if (data.fd < 0) + return data.fd; + + ret = adf_interface_simple_buffer_alloc(intf, data.w, data.h, + data.format, &dma_buf, &data.offset, &data.pitch); + if (ret < 0) + goto err_alloc; + + if (copy_to_user(arg, &data, sizeof(*arg))) { + ret = -EFAULT; + goto err_copy; + } + + fd_install(data.fd, dma_buf->file); + return 0; + +err_copy: + dma_buf_put(dma_buf); + +err_alloc: + put_unused_fd(data.fd); + return ret; +} + +static int adf_copy_attachment_list_to_user(struct adf_attachment_config __user *to, + size_t n_to, struct adf_attachment *from, size_t n_from) +{ + struct adf_attachment_config *temp; + size_t n = min(n_to, n_from); + size_t i; + int ret = 0; + + if (!n) + return 0; + + temp = kzalloc(n * sizeof(temp[0]), GFP_KERNEL); + if (!temp) + return -ENOMEM; + + for (i = 0; i < n; i++) { + temp[i].interface = from[i].interface->base.id; + temp[i].overlay_engine = from[i].overlay_engine->base.id; + } + + if (copy_to_user(to, temp, n * sizeof(to[0]))) { + ret = -EFAULT; + goto done; + } + +done: + kfree(temp); + return ret; +} + +static int adf_device_get_data(struct adf_device *dev, + struct adf_device_data __user *arg) +{ + struct adf_device_data data; + size_t n_attach; + struct adf_attachment *attach = NULL; + size_t n_allowed_attach; + struct adf_attachment *allowed_attach = NULL; + int ret = 0; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + if (data.n_attachments > ADF_MAX_ATTACHMENTS || + data.n_allowed_attachments > ADF_MAX_ATTACHMENTS) + return -ENOMEM; + + strlcpy(data.name, dev->base.name, sizeof(data.name)); + + if (data.n_attachments) { + attach = kzalloc(data.n_attachments * sizeof(attach[0]), + GFP_KERNEL); + if (!attach) + return -ENOMEM; + } + n_attach = adf_device_attachments(dev, attach, data.n_attachments); + + if (data.n_allowed_attachments) { + allowed_attach = kzalloc(data.n_allowed_attachments * + sizeof(allowed_attach[0]), GFP_KERNEL); + if (!allowed_attach) { + ret = -ENOMEM; + goto done; + } + } + n_allowed_attach = adf_device_attachments_allowed(dev, allowed_attach, + data.n_allowed_attachments); + + mutex_lock(&dev->client_lock); + ret = adf_obj_copy_custom_data_to_user(&dev->base, arg->custom_data, + &data.custom_data_size); + mutex_unlock(&dev->client_lock); + + if (ret < 0) + goto done; + + ret = adf_copy_attachment_list_to_user(arg->attachments, + data.n_attachments, attach, n_attach); + if (ret < 0) + goto done; + + ret = adf_copy_attachment_list_to_user(arg->allowed_attachments, + data.n_allowed_attachments, allowed_attach, + n_allowed_attach); + if (ret < 0) + goto done; + + data.n_attachments = n_attach; + data.n_allowed_attachments = n_allowed_attach; + + if (copy_to_user(arg, &data, sizeof(data))) + ret = -EFAULT; + +done: + kfree(allowed_attach); + kfree(attach); + return ret; +} + +static int adf_device_handle_attachment(struct adf_device *dev, + struct adf_attachment_config __user *arg, bool attach) +{ + struct adf_attachment_config data; + struct adf_overlay_engine *eng; + struct adf_interface *intf; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + eng = idr_find(&dev->overlay_engines, data.overlay_engine); + if (!eng) { + dev_err(&dev->base.dev, "invalid overlay engine id %u\n", + data.overlay_engine); + return -EINVAL; + } + + intf = idr_find(&dev->interfaces, data.interface); + if (!intf) { + dev_err(&dev->base.dev, "invalid interface id %u\n", + data.interface); + return -EINVAL; + } + + if (attach) + return adf_device_attach(dev, eng, intf); + else + return adf_device_detach(dev, eng, intf); +} + +static int adf_intf_set_mode(struct adf_interface *intf, + struct drm_mode_modeinfo __user *arg) +{ + struct drm_mode_modeinfo mode; + + if (copy_from_user(&mode, arg, sizeof(mode))) + return -EFAULT; + + return adf_interface_set_mode(intf, &mode); +} + +static int adf_intf_get_data(struct adf_interface *intf, + struct adf_interface_data __user *arg) +{ + struct adf_device *dev = intf->base.parent; + struct adf_interface_data data; + int ret = 0; + unsigned long flags; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + strlcpy(data.name, intf->base.name, sizeof(data.name)); + + data.type = intf->type; + data.id = intf->idx; + data.flags = intf->flags; + + adf_interface_screen_size(intf, &data.width_mm, &data.height_mm); + + mutex_lock(&dev->client_lock); + read_lock_irqsave(&intf->hotplug_modelist_lock, flags); + data.hotplug_detect = intf->hotplug_detect; + if (copy_to_user(arg->available_modes, intf->modelist, + min(data.n_available_modes, intf->n_modes) * + sizeof(intf->modelist[0]))) + ret = -EFAULT; + data.n_available_modes = intf->n_modes; + read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + + if (ret < 0) + goto done; + + data.dpms_state = intf->dpms_state; + read_lock_irqsave(&intf->hotplug_modelist_lock, flags); + data.hotplug_detect = intf->hotplug_detect; + read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + memcpy(&data.current_mode, &intf->current_mode, + sizeof(intf->current_mode)); + + ret = adf_obj_copy_custom_data_to_user(&intf->base, arg->custom_data, + &data.custom_data_size); +done: + mutex_unlock(&dev->client_lock); + + if (ret < 0) + return ret; + + if (copy_to_user(arg, &data, sizeof(data))) + ret = -EFAULT; + + return ret; +} + +static long adf_overlay_engine_ioctl(struct adf_overlay_engine *eng, + struct adf_file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case ADF_SET_EVENT: + return adf_obj_set_event(&eng->base, file, + (struct adf_set_event __user *)arg); + + case ADF_GET_OVERLAY_ENGINE_DATA: + return adf_eng_get_data(eng, + (struct adf_overlay_engine_data __user *)arg); + + case ADF_BLANK: + case ADF_POST_CONFIG: + case ADF_SET_MODE: + case ADF_GET_INTERFACE_DATA: + case ADF_SIMPLE_POST_CONFIG: + case ADF_SIMPLE_BUFFER_ALLOC: + case ADF_ATTACH: + case ADF_DETACH: + return -EINVAL; + + default: + if (eng->base.ops && eng->base.ops->ioctl) + return eng->base.ops->ioctl(&eng->base, cmd, arg); + return -ENOTTY; + } +} + +static long adf_interface_ioctl(struct adf_interface *intf, + struct adf_file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case ADF_SET_EVENT: + return adf_obj_set_event(&intf->base, file, + (struct adf_set_event __user *)arg); + + case ADF_BLANK: + return adf_interface_blank(intf, arg); + + case ADF_SET_MODE: + return adf_intf_set_mode(intf, + (struct drm_mode_modeinfo __user *)arg); + + case ADF_GET_INTERFACE_DATA: + return adf_intf_get_data(intf, + (struct adf_interface_data __user *)arg); + + case ADF_SIMPLE_POST_CONFIG: + return adf_intf_simple_post_config(intf, + (struct adf_simple_post_config __user *)arg); + + case ADF_SIMPLE_BUFFER_ALLOC: + return adf_intf_simple_buffer_alloc(intf, + (struct adf_simple_buffer_alloc __user *)arg); + + case ADF_POST_CONFIG: + case ADF_GET_OVERLAY_ENGINE_DATA: + case ADF_ATTACH: + case ADF_DETACH: + return -EINVAL; + + default: + if (intf->base.ops && intf->base.ops->ioctl) + return intf->base.ops->ioctl(&intf->base, cmd, arg); + return -ENOTTY; + } +} + +static long adf_device_ioctl(struct adf_device *dev, struct adf_file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case ADF_SET_EVENT: + return adf_obj_set_event(&dev->base, file, + (struct adf_set_event __user *)arg); + + case ADF_POST_CONFIG: + return adf_device_post_config(dev, + (struct adf_post_config __user *)arg); + + case ADF_GET_DEVICE_DATA: + return adf_device_get_data(dev, + (struct adf_device_data __user *)arg); + + case ADF_ATTACH: + return adf_device_handle_attachment(dev, + (struct adf_attachment_config __user *)arg, + true); + + case ADF_DETACH: + return adf_device_handle_attachment(dev, + (struct adf_attachment_config __user *)arg, + false); + + case ADF_BLANK: + case ADF_SET_MODE: + case ADF_GET_INTERFACE_DATA: + case ADF_GET_OVERLAY_ENGINE_DATA: + case ADF_SIMPLE_POST_CONFIG: + case ADF_SIMPLE_BUFFER_ALLOC: + return -EINVAL; + + default: + if (dev->base.ops && dev->base.ops->ioctl) + return dev->base.ops->ioctl(&dev->base, cmd, arg); + return -ENOTTY; + } +} + +static int adf_file_open(struct inode *inode, struct file *file) +{ + struct adf_obj *obj; + struct adf_file *fpriv = NULL; + unsigned long flags; + int ret = 0; + + obj = adf_obj_sysfs_find(iminor(inode)); + if (!obj) + return -ENODEV; + + dev_dbg(&obj->dev, "opening %s\n", dev_name(&obj->dev)); + + if (!try_module_get(obj->parent->ops->owner)) { + dev_err(&obj->dev, "getting owner module failed\n"); + return -ENODEV; + } + + fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL); + if (!fpriv) { + ret = -ENOMEM; + goto done; + } + + INIT_LIST_HEAD(&fpriv->head); + fpriv->obj = obj; + init_waitqueue_head(&fpriv->event_wait); + + file->private_data = fpriv; + + if (obj->ops && obj->ops->open) { + ret = obj->ops->open(obj, inode, file); + if (ret < 0) + goto done; + } + + spin_lock_irqsave(&obj->file_lock, flags); + list_add_tail(&fpriv->head, &obj->file_list); + spin_unlock_irqrestore(&obj->file_lock, flags); + +done: + if (ret < 0) { + kfree(fpriv); + module_put(obj->parent->ops->owner); + } + return ret; +} + +static int adf_file_release(struct inode *inode, struct file *file) +{ + struct adf_file *fpriv = file->private_data; + struct adf_obj *obj = fpriv->obj; + enum adf_event_type event_type; + unsigned long flags; + int ret = 0; + + if (obj->ops && obj->ops->release) { + ret = obj->ops->release(obj, inode, file); + if (ret < 0) + goto done; + } + + spin_lock_irqsave(&obj->file_lock, flags); + list_del(&fpriv->head); + spin_unlock_irqrestore(&obj->file_lock, flags); + + for_each_set_bit(event_type, fpriv->event_subscriptions, + ADF_EVENT_TYPE_MAX) { + adf_event_put(obj, event_type); + } + + kfree(fpriv); + module_put(obj->parent->ops->owner); + + dev_dbg(&obj->dev, "released %s\n", dev_name(&obj->dev)); +done: + return ret; +} + +long adf_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct adf_file *fpriv = file->private_data; + struct adf_obj *obj = fpriv->obj; + long ret = -EINVAL; + + dev_dbg(&obj->dev, "%s ioctl %u\n", dev_name(&obj->dev), _IOC_NR(cmd)); + + switch (obj->type) { + case ADF_OBJ_OVERLAY_ENGINE: + ret = adf_overlay_engine_ioctl(adf_obj_to_overlay_engine(obj), + fpriv, cmd, arg); + break; + + case ADF_OBJ_INTERFACE: + ret = adf_interface_ioctl(adf_obj_to_interface(obj), fpriv, cmd, + arg); + break; + + case ADF_OBJ_DEVICE: + ret = adf_device_ioctl(adf_obj_to_device(obj), fpriv, cmd, arg); + break; + } + + return ret; +} + +static inline bool adf_file_event_available(struct adf_file *fpriv) +{ + int head = fpriv->event_head; + int tail = fpriv->event_tail; + return CIRC_CNT(head, tail, sizeof(fpriv->event_buf)) != 0; +} + +void adf_file_queue_event(struct adf_file *fpriv, struct adf_event *event) +{ + int head = fpriv->event_head; + int tail = fpriv->event_tail; + size_t space = CIRC_SPACE(head, tail, sizeof(fpriv->event_buf)); + size_t space_to_end = + CIRC_SPACE_TO_END(head, tail, sizeof(fpriv->event_buf)); + + if (space < event->length) { + dev_dbg(&fpriv->obj->dev, + "insufficient buffer space for event %u\n", + event->type); + return; + } + + if (space_to_end >= event->length) { + memcpy(fpriv->event_buf + head, event, event->length); + } else { + memcpy(fpriv->event_buf + head, event, space_to_end); + memcpy(fpriv->event_buf, (u8 *)event + space_to_end, + event->length - space_to_end); + } + + smp_wmb(); + fpriv->event_head = (fpriv->event_head + event->length) & + (sizeof(fpriv->event_buf) - 1); + wake_up_interruptible_all(&fpriv->event_wait); +} + +static int adf_file_event_copy_to_user(struct adf_file *fpriv, + char __user *buffer, size_t size) +{ + int head = fpriv->event_head; + int tail = fpriv->event_tail; + size_t cnt_to_end = CIRC_CNT_TO_END(head, tail, + sizeof(fpriv->event_buf)); + + if (cnt_to_end >= size) { + return copy_to_user(buffer, fpriv->event_buf + tail, size); + } else { + int ret = copy_to_user(buffer, fpriv->event_buf + tail, + cnt_to_end); + if (ret) + return ret; + return copy_to_user(buffer + cnt_to_end, fpriv->event_buf, + size - cnt_to_end); + } +} + +static void adf_file_event_peek(struct adf_file *fpriv, + struct adf_event *e) +{ + int head = fpriv->event_head; + int tail = fpriv->event_tail; + size_t cnt_to_end = CIRC_CNT_TO_END(head, tail, + sizeof(fpriv->event_buf)); + size_t size = sizeof(*e); + + if (cnt_to_end >= size) { + memcpy(e, fpriv->event_buf + tail, size); + } else { + memcpy(e, fpriv->event_buf + tail, cnt_to_end); + memcpy((u8 *)e + cnt_to_end, fpriv->event_buf, + size - cnt_to_end); + } +} + +static ssize_t adf_file_dequeue_event(struct adf_file *fpriv, + char __user *buffer, size_t available) +{ + struct adf_event peeked; + unsigned long flags; + ssize_t ret = 0; + + spin_lock_irqsave(&fpriv->obj->file_lock, flags); + + if (!adf_file_event_available(fpriv)) + goto out; + + adf_file_event_peek(fpriv, &peeked); + if (peeked.length > available) + goto out; + + ret = adf_file_event_copy_to_user(fpriv, buffer, peeked.length); + if (ret < 0) { + ret = -EFAULT; + goto out; + } + + fpriv->event_tail = (fpriv->event_tail + peeked.length) & + (sizeof(fpriv->event_buf) - 1); + ret = peeked.length; + +out: + spin_unlock_irqrestore(&fpriv->obj->file_lock, flags); + return ret; +} + +ssize_t adf_file_read(struct file *filp, char __user *buffer, + size_t count, loff_t *offset) +{ + struct adf_file *fpriv = filp->private_data; + ssize_t ret = 0; + int err; + + err = wait_event_interruptible(fpriv->event_wait, + adf_file_event_available(fpriv)); + if (err < 0) + return err; + + while (count) { + ssize_t size = adf_file_dequeue_event(fpriv, buffer, count); + if (size < 0) + return size; + if (size == 0) + break; + + buffer += size; + count -= size; + ret += size; + } + + return ret; +} + +unsigned int adf_file_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct adf_file *fpriv = filp->private_data; + unsigned int mask = 0; + + poll_wait(filp, &fpriv->event_wait, wait); + + if (adf_file_event_available(fpriv)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +const struct file_operations adf_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = adf_file_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = adf_file_compat_ioctl, +#endif + .open = adf_file_open, + .release = adf_file_release, + .llseek = default_llseek, + .read = adf_file_read, + .poll = adf_file_poll, +}; diff --git a/drivers/video/adf/adf_fops.h b/drivers/video/adf/adf_fops.h new file mode 100644 index 0000000..2458c63 --- /dev/null +++ b/drivers/video/adf/adf_fops.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ADF_FOPS_H +#define __ADF_FOPS_H + +#include <linux/bitmap.h> +#include <linux/fs.h> + +extern const struct file_operations adf_fops; + +struct adf_file { + struct list_head head; + struct adf_obj *obj; + + DECLARE_BITMAP(event_subscriptions, ADF_EVENT_TYPE_MAX); + u8 event_buf[4096]; + int event_head; + int event_tail; + wait_queue_head_t event_wait; +}; + +void adf_file_queue_event(struct adf_file *file, struct adf_event *event); +long adf_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg); + +#endif /* __ADF_FOPS_H */ diff --git a/drivers/video/adf/adf_fops32.c b/drivers/video/adf/adf_fops32.c new file mode 100644 index 0000000..60a47cf --- /dev/null +++ b/drivers/video/adf/adf_fops32.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/uaccess.h> +#include <video/adf.h> + +#include "adf_fops.h" +#include "adf_fops32.h" + +long adf_compat_post_config(struct file *file, + struct adf_post_config32 __user *arg) +{ + struct adf_post_config32 cfg32; + struct adf_post_config __user *cfg; + int ret; + + if (copy_from_user(&cfg32, arg, sizeof(cfg32))) + return -EFAULT; + + cfg = compat_alloc_user_space(sizeof(*cfg)); + if (!access_ok(VERIFY_WRITE, cfg, sizeof(*cfg))) + return -EFAULT; + + if (put_user(cfg32.n_interfaces, &cfg->n_interfaces) || + put_user(compat_ptr(cfg32.interfaces), + &cfg->interfaces) || + put_user(cfg32.n_bufs, &cfg->n_bufs) || + put_user(compat_ptr(cfg32.bufs), &cfg->bufs) || + put_user(cfg32.custom_data_size, + &cfg->custom_data_size) || + put_user(compat_ptr(cfg32.custom_data), + &cfg->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_POST_CONFIG, (unsigned long)cfg); + if (ret < 0) + return ret; + + if (copy_in_user(&arg->complete_fence, &cfg->complete_fence, + sizeof(cfg->complete_fence))) + return -EFAULT; + + return 0; +} + +long adf_compat_get_device_data(struct file *file, + struct adf_device_data32 __user *arg) +{ + struct adf_device_data32 data32; + struct adf_device_data __user *data; + int ret; + + if (copy_from_user(&data32, arg, sizeof(data32))) + return -EFAULT; + + data = compat_alloc_user_space(sizeof(*data)); + if (!access_ok(VERIFY_WRITE, data, sizeof(*data))) + return -EFAULT; + + if (put_user(data32.n_attachments, &data->n_attachments) || + put_user(compat_ptr(data32.attachments), + &data->attachments) || + put_user(data32.n_allowed_attachments, + &data->n_allowed_attachments) || + put_user(compat_ptr(data32.allowed_attachments), + &data->allowed_attachments) || + put_user(data32.custom_data_size, + &data->custom_data_size) || + put_user(compat_ptr(data32.custom_data), + &data->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_GET_DEVICE_DATA32, (unsigned long)data); + if (ret < 0) + return ret; + + if (copy_in_user(arg->name, data->name, sizeof(arg->name)) || + copy_in_user(&arg->n_attachments, &data->n_attachments, + sizeof(arg->n_attachments)) || + copy_in_user(&arg->n_allowed_attachments, + &data->n_allowed_attachments, + sizeof(arg->n_allowed_attachments)) || + copy_in_user(&arg->custom_data_size, + &data->custom_data_size, + sizeof(arg->custom_data_size))) + return -EFAULT; + + return 0; +} + +long adf_compat_get_interface_data(struct file *file, + struct adf_interface_data32 __user *arg) +{ + struct adf_interface_data32 data32; + struct adf_interface_data __user *data; + int ret; + + if (copy_from_user(&data32, arg, sizeof(data32))) + return -EFAULT; + + data = compat_alloc_user_space(sizeof(*data)); + if (!access_ok(VERIFY_WRITE, data, sizeof(*data))) + return -EFAULT; + + if (put_user(data32.n_available_modes, &data->n_available_modes) || + put_user(compat_ptr(data32.available_modes), + &data->available_modes) || + put_user(data32.custom_data_size, + &data->custom_data_size) || + put_user(compat_ptr(data32.custom_data), + &data->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_GET_DEVICE_DATA32, (unsigned long)data); + if (ret < 0) + return ret; + + if (copy_in_user(arg->name, data->name, sizeof(arg->name)) || + copy_in_user(&arg->type, &data->type, + sizeof(arg->type)) || + copy_in_user(&arg->id, &data->id, sizeof(arg->id)) || + copy_in_user(&arg->flags, &data->flags, + sizeof(arg->flags)) || + copy_in_user(&arg->dpms_state, &data->dpms_state, + sizeof(arg->dpms_state)) || + copy_in_user(&arg->hotplug_detect, + &data->hotplug_detect, + sizeof(arg->hotplug_detect)) || + copy_in_user(&arg->width_mm, &data->width_mm, + sizeof(arg->width_mm)) || + copy_in_user(&arg->height_mm, &data->height_mm, + sizeof(arg->height_mm)) || + copy_in_user(&arg->current_mode, &data->current_mode, + sizeof(arg->current_mode)) || + copy_in_user(&arg->n_available_modes, + &data->n_available_modes, + sizeof(arg->n_available_modes)) || + copy_in_user(&arg->custom_data_size, + &data->custom_data_size, + sizeof(arg->custom_data_size))) + return -EFAULT; + + return 0; +} + +long adf_compat_get_overlay_engine_data(struct file *file, + struct adf_overlay_engine_data32 __user *arg) +{ + struct adf_overlay_engine_data32 data32; + struct adf_overlay_engine_data __user *data; + int ret; + + if (copy_from_user(&data32, arg, sizeof(data32))) + return -EFAULT; + + data = compat_alloc_user_space(sizeof(*data)); + if (!access_ok(VERIFY_WRITE, data, sizeof(*data))) + return -EFAULT; + + if (put_user(data32.n_supported_formats, &data->n_supported_formats) || + put_user(compat_ptr(data32.supported_formats), + &data->supported_formats) || + put_user(data32.custom_data_size, + &data->custom_data_size) || + put_user(compat_ptr(data32.custom_data), + &data->custom_data)) + return -EFAULT; + + ret = adf_file_ioctl(file, ADF_GET_OVERLAY_ENGINE_DATA, + (unsigned long)data); + if (ret < 0) + return ret; + + if (copy_in_user(arg->name, data->name, sizeof(arg->name)) || + copy_in_user(&arg->n_supported_formats, + &data->n_supported_formats, + sizeof(arg->n_supported_formats)) || + copy_in_user(&arg->custom_data_size, + &data->custom_data_size, + sizeof(arg->custom_data_size))) + return -EFAULT; + + return 0; +} + +long adf_file_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case ADF_POST_CONFIG32: + return adf_compat_post_config(file, compat_ptr(arg)); + + case ADF_GET_DEVICE_DATA32: + return adf_compat_get_device_data(file, compat_ptr(arg)); + + case ADF_GET_INTERFACE_DATA32: + return adf_compat_get_interface_data(file, compat_ptr(arg)); + + case ADF_GET_OVERLAY_ENGINE_DATA32: + return adf_compat_get_overlay_engine_data(file, + compat_ptr(arg)); + + default: + return adf_file_ioctl(file, cmd, arg); + } +} diff --git a/drivers/video/adf/adf_fops32.h b/drivers/video/adf/adf_fops32.h new file mode 100644 index 0000000..36c2c56 --- /dev/null +++ b/drivers/video/adf/adf_fops32.h @@ -0,0 +1,78 @@ +#ifndef __ADF_FOPS32_H +#define __ADF_FOPS32_H + +#include <linux/compat.h> +#include <linux/ioctl.h> + +#include <video/adf.h> + +#define ADF_POST_CONFIG32 \ + _IOW('D', 2, struct adf_post_config32) +#define ADF_GET_DEVICE_DATA32 \ + _IOR('D', 4, struct adf_device_data32) +#define ADF_GET_INTERFACE_DATA32 \ + _IOR('D', 5, struct adf_interface_data32) +#define ADF_GET_OVERLAY_ENGINE_DATA32 \ + _IOR('D', 6, struct adf_overlay_engine_data32) + +struct adf_post_config32 { + compat_size_t n_interfaces; + compat_uptr_t interfaces; + + compat_size_t n_bufs; + compat_uptr_t bufs; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; + + __s64 complete_fence; +}; + +struct adf_device_data32 { + char name[ADF_NAME_LEN]; + + compat_size_t n_attachments; + compat_uptr_t attachments; + + compat_size_t n_allowed_attachments; + compat_uptr_t allowed_attachments; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; +}; + +struct adf_interface_data32 { + char name[ADF_NAME_LEN]; + + __u8 type; + __u32 id; + /* e.g. type=ADF_INTF_TYPE_DSI, id=1 => DSI.1 */ + __u32 flags; + + __u8 dpms_state; + __u8 hotplug_detect; + __u16 width_mm; + __u16 height_mm; + + struct drm_mode_modeinfo current_mode; + compat_size_t n_available_modes; + compat_uptr_t available_modes; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; +}; + +struct adf_overlay_engine_data32 { + char name[ADF_NAME_LEN]; + + compat_size_t n_supported_formats; + compat_uptr_t supported_formats; + + compat_size_t custom_data_size; + compat_uptr_t custom_data; +}; + +long adf_file_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); + +#endif /* __ADF_FOPS32_H */ diff --git a/drivers/video/adf/adf_sysfs.c b/drivers/video/adf/adf_sysfs.c new file mode 100644 index 0000000..8ce80e7 --- /dev/null +++ b/drivers/video/adf/adf_sysfs.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <video/adf_client.h> + +#include "adf.h" +#include "adf_fops.h" +#include "adf_sysfs.h" + +static struct class *adf_class; +static int adf_major; +static DEFINE_IDR(adf_minors); + +#define dev_to_adf_interface(p) \ + adf_obj_to_interface(container_of(p, struct adf_obj, dev)) + +static ssize_t dpms_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + return snprintf(buf, PAGE_SIZE, "%u\n", adf_interface_dpms_state(intf)); +} + +static ssize_t current_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + struct drm_mode_modeinfo mode; + + adf_interface_current_mode(intf, &mode); + + if (mode.name[0]) { + return snprintf(buf, PAGE_SIZE, "%s\n", mode.name); + } else { + bool interlaced = !!(mode.flags & DRM_MODE_FLAG_INTERLACE); + return snprintf(buf, PAGE_SIZE, "%ux%u%s\n", mode.hdisplay, + mode.vdisplay, interlaced ? "i" : ""); + } +} + +static ssize_t type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + return snprintf(buf, PAGE_SIZE, "%s\n", + adf_interface_type_str(intf)); +} + +static ssize_t vsync_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + ktime_t timestamp; + unsigned long flags; + + read_lock_irqsave(&intf->vsync_lock, flags); + timestamp = intf->vsync_timestamp; + read_unlock_irqrestore(&intf->vsync_lock, flags); + + return snprintf(buf, PAGE_SIZE, "%llu\n", ktime_to_ns(timestamp)); +} + +static ssize_t hotplug_detect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adf_interface *intf = dev_to_adf_interface(dev); + bool detect; + unsigned long flags; + + read_lock_irqsave(&intf->hotplug_modelist_lock, flags); + detect = intf->hotplug_detect; + read_unlock_irqrestore(&intf->hotplug_modelist_lock, flags); + + return snprintf(buf, PAGE_SIZE, "%u\n", detect); +} + +static struct device_attribute adf_interface_attrs[] = { + __ATTR_RO(dpms_state), + __ATTR_RO(current_mode), + __ATTR_RO(hotplug_detect), + __ATTR_RO(type), + __ATTR_RO(vsync_timestamp), +}; + +static char *adf_devnode(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "adf/%s", dev_name(dev)); +} + +int adf_obj_sysfs_init(struct adf_obj *obj, struct device *parent) +{ + int ret = adf_new_id(obj, &adf_minors); + if (ret < 0) { + pr_err("%s: allocating adf minor failed: %d\n", __func__, + ret); + return ret; + } + + obj->minor = ret; + obj->dev.parent = parent; + obj->dev.class = adf_class; + obj->dev.devt = MKDEV(adf_major, obj->minor); + + ret = device_register(&obj->dev); + if (ret < 0) { + pr_err("%s: registering adf object failed: %d\n", __func__, + ret); + goto err_device_register; + } + + return 0; + +err_device_register: + idr_remove(&adf_minors, obj->minor); + return ret; +} + +static char *adf_device_devnode(struct device *dev, umode_t *mode, kuid_t *uid, + kgid_t *gid) +{ + struct adf_obj *obj = container_of(dev, struct adf_obj, dev); + return kasprintf(GFP_KERNEL, "adf/%s/device", obj->name); +} + +static char *adf_interface_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct adf_obj *obj = container_of(dev, struct adf_obj, dev); + struct adf_interface *intf = adf_obj_to_interface(obj); + struct adf_device *parent = intf->base.parent; + return kasprintf(GFP_KERNEL, "adf/%s/interface%d", + parent->base.name, intf->base.id); +} + +static char *adf_overlay_engine_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct adf_obj *obj = container_of(dev, struct adf_obj, dev); + struct adf_overlay_engine *eng = adf_obj_to_overlay_engine(obj); + struct adf_device *parent = eng->base.parent; + return kasprintf(GFP_KERNEL, "adf/%s/overlay-engine%d", + parent->base.name, eng->base.id); +} + +static void adf_noop_release(struct device *dev) +{ +} + +static struct device_type adf_device_type = { + .name = "adf_device", + .devnode = adf_device_devnode, + .release = adf_noop_release, +}; + +static struct device_type adf_interface_type = { + .name = "adf_interface", + .devnode = adf_interface_devnode, + .release = adf_noop_release, +}; + +static struct device_type adf_overlay_engine_type = { + .name = "adf_overlay_engine", + .devnode = adf_overlay_engine_devnode, + .release = adf_noop_release, +}; + +int adf_device_sysfs_init(struct adf_device *dev) +{ + dev->base.dev.type = &adf_device_type; + dev_set_name(&dev->base.dev, "%s", dev->base.name); + return adf_obj_sysfs_init(&dev->base, dev->dev); +} + +int adf_interface_sysfs_init(struct adf_interface *intf) +{ + struct adf_device *parent = intf->base.parent; + size_t i, j; + int ret; + + intf->base.dev.type = &adf_interface_type; + dev_set_name(&intf->base.dev, "%s-interface%d", parent->base.name, + intf->base.id); + + ret = adf_obj_sysfs_init(&intf->base, &parent->base.dev); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++) { + ret = device_create_file(&intf->base.dev, + &adf_interface_attrs[i]); + if (ret < 0) { + dev_err(&intf->base.dev, "creating sysfs attribute %s failed: %d\n", + adf_interface_attrs[i].attr.name, ret); + goto err; + } + } + + return 0; + +err: + for (j = 0; j < i; j++) + device_remove_file(&intf->base.dev, &adf_interface_attrs[j]); + return ret; +} + +int adf_overlay_engine_sysfs_init(struct adf_overlay_engine *eng) +{ + struct adf_device *parent = eng->base.parent; + + eng->base.dev.type = &adf_overlay_engine_type; + dev_set_name(&eng->base.dev, "%s-overlay-engine%d", parent->base.name, + eng->base.id); + + return adf_obj_sysfs_init(&eng->base, &parent->base.dev); +} + +struct adf_obj *adf_obj_sysfs_find(int minor) +{ + return idr_find(&adf_minors, minor); +} + +void adf_obj_sysfs_destroy(struct adf_obj *obj) +{ + idr_remove(&adf_minors, obj->minor); + device_unregister(&obj->dev); +} + +void adf_device_sysfs_destroy(struct adf_device *dev) +{ + adf_obj_sysfs_destroy(&dev->base); +} + +void adf_interface_sysfs_destroy(struct adf_interface *intf) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(adf_interface_attrs); i++) + device_remove_file(&intf->base.dev, &adf_interface_attrs[i]); + adf_obj_sysfs_destroy(&intf->base); +} + +void adf_overlay_engine_sysfs_destroy(struct adf_overlay_engine *eng) +{ + adf_obj_sysfs_destroy(&eng->base); +} + +int adf_sysfs_init(void) +{ + struct class *class; + int ret; + + class = class_create(THIS_MODULE, "adf"); + if (IS_ERR(class)) { + ret = PTR_ERR(class); + pr_err("%s: creating class failed: %d\n", __func__, ret); + return ret; + } + + ret = register_chrdev(0, "adf", &adf_fops); + if (ret < 0) { + pr_err("%s: registering device failed: %d\n", __func__, ret); + goto err_chrdev; + } + + class->devnode = adf_devnode; + adf_class = class; + adf_major = ret; + return 0; + +err_chrdev: + class_destroy(adf_class); + return ret; +} + +void adf_sysfs_destroy(void) +{ + idr_remove_all(&adf_minors); + idr_destroy(&adf_minors); + class_destroy(adf_class); +} diff --git a/drivers/video/adf/adf_sysfs.h b/drivers/video/adf/adf_sysfs.h new file mode 100644 index 0000000..4272040 --- /dev/null +++ b/drivers/video/adf/adf_sysfs.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ADF_SYSFS_H +#define __ADF_SYSFS_H + +struct adf_device; +struct adf_interface; +struct adf_overlay_engine; + +int adf_device_sysfs_init(struct adf_device *dev); +void adf_device_sysfs_destroy(struct adf_device *dev); +int adf_interface_sysfs_init(struct adf_interface *intf); +void adf_interface_sysfs_destroy(struct adf_interface *intf); +int adf_overlay_engine_sysfs_init(struct adf_overlay_engine *eng); +void adf_overlay_engine_sysfs_destroy(struct adf_overlay_engine *eng); +struct adf_obj *adf_obj_sysfs_find(int minor); + +int adf_sysfs_init(void); +void adf_sysfs_destroy(void); + +#endif /* __ADF_SYSFS_H */ diff --git a/drivers/video/adf/adf_trace.h b/drivers/video/adf/adf_trace.h new file mode 100644 index 0000000..456fb71 --- /dev/null +++ b/drivers/video/adf/adf_trace.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM adf + +#if !defined(_ADF_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _ADF_TRACE_H + +#include <linux/tracepoint.h> +#include <video/adf.h> + +TRACE_EVENT(adf_event, + TP_PROTO(struct adf_obj *obj, enum adf_event_type type), + TP_ARGS(obj, type), + + TP_STRUCT__entry( + __string(name, obj->name) + __field(enum adf_event_type, type) + __array(char, type_str, 32) + ), + TP_fast_assign( + __assign_str(name, obj->name); + __entry->type = type; + strlcpy(__entry->type_str, adf_event_type_str(obj, type), + sizeof(__entry->type_str)); + ), + TP_printk("obj=%s type=%u (%s)", + __get_str(name), + __entry->type, + __entry->type_str) +); + +TRACE_EVENT(adf_event_enable, + TP_PROTO(struct adf_obj *obj, enum adf_event_type type), + TP_ARGS(obj, type), + + TP_STRUCT__entry( + __string(name, obj->name) + __field(enum adf_event_type, type) + __array(char, type_str, 32) + ), + TP_fast_assign( + __assign_str(name, obj->name); + __entry->type = type; + strlcpy(__entry->type_str, adf_event_type_str(obj, type), + sizeof(__entry->type_str)); + ), + TP_printk("obj=%s type=%u (%s)", + __get_str(name), + __entry->type, + __entry->type_str) +); + +TRACE_EVENT(adf_event_disable, + TP_PROTO(struct adf_obj *obj, enum adf_event_type type), + TP_ARGS(obj, type), + + TP_STRUCT__entry( + __string(name, obj->name) + __field(enum adf_event_type, type) + __array(char, type_str, 32) + ), + TP_fast_assign( + __assign_str(name, obj->name); + __entry->type = type; + strlcpy(__entry->type_str, adf_event_type_str(obj, type), + sizeof(__entry->type_str)); + ), + TP_printk("obj=%s type=%u (%s)", + __get_str(name), + __entry->type, + __entry->type_str) +); + +#endif /* _ADF_TRACE_H */ + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE adf_trace +#include <trace/define_trace.h> diff --git a/include/video/adf.h b/include/video/adf.h new file mode 100644 index 0000000..b62eb60 --- /dev/null +++ b/include/video/adf.h @@ -0,0 +1,743 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDEO_ADF_H +#define _VIDEO_ADF_H + +#include <linux/ioctl.h> +#include <linux/types.h> + +#include <drm/drm_fourcc.h> +#include <drm/drm_mode.h> + +#ifdef __KERNEL__ +#include <linux/device.h> +#include <linux/dma-buf.h> +#include <linux/idr.h> +#include <linux/kref.h> +#include <linux/kthread.h> +#include <linux/ktime.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include "sync.h" +#endif + +#define ADF_NAME_LEN 32 +#define ADF_MAX_CUSTOM_DATA_SIZE PAGE_SIZE + +enum adf_interface_type { + ADF_INTF_DSI = 0, + ADF_INTF_eDP = 1, + ADF_INTF_DPI = 2, + ADF_INTF_VGA = 3, + ADF_INTF_DVI = 4, + ADF_INTF_HDMI = 5, + ADF_INTF_MEMORY = 6, + ADF_INTF_TYPE_DEVICE_CUSTOM = 128, + ADF_INTF_TYPE_MAX = (~(__u32)0), +}; + +#define ADF_INTF_FLAG_PRIMARY (1 << 0) +#define ADF_INTF_FLAG_EXTERNAL (1 << 1) + +enum adf_event_type { + ADF_EVENT_VSYNC = 0, + ADF_EVENT_HOTPLUG = 1, + ADF_EVENT_DEVICE_CUSTOM = 128, + ADF_EVENT_TYPE_MAX = 255, +}; + +/** + * struct adf_set_event - start or stop subscribing to ADF events + * + * @type: the type of event to (un)subscribe + * @enabled: subscribe or unsubscribe + * + * After subscribing to an event, userspace may poll() the ADF object's fd + * to wait for events or read() to consume the event's data. + * + * ADF reserves event types 0 to %ADF_EVENT_DEVICE_CUSTOM-1 for its own events. + * Devices may use event types %ADF_EVENT_DEVICE_CUSTOM to %ADF_EVENT_TYPE_MAX-1 + * for driver-private events. + */ +struct adf_set_event { + __u8 type; + __u8 enabled; +}; + +/** + * struct adf_event - common header for ADF event data + * + * @type: event type + * @length: total size of event data, header inclusive + */ +struct adf_event { + __u8 type; + __u32 length; +}; + +/** + * struct adf_vsync_event - ADF vsync event + * + * @base: event header (see &struct adf_event) + * @timestamp: time of vsync event, in nanoseconds + */ +struct adf_vsync_event { + struct adf_event base; + __u64 timestamp; +}; + +/** + * struct adf_vsync_event - ADF display hotplug event + * + * @base: event header (see &struct adf_event) + * @connected: whether a display is now connected to the interface + */ +struct adf_hotplug_event { + struct adf_event base; + __u8 connected; +}; + +#define ADF_MAX_PLANES 4 +/** + * struct adf_buffer_config - description of buffer displayed by adf_post_config + * + * @overlay_engine: id of the target overlay engine + * @w: width of display region in pixels + * @h: height of display region in pixels + * @format: DRM-style fourcc, see drm_fourcc.h for standard formats + * @fd: dma_buf fd for each plane + * @offset: location of first pixel to scan out, in bytes + * @pitch: stride (i.e. length of a scanline including padding) in bytes + * @n_planes: number of planes in buffer + * @acquire_fence: sync_fence fd which will clear when the buffer is + * ready for display, or <0 if the buffer is already ready + */ +struct adf_buffer_config { + __u32 overlay_engine; + + __u16 w; + __u16 h; + __u32 format; + + __s64 fd[ADF_MAX_PLANES]; + __u32 offset[ADF_MAX_PLANES]; + __u32 pitch[ADF_MAX_PLANES]; + __u8 n_planes; + + __s64 acquire_fence; +}; +#define ADF_MAX_BUFFERS (PAGE_SIZE / sizeof(struct adf_buffer_config)) + +/** + * struct adf_post_config - request to flip to a new set of buffers + * + * @n_interfaces: number of interfaces targeted by the flip (input) + * @interfaces: ids of interfaces targeted by the flip (input) + * @n_bufs: number of buffers displayed (input) + * @bufs: description of buffers displayed (input) + * @custom_data_size: size of driver-private data (input) + * @custom_data: driver-private data (input) + * @complete_fence: sync_fence fd which will clear when this + * configuration has left the screen (output) + */ +struct adf_post_config { + size_t n_interfaces; + __u32 __user *interfaces; + + size_t n_bufs; + struct adf_buffer_config __user *bufs; + + size_t custom_data_size; + void __user *custom_data; + + __s64 complete_fence; +}; +#define ADF_MAX_INTERFACES (PAGE_SIZE / sizeof(__u32)) + +/** + * struct adf_simple_buffer_allocate - request to allocate a "simple" buffer + * + * @w: width of buffer in pixels (input) + * @h: height of buffer in pixels (input) + * @format: DRM-style fourcc (input) + * + * @fd: dma_buf fd (output) + * @offset: location of first pixel, in bytes (output) + * @pitch: length of a scanline including padding, in bytes (output) + * + * Simple buffers are analogous to DRM's "dumb" buffers. They have a single + * plane of linear RGB data which can be allocated and scanned out without + * any driver-private ioctls or data. + * + * @format must be a standard RGB format defined in drm_fourcc.h. + * + * ADF clients must NOT assume that an interface can scan out a simple buffer + * allocated by a different ADF interface, even if the two interfaces belong to + * the same ADF device. + */ +struct adf_simple_buffer_alloc { + __u16 w; + __u16 h; + __u32 format; + + __s64 fd; + __u32 offset; + __u32 pitch; +}; + +/** + * struct adf_simple_post_config - request to flip to a single buffer without + * driver-private data + * + * @buf: description of buffer displayed (input) + * @complete_fence: sync_fence fd which will clear when this buffer has left the + * screen (output) + */ +struct adf_simple_post_config { + struct adf_buffer_config buf; + __s64 complete_fence; +}; + +/** + * struct adf_attachment - description of attachment between an overlay engine + * and an interface + * + * @overlay_engine: id of the overlay engine + * @interface: id of the interface + */ +struct adf_attachment_config { + __u32 overlay_engine; + __u32 interface; +}; + +/** + * struct adf_device_data - describes a display device + * + * @name: display device's name + * @n_attachments: the number of current attachments + * @attachments: list of current attachments + * @n_allowed_attachments: the number of allowed attachments + * @allowed_attachments: list of allowed attachments + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + */ +struct adf_device_data { + char name[ADF_NAME_LEN]; + + size_t n_attachments; + struct adf_attachment_config __user *attachments; + + size_t n_allowed_attachments; + struct adf_attachment_config __user *allowed_attachments; + + size_t custom_data_size; + void __user *custom_data; +}; +#define ADF_MAX_ATTACHMENTS (PAGE_SIZE / sizeof(struct adf_attachment)) + +/** + * struct adf_device_data - describes a display interface + * + * @name: display interface's name + * @type: interface type (see enum @adf_interface_type) + * @id: which interface of type @type; + * e.g. interface DSI.1 -> @type=%ADF_INTF_TYPE_DSI, @id=1 + * @flags: informational flags (bitmask of %ADF_INTF_FLAG_* values) + * @dpms_state: DPMS state (one of @DRM_MODE_DPMS_* defined in drm_mode.h) + * @hotplug_detect: whether a display is plugged in + * @width_mm: screen width in millimeters, or 0 if unknown + * @height_mm: screen height in millimeters, or 0 if unknown + * @current_mode: current display mode + * @n_available_modes: the number of hardware display modes + * @available_modes: list of hardware display modes + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + */ +struct adf_interface_data { + char name[ADF_NAME_LEN]; + + __u32 type; + __u32 id; + /* e.g. type=ADF_INTF_TYPE_DSI, id=1 => DSI.1 */ + __u32 flags; + + __u8 dpms_state; + __u8 hotplug_detect; + __u16 width_mm; + __u16 height_mm; + + struct drm_mode_modeinfo current_mode; + size_t n_available_modes; + struct drm_mode_modeinfo __user *available_modes; + + size_t custom_data_size; + void __user *custom_data; +}; +#define ADF_MAX_MODES (PAGE_SIZE / sizeof(struct drm_mode_modeinfo)) + +/** + * struct adf_overlay_engine_data - describes an overlay engine + * + * @name: overlay engine's name + * @n_supported_formats: number of supported formats + * @supported_formats: list of supported formats + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + */ +struct adf_overlay_engine_data { + char name[ADF_NAME_LEN]; + + size_t n_supported_formats; + __u32 __user *supported_formats; + + size_t custom_data_size; + void __user *custom_data; +}; +#define ADF_MAX_SUPPORTED_FORMATS (PAGE_SIZE / sizeof(__u32)) + +#define ADF_SET_EVENT _IOW('D', 0, struct adf_set_event) +#define ADF_BLANK _IOW('D', 1, __u8) +#define ADF_POST_CONFIG _IOW('D', 2, struct adf_post_config) +#define ADF_SET_MODE _IOW('D', 3, struct drm_mode_modeinfo) +#define ADF_GET_DEVICE_DATA _IOR('D', 4, struct adf_device_data) +#define ADF_GET_INTERFACE_DATA _IOR('D', 5, struct adf_interface_data) +#define ADF_GET_OVERLAY_ENGINE_DATA \ + _IOR('D', 6, struct adf_overlay_engine_data) +#define ADF_SIMPLE_POST_CONFIG _IOW('D', 7, struct adf_simple_post_config) +#define ADF_SIMPLE_BUFFER_ALLOC _IOW('D', 8, struct adf_simple_buffer_alloc) +#define ADF_ATTACH _IOW('D', 9, struct adf_attachment_config) +#define ADF_DETACH _IOW('D', 10, struct adf_attachment_config) + +#ifdef __KERNEL__ +struct adf_obj; +struct adf_obj_ops; +struct adf_device; +struct adf_device_ops; +struct adf_interface; +struct adf_interface_ops; +struct adf_overlay_engine; +struct adf_overlay_engine_ops; + +/** + * struct adf_buffer - buffer displayed by adf_post + * + * @overlay_engine: target overlay engine + * @w: width of display region in pixels + * @h: height of display region in pixels + * @format: DRM-style fourcc, see drm_fourcc.h for standard formats + * @dma_bufs: dma_buf for each plane + * @offset: location of first pixel to scan out, in bytes + * @pitch: length of a scanline including padding, in bytes + * @n_planes: number of planes in buffer + * @acquire_fence: sync_fence which will clear when the buffer is + * ready for display + * + * &struct adf_buffer is the in-kernel counterpart to the userspace-facing + * &struct adf_buffer_config. + */ +struct adf_buffer { + struct adf_overlay_engine *overlay_engine; + + u16 w; + u16 h; + u32 format; + + struct dma_buf *dma_bufs[ADF_MAX_PLANES]; + u32 offset[ADF_MAX_PLANES]; + u32 pitch[ADF_MAX_PLANES]; + u8 n_planes; + + struct sync_fence *acquire_fence; +}; + +/** + * struct adf_buffer_mapping - state for mapping a &struct adf_buffer into the + * display device + * + * @attachments: dma-buf attachment for each plane + * @sg_tables: SG tables for each plane + */ +struct adf_buffer_mapping { + struct dma_buf_attachment *attachments[ADF_MAX_PLANES]; + struct sg_table *sg_tables[ADF_MAX_PLANES]; +}; + +/** + * struct adf_post - request to flip to a new set of buffers + * + * @n_bufs: number of buffers displayed + * @bufs: buffers displayed + * @mappings: in-device mapping state for each buffer + * @custom_data_size: size of driver-private data + * @custom_data: driver-private data + * + * &struct adf_post is the in-kernel counterpart to the userspace-facing + * &struct adf_post_config. + */ +struct adf_post { + size_t n_bufs; + struct adf_buffer *bufs; + struct adf_buffer_mapping *mappings; + + size_t custom_data_size; + void *custom_data; +}; + +/** + * struct adf_attachment - description of attachment between an overlay engine + * and an interface + * + * @overlay_engine: the overlay engine + * @interface: the interface + * + * &struct adf_attachment is the in-kernel counterpart to the userspace-facing + * &struct adf_attachment_config. + */ +struct adf_attachment { + struct adf_overlay_engine *overlay_engine; + struct adf_interface *interface; +}; + +struct adf_pending_post { + struct list_head head; + struct adf_post config; + void *state; +}; + +enum adf_obj_type { + ADF_OBJ_OVERLAY_ENGINE = 0, + ADF_OBJ_INTERFACE = 1, + ADF_OBJ_DEVICE = 2, +}; + +/** + * struct adf_obj_ops - common ADF object implementation ops + * + * @open: handle opening the object's device node + * @release: handle releasing an open file + * @ioctl: handle custom ioctls + * + * @supports_event: return whether the object supports generating events of type + * @type + * @set_event: enable or disable events of type @type + * @event_type_str: return a string representation of custom event @type + * (@type >= %ADF_EVENT_DEVICE_CUSTOM). + * + * @custom_data: copy up to %ADF_MAX_CUSTOM_DATA_SIZE bytes of driver-private + * data into @data (allocated by ADF) and return the number of copied bytes + * in @size. Return 0 on success or an error code (<0) on failure. + */ +struct adf_obj_ops { + /* optional */ + int (*open)(struct adf_obj *obj, struct inode *inode, + struct file *file); + /* optional */ + int (*release)(struct adf_obj *obj, struct inode *inode, + struct file *file); + /* optional */ + long (*ioctl)(struct adf_obj *obj, unsigned int cmd, unsigned long arg); + + /* optional */ + bool (*supports_event)(struct adf_obj *obj, enum adf_event_type type); + /* required if supports_event is implemented */ + void (*set_event)(struct adf_obj *obj, enum adf_event_type type, + bool enabled); + /* optional */ + const char *(*event_type_str)(struct adf_obj *obj, + enum adf_event_type type); + + /* optional */ + int (*custom_data)(struct adf_obj *obj, void *data, size_t *size); +}; + +struct adf_obj { + enum adf_obj_type type; + char name[ADF_NAME_LEN]; + struct adf_device *parent; + + const struct adf_obj_ops *ops; + + struct device dev; + + struct spinlock file_lock; + struct list_head file_list; + + struct mutex event_lock; + struct rb_root event_refcount; + + int id; + int minor; +}; + +/** + * struct adf_device_ops - display device implementation ops + * + * @owner: device's module + * @base: common operations (see &struct adf_obj_ops) + * + * @attach: attach overlay engine @eng to interface @intf. Return 0 on success + * or error code (<0) on failure. + * @detach: detach overlay engine @eng from interface @intf. Return 0 on + * success or error code (<0) on failure. + * + * @validate_custom_format: validate the number and size of planes + * in buffers with a custom format (i.e., not one of the @DRM_FORMAT_* + * types defined in drm/drm_fourcc.h). Return 0 if the buffer is valid or + * an error code (<0) otherwise. + * + * @validate: validate that the proposed configuration @cfg is legal. The + * driver may optionally allocate and return some driver-private state in + * @driver_state, which will be passed to the corresponding post(). The + * driver may NOT commit any changes to hardware. Return 0 if @cfg is + * valid or an error code (<0) otherwise. + * @complete_fence: create a hardware-backed sync fence to be signaled when + * @cfg is removed from the screen. If unimplemented, ADF automatically + * creates an sw_sync fence. Return the sync fence on success or a + * PTR_ERR() on failure. + * @post: flip @cfg onto the screen. Wait for the display to begin scanning out + * @cfg before returning. + * @advance_timeline: signal the sync fence for the last configuration to leave + * the display. If unimplemented, ADF automatically advances an sw_sync + * timeline. + * @state_free: free driver-private state allocated during validate() + */ +struct adf_device_ops { + /* required */ + struct module *owner; + const struct adf_obj_ops base; + + /* optional */ + int (*attach)(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + /* optional */ + int (*detach)(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + + /* required if any of the device's overlay engines supports at least one + custom format */ + int (*validate_custom_format)(struct adf_device *dev, + struct adf_buffer *buf); + + /* required */ + int (*validate)(struct adf_device *dev, struct adf_post *cfg, + void **driver_state); + /* optional */ + struct sync_fence *(*complete_fence)(struct adf_device *dev, + struct adf_post *cfg, void *driver_state); + /* required */ + void (*post)(struct adf_device *dev, struct adf_post *cfg, + void *driver_state); + /* required if complete_fence is implemented */ + void (*advance_timeline)(struct adf_device *dev, + struct adf_post *cfg, void *driver_state); + /* required if validate() allocates driver state */ + void (*state_free)(struct adf_device *dev, void *driver_state); +}; + +struct adf_attachment_list { + struct adf_attachment attachment; + struct list_head head; +}; + +struct adf_device { + struct adf_obj base; + struct device *dev; + + const struct adf_device_ops *ops; + + struct mutex client_lock; + + struct idr interfaces; + size_t n_interfaces; + struct idr overlay_engines; + + struct list_head post_list; + struct mutex post_lock; + struct kthread_worker post_worker; + struct task_struct *post_thread; + struct kthread_work post_work; + + struct list_head attached; + size_t n_attached; + struct list_head attach_allowed; + size_t n_attach_allowed; + + struct adf_pending_post *onscreen; + + struct sw_sync_timeline *timeline; + int timeline_max; +}; + +/** + * struct adf_interface_ops - display interface implementation ops + * + * @base: common operations (see &struct adf_obj_ops) + * + * @blank: change the display's DPMS state. Return 0 on success or error + * code (<0) on failure. + * + * @alloc_simple_buffer: allocate a buffer with the specified @w, @h, and + * @format. @format will be a standard RGB format (i.e., + * adf_format_is_rgb(@format) == true). Return 0 on success or error code + * (<0) on failure. On success, return the buffer, offset, and pitch in + * @dma_buf, @offset, and @pitch respectively. + * @describe_simple_post: provide driver-private data needed to post a single + * buffer @buf. Copy up to ADF_MAX_CUSTOM_DATA_SIZE bytes into @data + * (allocated by ADF) and return the number of bytes in @size. Return 0 on + * success or error code (<0) on failure. + * + * @modeset: change the interface's mode. @mode is not necessarily part of the + * modelist passed to adf_hotplug_notify_connected(); the driver may + * accept or reject custom modes at its discretion. Return 0 on success or + * error code (<0) if the mode could not be set. + * + * @screen_size: copy the screen dimensions in millimeters into @width_mm + * and @height_mm. Return 0 on success or error code (<0) if the display + * dimensions are unknown. + * + * @type_str: return a string representation of custom @intf->type + * (@intf->type >= @ADF_INTF_TYPE_DEVICE_CUSTOM). + */ +struct adf_interface_ops { + const struct adf_obj_ops base; + + /* optional */ + int (*blank)(struct adf_interface *intf, u8 state); + + /* optional */ + int (*alloc_simple_buffer)(struct adf_interface *intf, + u16 w, u16 h, u32 format, + struct dma_buf **dma_buf, u32 *offset, u32 *pitch); + /* required if alloc_simple_buffer is implemented */ + int (*describe_simple_post)(struct adf_interface *intf, + struct adf_buffer *fb, void *data, size_t *size); + + /* optional */ + int (*modeset)(struct adf_interface *intf, + struct drm_mode_modeinfo *mode); + + /* optional */ + int (*screen_size)(struct adf_interface *intf, u16 *width_mm, + u16 *height_mm); + + /* optional */ + const char *(*type_str)(struct adf_interface *intf); +}; + +struct adf_interface { + struct adf_obj base; + const struct adf_interface_ops *ops; + + struct drm_mode_modeinfo current_mode; + + enum adf_interface_type type; + u32 idx; + u32 flags; + + wait_queue_head_t vsync_wait; + ktime_t vsync_timestamp; + rwlock_t vsync_lock; + + u8 dpms_state; + + bool hotplug_detect; + struct drm_mode_modeinfo *modelist; + size_t n_modes; + rwlock_t hotplug_modelist_lock; +}; + +/** + * struct adf_interface_ops - overlay engine implementation ops + * + * @base: common operations (see &struct adf_obj_ops) + * + * @supported_formats: list of fourccs the overlay engine can scan out + * @n_supported_formats: length of supported_formats, up to + * ADF_MAX_SUPPORTED_FORMATS + */ +struct adf_overlay_engine_ops { + const struct adf_obj_ops base; + + /* required */ + const u32 *supported_formats; + /* required */ + const size_t n_supported_formats; +}; + +struct adf_overlay_engine { + struct adf_obj base; + + const struct adf_overlay_engine_ops *ops; +}; + +#define adf_obj_to_device(ptr) \ + container_of((ptr), struct adf_device, base) + +#define adf_obj_to_interface(ptr) \ + container_of((ptr), struct adf_interface, base) + +#define adf_obj_to_overlay_engine(ptr) \ + container_of((ptr), struct adf_overlay_engine, base) + +int adf_device_init(struct adf_device *dev, struct device *parent, + const struct adf_device_ops *ops, const char *fmt, ...); +void adf_device_destroy(struct adf_device *dev); +int adf_interface_init(struct adf_interface *intf, struct adf_device *dev, + enum adf_interface_type type, u32 idx, u32 flags, + const struct adf_interface_ops *ops, const char *fmt, ...); +void adf_interface_destroy(struct adf_interface *intf); +int adf_overlay_engine_init(struct adf_overlay_engine *eng, + struct adf_device *dev, + const struct adf_overlay_engine_ops *ops, const char *fmt, ...); +void adf_overlay_engine_destroy(struct adf_overlay_engine *eng); + +int adf_attachment_allow(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + +const char *adf_obj_type_str(enum adf_obj_type type); +const char *adf_interface_type_str(struct adf_interface *intf); +const char *adf_event_type_str(struct adf_obj *obj, enum adf_event_type type); +void adf_format_str(u32 format, char buf[5]); + +int adf_event_get(struct adf_obj *obj, enum adf_event_type type); +int adf_event_put(struct adf_obj *obj, enum adf_event_type type); +int adf_event_notify(struct adf_obj *obj, struct adf_event *event); + +static inline void adf_vsync_get(struct adf_interface *intf) +{ + adf_event_get(&intf->base, ADF_EVENT_VSYNC); +} + +static inline void adf_vsync_put(struct adf_interface *intf) +{ + adf_event_put(&intf->base, ADF_EVENT_VSYNC); +} + +int adf_vsync_wait(struct adf_interface *intf, long timeout); +void adf_vsync_notify(struct adf_interface *intf, ktime_t timestamp); + +int adf_hotplug_notify_connected(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes); +void adf_hotplug_notify_disconnected(struct adf_interface *intf); + +#endif /* __KERNEL__ */ + +#endif /* _VIDEO_ADF_H */ diff --git a/include/video/adf_client.h b/include/video/adf_client.h new file mode 100644 index 0000000..ae75498 --- /dev/null +++ b/include/video/adf_client.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDEO_ADF_CLIENT_H_ +#define _VIDEO_ADF_CLIENT_H_ + +#include <video/adf.h> + +int adf_interface_blank(struct adf_interface *intf, u8 state); +u8 adf_interface_dpms_state(struct adf_interface *intf); + +void adf_interface_current_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode); +size_t adf_interface_modelist(struct adf_interface *intf, + struct drm_mode_modeinfo *modelist, size_t n_modes); +int adf_interface_set_mode(struct adf_interface *intf, + struct drm_mode_modeinfo *mode); +int adf_interface_screen_size(struct adf_interface *intf, u16 *width, + u16 *height); +int adf_interface_simple_buffer_alloc(struct adf_interface *intf, u16 w, u16 h, + u32 format, struct dma_buf **dma_buf, u32 *offset, u32 *pitch); +struct sync_fence *adf_interface_simple_post(struct adf_interface *intf, + struct adf_buffer *buf); + +bool adf_overlay_engine_supports_format(struct adf_overlay_engine *eng, + u32 format); + +size_t adf_device_attachments(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments); +size_t adf_device_attachments_allowed(struct adf_device *dev, + struct adf_attachment *attachments, size_t n_attachments); +bool adf_device_attached(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); +bool adf_device_attach_allowed(struct adf_device *dev, + struct adf_overlay_engine *eng, struct adf_interface *intf); +int adf_device_attach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); +int adf_device_detach(struct adf_device *dev, struct adf_overlay_engine *eng, + struct adf_interface *intf); + +struct sync_fence *adf_device_post(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, void *custom_data, + size_t custom_data_size); +struct sync_fence *adf_device_post_nocopy(struct adf_device *dev, + struct adf_interface **intfs, size_t n_intfs, + struct adf_buffer *bufs, size_t n_bufs, void *custom_data, + size_t custom_data_size); + +#endif /* _VIDEO_ADF_CLIENT_H_ */ diff --git a/include/video/adf_format.h b/include/video/adf_format.h new file mode 100644 index 0000000..5828c37 --- /dev/null +++ b/include/video/adf_format.h @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2013 Google, Inc. + * modified from drivers/gpu/drm/drm_crtc.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDEO_ADF_FORMAT_H +#define _VIDEO_ADF_FORMAT_H + +#include <drm/drm_fourcc.h> + +static inline bool adf_format_is_standard(u32 format) +{ + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_AYUV: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YVU444: + return true; + default: + return false; + } +} + +static inline bool adf_format_is_rgb(u32 format) +{ + switch (format) { + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + return true; + + default: + return false; + } +} + +static inline u8 adf_format_num_planes(u32 format) +{ + switch (format) { + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YVU444: + return 3; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + return 2; + default: + return 1; + } +} + +static inline u8 adf_format_bpp(u32 format) +{ + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB332: + case DRM_FORMAT_BGR233: + return 8; + + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return 16; + + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + return 24; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_BGRA8888: + return 32; + + default: + pr_debug("%s: unsupported pixel format %u\n", __func__, format); + return 0; + } +} + +static inline int adf_format_plane_cpp(u32 format, int plane) +{ + if (plane >= adf_format_num_planes(format)) + return 0; + + switch (format) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + return 2; + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + return plane ? 2 : 1; + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YVU444: + return 1; + default: + return adf_format_bpp(format) / 8; + } +} + +static inline int adf_format_horz_chroma_subsampling(u32 format) +{ + switch (format) { + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + return 4; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + return 2; + default: + return 1; + } +} + +static inline int adf_format_vert_chroma_subsampling(u32 format) +{ + switch (format) { + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YVU410: + return 4; + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + return 2; + default: + return 1; + } +} + +#endif /* _VIDEO_ADF_FORMAT_H */
Optional helper which implements some ADF interface ops for displays using the Display Core framework
Signed-off-by: Greg Hackmann ghackmann@google.com --- drivers/video/adf/Kconfig | 5 ++ drivers/video/adf/Makefile | 2 + drivers/video/adf/adf.c | 28 ++++++++- drivers/video/adf/adf.h | 1 + drivers/video/adf/adf_display.c | 123 ++++++++++++++++++++++++++++++++++++++++ include/video/adf_display.h | 31 ++++++++++ 6 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 drivers/video/adf/adf_display.c create mode 100644 include/video/adf_display.h
diff --git a/drivers/video/adf/Kconfig b/drivers/video/adf/Kconfig index 0b64408..30b0611 100644 --- a/drivers/video/adf/Kconfig +++ b/drivers/video/adf/Kconfig @@ -3,3 +3,8 @@ menuconfig ADF depends on SW_SYNC depends on DMA_SHARED_BUFFER tristate "Atomic Display Framework" + +menuconfig ADF_DISPLAY_CORE + depends on ADF + depends on DISPLAY_CORE + tristate "Helper for implementing ADF interface ops with Display Core devices" diff --git a/drivers/video/adf/Makefile b/drivers/video/adf/Makefile index 2af5f79..30164ee 100644 --- a/drivers/video/adf/Makefile +++ b/drivers/video/adf/Makefile @@ -8,3 +8,5 @@ obj-$(CONFIG_ADF) += adf.o \ adf_sysfs.o
obj-$(CONFIG_COMPAT) += adf_fops32.o + +obj-$(CONFIG_ADF_DISPLAY_CORE) += adf_display.o diff --git a/drivers/video/adf/adf.c b/drivers/video/adf/adf.c index 5dc04af..b3b57dd 100644 --- a/drivers/video/adf/adf.c +++ b/drivers/video/adf/adf.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2013 Google, Inc. - * adf_modeinfo_set_name modified from drm_mode_set_name in + * adf_modeinfo_{set_name,set_vrefresh} modified from * drivers/gpu/drm/drm_modes.c * * This software is licensed under the terms of the GNU General Public @@ -966,6 +966,32 @@ void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode) interlaced ? "i" : ""); }
+void adf_modeinfo_set_vrefresh(struct drm_mode_modeinfo *mode) +{ + int refresh = 0; + unsigned int calc_val; + + if (mode->vrefresh > 0) + return; + else if (mode->htotal > 0 && mode->vtotal > 0) { + int vtotal; + vtotal = mode->vtotal; + /* work out vrefresh the value will be x1000 */ + calc_val = (mode->clock * 1000); + calc_val /= mode->htotal; + refresh = (calc_val + vtotal / 2) / vtotal; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + refresh *= 2; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + refresh /= 2; + if (mode->vscan > 1) + refresh /= mode->vscan; + + mode->vrefresh = refresh; + } +} + static void __exit adf_exit(void); static int __init adf_init(void) { diff --git a/drivers/video/adf/adf.h b/drivers/video/adf/adf.h index acad631..5f7260d 100644 --- a/drivers/video/adf/adf.h +++ b/drivers/video/adf/adf.h @@ -44,5 +44,6 @@ struct adf_event_refcount *adf_obj_find_refcount(struct adf_obj *obj, enum adf_event_type type);
void adf_modeinfo_set_name(struct drm_mode_modeinfo *mode); +void adf_modeinfo_set_vrefresh(struct drm_mode_modeinfo *mode);
#endif /* __ADF_H */ diff --git a/drivers/video/adf/adf_display.c b/drivers/video/adf/adf_display.c new file mode 100644 index 0000000..c87f6a5 --- /dev/null +++ b/drivers/video/adf/adf_display.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013 Google, Inc. + * adf_modeinfo_from_videomode modified from drm_display_mode_from_videomode in + * drivers/gpu/drm/drm_modes.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/slab.h> +#include <video/adf.h> +#include <video/adf_display.h> + +#include "adf.h" + +/** + * adf_display_entity_screen_size - handle the screen_size interface op + * by querying a display core entity + */ +int adf_display_entity_screen_size(struct display_entity *display, + u16 *width_mm, u16 *height_mm) +{ + unsigned int cdf_width, cdf_height; + int ret; + + ret = display_entity_get_size(display, &cdf_width, &cdf_height); + if (!ret) { + *width_mm = cdf_width; + *height_mm = cdf_height; + } + return ret; +} +EXPORT_SYMBOL(adf_display_entity_screen_size); + +/** + * adf_display_entity_notify_connected - notify ADF of a display core entity + * being connected to an interface + * + * @intf: the interface + * @display: the display + * + * adf_display_entity_notify_connected() wraps adf_hotplug_notify_connected() + * but does not require a hardware modelist. @display is queried to + * automatically populate the modelist. + * + * Returns 0 on success or error code (<0) on failure. + */ +int adf_display_entity_notify_connected(struct adf_interface *intf, + struct display_entity *display) +{ + const struct videomode *vmodes; + struct drm_mode_modeinfo *dmodes = NULL; + int ret; + size_t i, n_modes; + + ret = display_entity_get_modes(display, &vmodes); + if (ret < 0) + return ret; + + n_modes = ret; + if (n_modes) { + dmodes = kzalloc(n_modes * sizeof(dmodes[0]), GFP_KERNEL); + if (!dmodes) + return -ENOMEM; + } + + for (i = 0; i < n_modes; i++) + adf_modeinfo_from_videomode(&vmodes[i], &dmodes[i]); + + ret = adf_hotplug_notify_connected(intf, dmodes, n_modes); + kfree(dmodes); + return ret; +} +EXPORT_SYMBOL(adf_display_entity_notify_connected); + +/** + * adf_modeinfo_from_videomode - copy a display core videomode into + * an equivalent &struct drm_mode_modeinfo + * + * @vm: the input display core videomode + * @dmode: the output DRM/ADF modeinfo + */ +void adf_modeinfo_from_videomode(const struct videomode *vm, + struct drm_mode_modeinfo *dmode) +{ + memset(dmode, 0, sizeof(*dmode)); + + dmode->hdisplay = vm->hactive; + dmode->hsync_start = dmode->hdisplay + vm->hfront_porch; + dmode->hsync_end = dmode->hsync_start + vm->hsync_len; + dmode->htotal = dmode->hsync_end + vm->hback_porch; + + dmode->vdisplay = vm->vactive; + dmode->vsync_start = dmode->vdisplay + vm->vfront_porch; + dmode->vsync_end = dmode->vsync_start + vm->vsync_len; + dmode->vtotal = dmode->vsync_end + vm->vback_porch; + + dmode->clock = vm->pixelclock / 1000; + + if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) + dmode->flags |= DRM_MODE_FLAG_PHSYNC; + else if (vm->flags & DISPLAY_FLAGS_HSYNC_LOW) + dmode->flags |= DRM_MODE_FLAG_NHSYNC; + if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) + dmode->flags |= DRM_MODE_FLAG_PVSYNC; + else if (vm->flags & DISPLAY_FLAGS_VSYNC_LOW) + dmode->flags |= DRM_MODE_FLAG_NVSYNC; + if (vm->flags & DISPLAY_FLAGS_INTERLACED) + dmode->flags |= DRM_MODE_FLAG_INTERLACE; + if (vm->flags & DISPLAY_FLAGS_DOUBLESCAN) + dmode->flags |= DRM_MODE_FLAG_DBLSCAN; + + adf_modeinfo_set_name(dmode); + adf_modeinfo_set_vrefresh(dmode); +} +EXPORT_SYMBOL_GPL(adf_modeinfo_from_videomode); diff --git a/include/video/adf_display.h b/include/video/adf_display.h new file mode 100644 index 0000000..af8cb6b --- /dev/null +++ b/include/video/adf_display.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDEO_ADF_DISPLAY_H +#define _VIDEO_ADF_DISPLAY_H + +#include <video/adf.h> +#include <video/display.h> +#include <video/videomode.h> + +int adf_display_entity_screen_size(struct display_entity *display, + u16 *width_mm, u16 *height_mm); + +int adf_display_entity_notify_connected(struct adf_interface *intf, + struct display_entity *display); + +void adf_modeinfo_from_videomode(const struct videomode *vm, + struct drm_mode_modeinfo *dmode); + +#endif /* _VIDEO_ADF_DISPLAY_H */
Provides a dma-buf exporter for memblocks, mainly useful for ADF devices to wrap their bootloader logos
Signed-off-by: Greg Hackmann ghackmann@google.com --- drivers/video/adf/Kconfig | 5 ++ drivers/video/adf/Makefile | 2 + drivers/video/adf/adf_memblock.c | 150 +++++++++++++++++++++++++++++++++++++++ include/video/adf_memblock.h | 20 ++++++ 4 files changed, 177 insertions(+) create mode 100644 drivers/video/adf/adf_memblock.c create mode 100644 include/video/adf_memblock.h
diff --git a/drivers/video/adf/Kconfig b/drivers/video/adf/Kconfig index 30b0611..ad0c0eb 100644 --- a/drivers/video/adf/Kconfig +++ b/drivers/video/adf/Kconfig @@ -8,3 +8,8 @@ menuconfig ADF_DISPLAY_CORE depends on ADF depends on DISPLAY_CORE tristate "Helper for implementing ADF interface ops with Display Core devices" + +menuconfig ADF_MEMBLOCK + depends on ADF + depends on HAVE_MEMBLOCK + tristate "Helper for using memblocks as buffers in ADF drivers" diff --git a/drivers/video/adf/Makefile b/drivers/video/adf/Makefile index 30164ee..97f9c98 100644 --- a/drivers/video/adf/Makefile +++ b/drivers/video/adf/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_ADF) += adf.o \ obj-$(CONFIG_COMPAT) += adf_fops32.o
obj-$(CONFIG_ADF_DISPLAY_CORE) += adf_display.o + +obj-$(CONFIG_ADF_MEMBLOCK) += adf_memblock.o diff --git a/drivers/video/adf/adf_memblock.c b/drivers/video/adf/adf_memblock.c new file mode 100644 index 0000000..a1b7ec6 --- /dev/null +++ b/drivers/video/adf/adf_memblock.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/dma-buf.h> +#include <linux/highmem.h> +#include <linux/memblock.h> +#include <linux/slab.h> + +struct adf_memblock_pdata { + phys_addr_t base; +}; + +struct sg_table *adf_memblock_map(struct dma_buf_attachment *attach, + enum dma_data_direction direction) +{ + struct adf_memblock_pdata *pdata = attach->dmabuf->priv; + unsigned long pfn = (pdata->base >> PAGE_SHIFT); + struct page *page = pfn_to_page(pfn); + struct sg_table *table; + int ret; + + table = kzalloc(sizeof(*table), GFP_KERNEL); + if (!table) + return ERR_PTR(-ENOMEM); + + ret = sg_alloc_table(table, 1, GFP_KERNEL); + if (ret < 0) + goto err; + + sg_set_page(table->sgl, page, attach->dmabuf->size, 0); + return table; + +err: + kfree(table); + return ERR_PTR(ret); +} + +void adf_memblock_unmap(struct dma_buf_attachment *attach, + struct sg_table *table, enum dma_data_direction direction) +{ + sg_free_table(table); +} + +static void __init_memblock adf_memblock_release(struct dma_buf *buf) +{ + struct adf_memblock_pdata *pdata = buf->priv; + int err = memblock_free(pdata->base, buf->size); + + if (err < 0) + pr_warn("%s: freeing memblock failed: %d\n", __func__, err); + kfree(pdata); +} + +static void *adf_memblock_do_kmap(struct dma_buf *buf, unsigned long pgoffset, + bool atomic) +{ + struct adf_memblock_pdata *pdata = buf->priv; + unsigned long pfn = (pdata->base >> PAGE_SHIFT) + pgoffset; + struct page *page = pfn_to_page(pfn); + + if (atomic) + return kmap_atomic(page); + else + return kmap(page); +} + +static void *adf_memblock_kmap_atomic(struct dma_buf *buf, + unsigned long pgoffset) +{ + return adf_memblock_do_kmap(buf, pgoffset, true); +} + +static void adf_memblock_kunmap_atomic(struct dma_buf *buf, + unsigned long pgoffset, void *vaddr) +{ + kunmap_atomic(vaddr); +} + +static void *adf_memblock_kmap(struct dma_buf *buf, unsigned long pgoffset) +{ + return adf_memblock_do_kmap(buf, pgoffset, false); +} + +static void adf_memblock_kunmap(struct dma_buf *buf, unsigned long pgoffset, + void *vaddr) +{ + kunmap(vaddr); +} + +static int adf_memblock_mmap(struct dma_buf *buf, struct vm_area_struct *vma) +{ + struct adf_memblock_pdata *pdata = buf->priv; + unsigned long pfn = pdata->base >> PAGE_SHIFT; + + return remap_pfn_range(vma, vma->vm_start, pfn, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +struct dma_buf_ops adf_memblock_ops = { + .map_dma_buf = adf_memblock_map, + .unmap_dma_buf = adf_memblock_unmap, + .release = adf_memblock_release, + .kmap_atomic = adf_memblock_kmap_atomic, + .kunmap_atomic = adf_memblock_kunmap_atomic, + .kmap = adf_memblock_kmap, + .kunmap = adf_memblock_kunmap, + .mmap = adf_memblock_mmap, +}; + +/** + * adf_memblock_export - export a memblock reserved area as a dma-buf + * + * @base: base physical address + * @size: memblock size + * @flags: mode flags for the dma-buf's file + * + * @base and @size must be page-aligned. + * + * Returns a dma-buf on success or ERR_PTR(-errno) on failure. + */ +struct dma_buf *adf_memblock_export(phys_addr_t base, size_t size, int flags) +{ + struct adf_memblock_pdata *pdata; + struct dma_buf *buf; + + if (PAGE_ALIGN(base) != base || PAGE_ALIGN(size) != size) + return ERR_PTR(-EINVAL); + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->base = base; + buf = dma_buf_export(pdata, &adf_memblock_ops, size, flags); + if (IS_ERR(buf)) + kfree(pdata); + + return buf; +} diff --git a/include/video/adf_memblock.h b/include/video/adf_memblock.h new file mode 100644 index 0000000..6256e0e --- /dev/null +++ b/include/video/adf_memblock.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDEO_ADF_MEMBLOCK_H_ +#define _VIDEO_ADF_MEMBLOCK_H_ + +struct dma_buf *adf_memblock_export(phys_addr_t base, size_t size, int flags); + +#endif /* _VIDEO_ADF_MEMBLOCK_H_ */
On Wed, Aug 28, 2013 at 9:51 PM, Greg Hackmann ghackmann@google.com wrote:
Hi,
ADF is an experimental display framework that I designed after experimenting with a KMS-based hardware composer for Android. ADF started as an proof-of-concept implemented from scratch, so right now it's a separate framework rather than a patchstack on top of KMS. If there's community interest, moving forward I'd like to merge its functionality into KMS rather than keep it as a separate thing.
I'm going to talk about ADF at the Android and Graphics session at Linux Plumbers. The documentation's not done but I wanted to post these patches to people a heads-up about ADF. If there's interest I can write up some more formal documentation ahead of Plumbers.
I designed ADF to deal with some serious issues I had working with KMS:
- The API is geared toward updating one object at a time. Android's graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible. I'm open to suggestions, of course, but the approach I was going for was to at least do most of the boring work for this in drm/kms core (ie. w/ the drm_{plane,crtc,etc}_state stuff)
- Some SoCs don't map well to KMS's CRTC + planes + encoders + connector model. At the time I was dealing with hardware where the blending engines didn't have their own framebuffer (they could only scan out constant colors or mix the output of other blocks), and you needed to gang several mixers together to drive high-res displays.
currently you can connect a crtc to multiple encoders (at least for some hw).. I suppose w/ the small addition of having a way to specify an encoder's x/y offset, we could cover this sort of case?
- KMS doesn't support custom pixel formats, which a lot of newer SoCs use internally to cut down on bandwidth between hardware blocks.
for custom formats, use a custom fourcc value, this should work.
err, hmm, looks like some of the error checking that was added is a bit too aggressive. What I'd propose is:
#define DRM_FORMAT_CUSTOM 0x80000000
and then 'if (format & DRM_FORMAT_CUSTOM) ... pass through to driver ..'
- KMS doesn't have a way to exchange sync fences. As a hack I managed to pass sync fences into the kernel as properties of the atomic pageflip, but there was no good way to get them back out of the kernel without a side channel.
I was going to recommend property to pass in. Or, we could possibly even just add a generic fence parameter to the pageflip/modeset ioctl, and just pass it through opaquely to the driver. I guess it should not be any harm to the upstream gpu drivers, they'd just ignore it and do same 'ol implicit synchronization.
for returning to userspace, just put the new fence value (if there is just one?) in the ioctl as an out param.
BR, -R
ADF represents display devices as collections of overlay engines and interfaces. Overlay engines (struct adf_overlay_engine) scan out images and interfaces (struct adf_interface) display those images. Overlay engines and interfaces can be connected in any n-to-n configuration that the hardware supports.
Clients issue atomic updates to the screen by passing in a list of buffers (struct adf_buffer) consisting of dma-buf handles, sync fences, and basic metadata like format and size. If this involves composing multiple buffers, clients include a block of custom data describing the actual composition (scaling, z-order, blending, etc.) in a driver-specific format.
Drivers provide hooks to validate these custom data blocks and commit the new configuration to hardware. ADF handles importing the dma-bufs and fences, waiting on incoming sync fences before committing, advancing the display's sync timeline, and releasing dma-bufs once they're removed from the screen.
ADF represents pixel formats using DRM-style fourccs, and automatically sanity-checks buffer sizes when using one of the formats listed in drm_fourcc.h. Drivers can support custom fourccs if they provide hooks to validate buffers that use them.
ADF also provides driver hooks for modesetting, managing and reporting hardware events like vsync, and changing DPMS state. These are documented in struct adf_{obj,overlay_engine,interface,device}_ops, and are similar to the equivalent DRM ops.
Greg Hackmann (3): video: add atomic display framework video: adf: add display core helper video: adf: add memblock helper
Laurent Pinchart (1): video: Add generic display entity core
drivers/video/Kconfig | 2 + drivers/video/Makefile | 2 + drivers/video/adf/Kconfig | 15 + drivers/video/adf/Makefile | 14 + drivers/video/adf/adf.c | 1013 ++++++++++++++++++++++++++++++++++ drivers/video/adf/adf.h | 49 ++ drivers/video/adf/adf_client.c | 853 ++++++++++++++++++++++++++++ drivers/video/adf/adf_display.c | 123 +++++ drivers/video/adf/adf_fops.c | 982 ++++++++++++++++++++++++++++++++ drivers/video/adf/adf_fops.h | 37 ++ drivers/video/adf/adf_fops32.c | 217 ++++++++ drivers/video/adf/adf_fops32.h | 78 +++ drivers/video/adf/adf_memblock.c | 150 +++++ drivers/video/adf/adf_sysfs.c | 291 ++++++++++ drivers/video/adf/adf_sysfs.h | 33 ++ drivers/video/adf/adf_trace.h | 93 ++++ drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/display-core.c | 362 ++++++++++++ include/video/adf.h | 743 +++++++++++++++++++++++++ include/video/adf_client.h | 61 ++ include/video/adf_display.h | 31 ++ include/video/adf_format.h | 282 ++++++++++ include/video/adf_memblock.h | 20 + include/video/display.h | 150 +++++ 25 files changed, 5606 insertions(+) create mode 100644 drivers/video/adf/Kconfig create mode 100644 drivers/video/adf/Makefile create mode 100644 drivers/video/adf/adf.c create mode 100644 drivers/video/adf/adf.h create mode 100644 drivers/video/adf/adf_client.c create mode 100644 drivers/video/adf/adf_display.c create mode 100644 drivers/video/adf/adf_fops.c create mode 100644 drivers/video/adf/adf_fops.h create mode 100644 drivers/video/adf/adf_fops32.c create mode 100644 drivers/video/adf/adf_fops32.h create mode 100644 drivers/video/adf/adf_memblock.c create mode 100644 drivers/video/adf/adf_sysfs.c create mode 100644 drivers/video/adf/adf_sysfs.h create mode 100644 drivers/video/adf/adf_trace.h create mode 100644 drivers/video/display/Kconfig create mode 100644 drivers/video/display/Makefile create mode 100644 drivers/video/display/display-core.c create mode 100644 include/video/adf.h create mode 100644 include/video/adf_client.h create mode 100644 include/video/adf_display.h create mode 100644 include/video/adf_format.h create mode 100644 include/video/adf_memblock.h create mode 100644 include/video/display.h
-- 1.8.0
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Aug 29, 2013 at 5:51 AM, Rob Clark robdclark@gmail.com wrote:
- The API is geared toward updating one object at a time. Android's graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible. I'm open to suggestions, of course, but the approach I was going for was to at least do most of the boring work for this in drm/kms core (ie. w/ the drm_{plane,crtc,etc}_state stuff)
Yeah, I think Rob's design that trickle-feeds the entire state to the driver is ok (preferrably using a driver-allocated state tracking object or so). And then we'll let some helper code make this sane for most drivers.
I guess especially for pageflips a mode where we just pass in new buffers (and leave all the blending as-is) would be useful, maybe we could add a flag that says "keep all other properties as-is". A second flag for "reset properties to defaults" would also be good, where default = the primary plane can display unobstructed. So mostly just for boot splash screens, fbcon, ...
- Some SoCs don't map well to KMS's CRTC + planes + encoders + connector model. At the time I was dealing with hardware where the blending engines didn't have their own framebuffer (they could only scan out constant colors or mix the output of other blocks), and you needed to gang several mixers together to drive high-res displays.
currently you can connect a crtc to multiple encoders (at least for some hw).. I suppose w/ the small addition of having a way to specify an encoder's x/y offset, we could cover this sort of case?
Hm, I've thought that if we convert the primary plane to a real plane we'd already have that. Then you can just place planes at x/y offset to construct the entire scanout area. Planes also have a crtc mask, as do connectors (through the encoders) so the n-to-m mapping is also possible on the output side afaics.
- KMS doesn't support custom pixel formats, which a lot of newer SoCs use internally to cut down on bandwidth between hardware blocks.
for custom formats, use a custom fourcc value, this should work.
err, hmm, looks like some of the error checking that was added is a bit too aggressive. What I'd propose is:
#define DRM_FORMAT_CUSTOM 0x80000000
and then 'if (format & DRM_FORMAT_CUSTOM) ... pass through to driver ..'
Imo we should just convert primary planes to real planes so that we can do the same table-based checking for them as we do for secondary planes right now. And then we can just ditch format_check and allow drivers to use whatever pleases them. Of course reserving a driver specific range would be good, otoh we could just add more stuff to the drm_fourcc.h header.
Cheers, Daniel
On Thu, Aug 29, 2013 at 09:01:01AM +0200, Daniel Vetter wrote:
On Thu, Aug 29, 2013 at 5:51 AM, Rob Clark robdclark@gmail.com wrote:
- The API is geared toward updating one object at a time. Android's graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible. I'm open to suggestions, of course, but the approach I was going for was to at least do most of the boring work for this in drm/kms core (ie. w/ the drm_{plane,crtc,etc}_state stuff)
Yeah, I think Rob's design that trickle-feeds the entire state to the driver is ok (preferrably using a driver-allocated state tracking object or so). And then we'll let some helper code make this sane for most drivers.
I guess especially for pageflips a mode where we just pass in new buffers (and leave all the blending as-is) would be useful, maybe we could add a flag that says "keep all other properties as-is".
That's the normal mode of operation as far as I'm concerned. If the user doesn't specify a prop, it retains its current value.
Also I don't generally want magic properties that flip their state in response to a change in other seemingly unrelated property. Either the user specifies a coherent state, or we return an error.
A second flag for "reset properties to defaults" would also be good, where default = the primary plane can display unobstructed. So mostly just for boot splash screens, fbcon, ...
That could be useful, or we could maintain per-master state/context.
But I don't consider the planes themselves the major issue here. It's very easy for the client to disable all unneeded planes explicitly. It's all the other stuff like various methods of gamma adjustments, various other color adjustemnt thingys, possibly some blending modes, the output quantization range stuff, etc.
- Some SoCs don't map well to KMS's CRTC + planes + encoders + connector model. At the time I was dealing with hardware where the blending engines didn't have their own framebuffer (they could only scan out constant colors or mix the output of other blocks), and you needed to gang several mixers together to drive high-res displays.
currently you can connect a crtc to multiple encoders (at least for some hw).. I suppose w/ the small addition of having a way to specify an encoder's x/y offset, we could cover this sort of case?
Hm, I've thought that if we convert the primary plane to a real plane we'd already have that. Then you can just place planes at x/y offset to construct the entire scanout area. Planes also have a crtc mask, as do connectors (through the encoders) so the n-to-m mapping is also possible on the output side afaics.
The all planes approach only works here if your drm_crtc is an abstract thing composed of multiple hardware mixers/blenders/whatever you call them.
- KMS doesn't support custom pixel formats, which a lot of newer SoCs use internally to cut down on bandwidth between hardware blocks.
for custom formats, use a custom fourcc value, this should work.
err, hmm, looks like some of the error checking that was added is a bit too aggressive. What I'd propose is:
#define DRM_FORMAT_CUSTOM 0x80000000
and then 'if (format & DRM_FORMAT_CUSTOM) ... pass through to driver ..'
Imo we should just convert primary planes to real planes so that we can do the same table-based checking for them as we do for secondary planes right now.
My plan for i915 is to convert them to drm_planes but first keep them hidden via Rob's private plane mechanism.
After that we need to figure out how we can expose them to userspace w/o risking breaking stuff too much. Maybe a new ioctl to enumerate private planes only? And the maybe only accept direct use of private planes via the atomic API, not through the old setplane API. Or if people want to use setplane we could add a flag there indicating that the user is aware of private planes and wants to use them.
And then we can just ditch format_check
I'd like to keep the check because it provides a small barrier against people just adding formats in driver code w/o even updating drm_fourcc.h. If we need the extra flexibility, let's add the custom bit, and then user space will know all bets are off when dealing with such formats.
and allow drivers to use whatever pleases them. Of course reserving a driver specific range would be good, otoh we could just add more stuff to the drm_fourcc.h header.
Cheers, Daniel
Daniel Vetter Software Engineer, Intel Corporation +41 (0) 79 365 57 48 - http://blog.ffwll.ch _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Aug 29, 2013 at 10:58:51AM +0300, Ville Syrjälä wrote:
After that we need to figure out how we can expose them to userspace w/o risking breaking stuff too much. Maybe a new ioctl to enumerate private planes only? And the maybe only accept direct use of private planes via the atomic API, not through the old setplane API. Or if people want to use setplane we could add a flag there indicating that the user is aware of private planes and wants to use them.
FWIW, in my yet to be published new stereo 3D series, I added a DRM_SET_CAP ioctl for userspace to tell DRM to expose some stereo bits in the mode structure.
Making it possible for the primary plane to be exposed as a drm_plane could be the second user of this ioctl (well, assuming people are fine with this new ioctl, a whole different story, but now that'd be 2 users of it).
On Thu, Aug 29, 2013 at 3:58 AM, Ville Syrjälä ville.syrjala@linux.intel.com wrote:
Imo we should just convert primary planes to real planes so that we can do the same table-based checking for them as we do for secondary planes right now.
My plan for i915 is to convert them to drm_planes but first keep them hidden via Rob's private plane mechanism.
After that we need to figure out how we can expose them to userspace w/o risking breaking stuff too much. Maybe a new ioctl to enumerate private planes only? And the maybe only accept direct use of private planes via the atomic API, not through the old setplane API. Or if people want to use setplane we could add a flag there indicating that the user is aware of private planes and wants to use them.
just fwiw, my thinking for private/primary[1] planes was to add a new array at the end of getplaneresources. This is really all we need. Old userspace doesn't see the new field, and continues on happily. New userspace sees the primary planes (and their possible_crtcs), and realizes that if it is not using a crtc, it can possibly grab that plane for other use.
BR, -R
[1] I think "primary" is actually a better name, despite the fact that I initially called 'em "private"
On Thu, Aug 29, 2013 at 3:01 AM, Daniel Vetter daniel.vetter@ffwll.ch wrote:
On Thu, Aug 29, 2013 at 5:51 AM, Rob Clark robdclark@gmail.com wrote:
- The API is geared toward updating one object at a time. Android's graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible. I'm open to suggestions, of course, but the approach I was going for was to at least do most of the boring work for this in drm/kms core (ie. w/ the drm_{plane,crtc,etc}_state stuff)
Yeah, I think Rob's design that trickle-feeds the entire state to the driver is ok (preferrably using a driver-allocated state tracking object or so). And then we'll let some helper code make this sane for most drivers.
I guess especially for pageflips a mode where we just pass in new buffers (and leave all the blending as-is) would be useful, maybe we could add a flag that says "keep all other properties as-is". A second flag for "reset properties to defaults" would also be good, where default = the primary plane can display unobstructed. So mostly just for boot splash screens, fbcon, ...
- Some SoCs don't map well to KMS's CRTC + planes + encoders + connector model. At the time I was dealing with hardware where the blending engines didn't have their own framebuffer (they could only scan out constant colors or mix the output of other blocks), and you needed to gang several mixers together to drive high-res displays.
currently you can connect a crtc to multiple encoders (at least for some hw).. I suppose w/ the small addition of having a way to specify an encoder's x/y offset, we could cover this sort of case?
Hm, I've thought that if we convert the primary plane to a real plane we'd already have that. Then you can just place planes at x/y offset to construct the entire scanout area. Planes also have a crtc mask, as do connectors (through the encoders) so the n-to-m mapping is also possible on the output side afaics.
yeah, that is probably better
- KMS doesn't support custom pixel formats, which a lot of newer SoCs use internally to cut down on bandwidth between hardware blocks.
for custom formats, use a custom fourcc value, this should work.
err, hmm, looks like some of the error checking that was added is a bit too aggressive. What I'd propose is:
#define DRM_FORMAT_CUSTOM 0x80000000
and then 'if (format & DRM_FORMAT_CUSTOM) ... pass through to driver ..'
Imo we should just convert primary planes to real planes so that we can do the same table-based checking for them as we do for secondary planes right now. And then we can just ditch format_check and allow drivers to use whatever pleases them. Of course reserving a driver specific range would be good, otoh we could just add more stuff to the drm_fourcc.h header.
agreed. But we also have to fix the format_check() when you try to create an fb ;-)
BR, -R
Cheers, Daniel
Daniel Vetter Software Engineer, Intel Corporation +41 (0) 79 365 57 48 - http://blog.ffwll.ch
On Wed, Aug 28, 2013 at 11:51:59PM -0400, Rob Clark wrote:
On Wed, Aug 28, 2013 at 9:51 PM, Greg Hackmann ghackmann@google.com wrote:
Hi,
ADF is an experimental display framework that I designed after experimenting with a KMS-based hardware composer for Android. ADF started as an proof-of-concept implemented from scratch, so right now it's a separate framework rather than a patchstack on top of KMS. If there's community interest, moving forward I'd like to merge its functionality into KMS rather than keep it as a separate thing.
I'm going to talk about ADF at the Android and Graphics session at Linux Plumbers. The documentation's not done but I wanted to post these patches to people a heads-up about ADF. If there's interest I can write up some more formal documentation ahead of Plumbers.
I designed ADF to deal with some serious issues I had working with KMS:
- The API is geared toward updating one object at a time. Android's graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible.
I guess the only reason adf is a bit different is that there can only be one custom (driver specific!) blob in the ioctl, so the driver is just free to dump that directly into whatever internal structure it uses to store the full state. So it just frees you from the per-prop state buildup process.
But if the idea would to be totally driver specific anyway, I wouldn't even bother with any of this fancy framework stuff. Just specify some massive driver specific structure with a custom ioctl and call it a day.
I'm open to suggestions, of course, but the approach I was going for was to at least do most of the boring work for this in drm/kms core (ie. w/ the drm_{plane,crtc,etc}_state stuff)
I'm of two minds about this. On the other hand it would be great to have the core take care of the boring stuff as you say, but on the other hand the driver most likely will duplicate a bunch of that stuff to some internal structures. So there's a slight danger of getting those two out of sync somehow.
But I guess relieving the drivers from the boring stuff wins, and we can live with the duplication. Or maybe if we get the core stuff right, we can avoid most duplication on the driver side.
- Some SoCs don't map well to KMS's CRTC + planes + encoders + connector model. At the time I was dealing with hardware where the blending engines didn't have their own framebuffer (they could only scan out constant colors or mix the output of other blocks), and you needed to gang several mixers together to drive high-res displays.
currently you can connect a crtc to multiple encoders (at least for some hw).. I suppose w/ the small addition of having a way to specify an encoder's x/y offset, we could cover this sort of case?
Hmm. Yeah simply killing the fb->crtc link (which is definitely on my TODO list for the atomic API) isn't enough to cover this case. At first glance the x/y offset in the encoder seems like a reasonable approach to me.
But there is one other issue here, and that is how to represent the connector level. Do we have multiple active connectors for such displays, or just one? If we would go with one connector, we'd need to link multiple encoders to the same connector, which isn't possibly currently.
The other option would be to abstract the hardware a bit, and just expose one crtc to userspace, but internally build it up from multiple mixers.
- KMS doesn't support custom pixel formats, which a lot of newer SoCs use internally to cut down on bandwidth between hardware blocks.
for custom formats, use a custom fourcc value, this should work.
err, hmm, looks like some of the error checking that was added is a bit too aggressive. What I'd propose is:
#define DRM_FORMAT_CUSTOM 0x80000000
and then 'if (format & DRM_FORMAT_CUSTOM) ... pass through to driver ..'
Bit 31 is already taken :) It has to be one of the other three high bits.
The aggresive checking was added precisely to avoid people adding formats at a moments whim. We kind of blew that with the exynos NV12MT format, but then again that thing still hasn't been added to format_check() so I guess it's not _that_ important. Maybe we could "rewrite" history a bit and move that to live under the new DRM_FORMAT_CUSTOM bit as well (assuming we add such a thing).
- KMS doesn't have a way to exchange sync fences. As a hack I managed to pass sync fences into the kernel as properties of the atomic pageflip, but there was no good way to get them back out of the kernel without a side channel.
I was going to recommend property to pass in. Or, we could possibly even just add a generic fence parameter to the pageflip/modeset ioctl, and just pass it through opaquely to the driver. I guess it should not be any harm to the upstream gpu drivers, they'd just ignore it and do same 'ol implicit synchronization.
for returning to userspace, just put the new fence value (if there is just one?) in the ioctl as an out param.
Or just add an array of them if we need many. My design for the atomic ioctl is already variable size, so adding one more special purpose list of things isn't that far fetched, assuming there's a good reason for it. Of course the caller has to specify the max number of things it's expecting back since it has to reserve enough space for them.
Another "multiple return values" issue I was thinking is the error reporting. It might be semi-useful to hand back per object errors. Then the user might be able to just drop those objects from the list and try again. But there are probably too many ways that things can still fail, and it's just not worth trying to iterate the config too much as that would take time, possibly leading to missed deadline.
BR, -R
ADF represents display devices as collections of overlay engines and interfaces. Overlay engines (struct adf_overlay_engine) scan out images and interfaces (struct adf_interface) display those images. Overlay engines and interfaces can be connected in any n-to-n configuration that the hardware supports.
Clients issue atomic updates to the screen by passing in a list of buffers (struct adf_buffer) consisting of dma-buf handles, sync fences, and basic metadata like format and size. If this involves composing multiple buffers, clients include a block of custom data describing the actual composition (scaling, z-order, blending, etc.) in a driver-specific format.
Drivers provide hooks to validate these custom data blocks and commit the new configuration to hardware. ADF handles importing the dma-bufs and fences, waiting on incoming sync fences before committing, advancing the display's sync timeline, and releasing dma-bufs once they're removed from the screen.
ADF represents pixel formats using DRM-style fourccs, and automatically sanity-checks buffer sizes when using one of the formats listed in drm_fourcc.h. Drivers can support custom fourccs if they provide hooks to validate buffers that use them.
ADF also provides driver hooks for modesetting, managing and reporting hardware events like vsync, and changing DPMS state. These are documented in struct adf_{obj,overlay_engine,interface,device}_ops, and are similar to the equivalent DRM ops.
Greg Hackmann (3): video: add atomic display framework video: adf: add display core helper video: adf: add memblock helper
Laurent Pinchart (1): video: Add generic display entity core
drivers/video/Kconfig | 2 + drivers/video/Makefile | 2 + drivers/video/adf/Kconfig | 15 + drivers/video/adf/Makefile | 14 + drivers/video/adf/adf.c | 1013 ++++++++++++++++++++++++++++++++++ drivers/video/adf/adf.h | 49 ++ drivers/video/adf/adf_client.c | 853 ++++++++++++++++++++++++++++ drivers/video/adf/adf_display.c | 123 +++++ drivers/video/adf/adf_fops.c | 982 ++++++++++++++++++++++++++++++++ drivers/video/adf/adf_fops.h | 37 ++ drivers/video/adf/adf_fops32.c | 217 ++++++++ drivers/video/adf/adf_fops32.h | 78 +++ drivers/video/adf/adf_memblock.c | 150 +++++ drivers/video/adf/adf_sysfs.c | 291 ++++++++++ drivers/video/adf/adf_sysfs.h | 33 ++ drivers/video/adf/adf_trace.h | 93 ++++ drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/display-core.c | 362 ++++++++++++ include/video/adf.h | 743 +++++++++++++++++++++++++ include/video/adf_client.h | 61 ++ include/video/adf_display.h | 31 ++ include/video/adf_format.h | 282 ++++++++++ include/video/adf_memblock.h | 20 + include/video/display.h | 150 +++++ 25 files changed, 5606 insertions(+) create mode 100644 drivers/video/adf/Kconfig create mode 100644 drivers/video/adf/Makefile create mode 100644 drivers/video/adf/adf.c create mode 100644 drivers/video/adf/adf.h create mode 100644 drivers/video/adf/adf_client.c create mode 100644 drivers/video/adf/adf_display.c create mode 100644 drivers/video/adf/adf_fops.c create mode 100644 drivers/video/adf/adf_fops.h create mode 100644 drivers/video/adf/adf_fops32.c create mode 100644 drivers/video/adf/adf_fops32.h create mode 100644 drivers/video/adf/adf_memblock.c create mode 100644 drivers/video/adf/adf_sysfs.c create mode 100644 drivers/video/adf/adf_sysfs.h create mode 100644 drivers/video/adf/adf_trace.h create mode 100644 drivers/video/display/Kconfig create mode 100644 drivers/video/display/Makefile create mode 100644 drivers/video/display/display-core.c create mode 100644 include/video/adf.h create mode 100644 include/video/adf_client.h create mode 100644 include/video/adf_display.h create mode 100644 include/video/adf_format.h create mode 100644 include/video/adf_memblock.h create mode 100644 include/video/display.h
-- 1.8.0
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Aug 29, 2013 at 3:36 AM, Ville Syrjälä ville.syrjala@linux.intel.com wrote:
On Wed, Aug 28, 2013 at 11:51:59PM -0400, Rob Clark wrote:
On Wed, Aug 28, 2013 at 9:51 PM, Greg Hackmann ghackmann@google.com wrote:
Hi,
ADF is an experimental display framework that I designed after experimenting with a KMS-based hardware composer for Android. ADF started as an proof-of-concept implemented from scratch, so right now it's a separate framework rather than a patchstack on top of KMS. If there's community interest, moving forward I'd like to merge its functionality into KMS rather than keep it as a separate thing.
I'm going to talk about ADF at the Android and Graphics session at Linux Plumbers. The documentation's not done but I wanted to post these patches to people a heads-up about ADF. If there's interest I can write up some more formal documentation ahead of Plumbers.
I designed ADF to deal with some serious issues I had working with KMS:
- The API is geared toward updating one object at a time. Android's graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible.
I guess the only reason adf is a bit different is that there can only be one custom (driver specific!) blob in the ioctl, so the driver is just free to dump that directly into whatever internal structure it uses to store the full state. So it just frees you from the per-prop state buildup process.
But if the idea would to be totally driver specific anyway, I wouldn't even bother with any of this fancy framework stuff. Just specify some massive driver specific structure with a custom ioctl and call it a day.
I'm open to suggestions, of course, but the approach I was going for was to at least do most of the boring work for this in drm/kms core (ie. w/ the drm_{plane,crtc,etc}_state stuff)
I'm of two minds about this. On the other hand it would be great to have the core take care of the boring stuff as you say, but on the other hand the driver most likely will duplicate a bunch of that stuff to some internal structures. So there's a slight danger of getting those two out of sync somehow.
re: duplicating a bunch of stuff.. I suppose we could make the state objects opaque (void *), and split everything in core that deals with 'em into helpers. That seems a bit like overkill (ie. would only make sense if there was some hw that *only* needed the driver specific derived parameters), but someone speak up if they have hw like this and I can try to make all the core state tracker stuff just helpers instead.
as far as getting out of sync, it should only happen if there is a driver bug (which should be fixed) or if the validate step fails (in which case the new state is thrown away anyway)
But I guess relieving the drivers from the boring stuff wins, and we can live with the duplication. Or maybe if we get the core stuff right, we can avoid most duplication on the driver side.
- Some SoCs don't map well to KMS's CRTC + planes + encoders + connector model. At the time I was dealing with hardware where the blending engines didn't have their own framebuffer (they could only scan out constant colors or mix the output of other blocks), and you needed to gang several mixers together to drive high-res displays.
currently you can connect a crtc to multiple encoders (at least for some hw).. I suppose w/ the small addition of having a way to specify an encoder's x/y offset, we could cover this sort of case?
Hmm. Yeah simply killing the fb->crtc link (which is definitely on my TODO list for the atomic API) isn't enough to cover this case. At first glance the x/y offset in the encoder seems like a reasonable approach to me.
But there is one other issue here, and that is how to represent the connector level. Do we have multiple active connectors for such displays, or just one? If we would go with one connector, we'd need to link multiple encoders to the same connector, which isn't possibly currently.
I guess if you have multiple encoders + multiple connectors for the "ganging" case, then it probably just looks like 2x displays, and nothing more really needed?
I'm a bit fuzzy on what the hw looks like in this "ganging" scenario, so I'm not completely sure what the best approach is. But if we do have hw like this, then it makes sense to support it *somehow* in KMS.
The other option would be to abstract the hardware a bit, and just expose one crtc to userspace, but internally build it up from multiple mixers.
- KMS doesn't support custom pixel formats, which a lot of newer SoCs use internally to cut down on bandwidth between hardware blocks.
for custom formats, use a custom fourcc value, this should work.
err, hmm, looks like some of the error checking that was added is a bit too aggressive. What I'd propose is:
#define DRM_FORMAT_CUSTOM 0x80000000
and then 'if (format & DRM_FORMAT_CUSTOM) ... pass through to driver ..'
Bit 31 is already taken :) It has to be one of the other three high bits.
oh, right, missed that.. ok, then 0x00800000 ;-)
The aggresive checking was added precisely to avoid people adding formats at a moments whim. We kind of blew that with the exynos NV12MT format, but then again that thing still hasn't been added to format_check() so I guess it's not _that_ important. Maybe we could "rewrite" history a bit and move that to live under the new DRM_FORMAT_CUSTOM bit as well (assuming we add such a thing).
perhaps even dev->driver->validate_custom_format() could be useful?
we probably do want to keep an eye on custom formats that are added to make sure they are really actually custom.. but either way, we do want to allow for custom tiled and/or compressed formats.
- KMS doesn't have a way to exchange sync fences. As a hack I managed to pass sync fences into the kernel as properties of the atomic pageflip, but there was no good way to get them back out of the kernel without a side channel.
I was going to recommend property to pass in. Or, we could possibly even just add a generic fence parameter to the pageflip/modeset ioctl, and just pass it through opaquely to the driver. I guess it should not be any harm to the upstream gpu drivers, they'd just ignore it and do same 'ol implicit synchronization.
for returning to userspace, just put the new fence value (if there is just one?) in the ioctl as an out param.
Or just add an array of them if we need many. My design for the atomic ioctl is already variable size, so adding one more special purpose list of things isn't that far fetched, assuming there's a good reason for it. Of course the caller has to specify the max number of things it's expecting back since it has to reserve enough space for them.
I wasn't completely sure if it was one fence, or N fences. Either way, drm core doesn't really need to know what they are, just that it should pass 'em through to the driver. So if it helps align android and upstream, I'm ok with adding something.
Another "multiple return values" issue I was thinking is the error reporting. It might be semi-useful to hand back per object errors. Then the user might be able to just drop those objects from the list and try again. But there are probably too many ways that things can still fail, and it's just not worth trying to iterate the config too much as that would take time, possibly leading to missed deadline.
yeah.. maybe not worth it on the page flip due to the time constraint. At least for mode set, it would be worthwhile to try to explain to userspace why it failed. (Ie. think 3x display on ivb sort of scenario where you have inter-dependencies between modes.) I'm ok to punt on this for v1 though. The nice thing about how drm_ioctl() works, if some care is taken we can actually extend ioctls rather than having to add a new ioctl.
BR, -R
BR, -R
ADF represents display devices as collections of overlay engines and interfaces. Overlay engines (struct adf_overlay_engine) scan out images and interfaces (struct adf_interface) display those images. Overlay engines and interfaces can be connected in any n-to-n configuration that the hardware supports.
Clients issue atomic updates to the screen by passing in a list of buffers (struct adf_buffer) consisting of dma-buf handles, sync fences, and basic metadata like format and size. If this involves composing multiple buffers, clients include a block of custom data describing the actual composition (scaling, z-order, blending, etc.) in a driver-specific format.
Drivers provide hooks to validate these custom data blocks and commit the new configuration to hardware. ADF handles importing the dma-bufs and fences, waiting on incoming sync fences before committing, advancing the display's sync timeline, and releasing dma-bufs once they're removed from the screen.
ADF represents pixel formats using DRM-style fourccs, and automatically sanity-checks buffer sizes when using one of the formats listed in drm_fourcc.h. Drivers can support custom fourccs if they provide hooks to validate buffers that use them.
ADF also provides driver hooks for modesetting, managing and reporting hardware events like vsync, and changing DPMS state. These are documented in struct adf_{obj,overlay_engine,interface,device}_ops, and are similar to the equivalent DRM ops.
Greg Hackmann (3): video: add atomic display framework video: adf: add display core helper video: adf: add memblock helper
Laurent Pinchart (1): video: Add generic display entity core
drivers/video/Kconfig | 2 + drivers/video/Makefile | 2 + drivers/video/adf/Kconfig | 15 + drivers/video/adf/Makefile | 14 + drivers/video/adf/adf.c | 1013 ++++++++++++++++++++++++++++++++++ drivers/video/adf/adf.h | 49 ++ drivers/video/adf/adf_client.c | 853 ++++++++++++++++++++++++++++ drivers/video/adf/adf_display.c | 123 +++++ drivers/video/adf/adf_fops.c | 982 ++++++++++++++++++++++++++++++++ drivers/video/adf/adf_fops.h | 37 ++ drivers/video/adf/adf_fops32.c | 217 ++++++++ drivers/video/adf/adf_fops32.h | 78 +++ drivers/video/adf/adf_memblock.c | 150 +++++ drivers/video/adf/adf_sysfs.c | 291 ++++++++++ drivers/video/adf/adf_sysfs.h | 33 ++ drivers/video/adf/adf_trace.h | 93 ++++ drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/display-core.c | 362 ++++++++++++ include/video/adf.h | 743 +++++++++++++++++++++++++ include/video/adf_client.h | 61 ++ include/video/adf_display.h | 31 ++ include/video/adf_format.h | 282 ++++++++++ include/video/adf_memblock.h | 20 + include/video/display.h | 150 +++++ 25 files changed, 5606 insertions(+) create mode 100644 drivers/video/adf/Kconfig create mode 100644 drivers/video/adf/Makefile create mode 100644 drivers/video/adf/adf.c create mode 100644 drivers/video/adf/adf.h create mode 100644 drivers/video/adf/adf_client.c create mode 100644 drivers/video/adf/adf_display.c create mode 100644 drivers/video/adf/adf_fops.c create mode 100644 drivers/video/adf/adf_fops.h create mode 100644 drivers/video/adf/adf_fops32.c create mode 100644 drivers/video/adf/adf_fops32.h create mode 100644 drivers/video/adf/adf_memblock.c create mode 100644 drivers/video/adf/adf_sysfs.c create mode 100644 drivers/video/adf/adf_sysfs.h create mode 100644 drivers/video/adf/adf_trace.h create mode 100644 drivers/video/display/Kconfig create mode 100644 drivers/video/display/Makefile create mode 100644 drivers/video/display/display-core.c create mode 100644 include/video/adf.h create mode 100644 include/video/adf_client.h create mode 100644 include/video/adf_display.h create mode 100644 include/video/adf_format.h create mode 100644 include/video/adf_memblock.h create mode 100644 include/video/display.h
-- 1.8.0
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Ville Syrjälä Intel OTC
On Thu, Aug 29, 2013 at 5:54 AM, Rob Clark robdclark@gmail.com wrote:
I guess if you have multiple encoders + multiple connectors for the "ganging" case, then it probably just looks like 2x displays, and nothing more really needed?
I'm a bit fuzzy on what the hw looks like in this "ganging" scenario, so I'm not completely sure what the best approach is. But if we do have hw like this, then it makes sense to support it *somehow* in KMS.
I don't have the hardware anymore so this is all working from memory, it didn't look like two independent displays. You had to explicitly set up connections between the two mixers to deal with things like scaled overlays that spanned both mixers (there was some in-hardware magic to make sure there wasn't a visible seam where the two mixers met).
On Thu, Aug 29, 2013 at 7:34 PM, Greg Hackmann ghackmann@google.com wrote:
On Thu, Aug 29, 2013 at 5:54 AM, Rob Clark robdclark@gmail.com wrote:
I guess if you have multiple encoders + multiple connectors for the "ganging" case, then it probably just looks like 2x displays, and nothing more really needed?
I'm a bit fuzzy on what the hw looks like in this "ganging" scenario, so I'm not completely sure what the best approach is. But if we do have hw like this, then it makes sense to support it *somehow* in KMS.
I don't have the hardware anymore so this is all working from memory, it didn't look like two independent displays. You had to explicitly set up connections between the two mixers to deal with things like scaled overlays that spanned both mixers (there was some in-hardware magic to make sure there wasn't a visible seam where the two mixers met).
Ok, sounds like mixer == crtc (ie. the thing the combines one or more planes)? So it sounds maybe a bit like:
plane0_0 -\ ... +---> CRTC -\ plane0_n -/ | +--> encoder -> connector plane1_0 -\ | ... +---> CRTC -/ plane1_n -/
?
BR, -R
On Fri, Aug 30, 2013 at 10:00:28AM -0400, Rob Clark wrote:
On Thu, Aug 29, 2013 at 7:34 PM, Greg Hackmann ghackmann@google.com wrote:
On Thu, Aug 29, 2013 at 5:54 AM, Rob Clark robdclark@gmail.com wrote:
I guess if you have multiple encoders + multiple connectors for the "ganging" case, then it probably just looks like 2x displays, and nothing more really needed?
I'm a bit fuzzy on what the hw looks like in this "ganging" scenario, so I'm not completely sure what the best approach is. But if we do have hw like this, then it makes sense to support it *somehow* in KMS.
I don't have the hardware anymore so this is all working from memory, it didn't look like two independent displays. You had to explicitly set up connections between the two mixers to deal with things like scaled overlays that spanned both mixers (there was some in-hardware magic to make sure there wasn't a visible seam where the two mixers met).
Ok, sounds like mixer == crtc (ie. the thing the combines one or more planes)? So it sounds maybe a bit like:
plane0_0 -\ ... +---> CRTC -\ plane0_n -/ | +--> encoder -> connector plane1_0 -\ | ... +---> CRTC -/ plane1_n -/
So I wonder if we should just add the x,y,w,h output parameters to crtc. That could also be used to specify borders in the normal one crtc per encoder case. Although the border color is then a bit a question mark. Supposedly the crtc can have its own background color, but i guess the border color need not match that necessarily. So maybe add an encoder bg color property too (or maybe just slap it to the connector since we don't currently have encoder properties).
Another related thing I really want to expose is the crtc input size (ie. the coordinate space that the planes' output coordinates live in). That way the user will get explicit control over the scaler in the crtc (panel fitter in i915 lingo).
On Fri, Aug 30, 2013 at 10:00:28AM -0400, Rob Clark wrote:
On Thu, Aug 29, 2013 at 7:34 PM, Greg Hackmann ghackmann@google.com wrote:
On Thu, Aug 29, 2013 at 5:54 AM, Rob Clark robdclark@gmail.com wrote:
I guess if you have multiple encoders + multiple connectors for the "ganging" case, then it probably just looks like 2x displays, and nothing more really needed?
I'm a bit fuzzy on what the hw looks like in this "ganging" scenario, so I'm not completely sure what the best approach is. But if we do have hw like this, then it makes sense to support it *somehow* in KMS.
I don't have the hardware anymore so this is all working from memory, it didn't look like two independent displays. You had to explicitly set up connections between the two mixers to deal with things like scaled overlays that spanned both mixers (there was some in-hardware magic to make sure there wasn't a visible seam where the two mixers met).
Ok, sounds like mixer == crtc (ie. the thing the combines one or more planes)? So it sounds maybe a bit like:
plane0_0 -\ ... +---> CRTC -\ plane0_n -/ | +--> encoder -> connector plane1_0 -\ | ... +---> CRTC -/ plane1_n -/
More than one crtc to the same encoder feels funny. Can't we just keep this mixer thing internal to the kms driver by making the failure conditions a bit more obtuse to userspace? Either way we need highly special userspace to get this thing going, since a generic modesetting driver probably can't figure out that it needs to split up the logical framebuffer into smaller planes to be able to actually shovel all the pixels to the screen. Thus far the assumption we've backed into all dumb kms drivers is that the crtc/plane limit is also the limit for the maximum output resolution ...
Could we have a notch more details on how this is exactly wired up?
Another approach would be a set of encoders for each part of the display and some metadata (like left/right-of, ...) so that userspace understands how the aggregate display is stitched togeter.
Cheers, Daniel
On Mon, Sep 2, 2013 at 3:39 AM, Daniel Vetter daniel@ffwll.ch wrote:
On Fri, Aug 30, 2013 at 10:00:28AM -0400, Rob Clark wrote:
On Thu, Aug 29, 2013 at 7:34 PM, Greg Hackmann ghackmann@google.com wrote:
On Thu, Aug 29, 2013 at 5:54 AM, Rob Clark robdclark@gmail.com wrote:
I guess if you have multiple encoders + multiple connectors for the "ganging" case, then it probably just looks like 2x displays, and nothing more really needed?
I'm a bit fuzzy on what the hw looks like in this "ganging" scenario, so I'm not completely sure what the best approach is. But if we do have hw like this, then it makes sense to support it *somehow* in KMS.
I don't have the hardware anymore so this is all working from memory, it didn't look like two independent displays. You had to explicitly set up connections between the two mixers to deal with things like scaled overlays that spanned both mixers (there was some in-hardware magic to make sure there wasn't a visible seam where the two mixers met).
Ok, sounds like mixer == crtc (ie. the thing the combines one or more planes)? So it sounds maybe a bit like:
plane0_0 -\ ... +---> CRTC -\ plane0_n -/ | +--> encoder -> connector plane1_0 -\ | ... +---> CRTC -/ plane1_n -/
More than one crtc to the same encoder feels funny. Can't we just keep this mixer thing internal to the kms driver by making the failure conditions a bit more obtuse to userspace?
If there is not also a case where you'd want userspace to be able to use the two CRTC's independently, then I guess we can hide it in kernel. Otherwise, it seems that would get a bit awkward.
Either way we need highly special userspace to get this thing going, since a generic modesetting driver probably can't figure out that it needs to split up the logical framebuffer into smaller planes to be able to actually shovel all the pixels to the screen. Thus far the assumption we've backed into all dumb kms drivers is that the crtc/plane limit is also the limit for the maximum output resolution ...
Yeah, that is the case today. But seems like we should be able to expose crtc/plane limits so that userspace can figure it out in a generic way.
Note that nothing actually has to split up fb's, but just setup planes to scanout a single fb at the appropriate x/y offsets.
Could we have a notch more details on how this is exactly wired up?
Another approach would be a set of encoders for each part of the display and some metadata (like left/right-of, ...) so that userspace understands how the aggregate display is stitched togeter.
yeah, I think understanding the hw better should help understand whether N CRTCs to one encoder, or N encoders to one connector, or ??
BR, -R
Cheers, Daniel
Daniel Vetter Software Engineer, Intel Corporation +41 (0) 79 365 57 48 - http://blog.ffwll.ch
On Mon, Sep 2, 2013 at 9:19 AM, Rob Clark robdclark@gmail.com wrote:
On Mon, Sep 2, 2013 at 3:39 AM, Daniel Vetter daniel@ffwll.ch wrote:
On Fri, Aug 30, 2013 at 10:00:28AM -0400, Rob Clark wrote:
On Thu, Aug 29, 2013 at 7:34 PM, Greg Hackmann ghackmann@google.com wrote:
On Thu, Aug 29, 2013 at 5:54 AM, Rob Clark robdclark@gmail.com wrote:
I guess if you have multiple encoders + multiple connectors for the "ganging" case, then it probably just looks like 2x displays, and nothing more really needed?
I'm a bit fuzzy on what the hw looks like in this "ganging" scenario, so I'm not completely sure what the best approach is. But if we do have hw like this, then it makes sense to support it *somehow* in KMS.
I don't have the hardware anymore so this is all working from memory, it didn't look like two independent displays. You had to explicitly set up connections between the two mixers to deal with things like scaled overlays that spanned both mixers (there was some in-hardware magic to make sure there wasn't a visible seam where the two mixers met).
Ok, sounds like mixer == crtc (ie. the thing the combines one or more planes)? So it sounds maybe a bit like:
plane0_0 -\ ... +---> CRTC -\ plane0_n -/ | +--> encoder -> connector plane1_0 -\ | ... +---> CRTC -/ plane1_n -/
More than one crtc to the same encoder feels funny. Can't we just keep this mixer thing internal to the kms driver by making the failure conditions a bit more obtuse to userspace?
If there is not also a case where you'd want userspace to be able to use the two CRTC's independently, then I guess we can hide it in kernel. Otherwise, it seems that would get a bit awkward.
Either way we need highly special userspace to get this thing going, since a generic modesetting driver probably can't figure out that it needs to split up the logical framebuffer into smaller planes to be able to actually shovel all the pixels to the screen. Thus far the assumption we've backed into all dumb kms drivers is that the crtc/plane limit is also the limit for the maximum output resolution ...
Yeah, that is the case today. But seems like we should be able to expose crtc/plane limits so that userspace can figure it out in a generic way.
Note that nothing actually has to split up fb's, but just setup planes to scanout a single fb at the appropriate x/y offsets.
Could we have a notch more details on how this is exactly wired up?
Another approach would be a set of encoders for each part of the display and some metadata (like left/right-of, ...) so that userspace understands how the aggregate display is stitched togeter.
yeah, I think understanding the hw better should help understand whether N CRTCs to one encoder, or N encoders to one connector, or ??
On our hardware there is basically a crossbar between the crtcs and the encoders:
GRPH -\ +---> CRTC -| OVL -/ | +--> encoder -> connector GRPH -\ | +---> CRTC -| OVL -/ | +--> encoder -> connector GRPH -\ | +---> CRTC -| OVL -/ | +--> encoder -> connector GRPH -\ | +---> CRTC -| OVL -/
Encoders are hardcoded to connectors, planes (GRPH and OVL) are hardcoded to crtcs, but crtcs can be routed between encoders. There are also cases where you can gang crtcs (e.g., map multiple crtcs to a single encoder) for handling certain corner cases.
Alex
BR, -R
Cheers, Daniel
Daniel Vetter Software Engineer, Intel Corporation +41 (0) 79 365 57 48 - http://blog.ffwll.ch
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Aug 29, 2013 at 12:36 AM, Ville Syrjälä < ville.syrjala@linux.intel.com> wrote:
On Wed, Aug 28, 2013 at 11:51:59PM -0400, Rob Clark wrote:
On Wed, Aug 28, 2013 at 9:51 PM, Greg Hackmann ghackmann@google.com
wrote:
- The API is geared toward updating one object at a time. Android's
graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible.
I guess the only reason adf is a bit different is that there can only be one custom (driver specific!) blob in the ioctl, so the driver is just free to dump that directly into whatever internal structure it uses to store the full state. So it just frees you from the per-prop state buildup process.
Right, the difference is that clients send the complete state they want rather than deltas against the current state. This means the driver doesn't have to track its current state and duplicate it at the beginning of the flip operation, which is a minor pain on hardware with a ton of knobs to twist across different hardware blocks.
Maybe the important question is whether incremental atomic updates is a use-case that clients need in practice. SurfaceFlinger tells the HW composer each frame "here's a complete description of what I want onscreen, make it so" and IIRC Weston works the same way.
I used a blob rather than property/value pairs because the composition is going to be inherently device specific anyway. Display controllers have such different features and constraints that you'd end up with each driver exposing a bunch of hardware-specific properties, and I'm not convinced that's any better than just letting the driver dictate how the requests are structured (modulo a handful of hardware-agnostic properties). I'm not strongly tied to blobs over properties but I think the former's easier on driver developers.
But if the idea would to be totally driver specific anyway, I wouldn't even bother with any of this fancy framework stuff. Just specify some massive driver specific structure with a custom ioctl and call it a day.
I disagree -- this is basically what vendors do today to support Android, and there's a lot of common scaffolding that could go into a framework. The custom ioctl handlers all look reasonably close to this:
1) import dma-bufs and fences from their respective fds 2) map the buffers into the display device 3) validate the buffer sizes against their formats and width/stride/height 4) validate the requested layout doesn't violate hardware constraints 5) hand everything off to a worker that waits for the buffers' sync fences to fire 6) commit the requested layout to hardware 7) unmap and release all the buffers that just left the screen 8) advance the sync timeline
with some leeway on the ordering of (2)-(4) and (7)-(8). ADF handles all of this except for (4) and (6), which are inherently hardware-specific and delegated to driver ops.
On Thu, Aug 29, 2013 at 7:26 PM, Greg Hackmann ghackmann@google.com wrote:
On Thu, Aug 29, 2013 at 12:36 AM, Ville Syrjälä ville.syrjala@linux.intel.com wrote:
On Wed, Aug 28, 2013 at 11:51:59PM -0400, Rob Clark wrote:
On Wed, Aug 28, 2013 at 9:51 PM, Greg Hackmann ghackmann@google.com wrote:
- The API is geared toward updating one object at a time. Android's
graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible.
I guess the only reason adf is a bit different is that there can only be one custom (driver specific!) blob in the ioctl, so the driver is just free to dump that directly into whatever internal structure it uses to store the full state. So it just frees you from the per-prop state buildup process.
Right, the difference is that clients send the complete state they want rather than deltas against the current state. This means the driver doesn't have to track its current state and duplicate it at the beginning of the flip operation, which is a minor pain on hardware with a ton of knobs to twist across different hardware blocks.
Maybe the important question is whether incremental atomic updates is a use-case that clients need in practice. SurfaceFlinger tells the HW composer each frame "here's a complete description of what I want onscreen, make it so" and IIRC Weston works the same way.
weston works this way (although per-display, it handles independent displays each with their own display loop).
But X does things more independently.. although effective use of overlays is a bit difficult with X.. but at least a couple drivers for hw that does not have dedicated hw cursor do use overlays/planes to implement hw cursor.
I used a blob rather than property/value pairs because the composition is going to be inherently device specific anyway. Display controllers have such different features and constraints that you'd end up with each driver exposing a bunch of hardware-specific properties, and I'm not convinced that's any better than just letting the driver dictate how the requests are structured (modulo a handful of hardware-agnostic properties). I'm not strongly tied to blobs over properties but I think the former's easier on driver developers.
weston (or other upcoming wayland compositors) use kms in a relatively generic way, so you don't have a userspace component to to the driver handling the display. This gets rid of a lot of duplicate kms code, which is currently duplicated in each xf86-video-foo.
The idea w/ property based "atomic" KMS is that you would have standard properties for all the generic/core KMS fields (mode, x/y, w/h, etc). And driver custom and semi-custom properties for things that are more hw specific. Ie. if multiple different hw supports some particular feature, for example solid-fill bg color, they would align on the same property name. In userspace you could query the properties on the plane/crtc/etc to see what custom things are supported. I guess you could think of it as the display/kms equivalent to GL extensions.
There are some things which are hard to express, like upscale/downscale/bandwidth limits. So possibly we eventually need to define some userspace plugin API where some hw specific module can help make better decisions about which surfaces to assign to which planes. But I think we want to try to share as much code in common as possible.
BR, -R
But if the idea would to be totally driver specific anyway, I wouldn't even bother with any of this fancy framework stuff. Just specify some massive driver specific structure with a custom ioctl and call it a day.
I disagree -- this is basically what vendors do today to support Android, and there's a lot of common scaffolding that could go into a framework. The custom ioctl handlers all look reasonably close to this:
- import dma-bufs and fences from their respective fds
- map the buffers into the display device
- validate the buffer sizes against their formats and width/stride/height
- validate the requested layout doesn't violate hardware constraints
- hand everything off to a worker that waits for the buffers' sync fences
to fire 6) commit the requested layout to hardware 7) unmap and release all the buffers that just left the screen 8) advance the sync timeline
with some leeway on the ordering of (2)-(4) and (7)-(8). ADF handles all of this except for (4) and (6), which are inherently hardware-specific and delegated to driver ops.
On Thu, Aug 29, 2013 at 04:26:08PM -0700, Greg Hackmann wrote:
On Thu, Aug 29, 2013 at 12:36 AM, Ville Syrjälä < ville.syrjala@linux.intel.com> wrote:
On Wed, Aug 28, 2013 at 11:51:59PM -0400, Rob Clark wrote:
On Wed, Aug 28, 2013 at 9:51 PM, Greg Hackmann ghackmann@google.com
wrote:
- The API is geared toward updating one object at a time. Android's
graphics stack needs the entire screen updated atomically to avoid tearing, and on some SoCs to avoid wedging the display hardware. Rob Clark's atomic modeset patchset worked, but copy/update/commit design meant the driver had to keep a lot more internal state.
I'm not entirely sure how to avoid that, because at least some hw we need to have the entire new-state in order to validate if it is possible.
I guess the only reason adf is a bit different is that there can only be one custom (driver specific!) blob in the ioctl, so the driver is just free to dump that directly into whatever internal structure it uses to store the full state. So it just frees you from the per-prop state buildup process.
Right, the difference is that clients send the complete state they want rather than deltas against the current state. This means the driver doesn't have to track its current state and duplicate it at the beginning of the flip operation, which is a minor pain on hardware with a ton of knobs to twist across different hardware blocks.
You could just keep around the same big blob you would expect from the client, and incrementally update it. Not a big problem in my mind.
Maybe the important question is whether incremental atomic updates is a use-case that clients need in practice. SurfaceFlinger tells the HW composer each frame "here's a complete description of what I want onscreen, make it so" and IIRC Weston works the same way.
Complete is a big word. There can be quite a bit of (either per plane, or per crtc) state that doesn't change all that often, like gamma ramps/luts, color adjustemnt knobs, etc. Some user space applications probably won't even care about most of that, but with your approach they're anyway forced to deal with it.
I used a blob rather than property/value pairs because the composition is going to be inherently device specific anyway. Display controllers have such different features and constraints that you'd end up with each driver exposing a bunch of hardware-specific properties, and I'm not convinced that's any better than just letting the driver dictate how the requests are structured (modulo a handful of hardware-agnostic properties). I'm not strongly tied to blobs over properties but I think the former's easier on driver developers.
Sure, there's a certainly stuff that's hardware specific, but I'm sure most of the properties can be abstracted reasonably well for "normal" hardware.
There are of course hardware specific restrictions on which combinations of property values are valid. I have no good solution to this problem. ATM the plan is just return EINVAL for the whole operation, or possibly allow the driver to adjust things a bit to make it work almost like the user requested. We may need some knob to select between strict and relaxed modes.
linaro-mm-sig@lists.linaro.org