diff options
Diffstat (limited to 'drivers/gpu/drm/tilcdc')
| -rw-r--r-- | drivers/gpu/drm/tilcdc/Kconfig | 12 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/Makefile | 5 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 36 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_drv.c | 99 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_drv.h | 6 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_external.c | 166 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_external.h (renamed from drivers/gpu/drm/tilcdc/tilcdc_slave.h) | 17 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_slave.c | 411 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c | 270 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts | 72 | ||||
| -rw-r--r-- | drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h | 25 | 
11 files changed, 670 insertions, 449 deletions
| diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig index 8394a0b3993e..78beafb0742c 100644 --- a/drivers/gpu/drm/tilcdc/Kconfig +++ b/drivers/gpu/drm/tilcdc/Kconfig @@ -12,3 +12,15 @@ config DRM_TILCDC  	  Choose this option if you have an TI SoC with LCDC display  	  controller, for example AM33xx in beagle-bone, DA8xx, or  	  OMAP-L1xx.  This driver replaces the FB_DA8XX fbdev driver. + +config DRM_TILCDC_SLAVE_COMPAT +	bool "Support device tree blobs using TI LCDC Slave binding" +	depends on DRM_TILCDC +	default y +	select OF_RESOLVE +	select OF_OVERLAY +	help +	  Choose this option if you need a kernel that is compatible +	  with device tree blobs using the obsolete "ti,tilcdc,slave" +	  binding. If you find "ti,tilcdc,slave"-string from your DTB, +	  you probably need this. Otherwise you do not. diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile index 7d2eefe94bf7..deeca4869d94 100644 --- a/drivers/gpu/drm/tilcdc/Makefile +++ b/drivers/gpu/drm/tilcdc/Makefile @@ -3,11 +3,14 @@ ifeq (, $(findstring -W,$(EXTRA_CFLAGS)))  	ccflags-y += -Werror  endif +obj-$(CONFIG_DRM_TILCDC_SLAVE_COMPAT) += tilcdc_slave_compat.o \ +					 tilcdc_slave_compat.dtb.o +  tilcdc-y := \  	tilcdc_crtc.o \  	tilcdc_tfp410.o \ -	tilcdc_slave.o \  	tilcdc_panel.o \ +	tilcdc_external.o \  	tilcdc_drv.o  obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c index c73588483be0..7d07733bdc86 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -37,6 +37,9 @@ struct tilcdc_crtc {  	/* for deferred fb unref's: */  	struct drm_flip_work unref_work; + +	/* Only set if an external encoder is connected */ +	bool simulate_vesa_sync;  };  #define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base) @@ -135,11 +138,12 @@ static void stop(struct drm_crtc *crtc)  	tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);  } +static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode);  static void tilcdc_crtc_destroy(struct drm_crtc *crtc)  {  	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); -	WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON); +	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);  	drm_crtc_cleanup(crtc);  	drm_flip_work_cleanup(&tilcdc_crtc->unref_work); @@ -213,6 +217,28 @@ static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,  		const struct drm_display_mode *mode,  		struct drm_display_mode *adjusted_mode)  { +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + +	if (!tilcdc_crtc->simulate_vesa_sync) +		return true; + +	/* +	 * tilcdc does not generate VESA-compliant sync but aligns +	 * VS on the second edge of HS instead of first edge. +	 * We use adjusted_mode, to fixup sync by aligning both rising +	 * edges and add HSKEW offset to fix the sync. +	 */ +	adjusted_mode->hskew = mode->hsync_end - mode->hsync_start; +	adjusted_mode->flags |= DRM_MODE_FLAG_HSKEW; + +	if (mode->flags & DRM_MODE_FLAG_NHSYNC) { +		adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; +		adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; +	} else { +		adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC; +		adjusted_mode->flags &= ~DRM_MODE_FLAG_PHSYNC; +	} +  	return true;  } @@ -533,6 +559,14 @@ void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,  	tilcdc_crtc->info = info;  } +void tilcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *crtc, +					bool simulate_vesa_sync) +{ +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + +	tilcdc_crtc->simulate_vesa_sync = simulate_vesa_sync; +} +  void tilcdc_crtc_update_clk(struct drm_crtc *crtc)  {  	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 095fca91525c..0f283a3b932c 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -17,16 +17,17 @@  /* LCDC DRM driver, based on da8xx-fb */ +#include <linux/component.h> +  #include "tilcdc_drv.h"  #include "tilcdc_regs.h"  #include "tilcdc_tfp410.h" -#include "tilcdc_slave.h"  #include "tilcdc_panel.h" +#include "tilcdc_external.h"  #include "drm_fb_helper.h"  static LIST_HEAD(module_list); -static bool slave_probing;  void tilcdc_module_init(struct tilcdc_module *mod, const char *name,  		const struct tilcdc_module_ops *funcs) @@ -42,11 +43,6 @@ void tilcdc_module_cleanup(struct tilcdc_module *mod)  	list_del(&mod->list);  } -void tilcdc_slave_probedefer(bool defered) -{ -	slave_probing = defered; -} -  static struct of_device_id tilcdc_of_match[];  static struct drm_framebuffer *tilcdc_fb_create(struct drm_device *dev, @@ -80,13 +76,6 @@ static int modeset_init(struct drm_device *dev)  		mod->funcs->modeset_init(mod, dev);  	} -	if ((priv->num_encoders == 0) || (priv->num_connectors == 0)) { -		/* oh nos! */ -		dev_err(dev->dev, "no encoders/connectors found\n"); -		drm_mode_config_cleanup(dev); -		return -ENXIO; -	} -  	dev->mode_config.min_width = 0;  	dev->mode_config.min_height = 0;  	dev->mode_config.max_width = tilcdc_crtc_max_width(priv->crtc); @@ -121,6 +110,8 @@ static int tilcdc_unload(struct drm_device *dev)  {  	struct tilcdc_drm_private *priv = dev->dev_private; +	tilcdc_remove_external_encoders(dev); +  	drm_fbdev_cma_fini(priv->fbdev);  	drm_kms_helper_poll_fini(dev);  	drm_mode_config_cleanup(dev); @@ -171,6 +162,9 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)  	dev->dev_private = priv; +	priv->is_componentized = +		tilcdc_get_external_components(dev->dev, NULL) > 0; +  	priv->wq = alloc_ordered_workqueue("tilcdc", 0);  	if (!priv->wq) {  		ret = -ENOMEM; @@ -233,6 +227,7 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)  	DBG("Maximum Pixel Clock Value %dKHz", priv->max_pixelclock);  	pm_runtime_enable(dev->dev); +	pm_runtime_irq_safe(dev->dev);  	/* Determine LCD IP Version */  	pm_runtime_get_sync(dev->dev); @@ -260,10 +255,28 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)  		goto fail_cpufreq_unregister;  	} +	platform_set_drvdata(pdev, dev); + +	if (priv->is_componentized) { +		ret = component_bind_all(dev->dev, dev); +		if (ret < 0) +			goto fail_mode_config_cleanup; + +		ret = tilcdc_add_external_encoders(dev, &bpp); +		if (ret < 0) +			goto fail_component_cleanup; +	} + +	if ((priv->num_encoders == 0) || (priv->num_connectors == 0)) { +		dev_err(dev->dev, "no encoders/connectors found\n"); +		ret = -ENXIO; +		goto fail_external_cleanup; +	} +  	ret = drm_vblank_init(dev, 1);  	if (ret < 0) {  		dev_err(dev->dev, "failed to initialize vblank\n"); -		goto fail_mode_config_cleanup; +		goto fail_external_cleanup;  	}  	pm_runtime_get_sync(dev->dev); @@ -274,9 +287,6 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)  		goto fail_vblank_cleanup;  	} -	platform_set_drvdata(pdev, dev); - -  	list_for_each_entry(mod, &module_list, list) {  		DBG("%s: preferred_bpp: %d", mod->name, mod->preferred_bpp);  		bpp = mod->preferred_bpp; @@ -307,6 +317,13 @@ fail_vblank_cleanup:  fail_mode_config_cleanup:  	drm_mode_config_cleanup(dev); +fail_component_cleanup: +	if (priv->is_componentized) +		component_unbind_all(dev->dev, dev); + +fail_external_cleanup: +	tilcdc_remove_external_encoders(dev); +  fail_cpufreq_unregister:  	pm_runtime_disable(dev->dev);  #ifdef CONFIG_CPU_FREQ @@ -612,24 +629,56 @@ static const struct dev_pm_ops tilcdc_pm_ops = {   * Platform driver:   */ +static int tilcdc_bind(struct device *dev) +{ +	return drm_platform_init(&tilcdc_driver, to_platform_device(dev)); +} + +static void tilcdc_unbind(struct device *dev) +{ +	drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops tilcdc_comp_ops = { +	.bind = tilcdc_bind, +	.unbind = tilcdc_unbind, +}; +  static int tilcdc_pdev_probe(struct platform_device *pdev)  { +	struct component_match *match = NULL; +	int ret; +  	/* bail out early if no DT data: */  	if (!pdev->dev.of_node) {  		dev_err(&pdev->dev, "device-tree data is missing\n");  		return -ENXIO;  	} -	/* defer probing if slave is in deferred probing */ -	if (slave_probing == true) -		return -EPROBE_DEFER; - -	return drm_platform_init(&tilcdc_driver, pdev); +	ret = tilcdc_get_external_components(&pdev->dev, &match); +	if (ret < 0) +		return ret; +	else if (ret == 0) +		return drm_platform_init(&tilcdc_driver, pdev); +	else +		return component_master_add_with_match(&pdev->dev, +						       &tilcdc_comp_ops, +						       match);  }  static int tilcdc_pdev_remove(struct platform_device *pdev)  { -	drm_put_dev(platform_get_drvdata(pdev)); +	struct drm_device *ddev = dev_get_drvdata(&pdev->dev); +	struct tilcdc_drm_private *priv = ddev->dev_private; + +	/* Check if a subcomponent has already triggered the unloading. */ +	if (!priv) +		return 0; + +	if (priv->is_componentized) +		component_master_del(&pdev->dev, &tilcdc_comp_ops); +	else +		drm_put_dev(platform_get_drvdata(pdev));  	return 0;  } @@ -654,7 +703,6 @@ static int __init tilcdc_drm_init(void)  {  	DBG("init");  	tilcdc_tfp410_init(); -	tilcdc_slave_init();  	tilcdc_panel_init();  	return platform_driver_register(&tilcdc_platform_driver);  } @@ -664,7 +712,6 @@ static void __exit tilcdc_drm_fini(void)  	DBG("fini");  	platform_driver_unregister(&tilcdc_platform_driver);  	tilcdc_panel_fini(); -	tilcdc_slave_fini();  	tilcdc_tfp410_fini();  } diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h index 7596c144a9fb..e863ad0d26fe 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h @@ -85,6 +85,9 @@ struct tilcdc_drm_private {  	unsigned int num_connectors;  	struct drm_connector *connectors[8]; +	const struct drm_connector_helper_funcs *connector_funcs[8]; + +	bool is_componentized;  };  /* Sub-module for display.  Since we don't know at compile time what panels @@ -116,7 +119,6 @@ struct tilcdc_module {  void tilcdc_module_init(struct tilcdc_module *mod, const char *name,  		const struct tilcdc_module_ops *funcs);  void tilcdc_module_cleanup(struct tilcdc_module *mod); -void tilcdc_slave_probedefer(bool defered);  /* Panel config that needs to be set in the crtc, but is not coming from   * the mode timings.  The display module is expected to call @@ -166,6 +168,8 @@ irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc);  void tilcdc_crtc_update_clk(struct drm_crtc *crtc);  void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,  		const struct tilcdc_panel_info *info); +void tilcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *crtc, +					bool simulate_vesa_sync);  int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);  int tilcdc_crtc_max_width(struct drm_crtc *crtc); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.c b/drivers/gpu/drm/tilcdc/tilcdc_external.c new file mode 100644 index 000000000000..03acb4f99982 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha <jsarha@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ + +#include <linux/component.h> +#include <linux/of_graph.h> + +#include "tilcdc_drv.h" +#include "tilcdc_external.h" + +static const struct tilcdc_panel_info panel_info_tda998x = { +		.ac_bias                = 255, +		.ac_bias_intrpt         = 0, +		.dma_burst_sz           = 16, +		.bpp                    = 16, +		.fdd                    = 0x80, +		.tft_alt_mode           = 0, +		.invert_pxl_clk		= 1, +		.sync_edge              = 1, +		.sync_ctrl              = 1, +		.raster_order           = 0, +}; + +static int tilcdc_external_mode_valid(struct drm_connector *connector, +				      struct drm_display_mode *mode) +{ +	struct tilcdc_drm_private *priv = connector->dev->dev_private; +	int ret, i; + +	ret = tilcdc_crtc_mode_valid(priv->crtc, mode); +	if (ret != MODE_OK) +		return ret; + +	for (i = 0; i < priv->num_connectors && +		     priv->connectors[i] != connector; i++) +		; + +	BUG_ON(priv->connectors[i] != connector); +	BUG_ON(!priv->connector_funcs[i]); + +	/* If the connector has its own mode_valid call it. */ +	if (!IS_ERR(priv->connector_funcs[i]) && +	    priv->connector_funcs[i]->mode_valid) +		return priv->connector_funcs[i]->mode_valid(connector, mode); + +	return MODE_OK; +} + +static int tilcdc_add_external_encoder(struct drm_device *dev, int *bpp, +				       struct drm_connector *connector) +{ +	struct tilcdc_drm_private *priv = dev->dev_private; +	struct drm_connector_helper_funcs *connector_funcs; + +	priv->connectors[priv->num_connectors] = connector; +	priv->encoders[priv->num_encoders++] = connector->encoder; + +	/* Only tda998x is supported at the moment. */ +	tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true); +	tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x); +	*bpp = panel_info_tda998x.bpp; + +	connector_funcs = devm_kzalloc(dev->dev, sizeof(*connector_funcs), +				       GFP_KERNEL); +	if (!connector_funcs) +		return -ENOMEM; + +	/* connector->helper_private contains always struct +	 * connector_helper_funcs pointer. For tilcdc crtc to have a +	 * say if a specific mode is Ok, we need to install our own +	 * helper functions. In our helper functions we copy +	 * everything else but use our own mode_valid() (above). +	 */ +	if (connector->helper_private) { +		priv->connector_funcs[priv->num_connectors] = +			connector->helper_private; +		*connector_funcs = *priv->connector_funcs[priv->num_connectors]; +	} else { +		priv->connector_funcs[priv->num_connectors] = ERR_PTR(-ENOENT); +	} +	connector_funcs->mode_valid = tilcdc_external_mode_valid; +	drm_connector_helper_add(connector, connector_funcs); +	priv->num_connectors++; + +	dev_dbg(dev->dev, "External encoder '%s' connected\n", +		connector->encoder->name); + +	return 0; +} + +int tilcdc_add_external_encoders(struct drm_device *dev, int *bpp) +{ +	struct tilcdc_drm_private *priv = dev->dev_private; +	struct drm_connector *connector; +	int num_internal_connectors = priv->num_connectors; + +	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { +		bool found = false; +		int i, ret; + +		for (i = 0; i < num_internal_connectors; i++) +			if (connector == priv->connectors[i]) +				found = true; +		if (!found) { +			ret = tilcdc_add_external_encoder(dev, bpp, connector); +			if (ret) +				return ret; +		} +	} +	return 0; +} + +void tilcdc_remove_external_encoders(struct drm_device *dev) +{ +	struct tilcdc_drm_private *priv = dev->dev_private; +	int i; + +	/* Restore the original helper functions, if any. */ +	for (i = 0; i < priv->num_connectors; i++) +		if (IS_ERR(priv->connector_funcs[i])) +			drm_connector_helper_add(priv->connectors[i], NULL); +		else if (priv->connector_funcs[i]) +			drm_connector_helper_add(priv->connectors[i], +						 priv->connector_funcs[i]); +} + +static int dev_match_of(struct device *dev, void *data) +{ +	return dev->of_node == data; +} + +int tilcdc_get_external_components(struct device *dev, +				   struct component_match **match) +{ +	struct device_node *ep = NULL; +	int count = 0; + +	while ((ep = of_graph_get_next_endpoint(dev->of_node, ep))) { +		struct device_node *node; + +		node = of_graph_get_remote_port_parent(ep); +		if (!node && !of_device_is_available(node)) { +			of_node_put(node); +			continue; +		} + +		dev_dbg(dev, "Subdevice node '%s' found\n", node->name); +		if (match) +			component_match_add(dev, match, dev_match_of, node); +		of_node_put(node); +		count++; +	} + +	if (count > 1) { +		dev_err(dev, "Only one external encoder is supported\n"); +		return -EINVAL; +	} + +	return count; +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_external.h index 2f8504848320..6aabe2788760 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_slave.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.h @@ -1,6 +1,6 @@  /* - * Copyright (C) 2012 Texas Instruments - * Author: Rob Clark <robdclark@gmail.com> + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha <jsarha@ti.com>   *   * This program is free software; you can redistribute it and/or modify it   * under the terms of the GNU General Public License version 2 as published by @@ -15,12 +15,11 @@   * this program.  If not, see <http://www.gnu.org/licenses/>.   */ -#ifndef __TILCDC_SLAVE_H__ -#define __TILCDC_SLAVE_H__ - -/* sub-module for i2c slave encoder output */ - -int tilcdc_slave_init(void); -void tilcdc_slave_fini(void); +#ifndef __TILCDC_EXTERNAL_H__ +#define __TILCDC_EXTERNAL_H__ +int tilcdc_add_external_encoders(struct drm_device *dev, int *bpp); +void tilcdc_remove_external_encoders(struct drm_device *dev); +int tilcdc_get_external_components(struct device *dev, +				   struct component_match **match);  #endif /* __TILCDC_SLAVE_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c deleted file mode 100644 index 3775fd49dac4..000000000000 --- a/drivers/gpu/drm/tilcdc/tilcdc_slave.c +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright (C) 2012 Texas Instruments - * Author: Rob Clark <robdclark@gmail.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * - * 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. - * - * You should have received a copy of the GNU General Public License along with - * this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -#include <linux/i2c.h> -#include <linux/pinctrl/pinmux.h> -#include <linux/pinctrl/consumer.h> -#include <drm/drm_encoder_slave.h> - -#include "tilcdc_drv.h" - -struct slave_module { -	struct tilcdc_module base; -	struct i2c_adapter *i2c; -}; -#define to_slave_module(x) container_of(x, struct slave_module, base) - -static const struct tilcdc_panel_info slave_info = { -		.bpp                    = 16, -		.ac_bias                = 255, -		.ac_bias_intrpt         = 0, -		.dma_burst_sz           = 16, -		.fdd                    = 0x80, -		.tft_alt_mode           = 0, -		.sync_edge              = 0, -		.sync_ctrl              = 1, -		.raster_order           = 0, -}; - - -/* - * Encoder: - */ - -struct slave_encoder { -	struct drm_encoder_slave base; -	struct slave_module *mod; -}; -#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base) - -static inline struct drm_encoder_slave_funcs * -get_slave_funcs(struct drm_encoder *enc) -{ -	return to_encoder_slave(enc)->slave_funcs; -} - -static void slave_encoder_destroy(struct drm_encoder *encoder) -{ -	struct slave_encoder *slave_encoder = to_slave_encoder(encoder); -	if (get_slave_funcs(encoder)) -		get_slave_funcs(encoder)->destroy(encoder); -	drm_encoder_cleanup(encoder); -	kfree(slave_encoder); -} - -static void slave_encoder_prepare(struct drm_encoder *encoder) -{ -	drm_i2c_encoder_prepare(encoder); -	tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info); -} - -static bool slave_encoder_fixup(struct drm_encoder *encoder, -		const struct drm_display_mode *mode, -		struct drm_display_mode *adjusted_mode) -{ -	/* -	 * tilcdc does not generate VESA-complient sync but aligns -	 * VS on the second edge of HS instead of first edge. -	 * We use adjusted_mode, to fixup sync by aligning both rising -	 * edges and add HSKEW offset to let the slave encoder fix it up. -	 */ -	adjusted_mode->hskew = mode->hsync_end - mode->hsync_start; -	adjusted_mode->flags |= DRM_MODE_FLAG_HSKEW; - -	if (mode->flags & DRM_MODE_FLAG_NHSYNC) { -		adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; -		adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; -	} else { -		adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC; -		adjusted_mode->flags &= ~DRM_MODE_FLAG_PHSYNC; -	} - -	return drm_i2c_encoder_mode_fixup(encoder, mode, adjusted_mode); -} - - -static const struct drm_encoder_funcs slave_encoder_funcs = { -		.destroy        = slave_encoder_destroy, -}; - -static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = { -		.dpms           = drm_i2c_encoder_dpms, -		.mode_fixup     = slave_encoder_fixup, -		.prepare        = slave_encoder_prepare, -		.commit         = drm_i2c_encoder_commit, -		.mode_set       = drm_i2c_encoder_mode_set, -		.save           = drm_i2c_encoder_save, -		.restore        = drm_i2c_encoder_restore, -}; - -static const struct i2c_board_info info = { -		I2C_BOARD_INFO("tda998x", 0x70) -}; - -static struct drm_encoder *slave_encoder_create(struct drm_device *dev, -		struct slave_module *mod) -{ -	struct slave_encoder *slave_encoder; -	struct drm_encoder *encoder; -	int ret; - -	slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL); -	if (!slave_encoder) { -		dev_err(dev->dev, "allocation failed\n"); -		return NULL; -	} - -	slave_encoder->mod = mod; - -	encoder = &slave_encoder->base.base; -	encoder->possible_crtcs = 1; - -	ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs, -			DRM_MODE_ENCODER_TMDS); -	if (ret) -		goto fail; - -	drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs); - -	ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info); -	if (ret) -		goto fail; - -	return encoder; - -fail: -	slave_encoder_destroy(encoder); -	return NULL; -} - -/* - * Connector: - */ - -struct slave_connector { -	struct drm_connector base; - -	struct drm_encoder *encoder;  /* our connected encoder */ -	struct slave_module *mod; -}; -#define to_slave_connector(x) container_of(x, struct slave_connector, base) - -static void slave_connector_destroy(struct drm_connector *connector) -{ -	struct slave_connector *slave_connector = to_slave_connector(connector); -	drm_connector_unregister(connector); -	drm_connector_cleanup(connector); -	kfree(slave_connector); -} - -static enum drm_connector_status slave_connector_detect( -		struct drm_connector *connector, -		bool force) -{ -	struct drm_encoder *encoder = to_slave_connector(connector)->encoder; -	return get_slave_funcs(encoder)->detect(encoder, connector); -} - -static int slave_connector_get_modes(struct drm_connector *connector) -{ -	struct drm_encoder *encoder = to_slave_connector(connector)->encoder; -	return get_slave_funcs(encoder)->get_modes(encoder, connector); -} - -static int slave_connector_mode_valid(struct drm_connector *connector, -		  struct drm_display_mode *mode) -{ -	struct drm_encoder *encoder = to_slave_connector(connector)->encoder; -	struct tilcdc_drm_private *priv = connector->dev->dev_private; -	int ret; - -	ret = tilcdc_crtc_mode_valid(priv->crtc, mode); -	if (ret != MODE_OK) -		return ret; - -	return get_slave_funcs(encoder)->mode_valid(encoder, mode); -} - -static struct drm_encoder *slave_connector_best_encoder( -		struct drm_connector *connector) -{ -	struct slave_connector *slave_connector = to_slave_connector(connector); -	return slave_connector->encoder; -} - -static int slave_connector_set_property(struct drm_connector *connector, -		struct drm_property *property, uint64_t value) -{ -	struct drm_encoder *encoder = to_slave_connector(connector)->encoder; -	return get_slave_funcs(encoder)->set_property(encoder, -			connector, property, value); -} - -static const struct drm_connector_funcs slave_connector_funcs = { -	.destroy            = slave_connector_destroy, -	.dpms               = drm_helper_connector_dpms, -	.detect             = slave_connector_detect, -	.fill_modes         = drm_helper_probe_single_connector_modes, -	.set_property       = slave_connector_set_property, -}; - -static const struct drm_connector_helper_funcs slave_connector_helper_funcs = { -	.get_modes          = slave_connector_get_modes, -	.mode_valid         = slave_connector_mode_valid, -	.best_encoder       = slave_connector_best_encoder, -}; - -static struct drm_connector *slave_connector_create(struct drm_device *dev, -		struct slave_module *mod, struct drm_encoder *encoder) -{ -	struct slave_connector *slave_connector; -	struct drm_connector *connector; -	int ret; - -	slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL); -	if (!slave_connector) { -		dev_err(dev->dev, "allocation failed\n"); -		return NULL; -	} - -	slave_connector->encoder = encoder; -	slave_connector->mod = mod; - -	connector = &slave_connector->base; - -	drm_connector_init(dev, connector, &slave_connector_funcs, -			DRM_MODE_CONNECTOR_HDMIA); -	drm_connector_helper_add(connector, &slave_connector_helper_funcs); - -	connector->polled = DRM_CONNECTOR_POLL_CONNECT | -			DRM_CONNECTOR_POLL_DISCONNECT; - -	connector->interlace_allowed = 0; -	connector->doublescan_allowed = 0; - -	get_slave_funcs(encoder)->create_resources(encoder, connector); - -	ret = drm_mode_connector_attach_encoder(connector, encoder); -	if (ret) -		goto fail; - -	drm_connector_register(connector); - -	return connector; - -fail: -	slave_connector_destroy(connector); -	return NULL; -} - -/* - * Module: - */ - -static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev) -{ -	struct slave_module *slave_mod = to_slave_module(mod); -	struct tilcdc_drm_private *priv = dev->dev_private; -	struct drm_encoder *encoder; -	struct drm_connector *connector; - -	encoder = slave_encoder_create(dev, slave_mod); -	if (!encoder) -		return -ENOMEM; - -	connector = slave_connector_create(dev, slave_mod, encoder); -	if (!connector) -		return -ENOMEM; - -	priv->encoders[priv->num_encoders++] = encoder; -	priv->connectors[priv->num_connectors++] = connector; - -	return 0; -} - -static const struct tilcdc_module_ops slave_module_ops = { -		.modeset_init = slave_modeset_init, -}; - -/* - * Device: - */ - -static struct of_device_id slave_of_match[]; - -static int slave_probe(struct platform_device *pdev) -{ -	struct device_node *node = pdev->dev.of_node; -	struct device_node *i2c_node; -	struct slave_module *slave_mod; -	struct tilcdc_module *mod; -	struct pinctrl *pinctrl; -	uint32_t i2c_phandle; -	struct i2c_adapter *slavei2c; -	int ret = -EINVAL; - -	/* bail out early if no DT data: */ -	if (!node) { -		dev_err(&pdev->dev, "device-tree data is missing\n"); -		return -ENXIO; -	} - -	/* Bail out early if i2c not specified */ -	if (of_property_read_u32(node, "i2c", &i2c_phandle)) { -		dev_err(&pdev->dev, "could not get i2c bus phandle\n"); -		return ret; -	} - -	i2c_node = of_find_node_by_phandle(i2c_phandle); -	if (!i2c_node) { -		dev_err(&pdev->dev, "could not get i2c bus node\n"); -		return ret; -	} - -	/* but defer the probe if it can't be initialized it might come later */ -	slavei2c = of_find_i2c_adapter_by_node(i2c_node); -	of_node_put(i2c_node); - -	if (!slavei2c) { -		ret = -EPROBE_DEFER; -		tilcdc_slave_probedefer(true); -		dev_err(&pdev->dev, "could not get i2c\n"); -		return ret; -	} - -	slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL); -	if (!slave_mod) { -		ret = -ENOMEM; -		goto fail_adapter; -	} - -	mod = &slave_mod->base; -	pdev->dev.platform_data = mod; - -	mod->preferred_bpp = slave_info.bpp; - -	slave_mod->i2c = slavei2c; - -	tilcdc_module_init(mod, "slave", &slave_module_ops); - -	pinctrl = devm_pinctrl_get_select_default(&pdev->dev); -	if (IS_ERR(pinctrl)) -		dev_warn(&pdev->dev, "pins are not configured\n"); - -	tilcdc_slave_probedefer(false); - -	return 0; - -fail_adapter: -	i2c_put_adapter(slavei2c); -	return ret; -} - -static int slave_remove(struct platform_device *pdev) -{ -	struct tilcdc_module *mod = dev_get_platdata(&pdev->dev); -	struct slave_module *slave_mod = to_slave_module(mod); - -	tilcdc_module_cleanup(mod); -	kfree(slave_mod); - -	return 0; -} - -static struct of_device_id slave_of_match[] = { -		{ .compatible = "ti,tilcdc,slave", }, -		{ }, -}; - -struct platform_driver slave_driver = { -	.probe = slave_probe, -	.remove = slave_remove, -	.driver = { -		.owner = THIS_MODULE, -		.name = "slave", -		.of_match_table = slave_of_match, -	}, -}; - -int __init tilcdc_slave_init(void) -{ -	return platform_driver_register(&slave_driver); -} - -void __exit tilcdc_slave_fini(void) -{ -	platform_driver_unregister(&slave_driver); -} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c new file mode 100644 index 000000000000..106679bca6cb --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha <jsarha@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ + +/* + * To support the old "ti,tilcdc,slave" binding the binding has to be + * transformed to the new external encoder binding. + */ + +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_fdt.h> +#include <linux/slab.h> +#include <linux/list.h> + +#include "tilcdc_slave_compat.h" + +struct kfree_table { +	int total; +	int num; +	void **table; +}; + +static int __init kfree_table_init(struct kfree_table *kft) +{ +	kft->total = 32; +	kft->num = 0; +	kft->table = kmalloc(kft->total * sizeof(*kft->table), +			     GFP_KERNEL); +	if (!kft->table) +		return -ENOMEM; + +	return 0; +} + +static int __init kfree_table_add(struct kfree_table *kft, void *p) +{ +	if (kft->num == kft->total) { +		void **old = kft->table; + +		kft->total *= 2; +		kft->table = krealloc(old, kft->total * sizeof(*kft->table), +				      GFP_KERNEL); +		if (!kft->table) { +			kft->table = old; +			kfree(p); +			return -ENOMEM; +		} +	} +	kft->table[kft->num++] = p; +	return 0; +} + +static void __init kfree_table_free(struct kfree_table *kft) +{ +	int i; + +	for (i = 0; i < kft->num; i++) +		kfree(kft->table[i]); + +	kfree(kft->table); +} + +static +struct property * __init tilcdc_prop_dup(const struct property *prop, +					 struct kfree_table *kft) +{ +	struct property *nprop; + +	nprop = kzalloc(sizeof(*nprop), GFP_KERNEL); +	if (!nprop || kfree_table_add(kft, nprop)) +		return NULL; + +	nprop->name = kstrdup(prop->name, GFP_KERNEL); +	if (!nprop->name || kfree_table_add(kft, nprop->name)) +		return NULL; + +	nprop->value = kmemdup(prop->value, prop->length, GFP_KERNEL); +	if (!nprop->value || kfree_table_add(kft, nprop->value)) +		return NULL; + +	nprop->length = prop->length; + +	return nprop; +} + +static void __init tilcdc_copy_props(struct device_node *from, +				     struct device_node *to, +				     const char * const props[], +				     struct kfree_table *kft) +{ +	struct property *prop; +	int i; + +	for (i = 0; props[i]; i++) { +		prop = of_find_property(from, props[i], NULL); +		if (!prop) +			continue; + +		prop = tilcdc_prop_dup(prop, kft); +		if (!prop) +			continue; + +		prop->next = to->properties; +		to->properties = prop; +	} +} + +static int __init tilcdc_prop_str_update(struct property *prop, +					  const char *str, +					  struct kfree_table *kft) +{ +	prop->value = kstrdup(str, GFP_KERNEL); +	if (kfree_table_add(kft, prop->value) || !prop->value) +		return -ENOMEM; +	prop->length = strlen(str)+1; +	return 0; +} + +static void __init tilcdc_node_disable(struct device_node *node) +{ +	struct property *prop; + +	prop = kzalloc(sizeof(*prop), GFP_KERNEL); +	if (!prop) +		return; + +	prop->name = "status"; +	prop->value = "disabled"; +	prop->length = strlen((char *)prop->value)+1; + +	of_update_property(node, prop); +} + +struct device_node * __init tilcdc_get_overlay(struct kfree_table *kft) +{ +	const int size = __dtb_tilcdc_slave_compat_end - +		__dtb_tilcdc_slave_compat_begin; +	static void *overlay_data; +	struct device_node *overlay; +	int ret; + +	if (!size) { +		pr_warn("%s: No overlay data\n", __func__); +		return NULL; +	} + +	overlay_data = kmemdup(__dtb_tilcdc_slave_compat_begin, +			       size, GFP_KERNEL); +	if (!overlay_data || kfree_table_add(kft, overlay_data)) +		return NULL; + +	of_fdt_unflatten_tree(overlay_data, &overlay); +	if (!overlay) { +		pr_warn("%s: Unfattening overlay tree failed\n", __func__); +		return NULL; +	} + +	of_node_set_flag(overlay, OF_DETACHED); +	ret = of_resolve_phandles(overlay); +	if (ret) { +		pr_err("%s: Failed to resolve phandles: %d\n", __func__, ret); +		return NULL; +	} + +	return overlay; +} + +static const struct of_device_id tilcdc_slave_of_match[] __initconst = { +	{ .compatible = "ti,tilcdc,slave", }, +	{}, +}; + +static const struct of_device_id tilcdc_of_match[] __initconst = { +	{ .compatible = "ti,am33xx-tilcdc", }, +	{}, +}; + +static const struct of_device_id tilcdc_tda998x_of_match[] __initconst = { +	{ .compatible = "nxp,tda998x", }, +	{}, +}; + +static const char * const tilcdc_slave_props[] __initconst = { +	"pinctrl-names", +	"pinctrl-0", +	"pinctrl-1", +	NULL +}; + +void __init tilcdc_convert_slave_node(void) +{ +	struct device_node *slave = NULL, *lcdc = NULL; +	struct device_node *i2c = NULL, *fragment = NULL; +	struct device_node *overlay, *encoder; +	struct property *prop; +	/* For all memory needed for the overlay tree. This memory can +	   be freed after the overlay has been applied. */ +	struct kfree_table kft; +	int ret; + +	if (kfree_table_init(&kft)) +		goto out; + +	lcdc = of_find_matching_node(NULL, tilcdc_of_match); +	slave = of_find_matching_node(NULL, tilcdc_slave_of_match); + +	if (!slave || !of_device_is_available(lcdc)) +		goto out; + +	i2c = of_parse_phandle(slave, "i2c", 0); +	if (!i2c) { +		pr_err("%s: Can't find i2c node trough phandle\n", __func__); +		goto out; +	} + +	overlay = tilcdc_get_overlay(&kft); +	if (!overlay) +		goto out; + +	encoder = of_find_matching_node(overlay, tilcdc_tda998x_of_match); +	if (!encoder) { +		pr_err("%s: Failed to find tda998x node\n", __func__); +		goto out; +	} + +	tilcdc_copy_props(slave, encoder, tilcdc_slave_props, &kft); + +	for_each_child_of_node(overlay, fragment) { +		prop = of_find_property(fragment, "target-path", NULL); +		if (!prop) +			continue; +		if (!strncmp("i2c", (char *)prop->value, prop->length)) +			if (tilcdc_prop_str_update(prop, i2c->full_name, &kft)) +				goto out; +		if (!strncmp("lcdc", (char *)prop->value, prop->length)) +			if (tilcdc_prop_str_update(prop, lcdc->full_name, &kft)) +				goto out; +	} + +	tilcdc_node_disable(slave); + +	ret = of_overlay_create(overlay); +	if (ret) +		pr_err("%s: Creating overlay failed: %d\n", __func__, ret); +	else +		pr_info("%s: ti,tilcdc,slave node successfully converted\n", +			__func__); +out: +	kfree_table_free(&kft); +	of_node_put(i2c); +	of_node_put(slave); +	of_node_put(lcdc); +	of_node_put(fragment); +} + +int __init tilcdc_slave_compat_init(void) +{ +	tilcdc_convert_slave_node(); +	return 0; +} + +subsys_initcall(tilcdc_slave_compat_init); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts new file mode 100644 index 000000000000..693f8b0aea2d --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts @@ -0,0 +1,72 @@ +/* + * DTS overlay for converting ti,tilcdc,slave binding to new binding. + * + * Copyright (C) 2015 Texas Instruments Inc. + * Author: Jyri Sarha <jsarha@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +/* + * target-path property values are simple tags that are replaced with + * correct values in tildcdc_slave_compat.c. Some properties are also + * copied over from the ti,tilcdc,slave node. + */ + +/dts-v1/; +/ { +	fragment@0 { +		target-path = "i2c"; +		__overlay__ { +			#address-cells = <1>; +			#size-cells = <0>; +			tda19988 { +				compatible = "nxp,tda998x"; +				reg = <0x70>; +				status = "okay"; + +				port { +					hdmi_0: endpoint@0 { +						remote-endpoint = <&lcd_0>; +					}; +				}; +			}; +		}; +	}; + +	fragment@1 { +		target-path = "lcdc"; +		__overlay__ { +			port { +				lcd_0: endpoint@0 { +					remote-endpoint = <&hdmi_0>; +				}; +			}; +		}; +	}; + +	__local_fixups__ { +		fragment@0 { +			__overlay__ { +				tda19988 { +					port { +						endpoint@0 { +							remote-endpoint	= <0>; +						}; +					}; +				}; +			}; +		}; +		fragment@1 { +			__overlay__ { +				port { +					endpoint@0 { +						remote-endpoint	= <0>; +					}; +				}; +			}; +		}; +	}; +}; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h new file mode 100644 index 000000000000..403d35d87d0b --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha <jsarha@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program.  If not, see <http://www.gnu.org/licenses/>. + */ +/* This header declares the symbols defined in tilcdc_slave_compat.dts */ + +#ifndef __TILCDC_SLAVE_COMPAT_H__ +#define __TILCDC_SLAVE_COMPAT_H__ + +extern uint8_t __dtb_tilcdc_slave_compat_begin[]; +extern uint8_t __dtb_tilcdc_slave_compat_end[]; + +#endif /* __TILCDC_SLAVE_COMPAT_H__ */ | 
