| /* |
| * Copyright (C) 2013 Google, Inc. |
| * adf_modeinfo_{set_name,set_vrefresh} modified from |
| * drivers/gpu/drm/drm_modes.c |
| * adf_format_validate_yuv modified from framebuffer_check in |
| * 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. |
| * |
| */ |
| |
| #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 <video/adf_format.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 DEFINE_IDR(adf_devices); |
| |
| 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_sw_advance_timeline(struct adf_device *dev) |
| { |
| #ifdef CONFIG_SW_SYNC |
| sw_sync_timeline_inc(dev->timeline, 1); |
| #else |
| BUG(); |
| #endif |
| } |
| |
| 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); |
| memcpy(&saved_list, &dev->post_list, sizeof(saved_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 |
| adf_sw_advance_timeline(dev); |
| |
| 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_event_refcount(struct adf_obj *obj, |
| enum adf_event_type type) |
| { |
| struct rb_root *root = &obj->event_refcount; |
| struct rb_node **new = &(root->rb_node); |
| struct 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; |
| refcount->type = type; |
| |
| 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; |
| int old_refcount; |
| int ret; |
| |
| ret = adf_obj_check_supports_event(obj, type); |
| if (ret < 0) |
| return ret; |
| |
| mutex_lock(&obj->event_lock); |
| |
| refcount = adf_obj_find_event_refcount(obj, type); |
| if (!refcount) { |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| old_refcount = refcount->refcount++; |
| |
| if (old_refcount == 0) { |
| obj->ops->set_event(obj, type, true); |
| trace_adf_event_enable(obj, type); |
| } |
| |
| done: |
| mutex_unlock(&obj->event_lock); |
| return ret; |
| } |
| 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; |
| int old_refcount; |
| int ret; |
| |
| ret = adf_obj_check_supports_event(obj, type); |
| if (ret < 0) |
| return ret; |
| |
| |
| mutex_lock(&obj->event_lock); |
| |
| refcount = adf_obj_find_event_refcount(obj, type); |
| if (!refcount) { |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| 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); |
| } |
| |
| done: |
| 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; |
| |
| 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); |
| |
| 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) |
| { |
| int ret; |
| |
| if (ops && ops->supports_event && !ops->set_event) { |
| pr_err("%s: %s implements supports_event but not set_event\n", |
| __func__, adf_obj_type_str(type)); |
| return -EINVAL; |
| } |
| |
| ret = idr_alloc(idr, obj, 0, 0, GFP_KERNEL); |
| if (ret < 0) { |
| pr_err("%s: allocating object id failed: %d\n", __func__, ret); |
| return ret; |
| } |
| obj->id = ret; |
| |
| 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 idr *idr) |
| { |
| struct rb_node *node = rb_first(&obj->event_refcount); |
| |
| while (node) { |
| struct adf_event_refcount *refcount = |
| container_of(node, struct adf_event_refcount, |
| node); |
| rb_erase(&refcount->node, &obj->event_refcount); |
| kfree(refcount); |
| node = rb_first(&obj->event_refcount); |
| } |
| |
| mutex_destroy(&obj->event_lock); |
| idr_remove(idr, obj->id); |
| } |
| |
| /** |
| * 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; |
| |
| if (!ops->validate || !ops->post) { |
| pr_err("%s: device must implement validate and post\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| if (!ops->complete_fence && !ops->advance_timeline) { |
| if (!IS_ENABLED(CONFIG_SW_SYNC)) { |
| pr_err("%s: device requires sw_sync but it is not enabled in the kernel\n", |
| __func__); |
| return -EINVAL; |
| } |
| } else if (!(ops->complete_fence && ops->advance_timeline)) { |
| pr_err("%s: device must implement both complete_fence and advance_timeline, or implement neither\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| memset(dev, 0, sizeof(*dev)); |
| |
| va_start(args, fmt); |
| ret = adf_obj_init(&dev->base, ADF_OBJ_DEVICE, &adf_devices, dev, |
| &ops->base, fmt, args); |
| va_end(args); |
| if (ret < 0) |
| return ret; |
| |
| dev->dev = parent; |
| dev->ops = ops; |
| idr_init(&dev->overlay_engines); |
| idr_init(&dev->interfaces); |
| 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; |
| |
| pr_err("%s: failed to run config posting thread: %d\n", |
| __func__, ret); |
| goto err; |
| } |
| init_kthread_work(&dev->post_work, adf_post_work_func); |
| |
| ret = adf_device_sysfs_init(dev); |
| if (ret < 0) |
| goto err; |
| |
| return 0; |
| |
| err: |
| adf_device_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_destroy(&dev->interfaces); |
| idr_destroy(&dev->overlay_engines); |
| |
| if (dev->post_thread) { |
| flush_kthread_worker(&dev->post_worker); |
| kthread_stop(dev->post_thread); |
| } |
| |
| 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); |
| } |
| mutex_destroy(&dev->post_lock); |
| mutex_destroy(&dev->client_lock); |
| |
| if (dev->timeline) |
| sync_timeline_destroy(&dev->timeline->obj); |
| |
| adf_obj_destroy(&dev->base, &adf_devices); |
| } |
| 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; |
| const u32 allowed_flags = ADF_INTF_FLAG_PRIMARY | |
| ADF_INTF_FLAG_EXTERNAL; |
| |
| if (dev->n_interfaces == ADF_MAX_INTERFACES) { |
| pr_err("%s: parent device %s has too many interfaces\n", |
| __func__, dev->base.name); |
| return -ENOMEM; |
| } |
| |
| if (type >= ADF_INTF_MEMORY && type <= ADF_INTF_TYPE_DEVICE_CUSTOM) { |
| pr_err("%s: invalid interface type %u\n", __func__, type); |
| return -EINVAL; |
| } |
| |
| if (flags & ~allowed_flags) { |
| pr_err("%s: invalid interface flags 0x%X\n", __func__, |
| flags & ~allowed_flags); |
| return -EINVAL; |
| } |
| |
| memset(intf, 0, sizeof(*intf)); |
| |
| 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) |
| return ret; |
| |
| intf->type = type; |
| intf->idx = idx; |
| intf->flags = flags; |
| intf->ops = ops; |
| intf->dpms_state = DRM_MODE_DPMS_OFF; |
| init_waitqueue_head(&intf->vsync_wait); |
| rwlock_init(&intf->vsync_lock); |
| rwlock_init(&intf->hotplug_modelist_lock); |
| |
| ret = adf_interface_sysfs_init(intf); |
| if (ret < 0) |
| goto err; |
| dev->n_interfaces++; |
| |
| return 0; |
| |
| err: |
| adf_obj_destroy(&intf->base, &dev->interfaces); |
| 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 = adf_interface_parent(intf); |
| 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) { |
| adf_device_detach_op(dev, |
| entry->attachment.overlay_engine, intf); |
| adf_attachment_free(entry); |
| dev->n_attached--; |
| } |
| } |
| kfree(intf->modelist); |
| adf_interface_sysfs_destroy(intf); |
| adf_obj_destroy(&intf->base, &dev->interfaces); |
| dev->n_interfaces--; |
| mutex_unlock(&dev->client_lock); |
| } |
| EXPORT_SYMBOL(adf_interface_destroy); |
| |
| static bool adf_overlay_engine_has_custom_formats( |
| const struct adf_overlay_engine_ops *ops) |
| { |
| size_t i; |
| for (i = 0; i < ops->n_supported_formats; i++) |
| if (!adf_format_is_standard(ops->supported_formats[i])) |
| return true; |
| return false; |
| } |
| |
| /** |
| * 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->supported_formats) { |
| pr_err("%s: overlay engine must support at least one format\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| if (ops->n_supported_formats > ADF_MAX_SUPPORTED_FORMATS) { |
| pr_err("%s: overlay engine supports too many formats\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| if (adf_overlay_engine_has_custom_formats(ops) && |
| !dev->ops->validate_custom_format) { |
| pr_err("%s: overlay engine has custom formats but parent device %s does not implement validate_custom_format\n", |
| __func__, dev->base.name); |
| return -EINVAL; |
| } |
| |
| memset(eng, 0, sizeof(*eng)); |
| |
| 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) |
| return ret; |
| |
| eng->ops = ops; |
| |
| ret = adf_overlay_engine_sysfs_init(eng); |
| if (ret < 0) |
| goto err; |
| |
| return 0; |
| |
| err: |
| adf_obj_destroy(&eng->base, &dev->overlay_engines); |
| 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 = adf_overlay_engine_parent(eng); |
| 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) { |
| adf_device_detach_op(dev, eng, |
| entry->attachment.interface); |
| adf_attachment_free(entry); |
| dev->n_attached--; |
| } |
| } |
| adf_overlay_engine_sysfs_destroy(eng); |
| adf_obj_destroy(&eng->base, &dev->overlay_engines); |
| 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) |
| { |
| struct adf_device *intf_dev = adf_interface_parent(intf); |
| struct adf_device *eng_dev = adf_overlay_engine_parent(eng); |
| |
| if (intf_dev != dev) { |
| dev_err(&dev->base.dev, "can't attach interface %s belonging to device %s\n", |
| intf->base.name, intf_dev->base.name); |
| return -EINVAL; |
| } |
| |
| if (eng_dev != dev) { |
| dev_err(&dev->base.dev, "can't attach overlay engine %s belonging to device %s\n", |
| eng->base.name, eng_dev->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; |
| } |
| EXPORT_SYMBOL(adf_attachment_allow); |
| |
| /** |
| * 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[ADF_FORMAT_STR_SIZE]) |
| { |
| 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); |
| |
| /** |
| * adf_format_validate_yuv - validate the number and size of planes in buffers |
| * with a custom YUV format. |
| * |
| * @dev: ADF device performing the validation |
| * @buf: buffer to validate |
| * @num_planes: expected number of planes |
| * @hsub: expected horizontal chroma subsampling factor, in pixels |
| * @vsub: expected vertical chroma subsampling factor, in pixels |
| * @cpp: expected bytes per pixel for each plane (length @num_planes) |
| * |
| * adf_format_validate_yuv() is intended to be called as a helper from @dev's |
| * validate_custom_format() op. |
| * |
| * Returns 0 if @buf has the expected number of planes and each plane |
| * has sufficient size, or -EINVAL otherwise. |
| */ |
| int adf_format_validate_yuv(struct adf_device *dev, struct adf_buffer *buf, |
| u8 num_planes, u8 hsub, u8 vsub, u8 cpp[]) |
| { |
| u8 i; |
| |
| if (num_planes != buf->n_planes) { |
| char format_str[ADF_FORMAT_STR_SIZE]; |
| adf_format_str(buf->format, format_str); |
| dev_err(&dev->base.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->base.dev, "bad buffer width %u\n", buf->w); |
| return -EINVAL; |
| } |
| |
| if (buf->h == 0 || buf->h % vsub) { |
| dev_err(&dev->base.dev, "bad buffer height %u\n", buf->h); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < num_planes; i++) { |
| u32 width = buf->w / (i != 0 ? hsub : 1); |
| u32 height = buf->h / (i != 0 ? vsub : 1); |
| u8 cpp = adf_format_plane_cpp(buf->format, i); |
| u32 last_line_size; |
| |
| if (buf->pitch[i] < (u64) width * cpp) { |
| dev_err(&dev->base.dev, "plane %u pitch is shorter than buffer width (pitch = %u, width = %u, bpp = %u)\n", |
| i, buf->pitch[i], width, cpp * 8); |
| return -EINVAL; |
| } |
| |
| switch (dev->ops->quirks.buffer_padding) { |
| case ADF_BUFFER_PADDED_TO_PITCH: |
| last_line_size = buf->pitch[i]; |
| break; |
| |
| case ADF_BUFFER_UNPADDED: |
| last_line_size = width * cpp; |
| break; |
| |
| default: |
| BUG(); |
| } |
| |
| if ((u64) (height - 1) * buf->pitch[i] + last_line_size + |
| buf->offset[i] > buf->dma_bufs[i]->size) { |
| dev_err(&dev->base.dev, "plane %u buffer too small (height = %u, pitch = %u, offset = %u, size = %zu)\n", |
| i, height, buf->pitch[i], |
| buf->offset[i], buf->dma_bufs[i]->size); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(adf_format_validate_yuv); |
| |
| /** |
| * adf_modeinfo_set_name - sets the name of a mode from its display resolution |
| * |
| * @mode: mode |
| * |
| * adf_modeinfo_set_name() fills in @mode->name in the format |
| * "[hdisplay]x[vdisplay](i)". It is intended to help drivers create |
| * ADF/DRM-style modelists from other mode formats. |
| */ |
| 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" : ""); |
| } |
| EXPORT_SYMBOL(adf_modeinfo_set_name); |
| |
| /** |
| * adf_modeinfo_set_vrefresh - sets the vrefresh of a mode from its other |
| * timing data |
| * |
| * @mode: mode |
| * |
| * adf_modeinfo_set_vrefresh() calculates @mode->vrefresh from |
| * @mode->{h,v}display and @mode->flags. It is intended to help drivers |
| * create ADF/DRM-style modelists from other mode formats. |
| */ |
| void adf_modeinfo_set_vrefresh(struct drm_mode_modeinfo *mode) |
| { |
| int refresh = 0; |
| unsigned int calc_val; |
| |
| if (mode->vrefresh > 0) |
| return; |
| |
| if (mode->htotal <= 0 || mode->vtotal <= 0) |
| return; |
| |
| /* work out vrefresh the value will be x1000 */ |
| calc_val = (mode->clock * 1000); |
| calc_val /= mode->htotal; |
| refresh = (calc_val + mode->vtotal / 2) / mode->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; |
| } |
| EXPORT_SYMBOL(adf_modeinfo_set_vrefresh); |
| |
| 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); |