Commit bbbe29d8 authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge branch 'drm-armada-devel' of git://ftp.arm.linux.org.uk/~rmk/linux-arm into drm-next

* remove support for the non-component support from the Armada DRM driver,
  switching it to component-only mode.
* create a "armada plane" to allow the primary and overlay planes to share
  some code.
* increase efficiency by using inherently atomic operations, rather than
  spinlocking to achieve atomicity.  Eg, if we want to exchange a value,
  using xchg().
* increase PM savings by stopping the external pixel clock when we're in
  DPMS mode.

* 'drm-armada-devel' of git://ftp.arm.linux.org.uk/~rmk/linux-arm:
  drm/armada: move frame wait wakeup into plane work
  drm/armada: convert overlay plane vbl worker to a armada plane worker
  drm/armada: move CRTC flip work to primary plane work
  drm/armada: move frame wait into armada_frame
  drm/armada: move the locking for armada_drm_vbl_event_remove()
  drm/armada: move the update of dplane->ctrl0 out of spinlock
  drm/armada: move write to dma_ctrl0 to armada_drm_crtc_plane_disable()
  drm/armada: provide a common helper to disable a plane
  drm/armada: allocate primary plane ourselves
  drm/armada: add primary plane creation
  drm/armada: introduce generic armada_plane struct
  drm/armada: update armada overlay to use drm_universal_plane_init()
  drm/armada: use xchg() to atomically update dplane->old_fb
  drm/armada: factor out retirement of old fb
  drm/armada: rename overlay identifiers
  drm/armada: redo locking and atomics for armada_drm_crtc_complete_frame_work()
  drm/armada: disable CRTC clock during DPMS
  drm/armada: use drm_plane_force_disable() to disable the overlay plane
  drm/armada: move vbl code into armada_crtc
  drm/armada: remove non-component support
parents d4070ff7 7cb410cd
Loading
Loading
Loading
Loading
+0 −9
Original line number Diff line number Diff line
@@ -14,12 +14,3 @@ config DRM_ARMADA
	  This driver provides no built-in acceleration; acceleration is
	  performed by other IP found on the SoC.  This driver provides
	  kernel mode setting and buffer management to userspace.

config DRM_ARMADA_TDA1998X
	bool "Support TDA1998X HDMI output"
	depends on DRM_ARMADA != n
	depends on I2C && DRM_I2C_NXP_TDA998X = y
	default y
	help
	  Support the TDA1998x HDMI output device found on the Solid-Run
	  CuBox.
+1 −2
Original line number Diff line number Diff line
armada-y	:= armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \
		   armada_gem.o armada_output.o armada_overlay.o \
		   armada_slave.o
		   armada_gem.o armada_overlay.o
armada-y	+= armada_510.o
armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o

+185 −73
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include "armada_hw.h"

struct armada_frame_work {
	struct armada_plane_work work;
	struct drm_pending_vblank_event *event;
	struct armada_regs regs[4];
	struct drm_framebuffer *old_fb;
@@ -33,6 +34,23 @@ enum csc_mode {
	CSC_RGB_STUDIO = 2,
};

static const uint32_t armada_primary_formats[] = {
	DRM_FORMAT_UYVY,
	DRM_FORMAT_YUYV,
	DRM_FORMAT_VYUY,
	DRM_FORMAT_YVYU,
	DRM_FORMAT_ARGB8888,
	DRM_FORMAT_ABGR8888,
	DRM_FORMAT_XRGB8888,
	DRM_FORMAT_XBGR8888,
	DRM_FORMAT_RGB888,
	DRM_FORMAT_BGR888,
	DRM_FORMAT_ARGB1555,
	DRM_FORMAT_ABGR1555,
	DRM_FORMAT_RGB565,
	DRM_FORMAT_BGR565,
};

/*
 * A note about interlacing.  Let's consider HDMI 1920x1080i.
 * The timing parameters we have from X are:
@@ -173,49 +191,82 @@ static unsigned armada_drm_crtc_calc_fb(struct drm_framebuffer *fb,
	return i;
}

static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
	struct armada_frame_work *work)
static void armada_drm_plane_work_run(struct armada_crtc *dcrtc,
	struct armada_plane *plane)
{
	struct armada_plane_work *work = xchg(&plane->work, NULL);

	/* Handle any pending frame work. */
	if (work) {
		work->fn(dcrtc, plane, work);
		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
	}

