2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-27 06:34:11 +08:00
linux-next/drivers/gpu/drm/bridge/ti-tfp410.c

459 lines
11 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2016 Texas Instruments
* Author: Jyri Sarha <jsarha@ti.com>
*/
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_print.h>
drm: Split out drm_probe_helper.h Having the probe helper stuff (which pretty much everyone needs) in the drm_crtc_helper.h file (which atomic drivers should never need) is confusing. Split them out. To make sure I actually achieved the goal here I went through all drivers. And indeed, all atomic drivers are now free of drm_crtc_helper.h includes. v2: Make it compile. There was so much compile fail on arm drivers that I figured I'll better not include any of the acks on v1. v3: Massive rebase because i915 has lost a lot of drmP.h includes, but not all: Through drm_crtc_helper.h > drm_modeset_helper.h -> drmP.h there was still one, which this patch largely removes. Which means rolling out lots more includes all over. This will also conflict with ongoing drmP.h cleanup by others I expect. v3: Rebase on top of atomic bochs. v4: Review from Laurent for bridge/rcar/omap/shmob/core bits: - (re)move some of the added includes, use the better include files in other places (all suggested from Laurent adopted unchanged). - sort alphabetically v5: Actually try to sort them, and while at it, sort all the ones I touch. v6: Rebase onto i915 changes. v7: Rebase once more. Acked-by: Harry Wentland <harry.wentland@amd.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Cc: Sam Ravnborg <sam@ravnborg.org> Cc: Jani Nikula <jani.nikula@linux.intel.com> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Rodrigo Vivi <rodrigo.vivi@intel.com> Acked-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> Acked-by: Jani Nikula <jani.nikula@intel.com> Acked-by: Neil Armstrong <narmstrong@baylibre.com> Acked-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com> Acked-by: CK Hu <ck.hu@mediatek.com> Acked-by: Alex Deucher <alexander.deucher@amd.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Liviu Dudau <liviu.dudau@arm.com> Signed-off-by: Daniel Vetter <daniel.vetter@intel.com> Cc: linux-arm-kernel@lists.infradead.org Cc: virtualization@lists.linux-foundation.org Cc: etnaviv@lists.freedesktop.org Cc: linux-samsung-soc@vger.kernel.org Cc: intel-gfx@lists.freedesktop.org Cc: linux-mediatek@lists.infradead.org Cc: linux-amlogic@lists.infradead.org Cc: linux-arm-msm@vger.kernel.org Cc: freedreno@lists.freedesktop.org Cc: nouveau@lists.freedesktop.org Cc: spice-devel@lists.freedesktop.org Cc: amd-gfx@lists.freedesktop.org Cc: linux-renesas-soc@vger.kernel.org Cc: linux-rockchip@lists.infradead.org Cc: linux-stm32@st-md-mailman.stormreply.com Cc: linux-tegra@vger.kernel.org Cc: xen-devel@lists.xen.org Link: https://patchwork.freedesktop.org/patch/msgid/20190117210334.13234-1-daniel.vetter@ffwll.ch
2019-01-18 05:03:34 +08:00
#include <drm/drm_probe_helper.h>
#define HOTPLUG_DEBOUNCE_MS 1100
struct tfp410 {
struct drm_bridge bridge;
struct drm_connector connector;
u32 bus_format;
struct delayed_work hpd_work;
struct gpio_desc *powerdown;
struct drm_bridge_timings timings;
struct drm_bridge *next_bridge;
struct device *dev;
};
static inline struct tfp410 *
drm_bridge_to_tfp410(struct drm_bridge *bridge)
{
return container_of(bridge, struct tfp410, bridge);
}
static inline struct tfp410 *
drm_connector_to_tfp410(struct drm_connector *connector)
{
return container_of(connector, struct tfp410, connector);
}
static int tfp410_get_modes(struct drm_connector *connector)
{
struct tfp410 *dvi = drm_connector_to_tfp410(connector);
struct edid *edid;
int ret;
edid = drm_bridge_get_edid(dvi->next_bridge, connector);
if (IS_ERR_OR_NULL(edid)) {
if (edid != ERR_PTR(-ENOTSUPP))
DRM_INFO("EDID read failed. Fallback to standard modes\n");
/*
* No EDID, fallback on the XGA standard modes and prefer a mode
* pretty much anything can handle.
*/
ret = drm_add_modes_noedid(connector, 1920, 1200);
drm_set_preferred_mode(connector, 1024, 768);
return ret;
}
drm_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
return ret;
}
static const struct drm_connector_helper_funcs tfp410_con_helper_funcs = {
.get_modes = tfp410_get_modes,
};
static enum drm_connector_status
tfp410_connector_detect(struct drm_connector *connector, bool force)
{
struct tfp410 *dvi = drm_connector_to_tfp410(connector);
return drm_bridge_detect(dvi->next_bridge);
}
static const struct drm_connector_funcs tfp410_con_funcs = {
.detect = tfp410_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static void tfp410_hpd_work_func(struct work_struct *work)
{
struct tfp410 *dvi;
dvi = container_of(work, struct tfp410, hpd_work.work);
if (dvi->bridge.dev)
drm_helper_hpd_irq_event(dvi->bridge.dev);
}
static void tfp410_hpd_callback(void *arg, enum drm_connector_status status)
{
struct tfp410 *dvi = arg;
mod_delayed_work(system_wq, &dvi->hpd_work,
msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
}
drm/bridge: Extend bridge API to disable connector creation Most bridge drivers create a DRM connector to model the connector at the output of the bridge. This model is historical and has worked pretty well so far, but causes several issues: - It prevents supporting more complex display pipelines where DRM connector operations are split over multiple components. For instance a pipeline with a bridge connected to the DDC signals to read EDID data, and another one connected to the HPD signal to detect connection and disconnection, will not be possible to support through this model. - It requires every bridge driver to implement similar connector handling code, resulting in code duplication. - It assumes that a bridge will either be wired to a connector or to another bridge, but doesn't support bridges that can be used in both positions very well (although there is some ad-hoc support for this in the analogix_dp bridge driver). In order to solve these issues, ownership of the connector should be moved to the display controller driver (where it can be implemented using helpers provided by the core). Extend the bridge API to allow disabling connector creation in bridge drivers as a first step towards the new model. The new flags argument to the bridge .attach() operation allows instructing the bridge driver to skip creating a connector. Unconditionally set the new flags argument to 0 for now to keep the existing behaviour, and modify all existing bridge drivers to return an error when connector creation is not requested as they don't support this feature yet. The change is based on the following semantic patch, with manual review and edits. @ rule1 @ identifier funcs; identifier fn; @@ struct drm_bridge_funcs funcs = { ..., .attach = fn }; @ depends on rule1 @ identifier rule1.fn; identifier bridge; statement S, S1; @@ int fn( struct drm_bridge *bridge + , enum drm_bridge_attach_flags flags ) { ... when != S + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + S1 ... } @ depends on rule1 @ identifier rule1.fn; identifier bridge, flags; expression E1, E2, E3; @@ int fn( struct drm_bridge *bridge, enum drm_bridge_attach_flags flags ) { <... drm_bridge_attach(E1, E2, E3 + , flags ) ...> } @@ expression E1, E2, E3; @@ drm_bridge_attach(E1, E2, E3 + , 0 ) Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200226112514.12455-10-laurent.pinchart@ideasonboard.com
2020-02-26 19:24:29 +08:00
static int tfp410_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
int ret;
ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret < 0)
return ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
return 0;
drm/bridge: Extend bridge API to disable connector creation Most bridge drivers create a DRM connector to model the connector at the output of the bridge. This model is historical and has worked pretty well so far, but causes several issues: - It prevents supporting more complex display pipelines where DRM connector operations are split over multiple components. For instance a pipeline with a bridge connected to the DDC signals to read EDID data, and another one connected to the HPD signal to detect connection and disconnection, will not be possible to support through this model. - It requires every bridge driver to implement similar connector handling code, resulting in code duplication. - It assumes that a bridge will either be wired to a connector or to another bridge, but doesn't support bridges that can be used in both positions very well (although there is some ad-hoc support for this in the analogix_dp bridge driver). In order to solve these issues, ownership of the connector should be moved to the display controller driver (where it can be implemented using helpers provided by the core). Extend the bridge API to allow disabling connector creation in bridge drivers as a first step towards the new model. The new flags argument to the bridge .attach() operation allows instructing the bridge driver to skip creating a connector. Unconditionally set the new flags argument to 0 for now to keep the existing behaviour, and modify all existing bridge drivers to return an error when connector creation is not requested as they don't support this feature yet. The change is based on the following semantic patch, with manual review and edits. @ rule1 @ identifier funcs; identifier fn; @@ struct drm_bridge_funcs funcs = { ..., .attach = fn }; @ depends on rule1 @ identifier rule1.fn; identifier bridge; statement S, S1; @@ int fn( struct drm_bridge *bridge + , enum drm_bridge_attach_flags flags ) { ... when != S + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + S1 ... } @ depends on rule1 @ identifier rule1.fn; identifier bridge, flags; expression E1, E2, E3; @@ int fn( struct drm_bridge *bridge, enum drm_bridge_attach_flags flags ) { <... drm_bridge_attach(E1, E2, E3 + , flags ) ...> } @@ expression E1, E2, E3; @@ drm_bridge_attach(E1, E2, E3 + , 0 ) Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200226112514.12455-10-laurent.pinchart@ideasonboard.com
2020-02-26 19:24:29 +08:00
if (!bridge->encoder) {
dev_err(dvi->dev, "Missing encoder\n");
return -ENODEV;
}
if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
dvi->connector.polled = DRM_CONNECTOR_POLL_HPD;
else
dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
if (dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func);
drm_bridge_hpd_enable(dvi->next_bridge, tfp410_hpd_callback,
dvi);
}
drm_connector_helper_add(&dvi->connector,
&tfp410_con_helper_funcs);
ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector,
&tfp410_con_funcs,
dvi->next_bridge->type,
dvi->next_bridge->ddc);
if (ret) {
dev_err(dvi->dev, "drm_connector_init_with_ddc() failed: %d\n",
ret);
return ret;
}
drm_display_info_set_bus_formats(&dvi->connector.display_info,
&dvi->bus_format, 1);
drm_connector_attach_encoder(&dvi->connector, bridge->encoder);
return 0;
}
static void tfp410_detach(struct drm_bridge *bridge)
{
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
if (dvi->connector.dev && dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
drm_bridge_hpd_disable(dvi->next_bridge);
cancel_delayed_work_sync(&dvi->hpd_work);
}
}
static void tfp410_enable(struct drm_bridge *bridge)
{
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
gpiod_set_value_cansleep(dvi->powerdown, 0);
}
static void tfp410_disable(struct drm_bridge *bridge)
{
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
gpiod_set_value_cansleep(dvi->powerdown, 1);
}
static enum drm_mode_status tfp410_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode)
{
if (mode->clock < 25000)
return MODE_CLOCK_LOW;
if (mode->clock > 165000)
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static const struct drm_bridge_funcs tfp410_bridge_funcs = {
.attach = tfp410_attach,
.detach = tfp410_detach,
.enable = tfp410_enable,
.disable = tfp410_disable,
.mode_valid = tfp410_mode_valid,
};
static const struct drm_bridge_timings tfp410_default_timings = {
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
| DRM_BUS_FLAG_DE_HIGH,
.setup_time_ps = 1200,
.hold_time_ps = 1300,
};
static int tfp410_parse_timings(struct tfp410 *dvi, bool i2c)
{
struct drm_bridge_timings *timings = &dvi->timings;
struct device_node *ep;
u32 pclk_sample = 0;
u32 bus_width = 24;
u32 deskew = 0;
/* Start with defaults. */
*timings = tfp410_default_timings;
if (i2c)
/*
* In I2C mode timings are configured through the I2C interface.
* As the driver doesn't support I2C configuration yet, we just
* go with the defaults (BSEL=1, DSEL=1, DKEN=0, EDGE=1).
*/
return 0;
/*
* In non-I2C mode, timings are configured through the BSEL, DSEL, DKEN
* and EDGE pins. They are specified in DT through endpoint properties
* and vendor-specific properties.
*/
ep = of_graph_get_endpoint_by_regs(dvi->dev->of_node, 0, 0);
if (!ep)
return -EINVAL;
/* Get the sampling edge from the endpoint. */
of_property_read_u32(ep, "pclk-sample", &pclk_sample);
of_property_read_u32(ep, "bus-width", &bus_width);
of_node_put(ep);
timings->input_bus_flags = DRM_BUS_FLAG_DE_HIGH;
switch (pclk_sample) {
case 0:
timings->input_bus_flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE
| DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE;
break;
case 1:
timings->input_bus_flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
| DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE;
break;
default:
return -EINVAL;
}
switch (bus_width) {
case 12:
dvi->bus_format = MEDIA_BUS_FMT_RGB888_2X12_LE;
break;
case 24:
dvi->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
break;
default:
return -EINVAL;
}
/* Get the setup and hold time from vendor-specific properties. */
of_property_read_u32(dvi->dev->of_node, "ti,deskew", &deskew);
if (deskew > 7)
return -EINVAL;
timings->setup_time_ps = 1200 - 350 * ((s32)deskew - 4);
timings->hold_time_ps = max(0, 1300 + 350 * ((s32)deskew - 4));
return 0;
}
static int tfp410_init(struct device *dev, bool i2c)
{
struct device_node *node;
struct tfp410 *dvi;
int ret;
if (!dev->of_node) {
dev_err(dev, "device-tree data is missing\n");
return -ENXIO;
}
dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL);
if (!dvi)
return -ENOMEM;
dvi->dev = dev;
dev_set_drvdata(dev, dvi);
dvi->bridge.funcs = &tfp410_bridge_funcs;
dvi->bridge.of_node = dev->of_node;
dvi->bridge.timings = &dvi->timings;
dvi->bridge.type = DRM_MODE_CONNECTOR_DVID;
ret = tfp410_parse_timings(dvi, i2c);
if (ret)
return ret;
/* Get the next bridge, connected to port@1. */
node = of_graph_get_remote_node(dev->of_node, 1, -1);
if (!node)
return -ENODEV;
dvi->next_bridge = of_drm_find_bridge(node);
of_node_put(node);
if (!dvi->next_bridge)
return -EPROBE_DEFER;
/* Get the powerdown GPIO. */
dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown",
GPIOD_OUT_HIGH);
if (IS_ERR(dvi->powerdown)) {
dev_err(dev, "failed to parse powerdown gpio\n");
return PTR_ERR(dvi->powerdown);
}
/* Register the DRM bridge. */
drm_bridge_add(&dvi->bridge);
return 0;
}
static int tfp410_fini(struct device *dev)
{
struct tfp410 *dvi = dev_get_drvdata(dev);
drm_bridge_remove(&dvi->bridge);
return 0;
}
static int tfp410_probe(struct platform_device *pdev)
{
return tfp410_init(&pdev->dev, false);
}
static int tfp410_remove(struct platform_device *pdev)
{
return tfp410_fini(&pdev->dev);
}
static const struct of_device_id tfp410_match[] = {
{ .compatible = "ti,tfp410" },
{},
};
MODULE_DEVICE_TABLE(of, tfp410_match);
static struct platform_driver tfp410_platform_driver = {
.probe = tfp410_probe,
.remove = tfp410_remove,
.driver = {
.name = "tfp410-bridge",
.of_match_table = tfp410_match,
},
};
#if IS_ENABLED(CONFIG_I2C)
/* There is currently no i2c functionality. */
static int tfp410_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int reg;
if (!client->dev.of_node ||
of_property_read_u32(client->dev.of_node, "reg", &reg)) {
dev_err(&client->dev,
"Can't get i2c reg property from device-tree\n");
return -ENXIO;
}
return tfp410_init(&client->dev, true);
}
static int tfp410_i2c_remove(struct i2c_client *client)
{
return tfp410_fini(&client->dev);
}
static const struct i2c_device_id tfp410_i2c_ids[] = {
{ "tfp410", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tfp410_i2c_ids);
static struct i2c_driver tfp410_i2c_driver = {
.driver = {
.name = "tfp410",
.of_match_table = of_match_ptr(tfp410_match),
},
.id_table = tfp410_i2c_ids,
.probe = tfp410_i2c_probe,
.remove = tfp410_i2c_remove,
};
#endif /* IS_ENABLED(CONFIG_I2C) */
static struct {
uint i2c:1;
uint platform:1;
} tfp410_registered_driver;
static int __init tfp410_module_init(void)
{
int ret;
#if IS_ENABLED(CONFIG_I2C)
ret = i2c_add_driver(&tfp410_i2c_driver);
if (ret)
pr_err("%s: registering i2c driver failed: %d",
__func__, ret);
else
tfp410_registered_driver.i2c = 1;
#endif
ret = platform_driver_register(&tfp410_platform_driver);
if (ret)
pr_err("%s: registering platform driver failed: %d",
__func__, ret);
else
tfp410_registered_driver.platform = 1;
if (tfp410_registered_driver.i2c ||
tfp410_registered_driver.platform)
return 0;
return ret;
}
module_init(tfp410_module_init);
static void __exit tfp410_module_exit(void)
{
#if IS_ENABLED(CONFIG_I2C)
if (tfp410_registered_driver.i2c)
i2c_del_driver(&tfp410_i2c_driver);
#endif
if (tfp410_registered_driver.platform)
platform_driver_unregister(&tfp410_platform_driver);
}
module_exit(tfp410_module_exit);
MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>");
MODULE_DESCRIPTION("TI TFP410 DVI bridge driver");
MODULE_LICENSE("GPL");