18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2016 Texas Instruments 48c2ecf20Sopenharmony_ci * Author: Jyri Sarha <jsarha@ti.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 88c2ecf20Sopenharmony_ci#include <linux/i2c.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/of_graph.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <drm/drm_atomic_helper.h> 158c2ecf20Sopenharmony_ci#include <drm/drm_bridge.h> 168c2ecf20Sopenharmony_ci#include <drm/drm_crtc.h> 178c2ecf20Sopenharmony_ci#include <drm/drm_print.h> 188c2ecf20Sopenharmony_ci#include <drm/drm_probe_helper.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define HOTPLUG_DEBOUNCE_MS 1100 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistruct tfp410 { 238c2ecf20Sopenharmony_ci struct drm_bridge bridge; 248c2ecf20Sopenharmony_ci struct drm_connector connector; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci u32 bus_format; 278c2ecf20Sopenharmony_ci struct delayed_work hpd_work; 288c2ecf20Sopenharmony_ci struct gpio_desc *powerdown; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci struct drm_bridge_timings timings; 318c2ecf20Sopenharmony_ci struct drm_bridge *next_bridge; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci struct device *dev; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic inline struct tfp410 * 378c2ecf20Sopenharmony_cidrm_bridge_to_tfp410(struct drm_bridge *bridge) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci return container_of(bridge, struct tfp410, bridge); 408c2ecf20Sopenharmony_ci} 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic inline struct tfp410 * 438c2ecf20Sopenharmony_cidrm_connector_to_tfp410(struct drm_connector *connector) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci return container_of(connector, struct tfp410, connector); 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic int tfp410_get_modes(struct drm_connector *connector) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci struct tfp410 *dvi = drm_connector_to_tfp410(connector); 518c2ecf20Sopenharmony_ci struct edid *edid; 528c2ecf20Sopenharmony_ci int ret; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci if (dvi->next_bridge->ops & DRM_BRIDGE_OP_EDID) { 558c2ecf20Sopenharmony_ci edid = drm_bridge_get_edid(dvi->next_bridge, connector); 568c2ecf20Sopenharmony_ci if (!edid) 578c2ecf20Sopenharmony_ci DRM_INFO("EDID read failed. Fallback to standard modes\n"); 588c2ecf20Sopenharmony_ci } else { 598c2ecf20Sopenharmony_ci edid = NULL; 608c2ecf20Sopenharmony_ci } 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if (!edid) { 638c2ecf20Sopenharmony_ci /* 648c2ecf20Sopenharmony_ci * No EDID, fallback on the XGA standard modes and prefer a mode 658c2ecf20Sopenharmony_ci * pretty much anything can handle. 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_ci ret = drm_add_modes_noedid(connector, 1920, 1200); 688c2ecf20Sopenharmony_ci drm_set_preferred_mode(connector, 1024, 768); 698c2ecf20Sopenharmony_ci return ret; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci drm_connector_update_edid_property(connector, edid); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = drm_add_edid_modes(connector, edid); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci kfree(edid); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci return ret; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic const struct drm_connector_helper_funcs tfp410_con_helper_funcs = { 828c2ecf20Sopenharmony_ci .get_modes = tfp410_get_modes, 838c2ecf20Sopenharmony_ci}; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic enum drm_connector_status 868c2ecf20Sopenharmony_citfp410_connector_detect(struct drm_connector *connector, bool force) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct tfp410 *dvi = drm_connector_to_tfp410(connector); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci return drm_bridge_detect(dvi->next_bridge); 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic const struct drm_connector_funcs tfp410_con_funcs = { 948c2ecf20Sopenharmony_ci .detect = tfp410_connector_detect, 958c2ecf20Sopenharmony_ci .fill_modes = drm_helper_probe_single_connector_modes, 968c2ecf20Sopenharmony_ci .destroy = drm_connector_cleanup, 978c2ecf20Sopenharmony_ci .reset = drm_atomic_helper_connector_reset, 988c2ecf20Sopenharmony_ci .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 998c2ecf20Sopenharmony_ci .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic void tfp410_hpd_work_func(struct work_struct *work) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci struct tfp410 *dvi; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci dvi = container_of(work, struct tfp410, hpd_work.work); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (dvi->bridge.dev) 1098c2ecf20Sopenharmony_ci drm_helper_hpd_irq_event(dvi->bridge.dev); 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic void tfp410_hpd_callback(void *arg, enum drm_connector_status status) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci struct tfp410 *dvi = arg; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci mod_delayed_work(system_wq, &dvi->hpd_work, 1178c2ecf20Sopenharmony_ci msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic int tfp410_attach(struct drm_bridge *bridge, 1218c2ecf20Sopenharmony_ci enum drm_bridge_attach_flags flags) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); 1248c2ecf20Sopenharmony_ci int ret; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge, 1278c2ecf20Sopenharmony_ci DRM_BRIDGE_ATTACH_NO_CONNECTOR); 1288c2ecf20Sopenharmony_ci if (ret < 0) 1298c2ecf20Sopenharmony_ci return ret; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) 1328c2ecf20Sopenharmony_ci return 0; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci if (!bridge->encoder) { 1358c2ecf20Sopenharmony_ci dev_err(dvi->dev, "Missing encoder\n"); 1368c2ecf20Sopenharmony_ci return -ENODEV; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT) 1408c2ecf20Sopenharmony_ci dvi->connector.polled = DRM_CONNECTOR_POLL_HPD; 1418c2ecf20Sopenharmony_ci else 1428c2ecf20Sopenharmony_ci dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci if (dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) { 1458c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func); 1468c2ecf20Sopenharmony_ci drm_bridge_hpd_enable(dvi->next_bridge, tfp410_hpd_callback, 1478c2ecf20Sopenharmony_ci dvi); 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci drm_connector_helper_add(&dvi->connector, 1518c2ecf20Sopenharmony_ci &tfp410_con_helper_funcs); 1528c2ecf20Sopenharmony_ci ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector, 1538c2ecf20Sopenharmony_ci &tfp410_con_funcs, 1548c2ecf20Sopenharmony_ci dvi->next_bridge->type, 1558c2ecf20Sopenharmony_ci dvi->next_bridge->ddc); 1568c2ecf20Sopenharmony_ci if (ret) { 1578c2ecf20Sopenharmony_ci dev_err(dvi->dev, "drm_connector_init_with_ddc() failed: %d\n", 1588c2ecf20Sopenharmony_ci ret); 1598c2ecf20Sopenharmony_ci return ret; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci drm_display_info_set_bus_formats(&dvi->connector.display_info, 1638c2ecf20Sopenharmony_ci &dvi->bus_format, 1); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci drm_connector_attach_encoder(&dvi->connector, bridge->encoder); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci return 0; 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic void tfp410_detach(struct drm_bridge *bridge) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (dvi->connector.dev && dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) { 1758c2ecf20Sopenharmony_ci drm_bridge_hpd_disable(dvi->next_bridge); 1768c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&dvi->hpd_work); 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic void tfp410_enable(struct drm_bridge *bridge) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(dvi->powerdown, 0); 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic void tfp410_disable(struct drm_bridge *bridge) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(dvi->powerdown, 1); 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic enum drm_mode_status tfp410_mode_valid(struct drm_bridge *bridge, 1958c2ecf20Sopenharmony_ci const struct drm_display_info *info, 1968c2ecf20Sopenharmony_ci const struct drm_display_mode *mode) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci if (mode->clock < 25000) 1998c2ecf20Sopenharmony_ci return MODE_CLOCK_LOW; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci if (mode->clock > 165000) 2028c2ecf20Sopenharmony_ci return MODE_CLOCK_HIGH; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci return MODE_OK; 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic const struct drm_bridge_funcs tfp410_bridge_funcs = { 2088c2ecf20Sopenharmony_ci .attach = tfp410_attach, 2098c2ecf20Sopenharmony_ci .detach = tfp410_detach, 2108c2ecf20Sopenharmony_ci .enable = tfp410_enable, 2118c2ecf20Sopenharmony_ci .disable = tfp410_disable, 2128c2ecf20Sopenharmony_ci .mode_valid = tfp410_mode_valid, 2138c2ecf20Sopenharmony_ci}; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic const struct drm_bridge_timings tfp410_default_timings = { 2168c2ecf20Sopenharmony_ci .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE 2178c2ecf20Sopenharmony_ci | DRM_BUS_FLAG_DE_HIGH, 2188c2ecf20Sopenharmony_ci .setup_time_ps = 1200, 2198c2ecf20Sopenharmony_ci .hold_time_ps = 1300, 2208c2ecf20Sopenharmony_ci}; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic int tfp410_parse_timings(struct tfp410 *dvi, bool i2c) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci struct drm_bridge_timings *timings = &dvi->timings; 2258c2ecf20Sopenharmony_ci struct device_node *ep; 2268c2ecf20Sopenharmony_ci u32 pclk_sample = 0; 2278c2ecf20Sopenharmony_ci u32 bus_width = 24; 2288c2ecf20Sopenharmony_ci u32 deskew = 0; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* Start with defaults. */ 2318c2ecf20Sopenharmony_ci *timings = tfp410_default_timings; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci if (i2c) 2348c2ecf20Sopenharmony_ci /* 2358c2ecf20Sopenharmony_ci * In I2C mode timings are configured through the I2C interface. 2368c2ecf20Sopenharmony_ci * As the driver doesn't support I2C configuration yet, we just 2378c2ecf20Sopenharmony_ci * go with the defaults (BSEL=1, DSEL=1, DKEN=0, EDGE=1). 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_ci return 0; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci /* 2428c2ecf20Sopenharmony_ci * In non-I2C mode, timings are configured through the BSEL, DSEL, DKEN 2438c2ecf20Sopenharmony_ci * and EDGE pins. They are specified in DT through endpoint properties 2448c2ecf20Sopenharmony_ci * and vendor-specific properties. 2458c2ecf20Sopenharmony_ci */ 2468c2ecf20Sopenharmony_ci ep = of_graph_get_endpoint_by_regs(dvi->dev->of_node, 0, 0); 2478c2ecf20Sopenharmony_ci if (!ep) 2488c2ecf20Sopenharmony_ci return -EINVAL; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci /* Get the sampling edge from the endpoint. */ 2518c2ecf20Sopenharmony_ci of_property_read_u32(ep, "pclk-sample", &pclk_sample); 2528c2ecf20Sopenharmony_ci of_property_read_u32(ep, "bus-width", &bus_width); 2538c2ecf20Sopenharmony_ci of_node_put(ep); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci timings->input_bus_flags = DRM_BUS_FLAG_DE_HIGH; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci switch (pclk_sample) { 2588c2ecf20Sopenharmony_ci case 0: 2598c2ecf20Sopenharmony_ci timings->input_bus_flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE 2608c2ecf20Sopenharmony_ci | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE; 2618c2ecf20Sopenharmony_ci break; 2628c2ecf20Sopenharmony_ci case 1: 2638c2ecf20Sopenharmony_ci timings->input_bus_flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE 2648c2ecf20Sopenharmony_ci | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE; 2658c2ecf20Sopenharmony_ci break; 2668c2ecf20Sopenharmony_ci default: 2678c2ecf20Sopenharmony_ci return -EINVAL; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci switch (bus_width) { 2718c2ecf20Sopenharmony_ci case 12: 2728c2ecf20Sopenharmony_ci dvi->bus_format = MEDIA_BUS_FMT_RGB888_2X12_LE; 2738c2ecf20Sopenharmony_ci break; 2748c2ecf20Sopenharmony_ci case 24: 2758c2ecf20Sopenharmony_ci dvi->bus_format = MEDIA_BUS_FMT_RGB888_1X24; 2768c2ecf20Sopenharmony_ci break; 2778c2ecf20Sopenharmony_ci default: 2788c2ecf20Sopenharmony_ci return -EINVAL; 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci /* Get the setup and hold time from vendor-specific properties. */ 2828c2ecf20Sopenharmony_ci of_property_read_u32(dvi->dev->of_node, "ti,deskew", &deskew); 2838c2ecf20Sopenharmony_ci if (deskew > 7) 2848c2ecf20Sopenharmony_ci return -EINVAL; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci timings->setup_time_ps = 1200 - 350 * ((s32)deskew - 4); 2878c2ecf20Sopenharmony_ci timings->hold_time_ps = max(0, 1300 + 350 * ((s32)deskew - 4)); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci return 0; 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_cistatic int tfp410_init(struct device *dev, bool i2c) 2938c2ecf20Sopenharmony_ci{ 2948c2ecf20Sopenharmony_ci struct device_node *node; 2958c2ecf20Sopenharmony_ci struct tfp410 *dvi; 2968c2ecf20Sopenharmony_ci int ret; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci if (!dev->of_node) { 2998c2ecf20Sopenharmony_ci dev_err(dev, "device-tree data is missing\n"); 3008c2ecf20Sopenharmony_ci return -ENXIO; 3018c2ecf20Sopenharmony_ci } 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL); 3048c2ecf20Sopenharmony_ci if (!dvi) 3058c2ecf20Sopenharmony_ci return -ENOMEM; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci dvi->dev = dev; 3088c2ecf20Sopenharmony_ci dev_set_drvdata(dev, dvi); 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci dvi->bridge.funcs = &tfp410_bridge_funcs; 3118c2ecf20Sopenharmony_ci dvi->bridge.of_node = dev->of_node; 3128c2ecf20Sopenharmony_ci dvi->bridge.timings = &dvi->timings; 3138c2ecf20Sopenharmony_ci dvi->bridge.type = DRM_MODE_CONNECTOR_DVID; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci ret = tfp410_parse_timings(dvi, i2c); 3168c2ecf20Sopenharmony_ci if (ret) 3178c2ecf20Sopenharmony_ci return ret; 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci /* Get the next bridge, connected to port@1. */ 3208c2ecf20Sopenharmony_ci node = of_graph_get_remote_node(dev->of_node, 1, -1); 3218c2ecf20Sopenharmony_ci if (!node) 3228c2ecf20Sopenharmony_ci return -ENODEV; 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci dvi->next_bridge = of_drm_find_bridge(node); 3258c2ecf20Sopenharmony_ci of_node_put(node); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci if (!dvi->next_bridge) 3288c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci /* Get the powerdown GPIO. */ 3318c2ecf20Sopenharmony_ci dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown", 3328c2ecf20Sopenharmony_ci GPIOD_OUT_HIGH); 3338c2ecf20Sopenharmony_ci if (IS_ERR(dvi->powerdown)) { 3348c2ecf20Sopenharmony_ci dev_err(dev, "failed to parse powerdown gpio\n"); 3358c2ecf20Sopenharmony_ci return PTR_ERR(dvi->powerdown); 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci /* Register the DRM bridge. */ 3398c2ecf20Sopenharmony_ci drm_bridge_add(&dvi->bridge); 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci return 0; 3428c2ecf20Sopenharmony_ci} 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_cistatic int tfp410_fini(struct device *dev) 3458c2ecf20Sopenharmony_ci{ 3468c2ecf20Sopenharmony_ci struct tfp410 *dvi = dev_get_drvdata(dev); 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci drm_bridge_remove(&dvi->bridge); 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci return 0; 3518c2ecf20Sopenharmony_ci} 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_cistatic int tfp410_probe(struct platform_device *pdev) 3548c2ecf20Sopenharmony_ci{ 3558c2ecf20Sopenharmony_ci return tfp410_init(&pdev->dev, false); 3568c2ecf20Sopenharmony_ci} 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_cistatic int tfp410_remove(struct platform_device *pdev) 3598c2ecf20Sopenharmony_ci{ 3608c2ecf20Sopenharmony_ci return tfp410_fini(&pdev->dev); 3618c2ecf20Sopenharmony_ci} 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_cistatic const struct of_device_id tfp410_match[] = { 3648c2ecf20Sopenharmony_ci { .compatible = "ti,tfp410" }, 3658c2ecf20Sopenharmony_ci {}, 3668c2ecf20Sopenharmony_ci}; 3678c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, tfp410_match); 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_cistatic struct platform_driver tfp410_platform_driver = { 3708c2ecf20Sopenharmony_ci .probe = tfp410_probe, 3718c2ecf20Sopenharmony_ci .remove = tfp410_remove, 3728c2ecf20Sopenharmony_ci .driver = { 3738c2ecf20Sopenharmony_ci .name = "tfp410-bridge", 3748c2ecf20Sopenharmony_ci .of_match_table = tfp410_match, 3758c2ecf20Sopenharmony_ci }, 3768c2ecf20Sopenharmony_ci}; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_I2C) 3798c2ecf20Sopenharmony_ci/* There is currently no i2c functionality. */ 3808c2ecf20Sopenharmony_cistatic int tfp410_i2c_probe(struct i2c_client *client, 3818c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 3828c2ecf20Sopenharmony_ci{ 3838c2ecf20Sopenharmony_ci int reg; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci if (!client->dev.of_node || 3868c2ecf20Sopenharmony_ci of_property_read_u32(client->dev.of_node, "reg", ®)) { 3878c2ecf20Sopenharmony_ci dev_err(&client->dev, 3888c2ecf20Sopenharmony_ci "Can't get i2c reg property from device-tree\n"); 3898c2ecf20Sopenharmony_ci return -ENXIO; 3908c2ecf20Sopenharmony_ci } 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci return tfp410_init(&client->dev, true); 3938c2ecf20Sopenharmony_ci} 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_cistatic int tfp410_i2c_remove(struct i2c_client *client) 3968c2ecf20Sopenharmony_ci{ 3978c2ecf20Sopenharmony_ci return tfp410_fini(&client->dev); 3988c2ecf20Sopenharmony_ci} 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_cistatic const struct i2c_device_id tfp410_i2c_ids[] = { 4018c2ecf20Sopenharmony_ci { "tfp410", 0 }, 4028c2ecf20Sopenharmony_ci { } 4038c2ecf20Sopenharmony_ci}; 4048c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, tfp410_i2c_ids); 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_cistatic struct i2c_driver tfp410_i2c_driver = { 4078c2ecf20Sopenharmony_ci .driver = { 4088c2ecf20Sopenharmony_ci .name = "tfp410", 4098c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(tfp410_match), 4108c2ecf20Sopenharmony_ci }, 4118c2ecf20Sopenharmony_ci .id_table = tfp410_i2c_ids, 4128c2ecf20Sopenharmony_ci .probe = tfp410_i2c_probe, 4138c2ecf20Sopenharmony_ci .remove = tfp410_i2c_remove, 4148c2ecf20Sopenharmony_ci}; 4158c2ecf20Sopenharmony_ci#endif /* IS_ENABLED(CONFIG_I2C) */ 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_cistatic struct { 4188c2ecf20Sopenharmony_ci uint i2c:1; 4198c2ecf20Sopenharmony_ci uint platform:1; 4208c2ecf20Sopenharmony_ci} tfp410_registered_driver; 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_cistatic int __init tfp410_module_init(void) 4238c2ecf20Sopenharmony_ci{ 4248c2ecf20Sopenharmony_ci int ret; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_I2C) 4278c2ecf20Sopenharmony_ci ret = i2c_add_driver(&tfp410_i2c_driver); 4288c2ecf20Sopenharmony_ci if (ret) 4298c2ecf20Sopenharmony_ci pr_err("%s: registering i2c driver failed: %d", 4308c2ecf20Sopenharmony_ci __func__, ret); 4318c2ecf20Sopenharmony_ci else 4328c2ecf20Sopenharmony_ci tfp410_registered_driver.i2c = 1; 4338c2ecf20Sopenharmony_ci#endif 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci ret = platform_driver_register(&tfp410_platform_driver); 4368c2ecf20Sopenharmony_ci if (ret) 4378c2ecf20Sopenharmony_ci pr_err("%s: registering platform driver failed: %d", 4388c2ecf20Sopenharmony_ci __func__, ret); 4398c2ecf20Sopenharmony_ci else 4408c2ecf20Sopenharmony_ci tfp410_registered_driver.platform = 1; 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci if (tfp410_registered_driver.i2c || 4438c2ecf20Sopenharmony_ci tfp410_registered_driver.platform) 4448c2ecf20Sopenharmony_ci return 0; 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci return ret; 4478c2ecf20Sopenharmony_ci} 4488c2ecf20Sopenharmony_cimodule_init(tfp410_module_init); 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_cistatic void __exit tfp410_module_exit(void) 4518c2ecf20Sopenharmony_ci{ 4528c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_I2C) 4538c2ecf20Sopenharmony_ci if (tfp410_registered_driver.i2c) 4548c2ecf20Sopenharmony_ci i2c_del_driver(&tfp410_i2c_driver); 4558c2ecf20Sopenharmony_ci#endif 4568c2ecf20Sopenharmony_ci if (tfp410_registered_driver.platform) 4578c2ecf20Sopenharmony_ci platform_driver_unregister(&tfp410_platform_driver); 4588c2ecf20Sopenharmony_ci} 4598c2ecf20Sopenharmony_cimodule_exit(tfp410_module_exit); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>"); 4628c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TI TFP410 DVI bridge driver"); 4638c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 464