diff options
-rw-r--r-- | drivers/gpu/drm/panel/Kconfig | 11 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/panel/simple-panel.c | 970 |
3 files changed, 982 insertions, 0 deletions
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 6020c30a33b3..d6e2f54799fb 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -38,6 +38,17 @@ config DRM_PANEL_SIMPLE that it can be automatically turned off when the panel goes into a low power state. +config DRM_SIMPLE_PANEL + tristate "support for simple panels configured via DTS" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + select VIDEOMODE_HELPERS + help + DRM panel driver for dumb panels that need at most a regulator and + a GPIO to be powered up. Optionally a backlight can be attached so + that it can be automatically turned off when the panel goes into a + low power state. Panel configuration can be achieved by DTS nodes. + config DRM_PANEL_ILITEK_IL9322 tristate "Ilitek ILI9322 320x240 QVGA panels" depends on OF && SPI diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 5ccaaa9d13af..1ce143e8ccd9 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o +obj-$(CONFIG_DRM_SIMPLE_PANEL) += simple-panel.o obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o diff --git a/drivers/gpu/drm/panel/simple-panel.c b/drivers/gpu/drm/panel/simple-panel.c new file mode 100644 index 000000000000..0f1eb6df487e --- /dev/null +++ b/drivers/gpu/drm/panel/simple-panel.c @@ -0,0 +1,970 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Theobroma Systems Design und Consulting GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <linux/backlight.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <video/display_timing.h> +#include <video/mipi_display.h> +#include <linux/of_device.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +struct cmd_ctrl_hdr { + u8 dtype; /* data type */ + u8 wait; /* ms */ + u8 dlen; /* payload len */ +} __packed; + +struct cmd_desc { + struct cmd_ctrl_hdr dchdr; + u8 *payload; +}; + +struct panel_cmds { + u8 *buf; + int blen; + struct cmd_desc *cmds; + int cmd_cnt; +}; + +struct panel_desc { + const struct drm_display_mode *modes; + unsigned int num_modes; + const struct display_timing *timings; + unsigned int num_timings; + + unsigned int bpc; + + /** + * @width: width (in millimeters) of the panel's active display area + * @height: height (in millimeters) of the panel's active display area + */ + struct { + unsigned int width; + unsigned int height; + } size; + + /** + * @reset: the time (in milliseconds) indicates the delay time + * after the panel to operate reset gpio + * @init: the time (in milliseconds) that it takes for the panel to + * power on and dsi host can send command to panel + * @prepare: the time (in milliseconds) that it takes for the panel to + * become ready and start receiving video data + * @enable: the time (in milliseconds) that it takes for the panel to + * display the first valid frame after starting to receive + * video data + * @disable: the time (in milliseconds) that it takes for the panel to + * turn the display off (no content is visible) + * @unprepare: the time (in milliseconds) that it takes for the panel + * to power itself down completely + */ + struct { + unsigned int reset; + unsigned int init; + unsigned int prepare; + unsigned int enable; + unsigned int disable; + unsigned int unprepare; + } delay; + + u32 bus_format; + u32 bus_flags; +}; + +struct panel_simple { + struct drm_panel base; + struct mipi_dsi_device *dsi; + bool prepared; + bool enabled; + bool power_invert; + + struct device *dev; + const struct panel_desc *desc; + + struct backlight_device *backlight; + struct regulator *supply; + struct i2c_adapter *ddc; + + struct gpio_desc *enable_gpio; + struct gpio_desc *reset_gpio; + int cmd_type; + + struct gpio_desc *spi_sdi_gpio; + struct gpio_desc *spi_scl_gpio; + struct gpio_desc *spi_cs_gpio; + + struct panel_cmds *on_cmds; + struct panel_cmds *off_cmds; +}; + +enum rockchip_cmd_type { + CMD_TYPE_DEFAULT, + CMD_TYPE_SPI, + CMD_TYPE_MCU +}; + +static inline int get_panel_cmd_type(const char *s) +{ + if (!s) + return -EINVAL; + + if (strncmp(s, "spi", 3) == 0) + return CMD_TYPE_SPI; + else if (strncmp(s, "mcu", 3) == 0) + return CMD_TYPE_MCU; + + return CMD_TYPE_DEFAULT; +} + +static inline struct panel_simple *to_panel_simple(struct drm_panel *panel) +{ + return container_of(panel, struct panel_simple, base); +} + +static void panel_simple_cmds_cleanup(struct panel_simple *p) +{ + if (p->on_cmds) { + kfree(p->on_cmds->buf); + kfree(p->on_cmds->cmds); + } + + if (p->off_cmds) { + kfree(p->off_cmds->buf); + kfree(p->off_cmds->cmds); + } +} + +static int panel_simple_parse_cmds(struct device *dev, + const u8 *data, int blen, + struct panel_cmds *pcmds) +{ + int len; + char *buf, *bp; + struct cmd_ctrl_hdr *dchdr; + int i, cnt; + + if (!pcmds) + return -EINVAL; + + buf = kmemdup(data, blen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* scan init commands */ + bp = buf; + len = blen; + cnt = 0; + while (len > sizeof(*dchdr)) { + dchdr = (struct cmd_ctrl_hdr *)bp; + + if (dchdr->dlen > len) { + dev_err(dev, "%s: error, len=%d", __func__, + dchdr->dlen); + return -EINVAL; + } + + bp += sizeof(*dchdr); + len -= sizeof(*dchdr); + bp += dchdr->dlen; + len -= dchdr->dlen; + cnt++; + } + + if (len != 0) { + dev_err(dev, "%s: dcs_cmd=%x len=%d error!", + __func__, buf[0], blen); + kfree(buf); + return -EINVAL; + } + + pcmds->cmds = kcalloc(cnt, sizeof(struct cmd_desc), GFP_KERNEL); + if (!pcmds->cmds) { + kfree(buf); + return -ENOMEM; + } + + pcmds->cmd_cnt = cnt; + pcmds->buf = buf; + pcmds->blen = blen; + + bp = buf; + len = blen; + for (i = 0; i < cnt; i++) { + dchdr = (struct cmd_ctrl_hdr *)bp; + len -= sizeof(*dchdr); + bp += sizeof(*dchdr); + pcmds->cmds[i].dchdr = *dchdr; + pcmds->cmds[i].payload = bp; + bp += dchdr->dlen; + len -= dchdr->dlen; + } + + return 0; +} + +static void panel_simple_spi_write_cmd(struct panel_simple *panel, + u8 type, int value) +{ + int i; + + gpiod_direction_output(panel->spi_cs_gpio, 0); + + if (type == 0) + value &= (~(1 << 8)); + else + value |= (1 << 8); + + for (i = 0; i < 9; i++) { + if (value & 0x100) + gpiod_direction_output(panel->spi_sdi_gpio, 1); + else + gpiod_direction_output(panel->spi_sdi_gpio, 0); + + gpiod_direction_output(panel->spi_scl_gpio, 0); + udelay(10); + gpiod_direction_output(panel->spi_scl_gpio, 1); + value <<= 1; + udelay(10); + } + + gpiod_direction_output(panel->spi_cs_gpio, 1); +} + +static int panel_simple_spi_send_cmds(struct panel_simple *panel, + struct panel_cmds *cmds) +{ + int i; + + if (!cmds) + return -EINVAL; + + for (i = 0; i < cmds->cmd_cnt; i++) { + struct cmd_desc *cmd = &cmds->cmds[i]; + int value = 0; + + if (cmd->dchdr.dlen == 2) + value = (cmd->payload[0] << 8) | cmd->payload[1]; + else + value = cmd->payload[0]; + panel_simple_spi_write_cmd(panel, cmd->dchdr.dtype, value); + + if (cmd->dchdr.wait) + msleep(cmd->dchdr.wait); + } + + return 0; +} + +#if IS_ENABLED(CONFIG_DRM_MIPI_DSI) +static int panel_simple_dsi_send_cmds(struct panel_simple *panel, + struct panel_cmds *cmds) +{ + struct mipi_dsi_device *dsi = panel->dsi; + int i, err; + + if (!cmds) + return -EINVAL; + + for (i = 0; i < cmds->cmd_cnt; i++) { + struct cmd_desc *cmd = &cmds->cmds[i]; + + switch (cmd->dchdr.dtype) { + case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM: + case MIPI_DSI_GENERIC_LONG_WRITE: + err = mipi_dsi_generic_write(dsi, cmd->payload, + cmd->dchdr.dlen); + break; + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + case MIPI_DSI_DCS_LONG_WRITE: + err = mipi_dsi_dcs_write_buffer(dsi, cmd->payload, + cmd->dchdr.dlen); + break; + default: + return -EINVAL; + } + + if (err < 0) + dev_err(panel->dev, "failed to write dcs cmd: %d\n", + err); + + if (cmd->dchdr.wait) + msleep(cmd->dchdr.wait); + } + + return 0; +} +#else +static inline int panel_simple_dsi_send_cmds(struct panel_simple *panel, + struct panel_cmds *cmds) +{ + return -EINVAL; +} +#endif + +static int panel_simple_get_cmds(struct panel_simple *panel) +{ + const void *data; + int len; + int err; + + data = of_get_property(panel->dev->of_node, "panel-init-sequence", + &len); + if (data) { + panel->on_cmds = devm_kzalloc(panel->dev, + sizeof(*panel->on_cmds), + GFP_KERNEL); + if (!panel->on_cmds) + return -ENOMEM; + + err = panel_simple_parse_cmds(panel->dev, data, len, + panel->on_cmds); + if (err) { + dev_err(panel->dev, "failed to parse panel init sequence\n"); + return err; + } + } + + data = of_get_property(panel->dev->of_node, "panel-exit-sequence", + &len); + if (data) { + panel->off_cmds = devm_kzalloc(panel->dev, + sizeof(*panel->off_cmds), + GFP_KERNEL); + if (!panel->off_cmds) + return -ENOMEM; + + err = panel_simple_parse_cmds(panel->dev, data, len, + panel->off_cmds); + if (err) { + dev_err(panel->dev, "failed to parse panel exit sequence\n"); + return err; + } + } + return 0; +} + +static int panel_simple_get_fixed_modes(struct panel_simple *panel) +{ + struct drm_connector *connector = panel->base.connector; + struct drm_device *drm = panel->base.drm; + struct drm_display_mode *mode; + unsigned int i, num = 0; + + if (!panel->desc) + return 0; + + for (i = 0; i < panel->desc->num_timings; i++) { + const struct display_timing *dt = &panel->desc->timings[i]; + struct videomode vm; + + videomode_from_timing(dt, &vm); + mode = drm_mode_create(drm); + if (!mode) { + dev_err(drm->dev, "failed to add mode %ux%u\n", + dt->hactive.typ, dt->vactive.typ); + continue; + } + + drm_display_mode_from_videomode(&vm, mode); + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + num++; + } + + for (i = 0; i < panel->desc->num_modes; i++) { + const struct drm_display_mode *m = &panel->desc->modes[i]; + + mode = drm_mode_duplicate(drm, m); + if (!mode) { + dev_err(drm->dev, "failed to add mode %ux%u@%u\n", + m->hdisplay, m->vdisplay, m->vrefresh); + continue; + } + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + num++; + } + + connector->display_info.bpc = panel->desc->bpc; + connector->display_info.width_mm = panel->desc->size.width; + connector->display_info.height_mm = panel->desc->size.height; + if (panel->desc->bus_format) + drm_display_info_set_bus_formats(&connector->display_info, + &panel->desc->bus_format, 1); + + return num; +} + +static int panel_simple_of_get_native_mode(struct panel_simple *panel) +{ + struct drm_connector *connector = panel->base.connector; + struct drm_device *drm = panel->base.drm; + struct drm_display_mode *mode; + struct device_node *timings_np; + int ret; + + timings_np = of_get_child_by_name(panel->dev->of_node, + "display-timings"); + if (!timings_np) { + dev_dbg(panel->dev, "failed to find display-timings node\n"); + return 0; + } + + of_node_put(timings_np); + mode = drm_mode_create(drm); + if (!mode) + return 0; + + ret = of_get_drm_display_mode(panel->dev->of_node, mode, + NULL, OF_USE_NATIVE_MODE); + if (ret) { + dev_dbg(panel->dev, "failed to find dts display timings\n"); + drm_mode_destroy(drm, mode); + return 0; + } + + drm_mode_set_name(mode); + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static int panel_simple_regulator_enable(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + int err = 0; + + if (p->power_invert) { + if (regulator_is_enabled(p->supply) > 0) + regulator_disable(p->supply); + } else { + err = regulator_enable(p->supply); + if (err < 0) { + dev_err(panel->dev, "failed to enable supply: %d\n", + err); + return err; + } + } + + return err; +} + +static int panel_simple_regulator_disable(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + int err = 0; + + if (p->power_invert) { + if (!regulator_is_enabled(p->supply)) { + err = regulator_enable(p->supply); + if (err < 0) { + dev_err(panel->dev, "failed to enable supply: %d\n", + err); + return err; + } + } + } else { + regulator_disable(p->supply); + } + + return err; +} + +static int panel_simple_disable(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + + if (!p->enabled) + return 0; + + if (p->backlight) { + p->backlight->props.power = FB_BLANK_POWERDOWN; + backlight_update_status(p->backlight); + } + + if (p->desc && p->desc->delay.disable) + msleep(p->desc->delay.disable); + + p->enabled = false; + + return 0; +} + +static int panel_simple_unprepare(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + int err = 0; + + if (!p->prepared) + return 0; + + if (p->off_cmds) { + if (p->dsi) + err = panel_simple_dsi_send_cmds(p, p->off_cmds); + else if (p->cmd_type == CMD_TYPE_SPI) + err = panel_simple_spi_send_cmds(p, p->off_cmds); + if (err) + dev_err(p->dev, "failed to send off cmds\n"); + } + + if (p->reset_gpio) + gpiod_direction_output(p->reset_gpio, 1); + + if (p->enable_gpio) + gpiod_direction_output(p->enable_gpio, 0); + + panel_simple_regulator_disable(panel); + + if (p->desc && p->desc->delay.unprepare) + msleep(p->desc->delay.unprepare); + + p->prepared = false; + + return 0; +} + +static int panel_simple_prepare(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + int err; + + if (p->prepared) + return 0; + + err = panel_simple_regulator_enable(panel); + if (err < 0) { + dev_err(panel->dev, "failed to enable supply: %d\n", err); + return err; + } + + if (p->enable_gpio) + gpiod_direction_output(p->enable_gpio, 1); + + if (p->desc && p->desc->delay.prepare) + msleep(p->desc->delay.prepare); + + if (p->reset_gpio) + gpiod_direction_output(p->reset_gpio, 1); + + if (p->desc && p->desc->delay.reset) + msleep(p->desc->delay.reset); + + if (p->reset_gpio) + gpiod_direction_output(p->reset_gpio, 0); + + if (p->desc && p->desc->delay.init) + msleep(p->desc->delay.init); + + if (p->on_cmds) { + if (p->dsi) + err = panel_simple_dsi_send_cmds(p, p->on_cmds); + else if (p->cmd_type == CMD_TYPE_SPI) + err = panel_simple_spi_send_cmds(p, p->on_cmds); + if (err) + dev_err(p->dev, "failed to send on cmds\n"); + } + + p->prepared = true; + + return 0; +} + +static int panel_simple_enable(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + + if (p->enabled) + return 0; + + if (p->desc && p->desc->delay.enable) + msleep(p->desc->delay.enable); + + if (p->backlight) { + p->backlight->props.power = FB_BLANK_UNBLANK; + backlight_update_status(p->backlight); + } + + p->enabled = true; + + return 0; +} + +static int panel_simple_get_modes(struct drm_panel *panel) +{ + struct panel_simple *p = to_panel_simple(panel); + int num = 0; + + /* add device node plane modes */ + num += panel_simple_of_get_native_mode(p); + + /* add hard-coded panel modes */ + num += panel_simple_get_fixed_modes(p); + + /* probe EDID if a DDC bus is available */ + if (p->ddc) { + struct edid *edid = drm_get_edid(panel->connector, p->ddc); + drm_connector_update_edid_property(panel->connector, edid); + if (edid) { + num += drm_add_edid_modes(panel->connector, edid); + kfree(edid); + } + } + + return num; +} + +static int panel_simple_get_timings(struct drm_panel *panel, + unsigned int num_timings, + struct display_timing *timings) +{ + struct panel_simple *p = to_panel_simple(panel); + unsigned int i; + + if (!p->desc) + return 0; + + if (p->desc->num_timings < num_timings) + num_timings = p->desc->num_timings; + + if (timings) + for (i = 0; i < num_timings; i++) + timings[i] = p->desc->timings[i]; + + return p->desc->num_timings; +} + +static const struct drm_panel_funcs panel_simple_funcs = { + .disable = panel_simple_disable, + .unprepare = panel_simple_unprepare, + .prepare = panel_simple_prepare, + .enable = panel_simple_enable, + .get_modes = panel_simple_get_modes, + .get_timings = panel_simple_get_timings, +}; + +static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) +{ + struct device_node *backlight, *ddc; + struct panel_simple *panel; + struct panel_desc *of_desc; + const char *cmd_type; + u32 val; + int err; + + panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); + if (!panel) + return -ENOMEM; + + if (!desc) + of_desc = devm_kzalloc(dev, sizeof(*of_desc), GFP_KERNEL); + else + of_desc = devm_kmemdup(dev, desc, sizeof(*of_desc), GFP_KERNEL); + + if (!of_property_read_u32(dev->of_node, "bus-format", &val)) + of_desc->bus_format = val; + if (!of_property_read_u32(dev->of_node, "prepare-delay-ms", &val)) + of_desc->delay.prepare = val; + if (!of_property_read_u32(dev->of_node, "enable-delay-ms", &val)) + of_desc->delay.enable = val; + if (!of_property_read_u32(dev->of_node, "disable-delay-ms", &val)) + of_desc->delay.disable = val; + if (!of_property_read_u32(dev->of_node, "unprepare-delay-ms", &val)) + of_desc->delay.unprepare = val; + if (!of_property_read_u32(dev->of_node, "reset-delay-ms", &val)) + of_desc->delay.reset = val; + if (!of_property_read_u32(dev->of_node, "init-delay-ms", &val)) + of_desc->delay.init = val; + if (!of_property_read_u32(dev->of_node, "width-mm", &val)) + of_desc->size.width = val; + if (!of_property_read_u32(dev->of_node, "height-mm", &val)) + of_desc->size.height = val; + + panel->enabled = false; + panel->prepared = false; + panel->desc = of_desc; + panel->dev = dev; + + err = panel_simple_get_cmds(panel); + if (err) { + dev_err(dev, "failed to get init cmd: %d\n", err); + return err; + } + panel->supply = devm_regulator_get(dev, "power"); + if (IS_ERR(panel->supply)) + return PTR_ERR(panel->supply); + + panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", 0); + if (IS_ERR(panel->enable_gpio)) { + err = PTR_ERR(panel->enable_gpio); + dev_err(dev, "failed to request enable GPIO: %d\n", err); + return err; + } + + panel->reset_gpio = devm_gpiod_get_optional(dev, "reset", 0); + if (IS_ERR(panel->reset_gpio)) { + err = PTR_ERR(panel->reset_gpio); + dev_err(dev, "failed to request reset GPIO: %d\n", err); + return err; + } + + if (of_property_read_string(dev->of_node, "rockchip,cmd-type", + &cmd_type)) + panel->cmd_type = CMD_TYPE_DEFAULT; + else + panel->cmd_type = get_panel_cmd_type(cmd_type); + + if (panel->cmd_type == CMD_TYPE_SPI) { + panel->spi_sdi_gpio = + devm_gpiod_get_optional(dev, "spi-sdi", 0); + if (IS_ERR(panel->spi_sdi_gpio)) { + err = PTR_ERR(panel->spi_sdi_gpio); + dev_err(dev, "failed to request spi_sdi: %d\n", err); + return err; + } + + panel->spi_scl_gpio = + devm_gpiod_get_optional(dev, "spi-scl", 0); + if (IS_ERR(panel->spi_scl_gpio)) { + err = PTR_ERR(panel->spi_scl_gpio); + dev_err(dev, "failed to request spi_scl: %d\n", err); + return err; + } + + panel->spi_cs_gpio = devm_gpiod_get_optional(dev, "spi-cs", 0); + if (IS_ERR(panel->spi_cs_gpio)) { + err = PTR_ERR(panel->spi_cs_gpio); + dev_err(dev, "failed to request spi_cs: %d\n", err); + return err; + } + gpiod_direction_output(panel->spi_cs_gpio, 1); + gpiod_direction_output(panel->spi_sdi_gpio, 1); + gpiod_direction_output(panel->spi_scl_gpio, 1); + } + panel->power_invert = + of_property_read_bool(dev->of_node, "power-invert"); + + backlight = of_parse_phandle(dev->of_node, "backlight", 0); + if (backlight) { + panel->backlight = of_find_backlight_by_node(backlight); + of_node_put(backlight); + + if (!panel->backlight) { + return -EPROBE_DEFER; + } + } + + ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); + if (ddc) { + panel->ddc = of_find_i2c_adapter_by_node(ddc); + of_node_put(ddc); + + if (!panel->ddc) { + err = -EPROBE_DEFER; + goto free_backlight; + } + } + + drm_panel_init(&panel->base); + panel->base.dev = dev; + panel->base.funcs = &panel_simple_funcs; + + err = drm_panel_add(&panel->base); + if (err < 0) { + goto free_ddc; + } + + dev_set_drvdata(dev, panel); + + return 0; + +free_ddc: + if (panel->ddc) + put_device(&panel->ddc->dev); +free_backlight: + if (panel->backlight) + put_device(&panel->backlight->dev); + + return err; +} + +static int panel_simple_remove(struct device *dev) +{ + struct panel_simple *panel = dev_get_drvdata(dev); + + drm_panel_detach(&panel->base); + drm_panel_remove(&panel->base); + + panel_simple_disable(&panel->base); + panel_simple_unprepare(&panel->base); + + if (panel->ddc) + put_device(&panel->ddc->dev); + + if (panel->backlight) + put_device(&panel->backlight->dev); + + panel_simple_cmds_cleanup(panel); + + return 0; +} + +static void panel_simple_shutdown(struct device *dev) +{ + struct panel_simple *panel = dev_get_drvdata(dev); + + panel_simple_disable(&panel->base); + panel_simple_unprepare(&panel->base); +} + +struct panel_desc_dsi { + struct panel_desc desc; + + unsigned long flags; + enum mipi_dsi_pixel_format format; + unsigned int lanes; +}; + +static const struct of_device_id dsi_of_match[] = { + { + .compatible = "simple-panel-dt", + .data = NULL + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, dsi_of_match); + +static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct panel_simple *panel; + const struct panel_desc_dsi *desc; + const struct of_device_id *id; + const struct panel_desc *pdesc; + int err; + u32 val; + + id = of_match_node(dsi_of_match, dsi->dev.of_node); + if (!id) { + return -ENODEV; + } + + desc = id->data; + + if (desc) { + dsi->mode_flags = desc->flags; + dsi->format = desc->format; + dsi->lanes = desc->lanes; + pdesc = &desc->desc; + } else { + pdesc = NULL; + } + + err = panel_simple_probe(&dsi->dev, pdesc); + if (err < 0) { + return err; + } + + panel = dev_get_drvdata(&dsi->dev); + panel->dsi = dsi; + + if (!of_property_read_u32(dsi->dev.of_node, "dsi,flags", &val)) + dsi->mode_flags = val; + + if (!of_property_read_u32(dsi->dev.of_node, "dsi,format", &val)) + dsi->format = val; + + if (!of_property_read_u32(dsi->dev.of_node, "dsi,lanes", &val)) + dsi->lanes = val; + + return mipi_dsi_attach(dsi); +} + +static int panel_simple_dsi_remove(struct mipi_dsi_device *dsi) +{ + int err; + + err = mipi_dsi_detach(dsi); + if (err < 0) + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); + + return panel_simple_remove(&dsi->dev); +} + +static void panel_simple_dsi_shutdown(struct mipi_dsi_device *dsi) +{ + panel_simple_shutdown(&dsi->dev); +} + +static struct mipi_dsi_driver panel_simple_dsi_driver = { + .driver = { + .name = "panel-simple-dt", + .of_match_table = dsi_of_match, + }, + .probe = panel_simple_dsi_probe, + .remove = panel_simple_dsi_remove, + .shutdown = panel_simple_dsi_shutdown, +}; + +static int __init panel_simple_init(void) +{ + int err; + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) { + err = mipi_dsi_driver_register(&panel_simple_dsi_driver); + if (err < 0) + return err; + } + + return 0; +} +module_init(panel_simple_init); + +static void __exit panel_simple_exit(void) +{ + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&panel_simple_dsi_driver); +} +module_exit(panel_simple_exit); + +MODULE_AUTHOR("Christoph Muellner <christoph.muellner@theobroma-systems.com>"); +MODULE_DESCRIPTION("DRM Driver for Simple Panels with DTS configuration"); +MODULE_LICENSE("GPL and additional rights"); |