mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 09:19:51 +00:00
Merge branch 'linux-4.1.0-rc5-tilcdc-refactor' of https://github.com/jsarha/linux into drm-next
Please pull the contents of "Use DRM component API in tilcdc to connect to tda998x" patch series. * 'linux-4.1.0-rc5-tilcdc-refactor' of https://github.com/jsarha/linux: drm/tilcdc: Force building of DRM_TILCDC_SLAVE_COMPAT drm/tilcdc: Add DRM_TILCDC_SLAVE_COMPAT for ti,tilcdc,slave binding support drm/tilcdc: use pm_runtime_irq_safe() drm/tilcdc: Add support for external tda998x encoder drm/tilcdc: Remove tilcdc slave support for tda998x driver drm/tilcdc: Fix module unloading
This commit is contained in:
commit
6aa6272503
14 changed files with 698 additions and 468 deletions
|
@ -1,18 +0,0 @@
|
|||
Device-Tree bindings for tilcdc DRM encoder slave output driver
|
||||
|
||||
Required properties:
|
||||
- compatible: value should be "ti,tilcdc,slave".
|
||||
- i2c: the phandle for the i2c device the encoder slave is connected to
|
||||
|
||||
Recommended properties:
|
||||
- pinctrl-names, pinctrl-0: the pincontrol settings to configure
|
||||
muxing properly for pins that connect to TFP410 device
|
||||
|
||||
Example:
|
||||
|
||||
hdmi {
|
||||
compatible = "ti,tilcdc,slave";
|
||||
i2c = <&i2c0>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&nxp_hdmi_bonelt_pins>;
|
||||
};
|
|
@ -18,6 +18,12 @@ Optional properties:
|
|||
- max-pixelclock: The maximum pixel clock that can be supported
|
||||
by the lcd controller in KHz.
|
||||
|
||||
Optional nodes:
|
||||
|
||||
- port/ports: to describe a connection to an external encoder. The
|
||||
binding follows Documentation/devicetree/bindings/graph.txt and
|
||||
suppors a single port with a single endpoint.
|
||||
|
||||
Example:
|
||||
|
||||
fb: fb@4830e000 {
|
||||
|
@ -26,4 +32,25 @@ Example:
|
|||
interrupt-parent = <&intc>;
|
||||
interrupts = <36>;
|
||||
ti,hwmods = "lcdc";
|
||||
|
||||
port {
|
||||
lcdc_0: endpoint@0 {
|
||||
remote-endpoint = <&hdmi_0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
tda19988: tda19988 {
|
||||
compatible = "nxp,tda998x";
|
||||
reg = <0x70>;
|
||||
|
||||
pinctrl-names = "default", "off";
|
||||
pinctrl-0 = <&nxp_hdmi_bonelt_pins>;
|
||||
pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>;
|
||||
|
||||
port {
|
||||
hdmi_0: endpoint@0 {
|
||||
remote-endpoint = <&lcdc_0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -58,7 +58,7 @@ obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/
|
|||
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
|
||||
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
|
||||
obj-$(CONFIG_DRM_OMAP) += omapdrm/
|
||||
obj-$(CONFIG_DRM_TILCDC) += tilcdc/
|
||||
obj-y += tilcdc/
|
||||
obj-$(CONFIG_DRM_QXL) += qxl/
|
||||
obj-$(CONFIG_DRM_BOCHS) += bochs/
|
||||
obj-$(CONFIG_DRM_MSM) += msm/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags)
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
166
drivers/gpu/drm/tilcdc/tilcdc_external.c
Normal file
166
drivers/gpu/drm/tilcdc/tilcdc_external.c
Normal file
|
@ -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;
|
||||
}
|
|
@ -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__ */
|
|
@ -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);
|
||||
}
|
270
drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c
Normal file
270
drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c
Normal file
|
@ -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);
|
72
drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts
Normal file
72
drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts
Normal file
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
25
drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h
Normal file
25
drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h
Normal file
|
@ -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__ */
|
Loading…
Reference in a new issue