// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019, Huaqin Telecom Technology Co., Ltd
*
* Author: Jerry Han <jerry.han.hq@gmail.com>
*
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#include <drm/drm_device.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#include <video/mipi_display.h>
struct panel_cmd {
char cmd;
char data;
};
struct panel_desc {
const struct drm_display_mode *display_mode;
unsigned int bpc;
unsigned int width_mm;
unsigned int height_mm;
unsigned long mode_flags;
enum mipi_dsi_pixel_format format;
unsigned int lanes;
const struct panel_cmd *on_cmds;
unsigned int on_cmds_num;
};
struct panel_info {
struct drm_panel base;
struct mipi_dsi_device *link;
const struct panel_desc *desc;
struct gpio_desc *enable_gpio;
struct gpio_desc *pp33_gpio;
struct gpio_desc *pp18_gpio;
bool prepared;
bool enabled;
};
static inline struct panel_info *to_panel_info(struct drm_panel *panel)
{
return container_of(panel, struct panel_info, base);
}
static void disable_gpios(struct panel_info *pinfo)
{
gpiod_set_value(pinfo->enable_gpio, 0);
gpiod_set_value(pinfo->pp33_gpio, 0);
gpiod_set_value(pinfo->pp18_gpio, 0);
}
static int send_mipi_cmds(struct drm_panel *panel, const struct panel_cmd *cmds)
{
struct panel_info *pinfo = to_panel_info(panel);
unsigned int i = 0;
int err;
for (i = 0; i < pinfo->desc->on_cmds_num; i++) {
err = mipi_dsi_dcs_write_buffer(pinfo->link, &cmds[i],
sizeof(struct panel_cmd));
if (err < 0)
return err;
}
return 0;
}
static int boe_panel_disable(struct drm_panel *panel)
{
struct panel_info *pinfo = to_panel_info(panel);
int err;
if (!pinfo->enabled)
return 0;
err = mipi_dsi_dcs_set_display_off(pinfo->link);
if (err < 0) {
dev_err(panel->dev, "failed to set display off: %d\n", err);
return err;
}
pinfo->enabled = false;
return 0;
}
static int boe_panel_unprepare(struct drm_panel *panel)
{
struct panel_info *pinfo = to_panel_info(panel);
int err;
if (!pinfo->prepared)
return 0;
err = mipi_dsi_dcs_set_display_off(pinfo->link);
if (err < 0)
dev_err(panel->dev, "failed to set display off: %d\n", err);
err = mipi_dsi_dcs_enter_sleep_mode(pinfo->link);
if (err < 0)
dev_err(panel->dev, "failed to enter sleep mode: %d\n", err);
/* sleep_mode_delay: 1ms - 2ms */
usleep_range(1000, 2000);
disable_gpios(pinfo);
pinfo->prepared = false;
return 0;
}
static int boe_panel_prepare(struct drm_panel *panel)
{
struct panel_info *pinfo = to_panel_info(panel);
int err;
if (pinfo->prepared)
return 0;
gpiod_set_value(pinfo->pp18_gpio, 1);
/* T1: 5ms - 6ms */
usleep_range(5000, 6000);
gpiod_set_value(pinfo->pp33_gpio, 1);
/* reset sequence */
/* T2: 14ms - 15ms */
usleep_range(14000, 15000);
gpiod_set_value(pinfo->enable_gpio, 1);
/* T3: 1ms - 2ms */
usleep_range(1000, 2000);
gpiod_set_value(pinfo->enable_gpio, 0);
/* T4: 1ms - 2ms */
usleep_range(1000, 2000);
gpiod_set_value(pinfo->enable_gpio, 1);
/* T5: 5ms - 6ms */
usleep_range(5000, 6000);
/* send init code */
err = send_mipi_cmds(panel, pinfo->desc->on_cmds);
if (err < 0) {
dev_err(panel->dev, "failed to send DCS Init Code: %d\n", err);
goto poweroff;
}
err = mipi_dsi_dcs_exit_sleep_mode(pinfo->link);
if (err < 0) {
dev_err(panel->dev, "failed to exit sleep mode: %d\n", err);
goto poweroff;
}
/* T6: 120ms - 121ms */
usleep_range(120000, 121000);
err = mipi_dsi_dcs_set_display_on(pinfo->link);
if (err < 0) {
dev_err(panel->dev, "failed to set display on: %d\n", err);
goto poweroff;
}
/* T7: 20ms - 21ms */
usleep_range(20000, 21000);
pinfo->prepared = true;
return 0;
poweroff:
disable_gpios(pinfo);
return err;
}
static int boe_panel_enable(struct drm_panel *panel)
{
struct panel_info *pinfo = to_panel_info(panel);
int ret;
if (pinfo->enabled)
return 0;
usleep_range(120000, 121000);
ret = mipi_dsi_dcs_set_display_on(pinfo->link);
if (ret < 0) {
dev_err(panel->dev, "failed to set display on: %d\n", ret);
return ret;
}
pinfo->enabled = true;
return 0;
}
static int boe_panel_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
struct panel_info *pinfo = to_panel_info(panel);
const struct drm_display_mode *m = pinfo->desc->display_mode;
struct drm_display_mode *mode;
mode = drm_mode_duplicate(connector