1f747473aSTeguh Sobirin // SPDX-License-Identifier: GPL-2.0-only 2f747473aSTeguh Sobirin /* 3f747473aSTeguh Sobirin * Chipone ICNA35XX Driver IC panels driver 4f747473aSTeguh Sobirin * 5f747473aSTeguh Sobirin * Copyright (c) 2025 Teguh Sobirin <teguh@sobir.in> 6f747473aSTeguh Sobirin */ 7f747473aSTeguh Sobirin 8f747473aSTeguh Sobirin #include <linux/backlight.h> 9f747473aSTeguh Sobirin #include <linux/delay.h> 10f747473aSTeguh Sobirin #include <linux/gpio/consumer.h> 11f747473aSTeguh Sobirin #include <linux/module.h> 12f747473aSTeguh Sobirin #include <linux/of.h> 13f747473aSTeguh Sobirin #include <linux/of_graph.h> 14f747473aSTeguh Sobirin #include <linux/regulator/consumer.h> 15f747473aSTeguh Sobirin 16f747473aSTeguh Sobirin #include <video/mipi_display.h> 17f747473aSTeguh Sobirin 18f747473aSTeguh Sobirin #include <drm/display/drm_dsc.h> 19f747473aSTeguh Sobirin #include <drm/display/drm_dsc_helper.h> 20f747473aSTeguh Sobirin #include <drm/drm_connector.h> 21f747473aSTeguh Sobirin #include <drm/drm_crtc.h> 22f747473aSTeguh Sobirin #include <drm/drm_mipi_dsi.h> 23f747473aSTeguh Sobirin #include <drm/drm_modes.h> 24*b5a8b1f2SThierry Reding #include <drm/drm_of.h> 25f747473aSTeguh Sobirin #include <drm/drm_panel.h> 26f747473aSTeguh Sobirin #include <drm/drm_probe_helper.h> 27f747473aSTeguh Sobirin 28f747473aSTeguh Sobirin struct panel_info { 29f747473aSTeguh Sobirin struct drm_panel panel; 30f747473aSTeguh Sobirin struct drm_connector *connector; 31f747473aSTeguh Sobirin struct mipi_dsi_device *dsi; 32f747473aSTeguh Sobirin struct panel_desc *desc; 33f747473aSTeguh Sobirin enum drm_panel_orientation orientation; 34f747473aSTeguh Sobirin 35f747473aSTeguh Sobirin struct gpio_desc *reset_gpio; 36f747473aSTeguh Sobirin struct regulator_bulk_data *supplies; 37f747473aSTeguh Sobirin }; 38f747473aSTeguh Sobirin 39f747473aSTeguh Sobirin struct panel_desc { 40f747473aSTeguh Sobirin unsigned int width_mm; 41f747473aSTeguh Sobirin unsigned int height_mm; 42f747473aSTeguh Sobirin 43f747473aSTeguh Sobirin unsigned int bpc; 44f747473aSTeguh Sobirin unsigned int lanes; 45f747473aSTeguh Sobirin unsigned long mode_flags; 46f747473aSTeguh Sobirin enum mipi_dsi_pixel_format format; 47f747473aSTeguh Sobirin 48f747473aSTeguh Sobirin const struct drm_display_mode *modes; 49f747473aSTeguh Sobirin unsigned int num_modes; 50f747473aSTeguh Sobirin int (*init_sequence)(struct panel_info *pinfo); 51f747473aSTeguh Sobirin 52f747473aSTeguh Sobirin struct drm_dsc_config dsc; 53f747473aSTeguh Sobirin }; 54f747473aSTeguh Sobirin 55f747473aSTeguh Sobirin static const struct regulator_bulk_data panel_supplies[] = { 56f747473aSTeguh Sobirin { .supply = "vdd" }, 57f747473aSTeguh Sobirin { .supply = "vddio" }, 58f747473aSTeguh Sobirin { .supply = "vci" }, 59f747473aSTeguh Sobirin { .supply = "disp" }, 60f747473aSTeguh Sobirin { .supply = "blvdd" }, 61f747473aSTeguh Sobirin }; 62f747473aSTeguh Sobirin 63f747473aSTeguh Sobirin static inline struct panel_info *to_panel_info(struct drm_panel *panel) 64f747473aSTeguh Sobirin { 65f747473aSTeguh Sobirin return container_of(panel, struct panel_info, panel); 66f747473aSTeguh Sobirin } 67f747473aSTeguh Sobirin 68f747473aSTeguh Sobirin static int icna3512_init_sequence(struct panel_info *pinfo) 69f747473aSTeguh Sobirin { 70f747473aSTeguh Sobirin struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; 71f747473aSTeguh Sobirin struct drm_dsc_picture_parameter_set pps; 72f747473aSTeguh Sobirin 73f747473aSTeguh Sobirin pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM; 74f747473aSTeguh Sobirin 75f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5); 76f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A); 77f747473aSTeguh Sobirin 78f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0); 79f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); 80f747473aSTeguh Sobirin 81f747473aSTeguh Sobirin mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 82f747473aSTeguh Sobirin 83f747473aSTeguh Sobirin mipi_dsi_msleep(&dsi_ctx, 120); 84f747473aSTeguh Sobirin 85f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0F); 86f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, 0x22); 87f747473aSTeguh Sobirin 88f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01); 89f747473aSTeguh Sobirin 90f747473aSTeguh Sobirin /* 165 hz */ 91f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x20); 92f747473aSTeguh Sobirin 93f747473aSTeguh Sobirin drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc); 94f747473aSTeguh Sobirin mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); 95f747473aSTeguh Sobirin 96f747473aSTeguh Sobirin mipi_dsi_msleep(&dsi_ctx, 20); 97f747473aSTeguh Sobirin 98f747473aSTeguh Sobirin mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 99f747473aSTeguh Sobirin 100f747473aSTeguh Sobirin return dsi_ctx.accum_err; 101f747473aSTeguh Sobirin } 102f747473aSTeguh Sobirin 103f747473aSTeguh Sobirin static int icna3520_init_sequence(struct panel_info *pinfo) 104f747473aSTeguh Sobirin { 105f747473aSTeguh Sobirin struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; 106f747473aSTeguh Sobirin struct drm_dsc_picture_parameter_set pps; 107f747473aSTeguh Sobirin 108f747473aSTeguh Sobirin pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM; 109f747473aSTeguh Sobirin 110f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5); 111f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A); 112f747473aSTeguh Sobirin 113f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0); 114f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00); 115f747473aSTeguh Sobirin 116f747473aSTeguh Sobirin mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); 117f747473aSTeguh Sobirin 118f747473aSTeguh Sobirin mipi_dsi_msleep(&dsi_ctx, 120); 119f747473aSTeguh Sobirin 120f747473aSTeguh Sobirin /* 120 hz */ 121f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00); 122f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x00); 123f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3, 124f747473aSTeguh Sobirin 0x00, 0xD8, 0x00, 0x1C, 0x00, 0x4C); 125f747473aSTeguh Sobirin 126f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01); 127f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x00); 128f747473aSTeguh Sobirin 129f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0D); 130f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x27); 131f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB6, 0x03); 132f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xBB, 0x01); 133f747473aSTeguh Sobirin mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x24); 134f747473aSTeguh Sobirin 135f747473aSTeguh Sobirin drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc); 136f747473aSTeguh Sobirin mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); 137f747473aSTeguh Sobirin 138f747473aSTeguh Sobirin mipi_dsi_msleep(&dsi_ctx, 20); 139f747473aSTeguh Sobirin 140f747473aSTeguh Sobirin mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); 141f747473aSTeguh Sobirin 142f747473aSTeguh Sobirin return dsi_ctx.accum_err; 143f747473aSTeguh Sobirin } 144f747473aSTeguh Sobirin 145f747473aSTeguh Sobirin static const struct drm_display_mode odin2portal_modes[] = { 146f747473aSTeguh Sobirin { 147f747473aSTeguh Sobirin /* 165Hz */ 148f747473aSTeguh Sobirin .clock = (1080 + 98 + 1 + 23) * (1920 + 20 + 1 + 15) * 165 / 1000, 149f747473aSTeguh Sobirin .hdisplay = 1080, 150f747473aSTeguh Sobirin .hsync_start = 1080 + 98, 151f747473aSTeguh Sobirin .hsync_end = 1080 + 98 + 1, 152f747473aSTeguh Sobirin .htotal = 1080 + 98 + 1 + 23, 153f747473aSTeguh Sobirin .vdisplay = 1920, 154f747473aSTeguh Sobirin .vsync_start = 1920 + 20, 155f747473aSTeguh Sobirin .vsync_end = 1920 + 20 + 1, 156f747473aSTeguh Sobirin .vtotal = 1920 + 20 + 1 + 15, 157f747473aSTeguh Sobirin } 158f747473aSTeguh Sobirin }; 159f747473aSTeguh Sobirin 160f747473aSTeguh Sobirin static const struct drm_display_mode thor_top_modes[] = { 161f747473aSTeguh Sobirin { 162f747473aSTeguh Sobirin /* 120Hz */ 163f747473aSTeguh Sobirin .clock = (1080 + 24 + 1 + 24) * (1920 + 28 + 1 + 28) * 120 / 1000, 164f747473aSTeguh Sobirin .hdisplay = 1080, 165f747473aSTeguh Sobirin .hsync_start = 1080 + 24, 166f747473aSTeguh Sobirin .hsync_end = 1080 + 24 + 1, 167f747473aSTeguh Sobirin .htotal = 1080 + 24 + 1 + 24, 168f747473aSTeguh Sobirin .vdisplay = 1920, 169f747473aSTeguh Sobirin .vsync_start = 1920 + 28, 170f747473aSTeguh Sobirin .vsync_end = 1920 + 28 + 1, 171f747473aSTeguh Sobirin .vtotal = 1920 + 28 + 1 + 28, 172f747473aSTeguh Sobirin } 173f747473aSTeguh Sobirin }; 174f747473aSTeguh Sobirin 175f747473aSTeguh Sobirin static struct panel_desc odin2portal_desc = { 176f747473aSTeguh Sobirin .modes = odin2portal_modes, 177f747473aSTeguh Sobirin .num_modes = ARRAY_SIZE(odin2portal_modes), 178f747473aSTeguh Sobirin .width_mm = 160, 179f747473aSTeguh Sobirin .height_mm = 89, 180f747473aSTeguh Sobirin .bpc = 8, 181f747473aSTeguh Sobirin .lanes = 4, 182f747473aSTeguh Sobirin .format = MIPI_DSI_FMT_RGB888, 183f747473aSTeguh Sobirin .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS | 184f747473aSTeguh Sobirin MIPI_DSI_MODE_LPM, 185f747473aSTeguh Sobirin .init_sequence = icna3512_init_sequence, 186f747473aSTeguh Sobirin .dsc = { 187f747473aSTeguh Sobirin .dsc_version_major = 0x1, 188f747473aSTeguh Sobirin .dsc_version_minor = 0x1, 189f747473aSTeguh Sobirin .slice_height = 20, 190f747473aSTeguh Sobirin .slice_width = 540, 191f747473aSTeguh Sobirin .slice_count = 2, 192f747473aSTeguh Sobirin .bits_per_component = 8, 193f747473aSTeguh Sobirin .bits_per_pixel = 8 << 4, 194f747473aSTeguh Sobirin .block_pred_enable = true, 195f747473aSTeguh Sobirin }, 196f747473aSTeguh Sobirin }; 197f747473aSTeguh Sobirin 198f747473aSTeguh Sobirin static struct panel_desc thor_top_desc = { 199f747473aSTeguh Sobirin .modes = thor_top_modes, 200f747473aSTeguh Sobirin .num_modes = ARRAY_SIZE(thor_top_modes), 201f747473aSTeguh Sobirin .width_mm = 136, 202f747473aSTeguh Sobirin .height_mm = 68, 203f747473aSTeguh Sobirin .bpc = 8, 204f747473aSTeguh Sobirin .lanes = 4, 205f747473aSTeguh Sobirin .format = MIPI_DSI_FMT_RGB888, 206f747473aSTeguh Sobirin .mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS | 207f747473aSTeguh Sobirin MIPI_DSI_MODE_LPM, 208f747473aSTeguh Sobirin .init_sequence = icna3520_init_sequence, 209f747473aSTeguh Sobirin .dsc = { 210f747473aSTeguh Sobirin .dsc_version_major = 0x1, 211f747473aSTeguh Sobirin .dsc_version_minor = 0x1, 212f747473aSTeguh Sobirin .slice_height = 12, 213f747473aSTeguh Sobirin .slice_width = 540, 214f747473aSTeguh Sobirin .slice_count = 2, 215f747473aSTeguh Sobirin .bits_per_component = 8, 216f747473aSTeguh Sobirin .bits_per_pixel = 8 << 4, 217f747473aSTeguh Sobirin .block_pred_enable = true, 218f747473aSTeguh Sobirin }, 219f747473aSTeguh Sobirin }; 220f747473aSTeguh Sobirin 221f747473aSTeguh Sobirin static void icna35xx_reset(struct panel_info *pinfo) 222f747473aSTeguh Sobirin { 223f747473aSTeguh Sobirin gpiod_set_value_cansleep(pinfo->reset_gpio, 0); 224f747473aSTeguh Sobirin usleep_range(20000, 21000); 225f747473aSTeguh Sobirin gpiod_set_value_cansleep(pinfo->reset_gpio, 1); 226f747473aSTeguh Sobirin usleep_range(20000, 21000); 227f747473aSTeguh Sobirin gpiod_set_value_cansleep(pinfo->reset_gpio, 0); 228f747473aSTeguh Sobirin usleep_range(20000, 21000); 229f747473aSTeguh Sobirin } 230f747473aSTeguh Sobirin 231f747473aSTeguh Sobirin static int icna35xx_prepare(struct drm_panel *panel) 232f747473aSTeguh Sobirin { 233f747473aSTeguh Sobirin struct panel_info *pinfo = to_panel_info(panel); 234f747473aSTeguh Sobirin int ret; 235f747473aSTeguh Sobirin 236f747473aSTeguh Sobirin ret = regulator_bulk_enable(ARRAY_SIZE(panel_supplies), pinfo->supplies); 237f747473aSTeguh Sobirin if (ret < 0) { 238f747473aSTeguh Sobirin dev_err(panel->dev, "failed to enable regulators: %d\n", ret); 239f747473aSTeguh Sobirin return ret; 240f747473aSTeguh Sobirin } 241f747473aSTeguh Sobirin 242f747473aSTeguh Sobirin icna35xx_reset(pinfo); 243f747473aSTeguh Sobirin 244f747473aSTeguh Sobirin ret = pinfo->desc->init_sequence(pinfo); 245f747473aSTeguh Sobirin if (ret < 0) { 246f747473aSTeguh Sobirin regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies); 247f747473aSTeguh Sobirin dev_err(panel->dev, "failed to initialize panel: %d\n", ret); 248f747473aSTeguh Sobirin return ret; 249f747473aSTeguh Sobirin } 250f747473aSTeguh Sobirin 251f747473aSTeguh Sobirin return 0; 252f747473aSTeguh Sobirin } 253f747473aSTeguh Sobirin 254f747473aSTeguh Sobirin static int icna35xx_disable(struct drm_panel *panel) 255f747473aSTeguh Sobirin { 256f747473aSTeguh Sobirin struct panel_info *pinfo = to_panel_info(panel); 257f747473aSTeguh Sobirin struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi }; 258f747473aSTeguh Sobirin 259f747473aSTeguh Sobirin pinfo->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 260f747473aSTeguh Sobirin 261f747473aSTeguh Sobirin mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); 262f747473aSTeguh Sobirin mipi_dsi_msleep(&dsi_ctx, 50); 263f747473aSTeguh Sobirin mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); 264f747473aSTeguh Sobirin mipi_dsi_msleep(&dsi_ctx, 120); 265f747473aSTeguh Sobirin 266f747473aSTeguh Sobirin return dsi_ctx.accum_err; 267f747473aSTeguh Sobirin } 268f747473aSTeguh Sobirin 269f747473aSTeguh Sobirin static int icna35xx_unprepare(struct drm_panel *panel) 270f747473aSTeguh Sobirin { 271f747473aSTeguh Sobirin struct panel_info *pinfo = to_panel_info(panel); 272f747473aSTeguh Sobirin 273f747473aSTeguh Sobirin gpiod_set_value_cansleep(pinfo->reset_gpio, 1); 274f747473aSTeguh Sobirin regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies); 275f747473aSTeguh Sobirin 276f747473aSTeguh Sobirin return 0; 277f747473aSTeguh Sobirin } 278f747473aSTeguh Sobirin 279f747473aSTeguh Sobirin static int icna35xx_get_modes(struct drm_panel *panel, 280f747473aSTeguh Sobirin struct drm_connector *connector) 281f747473aSTeguh Sobirin { 282f747473aSTeguh Sobirin struct panel_info *pinfo = to_panel_info(panel); 283f747473aSTeguh Sobirin 284f747473aSTeguh Sobirin return drm_connector_helper_get_modes_fixed(connector, pinfo->desc->modes); 285f747473aSTeguh Sobirin } 286f747473aSTeguh Sobirin 287f747473aSTeguh Sobirin static enum drm_panel_orientation icna35xx_get_orientation(struct drm_panel *panel) 288f747473aSTeguh Sobirin { 289f747473aSTeguh Sobirin struct panel_info *pinfo = to_panel_info(panel); 290f747473aSTeguh Sobirin 291f747473aSTeguh Sobirin return pinfo->orientation; 292f747473aSTeguh Sobirin } 293f747473aSTeguh Sobirin 294f747473aSTeguh Sobirin static const struct drm_panel_funcs icna35xx_panel_funcs = { 295f747473aSTeguh Sobirin .disable = icna35xx_disable, 296f747473aSTeguh Sobirin .prepare = icna35xx_prepare, 297f747473aSTeguh Sobirin .unprepare = icna35xx_unprepare, 298f747473aSTeguh Sobirin .get_modes = icna35xx_get_modes, 299f747473aSTeguh Sobirin .get_orientation = icna35xx_get_orientation, 300f747473aSTeguh Sobirin }; 301f747473aSTeguh Sobirin 302f747473aSTeguh Sobirin static int icna35xx_bl_update_status(struct backlight_device *bl) 303f747473aSTeguh Sobirin { 304f747473aSTeguh Sobirin struct mipi_dsi_device *dsi = bl_get_data(bl); 305f747473aSTeguh Sobirin u16 brightness = backlight_get_brightness(bl); 306f747473aSTeguh Sobirin int ret; 307f747473aSTeguh Sobirin 308f747473aSTeguh Sobirin dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 309f747473aSTeguh Sobirin 310f747473aSTeguh Sobirin ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness); 311f747473aSTeguh Sobirin 312f747473aSTeguh Sobirin dsi->mode_flags |= MIPI_DSI_MODE_LPM; 313f747473aSTeguh Sobirin 314f747473aSTeguh Sobirin return ret; 315f747473aSTeguh Sobirin } 316f747473aSTeguh Sobirin 317f747473aSTeguh Sobirin static int icna35xx_bl_get_brightness(struct backlight_device *bl) 318f747473aSTeguh Sobirin { 319f747473aSTeguh Sobirin struct mipi_dsi_device *dsi = bl_get_data(bl); 320f747473aSTeguh Sobirin u16 brightness; 321f747473aSTeguh Sobirin int ret; 322f747473aSTeguh Sobirin 323f747473aSTeguh Sobirin dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 324f747473aSTeguh Sobirin 325f747473aSTeguh Sobirin ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness); 326f747473aSTeguh Sobirin 327f747473aSTeguh Sobirin dsi->mode_flags |= MIPI_DSI_MODE_LPM; 328f747473aSTeguh Sobirin 329f747473aSTeguh Sobirin return ret < 0 ? ret : brightness; 330f747473aSTeguh Sobirin } 331f747473aSTeguh Sobirin 332f747473aSTeguh Sobirin static const struct backlight_ops icna35xx_bl_ops = { 333f747473aSTeguh Sobirin .update_status = icna35xx_bl_update_status, 334f747473aSTeguh Sobirin .get_brightness = icna35xx_bl_get_brightness, 335f747473aSTeguh Sobirin }; 336f747473aSTeguh Sobirin 337f747473aSTeguh Sobirin static struct backlight_device *icna35xx_create_backlight(struct mipi_dsi_device *dsi) 338f747473aSTeguh Sobirin { 339f747473aSTeguh Sobirin struct device *dev = &dsi->dev; 340f747473aSTeguh Sobirin const struct backlight_properties props = { 341f747473aSTeguh Sobirin .type = BACKLIGHT_RAW, 342f747473aSTeguh Sobirin .brightness = 4096, 343f747473aSTeguh Sobirin .max_brightness = 4096, 344f747473aSTeguh Sobirin }; 345f747473aSTeguh Sobirin 346f747473aSTeguh Sobirin return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 347f747473aSTeguh Sobirin &icna35xx_bl_ops, &props); 348f747473aSTeguh Sobirin } 349f747473aSTeguh Sobirin 350f747473aSTeguh Sobirin static int icna35xx_probe(struct mipi_dsi_device *dsi) 351f747473aSTeguh Sobirin { 352f747473aSTeguh Sobirin struct device *dev = &dsi->dev; 353f747473aSTeguh Sobirin struct panel_info *pinfo; 354f747473aSTeguh Sobirin int ret; 355f747473aSTeguh Sobirin 356f747473aSTeguh Sobirin pinfo = devm_drm_panel_alloc(dev, __typeof(*pinfo), panel, 357f747473aSTeguh Sobirin &icna35xx_panel_funcs, 358f747473aSTeguh Sobirin DRM_MODE_CONNECTOR_DSI); 359f747473aSTeguh Sobirin if (IS_ERR(pinfo)) 360f747473aSTeguh Sobirin return PTR_ERR(pinfo); 361f747473aSTeguh Sobirin 362f747473aSTeguh Sobirin ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(panel_supplies), 363f747473aSTeguh Sobirin panel_supplies, &pinfo->supplies); 364f747473aSTeguh Sobirin if (ret < 0) 365f747473aSTeguh Sobirin return dev_err_probe(dev, ret, "Failed to get regulators\n"); 366f747473aSTeguh Sobirin 367f747473aSTeguh Sobirin pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); 368f747473aSTeguh Sobirin if (IS_ERR(pinfo->reset_gpio)) 369f747473aSTeguh Sobirin return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), "failed to get reset gpio\n"); 370f747473aSTeguh Sobirin 371f747473aSTeguh Sobirin pinfo->desc = (struct panel_desc *)of_device_get_match_data(dev); 372f747473aSTeguh Sobirin if (!pinfo->desc) 373f747473aSTeguh Sobirin return -ENODEV; 374f747473aSTeguh Sobirin 375f747473aSTeguh Sobirin pinfo->dsi = dsi; 376f747473aSTeguh Sobirin mipi_dsi_set_drvdata(dsi, pinfo); 377f747473aSTeguh Sobirin 378*b5a8b1f2SThierry Reding ret = drm_of_get_panel_orientation(dev->of_node, &pinfo->orientation); 379f747473aSTeguh Sobirin if (ret < 0) { 380f747473aSTeguh Sobirin dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); 381f747473aSTeguh Sobirin return ret; 382f747473aSTeguh Sobirin } 383f747473aSTeguh Sobirin 384f747473aSTeguh Sobirin pinfo->panel.prepare_prev_first = true; 385f747473aSTeguh Sobirin 386f747473aSTeguh Sobirin pinfo->panel.backlight = icna35xx_create_backlight(dsi); 387f747473aSTeguh Sobirin if (IS_ERR(pinfo->panel.backlight)) 388f747473aSTeguh Sobirin return dev_err_probe(dev, PTR_ERR(pinfo->panel.backlight), 389f747473aSTeguh Sobirin "Failed to create backlight\n"); 390f747473aSTeguh Sobirin 391f747473aSTeguh Sobirin ret = devm_drm_panel_add(dev, &pinfo->panel); 392f747473aSTeguh Sobirin if (ret) 393f747473aSTeguh Sobirin return ret; 394f747473aSTeguh Sobirin 395f747473aSTeguh Sobirin pinfo->dsi->lanes = pinfo->desc->lanes; 396f747473aSTeguh Sobirin pinfo->dsi->format = pinfo->desc->format; 397f747473aSTeguh Sobirin pinfo->dsi->mode_flags = pinfo->desc->mode_flags; 398f747473aSTeguh Sobirin pinfo->dsi->dsc = &pinfo->desc->dsc; 399f747473aSTeguh Sobirin 400f747473aSTeguh Sobirin return devm_mipi_dsi_attach(dev, dsi); 401f747473aSTeguh Sobirin } 402f747473aSTeguh Sobirin 403f747473aSTeguh Sobirin static const struct of_device_id icna35xx_of_match[] = { 404f747473aSTeguh Sobirin { .compatible = "ayaneo,pocketds-panel-top", .data = &odin2portal_desc }, 405f747473aSTeguh Sobirin { .compatible = "ayntec,odin2portal-panel", .data = &odin2portal_desc }, 406f747473aSTeguh Sobirin { .compatible = "ayntec,odin3-panel", .data = &thor_top_desc }, 407f747473aSTeguh Sobirin { .compatible = "ayntec,thor-panel-top", .data = &thor_top_desc }, 408f747473aSTeguh Sobirin { /* sentinel */ } 409f747473aSTeguh Sobirin }; 410f747473aSTeguh Sobirin MODULE_DEVICE_TABLE(of, icna35xx_of_match); 411f747473aSTeguh Sobirin 412f747473aSTeguh Sobirin static struct mipi_dsi_driver icna35xx_driver = { 413f747473aSTeguh Sobirin .probe = icna35xx_probe, 414f747473aSTeguh Sobirin .driver = { 415f747473aSTeguh Sobirin .name = "panel-chipone-icna35xx", 416f747473aSTeguh Sobirin .of_match_table = icna35xx_of_match, 417f747473aSTeguh Sobirin }, 418f747473aSTeguh Sobirin }; 419f747473aSTeguh Sobirin module_mipi_dsi_driver(icna35xx_driver); 420f747473aSTeguh Sobirin 421f747473aSTeguh Sobirin MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>"); 422f747473aSTeguh Sobirin MODULE_DESCRIPTION("DRM driver for Chipone ICNA35XX based MIPI DSI panels"); 423f747473aSTeguh Sobirin MODULE_LICENSE("GPL"); 424