162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2019, The Linux Foundation. All rights reserved. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/delay.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 962306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1062306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <video/mipi_display.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h> 1562306a36Sopenharmony_ci#include <drm/drm_modes.h> 1662306a36Sopenharmony_ci#include <drm/drm_panel.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistruct visionox_rm69299 { 1962306a36Sopenharmony_ci struct drm_panel panel; 2062306a36Sopenharmony_ci struct regulator_bulk_data supplies[2]; 2162306a36Sopenharmony_ci struct gpio_desc *reset_gpio; 2262306a36Sopenharmony_ci struct mipi_dsi_device *dsi; 2362306a36Sopenharmony_ci bool prepared; 2462306a36Sopenharmony_ci bool enabled; 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic inline struct visionox_rm69299 *panel_to_ctx(struct drm_panel *panel) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci return container_of(panel, struct visionox_rm69299, panel); 3062306a36Sopenharmony_ci} 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic int visionox_rm69299_power_on(struct visionox_rm69299 *ctx) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci int ret; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 3762306a36Sopenharmony_ci if (ret < 0) 3862306a36Sopenharmony_ci return ret; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci /* 4162306a36Sopenharmony_ci * Reset sequence of visionox panel requires the panel to be 4262306a36Sopenharmony_ci * out of reset for 10ms, followed by being held in reset 4362306a36Sopenharmony_ci * for 10ms and then out again 4462306a36Sopenharmony_ci */ 4562306a36Sopenharmony_ci gpiod_set_value(ctx->reset_gpio, 1); 4662306a36Sopenharmony_ci usleep_range(10000, 20000); 4762306a36Sopenharmony_ci gpiod_set_value(ctx->reset_gpio, 0); 4862306a36Sopenharmony_ci usleep_range(10000, 20000); 4962306a36Sopenharmony_ci gpiod_set_value(ctx->reset_gpio, 1); 5062306a36Sopenharmony_ci usleep_range(10000, 20000); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic int visionox_rm69299_power_off(struct visionox_rm69299 *ctx) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci gpiod_set_value(ctx->reset_gpio, 0); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic int visionox_rm69299_unprepare(struct drm_panel *panel) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci struct visionox_rm69299 *ctx = panel_to_ctx(panel); 6562306a36Sopenharmony_ci int ret; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci ctx->dsi->mode_flags = 0; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(ctx->dsi, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0); 7062306a36Sopenharmony_ci if (ret < 0) 7162306a36Sopenharmony_ci dev_err(ctx->panel.dev, "set_display_off cmd failed ret = %d\n", ret); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci /* 120ms delay required here as per DCS spec */ 7462306a36Sopenharmony_ci msleep(120); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(ctx->dsi, MIPI_DCS_ENTER_SLEEP_MODE, NULL, 0); 7762306a36Sopenharmony_ci if (ret < 0) { 7862306a36Sopenharmony_ci dev_err(ctx->panel.dev, "enter_sleep cmd failed ret = %d\n", ret); 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci ret = visionox_rm69299_power_off(ctx); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci ctx->prepared = false; 8462306a36Sopenharmony_ci return ret; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic int visionox_rm69299_prepare(struct drm_panel *panel) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci struct visionox_rm69299 *ctx = panel_to_ctx(panel); 9062306a36Sopenharmony_ci int ret; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (ctx->prepared) 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci ret = visionox_rm69299_power_on(ctx); 9662306a36Sopenharmony_ci if (ret < 0) 9762306a36Sopenharmony_ci return ret; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0xfe, 0x00 }, 2); 10262306a36Sopenharmony_ci if (ret < 0) { 10362306a36Sopenharmony_ci dev_err(ctx->panel.dev, "cmd set tx 0 failed, ret = %d\n", ret); 10462306a36Sopenharmony_ci goto power_off; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0xc2, 0x08 }, 2); 10862306a36Sopenharmony_ci if (ret < 0) { 10962306a36Sopenharmony_ci dev_err(ctx->panel.dev, "cmd set tx 1 failed, ret = %d\n", ret); 11062306a36Sopenharmony_ci goto power_off; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0x35, 0x00 }, 2); 11462306a36Sopenharmony_ci if (ret < 0) { 11562306a36Sopenharmony_ci dev_err(ctx->panel.dev, "cmd set tx 2 failed, ret = %d\n", ret); 11662306a36Sopenharmony_ci goto power_off; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci ret = mipi_dsi_dcs_write_buffer(ctx->dsi, (u8[]) { 0x51, 0xff }, 2); 12062306a36Sopenharmony_ci if (ret < 0) { 12162306a36Sopenharmony_ci dev_err(ctx->panel.dev, "cmd set tx 3 failed, ret = %d\n", ret); 12262306a36Sopenharmony_ci goto power_off; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(ctx->dsi, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0); 12662306a36Sopenharmony_ci if (ret < 0) { 12762306a36Sopenharmony_ci dev_err(ctx->panel.dev, "exit_sleep_mode cmd failed ret = %d\n", ret); 12862306a36Sopenharmony_ci goto power_off; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* Per DSI spec wait 120ms after sending exit sleep DCS command */ 13262306a36Sopenharmony_ci msleep(120); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(ctx->dsi, MIPI_DCS_SET_DISPLAY_ON, NULL, 0); 13562306a36Sopenharmony_ci if (ret < 0) { 13662306a36Sopenharmony_ci dev_err(ctx->panel.dev, "set_display_on cmd failed ret = %d\n", ret); 13762306a36Sopenharmony_ci goto power_off; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* Per DSI spec wait 120ms after sending set_display_on DCS command */ 14162306a36Sopenharmony_ci msleep(120); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci ctx->prepared = true; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci return 0; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cipower_off: 14862306a36Sopenharmony_ci return ret; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic const struct drm_display_mode visionox_rm69299_1080x2248_60hz = { 15262306a36Sopenharmony_ci .name = "1080x2248", 15362306a36Sopenharmony_ci .clock = 158695, 15462306a36Sopenharmony_ci .hdisplay = 1080, 15562306a36Sopenharmony_ci .hsync_start = 1080 + 26, 15662306a36Sopenharmony_ci .hsync_end = 1080 + 26 + 2, 15762306a36Sopenharmony_ci .htotal = 1080 + 26 + 2 + 36, 15862306a36Sopenharmony_ci .vdisplay = 2248, 15962306a36Sopenharmony_ci .vsync_start = 2248 + 56, 16062306a36Sopenharmony_ci .vsync_end = 2248 + 56 + 4, 16162306a36Sopenharmony_ci .vtotal = 2248 + 56 + 4 + 4, 16262306a36Sopenharmony_ci .flags = 0, 16362306a36Sopenharmony_ci}; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic int visionox_rm69299_get_modes(struct drm_panel *panel, 16662306a36Sopenharmony_ci struct drm_connector *connector) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct visionox_rm69299 *ctx = panel_to_ctx(panel); 16962306a36Sopenharmony_ci struct drm_display_mode *mode; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, 17262306a36Sopenharmony_ci &visionox_rm69299_1080x2248_60hz); 17362306a36Sopenharmony_ci if (!mode) { 17462306a36Sopenharmony_ci dev_err(ctx->panel.dev, "failed to create a new display mode\n"); 17562306a36Sopenharmony_ci return 0; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci connector->display_info.width_mm = 74; 17962306a36Sopenharmony_ci connector->display_info.height_mm = 131; 18062306a36Sopenharmony_ci mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 18162306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci return 1; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic const struct drm_panel_funcs visionox_rm69299_drm_funcs = { 18762306a36Sopenharmony_ci .unprepare = visionox_rm69299_unprepare, 18862306a36Sopenharmony_ci .prepare = visionox_rm69299_prepare, 18962306a36Sopenharmony_ci .get_modes = visionox_rm69299_get_modes, 19062306a36Sopenharmony_ci}; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic int visionox_rm69299_probe(struct mipi_dsi_device *dsi) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci struct device *dev = &dsi->dev; 19562306a36Sopenharmony_ci struct visionox_rm69299 *ctx; 19662306a36Sopenharmony_ci int ret; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 19962306a36Sopenharmony_ci if (!ctx) 20062306a36Sopenharmony_ci return -ENOMEM; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci mipi_dsi_set_drvdata(dsi, ctx); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci ctx->panel.dev = dev; 20562306a36Sopenharmony_ci ctx->dsi = dsi; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci ctx->supplies[0].supply = "vdda"; 20862306a36Sopenharmony_ci ctx->supplies[1].supply = "vdd3p3"; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci ret = devm_regulator_bulk_get(ctx->panel.dev, ARRAY_SIZE(ctx->supplies), 21162306a36Sopenharmony_ci ctx->supplies); 21262306a36Sopenharmony_ci if (ret < 0) 21362306a36Sopenharmony_ci return ret; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci ctx->reset_gpio = devm_gpiod_get(ctx->panel.dev, 21662306a36Sopenharmony_ci "reset", GPIOD_OUT_LOW); 21762306a36Sopenharmony_ci if (IS_ERR(ctx->reset_gpio)) { 21862306a36Sopenharmony_ci dev_err(dev, "cannot get reset gpio %ld\n", PTR_ERR(ctx->reset_gpio)); 21962306a36Sopenharmony_ci return PTR_ERR(ctx->reset_gpio); 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci drm_panel_init(&ctx->panel, dev, &visionox_rm69299_drm_funcs, 22362306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DSI); 22462306a36Sopenharmony_ci ctx->panel.dev = dev; 22562306a36Sopenharmony_ci ctx->panel.funcs = &visionox_rm69299_drm_funcs; 22662306a36Sopenharmony_ci drm_panel_add(&ctx->panel); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci dsi->lanes = 4; 22962306a36Sopenharmony_ci dsi->format = MIPI_DSI_FMT_RGB888; 23062306a36Sopenharmony_ci dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM | 23162306a36Sopenharmony_ci MIPI_DSI_CLOCK_NON_CONTINUOUS; 23262306a36Sopenharmony_ci ret = mipi_dsi_attach(dsi); 23362306a36Sopenharmony_ci if (ret < 0) { 23462306a36Sopenharmony_ci dev_err(dev, "dsi attach failed ret = %d\n", ret); 23562306a36Sopenharmony_ci goto err_dsi_attach; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci ret = regulator_set_load(ctx->supplies[0].consumer, 32000); 23962306a36Sopenharmony_ci if (ret) { 24062306a36Sopenharmony_ci dev_err(dev, "regulator set load failed for vdda supply ret = %d\n", ret); 24162306a36Sopenharmony_ci goto err_set_load; 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci ret = regulator_set_load(ctx->supplies[1].consumer, 13200); 24562306a36Sopenharmony_ci if (ret) { 24662306a36Sopenharmony_ci dev_err(dev, "regulator set load failed for vdd3p3 supply ret = %d\n", ret); 24762306a36Sopenharmony_ci goto err_set_load; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci return 0; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cierr_set_load: 25362306a36Sopenharmony_ci mipi_dsi_detach(dsi); 25462306a36Sopenharmony_cierr_dsi_attach: 25562306a36Sopenharmony_ci drm_panel_remove(&ctx->panel); 25662306a36Sopenharmony_ci return ret; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic void visionox_rm69299_remove(struct mipi_dsi_device *dsi) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci struct visionox_rm69299 *ctx = mipi_dsi_get_drvdata(dsi); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci mipi_dsi_detach(ctx->dsi); 26462306a36Sopenharmony_ci mipi_dsi_device_unregister(ctx->dsi); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci drm_panel_remove(&ctx->panel); 26762306a36Sopenharmony_ci} 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_cistatic const struct of_device_id visionox_rm69299_of_match[] = { 27062306a36Sopenharmony_ci { .compatible = "visionox,rm69299-1080p-display", }, 27162306a36Sopenharmony_ci { /* sentinel */ } 27262306a36Sopenharmony_ci}; 27362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, visionox_rm69299_of_match); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_cistatic struct mipi_dsi_driver visionox_rm69299_driver = { 27662306a36Sopenharmony_ci .driver = { 27762306a36Sopenharmony_ci .name = "panel-visionox-rm69299", 27862306a36Sopenharmony_ci .of_match_table = visionox_rm69299_of_match, 27962306a36Sopenharmony_ci }, 28062306a36Sopenharmony_ci .probe = visionox_rm69299_probe, 28162306a36Sopenharmony_ci .remove = visionox_rm69299_remove, 28262306a36Sopenharmony_ci}; 28362306a36Sopenharmony_cimodule_mipi_dsi_driver(visionox_rm69299_driver); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ciMODULE_DESCRIPTION("Visionox RM69299 DSI Panel Driver"); 28662306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 287