blob: dec9144578c6eb967a27491a734740e2cff4e8c5 [file] [log] [blame]
/*
* Copyright (C) 2020 Google LLC
* Author: Michael Hoang
*
* Based on driver of same name originally meant for different platform.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
*/
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pwm.h>
#include <uapi/linux/media-bus-format.h>
#include <video/mipi_display.h>
#include <video/videomode.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
struct startek_panel {
struct drm_panel panel;
struct mipi_dsi_device *dsi;
struct gpio_desc *reset;
struct backlight_device *backlight;
};
static const struct drm_display_mode startek_default_timing = {
.clock = 17700000 / 1000,
.hdisplay = 320,
.hsync_start = 320 + 138,
.hsync_end = 320 + 138 + 50,
.htotal = 320 + 138 + 50 + 100,
.vdisplay = 480,
.vsync_start = 480 + 2,
.vsync_end = 480 + 2 + 1,
.vtotal = 480 + 2 + 1 + 2,
.vrefresh = 60,
};
#define MAX_PARA_NUM 16
struct dcs_command {
unsigned char cmd;
unsigned char para_size;
unsigned char para[MAX_PARA_NUM];
unsigned int sleep;
};
static const struct dcs_command g_startek_init[] = {
// PGAMCTRL
{.cmd = 0xe0,
.para_size = 15,
.para = {0x00, 0x04, 0x0e, 0x08, 0x17, 0x0a, 0x40, 0x79, 0x4d, 0x07, 0x0e,
0x0a, 0x1a, 0x1d, 0x0f}},
// NGAMCTRL
{.cmd = 0xe1,
.para_size = 15,
.para = {0x00, 0x1b, 0x1f, 0x02, 0x10, 0x05, 0x32, 0x34, 0x43, 0x02, 0x0a,
0x09, 0x33, 0x37, 0x0f}},
// Power Control 1
{.cmd = 0xc0, .para_size = 2, .para = {0x18, 0x16}},
// Power Control 2
{.cmd = 0xc1, .para_size = 1, .para = {0x41}},
// VCOM Control
{.cmd = 0xc5, .para_size = 3, .para = {0x00, 0x1e, 0x80}},
// Interface Mode Control
{.cmd = 0x3a, .para_size = 1, .para = {0x06}},
// Frame rate 60HZ
{.cmd = 0xb1, .para_size = 2, .para = {0xa0, 0x11}},
// Display Inversion Control
{.cmd = 0xb4, .para_size = 1, .para = {0x02}},
// Blanking Porch Control
{.cmd = 0xb5, .para_size = 4, .para = {0x02, 0x03, 0x8a, 0x96}},
// Set Image Function
{.cmd = 0xe9, .para_size = 1, .para = {0x01}},
// Adjust Control 3
{.cmd = 0xf7, .para_size = 4, .para = {0xa9, 0x51, 0x2c, 0x82}},
// Display Function Control
{.cmd = 0xb6, .para_size = 3, .para = {0x02, 0x02, 0x3b}},
// Column Address Set
{.cmd = 0x2a, .para_size = 4, .para = {0x00, 0x00, 0x01, 0x3f}},
// Page Address Set
{.cmd = 0x2b, .para_size = 4, .para = {0x00, 0x00, 0x01, 0xdf}},
// Entry Mode Set
{.cmd = 0xb7, .para_size = 1, .para = {0xc6}},
// Interface Mode Control
{.cmd = 0xb0, .para_size = 1, .para = {0x00}},
};
// Seems 0x36 only takes effect after exit sleep.
// And if init all registers after exit sleep, other issue.
// So we seperate 2 init phases.
static const struct dcs_command g_startek_later_init[] = {
// Memory Access Control
{.cmd = 0x36, .para_size = 1, .para = {0x48}},
};
static int startek_init(struct mipi_dsi_device *dsi,
const struct dcs_command *init_command, int num)
{
int i;
int ret;
dev_info(&dsi->dev, "%s(), command num %d\n", __func__, num);
for (i = 0; i < num; i++) {
struct dcs_command *cmd = &init_command[i];
ret = mipi_dsi_dcs_write(dsi, cmd->cmd, cmd->para, cmd->para_size);
if (ret < 0) {
dev_err(&dsi->dev, "%s(), command 0x%x, ret %d\n", __func__, cmd->cmd,
ret);
return ret;
}
if (cmd->sleep)
msleep(cmd->sleep);
}
return 0;
}
static int startek_panel_get_modes(struct drm_panel *panel)
{
struct drm_display_mode *mode;
static const u32 bus_format = MEDIA_BUS_FMT_RGB666_1X18;
int ret;
mode = drm_mode_duplicate(panel->drm, &startek_default_timing);
if (!mode) {
dev_err(panel->drm->dev, "failed to add mode %ux%ux@%u\n",
startek_default_timing.hdisplay, startek_default_timing.vdisplay,
startek_default_timing.vrefresh);
return -ENOMEM;
}
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(mode);
drm_mode_probed_add(panel->connector, mode);
panel->connector->display_info.bpc = 6;
panel->connector->display_info.height_mm = 49;
panel->connector->display_info.width_mm = 74;
panel->connector->display_info.bus_flags =
DRM_BUS_FLAG_PIXDATA_POSEDGE | DRM_BUS_FLAG_DE_HIGH;
ret = drm_display_info_set_bus_formats(&panel->connector->display_info,
&bus_format, 1);
if (ret) {
return 0;
}
return 1;
}
static int startek_panel_prepare(struct drm_panel *panel)
{
int ret = 0;
struct startek_panel *startek =
container_of(panel, struct startek_panel, panel);
struct mipi_dsi_device *dsi = startek->dsi;
uint8_t ctrl = 0x2C;
uint8_t powersave = 0x0;
uint16_t min_bright = 0x80;
gpiod_set_value(startek->reset, 1);
msleep(1);
gpiod_set_value(startek->reset, 0);
msleep(10);
gpiod_set_value(startek->reset, 1);
msleep(120);
ret = startek_init(dsi, g_startek_init, ARRAY_SIZE(g_startek_init));
if (ret < 0) {
dev_err(&dsi->dev, "%s(), startek_init failed, ret %d\n", __func__, ret);
return ret;
}
ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x0ff);
if (ret < 0) {
dev_err(&dsi->dev, "Failed to set brightness: %d", ret);
return ret;
}
ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, &ctrl,
sizeof(ctrl));
if (ret < 0) {
dev_err(&dsi->dev, "Failed to set control: %d", ret);
return ret;
}
ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_POWER_SAVE, &powersave,
sizeof(powersave));
if (ret < 0) {
dev_err(&dsi->dev, "Failed to set powersave: %d", ret);
return ret;
}
ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_CABC_MIN_BRIGHTNESS, &min_bright,
sizeof(min_bright));
if (ret < 0) {
dev_err(&dsi->dev, "Failed to set min_bright: %d", ret);
return ret;
}
dsi->mode_flags |= MIPI_DSI_MODE_LPM;
ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
if (ret < 0) {
dev_err(&dsi->dev, "Failed to exit sleep mode: %d", ret);
return ret;
}
msleep(120);
startek_init(dsi, g_startek_later_init, ARRAY_SIZE(g_startek_later_init));
ret = mipi_dsi_dcs_set_display_on(dsi);
if (ret < 0) {
dev_err(&dsi->dev, "Failed to turn on display: %d", ret);
return ret;
}
dev_info(&dsi->dev, "prepare ok\n");
return 0;
}
static int startek_panel_unprepare(struct drm_panel *panel)
{
struct startek_panel *startek =
container_of(panel, struct startek_panel, panel);
struct mipi_dsi_device *dsi = startek->dsi;
int ret;
dsi->mode_flags |= MIPI_DSI_MODE_LPM;
ret = mipi_dsi_dcs_set_display_off(dsi);
if (ret < 0) {
dev_err(&dsi->dev, "Could not set display off: %d", ret);
return ret;
}
ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
if (ret < 0) {
dev_err(&dsi->dev, "Could not enter sleep mode: %d", ret);
return ret;
}
return 0;
}
static int startek_panel_enable(struct drm_panel *panel)
{
int ret;
struct startek_panel *startek =
container_of(panel, struct startek_panel, panel);
struct mipi_dsi_device *dsi = startek->dsi;
ret = backlight_enable(startek->backlight);
if (ret < 0) {
dev_err(&dsi->dev, "failed to enable backlight: %d", ret);
return ret;
}
return 0;
}
static int startek_panel_disable(struct drm_panel *panel)
{
int ret;
struct startek_panel *startek =
container_of(panel, struct startek_panel, panel);
struct mipi_dsi_device *dsi = startek->dsi;
ret = backlight_disable(startek->backlight);
if (ret < 0) {
dev_err(&dsi->dev, "failed to disable backlight: %d", ret);
return ret;
}
return 0;
}
static const struct drm_panel_funcs startek_panel_funcs =
{
.get_modes = startek_panel_get_modes,
.enable = startek_panel_enable,
.disable = startek_panel_disable,
.prepare = startek_panel_prepare,
.unprepare = startek_panel_unprepare,
};
static int startek_panel_probe(struct mipi_dsi_device *dsi) {
struct startek_panel *startek;
int ret;
startek = devm_kzalloc(&dsi->dev, sizeof(*startek), GFP_KERNEL);
if (!startek)
return -ENOMEM;
dsi->format = MIPI_DSI_FMT_RGB666_PACKED;
dsi->mode_flags =
MIPI_DSI_MODE_VIDEO_HSE |
MIPI_DSI_MODE_VIDEO |
MIPI_DSI_CLOCK_NON_CONTINUOUS;
ret = of_property_read_u32(dsi->dev.of_node, "dsi-lanes", &dsi->lanes);
if (ret < 0) {
dev_err(&dsi->dev, "Failed to get dsi-lanes property: %d", ret);
return ret;
}
mipi_dsi_set_drvdata(dsi, startek);
startek->dsi = dsi;
startek->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(startek->reset)) {
ret = PTR_ERR(startek->reset);
dev_err(&dsi->dev, "Failed to get reset gpio: %d\n", ret);
return ret;
}
startek->backlight = devm_of_find_backlight(&startek->dsi->dev);
if (IS_ERR(startek->backlight)) {
ret = PTR_ERR(startek->backlight);
dev_err(&dsi->dev, "Failed to get backlight: %d\n", ret);
return ret;
}
drm_panel_init(&startek->panel);
startek->panel.funcs = &startek_panel_funcs;
startek->panel.dev = &startek->dsi->dev;
ret = drm_panel_add(&startek->panel);
if (ret < 0) {
dev_err(&dsi->dev, "Failed to add panel: %d\n", ret);
return ret;
}
ret = mipi_dsi_attach(dsi);
if (ret < 0) {
dev_err(&dsi->dev, "Unable to mipi_dsi_attach! %d\n", ret);
return ret;
}
return ret;
}
static int startek_panel_remove(struct mipi_dsi_device *dsi) {
struct startek_panel *startek = mipi_dsi_get_drvdata(dsi);
int ret;
gpiod_set_value(startek->reset, 1);
ret = mipi_dsi_detach(dsi);
if (ret < 0)
dev_err(&dsi->dev, "Failed to mipi_dsi_detach! %d\n", ret);
if (startek->panel.dev)
drm_panel_remove(&startek->panel);
return 0;
}
static const struct of_device_id startek_of_match[] = {
{
.compatible = "startek,ili9488",
},
{}};
MODULE_DEVICE_TABLE(of, startek_of_match);
static struct mipi_dsi_driver startek_panel_driver = {
.driver =
{
.name = "panel-startek-ili9488", .of_match_table = startek_of_match,
},
.probe = startek_panel_probe,
.remove = startek_panel_remove,
};
module_mipi_dsi_driver(startek_panel_driver);
MODULE_AUTHOR("Coral Support <coral-support@google.com>");
MODULE_DESCRIPTION("Startek Display panel");
MODULE_LICENSE("GPL v2");