Commit | Line | Data |
---|---|---|
69dc678a JT |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright (C) 2018 Amarula Solutions | |
4 | * Author: Jagan Teki <jagan@amarulasolutions.com> | |
5 | */ | |
6 | ||
7 | #include <drm/drm_mipi_dsi.h> | |
8 | #include <drm/drm_modes.h> | |
9 | #include <drm/drm_panel.h> | |
10 | #include <drm/drm_print.h> | |
11 | ||
12 | #include <linux/backlight.h> | |
13 | #include <linux/gpio/consumer.h> | |
14 | #include <linux/delay.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/of_device.h> | |
17 | #include <linux/regulator/consumer.h> | |
18 | ||
19 | #define FEIYANG_INIT_CMD_LEN 2 | |
20 | ||
21 | struct feiyang { | |
22 | struct drm_panel panel; | |
23 | struct mipi_dsi_device *dsi; | |
24 | ||
25 | struct backlight_device *backlight; | |
26 | struct regulator *dvdd; | |
27 | struct regulator *avdd; | |
28 | struct gpio_desc *reset; | |
29 | }; | |
30 | ||
31 | static inline struct feiyang *panel_to_feiyang(struct drm_panel *panel) | |
32 | { | |
33 | return container_of(panel, struct feiyang, panel); | |
34 | } | |
35 | ||
36 | struct feiyang_init_cmd { | |
37 | u8 data[FEIYANG_INIT_CMD_LEN]; | |
38 | }; | |
39 | ||
40 | static const struct feiyang_init_cmd feiyang_init_cmds[] = { | |
41 | { .data = { 0x80, 0x58 } }, | |
42 | { .data = { 0x81, 0x47 } }, | |
43 | { .data = { 0x82, 0xD4 } }, | |
44 | { .data = { 0x83, 0x88 } }, | |
45 | { .data = { 0x84, 0xA9 } }, | |
46 | { .data = { 0x85, 0xC3 } }, | |
47 | { .data = { 0x86, 0x82 } }, | |
48 | }; | |
49 | ||
50 | static int feiyang_prepare(struct drm_panel *panel) | |
51 | { | |
52 | struct feiyang *ctx = panel_to_feiyang(panel); | |
53 | struct mipi_dsi_device *dsi = ctx->dsi; | |
54 | unsigned int i; | |
55 | int ret; | |
56 | ||
57 | ret = regulator_enable(ctx->dvdd); | |
58 | if (ret) | |
59 | return ret; | |
60 | ||
61 | /* T1 (dvdd start + dvdd rise) 0 < T1 <= 10ms */ | |
62 | msleep(10); | |
63 | ||
64 | ret = regulator_enable(ctx->avdd); | |
65 | if (ret) | |
66 | return ret; | |
67 | ||
68 | /* T3 (dvdd rise + avdd start + avdd rise) T3 >= 20ms */ | |
69 | msleep(20); | |
70 | ||
71 | gpiod_set_value(ctx->reset, 0); | |
72 | ||
73 | /* | |
74 | * T5 + T6 (avdd rise + video & logic signal rise) | |
75 | * T5 >= 10ms, 0 < T6 <= 10ms | |
76 | */ | |
77 | msleep(20); | |
78 | ||
79 | gpiod_set_value(ctx->reset, 1); | |
80 | ||
81 | /* T12 (video & logic signal rise + backlight rise) T12 >= 200ms */ | |
82 | msleep(200); | |
83 | ||
84 | for (i = 0; i < ARRAY_SIZE(feiyang_init_cmds); i++) { | |
85 | const struct feiyang_init_cmd *cmd = | |
86 | &feiyang_init_cmds[i]; | |
87 | ||
88 | ret = mipi_dsi_dcs_write_buffer(dsi, cmd->data, | |
89 | FEIYANG_INIT_CMD_LEN); | |
90 | if (ret < 0) | |
91 | return ret; | |
92 | } | |
93 | ||
94 | return 0; | |
95 | } | |
96 | ||
97 | static int feiyang_enable(struct drm_panel *panel) | |
98 | { | |
99 | struct feiyang *ctx = panel_to_feiyang(panel); | |
100 | ||
101 | /* T12 (video & logic signal rise + backlight rise) T12 >= 200ms */ | |
102 | msleep(200); | |
103 | ||
104 | mipi_dsi_dcs_set_display_on(ctx->dsi); | |
105 | backlight_enable(ctx->backlight); | |
106 | ||
107 | return 0; | |
108 | } | |
109 | ||
110 | static int feiyang_disable(struct drm_panel *panel) | |
111 | { | |
112 | struct feiyang *ctx = panel_to_feiyang(panel); | |
113 | ||
114 | backlight_disable(ctx->backlight); | |
115 | return mipi_dsi_dcs_set_display_off(ctx->dsi); | |
116 | } | |
117 | ||
118 | static int feiyang_unprepare(struct drm_panel *panel) | |
119 | { | |
120 | struct feiyang *ctx = panel_to_feiyang(panel); | |
121 | int ret; | |
122 | ||
123 | ret = mipi_dsi_dcs_set_display_off(ctx->dsi); | |
124 | if (ret < 0) | |
125 | DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n", | |
126 | ret); | |
127 | ||
128 | ret = mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); | |
129 | if (ret < 0) | |
130 | DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n", | |
131 | ret); | |
132 | ||
133 | /* T13 (backlight fall + video & logic signal fall) T13 >= 200ms */ | |
134 | msleep(200); | |
135 | ||
136 | gpiod_set_value(ctx->reset, 0); | |
137 | ||
138 | regulator_disable(ctx->avdd); | |
139 | ||
140 | /* T11 (dvdd rise to fall) 0 < T11 <= 10ms */ | |
141 | msleep(10); | |
142 | ||
143 | regulator_disable(ctx->dvdd); | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
148 | static const struct drm_display_mode feiyang_default_mode = { | |
149 | .clock = 55000, | |
150 | ||
151 | .hdisplay = 1024, | |
152 | .hsync_start = 1024 + 310, | |
153 | .hsync_end = 1024 + 310 + 20, | |
154 | .htotal = 1024 + 310 + 20 + 90, | |
155 | ||
156 | .vdisplay = 600, | |
157 | .vsync_start = 600 + 12, | |
158 | .vsync_end = 600 + 12 + 2, | |
159 | .vtotal = 600 + 12 + 2 + 21, | |
160 | .vrefresh = 60, | |
161 | ||
162 | .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, | |
163 | }; | |
164 | ||
165 | static int feiyang_get_modes(struct drm_panel *panel) | |
166 | { | |
167 | struct drm_connector *connector = panel->connector; | |
168 | struct feiyang *ctx = panel_to_feiyang(panel); | |
169 | struct drm_display_mode *mode; | |
170 | ||
171 | mode = drm_mode_duplicate(panel->drm, &feiyang_default_mode); | |
172 | if (!mode) { | |
173 | DRM_DEV_ERROR(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n", | |
174 | feiyang_default_mode.hdisplay, | |
175 | feiyang_default_mode.vdisplay, | |
176 | feiyang_default_mode.vrefresh); | |
177 | return -ENOMEM; | |
178 | } | |
179 | ||
180 | drm_mode_set_name(mode); | |
181 | ||
182 | drm_mode_probed_add(connector, mode); | |
183 | ||
184 | return 1; | |
185 | } | |
186 | ||
187 | static const struct drm_panel_funcs feiyang_funcs = { | |
188 | .disable = feiyang_disable, | |
189 | .unprepare = feiyang_unprepare, | |
190 | .prepare = feiyang_prepare, | |
191 | .enable = feiyang_enable, | |
192 | .get_modes = feiyang_get_modes, | |
193 | }; | |
194 | ||
195 | static int feiyang_dsi_probe(struct mipi_dsi_device *dsi) | |
196 | { | |
197 | struct feiyang *ctx; | |
198 | int ret; | |
199 | ||
200 | ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL); | |
201 | if (!ctx) | |
202 | return -ENOMEM; | |
203 | ||
204 | mipi_dsi_set_drvdata(dsi, ctx); | |
205 | ctx->dsi = dsi; | |
206 | ||
207 | drm_panel_init(&ctx->panel); | |
208 | ctx->panel.dev = &dsi->dev; | |
209 | ctx->panel.funcs = &feiyang_funcs; | |
210 | ||
211 | ctx->dvdd = devm_regulator_get(&dsi->dev, "dvdd"); | |
212 | if (IS_ERR(ctx->dvdd)) { | |
213 | DRM_DEV_ERROR(&dsi->dev, "Couldn't get dvdd regulator\n"); | |
214 | return PTR_ERR(ctx->dvdd); | |
215 | } | |
216 | ||
217 | ctx->avdd = devm_regulator_get(&dsi->dev, "avdd"); | |
218 | if (IS_ERR(ctx->avdd)) { | |
219 | DRM_DEV_ERROR(&dsi->dev, "Couldn't get avdd regulator\n"); | |
220 | return PTR_ERR(ctx->avdd); | |
221 | } | |
222 | ||
223 | ctx->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); | |
224 | if (IS_ERR(ctx->reset)) { | |
225 | DRM_DEV_ERROR(&dsi->dev, "Couldn't get our reset GPIO\n"); | |
226 | return PTR_ERR(ctx->reset); | |
227 | } | |
228 | ||
229 | ctx->backlight = devm_of_find_backlight(&dsi->dev); | |
230 | if (IS_ERR(ctx->backlight)) | |
231 | return PTR_ERR(ctx->backlight); | |
232 | ||
233 | ret = drm_panel_add(&ctx->panel); | |
234 | if (ret < 0) | |
235 | return ret; | |
236 | ||
237 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST; | |
238 | dsi->format = MIPI_DSI_FMT_RGB888; | |
239 | dsi->lanes = 4; | |
240 | ||
241 | return mipi_dsi_attach(dsi); | |
242 | } | |
243 | ||
244 | static int feiyang_dsi_remove(struct mipi_dsi_device *dsi) | |
245 | { | |
246 | struct feiyang *ctx = mipi_dsi_get_drvdata(dsi); | |
247 | ||
248 | mipi_dsi_detach(dsi); | |
249 | drm_panel_remove(&ctx->panel); | |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
254 | static const struct of_device_id feiyang_of_match[] = { | |
255 | { .compatible = "feiyang,fy07024di26a30d", }, | |
256 | { /* sentinel */ } | |
257 | }; | |
258 | MODULE_DEVICE_TABLE(of, feiyang_of_match); | |
259 | ||
260 | static struct mipi_dsi_driver feiyang_driver = { | |
261 | .probe = feiyang_dsi_probe, | |
262 | .remove = feiyang_dsi_remove, | |
263 | .driver = { | |
264 | .name = "feiyang-fy07024di26a30d", | |
265 | .of_match_table = feiyang_of_match, | |
266 | }, | |
267 | }; | |
268 | module_mipi_dsi_driver(feiyang_driver); | |
269 | ||
270 | MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>"); | |
271 | MODULE_DESCRIPTION("Feiyang FY07024DI26A30-D MIPI-DSI LCD panel"); | |
272 | MODULE_LICENSE("GPL"); |