Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a18d483d authored by Clarence Ip's avatar Clarence Ip
Browse files

drm/msm/sde: support connector/crtc events



This patch adds support for internal driver events:
- direct callbacks may be registered against the sde connector,
and triggered via a new "trigger event" sde connector function;
this is suitable for simple event handlers that may be run
from within an ISR context
- delayed event handling may be posted against CRTCs, allowing
for event processing to occur within a worker thread rather
than the originating context (e.g., ISR handlers)

CRs-Fixed: 2005395
Change-Id: Idb0439892dfbda72d3c0ada882915492e3836ad8
Signed-off-by: default avatarClarence Ip <cip@codeaurora.org>
parent 9c65f7bf
Loading
Loading
Loading
Loading
+79 −0
Original line number Diff line number Diff line
@@ -115,6 +115,83 @@ static int sde_backlight_setup(struct sde_connector *c_conn)
	return 0;
}

int sde_connector_trigger_event(void *drm_connector,
		uint32_t event_idx, uint32_t instance_idx,
		uint32_t data0, uint32_t data1,
		uint32_t data2, uint32_t data3)
{
	struct sde_connector *c_conn;
	unsigned long irq_flags;
	void (*cb_func)(uint32_t event_idx,
			uint32_t instance_idx, void *usr,
			uint32_t data0, uint32_t data1,
			uint32_t data2, uint32_t data3);
	void *usr;
	int rc = 0;

	/*
	 * This function may potentially be called from an ISR context, so
	 * avoid excessive logging/etc.
	 */
	if (!drm_connector)
		return -EINVAL;
	else if (event_idx >= SDE_CONN_EVENT_COUNT)
		return -EINVAL;
	c_conn = to_sde_connector(drm_connector);

	spin_lock_irqsave(&c_conn->event_lock, irq_flags);
	cb_func = c_conn->event_table[event_idx].cb_func;
	usr = c_conn->event_table[event_idx].usr;
	spin_unlock_irqrestore(&c_conn->event_lock, irq_flags);

	if (cb_func)
		cb_func(event_idx, instance_idx, usr,
			data0, data1, data2, data3);
	else
		rc = -EAGAIN;

	return rc;
}

int sde_connector_register_event(struct drm_connector *connector,
		uint32_t event_idx,
		void (*cb_func)(uint32_t event_idx,
			uint32_t instance_idx, void *usr,
			uint32_t data0, uint32_t data1,
			uint32_t data2, uint32_t data3),
		void *usr)
{
	struct sde_connector *c_conn;
	unsigned long irq_flags;

	if (!connector) {
		SDE_ERROR("invalid connector\n");
		return -EINVAL;
	} else if (event_idx >= SDE_CONN_EVENT_COUNT) {
		SDE_ERROR("conn%d, invalid event %d\n",
				connector->base.id, event_idx);
		return -EINVAL;
	}
	c_conn = to_sde_connector(connector);

	spin_lock_irqsave(&c_conn->event_lock, irq_flags);
	c_conn->event_table[event_idx].cb_func = cb_func;
	c_conn->event_table[event_idx].usr = usr;
	spin_unlock_irqrestore(&c_conn->event_lock, irq_flags);

	/* optionally notify display of event registration */
	if (c_conn->ops.enable_event && c_conn->display)
		c_conn->ops.enable_event(connector, event_idx,
				cb_func != NULL, c_conn->display);
	return 0;
}

void sde_connector_unregister_event(struct drm_connector *connector,
		uint32_t event_idx)
{
	(void)sde_connector_register_event(connector, event_idx, 0, 0);
}