	wake_up(&plane->frame_wait);
}

int armada_drm_plane_work_queue(struct armada_crtc *dcrtc,
	struct armada_plane *plane, struct armada_plane_work *work)
{
	struct drm_device *dev = dcrtc->crtc.dev;
	unsigned long flags;
	int ret;

	ret = drm_vblank_get(dev, dcrtc->num);
	ret = drm_vblank_get(dcrtc->crtc.dev, dcrtc->num);
	if (ret) {
		DRM_ERROR("failed to acquire vblank counter\n");
		return ret;
	}

	spin_lock_irqsave(&dev->event_lock, flags);
	if (!dcrtc->frame_work)
		dcrtc->frame_work = work;
	else
		ret = -EBUSY;
	spin_unlock_irqrestore(&dev->event_lock, flags);

	ret = cmpxchg(&plane->work, NULL, work) ? -EBUSY : 0;
	if (ret)
		drm_vblank_put(dev, dcrtc->num);
		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);

	return ret;
}

static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc)
int armada_drm_plane_work_wait(struct armada_plane *plane, long timeout)
{
	struct drm_device *dev = dcrtc->crtc.dev;
	struct armada_frame_work *work = dcrtc->frame_work;
	return wait_event_timeout(plane->frame_wait, !plane->work, timeout);
}

struct armada_plane_work *armada_drm_plane_work_cancel(
	struct armada_crtc *dcrtc, struct armada_plane *plane)
{
	struct armada_plane_work *work = xchg(&plane->work, NULL);

	if (work)
		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);

	dcrtc->frame_work = NULL;
	return work;
}

	armada_drm_crtc_update_regs(dcrtc, work->regs);
static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc,
	struct armada_frame_work *work)
{
	struct armada_plane *plane = drm_to_armada_plane(dcrtc->crtc.primary);

	if (work->event)
		drm_send_vblank_event(dev, dcrtc->num, work->event);
	return armada_drm_plane_work_queue(dcrtc, plane, &work->work);
}

	drm_vblank_put(dev, dcrtc->num);
static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc,
	struct armada_plane *plane, struct armada_plane_work *work)
{
	struct armada_frame_work *fwork = container_of(work, struct armada_frame_work, work);
	struct drm_device *dev = dcrtc->crtc.dev;
	unsigned long flags;

	spin_lock_irqsave(&dcrtc->irq_lock, flags);
	armada_drm_crtc_update_regs(dcrtc, fwork->regs);
	spin_unlock_irqrestore(&dcrtc->irq_lock, flags);

	if (fwork->event) {
		spin_lock_irqsave(&dev->event_lock, flags);
		drm_send_vblank_event(dev, dcrtc->num, fwork->event);
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}

	/* Finally, queue the process-half of the cleanup. */
	__armada_drm_queue_unref_work(dcrtc->crtc.dev, work->old_fb);
	kfree(work);
	__armada_drm_queue_unref_work(dcrtc->crtc.dev, fwork->old_fb);
	kfree(fwork);
}

