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