int sde_connector_get_info(struct drm_connector *connector,
		struct msm_display_info *info)
{
@@ -616,6 +693,8 @@ struct drm_connector *sde_connector_init(struct drm_device *dev,
	if (rc)
		goto error_free_conn;

	spin_lock_init(&c_conn->event_lock);

	c_conn->connector_type = connector_type;
	c_conn->encoder = encoder;
	c_conn->panel = panel;
+78 −0
Original line number Diff line number Diff line
@@ -120,6 +120,16 @@ struct sde_connector_ops {
	 */
	int (*get_info)(struct msm_display_info *info, void *display);

	/**
	 * enable_event - notify display of event registration/unregistration
	 * @connector: Pointer to drm connector structure
	 * @event_idx: SDE connector event index
	 * @enable: Whether the event is being enabled/disabled
	 * @display: Pointer to private display structure
	 */
	void (*enable_event)(struct drm_connector *connector,
			uint32_t event_idx, bool enable, void *display);

	int (*set_backlight)(void *display, u32 bl_lvl);

	/**
@@ -130,6 +140,28 @@ struct sde_connector_ops {
	int (*soft_reset)(void *display);
};

/**
 * enum sde_connector_events - list of recognized connector events
 */
enum sde_connector_events {
	SDE_CONN_EVENT_VID_DONE, /* video mode frame done */
	SDE_CONN_EVENT_CMD_DONE, /* command mode frame done */
	SDE_CONN_EVENT_COUNT,
};

/**
 * struct sde_connector_evt - local event registration entry structure
 * @cb_func: Pointer to desired callback function
 * @usr: User pointer to pass to callback on event trigger
 */
struct sde_connector_evt {
	void (*cb_func)(uint32_t event_idx,
			uint32_t instance_idx, void *usr,
			uint32_t data0, uint32_t data1,
			uint32_t data2, uint32_t data3);
	void *usr;
};

/**
 * struct sde_connector - local sde connector structure
 * @base: Base drm connector structure
@@ -146,6 +178,8 @@ struct sde_connector_ops {
 * @property_data: Array of private data for generic property handling
 * @blob_caps: Pointer to blob structure for 'capabilities' property
 * @fb_kmap: true if kernel mapping of framebuffer is requested
 * @event_table: Array of registered events
 * @event_lock: Lock object for event_table
 */
struct sde_connector {
	struct drm_connector base;
@@ -168,6 +202,8 @@ struct sde_connector {
	struct drm_property_blob *blob_caps;

	bool fb_kmap;
	struct sde_connector_evt event_table[SDE_CONN_EVENT_COUNT];
	spinlock_t event_lock;
};

/**
@@ -312,5 +348,47 @@ void sde_connector_complete_commit(struct drm_connector *connector);
int sde_connector_get_info(struct drm_connector *connector,
		struct msm_display_info *info);

/**
 * sde_connector_trigger_event - indicate that an event has occurred
 *	Any callbacks that have been registered against this event will
 *	be called from the same thread context.
 * @connector: Pointer to drm connector structure
 * @event_idx: Index of event to trigger
 * @instance_idx: Event-specific "instance index" to pass to callback
 * @data0: Event-specific "data" to pass to callback
 * @data1: Event-specific "data" to pass to callback
 * @data2: Event-specific "data" to pass to callback
 * @data3: Event-specific "data" to pass to callback
 * Returns: Zero on success
 */
int sde_connector_trigger_event(void *drm_connector,
		uint32_t event_idx, uint32_t instance_idx,
		uint32_t data0, uint32_t data1,
		uint32_t data2, uint32_t data3);

/**
 * sde_connector_register_event - register a callback function for an event
 * @connector: Pointer to drm connector structure
 * @event_idx: Index of event to register
 * @cb_func: Pointer to desired callback function
 * @usr: User pointer to pass to callback on event trigger
 * Returns: Zero on success
 */
int sde_connector_register_event(struct drm_connector *connector,
		uint32_t event_idx,
		void (*cb_func)(uint32_t event_idx,
			uint32_t instance_idx, void *usr,
			uint32_t data0, uint32_t data1,
			uint32_t data2, uint32_t data3),
		void *usr);

/**
 * sde_connector_unregister_event - unregister all callbacks for an event
 * @connector: Pointer to drm connector structure
 * @event_idx: Index of event to register
 */
void sde_connector_unregister_event(struct drm_connector *connector,
		uint32_t event_idx);

#endif /* _SDE_CONNECTOR_H_ */
+116 −1
Original line number Diff line number Diff line
@@ -58,6 +58,18 @@ static inline struct sde_kms *_sde_crtc_get_kms(struct drm_crtc *crtc)
	return to_sde_kms(priv->kms);
}

static void _sde_crtc_deinit_events(struct sde_crtc *sde_crtc)
{
	if (!sde_crtc)
		return;

	if (sde_crtc->event_thread) {
		kthread_flush_worker(&sde_crtc->event_worker);
		kthread_stop(sde_crtc->event_thread);
		sde_crtc->event_thread = NULL;
	}
}

static void sde_crtc_destroy(struct drm_crtc *crtc)
{
	struct sde_crtc *sde_crtc = to_sde_crtc(crtc);
@@ -74,6 +86,7 @@ static void sde_crtc_destroy(struct drm_crtc *crtc)

	mutex_destroy(&sde_crtc->crtc_lock);
	sde_fence_deinit(&sde_crtc->output_fence);
	_sde_crtc_deinit_events(sde_crtc);

	drm_crtc_cleanup(crtc);
	kfree(sde_crtc);
@@ -1967,6 +1980,100 @@ static const struct drm_crtc_helper_funcs sde_crtc_helper_funcs = {
	.atomic_flush = sde_crtc_atomic_flush,
};

static void _sde_crtc_event_cb(struct kthread_work *work)
{
	struct sde_crtc_event *event;
	struct sde_crtc *sde_crtc;
	unsigned long irq_flags;

	if (!work) {
		SDE_ERROR("invalid work item\n");
		return;
	}

	event = container_of(work, struct sde_crtc_event, kt_work);
	if (event->cb_func)
		event->cb_func(event->usr);

	/* set sde_crtc to NULL for static work structures */
	sde_crtc = event->sde_crtc;
	if (!sde_crtc)
		return;

	spin_lock_irqsave(&sde_crtc->event_lock, irq_flags);
	list_add_tail(&event->list, &sde_crtc->event_free_list);
	spin_unlock_irqrestore(&sde_crtc->event_lock, irq_flags);
}

int sde_crtc_event_queue(struct drm_crtc *crtc,
		void (*func)(void *usr), void *usr)
{
	unsigned long irq_flags;
	struct sde_crtc *sde_crtc;
	struct sde_crtc_event *event = NULL;

	if (!crtc || !func)
		return -EINVAL;
	sde_crtc = to_sde_crtc(crtc);

	/*
	 * Obtain an event struct from the private cache. This event
	 * queue may be called from ISR contexts, so use a private
	 * cache to avoid calling any memory allocation functions.
	 */
	spin_lock_irqsave(&sde_crtc->event_lock, irq_flags);
	if (!list_empty(&sde_crtc->event_free_list)) {
		event = list_first_entry(&sde_crtc->event_free_list,
				struct sde_crtc_event, list);
		list_del_init(&event->list);
	}
	spin_unlock_irqrestore(&sde_crtc->event_lock, irq_flags);

	if (!event)
		return -ENOMEM;

	/* populate event node */
	event->sde_crtc = sde_crtc;
	event->cb_func = func;
	event->usr = usr;

	/* queue new event request */
	kthread_init_work(&event->kt_work, _sde_crtc_event_cb);
	kthread_queue_work(&sde_crtc->event_worker, &event->kt_work);

	return 0;
}

static int _sde_crtc_init_events(struct sde_crtc *sde_crtc)
{
	int i, rc = 0;

	if (!sde_crtc) {
		SDE_ERROR("invalid crtc\n");
		return -EINVAL;
	}

	spin_lock_init(&sde_crtc->event_lock);

	INIT_LIST_HEAD(&sde_crtc->event_free_list);
	for (i = 0; i < SDE_CRTC_MAX_EVENT_COUNT; ++i)
		list_add_tail(&sde_crtc->event_cache[i].list,
				&sde_crtc->event_free_list);

	kthread_init_worker(&sde_crtc->event_worker);
	sde_crtc->event_thread = kthread_run(kthread_worker_fn,
			&sde_crtc->event_worker, "crtc_event:%d",
			sde_crtc->base.base.id);

	if (IS_ERR_OR_NULL(sde_crtc->event_thread)) {
		SDE_ERROR("failed to create event thread\n");
		rc = PTR_ERR(sde_crtc->event_thread);
		sde_crtc->event_thread = NULL;
	}

	return rc;
}

/* initialize crtc */
struct drm_crtc *sde_crtc_init(struct drm_device *dev, struct drm_plane *plane)
{
@@ -1974,7 +2081,7 @@ struct drm_crtc *sde_crtc_init(struct drm_device *dev, struct drm_plane *plane)
	struct sde_crtc *sde_crtc = NULL;
	struct msm_drm_private *priv = NULL;
	struct sde_kms *kms = NULL;
	int i;
	int i, rc;

	priv = dev->dev_private;
	kms = to_sde_kms(priv->kms);
@@ -2008,6 +2115,14 @@ struct drm_crtc *sde_crtc_init(struct drm_device *dev, struct drm_plane *plane)
	/* save user friendly CRTC name for later */
	snprintf(sde_crtc->name, SDE_CRTC_NAME_SIZE, "crtc%u", crtc->base.id);

	/* initialize event handling */
	rc = _sde_crtc_init_events(sde_crtc);
	if (rc) {
		drm_crtc_cleanup(crtc);
		kfree(sde_crtc);
		return ERR_PTR(rc);
	}

	/* initialize output fence support */
	mutex_init(&sde_crtc->crtc_lock);
	sde_fence_init(&sde_crtc->output_fence, sde_crtc->name, crtc->base.id);
+45 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#ifndef _SDE_CRTC_H_
#define _SDE_CRTC_H_

#include <linux/kthread.h>
#include "drm_crtc.h"
#include "msm_prop.h"
#include "sde_fence.h"
@@ -78,6 +79,28 @@ struct sde_crtc_frame_event {
	u32 event;
};

/**
 * struct sde_crtc_event - event callback tracking structure
 * @list:     Linked list tracking node
 * @kt_work:  Kthread worker structure
 * @sde_crtc: Pointer to associated sde_crtc structure
 * @cb_func:  Pointer to callback function
 * @usr:      Pointer to user data to be provided to the callback
 */
struct sde_crtc_event {
	struct list_head list;
	struct kthread_work kt_work;
	void *sde_crtc;

	void (*cb_func)(void *usr);
	void *usr;
};

/*
 * Maximum number of free event structures to cache
 */
#define SDE_CRTC_MAX_EVENT_COUNT	16

/**
 * struct sde_crtc - virtualized CRTC data structure
 * @base          : Base drm crtc structure
@@ -105,6 +128,11 @@ struct sde_crtc_frame_event {
 * @frame_events  : static allocation of in-flight frame events
 * @frame_event_list : available frame event list
 * @spin_lock     : spin lock for frame event, transaction status, etc...
 * @event_thread  : Pointer to event handler thread
 * @event_worker  : Event worker queue
 * @event_cache   : Local cache of event worker structures
 * @event_free_list : List of available event structures
 * @event_lock    : Spinlock around event handling code
 */
struct sde_crtc {
	struct drm_crtc base;
@@ -142,6 +170,13 @@ struct sde_crtc {
	struct sde_crtc_frame_event frame_events[SDE_CRTC_FRAME_EVENT_SIZE];
	struct list_head frame_event_list;
	spinlock_t spin_lock;

	/* for handling internal event thread */
	struct task_struct *event_thread;
	struct kthread_worker event_worker;
	struct sde_crtc_event event_cache[SDE_CRTC_MAX_EVENT_COUNT];
	struct list_head event_free_list;
	spinlock_t event_lock;
};

#define to_sde_crtc(x) container_of(x, struct sde_crtc, base)
@@ -308,4 +343,14 @@ static inline bool sde_crtc_is_enabled(struct drm_crtc *crtc)
	return crtc ? crtc->enabled : false;
}

/**
 * sde_crtc_event_queue - request event callback
 * @crtc: Pointer to drm crtc structure
 * @func: Pointer to callback function
 * @usr: Pointer to user data to be passed to callback
 * Returns: Zero on success
 */
int sde_crtc_event_queue(struct drm_crtc *crtc,
		void (*func)(void *usr), void *usr);

#endif /* _SDE_CRTC_H_ */