| .. | .. |
|---|
| 5 | 5 | * Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org> |
|---|
| 6 | 6 | */ |
|---|
| 7 | 7 | |
|---|
| 8 | | -#include <drm/drmP.h> |
|---|
| 9 | | -#include <drm/drm_bridge.h> |
|---|
| 10 | | -#include <drm/drm_panel.h> |
|---|
| 11 | | - |
|---|
| 12 | 8 | #include <linux/gpio/consumer.h> |
|---|
| 9 | +#include <linux/module.h> |
|---|
| 10 | +#include <linux/of.h> |
|---|
| 13 | 11 | #include <linux/of_graph.h> |
|---|
| 12 | +#include <linux/platform_device.h> |
|---|
| 14 | 13 | #include <linux/regulator/consumer.h> |
|---|
| 15 | 14 | #include <linux/slab.h> |
|---|
| 15 | + |
|---|
| 16 | +#include <drm/drm_bridge.h> |
|---|
| 17 | +#include <drm/drm_panel.h> |
|---|
| 16 | 18 | |
|---|
| 17 | 19 | enum thc63_ports { |
|---|
| 18 | 20 | THC63_LVDS_IN0, |
|---|
| .. | .. |
|---|
| 31 | 33 | |
|---|
| 32 | 34 | struct drm_bridge bridge; |
|---|
| 33 | 35 | struct drm_bridge *next; |
|---|
| 36 | + |
|---|
| 37 | + struct drm_bridge_timings timings; |
|---|
| 34 | 38 | }; |
|---|
| 35 | 39 | |
|---|
| 36 | 40 | static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge) |
|---|
| .. | .. |
|---|
| 38 | 42 | return container_of(bridge, struct thc63_dev, bridge); |
|---|
| 39 | 43 | } |
|---|
| 40 | 44 | |
|---|
| 41 | | -static int thc63_attach(struct drm_bridge *bridge) |
|---|
| 45 | +static int thc63_attach(struct drm_bridge *bridge, |
|---|
| 46 | + enum drm_bridge_attach_flags flags) |
|---|
| 42 | 47 | { |
|---|
| 43 | 48 | struct thc63_dev *thc63 = to_thc63(bridge); |
|---|
| 44 | 49 | |
|---|
| 45 | | - return drm_bridge_attach(bridge->encoder, thc63->next, bridge); |
|---|
| 50 | + return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags); |
|---|
| 51 | +} |
|---|
| 52 | + |
|---|
| 53 | +static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, |
|---|
| 54 | + const struct drm_display_info *info, |
|---|
| 55 | + const struct drm_display_mode *mode) |
|---|
| 56 | +{ |
|---|
| 57 | + struct thc63_dev *thc63 = to_thc63(bridge); |
|---|
| 58 | + unsigned int min_freq; |
|---|
| 59 | + unsigned int max_freq; |
|---|
| 60 | + |
|---|
| 61 | + /* |
|---|
| 62 | + * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but |
|---|
| 63 | + * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out |
|---|
| 64 | + * isn't supported by the driver yet, simply derive the limits from the |
|---|
| 65 | + * input mode. |
|---|
| 66 | + */ |
|---|
| 67 | + if (thc63->timings.dual_link) { |
|---|
| 68 | + min_freq = 40000; |
|---|
| 69 | + max_freq = 150000; |
|---|
| 70 | + } else { |
|---|
| 71 | + min_freq = 8000; |
|---|
| 72 | + max_freq = 135000; |
|---|
| 73 | + } |
|---|
| 74 | + |
|---|
| 75 | + if (mode->clock < min_freq) |
|---|
| 76 | + return MODE_CLOCK_LOW; |
|---|
| 77 | + |
|---|
| 78 | + if (mode->clock > max_freq) |
|---|
| 79 | + return MODE_CLOCK_HIGH; |
|---|
| 80 | + |
|---|
| 81 | + return MODE_OK; |
|---|
| 46 | 82 | } |
|---|
| 47 | 83 | |
|---|
| 48 | 84 | static void thc63_enable(struct drm_bridge *bridge) |
|---|
| .. | .. |
|---|
| 77 | 113 | |
|---|
| 78 | 114 | static const struct drm_bridge_funcs thc63_bridge_func = { |
|---|
| 79 | 115 | .attach = thc63_attach, |
|---|
| 116 | + .mode_valid = thc63_mode_valid, |
|---|
| 80 | 117 | .enable = thc63_enable, |
|---|
| 81 | 118 | .disable = thc63_disable, |
|---|
| 82 | 119 | }; |
|---|
| 83 | 120 | |
|---|
| 84 | 121 | static int thc63_parse_dt(struct thc63_dev *thc63) |
|---|
| 85 | 122 | { |
|---|
| 86 | | - struct device_node *thc63_out; |
|---|
| 123 | + struct device_node *endpoint; |
|---|
| 87 | 124 | struct device_node *remote; |
|---|
| 88 | 125 | |
|---|
| 89 | | - thc63_out = of_graph_get_endpoint_by_regs(thc63->dev->of_node, |
|---|
| 90 | | - THC63_RGB_OUT0, -1); |
|---|
| 91 | | - if (!thc63_out) { |
|---|
| 126 | + endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, |
|---|
| 127 | + THC63_RGB_OUT0, -1); |
|---|
| 128 | + if (!endpoint) { |
|---|
| 92 | 129 | dev_err(thc63->dev, "Missing endpoint in port@%u\n", |
|---|
| 93 | 130 | THC63_RGB_OUT0); |
|---|
| 94 | 131 | return -ENODEV; |
|---|
| 95 | 132 | } |
|---|
| 96 | 133 | |
|---|
| 97 | | - remote = of_graph_get_remote_port_parent(thc63_out); |
|---|
| 98 | | - of_node_put(thc63_out); |
|---|
| 134 | + remote = of_graph_get_remote_port_parent(endpoint); |
|---|
| 135 | + of_node_put(endpoint); |
|---|
| 99 | 136 | if (!remote) { |
|---|
| 100 | 137 | dev_err(thc63->dev, "Endpoint in port@%u unconnected\n", |
|---|
| 101 | 138 | THC63_RGB_OUT0); |
|---|
| .. | .. |
|---|
| 113 | 150 | of_node_put(remote); |
|---|
| 114 | 151 | if (!thc63->next) |
|---|
| 115 | 152 | return -EPROBE_DEFER; |
|---|
| 153 | + |
|---|
| 154 | + endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node, |
|---|
| 155 | + THC63_LVDS_IN1, -1); |
|---|
| 156 | + if (endpoint) { |
|---|
| 157 | + remote = of_graph_get_remote_port_parent(endpoint); |
|---|
| 158 | + of_node_put(endpoint); |
|---|
| 159 | + |
|---|
| 160 | + if (remote) { |
|---|
| 161 | + if (of_device_is_available(remote)) |
|---|
| 162 | + thc63->timings.dual_link = true; |
|---|
| 163 | + of_node_put(remote); |
|---|
| 164 | + } |
|---|
| 165 | + } |
|---|
| 166 | + |
|---|
| 167 | + dev_dbg(thc63->dev, "operating in %s-link mode\n", |
|---|
| 168 | + thc63->timings.dual_link ? "dual" : "single"); |
|---|
| 116 | 169 | |
|---|
| 117 | 170 | return 0; |
|---|
| 118 | 171 | } |
|---|
| .. | .. |
|---|
| 170 | 223 | thc63->bridge.driver_private = thc63; |
|---|
| 171 | 224 | thc63->bridge.of_node = pdev->dev.of_node; |
|---|
| 172 | 225 | thc63->bridge.funcs = &thc63_bridge_func; |
|---|
| 226 | + thc63->bridge.timings = &thc63->timings; |
|---|
| 173 | 227 | |
|---|
| 174 | 228 | drm_bridge_add(&thc63->bridge); |
|---|
| 175 | 229 | |
|---|