static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
@@ -235,6 +286,7 @@ static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
	work = kmalloc(sizeof(*work), GFP_KERNEL);
	if (work) {
		int i = 0;
		work->work.fn = armada_drm_crtc_complete_frame_work;
		work->event = NULL;
		work->old_fb = fb;
		armada_reg_queue_end(work->regs, i);
@@ -255,19 +307,14 @@ static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,

static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
{
	struct drm_device *dev = dcrtc->crtc.dev;
	struct armada_plane *plane = drm_to_armada_plane(dcrtc->crtc.primary);

	/*
	 * Tell the DRM core that vblank IRQs aren't going to happen for
	 * a while.  This cleans up any pending vblank events for us.
	 */
	drm_crtc_vblank_off(&dcrtc->crtc);

	/* Handle any pending flip event. */
	spin_lock_irq(&dev->event_lock);
	if (dcrtc->frame_work)
		armada_drm_crtc_complete_frame_work(dcrtc);
	spin_unlock_irq(&dev->event_lock);
	armada_drm_plane_work_run(dcrtc, plane);
}

void armada_drm_crtc_gamma_set(struct drm_crtc *crtc, u16 r, u16 g, u16 b,
@@ -287,7 +334,11 @@ static void armada_drm_crtc_dpms(struct drm_crtc *crtc, int dpms)

	if (dcrtc->dpms != dpms) {
		dcrtc->dpms = dpms;
		if (!IS_ERR(dcrtc->clk) && !dpms_blanked(dpms))
			WARN_ON(clk_prepare_enable(dcrtc->clk));
		armada_drm_crtc_update(dcrtc);
		if (!IS_ERR(dcrtc->clk) && dpms_blanked(dpms))
			clk_disable_unprepare(dcrtc->clk);
		if (dpms_blanked(dpms))
			armada_drm_vblank_off(dcrtc);
		else
@@ -310,17 +361,11 @@ static void armada_drm_crtc_prepare(struct drm_crtc *crtc)
	/*
	 * If we have an overlay plane associated with this CRTC, disable
	 * it before the modeset to avoid its coordinates being outside
	 * the new mode parameters.  DRM doesn't provide help with this.
	 * the new mode parameters.
	 */
	plane = dcrtc->plane;
	if (plane) {
		struct drm_framebuffer *fb = plane->fb;

		plane->funcs->disable_plane(plane);
		plane->fb = NULL;
		plane->crtc = NULL;
		drm_framebuffer_unreference(fb);
	}
	if (plane)
		drm_plane_force_disable(plane);
}

/* The mode_config.mutex will be held for this call */
@@ -356,8 +401,8 @@ static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc,

static void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
{
	struct armada_vbl_event *e, *n;
	void __iomem *base = dcrtc->base;
	struct drm_plane *ovl_plane;

	if (stat & DMA_FF_UNDERFLOW)
		DRM_ERROR("video underflow on crtc %u\n", dcrtc->num);
@@ -368,11 +413,10 @@ static void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
		drm_handle_vblank(dcrtc->crtc.dev, dcrtc->num);

	spin_lock(&dcrtc->irq_lock);

	list_for_each_entry_safe(e, n, &dcrtc->vbl_list, node) {
		list_del_init(&e->node);
		drm_vblank_put(dcrtc->crtc.dev, dcrtc->num);
		e->fn(dcrtc, e->data);
	ovl_plane = dcrtc->plane;
	if (ovl_plane) {
		struct armada_plane *plane = drm_to_armada_plane(ovl_plane);
		armada_drm_plane_work_run(dcrtc, plane);
	}

	if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) {
@@ -404,14 +448,8 @@ static void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
	spin_unlock(&dcrtc->irq_lock);

	if (stat & GRA_FRAME_IRQ) {
		struct drm_device *dev = dcrtc->crtc.dev;

		spin_lock(&dev->event_lock);
		if (dcrtc->frame_work)
			armada_drm_crtc_complete_frame_work(dcrtc);
		spin_unlock(&dev->event_lock);

		wake_up(&dcrtc->frame_wait);
		struct armada_plane *plane = drm_to_armada_plane(dcrtc->crtc.primary);
		armada_drm_plane_work_run(dcrtc, plane);
	}
}

@@ -527,7 +565,8 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
		adj->crtc_vtotal, tm, bm);

	/* Wait for pending flips to complete */
	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
	armada_drm_plane_work_wait(drm_to_armada_plane(dcrtc->crtc.primary),
				   MAX_SCHEDULE_TIMEOUT);

	drm_crtc_vblank_off(crtc);

@@ -537,6 +576,13 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
		writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
	}

	/*
	 * If we are blanked, we would have disabled the clock.  Re-enable
	 * it so that compute_clock() does the right thing.
	 */
	if (!IS_ERR(dcrtc->clk) && dpms_blanked(dcrtc->dpms))
		WARN_ON(clk_prepare_enable(dcrtc->clk));

	/* Now compute the divider for real */
	dcrtc->variant->compute_clock(dcrtc, adj, &sclk);

@@ -637,7 +683,8 @@ static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
	armada_reg_queue_end(regs, i);

	/* Wait for pending flips to complete */
	wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
	armada_drm_plane_work_wait(drm_to_armada_plane(dcrtc->crtc.primary),
				   MAX_SCHEDULE_TIMEOUT);

	/* Take a reference to the new fb as we're using it */
	drm_framebuffer_reference(crtc->primary->fb);
@@ -651,18 +698,47 @@ static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
	return 0;
}

void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc,
	struct drm_plane *plane)
{
	u32 sram_para1, dma_ctrl0_mask;

