1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | |
3 | #include <linux/backlight.h> |
4 | #include <linux/delay.h> |
5 | #include <linux/gpio/consumer.h> |
6 | #include <linux/module.h> |
7 | #include <linux/of.h> |
8 | #include <linux/regulator/consumer.h> |
9 | |
10 | #include <drm/drm_mipi_dsi.h> |
11 | #include <drm/drm_modes.h> |
12 | #include <drm/drm_panel.h> |
13 | |
14 | struct tm5p5_nt35596 { |
15 | struct drm_panel panel; |
16 | struct mipi_dsi_device *dsi; |
17 | struct regulator_bulk_data supplies[2]; |
18 | struct gpio_desc *reset_gpio; |
19 | }; |
20 | |
21 | static inline struct tm5p5_nt35596 *to_tm5p5_nt35596(struct drm_panel *panel) |
22 | { |
23 | return container_of(panel, struct tm5p5_nt35596, panel); |
24 | } |
25 | |
26 | static void tm5p5_nt35596_reset(struct tm5p5_nt35596 *ctx) |
27 | { |
28 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
29 | usleep_range(min: 1000, max: 2000); |
30 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
31 | usleep_range(min: 1000, max: 2000); |
32 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
33 | usleep_range(min: 15000, max: 16000); |
34 | } |
35 | |
36 | static int tm5p5_nt35596_on(struct tm5p5_nt35596 *ctx) |
37 | { |
38 | struct mipi_dsi_device *dsi = ctx->dsi; |
39 | |
40 | mipi_dsi_generic_write_seq(dsi, 0xff, 0x05); |
41 | mipi_dsi_generic_write_seq(dsi, 0xfb, 0x01); |
42 | mipi_dsi_generic_write_seq(dsi, 0xc5, 0x31); |
43 | mipi_dsi_generic_write_seq(dsi, 0xff, 0x04); |
44 | mipi_dsi_generic_write_seq(dsi, 0x01, 0x84); |
45 | mipi_dsi_generic_write_seq(dsi, 0x05, 0x25); |
46 | mipi_dsi_generic_write_seq(dsi, 0x06, 0x01); |
47 | mipi_dsi_generic_write_seq(dsi, 0x07, 0x20); |
48 | mipi_dsi_generic_write_seq(dsi, 0x08, 0x06); |
49 | mipi_dsi_generic_write_seq(dsi, 0x09, 0x08); |
50 | mipi_dsi_generic_write_seq(dsi, 0x0a, 0x10); |
51 | mipi_dsi_generic_write_seq(dsi, 0x0b, 0x10); |
52 | mipi_dsi_generic_write_seq(dsi, 0x0c, 0x10); |
53 | mipi_dsi_generic_write_seq(dsi, 0x0d, 0x14); |
54 | mipi_dsi_generic_write_seq(dsi, 0x0e, 0x14); |
55 | mipi_dsi_generic_write_seq(dsi, 0x0f, 0x14); |
56 | mipi_dsi_generic_write_seq(dsi, 0x10, 0x14); |
57 | mipi_dsi_generic_write_seq(dsi, 0x11, 0x14); |
58 | mipi_dsi_generic_write_seq(dsi, 0x12, 0x14); |
59 | mipi_dsi_generic_write_seq(dsi, 0x17, 0xf3); |
60 | mipi_dsi_generic_write_seq(dsi, 0x18, 0xc0); |
61 | mipi_dsi_generic_write_seq(dsi, 0x19, 0xc0); |
62 | mipi_dsi_generic_write_seq(dsi, 0x1a, 0xc0); |
63 | mipi_dsi_generic_write_seq(dsi, 0x1b, 0xb3); |
64 | mipi_dsi_generic_write_seq(dsi, 0x1c, 0xb3); |
65 | mipi_dsi_generic_write_seq(dsi, 0x1d, 0xb3); |
66 | mipi_dsi_generic_write_seq(dsi, 0x1e, 0xb3); |
67 | mipi_dsi_generic_write_seq(dsi, 0x1f, 0xb3); |
68 | mipi_dsi_generic_write_seq(dsi, 0x20, 0xb3); |
69 | mipi_dsi_generic_write_seq(dsi, 0xfb, 0x01); |
70 | mipi_dsi_generic_write_seq(dsi, 0xff, 0x00); |
71 | mipi_dsi_generic_write_seq(dsi, 0xfb, 0x01); |
72 | mipi_dsi_generic_write_seq(dsi, 0x35, 0x01); |
73 | mipi_dsi_generic_write_seq(dsi, 0xd3, 0x06); |
74 | mipi_dsi_generic_write_seq(dsi, 0xd4, 0x04); |
75 | mipi_dsi_generic_write_seq(dsi, 0x5e, 0x0d); |
76 | mipi_dsi_generic_write_seq(dsi, 0x11, 0x00); |
77 | msleep(msecs: 100); |
78 | mipi_dsi_generic_write_seq(dsi, 0x29, 0x00); |
79 | mipi_dsi_generic_write_seq(dsi, 0x53, 0x24); |
80 | |
81 | return 0; |
82 | } |
83 | |
84 | static int tm5p5_nt35596_off(struct tm5p5_nt35596 *ctx) |
85 | { |
86 | struct mipi_dsi_device *dsi = ctx->dsi; |
87 | struct device *dev = &dsi->dev; |
88 | int ret; |
89 | |
90 | ret = mipi_dsi_dcs_set_display_off(dsi); |
91 | if (ret < 0) { |
92 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
93 | return ret; |
94 | } |
95 | msleep(msecs: 60); |
96 | |
97 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
98 | if (ret < 0) { |
99 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
100 | return ret; |
101 | } |
102 | |
103 | mipi_dsi_dcs_write_seq(dsi, 0x4f, 0x01); |
104 | |
105 | return 0; |
106 | } |
107 | |
108 | static int tm5p5_nt35596_prepare(struct drm_panel *panel) |
109 | { |
110 | struct tm5p5_nt35596 *ctx = to_tm5p5_nt35596(panel); |
111 | struct device *dev = &ctx->dsi->dev; |
112 | int ret; |
113 | |
114 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
115 | if (ret < 0) { |
116 | dev_err(dev, "Failed to enable regulators: %d\n" , ret); |
117 | return ret; |
118 | } |
119 | |
120 | tm5p5_nt35596_reset(ctx); |
121 | |
122 | ret = tm5p5_nt35596_on(ctx); |
123 | if (ret < 0) { |
124 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
125 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
126 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), |
127 | consumers: ctx->supplies); |
128 | return ret; |
129 | } |
130 | |
131 | return 0; |
132 | } |
133 | |
134 | static int tm5p5_nt35596_unprepare(struct drm_panel *panel) |
135 | { |
136 | struct tm5p5_nt35596 *ctx = to_tm5p5_nt35596(panel); |
137 | struct device *dev = &ctx->dsi->dev; |
138 | int ret; |
139 | |
140 | ret = tm5p5_nt35596_off(ctx); |
141 | if (ret < 0) |
142 | dev_err(dev, "Failed to un-initialize panel: %d\n" , ret); |
143 | |
144 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
145 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), |
146 | consumers: ctx->supplies); |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static const struct drm_display_mode tm5p5_nt35596_mode = { |
152 | .clock = (1080 + 100 + 8 + 16) * (1920 + 4 + 2 + 4) * 60 / 1000, |
153 | .hdisplay = 1080, |
154 | .hsync_start = 1080 + 100, |
155 | .hsync_end = 1080 + 100 + 8, |
156 | .htotal = 1080 + 100 + 8 + 16, |
157 | .vdisplay = 1920, |
158 | .vsync_start = 1920 + 4, |
159 | .vsync_end = 1920 + 4 + 2, |
160 | .vtotal = 1920 + 4 + 2 + 4, |
161 | .width_mm = 68, |
162 | .height_mm = 121, |
163 | }; |
164 | |
165 | static int tm5p5_nt35596_get_modes(struct drm_panel *panel, |
166 | struct drm_connector *connector) |
167 | { |
168 | struct drm_display_mode *mode; |
169 | |
170 | mode = drm_mode_duplicate(dev: connector->dev, mode: &tm5p5_nt35596_mode); |
171 | if (!mode) |
172 | return -ENOMEM; |
173 | |
174 | drm_mode_set_name(mode); |
175 | |
176 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
177 | connector->display_info.width_mm = mode->width_mm; |
178 | connector->display_info.height_mm = mode->height_mm; |
179 | drm_mode_probed_add(connector, mode); |
180 | |
181 | return 1; |
182 | } |
183 | |
184 | static const struct drm_panel_funcs tm5p5_nt35596_panel_funcs = { |
185 | .prepare = tm5p5_nt35596_prepare, |
186 | .unprepare = tm5p5_nt35596_unprepare, |
187 | .get_modes = tm5p5_nt35596_get_modes, |
188 | }; |
189 | |
190 | static int tm5p5_nt35596_bl_update_status(struct backlight_device *bl) |
191 | { |
192 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
193 | u16 brightness = backlight_get_brightness(bd: bl); |
194 | int ret; |
195 | |
196 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
197 | |
198 | ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness); |
199 | if (ret < 0) |
200 | return ret; |
201 | |
202 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static int tm5p5_nt35596_bl_get_brightness(struct backlight_device *bl) |
208 | { |
209 | struct mipi_dsi_device *dsi = bl_get_data(bl_dev: bl); |
210 | u16 brightness = bl->props.brightness; |
211 | int ret; |
212 | |
213 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
214 | |
215 | ret = mipi_dsi_dcs_get_display_brightness(dsi, brightness: &brightness); |
216 | if (ret < 0) |
217 | return ret; |
218 | |
219 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
220 | |
221 | return brightness & 0xff; |
222 | } |
223 | |
224 | static const struct backlight_ops tm5p5_nt35596_bl_ops = { |
225 | .update_status = tm5p5_nt35596_bl_update_status, |
226 | .get_brightness = tm5p5_nt35596_bl_get_brightness, |
227 | }; |
228 | |
229 | static struct backlight_device * |
230 | tm5p5_nt35596_create_backlight(struct mipi_dsi_device *dsi) |
231 | { |
232 | struct device *dev = &dsi->dev; |
233 | const struct backlight_properties props = { |
234 | .type = BACKLIGHT_RAW, |
235 | .brightness = 255, |
236 | .max_brightness = 255, |
237 | }; |
238 | |
239 | return devm_backlight_device_register(dev, name: dev_name(dev), parent: dev, devdata: dsi, |
240 | ops: &tm5p5_nt35596_bl_ops, props: &props); |
241 | } |
242 | |
243 | static int tm5p5_nt35596_probe(struct mipi_dsi_device *dsi) |
244 | { |
245 | struct device *dev = &dsi->dev; |
246 | struct tm5p5_nt35596 *ctx; |
247 | int ret; |
248 | |
249 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
250 | if (!ctx) |
251 | return -ENOMEM; |
252 | |
253 | ctx->supplies[0].supply = "vdd" ; |
254 | ctx->supplies[1].supply = "vddio" ; |
255 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
256 | consumers: ctx->supplies); |
257 | if (ret < 0) { |
258 | dev_err(dev, "Failed to get regulators: %d\n" , ret); |
259 | return ret; |
260 | } |
261 | |
262 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
263 | if (IS_ERR(ptr: ctx->reset_gpio)) { |
264 | ret = PTR_ERR(ptr: ctx->reset_gpio); |
265 | dev_err(dev, "Failed to get reset-gpios: %d\n" , ret); |
266 | return ret; |
267 | } |
268 | |
269 | ctx->dsi = dsi; |
270 | mipi_dsi_set_drvdata(dsi, data: ctx); |
271 | |
272 | dsi->lanes = 4; |
273 | dsi->format = MIPI_DSI_FMT_RGB888; |
274 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | |
275 | MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_NO_EOT_PACKET | |
276 | MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; |
277 | |
278 | drm_panel_init(panel: &ctx->panel, dev, funcs: &tm5p5_nt35596_panel_funcs, |
279 | DRM_MODE_CONNECTOR_DSI); |
280 | |
281 | ctx->panel.backlight = tm5p5_nt35596_create_backlight(dsi); |
282 | if (IS_ERR(ptr: ctx->panel.backlight)) { |
283 | ret = PTR_ERR(ptr: ctx->panel.backlight); |
284 | dev_err(dev, "Failed to create backlight: %d\n" , ret); |
285 | return ret; |
286 | } |
287 | |
288 | drm_panel_add(panel: &ctx->panel); |
289 | |
290 | ret = mipi_dsi_attach(dsi); |
291 | if (ret < 0) { |
292 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
293 | return ret; |
294 | } |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | static void tm5p5_nt35596_remove(struct mipi_dsi_device *dsi) |
300 | { |
301 | struct tm5p5_nt35596 *ctx = mipi_dsi_get_drvdata(dsi); |
302 | int ret; |
303 | |
304 | ret = mipi_dsi_detach(dsi); |
305 | if (ret < 0) |
306 | dev_err(&dsi->dev, |
307 | "Failed to detach from DSI host: %d\n" , ret); |
308 | |
309 | drm_panel_remove(panel: &ctx->panel); |
310 | } |
311 | |
312 | static const struct of_device_id tm5p5_nt35596_of_match[] = { |
313 | { .compatible = "asus,z00t-tm5p5-n35596" }, |
314 | { /* sentinel */ } |
315 | }; |
316 | MODULE_DEVICE_TABLE(of, tm5p5_nt35596_of_match); |
317 | |
318 | static struct mipi_dsi_driver tm5p5_nt35596_driver = { |
319 | .probe = tm5p5_nt35596_probe, |
320 | .remove = tm5p5_nt35596_remove, |
321 | .driver = { |
322 | .name = "panel-tm5p5-nt35596" , |
323 | .of_match_table = tm5p5_nt35596_of_match, |
324 | }, |
325 | }; |
326 | module_mipi_dsi_driver(tm5p5_nt35596_driver); |
327 | |
328 | MODULE_AUTHOR("Konrad Dybcio <konradybcio@gmail.com>" ); |
329 | MODULE_DESCRIPTION("DRM driver for tm5p5 nt35596 1080p video mode dsi panel" ); |
330 | MODULE_LICENSE("GPL v2" ); |
331 | |