1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd |
4 | * Author: |
5 | * Sandy Huang <hjc@rock-chips.com> |
6 | */ |
7 | |
8 | #include <linux/component.h> |
9 | #include <linux/media-bus-format.h> |
10 | #include <linux/of_graph.h> |
11 | |
12 | #include <drm/display/drm_dp_helper.h> |
13 | #include <drm/drm_atomic_helper.h> |
14 | #include <drm/drm_bridge.h> |
15 | #include <drm/drm_bridge_connector.h> |
16 | #include <drm/drm_of.h> |
17 | #include <drm/drm_panel.h> |
18 | #include <drm/drm_probe_helper.h> |
19 | #include <drm/drm_simple_kms_helper.h> |
20 | |
21 | #include "rockchip_drm_drv.h" |
22 | #include "rockchip_rgb.h" |
23 | |
24 | struct rockchip_rgb { |
25 | struct device *dev; |
26 | struct drm_device *drm_dev; |
27 | struct drm_bridge *bridge; |
28 | struct rockchip_encoder encoder; |
29 | struct drm_connector connector; |
30 | int output_mode; |
31 | }; |
32 | |
33 | static int |
34 | rockchip_rgb_encoder_atomic_check(struct drm_encoder *encoder, |
35 | struct drm_crtc_state *crtc_state, |
36 | struct drm_connector_state *conn_state) |
37 | { |
38 | struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); |
39 | struct drm_connector *connector = conn_state->connector; |
40 | struct drm_display_info *info = &connector->display_info; |
41 | u32 bus_format; |
42 | |
43 | if (info->num_bus_formats) |
44 | bus_format = info->bus_formats[0]; |
45 | else |
46 | bus_format = MEDIA_BUS_FMT_RGB888_1X24; |
47 | |
48 | switch (bus_format) { |
49 | case MEDIA_BUS_FMT_RGB666_1X18: |
50 | s->output_mode = ROCKCHIP_OUT_MODE_P666; |
51 | break; |
52 | case MEDIA_BUS_FMT_RGB565_1X16: |
53 | s->output_mode = ROCKCHIP_OUT_MODE_P565; |
54 | break; |
55 | case MEDIA_BUS_FMT_RGB888_1X24: |
56 | case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: |
57 | default: |
58 | s->output_mode = ROCKCHIP_OUT_MODE_P888; |
59 | break; |
60 | } |
61 | |
62 | s->output_type = DRM_MODE_CONNECTOR_LVDS; |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static const |
68 | struct drm_encoder_helper_funcs rockchip_rgb_encoder_helper_funcs = { |
69 | .atomic_check = rockchip_rgb_encoder_atomic_check, |
70 | }; |
71 | |
72 | struct rockchip_rgb *rockchip_rgb_init(struct device *dev, |
73 | struct drm_crtc *crtc, |
74 | struct drm_device *drm_dev, |
75 | int video_port) |
76 | { |
77 | struct rockchip_rgb *rgb; |
78 | struct drm_encoder *encoder; |
79 | struct device_node *port, *endpoint; |
80 | u32 endpoint_id; |
81 | int ret = 0, child_count = 0; |
82 | struct drm_panel *panel; |
83 | struct drm_bridge *bridge; |
84 | struct drm_connector *connector; |
85 | |
86 | rgb = devm_kzalloc(dev, size: sizeof(*rgb), GFP_KERNEL); |
87 | if (!rgb) |
88 | return ERR_PTR(error: -ENOMEM); |
89 | |
90 | rgb->dev = dev; |
91 | rgb->drm_dev = drm_dev; |
92 | |
93 | port = of_graph_get_port_by_id(node: dev->of_node, id: video_port); |
94 | if (!port) |
95 | return ERR_PTR(error: -EINVAL); |
96 | |
97 | for_each_child_of_node(port, endpoint) { |
98 | if (of_property_read_u32(np: endpoint, propname: "reg" , out_value: &endpoint_id)) |
99 | endpoint_id = 0; |
100 | |
101 | /* if subdriver (> 0) or error case (< 0), ignore entry */ |
102 | if (rockchip_drm_endpoint_is_subdriver(ep: endpoint) != 0) |
103 | continue; |
104 | |
105 | child_count++; |
106 | ret = drm_of_find_panel_or_bridge(np: dev->of_node, port: video_port, |
107 | endpoint: endpoint_id, panel: &panel, bridge: &bridge); |
108 | if (!ret) { |
109 | of_node_put(node: endpoint); |
110 | break; |
111 | } |
112 | } |
113 | |
114 | of_node_put(node: port); |
115 | |
116 | /* if the rgb output is not connected to anything, just return */ |
117 | if (!child_count) |
118 | return NULL; |
119 | |
120 | if (ret < 0) { |
121 | if (ret != -EPROBE_DEFER) |
122 | DRM_DEV_ERROR(dev, "failed to find panel or bridge %d\n" , ret); |
123 | return ERR_PTR(error: ret); |
124 | } |
125 | |
126 | encoder = &rgb->encoder.encoder; |
127 | encoder->possible_crtcs = drm_crtc_mask(crtc); |
128 | |
129 | ret = drm_simple_encoder_init(dev: drm_dev, encoder, DRM_MODE_ENCODER_NONE); |
130 | if (ret < 0) { |
131 | DRM_DEV_ERROR(drm_dev->dev, |
132 | "failed to initialize encoder: %d\n" , ret); |
133 | return ERR_PTR(error: ret); |
134 | } |
135 | |
136 | drm_encoder_helper_add(encoder, funcs: &rockchip_rgb_encoder_helper_funcs); |
137 | |
138 | if (panel) { |
139 | bridge = drm_panel_bridge_add_typed(panel, |
140 | DRM_MODE_CONNECTOR_LVDS); |
141 | if (IS_ERR(ptr: bridge)) |
142 | return ERR_CAST(ptr: bridge); |
143 | } |
144 | |
145 | rgb->bridge = bridge; |
146 | |
147 | ret = drm_bridge_attach(encoder, bridge: rgb->bridge, NULL, |
148 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
149 | if (ret) |
150 | goto err_free_encoder; |
151 | |
152 | connector = &rgb->connector; |
153 | connector = drm_bridge_connector_init(drm: rgb->drm_dev, encoder); |
154 | if (IS_ERR(ptr: connector)) { |
155 | DRM_DEV_ERROR(drm_dev->dev, |
156 | "failed to initialize bridge connector: %pe\n" , |
157 | connector); |
158 | ret = PTR_ERR(ptr: connector); |
159 | goto err_free_encoder; |
160 | } |
161 | |
162 | rgb->encoder.crtc_endpoint_id = endpoint_id; |
163 | |
164 | ret = drm_connector_attach_encoder(connector, encoder); |
165 | if (ret < 0) { |
166 | DRM_DEV_ERROR(drm_dev->dev, |
167 | "failed to attach encoder: %d\n" , ret); |
168 | goto err_free_connector; |
169 | } |
170 | |
171 | return rgb; |
172 | |
173 | err_free_connector: |
174 | drm_connector_cleanup(connector); |
175 | err_free_encoder: |
176 | drm_encoder_cleanup(encoder); |
177 | return ERR_PTR(error: ret); |
178 | } |
179 | EXPORT_SYMBOL_GPL(rockchip_rgb_init); |
180 | |
181 | void rockchip_rgb_fini(struct rockchip_rgb *rgb) |
182 | { |
183 | drm_panel_bridge_remove(bridge: rgb->bridge); |
184 | drm_connector_cleanup(connector: &rgb->connector); |
185 | drm_encoder_cleanup(encoder: &rgb->encoder.encoder); |
186 | } |
187 | EXPORT_SYMBOL_GPL(rockchip_rgb_fini); |
188 | |