1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2022 Konrad Dybcio <konrad.dybcio@somainline.org> |
4 | * |
5 | * Generated with linux-mdss-dsi-panel-driver-generator with a |
6 | * substantial amount of manual adjustments. |
7 | * |
8 | * SONY Downstream kernel calls this one: |
9 | * - "JDI ID3" for Akari (XZ2) |
10 | * - "JDI ID4" for Apollo (XZ2 Compact) |
11 | */ |
12 | |
13 | #include <linux/delay.h> |
14 | #include <linux/gpio/consumer.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/regulator/consumer.h> |
18 | |
19 | #include <video/mipi_display.h> |
20 | |
21 | #include <drm/drm_mipi_dsi.h> |
22 | #include <drm/drm_modes.h> |
23 | #include <drm/drm_panel.h> |
24 | |
25 | enum { |
26 | TYPE_TAMA_60HZ, |
27 | /* |
28 | * Leaving room for expansion - SONY very often uses |
29 | * *truly reliably* overclockable panels on their flagships! |
30 | */ |
31 | }; |
32 | |
33 | struct sony_td4353_jdi { |
34 | struct drm_panel panel; |
35 | struct mipi_dsi_device *dsi; |
36 | struct regulator_bulk_data supplies[3]; |
37 | struct gpio_desc *panel_reset_gpio; |
38 | struct gpio_desc *touch_reset_gpio; |
39 | int type; |
40 | }; |
41 | |
42 | static inline struct sony_td4353_jdi *to_sony_td4353_jdi(struct drm_panel *panel) |
43 | { |
44 | return container_of(panel, struct sony_td4353_jdi, panel); |
45 | } |
46 | |
47 | static int sony_td4353_jdi_on(struct sony_td4353_jdi *ctx) |
48 | { |
49 | struct mipi_dsi_device *dsi = ctx->dsi; |
50 | struct device *dev = &dsi->dev; |
51 | int ret; |
52 | |
53 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
54 | |
55 | ret = mipi_dsi_dcs_set_column_address(dsi, start: 0x0000, end: 1080 - 1); |
56 | if (ret < 0) { |
57 | dev_err(dev, "Failed to set column address: %d\n" , ret); |
58 | return ret; |
59 | } |
60 | |
61 | ret = mipi_dsi_dcs_set_page_address(dsi, start: 0x0000, end: 2160 - 1); |
62 | if (ret < 0) { |
63 | dev_err(dev, "Failed to set page address: %d\n" , ret); |
64 | return ret; |
65 | } |
66 | |
67 | ret = mipi_dsi_dcs_set_tear_scanline(dsi, scanline: 0); |
68 | if (ret < 0) { |
69 | dev_err(dev, "Failed to set tear scanline: %d\n" , ret); |
70 | return ret; |
71 | } |
72 | |
73 | ret = mipi_dsi_dcs_set_tear_on(dsi, mode: MIPI_DSI_DCS_TEAR_MODE_VBLANK); |
74 | if (ret < 0) { |
75 | dev_err(dev, "Failed to set tear on: %d\n" , ret); |
76 | return ret; |
77 | } |
78 | |
79 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_ADDRESS_MODE, 0x00); |
80 | |
81 | ret = mipi_dsi_dcs_set_pixel_format(dsi, format: 0x77); |
82 | if (ret < 0) { |
83 | dev_err(dev, "Failed to set pixel format: %d\n" , ret); |
84 | return ret; |
85 | } |
86 | |
87 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_PARTIAL_ROWS, |
88 | 0x00, 0x00, 0x08, 0x6f); |
89 | |
90 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
91 | if (ret < 0) { |
92 | dev_err(dev, "Failed to exit sleep mode: %d\n" , ret); |
93 | return ret; |
94 | } |
95 | msleep(msecs: 70); |
96 | |
97 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_MEMORY_START); |
98 | |
99 | ret = mipi_dsi_dcs_set_display_on(dsi); |
100 | if (ret < 0) { |
101 | dev_err(dev, "Failed to turn display on: %d\n" , ret); |
102 | return ret; |
103 | } |
104 | |
105 | return 0; |
106 | } |
107 | |
108 | static int sony_td4353_jdi_off(struct sony_td4353_jdi *ctx) |
109 | { |
110 | struct mipi_dsi_device *dsi = ctx->dsi; |
111 | struct device *dev = &dsi->dev; |
112 | int ret; |
113 | |
114 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
115 | |
116 | ret = mipi_dsi_dcs_set_display_off(dsi); |
117 | if (ret < 0) { |
118 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
119 | return ret; |
120 | } |
121 | msleep(msecs: 22); |
122 | |
123 | ret = mipi_dsi_dcs_set_tear_off(dsi); |
124 | if (ret < 0) { |
125 | dev_err(dev, "Failed to set tear off: %d\n" , ret); |
126 | return ret; |
127 | } |
128 | |
129 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
130 | if (ret < 0) { |
131 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
132 | return ret; |
133 | } |
134 | msleep(msecs: 80); |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | static void sony_td4353_assert_reset_gpios(struct sony_td4353_jdi *ctx, int mode) |
140 | { |
141 | gpiod_set_value_cansleep(desc: ctx->touch_reset_gpio, value: mode); |
142 | gpiod_set_value_cansleep(desc: ctx->panel_reset_gpio, value: mode); |
143 | usleep_range(min: 5000, max: 5100); |
144 | } |
145 | |
146 | static int sony_td4353_jdi_prepare(struct drm_panel *panel) |
147 | { |
148 | struct sony_td4353_jdi *ctx = to_sony_td4353_jdi(panel); |
149 | struct device *dev = &ctx->dsi->dev; |
150 | int ret; |
151 | |
152 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
153 | if (ret < 0) { |
154 | dev_err(dev, "Failed to enable regulators: %d\n" , ret); |
155 | return ret; |
156 | } |
157 | |
158 | msleep(msecs: 100); |
159 | |
160 | sony_td4353_assert_reset_gpios(ctx, mode: 1); |
161 | |
162 | ret = sony_td4353_jdi_on(ctx); |
163 | if (ret < 0) { |
164 | dev_err(dev, "Failed to power on panel: %d\n" , ret); |
165 | sony_td4353_assert_reset_gpios(ctx, mode: 0); |
166 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
167 | return ret; |
168 | } |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static int sony_td4353_jdi_unprepare(struct drm_panel *panel) |
174 | { |
175 | struct sony_td4353_jdi *ctx = to_sony_td4353_jdi(panel); |
176 | struct device *dev = &ctx->dsi->dev; |
177 | int ret; |
178 | |
179 | ret = sony_td4353_jdi_off(ctx); |
180 | if (ret < 0) |
181 | dev_err(dev, "Failed to power off panel: %d\n" , ret); |
182 | |
183 | sony_td4353_assert_reset_gpios(ctx, mode: 0); |
184 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static const struct drm_display_mode sony_td4353_jdi_mode_tama_60hz = { |
190 | .clock = (1080 + 4 + 8 + 8) * (2160 + 259 + 8 + 8) * 60 / 1000, |
191 | .hdisplay = 1080, |
192 | .hsync_start = 1080 + 4, |
193 | .hsync_end = 1080 + 4 + 8, |
194 | .htotal = 1080 + 4 + 8 + 8, |
195 | .vdisplay = 2160, |
196 | .vsync_start = 2160 + 259, |
197 | .vsync_end = 2160 + 259 + 8, |
198 | .vtotal = 2160 + 259 + 8 + 8, |
199 | .width_mm = 64, |
200 | .height_mm = 128, |
201 | }; |
202 | |
203 | static int sony_td4353_jdi_get_modes(struct drm_panel *panel, |
204 | struct drm_connector *connector) |
205 | { |
206 | struct sony_td4353_jdi *ctx = to_sony_td4353_jdi(panel); |
207 | struct drm_display_mode *mode = NULL; |
208 | |
209 | if (ctx->type == TYPE_TAMA_60HZ) |
210 | mode = drm_mode_duplicate(dev: connector->dev, mode: &sony_td4353_jdi_mode_tama_60hz); |
211 | else |
212 | return -EINVAL; |
213 | |
214 | if (!mode) |
215 | return -ENOMEM; |
216 | |
217 | drm_mode_set_name(mode); |
218 | |
219 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
220 | connector->display_info.width_mm = mode->width_mm; |
221 | connector->display_info.height_mm = mode->height_mm; |
222 | drm_mode_probed_add(connector, mode); |
223 | |
224 | return 1; |
225 | } |
226 | |
227 | static const struct drm_panel_funcs sony_td4353_jdi_panel_funcs = { |
228 | .prepare = sony_td4353_jdi_prepare, |
229 | .unprepare = sony_td4353_jdi_unprepare, |
230 | .get_modes = sony_td4353_jdi_get_modes, |
231 | }; |
232 | |
233 | static int sony_td4353_jdi_probe(struct mipi_dsi_device *dsi) |
234 | { |
235 | struct device *dev = &dsi->dev; |
236 | struct sony_td4353_jdi *ctx; |
237 | int ret; |
238 | |
239 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
240 | if (!ctx) |
241 | return -ENOMEM; |
242 | |
243 | ctx->type = (uintptr_t)of_device_get_match_data(dev); |
244 | |
245 | ctx->supplies[0].supply = "vddio" ; |
246 | ctx->supplies[1].supply = "vsp" ; |
247 | ctx->supplies[2].supply = "vsn" ; |
248 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
249 | consumers: ctx->supplies); |
250 | if (ret < 0) |
251 | return dev_err_probe(dev, err: ret, fmt: "Failed to get regulators\n" ); |
252 | |
253 | ctx->panel_reset_gpio = devm_gpiod_get(dev, con_id: "panel-reset" , flags: GPIOD_ASIS); |
254 | if (IS_ERR(ptr: ctx->panel_reset_gpio)) |
255 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->panel_reset_gpio), |
256 | fmt: "Failed to get panel-reset-gpios\n" ); |
257 | |
258 | ctx->touch_reset_gpio = devm_gpiod_get(dev, con_id: "touch-reset" , flags: GPIOD_ASIS); |
259 | if (IS_ERR(ptr: ctx->touch_reset_gpio)) |
260 | return dev_err_probe(dev, err: PTR_ERR(ptr: ctx->touch_reset_gpio), |
261 | fmt: "Failed to get touch-reset-gpios\n" ); |
262 | |
263 | ctx->dsi = dsi; |
264 | mipi_dsi_set_drvdata(dsi, data: ctx); |
265 | |
266 | dsi->lanes = 4; |
267 | dsi->format = MIPI_DSI_FMT_RGB888; |
268 | dsi->mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS; |
269 | |
270 | drm_panel_init(panel: &ctx->panel, dev, funcs: &sony_td4353_jdi_panel_funcs, |
271 | DRM_MODE_CONNECTOR_DSI); |
272 | |
273 | ret = drm_panel_of_backlight(panel: &ctx->panel); |
274 | if (ret) |
275 | return dev_err_probe(dev, err: ret, fmt: "Failed to get backlight\n" ); |
276 | |
277 | drm_panel_add(panel: &ctx->panel); |
278 | |
279 | ret = mipi_dsi_attach(dsi); |
280 | if (ret < 0) { |
281 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
282 | drm_panel_remove(panel: &ctx->panel); |
283 | return ret; |
284 | } |
285 | |
286 | return 0; |
287 | } |
288 | |
289 | static void sony_td4353_jdi_remove(struct mipi_dsi_device *dsi) |
290 | { |
291 | struct sony_td4353_jdi *ctx = mipi_dsi_get_drvdata(dsi); |
292 | int ret; |
293 | |
294 | ret = mipi_dsi_detach(dsi); |
295 | if (ret < 0) |
296 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
297 | |
298 | drm_panel_remove(panel: &ctx->panel); |
299 | } |
300 | |
301 | static const struct of_device_id sony_td4353_jdi_of_match[] = { |
302 | { .compatible = "sony,td4353-jdi-tama" , .data = (void *)TYPE_TAMA_60HZ }, |
303 | { /* sentinel */ } |
304 | }; |
305 | MODULE_DEVICE_TABLE(of, sony_td4353_jdi_of_match); |
306 | |
307 | static struct mipi_dsi_driver sony_td4353_jdi_driver = { |
308 | .probe = sony_td4353_jdi_probe, |
309 | .remove = sony_td4353_jdi_remove, |
310 | .driver = { |
311 | .name = "panel-sony-td4353-jdi" , |
312 | .of_match_table = sony_td4353_jdi_of_match, |
313 | }, |
314 | }; |
315 | module_mipi_dsi_driver(sony_td4353_jdi_driver); |
316 | |
317 | MODULE_AUTHOR("Konrad Dybcio <konrad.dybcio@somainline.org>" ); |
318 | MODULE_DESCRIPTION("DRM panel driver for SONY Xperia XZ2/XZ2c JDI panel" ); |
319 | MODULE_LICENSE("GPL" ); |
320 | |