/* * 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; }