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
25enum {
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
33struct 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
42static 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
47static 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
108static 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
139static 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
146static 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
173static 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
189static 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
203static 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
227static 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
233static 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
289static 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
301static const struct of_device_id sony_td4353_jdi_of_match[] = {
302 { .compatible = "sony,td4353-jdi-tama", .data = (void *)TYPE_TAMA_60HZ },
303 { /* sentinel */ }
304};
305MODULE_DEVICE_TABLE(of, sony_td4353_jdi_of_match);
306
307static 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};
315module_mipi_dsi_driver(sony_td4353_jdi_driver);
316
317MODULE_AUTHOR("Konrad Dybcio <konrad.dybcio@somainline.org>");
318MODULE_DESCRIPTION("DRM panel driver for SONY Xperia XZ2/XZ2c JDI panel");
319MODULE_LICENSE("GPL");
320

source code of linux/drivers/gpu/drm/panel/panel-sony-td4353-jdi.c