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

Commit 8a105aaa 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

Merge armada changes, I've confirmed the componenet changes are same as in Greg's tree.
* 'drm-armada-devel' of git://ftp.arm.linux.org.uk/~rmk/linux-arm:
  drm/armada: register crtc with port
  drm/armada: permit CRTCs to be registered as separate devices
  dt-bindings: add Marvell Dove LCD controller documentation
  drm/armada: update Armada 510 (Dove) to use "ext_ref_clk1" as the clock
  drm/armada: convert to componentized support
  drm: add of_graph endpoint helper to find possible CRTCs
  component: fix bug with legacy API
  drm/armada: make variant a CRTC thing
  drm/armada: move variant initialisation to CRTC init
  drm/armada: use number of CRTCs registered
  drm/armada: move IRQ handling into CRTC
  component: add support for component match array
  component: ignore multiple additions of the same component
  component: fix missed cleanup in case of devres failure
parents 7296c849 9611cb93
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
Device Tree bindings for Armada DRM CRTC driver

Required properties:
 - compatible: value should be "marvell,dove-lcd".
 - reg: base address and size of the LCD controller
 - interrupts: single interrupt number for the LCD controller
 - port: video output port with endpoints, as described by graph.txt

Optional properties:

 - clocks: as described by clock-bindings.txt
 - clock-names: as described by clock-bindings.txt
	"axiclk" - axi bus clock for pixel clock
	"plldivider" - pll divider clock for pixel clock
	"ext_ref_clk0" - external clock 0 for pixel clock
	"ext_ref_clk1" - external clock 1 for pixel clock

Note: all clocks are optional but at least one must be specified.
Further clocks may be added in the future according to requirements of
different SoCs.

Example:

	lcd0: lcd-controller@820000 {
		compatible = "marvell,dove-lcd";
		reg = <0x820000 0x1000>;
		interrupts = <47>;
		clocks = <&si5351 0>;
		clock-names = "ext_ref_clk_1";
	};
+157 −35
Original line number Diff line number Diff line
@@ -18,6 +18,15 @@
#include <linux/mutex.h>
#include <linux/slab.h>

struct component_match {
	size_t alloc;
	size_t num;
	struct {
		void *data;
		int (*fn)(struct device *, void *);
	} compare[0];
};

