/* * 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 = adf_interface_parent(intf); 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 -EOPNOTSUPP; if (state > DRM_MODE_DPMS_OFF) return -EINVAL; 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_event_refcount(&intf->base, ADF_EVENT_VSYNC); if (!vsync_refcount) { ret = -ENOMEM; goto done; } prev_state = intf->dpms_state; if (prev_state == state) { ret = -EBUSY; goto done; } 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 (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 = adf_interface_parent(intf); 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 = adf_interface_parent(intf); 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 = adf_interface_parent(intf); int ret = 0; if (!intf->ops || !intf->ops->modeset) return -EOPNOTSUPP; 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. */ int adf_interface_get_screen_size(struct adf_interface *intf, u16 *width_mm, u16 *height_mm) { struct adf_device *dev = adf_interface_parent(intf); int ret; if (!intf->ops || !intf->ops->screen_size) return -EOPNOTSUPP; mutex_lock(&dev->client_lock); ret = intf->ops->screen_size(intf, width_mm, height_mm); mutex_unlock(&dev->client_lock); return ret; } EXPORT_SYMBOL(adf_interface_get_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; struct adf_device *parent = adf_overlay_engine_parent(eng); u8 hsub, vsub, num_planes, cpp[ADF_MAX_PLANES], i; if (!adf_overlay_engine_supports_format(eng, buf->format)) { char format_str[ADF_FORMAT_STR_SIZE]; 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)) 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); for (i = 0; i < num_planes; i++) cpp[i] = adf_format_plane_cpp(buf->format, i); return adf_format_validate_yuv(parent, buf, num_planes, hsub, vsub, cpp); } 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 %zu 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 %zu failed: %d", i, ret); goto done; } else if (!sg_table) { ret = -ENOMEM; dev_err(&dev->base.dev, "mapping plane %zu 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); if (!dev->timeline) return ERR_PTR(-ENOMEM); dev->timeline_max = 1; } dev->timeline_max++; pt = sw_sync_pt_create(dev->timeline, dev->timeline_max); if (!pt) goto err_pt_create; complete_fence = sync_fence_create(dev->base.name, pt); if (!complete_fence) goto err_fence_create; return complete_fence; err_fence_create: sync_pt_free(pt); err_pt_create: dev->timeline_max--; return ERR_PTR(-ENOSYS); } /** * 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; 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_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]); 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 - 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) { struct adf_attachment_list *attachment; mutex_lock(&dev->client_lock); attachment = adf_attachment_find(&dev->attached, eng, intf); mutex_unlock(&dev->client_lock); return attachment != NULL; } 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) { struct adf_attachment_list *attachment; mutex_lock(&dev->client_lock); attachment = adf_attachment_find(&dev->attach_allowed, eng, intf); mutex_unlock(&dev->client_lock); return attachment != NULL; } 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)) { ret = -EINVAL; goto done; } if (adf_attachment_find(&dev->attached, eng, intf)) { ret = -EALREADY; goto done; } ret = adf_device_attach_op(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; } ret = adf_device_detach_op(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 -EOPNOTSUPP; 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(adf_interface_parent(intf), &intf, 1, buf, 1, custom_data, custom_data_size); done: kfree(custom_data); return ret; } EXPORT_SYMBOL(adf_interface_simple_post);