429 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * BOE BF060Y8M-AJ0 5.99" MIPI-DSI OLED Panel on SW43404 DriverIC4 *5 * Copyright (c) 2020 AngeloGioacchino Del Regno6 * <angelogioacchino.delregno@somainline.org>7 */8 9#include <linux/backlight.h>10#include <linux/delay.h>11#include <linux/gpio/consumer.h>12#include <linux/module.h>13#include <linux/of.h>14#include <linux/regulator/consumer.h>15#include <video/mipi_display.h>16#include <drm/drm_mipi_dsi.h>17#include <drm/drm_modes.h>18#include <drm/drm_panel.h>19 20#define DCS_ALLOW_HBM_RANGE 0x0c21#define DCS_DISALLOW_HBM_RANGE 0x0822 23enum boe_bf060y8m_aj0_supplies {24 BF060Y8M_VREG_VCC,25 BF060Y8M_VREG_VDDIO,26 BF060Y8M_VREG_VCI,27 BF060Y8M_VREG_EL_VDD,28 BF060Y8M_VREG_EL_VSS,29 BF060Y8M_VREG_MAX30};31 32struct boe_bf060y8m_aj0 {33 struct drm_panel panel;34 struct mipi_dsi_device *dsi;35 struct regulator_bulk_data vregs[BF060Y8M_VREG_MAX];36 struct gpio_desc *reset_gpio;37};38 39static inline40struct boe_bf060y8m_aj0 *to_boe_bf060y8m_aj0(struct drm_panel *panel)41{42 return container_of(panel, struct boe_bf060y8m_aj0, panel);43}44 45static void boe_bf060y8m_aj0_reset(struct boe_bf060y8m_aj0 *boe)46{47 gpiod_set_value_cansleep(boe->reset_gpio, 0);48 usleep_range(2000, 3000);49 gpiod_set_value_cansleep(boe->reset_gpio, 1);50 usleep_range(15000, 16000);51 gpiod_set_value_cansleep(boe->reset_gpio, 0);52 usleep_range(5000, 6000);53}54 55static int boe_bf060y8m_aj0_on(struct boe_bf060y8m_aj0 *boe)56{57 struct mipi_dsi_device *dsi = boe->dsi;58 struct device *dev = &dsi->dev;59 int ret;60 61 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0xa5, 0x00);62 mipi_dsi_dcs_write_seq(dsi, 0xb2, 0x00, 0x4c);63 mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_SET_3D_CONTROL, 0x10);64 mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, DCS_ALLOW_HBM_RANGE);65 mipi_dsi_dcs_write_seq(dsi, 0xf8,66 0x00, 0x08, 0x10, 0x00, 0x22, 0x00, 0x00, 0x2d);67 68 ret = mipi_dsi_dcs_exit_sleep_mode(dsi);69 if (ret < 0) {70 dev_err(dev, "Failed to exit sleep mode: %d\n", ret);71 return ret;72 }73 msleep(30);74 75 mipi_dsi_dcs_write_seq(dsi, 0xb0, 0xa5, 0x00);76 mipi_dsi_dcs_write_seq(dsi, 0xc0,77 0x08, 0x48, 0x65, 0x33, 0x33, 0x33,78 0x2a, 0x31, 0x39, 0x20, 0x09);79 mipi_dsi_dcs_write_seq(dsi, 0xc1, 0x00, 0x00, 0x00, 0x1f, 0x1f,80 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,81 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f);82 mipi_dsi_dcs_write_seq(dsi, 0xe2, 0x20, 0x04, 0x10, 0x12, 0x92,83 0x4f, 0x8f, 0x44, 0x84, 0x83, 0x83, 0x83,84 0x5c, 0x5c, 0x5c);85 mipi_dsi_dcs_write_seq(dsi, 0xde, 0x01, 0x2c, 0x00, 0x77, 0x3e);86 87 msleep(30);88 89 ret = mipi_dsi_dcs_set_display_on(dsi);90 if (ret < 0) {91 dev_err(dev, "Failed to set display on: %d\n", ret);92 return ret;93 }94 msleep(50);95 96 return 0;97}98 99static int boe_bf060y8m_aj0_off(struct boe_bf060y8m_aj0 *boe)100{101 struct mipi_dsi_device *dsi = boe->dsi;102 struct device *dev = &dsi->dev;103 int ret;104 105 /* OFF commands sent in HS mode */106 dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;107 ret = mipi_dsi_dcs_set_display_off(dsi);108 if (ret < 0) {109 dev_err(dev, "Failed to set display off: %d\n", ret);110 return ret;111 }112 msleep(20);113 114 ret = mipi_dsi_dcs_enter_sleep_mode(dsi);115 if (ret < 0) {116 dev_err(dev, "Failed to enter sleep mode: %d\n", ret);117 return ret;118 }119 usleep_range(1000, 2000);120 dsi->mode_flags |= MIPI_DSI_MODE_LPM;121 122 return 0;123}124 125static int boe_bf060y8m_aj0_prepare(struct drm_panel *panel)126{127 struct boe_bf060y8m_aj0 *boe = to_boe_bf060y8m_aj0(panel);128 struct device *dev = &boe->dsi->dev;129 int ret;130 131 /*132 * Enable EL Driving Voltage first - doing that at the beginning133 * or at the end of the power sequence doesn't matter, so enable134 * it here to avoid yet another usleep at the end.135 */136 ret = regulator_enable(boe->vregs[BF060Y8M_VREG_EL_VDD].consumer);137 if (ret)138 return ret;139 ret = regulator_enable(boe->vregs[BF060Y8M_VREG_EL_VSS].consumer);140 if (ret)141 goto err_elvss;142 143 ret = regulator_enable(boe->vregs[BF060Y8M_VREG_VCC].consumer);144 if (ret)145 goto err_vcc;146 usleep_range(1000, 2000);147 ret = regulator_enable(boe->vregs[BF060Y8M_VREG_VDDIO].consumer);148 if (ret)149 goto err_vddio;150 usleep_range(500, 1000);151 ret = regulator_enable(boe->vregs[BF060Y8M_VREG_VCI].consumer);152 if (ret)153 goto err_vci;154 usleep_range(2000, 3000);155 156 boe_bf060y8m_aj0_reset(boe);157 158 ret = boe_bf060y8m_aj0_on(boe);159 if (ret < 0) {160 dev_err(dev, "Failed to initialize panel: %d\n", ret);161 gpiod_set_value_cansleep(boe->reset_gpio, 1);162 return ret;163 }164 165 return 0;166 167err_vci:168 regulator_disable(boe->vregs[BF060Y8M_VREG_VDDIO].consumer);169err_vddio:170 regulator_disable(boe->vregs[BF060Y8M_VREG_VCC].consumer);171err_vcc:172 regulator_disable(boe->vregs[BF060Y8M_VREG_EL_VSS].consumer);173err_elvss:174 regulator_disable(boe->vregs[BF060Y8M_VREG_EL_VDD].consumer);175 return ret;176}177 178static int boe_bf060y8m_aj0_unprepare(struct drm_panel *panel)179{180 struct boe_bf060y8m_aj0 *boe = to_boe_bf060y8m_aj0(panel);181 struct device *dev = &boe->dsi->dev;182 int ret;183 184 ret = boe_bf060y8m_aj0_off(boe);185 if (ret < 0)186 dev_err(dev, "Failed to un-initialize panel: %d\n", ret);187 188 gpiod_set_value_cansleep(boe->reset_gpio, 1);189 ret = regulator_bulk_disable(ARRAY_SIZE(boe->vregs), boe->vregs);190 191 return 0;192}193 194static const struct drm_display_mode boe_bf060y8m_aj0_mode = {195 .clock = 165268,196 .hdisplay = 1080,197 .hsync_start = 1080 + 36,198 .hsync_end = 1080 + 36 + 24,199 .htotal = 1080 + 36 + 24 + 96,200 .vdisplay = 2160,201 .vsync_start = 2160 + 16,202 .vsync_end = 2160 + 16 + 1,203 .vtotal = 2160 + 16 + 1 + 15,204 .width_mm = 68, /* 68.04 mm */205 .height_mm = 136, /* 136.08 mm */206};207 208static int boe_bf060y8m_aj0_get_modes(struct drm_panel *panel,209 struct drm_connector *connector)210{211 struct drm_display_mode *mode;212 213 mode = drm_mode_duplicate(connector->dev, &boe_bf060y8m_aj0_mode);214 if (!mode)215 return -ENOMEM;216 217 drm_mode_set_name(mode);218 219 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;220 connector->display_info.width_mm = mode->width_mm;221 connector->display_info.height_mm = mode->height_mm;222 drm_mode_probed_add(connector, mode);223 224 return 1;225}226 227static const struct drm_panel_funcs boe_bf060y8m_aj0_panel_funcs = {228 .prepare = boe_bf060y8m_aj0_prepare,229 .unprepare = boe_bf060y8m_aj0_unprepare,230 .get_modes = boe_bf060y8m_aj0_get_modes,231};232 233static int boe_bf060y8m_aj0_bl_update_status(struct backlight_device *bl)234{235 struct mipi_dsi_device *dsi = bl_get_data(bl);236 u16 brightness = backlight_get_brightness(bl);237 int ret;238 239 ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness);240 if (ret < 0)241 return ret;242 243 return 0;244}245 246static int boe_bf060y8m_aj0_bl_get_brightness(struct backlight_device *bl)247{248 struct mipi_dsi_device *dsi = bl_get_data(bl);249 u16 brightness;250 int ret;251 252 ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness);253 if (ret < 0)254 return ret;255 256 return brightness & 0xff;257}258 259static const struct backlight_ops boe_bf060y8m_aj0_bl_ops = {260 .update_status = boe_bf060y8m_aj0_bl_update_status,261 .get_brightness = boe_bf060y8m_aj0_bl_get_brightness,262};263 264static struct backlight_device *265boe_bf060y8m_aj0_create_backlight(struct mipi_dsi_device *dsi)266{267 struct device *dev = &dsi->dev;268 const struct backlight_properties props = {269 .type = BACKLIGHT_RAW,270 .brightness = 127,271 .max_brightness = 255,272 .scale = BACKLIGHT_SCALE_NON_LINEAR,273 };274 275 return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,276 &boe_bf060y8m_aj0_bl_ops, &props);277}278 279static int boe_bf060y8m_aj0_init_vregs(struct boe_bf060y8m_aj0 *boe,280 struct device *dev)281{282 struct regulator *vreg;283 int ret;284 285 boe->vregs[BF060Y8M_VREG_VCC].supply = "vcc";286 boe->vregs[BF060Y8M_VREG_VDDIO].supply = "vddio";287 boe->vregs[BF060Y8M_VREG_VCI].supply = "vci";288 boe->vregs[BF060Y8M_VREG_EL_VDD].supply = "elvdd";289 boe->vregs[BF060Y8M_VREG_EL_VSS].supply = "elvss";290 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(boe->vregs),291 boe->vregs);292 if (ret < 0) {293 dev_err(dev, "Failed to get regulators: %d\n", ret);294 return ret;295 }296 297 vreg = boe->vregs[BF060Y8M_VREG_VCC].consumer;298 ret = regulator_is_supported_voltage(vreg, 2700000, 3600000);299 if (!ret)300 return ret;301 302 vreg = boe->vregs[BF060Y8M_VREG_VDDIO].consumer;303 ret = regulator_is_supported_voltage(vreg, 1620000, 1980000);304 if (!ret)305 return ret;306 307 vreg = boe->vregs[BF060Y8M_VREG_VCI].consumer;308 ret = regulator_is_supported_voltage(vreg, 2600000, 3600000);309 if (!ret)310 return ret;311 312 vreg = boe->vregs[BF060Y8M_VREG_EL_VDD].consumer;313 ret = regulator_is_supported_voltage(vreg, 4400000, 4800000);314 if (!ret)315 return ret;316 317 /* ELVSS is negative: -5.00V to -1.40V */318 vreg = boe->vregs[BF060Y8M_VREG_EL_VSS].consumer;319 ret = regulator_is_supported_voltage(vreg, 1400000, 5000000);320 if (!ret)321 return ret;322 323 /*324 * Set min/max rated current, known only for VCI and VDDIO and,325 * in case of failure, just go on gracefully, as this step is not326 * guaranteed to succeed on all regulator HW but do a debug print327 * to inform the developer during debugging.328 * In any case, these two supplies are also optional, so they may329 * be fixed-regulator which, at the time of writing, does not330 * support fake current limiting.331 */332 vreg = boe->vregs[BF060Y8M_VREG_VDDIO].consumer;333 ret = regulator_set_current_limit(vreg, 1500, 2500);334 if (ret)335 dev_dbg(dev, "Current limit cannot be set on %s: %d\n",336 boe->vregs[1].supply, ret);337 338 vreg = boe->vregs[BF060Y8M_VREG_VCI].consumer;339 ret = regulator_set_current_limit(vreg, 20000, 40000);340 if (ret)341 dev_dbg(dev, "Current limit cannot be set on %s: %d\n",342 boe->vregs[2].supply, ret);343 344 return 0;345}346 347static int boe_bf060y8m_aj0_probe(struct mipi_dsi_device *dsi)348{349 struct device *dev = &dsi->dev;350 struct boe_bf060y8m_aj0 *boe;351 int ret;352 353 boe = devm_kzalloc(dev, sizeof(*boe), GFP_KERNEL);354 if (!boe)355 return -ENOMEM;356 357 ret = boe_bf060y8m_aj0_init_vregs(boe, dev);358 if (ret)359 return dev_err_probe(dev, ret,360 "Failed to initialize supplies.\n");361 362 boe->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);363 if (IS_ERR(boe->reset_gpio))364 return dev_err_probe(dev, PTR_ERR(boe->reset_gpio),365 "Failed to get reset-gpios\n");366 367 boe->dsi = dsi;368 mipi_dsi_set_drvdata(dsi, boe);369 370 dsi->lanes = 4;371 dsi->format = MIPI_DSI_FMT_RGB888;372 dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_NO_EOT_PACKET |373 MIPI_DSI_MODE_VIDEO_SYNC_PULSE |374 MIPI_DSI_CLOCK_NON_CONTINUOUS |375 MIPI_DSI_MODE_LPM;376 377 drm_panel_init(&boe->panel, dev, &boe_bf060y8m_aj0_panel_funcs,378 DRM_MODE_CONNECTOR_DSI);379 380 boe->panel.prepare_prev_first = true;381 382 boe->panel.backlight = boe_bf060y8m_aj0_create_backlight(dsi);383 if (IS_ERR(boe->panel.backlight))384 return dev_err_probe(dev, PTR_ERR(boe->panel.backlight),385 "Failed to create backlight\n");386 387 drm_panel_add(&boe->panel);388 389 ret = mipi_dsi_attach(dsi);390 if (ret < 0) {391 dev_err(dev, "Failed to attach to DSI host: %d\n", ret);392 return ret;393 }394 395 return 0;396}397 398static void boe_bf060y8m_aj0_remove(struct mipi_dsi_device *dsi)399{400 struct boe_bf060y8m_aj0 *boe = mipi_dsi_get_drvdata(dsi);401 int ret;402 403 ret = mipi_dsi_detach(dsi);404 if (ret < 0)405 dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret);406 407 drm_panel_remove(&boe->panel);408}409 410static const struct of_device_id boe_bf060y8m_aj0_of_match[] = {411 { .compatible = "boe,bf060y8m-aj0" },412 { /* sentinel */ }413};414MODULE_DEVICE_TABLE(of, boe_bf060y8m_aj0_of_match);415 416static struct mipi_dsi_driver boe_bf060y8m_aj0_driver = {417 .probe = boe_bf060y8m_aj0_probe,418 .remove = boe_bf060y8m_aj0_remove,419 .driver = {420 .name = "panel-sw43404-boe-fhd-amoled",421 .of_match_table = boe_bf060y8m_aj0_of_match,422 },423};424module_mipi_dsi_driver(boe_bf060y8m_aj0_driver);425 426MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>");427MODULE_DESCRIPTION("BOE BF060Y8M-AJ0 MIPI-DSI OLED panel");428MODULE_LICENSE("GPL v2");429