1*9e0f93f7SMaxim Schwalm // SPDX-License-Identifier: GPL-2.0 2*9e0f93f7SMaxim Schwalm 3*9e0f93f7SMaxim Schwalm #include <linux/array_size.h> 4*9e0f93f7SMaxim Schwalm #include <linux/delay.h> 5*9e0f93f7SMaxim Schwalm #include <linux/err.h> 6*9e0f93f7SMaxim Schwalm #include <linux/gpio/consumer.h> 7*9e0f93f7SMaxim Schwalm #include <linux/mod_devicetable.h> 8*9e0f93f7SMaxim Schwalm #include <linux/module.h> 9*9e0f93f7SMaxim Schwalm #include <linux/property.h> 10*9e0f93f7SMaxim Schwalm #include <linux/regulator/consumer.h> 11*9e0f93f7SMaxim Schwalm 12*9e0f93f7SMaxim Schwalm #include <video/mipi_display.h> 13*9e0f93f7SMaxim Schwalm 14*9e0f93f7SMaxim Schwalm #include <drm/drm_mipi_dsi.h> 15*9e0f93f7SMaxim Schwalm #include <drm/drm_modes.h> 16*9e0f93f7SMaxim Schwalm #include <drm/drm_panel.h> 17*9e0f93f7SMaxim Schwalm 18*9e0f93f7SMaxim Schwalm #define R69328_MACP 0xb0 /* Manufacturer Access CMD Protect */ 19*9e0f93f7SMaxim Schwalm #define R69328_MACP_ON 0x03 20*9e0f93f7SMaxim Schwalm #define R69328_MACP_OFF 0x04 21*9e0f93f7SMaxim Schwalm 22*9e0f93f7SMaxim Schwalm #define R69328_GAMMA_SET_A 0xc8 /* Gamma Setting A */ 23*9e0f93f7SMaxim Schwalm #define R69328_GAMMA_SET_B 0xc9 /* Gamma Setting B */ 24*9e0f93f7SMaxim Schwalm #define R69328_GAMMA_SET_C 0xca /* Gamma Setting C */ 25*9e0f93f7SMaxim Schwalm 26*9e0f93f7SMaxim Schwalm #define R69328_POWER_SET 0xd1 27*9e0f93f7SMaxim Schwalm 28*9e0f93f7SMaxim Schwalm struct renesas_r69328 { 29*9e0f93f7SMaxim Schwalm struct drm_panel panel; 30*9e0f93f7SMaxim Schwalm struct mipi_dsi_device *dsi; 31*9e0f93f7SMaxim Schwalm 32*9e0f93f7SMaxim Schwalm struct regulator *vdd_supply; 33*9e0f93f7SMaxim Schwalm struct regulator *vddio_supply; 34*9e0f93f7SMaxim Schwalm struct gpio_desc *reset_gpio; 35*9e0f93f7SMaxim Schwalm 36*9e0f93f7SMaxim Schwalm bool prepared; 37*9e0f93f7SMaxim Schwalm }; 38*9e0f93f7SMaxim Schwalm 39*9e0f93f7SMaxim Schwalm static inline struct renesas_r69328 *to_renesas_r69328(struct drm_panel *panel) 40*9e0f93f7SMaxim Schwalm { 41*9e0f93f7SMaxim Schwalm return container_of(panel, struct renesas_r69328, panel); 42*9e0f93f7SMaxim Schwalm } 43*9e0f93f7SMaxim Schwalm 44*9e0f93f7SMaxim Schwalm static void renesas_r69328_reset(struct renesas_r69328 *priv) 45*9e0f93f7SMaxim Schwalm { 46*9e0f93f7SMaxim Schwalm gpiod_set_value_cansleep(priv->reset_gpio, 1); 47*9e0f93f7SMaxim Schwalm usleep_range(10000, 11000); 48*9e0f93f7SMaxim Schwalm gpiod_set_value_cansleep(priv->reset_gpio, 0); 49*9e0f93f7SMaxim Schwalm usleep_range(2000, 3000); 50*9e0f93f7SMaxim Schwalm } 51*9e0f93f7SMaxim Schwalm 52*9e0f93f7SMaxim Schwalm static int renesas_r69328_prepare(struct drm_panel *panel) 53*9e0f93f7SMaxim Schwalm { 54*9e0f93f7SMaxim Schwalm struct renesas_r69328 *priv = to_renesas_r69328(panel); 55*9e0f93f7SMaxim Schwalm struct device *dev = &priv->dsi->dev; 56*9e0f93f7SMaxim Schwalm int ret; 57*9e0f93f7SMaxim Schwalm 58*9e0f93f7SMaxim Schwalm if (priv->prepared) 59*9e0f93f7SMaxim Schwalm return 0; 60*9e0f93f7SMaxim Schwalm 61*9e0f93f7SMaxim Schwalm ret = regulator_enable(priv->vdd_supply); 62*9e0f93f7SMaxim Schwalm if (ret) { 63*9e0f93f7SMaxim Schwalm dev_err(dev, "failed to enable vdd power supply\n"); 64*9e0f93f7SMaxim Schwalm return ret; 65*9e0f93f7SMaxim Schwalm } 66*9e0f93f7SMaxim Schwalm 67*9e0f93f7SMaxim Schwalm usleep_range(10000, 11000); 68*9e0f93f7SMaxim Schwalm 69*9e0f93f7SMaxim Schwalm ret = regulator_enable(priv->vddio_supply); 70*9e0f93f7SMaxim Schwalm if (ret < 0) { 71*9e0f93f7SMaxim Schwalm dev_err(dev, "failed to enable vddio power supply\n"); 72*9e0f93f7SMaxim Schwalm return ret; 73*9e0f93f7SMaxim Schwalm } 74*9e0f93f7SMaxim Schwalm 75*9e0f93f7SMaxim Schwalm usleep_range(10000, 11000); 76*9e0f93f7SMaxim Schwalm 77*9e0f93f7SMaxim Schwalm renesas_r69328_reset(priv); 78*9e0f93f7SMaxim Schwalm 79*9e0f93f7SMaxim Schwalm priv->prepared = true; 80*9e0f93f7SMaxim Schwalm return 0; 81*9e0f93f7SMaxim Schwalm } 82*9e0f93f7SMaxim Schwalm 83*9e0f93f7SMaxim Schwalm static int renesas_r69328_enable(struct drm_panel *panel) 84*9e0f93f7SMaxim Schwalm { 85*9e0f93f7SMaxim Schwalm struct renesas_r69328 *priv = to_renesas_r69328(panel); 86*9e0f93f7SMaxim Schwalm struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; 87*9e0f93f7SMaxim Schwalm 88*9e0f93f7SMaxim Schwalm /* Set address mode */ 89*9e0f93f7SMaxim Schwalm mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x00); 90*9e0f93f7SMaxim Schwalm mipi_dsi_dcs_set_pixel_format_multi(&ctx, MIPI_DCS_PIXEL_FMT_24BIT << 4); 91*9e0f93f7SMaxim Schwalm mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); 92*9e0f93f7SMaxim Schwalm 93*9e0f93f7SMaxim Schwalm mipi_dsi_msleep(&ctx, 100); 94*9e0f93f7SMaxim Schwalm 95*9e0f93f7SMaxim Schwalm /* MACP Off */ 96*9e0f93f7SMaxim Schwalm mipi_dsi_generic_write_seq_multi(&ctx, R69328_MACP, R69328_MACP_OFF); 97*9e0f93f7SMaxim Schwalm 98*9e0f93f7SMaxim Schwalm mipi_dsi_generic_write_seq_multi(&ctx, R69328_POWER_SET, 0x14, 0x1d, 99*9e0f93f7SMaxim Schwalm 0x21, 0x67, 0x11, 0x9a); 100*9e0f93f7SMaxim Schwalm 101*9e0f93f7SMaxim Schwalm mipi_dsi_generic_write_seq_multi(&ctx, R69328_GAMMA_SET_A, 0x00, 0x1a, 102*9e0f93f7SMaxim Schwalm 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, 103*9e0f93f7SMaxim Schwalm 0x11, 0x18, 0x1e, 0x1c, 0x00, 0x00, 0x1a, 104*9e0f93f7SMaxim Schwalm 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, 105*9e0f93f7SMaxim Schwalm 0x11, 0x18, 0x1e, 0x1c, 0x00); 106*9e0f93f7SMaxim Schwalm 107*9e0f93f7SMaxim Schwalm mipi_dsi_generic_write_seq_multi(&ctx, R69328_GAMMA_SET_B, 0x00, 0x1a, 108*9e0f93f7SMaxim Schwalm 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, 109*9e0f93f7SMaxim Schwalm 0x11, 0x18, 0x1e, 0x1c, 0x00, 0x00, 0x1a, 110*9e0f93f7SMaxim Schwalm 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, 111*9e0f93f7SMaxim Schwalm 0x11, 0x18, 0x1e, 0x1c, 0x00); 112*9e0f93f7SMaxim Schwalm 113*9e0f93f7SMaxim Schwalm mipi_dsi_generic_write_seq_multi(&ctx, R69328_GAMMA_SET_C, 0x00, 0x1a, 114*9e0f93f7SMaxim Schwalm 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, 115*9e0f93f7SMaxim Schwalm 0x11, 0x18, 0x1e, 0x1c, 0x00, 0x00, 0x1a, 116*9e0f93f7SMaxim Schwalm 0x20, 0x28, 0x25, 0x24, 0x26, 0x15, 0x13, 117*9e0f93f7SMaxim Schwalm 0x11, 0x18, 0x1e, 0x1c, 0x00); 118*9e0f93f7SMaxim Schwalm 119*9e0f93f7SMaxim Schwalm /* MACP On */ 120*9e0f93f7SMaxim Schwalm mipi_dsi_generic_write_seq_multi(&ctx, R69328_MACP, R69328_MACP_ON); 121*9e0f93f7SMaxim Schwalm 122*9e0f93f7SMaxim Schwalm mipi_dsi_dcs_set_display_on_multi(&ctx); 123*9e0f93f7SMaxim Schwalm mipi_dsi_msleep(&ctx, 50); 124*9e0f93f7SMaxim Schwalm 125*9e0f93f7SMaxim Schwalm return 0; 126*9e0f93f7SMaxim Schwalm } 127*9e0f93f7SMaxim Schwalm 128*9e0f93f7SMaxim Schwalm static int renesas_r69328_disable(struct drm_panel *panel) 129*9e0f93f7SMaxim Schwalm { 130*9e0f93f7SMaxim Schwalm struct renesas_r69328 *priv = to_renesas_r69328(panel); 131*9e0f93f7SMaxim Schwalm struct mipi_dsi_multi_context ctx = { .dsi = priv->dsi }; 132*9e0f93f7SMaxim Schwalm 133*9e0f93f7SMaxim Schwalm mipi_dsi_dcs_set_display_off_multi(&ctx); 134*9e0f93f7SMaxim Schwalm mipi_dsi_msleep(&ctx, 60); 135*9e0f93f7SMaxim Schwalm mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); 136*9e0f93f7SMaxim Schwalm 137*9e0f93f7SMaxim Schwalm return 0; 138*9e0f93f7SMaxim Schwalm } 139*9e0f93f7SMaxim Schwalm 140*9e0f93f7SMaxim Schwalm static int renesas_r69328_unprepare(struct drm_panel *panel) 141*9e0f93f7SMaxim Schwalm { 142*9e0f93f7SMaxim Schwalm struct renesas_r69328 *priv = to_renesas_r69328(panel); 143*9e0f93f7SMaxim Schwalm 144*9e0f93f7SMaxim Schwalm if (!priv->prepared) 145*9e0f93f7SMaxim Schwalm return 0; 146*9e0f93f7SMaxim Schwalm 147*9e0f93f7SMaxim Schwalm gpiod_set_value_cansleep(priv->reset_gpio, 1); 148*9e0f93f7SMaxim Schwalm 149*9e0f93f7SMaxim Schwalm usleep_range(5000, 6000); 150*9e0f93f7SMaxim Schwalm 151*9e0f93f7SMaxim Schwalm regulator_disable(priv->vddio_supply); 152*9e0f93f7SMaxim Schwalm regulator_disable(priv->vdd_supply); 153*9e0f93f7SMaxim Schwalm 154*9e0f93f7SMaxim Schwalm priv->prepared = false; 155*9e0f93f7SMaxim Schwalm return 0; 156*9e0f93f7SMaxim Schwalm } 157*9e0f93f7SMaxim Schwalm 158*9e0f93f7SMaxim Schwalm static const struct drm_display_mode renesas_r69328_mode = { 159*9e0f93f7SMaxim Schwalm .clock = (720 + 92 + 62 + 4) * (1280 + 6 + 3 + 1) * 60 / 1000, 160*9e0f93f7SMaxim Schwalm .hdisplay = 720, 161*9e0f93f7SMaxim Schwalm .hsync_start = 720 + 92, 162*9e0f93f7SMaxim Schwalm .hsync_end = 720 + 92 + 62, 163*9e0f93f7SMaxim Schwalm .htotal = 720 + 92 + 62 + 4, 164*9e0f93f7SMaxim Schwalm .vdisplay = 1280, 165*9e0f93f7SMaxim Schwalm .vsync_start = 1280 + 6, 166*9e0f93f7SMaxim Schwalm .vsync_end = 1280 + 6 + 3, 167*9e0f93f7SMaxim Schwalm .vtotal = 1280 + 6 + 3 + 1, 168*9e0f93f7SMaxim Schwalm .width_mm = 59, 169*9e0f93f7SMaxim Schwalm .height_mm = 105, 170*9e0f93f7SMaxim Schwalm }; 171*9e0f93f7SMaxim Schwalm 172*9e0f93f7SMaxim Schwalm static int renesas_r69328_get_modes(struct drm_panel *panel, 173*9e0f93f7SMaxim Schwalm struct drm_connector *connector) 174*9e0f93f7SMaxim Schwalm { 175*9e0f93f7SMaxim Schwalm struct drm_display_mode *mode; 176*9e0f93f7SMaxim Schwalm 177*9e0f93f7SMaxim Schwalm mode = drm_mode_duplicate(connector->dev, &renesas_r69328_mode); 178*9e0f93f7SMaxim Schwalm if (!mode) 179*9e0f93f7SMaxim Schwalm return -ENOMEM; 180*9e0f93f7SMaxim Schwalm 181*9e0f93f7SMaxim Schwalm drm_mode_set_name(mode); 182*9e0f93f7SMaxim Schwalm 183*9e0f93f7SMaxim Schwalm mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 184*9e0f93f7SMaxim Schwalm connector->display_info.width_mm = mode->width_mm; 185*9e0f93f7SMaxim Schwalm connector->display_info.height_mm = mode->height_mm; 186*9e0f93f7SMaxim Schwalm drm_mode_probed_add(connector, mode); 187*9e0f93f7SMaxim Schwalm 188*9e0f93f7SMaxim Schwalm return 1; 189*9e0f93f7SMaxim Schwalm } 190*9e0f93f7SMaxim Schwalm 191*9e0f93f7SMaxim Schwalm static const struct drm_panel_funcs renesas_r69328_panel_funcs = { 192*9e0f93f7SMaxim Schwalm .prepare = renesas_r69328_prepare, 193*9e0f93f7SMaxim Schwalm .enable = renesas_r69328_enable, 194*9e0f93f7SMaxim Schwalm .disable = renesas_r69328_disable, 195*9e0f93f7SMaxim Schwalm .unprepare = renesas_r69328_unprepare, 196*9e0f93f7SMaxim Schwalm .get_modes = renesas_r69328_get_modes, 197*9e0f93f7SMaxim Schwalm }; 198*9e0f93f7SMaxim Schwalm 199*9e0f93f7SMaxim Schwalm static int renesas_r69328_probe(struct mipi_dsi_device *dsi) 200*9e0f93f7SMaxim Schwalm { 201*9e0f93f7SMaxim Schwalm struct device *dev = &dsi->dev; 202*9e0f93f7SMaxim Schwalm struct renesas_r69328 *priv; 203*9e0f93f7SMaxim Schwalm int ret; 204*9e0f93f7SMaxim Schwalm 205*9e0f93f7SMaxim Schwalm priv = devm_drm_panel_alloc(dev, struct renesas_r69328, panel, 206*9e0f93f7SMaxim Schwalm &renesas_r69328_panel_funcs, 207*9e0f93f7SMaxim Schwalm DRM_MODE_CONNECTOR_DSI); 208*9e0f93f7SMaxim Schwalm if (IS_ERR(priv)) 209*9e0f93f7SMaxim Schwalm return PTR_ERR(priv); 210*9e0f93f7SMaxim Schwalm 211*9e0f93f7SMaxim Schwalm priv->vdd_supply = devm_regulator_get(dev, "vdd"); 212*9e0f93f7SMaxim Schwalm if (IS_ERR(priv->vdd_supply)) 213*9e0f93f7SMaxim Schwalm return dev_err_probe(dev, PTR_ERR(priv->vdd_supply), 214*9e0f93f7SMaxim Schwalm "Failed to get vdd-supply\n"); 215*9e0f93f7SMaxim Schwalm 216*9e0f93f7SMaxim Schwalm priv->vddio_supply = devm_regulator_get(dev, "vddio"); 217*9e0f93f7SMaxim Schwalm if (IS_ERR(priv->vddio_supply)) 218*9e0f93f7SMaxim Schwalm return dev_err_probe(dev, PTR_ERR(priv->vddio_supply), 219*9e0f93f7SMaxim Schwalm "Failed to get vddio-supply\n"); 220*9e0f93f7SMaxim Schwalm 221*9e0f93f7SMaxim Schwalm priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", 222*9e0f93f7SMaxim Schwalm GPIOD_OUT_LOW); 223*9e0f93f7SMaxim Schwalm if (IS_ERR(priv->reset_gpio)) 224*9e0f93f7SMaxim Schwalm return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), 225*9e0f93f7SMaxim Schwalm "Failed to get reset-gpios\n"); 226*9e0f93f7SMaxim Schwalm 227*9e0f93f7SMaxim Schwalm priv->dsi = dsi; 228*9e0f93f7SMaxim Schwalm mipi_dsi_set_drvdata(dsi, priv); 229*9e0f93f7SMaxim Schwalm 230*9e0f93f7SMaxim Schwalm dsi->lanes = 4; 231*9e0f93f7SMaxim Schwalm dsi->format = MIPI_DSI_FMT_RGB888; 232*9e0f93f7SMaxim Schwalm dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | 233*9e0f93f7SMaxim Schwalm MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM; 234*9e0f93f7SMaxim Schwalm 235*9e0f93f7SMaxim Schwalm ret = drm_panel_of_backlight(&priv->panel); 236*9e0f93f7SMaxim Schwalm if (ret) 237*9e0f93f7SMaxim Schwalm return dev_err_probe(dev, ret, "Failed to get backlight\n"); 238*9e0f93f7SMaxim Schwalm 239*9e0f93f7SMaxim Schwalm drm_panel_add(&priv->panel); 240*9e0f93f7SMaxim Schwalm 241*9e0f93f7SMaxim Schwalm ret = mipi_dsi_attach(dsi); 242*9e0f93f7SMaxim Schwalm if (ret) { 243*9e0f93f7SMaxim Schwalm drm_panel_remove(&priv->panel); 244*9e0f93f7SMaxim Schwalm return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); 245*9e0f93f7SMaxim Schwalm } 246*9e0f93f7SMaxim Schwalm 247*9e0f93f7SMaxim Schwalm return 0; 248*9e0f93f7SMaxim Schwalm } 249*9e0f93f7SMaxim Schwalm 250*9e0f93f7SMaxim Schwalm static void renesas_r69328_remove(struct mipi_dsi_device *dsi) 251*9e0f93f7SMaxim Schwalm { 252*9e0f93f7SMaxim Schwalm struct renesas_r69328 *priv = mipi_dsi_get_drvdata(dsi); 253*9e0f93f7SMaxim Schwalm int ret; 254*9e0f93f7SMaxim Schwalm 255*9e0f93f7SMaxim Schwalm ret = mipi_dsi_detach(dsi); 256*9e0f93f7SMaxim Schwalm if (ret) 257*9e0f93f7SMaxim Schwalm dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); 258*9e0f93f7SMaxim Schwalm 259*9e0f93f7SMaxim Schwalm drm_panel_remove(&priv->panel); 260*9e0f93f7SMaxim Schwalm } 261*9e0f93f7SMaxim Schwalm 262*9e0f93f7SMaxim Schwalm static const struct of_device_id renesas_r69328_of_match[] = { 263*9e0f93f7SMaxim Schwalm { .compatible = "jdi,dx12d100vm0eaa" }, 264*9e0f93f7SMaxim Schwalm { /* sentinel */ } 265*9e0f93f7SMaxim Schwalm }; 266*9e0f93f7SMaxim Schwalm MODULE_DEVICE_TABLE(of, renesas_r69328_of_match); 267*9e0f93f7SMaxim Schwalm 268*9e0f93f7SMaxim Schwalm static struct mipi_dsi_driver renesas_r69328_driver = { 269*9e0f93f7SMaxim Schwalm .probe = renesas_r69328_probe, 270*9e0f93f7SMaxim Schwalm .remove = renesas_r69328_remove, 271*9e0f93f7SMaxim Schwalm .driver = { 272*9e0f93f7SMaxim Schwalm .name = "panel-renesas-r69328", 273*9e0f93f7SMaxim Schwalm .of_match_table = renesas_r69328_of_match, 274*9e0f93f7SMaxim Schwalm }, 275*9e0f93f7SMaxim Schwalm }; 276*9e0f93f7SMaxim Schwalm module_mipi_dsi_driver(renesas_r69328_driver); 277*9e0f93f7SMaxim Schwalm 278*9e0f93f7SMaxim Schwalm MODULE_AUTHOR("Maxim Schwalm <maxim.schwalm@gmail.com>"); 279*9e0f93f7SMaxim Schwalm MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); 280*9e0f93f7SMaxim Schwalm MODULE_DESCRIPTION("Renesas R69328-based panel driver"); 281*9e0f93f7SMaxim Schwalm MODULE_LICENSE("GPL"); 282