	/*
	 * Drop our reference on any framebuffer attached to this plane.
	 * We don't need to NULL this out as drm_plane_force_disable(),
	 * and __setplane_internal() will do so for an overlay plane, and
	 * __drm_helper_disable_unused_functions() will do so for the
	 * primary plane.
	 */
	if (plane->fb)
		drm_framebuffer_unreference(plane->fb);

	/* Power down the Y/U/V FIFOs */
	sram_para1 = CFG_PDWN16x66 | CFG_PDWN32x66;

	/* Power down most RAMs and FIFOs if this is the primary plane */
	if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
		sram_para1 |= CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 |
			      CFG_PDWN32x32 | CFG_PDWN64x66;
		dma_ctrl0_mask = CFG_GRA_ENA;
	} else {
		dma_ctrl0_mask = CFG_DMA_ENA;
	}

	spin_lock_irq(&dcrtc->irq_lock);
	armada_updatel(0, dma_ctrl0_mask, dcrtc->base + LCD_SPU_DMA_CTRL0);
	spin_unlock_irq(&dcrtc->irq_lock);

	armada_updatel(sram_para1, 0, dcrtc->base + LCD_SPU_SRAM_PARA1);
}

/* The mode_config.mutex will be held for this call */
static void armada_drm_crtc_disable(struct drm_crtc *crtc)
{
	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);

	armada_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
	armada_drm_crtc_finish_fb(dcrtc, crtc->primary->fb, true);

	/* Power down most RAMs and FIFOs */
	writel_relaxed(CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 |
		       CFG_PDWN32x32 | CFG_PDWN16x66 | CFG_PDWN32x66 |
		       CFG_PDWN64x66, dcrtc->base + LCD_SPU_SRAM_PARA1);
	armada_drm_crtc_plane_disable(dcrtc, crtc->primary);
}

static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
@@ -920,8 +996,6 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
{
	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
	struct armada_frame_work *work;
	struct drm_device *dev = crtc->dev;
	unsigned long flags;
	unsigned i;
	int ret;

@@ -933,6 +1007,7 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
	if (!work)
		return -ENOMEM;

	work->work.fn = armada_drm_crtc_complete_frame_work;
	work->event = event;
	work->old_fb = dcrtc->crtc.primary->fb;

@@ -966,12 +1041,8 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
	 * Finally, if the display is blanked, we won't receive an
	 * interrupt, so complete it now.
	 */
	if (dpms_blanked(dcrtc->dpms)) {
		spin_lock_irqsave(&dev->event_lock, flags);
		if (dcrtc->frame_work)
			armada_drm_crtc_complete_frame_work(dcrtc);
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
	if (dpms_blanked(dcrtc->dpms))
		armada_drm_plane_work_run(dcrtc, drm_to_armada_plane(dcrtc->crtc.primary));

	return 0;
}
@@ -1012,6 +1083,19 @@ static struct drm_crtc_funcs armada_crtc_funcs = {
	.set_property	= armada_drm_crtc_set_property,
};

static const struct drm_plane_funcs armada_primary_plane_funcs = {
	.update_plane	= drm_primary_helper_update,
	.disable_plane	= drm_primary_helper_disable,
	.destroy	= drm_primary_helper_destroy,
};

int armada_drm_plane_init(struct armada_plane *plane)
{
	init_waitqueue_head(&plane->frame_wait);

	return 0;
}

static struct drm_prop_enum_list armada_drm_csc_yuv_enum_list[] = {
	{ CSC_AUTO,        "Auto" },
	{ CSC_YUV_CCIR601, "CCIR601" },
@@ -1044,12 +1128,13 @@ static int armada_drm_crtc_create_properties(struct drm_device *dev)
	return 0;
}

int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
	struct resource *res, int irq, const struct armada_variant *variant,
	struct device_node *port)
{
	struct armada_private *priv = drm->dev_private;
	struct armada_crtc *dcrtc;
	struct armada_plane *primary;
	void __iomem *base;
	int ret;

@@ -1080,8 +1165,6 @@ int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
	dcrtc->spu_iopad_ctrl = CFG_VSCALE_LN_EN | CFG_IOPAD_DUMB24;
	spin_lock_init(&dcrtc->irq_lock);
	dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR;
	INIT_LIST_HEAD(&dcrtc->vbl_list);
	init_waitqueue_head(&dcrtc->frame_wait);

