722d4f06e5
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Signed-off-by: Rob Herring <robh@kernel.org> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Steven Price <steven.price@arm.com> Acked-by: Liviu Dudau <liviu.dudau@arm.com> Reviewed-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> Acked-by: Robert Foss <rfoss@kernel.org> Signed-off-by: Thierry Reding <treding@nvidia.com> Link: https://patchwork.freedesktop.org/patch/msgid/20230714174545.4056287-1-robh@kernel.org
263 lines
5.9 KiB
C
263 lines
5.9 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2018 Amarula Solutions
|
|
* Author: Jagan Teki <jagan@amarulasolutions.com>
|
|
*/
|
|
|
|
#include <drm/drm_mipi_dsi.h>
|
|
#include <drm/drm_modes.h>
|
|
#include <drm/drm_panel.h>
|
|
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#define FEIYANG_INIT_CMD_LEN 2
|
|
|
|
struct feiyang {
|
|
struct drm_panel panel;
|
|
struct mipi_dsi_device *dsi;
|
|
|
|
struct regulator *dvdd;
|
|
struct regulator *avdd;
|
|
struct gpio_desc *reset;
|
|
};
|
|
|
|
static inline struct feiyang *panel_to_feiyang(struct drm_panel *panel)
|
|
{
|
|
return container_of(panel, struct feiyang, panel);
|
|
}
|
|
|
|
struct feiyang_init_cmd {
|
|
u8 data[FEIYANG_INIT_CMD_LEN];
|
|
};
|
|
|
|
static const struct feiyang_init_cmd feiyang_init_cmds[] = {
|
|
{ .data = { 0x80, 0x58 } },
|
|
{ .data = { 0x81, 0x47 } },
|
|
{ .data = { 0x82, 0xD4 } },
|
|
{ .data = { 0x83, 0x88 } },
|
|
{ .data = { 0x84, 0xA9 } },
|
|
{ .data = { 0x85, 0xC3 } },
|
|
{ .data = { 0x86, 0x82 } },
|
|
};
|
|
|
|
static int feiyang_prepare(struct drm_panel *panel)
|
|
{
|
|
struct feiyang *ctx = panel_to_feiyang(panel);
|
|
struct mipi_dsi_device *dsi = ctx->dsi;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = regulator_enable(ctx->dvdd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* T1 (dvdd start + dvdd rise) 0 < T1 <= 10ms */
|
|
msleep(10);
|
|
|
|
ret = regulator_enable(ctx->avdd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* T3 (dvdd rise + avdd start + avdd rise) T3 >= 20ms */
|
|
msleep(20);
|
|
|
|
gpiod_set_value(ctx->reset, 0);
|
|
|
|
/*
|
|
* T5 + T6 (avdd rise + video & logic signal rise)
|
|
* T5 >= 10ms, 0 < T6 <= 10ms
|
|
*/
|
|
msleep(20);
|
|
|
|
gpiod_set_value(ctx->reset, 1);
|
|
|
|
/* T12 (video & logic signal rise + backlight rise) T12 >= 200ms */
|
|
msleep(200);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(feiyang_init_cmds); i++) {
|
|
const struct feiyang_init_cmd *cmd =
|
|
&feiyang_init_cmds[i];
|
|
|
|
ret = mipi_dsi_dcs_write_buffer(dsi, cmd->data,
|
|
FEIYANG_INIT_CMD_LEN);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int feiyang_enable(struct drm_panel *panel)
|
|
{
|
|
struct feiyang *ctx = panel_to_feiyang(panel);
|
|
|
|
/* T12 (video & logic signal rise + backlight rise) T12 >= 200ms */
|
|
msleep(200);
|
|
|
|
mipi_dsi_dcs_set_display_on(ctx->dsi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int feiyang_disable(struct drm_panel *panel)
|
|
{
|
|
struct feiyang *ctx = panel_to_feiyang(panel);
|
|
|
|
return mipi_dsi_dcs_set_display_off(ctx->dsi);
|
|
}
|
|
|
|
static int feiyang_unprepare(struct drm_panel *panel)
|
|
{
|
|
struct feiyang *ctx = panel_to_feiyang(panel);
|
|
int ret;
|
|
|
|
ret = mipi_dsi_dcs_set_display_off(ctx->dsi);
|
|
if (ret < 0)
|
|
dev_err(panel->dev, "failed to set display off: %d\n", ret);
|
|
|
|
ret = mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
|
|
if (ret < 0)
|
|
dev_err(panel->dev, "failed to enter sleep mode: %d\n", ret);
|
|
|
|
/* T13 (backlight fall + video & logic signal fall) T13 >= 200ms */
|
|
msleep(200);
|
|
|
|
gpiod_set_value(ctx->reset, 0);
|
|
|
|
regulator_disable(ctx->avdd);
|
|
|
|
/* T11 (dvdd rise to fall) 0 < T11 <= 10ms */
|
|
msleep(10);
|
|
|
|
regulator_disable(ctx->dvdd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct drm_display_mode feiyang_default_mode = {
|
|
.clock = 55000,
|
|
|
|
.hdisplay = 1024,
|
|
.hsync_start = 1024 + 310,
|
|
.hsync_end = 1024 + 310 + 20,
|
|
.htotal = 1024 + 310 + 20 + 90,
|
|
|
|
.vdisplay = 600,
|
|
.vsync_start = 600 + 12,
|
|
.vsync_end = 600 + 12 + 2,
|
|
.vtotal = 600 + 12 + 2 + 21,
|
|
|
|
.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
|
|
};
|
|
|
|
static int feiyang_get_modes(struct drm_panel *panel,
|
|
struct drm_connector *connector)
|
|
{
|
|
struct feiyang *ctx = panel_to_feiyang(panel);
|
|
struct drm_display_mode *mode;
|
|
|
|
mode = drm_mode_duplicate(connector->dev, &feiyang_default_mode);
|
|
if (!mode) {
|
|
dev_err(&ctx->dsi->dev, "failed to add mode %ux%u@%u\n",
|
|
feiyang_default_mode.hdisplay,
|
|
feiyang_default_mode.vdisplay,
|
|
drm_mode_vrefresh(&feiyang_default_mode));
|
|
return -ENOMEM;
|
|
}
|
|
|
|
drm_mode_set_name(mode);
|
|
|
|
drm_mode_probed_add(connector, mode);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static const struct drm_panel_funcs feiyang_funcs = {
|
|
.disable = feiyang_disable,
|
|
.unprepare = feiyang_unprepare,
|
|
.prepare = feiyang_prepare,
|
|
.enable = feiyang_enable,
|
|
.get_modes = feiyang_get_modes,
|
|
};
|
|
|
|
static int feiyang_dsi_probe(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct feiyang *ctx;
|
|
int ret;
|
|
|
|
ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
mipi_dsi_set_drvdata(dsi, ctx);
|
|
ctx->dsi = dsi;
|
|
|
|
drm_panel_init(&ctx->panel, &dsi->dev, &feiyang_funcs,
|
|
DRM_MODE_CONNECTOR_DSI);
|
|
|
|
ctx->dvdd = devm_regulator_get(&dsi->dev, "dvdd");
|
|
if (IS_ERR(ctx->dvdd))
|
|
return dev_err_probe(&dsi->dev, PTR_ERR(ctx->dvdd),
|
|
"Couldn't get dvdd regulator\n");
|
|
|
|
ctx->avdd = devm_regulator_get(&dsi->dev, "avdd");
|
|
if (IS_ERR(ctx->avdd))
|
|
return dev_err_probe(&dsi->dev, PTR_ERR(ctx->avdd),
|
|
"Couldn't get avdd regulator\n");
|
|
|
|
ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_LOW);
|
|
if (IS_ERR(ctx->reset))
|
|
return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset),
|
|
"Couldn't get our reset GPIO\n");
|
|
|
|
ret = drm_panel_of_backlight(&ctx->panel);
|
|
if (ret)
|
|
return ret;
|
|
|
|
drm_panel_add(&ctx->panel);
|
|
|
|
dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST;
|
|
dsi->format = MIPI_DSI_FMT_RGB888;
|
|
dsi->lanes = 4;
|
|
|
|
ret = mipi_dsi_attach(dsi);
|
|
if (ret < 0) {
|
|
drm_panel_remove(&ctx->panel);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void feiyang_dsi_remove(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct feiyang *ctx = mipi_dsi_get_drvdata(dsi);
|
|
|
|
mipi_dsi_detach(dsi);
|
|
drm_panel_remove(&ctx->panel);
|
|
}
|
|
|
|
static const struct of_device_id feiyang_of_match[] = {
|
|
{ .compatible = "feiyang,fy07024di26a30d", },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, feiyang_of_match);
|
|
|
|
static struct mipi_dsi_driver feiyang_driver = {
|
|
.probe = feiyang_dsi_probe,
|
|
.remove = feiyang_dsi_remove,
|
|
.driver = {
|
|
.name = "feiyang-fy07024di26a30d",
|
|
.of_match_table = feiyang_of_match,
|
|
},
|
|
};
|
|
module_mipi_dsi_driver(feiyang_driver);
|
|
|
|
MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>");
|
|
MODULE_DESCRIPTION("Feiyang FY07024DI26A30-D MIPI-DSI LCD panel");
|
|
MODULE_LICENSE("GPL");
|