mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-03-31 14:41:55 -07:00
Major architecture change: move from BSP kernel 6.6 with pre-merged panel DTBs to mainline kernel 6.12.61 LTS with separated board DTBs + panel overlays. - Board DTB = hardware profile (GPIOs, PMIC, joypad, audio). 16 boards. Auto-selected by U-Boot via SARADC ADC reading (hwrev). - Panel overlay = display init sequence. 20 panels. Applied at boot time via boot.ini fdt apply. - Two image variants: original (a_boot.ini) and clone (b_boot.ini) - Kernel cross-compiles from x86 host (no ARM chroot needed) - Initramfs boot splash with SVG rendering at 0.7s - Out-of-tree joypad driver (singleadc-joypad) for clone boards - Panel generic-dsi driver with archr,generic-dsi compatible Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
661 lines
16 KiB
C
661 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Arch R Generic MIPI-DSI panel driver
|
|
* Copyright (C) 2025 Arch R Project
|
|
*
|
|
* Based on panel-generic-dsi.c by ROCKNIX (C) 2024
|
|
* Originally based on Rockteck jh057n00900 panel driver
|
|
* Copyright (C) Purism SPC 2019
|
|
*
|
|
* Reads panel configuration from device tree "panel_description" property.
|
|
* Supports any MIPI-DSI panel without recompilation — just change the DTS.
|
|
*
|
|
* panel_description format (array of strings):
|
|
* G delays=P,R,I,E,Y size=W,H format=rgb888 lanes=4 flags=0xNNN
|
|
* M clock=KHZ horizontal=H,HFP,HSYNC,HBP vertical=V,VFP,VSYNC,VBP default=1
|
|
* I dcs=0xCMD data=HEXPARAMS wait=MS
|
|
* I seq=HEXBYTES wait=MS
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/media-bus-format.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <video/display_timing.h>
|
|
#include <video/mipi_display.h>
|
|
|
|
#include <drm/drm_mipi_dsi.h>
|
|
#include <drm/drm_modes.h>
|
|
#include <drm/drm_panel.h>
|
|
|
|
#define DRIVER_NAME "panel-archr-dsi"
|
|
|
|
struct archr_panel_delays {
|
|
int prepare;
|
|
int reset;
|
|
int init;
|
|
int enable;
|
|
int ready;
|
|
};
|
|
|
|
struct archr_panel_size {
|
|
int width;
|
|
int height;
|
|
};
|
|
|
|
struct archr_panel_mode {
|
|
int clock;
|
|
int horizontal[5];
|
|
int vertical[5];
|
|
int is_default;
|
|
struct archr_panel_mode *prev;
|
|
};
|
|
|
|
#define DCS_PSEUDO_CMD_SEQ 0x10000
|
|
struct archr_panel_init_seq {
|
|
int dcs;
|
|
int len;
|
|
int read;
|
|
int wait;
|
|
u8 *data;
|
|
struct archr_panel_init_seq *link;
|
|
};
|
|
|
|
struct archr_panel {
|
|
struct device *dev;
|
|
struct drm_panel panel;
|
|
struct gpio_desc *enable_gpio;
|
|
struct gpio_desc *reset_gpio;
|
|
struct regulator *vdd;
|
|
struct regulator *iovcc;
|
|
|
|
struct archr_panel_delays delays;
|
|
struct archr_panel_size size;
|
|
struct archr_panel_mode *modes;
|
|
struct archr_panel_init_seq *iseq;
|
|
|
|
enum drm_panel_orientation orientation;
|
|
bool prepared;
|
|
};
|
|
|
|
static int load_globals(char *data, struct mipi_dsi_device *dsi,
|
|
struct archr_panel *ctx)
|
|
{
|
|
struct device *dev = &dsi->dev;
|
|
char *param, *val;
|
|
|
|
while (*data) {
|
|
data = next_arg(data, ¶m, &val);
|
|
if (!val)
|
|
continue;
|
|
if (strcmp(param, "delays") == 0) {
|
|
int delays[] = {0, 5, 1, 25, 120, 50};
|
|
get_options(val, 6, delays);
|
|
ctx->delays.prepare = delays[1];
|
|
ctx->delays.reset = delays[2];
|
|
ctx->delays.init = delays[3];
|
|
ctx->delays.enable = delays[4];
|
|
ctx->delays.ready = delays[5];
|
|
} else if (strcmp(param, "size") == 0) {
|
|
int size[] = {0, -1, -1};
|
|
get_options(val, 3, size);
|
|
ctx->size.width = size[1];
|
|
ctx->size.height = size[2];
|
|
} else if (strcmp(param, "format") == 0) {
|
|
if (strcmp(val, "rgb888") == 0)
|
|
dsi->format = MIPI_DSI_FMT_RGB888;
|
|
else if (strcmp(val, "rgb666") == 0)
|
|
dsi->format = MIPI_DSI_FMT_RGB666;
|
|
else if (strcmp(val, "rgb666_packed") == 0)
|
|
dsi->format = MIPI_DSI_FMT_RGB666_PACKED;
|
|
else if (strcmp(val, "rgb565") == 0)
|
|
dsi->format = MIPI_DSI_FMT_RGB565;
|
|
else
|
|
dev_info(dev, "bad format %s\n", val);
|
|
} else if (strcmp(param, "lanes") == 0) {
|
|
if (get_option(&val, &dsi->lanes) == 0)
|
|
dev_info(dev, "bad lanes %s\n", val);
|
|
} else if (strcmp(param, "flags") == 0) {
|
|
int flags;
|
|
if (get_option(&val, &flags) == 0)
|
|
dev_info(dev, "bad flags %s\n", val);
|
|
else
|
|
dsi->mode_flags = flags;
|
|
} else {
|
|
dev_info(dev, "unknown param %s\n", param);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int load_mode(char *data, struct mipi_dsi_device *dsi,
|
|
struct archr_panel *ctx)
|
|
{
|
|
struct device *dev = &dsi->dev;
|
|
struct archr_panel_mode *mode;
|
|
char *param, *val;
|
|
|
|
mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL);
|
|
if (!mode)
|
|
return -ENOMEM;
|
|
|
|
while (*data) {
|
|
data = next_arg(data, ¶m, &val);
|
|
if (!val)
|
|
continue;
|
|
if (strcmp(param, "clock") == 0) {
|
|
if (get_option(&val, &mode->clock) == 0)
|
|
dev_info(dev, "bad clock %s\n", val);
|
|
} else if (strcmp(param, "horizontal") == 0) {
|
|
get_options(val, 5, &mode->horizontal[0]);
|
|
} else if (strcmp(param, "vertical") == 0) {
|
|
get_options(val, 5, &mode->vertical[0]);
|
|
} else if (strcmp(param, "default") == 0) {
|
|
get_option(&val, &mode->is_default);
|
|
} else {
|
|
dev_info(dev, "Mode unhandled %s = %s\n", param, val);
|
|
}
|
|
}
|
|
|
|
if ((mode->clock > 5000) && (mode->vertical[1] > 0) &&
|
|
(mode->horizontal[1] > 0)) {
|
|
mode->prev = ctx->modes;
|
|
ctx->modes = mode;
|
|
dev_dbg(dev, "Mode %d %d %d %p -> %p\n",
|
|
mode->clock, mode->vertical[1],
|
|
mode->horizontal[1], mode, mode->prev);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int load_init_seq(char *data, struct mipi_dsi_device *dsi,
|
|
struct archr_panel *ctx)
|
|
{
|
|
struct device *dev = &dsi->dev;
|
|
struct archr_panel_init_seq *item;
|
|
char *param, *val;
|
|
|
|
item = devm_kzalloc(dev, sizeof(*item), GFP_KERNEL);
|
|
if (!item)
|
|
return -ENOMEM;
|
|
|
|
item->dcs = -1;
|
|
item->len = -1;
|
|
item->read = 0;
|
|
item->wait = 0;
|
|
|
|
while (*data) {
|
|
data = next_arg(data, ¶m, &val);
|
|
if (!val)
|
|
continue;
|
|
if (strcmp(param, "dcs") == 0) {
|
|
item->dcs = simple_strtoul(val, NULL, 16) & 0xFF;
|
|
} else if (strcmp(param, "data") == 0) {
|
|
item->len = (strlen(val)) >> 1;
|
|
item->data = devm_kzalloc(dev, item->len, GFP_KERNEL);
|
|
if (!item->data)
|
|
return -ENOMEM;
|
|
if (hex2bin(item->data, val, item->len) != 0) {
|
|
dev_info(dev, "bad data %s\n", val);
|
|
return -1;
|
|
}
|
|
} else if (strcmp(param, "seq") == 0) {
|
|
item->dcs = DCS_PSEUDO_CMD_SEQ;
|
|
item->len = (strlen(val)) >> 1;
|
|
item->data = devm_kzalloc(dev, item->len, GFP_KERNEL);
|
|
if (!item->data)
|
|
return -ENOMEM;
|
|
if (hex2bin(item->data, val, item->len) != 0) {
|
|
dev_info(dev, "bad seq %s\n", val);
|
|
return -1;
|
|
}
|
|
dev_dbg(dev, "loaded seq len=%d %s\n", item->len, val);
|
|
} else if (strcmp(param, "read") == 0) {
|
|
item->read = simple_strtoul(val, NULL, 16);
|
|
} else if (strcmp(param, "wait") == 0) {
|
|
item->wait = simple_strtoul(val, NULL, 16);
|
|
} else {
|
|
dev_info(dev, "Init unhandled %s = %s\n", param, val);
|
|
}
|
|
}
|
|
|
|
if (item->dcs >= 0) {
|
|
item->link = ctx->iseq;
|
|
ctx->iseq = item;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int load_panel_description_line(char *data, struct mipi_dsi_device *dsi,
|
|
struct archr_panel *ctx)
|
|
{
|
|
size_t pos;
|
|
|
|
for (pos = 0; data[pos] != 0; pos++) {
|
|
if ((data[pos] == '#') || data[pos] == '\n') {
|
|
data[pos] = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (pos < 2)
|
|
return 0;
|
|
|
|
switch (data[0]) {
|
|
case 'G':
|
|
load_globals(data + 1, dsi, ctx);
|
|
break;
|
|
case 'M':
|
|
load_mode(data + 1, dsi, ctx);
|
|
break;
|
|
case 'I':
|
|
load_init_seq(data + 1, dsi, ctx);
|
|
break;
|
|
default:
|
|
dev_info(&dsi->dev, "Unhandled line: %s\n", data);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int load_panel_description(struct mipi_dsi_device *dsi,
|
|
struct archr_panel *ctx)
|
|
{
|
|
struct device *dev = &dsi->dev;
|
|
struct archr_panel_init_seq *rev, *fwd, *tmp;
|
|
const char *line;
|
|
char *buf;
|
|
size_t linescnt, i;
|
|
int ret;
|
|
|
|
linescnt = of_property_count_strings(dev->of_node, "panel_description");
|
|
if (linescnt < 1) {
|
|
dev_err(dev, "failed to read panel_description from device tree (%ld)\n",
|
|
linescnt);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < linescnt; i++) {
|
|
ret = of_property_read_string_index(dev->of_node,
|
|
"panel_description",
|
|
i, &line);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to read panel_description[%ld]: %d\n",
|
|
i, ret);
|
|
return ret;
|
|
}
|
|
buf = kstrdup(line, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
dev_dbg(dev, "desc[%ld]: %s\n", i, line);
|
|
load_panel_description_line(buf, dsi, ctx);
|
|
kfree(buf);
|
|
}
|
|
|
|
/* Reverse init sequence list (DTS order → execution order) */
|
|
rev = ctx->iseq;
|
|
fwd = NULL;
|
|
while (rev) {
|
|
tmp = rev;
|
|
rev = tmp->link;
|
|
tmp->link = fwd;
|
|
fwd = tmp;
|
|
}
|
|
ctx->iseq = fwd;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline struct archr_panel *panel_to_archr(struct drm_panel *panel)
|
|
{
|
|
return container_of(panel, struct archr_panel, panel);
|
|
}
|
|
|
|
static int archr_panel_init_sequence(struct archr_panel *ctx)
|
|
{
|
|
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
|
|
struct device *dev = ctx->dev;
|
|
struct archr_panel_init_seq *iseq = ctx->iseq;
|
|
int ret;
|
|
|
|
while (iseq) {
|
|
if (iseq->read > 0) {
|
|
u8 readbuf[8];
|
|
if (iseq->read > 8)
|
|
iseq->read = 8;
|
|
ret = mipi_dsi_generic_read(dsi, iseq->data,
|
|
iseq->len, &readbuf[0],
|
|
iseq->read);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to read: %d\n", ret);
|
|
} else {
|
|
int j;
|
|
for (j = 0; j < ret; j++)
|
|
dev_info(dev, "read[%d]: %02x\n",
|
|
j, readbuf[j]);
|
|
}
|
|
} else if (iseq->dcs == DCS_PSEUDO_CMD_SEQ) {
|
|
ret = mipi_dsi_dcs_write_buffer(dsi, iseq->data,
|
|
iseq->len);
|
|
dev_dbg(dev, "iseq %px len=%d -> %d\n",
|
|
(void *)iseq, iseq->len, ret);
|
|
} else {
|
|
ret = mipi_dsi_dcs_write(dsi, iseq->dcs, iseq->data,
|
|
iseq->len);
|
|
dev_dbg(dev, "iseq %02x len=%d -> %d\n",
|
|
iseq->dcs, iseq->len, ret);
|
|
}
|
|
if (iseq->wait)
|
|
msleep(iseq->wait);
|
|
iseq = iseq->link;
|
|
}
|
|
|
|
dev_dbg(dev, "Panel init sequence done\n");
|
|
return 0;
|
|
}
|
|
|
|
static int archr_panel_unprepare(struct drm_panel *panel)
|
|
{
|
|
struct archr_panel *ctx = panel_to_archr(panel);
|
|
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
|
|
int ret;
|
|
|
|
if (!ctx->prepared)
|
|
return 0;
|
|
|
|
ret = mipi_dsi_dcs_set_display_off(dsi);
|
|
if (ret < 0)
|
|
dev_err(ctx->dev, "failed to set display off: %d\n", ret);
|
|
|
|
ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
|
|
if (ret < 0) {
|
|
dev_err(ctx->dev, "failed to enter sleep mode: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (ctx->enable_gpio)
|
|
gpiod_set_value_cansleep(ctx->enable_gpio, 0);
|
|
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
|
|
|
|
regulator_disable(ctx->iovcc);
|
|
regulator_disable(ctx->vdd);
|
|
|
|
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
|
|
|
|
ctx->prepared = false;
|
|
return 0;
|
|
}
|
|
|
|
static int archr_panel_prepare(struct drm_panel *panel)
|
|
{
|
|
struct archr_panel *ctx = panel_to_archr(panel);
|
|
int ret;
|
|
|
|
if (ctx->prepared)
|
|
return 0;
|
|
|
|
ret = regulator_enable(ctx->vdd);
|
|
if (ret < 0) {
|
|
dev_err(ctx->dev, "Failed to enable vdd supply: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_enable(ctx->iovcc);
|
|
if (ret < 0) {
|
|
dev_err(ctx->dev, "Failed to enable iovcc supply: %d\n", ret);
|
|
goto disable_vdd;
|
|
}
|
|
|
|
if (ctx->enable_gpio)
|
|
gpiod_set_value_cansleep(ctx->enable_gpio, 1);
|
|
msleep(ctx->delays.prepare);
|
|
|
|
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
|
|
msleep(ctx->delays.reset);
|
|
gpiod_set_value_cansleep(ctx->reset_gpio, 0);
|
|
|
|
msleep(ctx->delays.init);
|
|
|
|
ret = archr_panel_init_sequence(ctx);
|
|
if (ret < 0) {
|
|
dev_err(ctx->dev, "Panel init sequence failed: %d\n", ret);
|
|
goto disable_iovcc;
|
|
}
|
|
|
|
ret = mipi_dsi_dcs_set_display_on(
|
|
to_mipi_dsi_device(ctx->dev));
|
|
if (ret < 0) {
|
|
dev_err(ctx->dev, "Failed to set display on: %d\n", ret);
|
|
goto disable_iovcc;
|
|
}
|
|
|
|
ret = mipi_dsi_dcs_exit_sleep_mode(
|
|
to_mipi_dsi_device(ctx->dev));
|
|
if (ret < 0) {
|
|
dev_err(ctx->dev, "Failed to exit sleep mode: %d\n", ret);
|
|
goto disable_iovcc;
|
|
}
|
|
|
|
msleep(ctx->delays.enable);
|
|
|
|
ctx->prepared = true;
|
|
return 0;
|
|
|
|
disable_iovcc:
|
|
regulator_disable(ctx->iovcc);
|
|
disable_vdd:
|
|
regulator_disable(ctx->vdd);
|
|
return ret;
|
|
}
|
|
|
|
static const struct drm_display_mode mode_template = { };
|
|
|
|
static int archr_panel_get_modes(struct drm_panel *panel,
|
|
struct drm_connector *connector)
|
|
{
|
|
struct archr_panel *ctx = panel_to_archr(panel);
|
|
struct drm_display_mode mode_tmp;
|
|
struct drm_display_mode *mode;
|
|
struct archr_panel_mode *genmode = ctx->modes;
|
|
|
|
while (genmode) {
|
|
dev_dbg(ctx->dev, "mode %d %dx%d\n",
|
|
genmode->clock, genmode->horizontal[1],
|
|
genmode->vertical[1]);
|
|
|
|
mode_tmp = mode_template;
|
|
|
|
mode_tmp.clock = genmode->clock;
|
|
|
|
mode_tmp.hdisplay = genmode->horizontal[1];
|
|
mode_tmp.hsync_start = mode_tmp.hdisplay + genmode->horizontal[2];
|
|
mode_tmp.hsync_end = mode_tmp.hsync_start + genmode->horizontal[3];
|
|
mode_tmp.htotal = mode_tmp.hsync_end + genmode->horizontal[4];
|
|
|
|
mode_tmp.vdisplay = genmode->vertical[1];
|
|
mode_tmp.vsync_start = mode_tmp.vdisplay + genmode->vertical[2];
|
|
mode_tmp.vsync_end = mode_tmp.vsync_start + genmode->vertical[3];
|
|
mode_tmp.vtotal = mode_tmp.vsync_end + genmode->vertical[4];
|
|
|
|
mode_tmp.width_mm = ctx->size.width;
|
|
mode_tmp.height_mm = ctx->size.height;
|
|
|
|
mode_tmp.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
|
|
|
|
mode = drm_mode_duplicate(connector->dev, &mode_tmp);
|
|
if (!mode) {
|
|
dev_err(ctx->dev, "Failed to add mode\n");
|
|
return -ENOMEM;
|
|
}
|
|
drm_mode_set_name(mode);
|
|
|
|
mode->type = DRM_MODE_TYPE_DRIVER;
|
|
if (genmode->is_default)
|
|
mode->type |= DRM_MODE_TYPE_PREFERRED;
|
|
|
|
drm_mode_probed_add(connector, mode);
|
|
|
|
genmode = genmode->prev;
|
|
}
|
|
|
|
connector->display_info.width_mm = ctx->size.width;
|
|
connector->display_info.height_mm = ctx->size.height;
|
|
|
|
drm_connector_set_panel_orientation(connector, ctx->orientation);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static enum drm_panel_orientation archr_panel_get_orientation(
|
|
struct drm_panel *panel)
|
|
{
|
|
struct archr_panel *ctx = panel_to_archr(panel);
|
|
return ctx->orientation;
|
|
}
|
|
|
|
static const struct drm_panel_funcs archr_panel_funcs = {
|
|
.unprepare = archr_panel_unprepare,
|
|
.prepare = archr_panel_prepare,
|
|
.get_modes = archr_panel_get_modes,
|
|
.get_orientation = archr_panel_get_orientation,
|
|
};
|
|
|
|
static int archr_panel_probe(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct device *dev = &dsi->dev;
|
|
struct archr_panel *ctx;
|
|
int ret;
|
|
|
|
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable",
|
|
GPIOD_OUT_LOW);
|
|
if (IS_ERR(ctx->enable_gpio)) {
|
|
dev_err(dev, "cannot get enable gpio\n");
|
|
ctx->enable_gpio = NULL;
|
|
}
|
|
|
|
ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset",
|
|
GPIOD_OUT_LOW);
|
|
if (IS_ERR(ctx->reset_gpio)) {
|
|
dev_err(dev, "cannot get reset gpio\n");
|
|
return PTR_ERR(ctx->reset_gpio);
|
|
}
|
|
|
|
ctx->vdd = devm_regulator_get(dev, "vdd");
|
|
if (IS_ERR(ctx->vdd)) {
|
|
ret = PTR_ERR(ctx->vdd);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Failed to request vdd regulator: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ctx->iovcc = devm_regulator_get(dev, "iovcc");
|
|
if (IS_ERR(ctx->iovcc)) {
|
|
ret = PTR_ERR(ctx->iovcc);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Failed to request iovcc regulator: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation);
|
|
if (ret < 0) {
|
|
dev_err(dev, "%pOF: failed to get orientation %d\n",
|
|
dev->of_node, ret);
|
|
return ret;
|
|
}
|
|
|
|
mipi_dsi_set_drvdata(dsi, ctx);
|
|
|
|
ctx->dev = dev;
|
|
|
|
/* Defaults (overridden by G line in panel_description) */
|
|
dsi->lanes = 1;
|
|
dsi->format = MIPI_DSI_FMT_RGB888;
|
|
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
|
|
MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET |
|
|
MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
|
|
|
ret = load_panel_description(dsi, ctx);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to load panel description\n");
|
|
return ret;
|
|
}
|
|
|
|
dev_info(dev, "lanes %d, format %d, mode %lx\n",
|
|
dsi->lanes, dsi->format, dsi->mode_flags);
|
|
|
|
drm_panel_init(&ctx->panel, &dsi->dev, &archr_panel_funcs,
|
|
DRM_MODE_CONNECTOR_DSI);
|
|
|
|
ret = drm_panel_of_backlight(&ctx->panel);
|
|
if (ret)
|
|
return ret;
|
|
|
|
drm_panel_add(&ctx->panel);
|
|
|
|
ret = mipi_dsi_attach(dsi);
|
|
if (ret < 0) {
|
|
dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);
|
|
drm_panel_remove(&ctx->panel);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void archr_panel_shutdown(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct archr_panel *ctx = mipi_dsi_get_drvdata(dsi);
|
|
|
|
drm_panel_unprepare(&ctx->panel);
|
|
drm_panel_disable(&ctx->panel);
|
|
}
|
|
|
|
static void archr_panel_remove(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct archr_panel *ctx = mipi_dsi_get_drvdata(dsi);
|
|
int ret;
|
|
|
|
archr_panel_shutdown(dsi);
|
|
|
|
ret = mipi_dsi_detach(dsi);
|
|
if (ret < 0)
|
|
dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n",
|
|
ret);
|
|
|
|
drm_panel_remove(&ctx->panel);
|
|
}
|
|
|
|
static const struct of_device_id archr_panel_of_match[] = {
|
|
{ .compatible = "archr,generic-dsi" },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, archr_panel_of_match);
|
|
|
|
static struct mipi_dsi_driver archr_panel_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.of_match_table = archr_panel_of_match,
|
|
},
|
|
.probe = archr_panel_probe,
|
|
.remove = archr_panel_remove,
|
|
.shutdown = archr_panel_shutdown,
|
|
};
|
|
module_mipi_dsi_driver(archr_panel_driver);
|
|
|
|
MODULE_AUTHOR("Arch R Project");
|
|
MODULE_DESCRIPTION("Arch R Generic MIPI-DSI panel driver");
|
|
MODULE_LICENSE("GPL v2");
|