	/* Initialize some registers which we don't otherwise set */
	writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV);
@@ -1118,7 +1201,32 @@ int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
	priv->dcrtc[dcrtc->num] = dcrtc;

	dcrtc->crtc.port = port;
	drm_crtc_init(drm, &dcrtc->crtc, &armada_crtc_funcs);

	primary = kzalloc(sizeof(*primary), GFP_KERNEL);
	if (!primary)
		return -ENOMEM;

	ret = armada_drm_plane_init(primary);
	if (ret) {
		kfree(primary);
		return ret;
	}

	ret = drm_universal_plane_init(drm, &primary->base, 0,
				       &armada_primary_plane_funcs,
				       armada_primary_formats,
				       ARRAY_SIZE(armada_primary_formats),
				       DRM_PLANE_TYPE_PRIMARY);
	if (ret) {
		kfree(primary);
		return ret;
	}

	ret = drm_crtc_init_with_planes(drm, &dcrtc->crtc, &primary->base, NULL,
					&armada_crtc_funcs);
	if (ret)
		goto err_crtc_init;

	drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs);

	drm_object_attach_property(&dcrtc->crtc.base, priv->csc_yuv_prop,
@@ -1127,6 +1235,10 @@ int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
				   dcrtc->csc_rgb_mode);

	return armada_overlay_plane_create(drm, 1 << dcrtc->num);

err_crtc_init:
	primary->base.funcs->destroy(&primary->base);
	return ret;
}

static int
+25 −9
Original line number Diff line number Diff line
@@ -31,9 +31,30 @@ struct armada_regs {
#define armada_reg_queue_end(_r, _i)		\
	armada_reg_queue_mod(_r, _i, 0, 0, ~0)

struct armada_frame_work;
struct armada_crtc;
struct armada_plane;
struct armada_variant;

struct armada_plane_work {
	void			(*fn)(struct armada_crtc *,
				      struct armada_plane *,
				      struct armada_plane_work *);
};

struct armada_plane {
	struct drm_plane	base;
	wait_queue_head_t	frame_wait;
	struct armada_plane_work *work;
};
#define drm_to_armada_plane(p) container_of(p, struct armada_plane, base)

int armada_drm_plane_init(struct armada_plane *plane);
int armada_drm_plane_work_queue(struct armada_crtc *dcrtc,
	struct armada_plane *plane, struct armada_plane_work *work);
int armada_drm_plane_work_wait(struct armada_plane *plane, long timeout);
struct armada_plane_work *armada_drm_plane_work_cancel(
	struct armada_crtc *dcrtc, struct armada_plane *plane);

struct armada_crtc {
	struct drm_crtc		crtc;
	const struct armada_variant *variant;
@@ -66,25 +87,20 @@ struct armada_crtc {
	uint32_t		dumb_ctrl;
	uint32_t		spu_iopad_ctrl;

	wait_queue_head_t	frame_wait;
	struct armada_frame_work *frame_work;

	spinlock_t		irq_lock;
	uint32_t		irq_ena;
	struct list_head	vbl_list;
};
#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc)

struct device_node;
int armada_drm_crtc_create(struct drm_device *, struct device *,
	struct resource *, int, const struct armada_variant *,
	struct device_node *);
void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int);
void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int);
void armada_drm_crtc_disable_irq(struct armada_crtc *, u32);
void armada_drm_crtc_enable_irq(struct armada_crtc *, u32);
void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *);

void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc,
	struct drm_plane *plane);

extern struct platform_driver armada_lcd_platform_driver;

#endif
+0 −16
Original line number Diff line number Diff line
@@ -37,22 +37,6 @@ static inline uint32_t armada_pitch(uint32_t width, uint32_t bpp)
	return ALIGN(pitch, 128);
}

struct armada_vbl_event {
	struct list_head	node;
	void			*data;
	void			(*fn)(struct armada_crtc *, void *);
};
void armada_drm_vbl_event_add(struct armada_crtc *,
	struct armada_vbl_event *);
void armada_drm_vbl_event_remove(struct armada_crtc *,
	struct armada_vbl_event *);
#define armada_drm_vbl_event_init(_e, _f, _d) do {	\
	struct armada_vbl_event *__e = _e;		\
	INIT_LIST_HEAD(&__e->node);			\
	__e->data = _d;					\
	__e->fn = _f;					\
} while (0)


struct armada_private;

Loading