1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright (C) 2019, Michael Srba |
3 | |
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 <video/mipi_display.h> |
11 | |
12 | #include <drm/drm_mipi_dsi.h> |
13 | #include <drm/drm_modes.h> |
14 | #include <drm/drm_panel.h> |
15 | |
16 | struct s6e88a0_ams452ef01 { |
17 | struct drm_panel panel; |
18 | struct mipi_dsi_device *dsi; |
19 | struct regulator_bulk_data supplies[2]; |
20 | struct gpio_desc *reset_gpio; |
21 | }; |
22 | |
23 | static inline struct |
24 | s6e88a0_ams452ef01 *to_s6e88a0_ams452ef01(struct drm_panel *panel) |
25 | { |
26 | return container_of(panel, struct s6e88a0_ams452ef01, panel); |
27 | } |
28 | |
29 | static void s6e88a0_ams452ef01_reset(struct s6e88a0_ams452ef01 *ctx) |
30 | { |
31 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
32 | usleep_range(min: 5000, max: 6000); |
33 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
34 | usleep_range(min: 1000, max: 2000); |
35 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 1); |
36 | usleep_range(min: 10000, max: 11000); |
37 | } |
38 | |
39 | static int s6e88a0_ams452ef01_on(struct s6e88a0_ams452ef01 *ctx) |
40 | { |
41 | struct mipi_dsi_device *dsi = ctx->dsi; |
42 | struct device *dev = &dsi->dev; |
43 | int ret; |
44 | |
45 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; |
46 | |
47 | mipi_dsi_dcs_write_seq(dsi, 0xf0, 0x5a, 0x5a); // enable LEVEL2 commands |
48 | mipi_dsi_dcs_write_seq(dsi, 0xcc, 0x4c); // set Pixel Clock Divider polarity |
49 | |
50 | ret = mipi_dsi_dcs_exit_sleep_mode(dsi); |
51 | if (ret < 0) { |
52 | dev_err(dev, "Failed to exit sleep mode: %d\n" , ret); |
53 | return ret; |
54 | } |
55 | msleep(msecs: 120); |
56 | |
57 | // set default brightness/gama |
58 | mipi_dsi_dcs_write_seq(dsi, 0xca, |
59 | 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, // V255 RR,GG,BB |
60 | 0x80, 0x80, 0x80, // V203 R,G,B |
61 | 0x80, 0x80, 0x80, // V151 R,G,B |
62 | 0x80, 0x80, 0x80, // V87 R,G,B |
63 | 0x80, 0x80, 0x80, // V51 R,G,B |
64 | 0x80, 0x80, 0x80, // V35 R,G,B |
65 | 0x80, 0x80, 0x80, // V23 R,G,B |
66 | 0x80, 0x80, 0x80, // V11 R,G,B |
67 | 0x6b, 0x68, 0x71, // V3 R,G,B |
68 | 0x00, 0x00, 0x00); // V1 R,G,B |
69 | // set default Amoled Off Ratio |
70 | mipi_dsi_dcs_write_seq(dsi, 0xb2, 0x40, 0x0a, 0x17, 0x00, 0x0a); |
71 | mipi_dsi_dcs_write_seq(dsi, 0xb6, 0x2c, 0x0b); // set default elvss voltage |
72 | mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00); |
73 | mipi_dsi_dcs_write_seq(dsi, 0xf7, 0x03); // gamma/aor update |
74 | mipi_dsi_dcs_write_seq(dsi, 0xf0, 0xa5, 0xa5); // disable LEVEL2 commands |
75 | |
76 | ret = mipi_dsi_dcs_set_display_on(dsi); |
77 | if (ret < 0) { |
78 | dev_err(dev, "Failed to set display on: %d\n" , ret); |
79 | return ret; |
80 | } |
81 | |
82 | return 0; |
83 | } |
84 | |
85 | static int s6e88a0_ams452ef01_off(struct s6e88a0_ams452ef01 *ctx) |
86 | { |
87 | struct mipi_dsi_device *dsi = ctx->dsi; |
88 | struct device *dev = &dsi->dev; |
89 | int ret; |
90 | |
91 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; |
92 | |
93 | ret = mipi_dsi_dcs_set_display_off(dsi); |
94 | if (ret < 0) { |
95 | dev_err(dev, "Failed to set display off: %d\n" , ret); |
96 | return ret; |
97 | } |
98 | msleep(msecs: 35); |
99 | |
100 | ret = mipi_dsi_dcs_enter_sleep_mode(dsi); |
101 | if (ret < 0) { |
102 | dev_err(dev, "Failed to enter sleep mode: %d\n" , ret); |
103 | return ret; |
104 | } |
105 | msleep(msecs: 120); |
106 | |
107 | return 0; |
108 | } |
109 | |
110 | static int s6e88a0_ams452ef01_prepare(struct drm_panel *panel) |
111 | { |
112 | struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel); |
113 | struct device *dev = &ctx->dsi->dev; |
114 | int ret; |
115 | |
116 | ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
117 | if (ret < 0) { |
118 | dev_err(dev, "Failed to enable regulators: %d\n" , ret); |
119 | return ret; |
120 | } |
121 | |
122 | s6e88a0_ams452ef01_reset(ctx); |
123 | |
124 | ret = s6e88a0_ams452ef01_on(ctx); |
125 | if (ret < 0) { |
126 | dev_err(dev, "Failed to initialize panel: %d\n" , ret); |
127 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
128 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), |
129 | consumers: ctx->supplies); |
130 | return ret; |
131 | } |
132 | |
133 | return 0; |
134 | } |
135 | |
136 | static int s6e88a0_ams452ef01_unprepare(struct drm_panel *panel) |
137 | { |
138 | struct s6e88a0_ams452ef01 *ctx = to_s6e88a0_ams452ef01(panel); |
139 | struct device *dev = &ctx->dsi->dev; |
140 | int ret; |
141 | |
142 | ret = s6e88a0_ams452ef01_off(ctx); |
143 | if (ret < 0) |
144 | dev_err(dev, "Failed to un-initialize panel: %d\n" , ret); |
145 | |
146 | gpiod_set_value_cansleep(desc: ctx->reset_gpio, value: 0); |
147 | regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), consumers: ctx->supplies); |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static const struct drm_display_mode s6e88a0_ams452ef01_mode = { |
153 | .clock = (540 + 88 + 4 + 20) * (960 + 14 + 2 + 8) * 60 / 1000, |
154 | .hdisplay = 540, |
155 | .hsync_start = 540 + 88, |
156 | .hsync_end = 540 + 88 + 4, |
157 | .htotal = 540 + 88 + 4 + 20, |
158 | .vdisplay = 960, |
159 | .vsync_start = 960 + 14, |
160 | .vsync_end = 960 + 14 + 2, |
161 | .vtotal = 960 + 14 + 2 + 8, |
162 | .width_mm = 56, |
163 | .height_mm = 100, |
164 | }; |
165 | |
166 | static int s6e88a0_ams452ef01_get_modes(struct drm_panel *panel, |
167 | struct drm_connector *connector) |
168 | { |
169 | struct drm_display_mode *mode; |
170 | |
171 | mode = drm_mode_duplicate(dev: connector->dev, mode: &s6e88a0_ams452ef01_mode); |
172 | if (!mode) |
173 | return -ENOMEM; |
174 | |
175 | drm_mode_set_name(mode); |
176 | |
177 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; |
178 | connector->display_info.width_mm = mode->width_mm; |
179 | connector->display_info.height_mm = mode->height_mm; |
180 | drm_mode_probed_add(connector, mode); |
181 | |
182 | return 1; |
183 | } |
184 | |
185 | static const struct drm_panel_funcs s6e88a0_ams452ef01_panel_funcs = { |
186 | .unprepare = s6e88a0_ams452ef01_unprepare, |
187 | .prepare = s6e88a0_ams452ef01_prepare, |
188 | .get_modes = s6e88a0_ams452ef01_get_modes, |
189 | }; |
190 | |
191 | static int s6e88a0_ams452ef01_probe(struct mipi_dsi_device *dsi) |
192 | { |
193 | struct device *dev = &dsi->dev; |
194 | struct s6e88a0_ams452ef01 *ctx; |
195 | int ret; |
196 | |
197 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
198 | if (!ctx) |
199 | return -ENOMEM; |
200 | |
201 | ctx->supplies[0].supply = "vdd3" ; |
202 | ctx->supplies[1].supply = "vci" ; |
203 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), |
204 | consumers: ctx->supplies); |
205 | if (ret < 0) { |
206 | dev_err(dev, "Failed to get regulators: %d\n" , ret); |
207 | return ret; |
208 | } |
209 | |
210 | ctx->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
211 | if (IS_ERR(ptr: ctx->reset_gpio)) { |
212 | ret = PTR_ERR(ptr: ctx->reset_gpio); |
213 | dev_err(dev, "Failed to get reset-gpios: %d\n" , ret); |
214 | return ret; |
215 | } |
216 | |
217 | ctx->dsi = dsi; |
218 | mipi_dsi_set_drvdata(dsi, data: ctx); |
219 | |
220 | dsi->lanes = 2; |
221 | dsi->format = MIPI_DSI_FMT_RGB888; |
222 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST; |
223 | |
224 | drm_panel_init(panel: &ctx->panel, dev, funcs: &s6e88a0_ams452ef01_panel_funcs, |
225 | DRM_MODE_CONNECTOR_DSI); |
226 | |
227 | drm_panel_add(panel: &ctx->panel); |
228 | |
229 | ret = mipi_dsi_attach(dsi); |
230 | if (ret < 0) { |
231 | dev_err(dev, "Failed to attach to DSI host: %d\n" , ret); |
232 | drm_panel_remove(panel: &ctx->panel); |
233 | return ret; |
234 | } |
235 | |
236 | return 0; |
237 | } |
238 | |
239 | static void s6e88a0_ams452ef01_remove(struct mipi_dsi_device *dsi) |
240 | { |
241 | struct s6e88a0_ams452ef01 *ctx = mipi_dsi_get_drvdata(dsi); |
242 | int ret; |
243 | |
244 | ret = mipi_dsi_detach(dsi); |
245 | if (ret < 0) |
246 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n" , ret); |
247 | |
248 | drm_panel_remove(panel: &ctx->panel); |
249 | } |
250 | |
251 | static const struct of_device_id s6e88a0_ams452ef01_of_match[] = { |
252 | { .compatible = "samsung,s6e88a0-ams452ef01" }, |
253 | { /* sentinel */ }, |
254 | }; |
255 | MODULE_DEVICE_TABLE(of, s6e88a0_ams452ef01_of_match); |
256 | |
257 | static struct mipi_dsi_driver s6e88a0_ams452ef01_driver = { |
258 | .probe = s6e88a0_ams452ef01_probe, |
259 | .remove = s6e88a0_ams452ef01_remove, |
260 | .driver = { |
261 | .name = "panel-s6e88a0-ams452ef01" , |
262 | .of_match_table = s6e88a0_ams452ef01_of_match, |
263 | }, |
264 | }; |
265 | module_mipi_dsi_driver(s6e88a0_ams452ef01_driver); |
266 | |
267 | MODULE_AUTHOR("Michael Srba <Michael.Srba@seznam.cz>" ); |
268 | MODULE_DESCRIPTION("MIPI-DSI based Panel Driver for AMS452EF01 AMOLED LCD with a S6E88A0 controller" ); |
269 | MODULE_LICENSE("GPL v2" ); |
270 | |