struct master {
	struct list_head node;
	struct list_head components;
@@ -25,6 +34,7 @@ struct master {

	const struct component_master_ops *ops;
	struct device *dev;
	struct component_match *match;
};

struct component {
@@ -69,6 +79,11 @@ static void component_detach_master(struct master *master, struct component *c)
	c->master = NULL;
}

/*
 * Add a component to a master, finding the component via the compare
 * function and compare data.  This is safe to call for duplicate matches
 * and will not result in the same component being added multiple times.
 */
int component_master_add_child(struct master *master,
	int (*compare)(struct device *, void *), void *compare_data)
{
@@ -76,10 +91,11 @@ int component_master_add_child(struct master *master,
	int ret = -ENXIO;

	list_for_each_entry(c, &component_list, node) {
		if (c->master)
		if (c->master && c->master != master)
			continue;

		if (compare(c->dev, compare_data)) {
			if (!c->master)
				component_attach_master(master, c);
			ret = 0;
			break;
@@ -90,6 +106,34 @@ int component_master_add_child(struct master *master,
}
EXPORT_SYMBOL_GPL(component_master_add_child);

static int find_components(struct master *master)
{
	struct component_match *match = master->match;
	size_t i;
	int ret = 0;

	if (!match) {
		/*
		 * Search the list of components, looking for components that
		 * belong to this master, and attach them to the master.
		 */
		return master->ops->add_components(master->dev, master);
	}

	/*
	 * Scan the array of match functions and attach
	 * any components which are found to this master.
	 */
	for (i = 0; i < match->num; i++) {
		ret = component_master_add_child(master,
						 match->compare[i].fn,
						 match->compare[i].data);
		if (ret)
			break;
	}
	return ret;
}

/* Detach all attached components from this master */
static void master_remove_components(struct master *master)
{
@@ -113,22 +157,22 @@ static void master_remove_components(struct master *master)
static int try_to_bring_up_master(struct master *master,
	struct component *component)
{
	int ret = 0;
	int ret;

	if (master->bound)
		return 0;

	if (!master->bound) {
	/*
	 * Search the list of components, looking for components that
	 * belong to this master, and attach them to the master.
	 */
		if (master->ops->add_components(master->dev, master)) {
	if (find_components(master)) {
		/* Failed to find all components */
			master_remove_components(master);
		ret = 0;
		goto out;
	}

	if (component && component->master != master) {
			master_remove_components(master);
		ret = 0;
		goto out;
	}
@@ -143,14 +187,14 @@ static int try_to_bring_up_master(struct master *master,
	if (ret < 0) {
		devres_release_group(master->dev, NULL);
		dev_info(master->dev, "master bind failed: %d\n", ret);
			master_remove_components(master);
		goto out;
	}

	master->bound = true;
		ret = 1;
	}
	return 1;

out:
	master_remove_components(master);

	return ret;
}
@@ -180,18 +224,89 @@ static void take_down_master(struct master *master)
	master_remove_components(master);
}

int component_master_add(struct device *dev,
	const struct component_master_ops *ops)
static size_t component_match_size(size_t num)
{
	return offsetof(struct component_match, compare[num]);
}

static struct component_match *component_match_realloc(struct device *dev,
	struct component_match *match, size_t num)
{
	struct component_match *new;

	if (match && match->alloc == num)
		return match;

	new = devm_kmalloc(dev, component_match_size(num), GFP_KERNEL);
	if (!new)
		return ERR_PTR(-ENOMEM);

	if (match) {
		memcpy(new, match, component_match_size(min(match->num, num)));
		devm_kfree(dev, match);
	} else {
		new->num = 0;
	}

	new->alloc = num;

	return new;
}

/*
 * Add a component to be matched.
 *
 * The match array is first created or extended if necessary.
 */
void component_match_add(struct device *dev, struct component_match **matchptr,
	int (*compare)(struct device *, void *), void *compare_data)
{
	struct component_match *match = *matchptr;

	if (IS_ERR(match))
		return;

	if (!match || match->num == match->alloc) {
		size_t new_size = match ? match->alloc + 16 : 15;

		match = component_match_realloc(dev, match, new_size);

		*matchptr = match;

		if (IS_ERR(match))
			return;
	}

	match->compare[match->num].fn = compare;
	match->compare[match->num].data = compare_data;
	match->num++;
}
EXPORT_SYMBOL(component_match_add);

int component_master_add_with_match(struct device *dev,
	const struct component_master_ops *ops,
	struct component_match *match)
{
	struct master *master;
	int ret;

	if (ops->add_components && match)
		return -EINVAL;

	if (match) {
		/* Reallocate the match array for its true size */
		match = component_match_realloc(dev, match, match->num);
		if (IS_ERR(match))
			return PTR_ERR(match);
	}

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

	master->dev = dev;
	master->ops = ops;
	master->match = match;
	INIT_LIST_HEAD(&master->components);

	/* Add to the list of available masters. */
@@ -209,6 +324,13 @@ int component_master_add(struct device *dev,

	return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL_GPL(component_master_add_with_match);

int component_master_add(struct device *dev,
	const struct component_master_ops *ops)
{
	return component_master_add_with_match(dev, ops, NULL);
}
EXPORT_SYMBOL_GPL(component_master_add);

void component_master_del(struct device *dev,
+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ drm-$(CONFIG_COMPAT) += drm_ioc32.o
drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
drm-$(CONFIG_PCI) += ati_pcigart.o
drm-$(CONFIG_DRM_PANEL) += drm_panel.o
drm-$(CONFIG_OF) += drm_of.o

drm-usb-y   := drm_usb.o

+10 −13
Original line number Diff line number Diff line
@@ -15,20 +15,19 @@
#include "armada_drm.h"
#include "armada_hw.h"

static int armada510_init(struct armada_private *priv, struct device *dev)
static int armada510_crtc_init(struct armada_crtc *dcrtc, struct device *dev)
{
	priv->extclk[0] = devm_clk_get(dev, "ext_ref_clk_1");
	struct clk *clk;

	if (IS_ERR(priv->extclk[0]) && PTR_ERR(priv->extclk[0]) == -ENOENT)
		priv->extclk[0] = ERR_PTR(-EPROBE_DEFER);
	clk = devm_clk_get(dev, "ext_ref_clk1");
	if (IS_ERR(clk))
		return PTR_ERR(clk) == -ENOENT ? -EPROBE_DEFER : PTR_ERR(clk);

	return PTR_RET(priv->extclk[0]);
}
	dcrtc->extclk[0] = clk;

static int armada510_crtc_init(struct armada_crtc *dcrtc)
{
	/* Lower the watermark so to eliminate jitter at higher bandwidths */
	armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);

	return 0;
}

@@ -45,8 +44,7 @@ static int armada510_crtc_init(struct armada_crtc *dcrtc)
static int armada510_crtc_compute_clock(struct armada_crtc *dcrtc,
	const struct drm_display_mode *mode, uint32_t *sclk)
{
	struct armada_private *priv = dcrtc->crtc.dev->dev_private;
	struct clk *clk = priv->extclk[0];
	struct clk *clk = dcrtc->extclk[0];
	int ret;

	if (dcrtc->num == 1)
@@ -81,7 +79,6 @@ static int armada510_crtc_compute_clock(struct armada_crtc *dcrtc,
const struct armada_variant armada510_ops = {
	.has_spu_adv_reg = true,
	.spu_adv_reg = ADV_HWC32ENABLE | ADV_HWC32ARGB | ADV_HWC32BLEND,
	.init = armada510_init,
	.crtc_init = armada510_crtc_init,
	.crtc_compute_clock = armada510_crtc_compute_clock,
	.init = armada510_crtc_init,
	.compute_clock = armada510_crtc_compute_clock,
};
+164 −23
Original line number Diff line number Diff line
@@ -7,6 +7,9 @@
 * published by the Free Software Foundation.
 */
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "armada_crtc.h"
@@ -332,24 +335,23 @@ static void armada_drm_crtc_commit(struct drm_crtc *crtc)
static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc,
	const struct drm_display_mode *mode, struct drm_display_mode *adj)
{
	struct armada_private *priv = crtc->dev->dev_private;
	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
	int ret;

	/* We can't do interlaced modes if we don't have the SPU_ADV_REG */
	if (!priv->variant->has_spu_adv_reg &&
	if (!dcrtc->variant->has_spu_adv_reg &&
	    adj->flags & DRM_MODE_FLAG_INTERLACE)
		return false;

	/* Check whether the display mode is possible */
	ret = priv->variant->crtc_compute_clock(dcrtc, adj, NULL);
	ret = dcrtc->variant->compute_clock(dcrtc, adj, NULL);
	if (ret)
		return false;

	return true;
}

void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
static void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
{
	struct armada_vbl_event *e, *n;
	void __iomem *base = dcrtc->base;
@@ -410,6 +412,27 @@ void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat)
	}
}

static irqreturn_t armada_drm_irq(int irq, void *arg)
{
	struct armada_crtc *dcrtc = arg;
	u32 v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR);

	/*
	 * This is rediculous - rather than writing bits to clear, we
	 * have to set the actual status register value.  This is racy.
	 */
	writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);

	/* Mask out those interrupts we haven't enabled */
	v = stat & dcrtc->irq_ena;

	if (v & (VSYNC_IRQ|GRA_FRAME_IRQ|DUMB_FRAMEDONE)) {
		armada_drm_crtc_irq(dcrtc, stat);
		return IRQ_HANDLED;
	}
	return IRQ_NONE;
}

/* These are locked by dev->vbl_lock */
void armada_drm_crtc_disable_irq(struct armada_crtc *dcrtc, u32 mask)
{
@@ -470,7 +493,6 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
	struct drm_display_mode *mode, struct drm_display_mode *adj,
	int x, int y, struct drm_framebuffer *old_fb)
{
	struct armada_private *priv = crtc->dev->dev_private;
	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
	struct armada_regs regs[17];
	uint32_t lm, rm, tm, bm, val, sclk;
@@ -515,7 +537,7 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
	}

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

	/* Ensure graphic fifo is enabled */
	armada_reg_queue_mod(regs, i, 0, CFG_PDWN64x66, LCD_SPU_SRAM_PARA1);
@@ -537,7 +559,7 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
	dcrtc->v[1].spu_v_porch = tm << 16 | bm;
	val = adj->crtc_hsync_start;
	dcrtc->v[1].spu_adv_reg = val << 20 | val | ADV_VSYNCOFFEN |
		priv->variant->spu_adv_reg;
		dcrtc->variant->spu_adv_reg;

	if (interlaced) {
		/* Odd interlaced frame */
@@ -546,7 +568,7 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
		dcrtc->v[0].spu_v_porch = dcrtc->v[1].spu_v_porch + 1;
		val = adj->crtc_hsync_start - adj->crtc_htotal / 2;
		dcrtc->v[0].spu_adv_reg = val << 20 | val | ADV_VSYNCOFFEN |
			priv->variant->spu_adv_reg;
			dcrtc->variant->spu_adv_reg;
	} else {
		dcrtc->v[0] = dcrtc->v[1];
	}
@@ -561,7 +583,7 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
	armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_h_total,
			   LCD_SPUT_V_H_TOTAL);

	if (priv->variant->has_spu_adv_reg) {
	if (dcrtc->variant->has_spu_adv_reg) {
		armada_reg_queue_mod(regs, i, dcrtc->v[0].spu_adv_reg,
				     ADV_VSYNC_L_OFF | ADV_VSYNC_H_OFF |
				     ADV_VSYNCOFFEN, LCD_SPU_ADV_REG);
@@ -805,12 +827,11 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc,
{
	struct drm_device *dev = crtc->dev;
	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
	struct armada_private *priv = crtc->dev->dev_private;
	struct armada_gem_object *obj = NULL;
	int ret;

	/* If no cursor support, replicate drm's return value */
	if (!priv->variant->has_spu_adv_reg)
	if (!dcrtc->variant->has_spu_adv_reg)
		return -ENXIO;

	if (handle && w > 0 && h > 0) {
@@ -858,11 +879,10 @@ static int armada_drm_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
{
	struct drm_device *dev = crtc->dev;
	struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
	struct armada_private *priv = crtc->dev->dev_private;
	int ret;

	/* If no cursor support, replicate drm's return value */
	if (!priv->variant->has_spu_adv_reg)
	if (!dcrtc->variant->has_spu_adv_reg)
		return -EFAULT;

	mutex_lock(&dev->struct_mutex);
@@ -888,6 +908,10 @@ static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
	if (!IS_ERR(dcrtc->clk))
		clk_disable_unprepare(dcrtc->clk);

	writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ENA);

	of_node_put(dcrtc->crtc.port);

	kfree(dcrtc);
}

@@ -1027,19 +1051,20 @@ static int armada_drm_crtc_create_properties(struct drm_device *dev)
	return 0;
}

int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
	struct resource *res)
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 = dev->dev_private;
	struct armada_private *priv = drm->dev_private;
	struct armada_crtc *dcrtc;
	void __iomem *base;
	int ret;

	ret = armada_drm_crtc_create_properties(dev);
	ret = armada_drm_crtc_create_properties(drm);
	if (ret)
		return ret;

	base = devm_request_and_ioremap(dev->dev, res);
	base = devm_request_and_ioremap(dev, res);
	if (!base) {
		DRM_ERROR("failed to ioremap register\n");
		return -ENOMEM;
@@ -1051,8 +1076,12 @@ int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
		return -ENOMEM;
	}

	if (dev != drm->dev)
		dev_set_drvdata(dev, dcrtc);

	dcrtc->variant = variant;
	dcrtc->base = base;
	dcrtc->num = num;
	dcrtc->num = drm->mode_config.num_crtc;
	dcrtc->clk = ERR_PTR(-EINVAL);
	dcrtc->csc_yuv_mode = CSC_AUTO;
	dcrtc->csc_rgb_mode = CSC_AUTO;
@@ -1074,9 +1103,18 @@ int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
		       CFG_PDWN64x66, dcrtc->base + LCD_SPU_SRAM_PARA1);
	writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
	writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN);
	writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
	writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);

	ret = devm_request_irq(dev, irq, armada_drm_irq, 0, "armada_drm_crtc",
			       dcrtc);
	if (ret < 0) {
		kfree(dcrtc);
		return ret;
	}

	if (priv->variant->crtc_init) {
		ret = priv->variant->crtc_init(dcrtc);
	if (dcrtc->variant->init) {
		ret = dcrtc->variant->init(dcrtc, dev);
		if (ret) {
			kfree(dcrtc);
			return ret;
@@ -1088,7 +1126,8 @@ int armada_drm_crtc_create(struct drm_device *dev, unsigned num,

	priv->dcrtc[dcrtc->num] = dcrtc;

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

	drm_object_attach_property(&dcrtc->crtc.base, priv->csc_yuv_prop,
@@ -1096,5 +1135,107 @@ int armada_drm_crtc_create(struct drm_device *dev, unsigned num,
	drm_object_attach_property(&dcrtc->crtc.base, priv->csc_rgb_prop,
				   dcrtc->csc_rgb_mode);

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

static int
armada_lcd_bind(struct device *dev, struct device *master, void *data)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct drm_device *drm = data;
	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	int irq = platform_get_irq(pdev, 0);
	const struct armada_variant *variant;
	struct device_node *port = NULL;

	if (irq < 0)
		return irq;

	if (!dev->of_node) {
		const struct platform_device_id *id;

		id = platform_get_device_id(pdev);
		if (!id)
			return -ENXIO;

		variant = (const struct armada_variant *)id->driver_data;
	} else {
		const struct of_device_id *match;
		struct device_node *np, *parent = dev->of_node;

		match = of_match_device(dev->driver->of_match_table, dev);
		if (!match)
			return -ENXIO;

		np = of_get_child_by_name(parent, "ports");
		if (np)
			parent = np;
		port = of_get_child_by_name(parent, "port");
		of_node_put(np);
		if (!port) {
			dev_err(dev, "no port node found in %s\n",
				parent->full_name);
			return -ENXIO;
		}

		variant = match->data;
	}

	return armada_drm_crtc_create(drm, dev, res, irq, variant, port);
}

static void
armada_lcd_unbind(struct device *dev, struct device *master, void *data)
{
	struct armada_crtc *dcrtc = dev_get_drvdata(dev);

	armada_drm_crtc_destroy(&dcrtc->crtc);
}

static const struct component_ops armada_lcd_ops = {
	.bind = armada_lcd_bind,
	.unbind = armada_lcd_unbind,
};

static int armada_lcd_probe(struct platform_device *pdev)
{
	return component_add(&pdev->dev, &armada_lcd_ops);
}

static int armada_lcd_remove(struct platform_device *pdev)
{
	component_del(&pdev->dev, &armada_lcd_ops);
	return 0;
}

static struct of_device_id armada_lcd_of_match[] = {
	{
		.compatible	= "marvell,dove-lcd",
		.data		= &armada510_ops,
	},
	{}
};
MODULE_DEVICE_TABLE(of, armada_lcd_of_match);

static const struct platform_device_id armada_lcd_platform_ids[] = {
	{
		.name		= "armada-lcd",
		.driver_data	= (unsigned long)&armada510_ops,
	}, {
		.name		= "armada-510-lcd",
		.driver_data	= (unsigned long)&armada510_ops,
	},
	{ },
};
MODULE_DEVICE_TABLE(platform, armada_lcd_platform_ids);

struct platform_driver armada_lcd_platform_driver = {
	.probe	= armada_lcd_probe,
	.remove	= armada_lcd_remove,
	.driver = {
		.name	= "armada-lcd",
		.owner	=  THIS_MODULE,
		.of_match_table = armada_lcd_of_match,
	},
	.id_table = armada_lcd_platform_ids,
};
Loading