- 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 {
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>;
+ };
+ };
};
tilcdc_crtc.o \
tilcdc_tfp410.o \
tilcdc_panel.o \
+ tilcdc_external.o \
tilcdc_drv.o
obj-$(CONFIG_DRM_TILCDC) += tilcdc.o
/* 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)
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;
}
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);
/* 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_panel.h"
+#include "tilcdc_external.h"
#include "drm_fb_helper.h"
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);
{
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);
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;
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);
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;
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
* 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;
}
- 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;
}
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
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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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/>.
+ */
+
+#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__ */