2019-06-04 16:11:33 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2016-10-31 23:21:31 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2016 Texas Instruments
|
|
|
|
* Author: Jyri Sarha <jsarha@ti.com>
|
|
|
|
*/
|
|
|
|
|
2017-03-07 05:40:43 +08:00
|
|
|
#include <linux/gpio/consumer.h>
|
2019-05-20 02:36:36 +08:00
|
|
|
#include <linux/i2c.h>
|
2016-10-31 23:21:31 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of_graph.h>
|
|
|
|
#include <linux/platform_device.h>
|
2020-02-26 19:24:38 +08:00
|
|
|
#include <linux/workqueue.h>
|
2016-10-31 23:21:31 +08:00
|
|
|
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
2019-08-26 23:26:29 +08:00
|
|
|
#include <drm/drm_bridge.h>
|
2016-10-31 23:21:31 +08:00
|
|
|
#include <drm/drm_crtc.h>
|
2019-05-20 02:36:36 +08:00
|
|
|
#include <drm/drm_print.h>
|
2019-01-18 05:03:34 +08:00
|
|
|
#include <drm/drm_probe_helper.h>
|
2016-10-31 23:21:31 +08:00
|
|
|
|
2017-03-07 05:40:43 +08:00
|
|
|
#define HOTPLUG_DEBOUNCE_MS 1100
|
|
|
|
|
2016-10-31 23:21:31 +08:00
|
|
|
struct tfp410 {
|
|
|
|
struct drm_bridge bridge;
|
|
|
|
struct drm_connector connector;
|
|
|
|
|
2019-04-01 20:41:43 +08:00
|
|
|
u32 bus_format;
|
2017-03-07 05:40:43 +08:00
|
|
|
struct delayed_work hpd_work;
|
2018-10-01 23:07:48 +08:00
|
|
|
struct gpio_desc *powerdown;
|
2016-10-31 23:21:31 +08:00
|
|
|
|
2018-09-27 16:29:48 +08:00
|
|
|
struct drm_bridge_timings timings;
|
2020-02-26 19:24:38 +08:00
|
|
|
struct drm_bridge *next_bridge;
|
2018-09-27 16:29:48 +08:00
|
|
|
|
2016-10-31 23:21:31 +08:00
|
|
|
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;
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
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");
|
2016-10-31 23:21:31 +08:00
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
/*
|
|
|
|
* 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;
|
2016-10-31 23:21:31 +08:00
|
|
|
}
|
|
|
|
|
2018-07-09 16:40:06 +08:00
|
|
|
drm_connector_update_edid_property(connector, edid);
|
2016-10-31 23:21:31 +08:00
|
|
|
|
2019-06-10 21:57:38 +08:00
|
|
|
ret = drm_add_edid_modes(connector, edid);
|
|
|
|
|
|
|
|
kfree(edid);
|
|
|
|
|
2016-10-31 23:21:31 +08:00
|
|
|
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);
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
return drm_bridge_detect(dvi->next_bridge);
|
2016-10-31 23:21:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
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)
|
2016-10-31 23:21:31 +08:00
|
|
|
{
|
|
|
|
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
|
|
|
|
int ret;
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge,
|
|
|
|
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2020-02-26 19:24:39 +08:00
|
|
|
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
|
|
|
|
2016-10-31 23:21:31 +08:00
|
|
|
if (!bridge->encoder) {
|
|
|
|
dev_err(dvi->dev, "Missing encoder\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
|
2017-03-07 05:40:43 +08:00
|
|
|
dvi->connector.polled = DRM_CONNECTOR_POLL_HPD;
|
2019-04-01 20:33:42 +08:00
|
|
|
else
|
|
|
|
dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
|
2017-03-07 05:40:43 +08:00
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-10-31 23:21:31 +08:00
|
|
|
drm_connector_helper_add(&dvi->connector,
|
|
|
|
&tfp410_con_helper_funcs);
|
2019-07-27 01:23:15 +08:00
|
|
|
ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector,
|
|
|
|
&tfp410_con_funcs,
|
2020-02-26 19:24:38 +08:00
|
|
|
dvi->next_bridge->type,
|
|
|
|
dvi->next_bridge->ddc);
|
2016-10-31 23:21:31 +08:00
|
|
|
if (ret) {
|
2020-01-15 20:56:53 +08:00
|
|
|
dev_err(dvi->dev, "drm_connector_init_with_ddc() failed: %d\n",
|
|
|
|
ret);
|
2016-10-31 23:21:31 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-04-01 20:41:43 +08:00
|
|
|
drm_display_info_set_bus_formats(&dvi->connector.display_info,
|
|
|
|
&dvi->bus_format, 1);
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
drm_connector_attach_encoder(&dvi->connector, bridge->encoder);
|
2016-10-31 23:21:31 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-01 23:07:48 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-01-21 17:46:55 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-10-31 23:21:31 +08:00
|
|
|
static const struct drm_bridge_funcs tfp410_bridge_funcs = {
|
|
|
|
.attach = tfp410_attach,
|
2020-02-26 19:24:38 +08:00
|
|
|
.detach = tfp410_detach,
|
2018-10-01 23:07:48 +08:00
|
|
|
.enable = tfp410_enable,
|
|
|
|
.disable = tfp410_disable,
|
2020-01-21 17:46:55 +08:00
|
|
|
.mode_valid = tfp410_mode_valid,
|
2016-10-31 23:21:31 +08:00
|
|
|
};
|
|
|
|
|
2018-09-27 16:29:48 +08:00
|
|
|
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;
|
2019-04-01 20:41:43 +08:00
|
|
|
u32 bus_width = 24;
|
2020-06-17 17:46:32 +08:00
|
|
|
u32 deskew = 0;
|
2018-09-27 16:29:48 +08:00
|
|
|
|
|
|
|
/* 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);
|
2019-04-01 20:41:43 +08:00
|
|
|
of_property_read_u32(ep, "bus-width", &bus_width);
|
2018-09-27 16:29:48 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-04-01 20:41:43 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-09-27 16:29:48 +08:00
|
|
|
/* Get the setup and hold time from vendor-specific properties. */
|
2020-06-17 17:46:32 +08:00
|
|
|
of_property_read_u32(dvi->dev->of_node, "ti,deskew", &deskew);
|
|
|
|
if (deskew > 7)
|
2018-09-27 16:29:48 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2020-06-17 17:46:33 +08:00
|
|
|
timings->setup_time_ps = 1200 - 350 * ((s32)deskew - 4);
|
|
|
|
timings->hold_time_ps = max(0, 1300 + 350 * ((s32)deskew - 4));
|
2018-09-27 16:29:48 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tfp410_init(struct device *dev, bool i2c)
|
2016-10-31 23:21:31 +08:00
|
|
|
{
|
2020-02-26 19:24:38 +08:00
|
|
|
struct device_node *node;
|
2016-10-31 23:21:31 +08:00
|
|
|
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;
|
2020-02-26 19:24:38 +08:00
|
|
|
|
|
|
|
dvi->dev = dev;
|
2016-10-31 23:21:31 +08:00
|
|
|
dev_set_drvdata(dev, dvi);
|
|
|
|
|
|
|
|
dvi->bridge.funcs = &tfp410_bridge_funcs;
|
|
|
|
dvi->bridge.of_node = dev->of_node;
|
2018-09-27 16:29:48 +08:00
|
|
|
dvi->bridge.timings = &dvi->timings;
|
2020-02-26 19:24:38 +08:00
|
|
|
dvi->bridge.type = DRM_MODE_CONNECTOR_DVID;
|
2016-10-31 23:21:31 +08:00
|
|
|
|
2018-09-27 16:29:48 +08:00
|
|
|
ret = tfp410_parse_timings(dvi, i2c);
|
|
|
|
if (ret)
|
2020-02-26 19:24:38 +08:00
|
|
|
return ret;
|
2018-09-27 16:29:48 +08:00
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
/* 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);
|
2016-10-31 23:21:31 +08:00
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
if (!dvi->next_bridge)
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
|
|
|
|
/* Get the powerdown GPIO. */
|
2018-10-01 23:07:48 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-02-26 19:24:38 +08:00
|
|
|
/* Register the DRM bridge. */
|
2017-07-03 16:42:27 +08:00
|
|
|
drm_bridge_add(&dvi->bridge);
|
2016-10-31 23:21:31 +08:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2018-09-27 16:29:48 +08:00
|
|
|
return tfp410_init(&pdev->dev, false);
|
2016-10-31 23:21:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2017-02-09 23:25:49 +08:00
|
|
|
static struct platform_driver tfp410_platform_driver = {
|
2016-10-31 23:21:31 +08:00
|
|
|
.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", ®)) {
|
|
|
|
dev_err(&client->dev,
|
|
|
|
"Can't get i2c reg property from device-tree\n");
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
2018-09-27 16:29:48 +08:00
|
|
|
return tfp410_init(&client->dev, true);
|
2016-10-31 23:21:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|