From cd117c5e63682b561b29cac1794e2e4413ad6773 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 14 Apr 2023 13:50:08 +0100 Subject: [PATCH] drm/panel: Add panel driver for Waveshare DSI touchscreens Waveshare sell a range of DSI panels of varying sizes, all using a common MCU to control the panel and backlight. Add a panel driver that supports these panels. Signed-off-by: Dave Stevenson --- drivers/gpu/drm/panel/Kconfig | 10 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-waveshare-dsi.c | 411 ++++++++++++++++++++ 3 files changed, 422 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-waveshare-dsi.c --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -720,6 +720,16 @@ config DRM_PANEL_VISIONOX_RM69299 Say Y here if you want to enable support for Visionox RM69299 DSI Video Mode panel. +config DRM_PANEL_WAVESHARE_TOUCHSCREEN + tristate "Waveshare touchscreen panels" + depends on DRM_MIPI_DSI + depends on I2C + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the Waveshare + DSI Touchscreens. To compile this driver as a module, + choose M here. + config DRM_PANEL_WIDECHIPS_WS2401 tristate "Widechips WS2401 DPI panel driver" depends on SPI && GPIOLIB --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -73,5 +73,6 @@ obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) + obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o +obj-$(CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN) += panel-waveshare-dsi.o obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o --- /dev/null +++ b/drivers/gpu/drm/panel/panel-waveshare-dsi.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright © 2023 Raspberry Pi Ltd + * + * Based on panel-raspberrypi-touchscreen by Broadcom + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define WS_DSI_DRIVER_NAME "ws-ts-dsi" + +struct ws_panel { + struct drm_panel base; + struct mipi_dsi_device *dsi; + struct i2c_client *i2c; + const struct drm_display_mode *mode; + enum drm_panel_orientation orientation; +}; + +/* 2.8inch 480x640 + * https://www.waveshare.com/product/raspberry-pi/displays/2.8inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_2_8_mode = { + .clock = 50000, + .hdisplay = 480, + .hsync_start = 480 + 150, + .hsync_end = 480 + 150 + 50, + .htotal = 480 + 150 + 50 + 150, + .vdisplay = 640, + .vsync_start = 640 + 150, + .vsync_end = 640 + 150 + 50, + .vtotal = 640 + 150 + 50 + 150, +}; + +/* 3.4inch 800x800 Round + * https://www.waveshare.com/product/displays/lcd-oled/3.4inch-dsi-lcd-c.htm + */ +static const struct drm_display_mode ws_panel_3_4_mode = { + .clock = 50000, + .hdisplay = 800, + .hsync_start = 800 + 32, + .hsync_end = 800 + 32 + 6, + .htotal = 800 + 32 + 6 + 120, + .vdisplay = 800, + .vsync_start = 800 + 8, + .vsync_end = 800 + 8 + 4, + .vtotal = 800 + 8 + 4 + 16, +}; + +/* 4.0inch 480x800 + * https://www.waveshare.com/product/raspberry-pi/displays/4inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_4_0_mode = { + .clock = 50000, + .hdisplay = 480, + .hsync_start = 480 + 150, + .hsync_end = 480 + 150 + 100, + .htotal = 480 + 150 + 100 + 150, + .vdisplay = 800, + .vsync_start = 800 + 20, + .vsync_end = 800 + 20 + 100, + .vtotal = 800 + 20 + 100 + 20, +}; + +/* 7.0inch C 1024x600 + * https://www.waveshare.com/product/raspberry-pi/displays/lcd-oled/7inch-dsi-lcd-c-with-case-a.htm + */ +static const struct drm_display_mode ws_panel_7_0_c_mode = { + .clock = 50000, + .hdisplay = 1024, + .hsync_start = 1024 + 100, + .hsync_end = 1024 + 100 + 100, + .htotal = 1024 + 100 + 100 + 100, + .vdisplay = 600, + .vsync_start = 600 + 10, + .vsync_end = 600 + 10 + 10, + .vtotal = 600 + 10 + 10 + 10, +}; + +/* 7.9inch 400x1280 + * https://www.waveshare.com/product/raspberry-pi/displays/7.9inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_7_9_mode = { + .clock = 50000, + .hdisplay = 400, + .hsync_start = 400 + 40, + .hsync_end = 400 + 40 + 30, + .htotal = 400 + 40 + 30 + 40, + .vdisplay = 1280, + .vsync_start = 1280 + 20, + .vsync_end = 1280 + 20 + 10, + .vtotal = 1280 + 20 + 10 + 20, +}; + +/* 8.0inch or 10.1inch 1280x800 + * https://www.waveshare.com/product/raspberry-pi/displays/8inch-dsi-lcd-c.htm + * https://www.waveshare.com/product/raspberry-pi/displays/10.1inch-dsi-lcd-c.htm + */ +static const struct drm_display_mode ws_panel_10_1_mode = { + .clock = 76800, + .hdisplay = 1280, + .hsync_start = 1280 + 40, + .hsync_end = 1280 + 40 + 20, + .htotal = 1280 + 40 + 20 + 40, + .vdisplay = 800, + .vsync_start = 800 + 40, + .vsync_end = 800 + 40 + 48, + .vtotal = 800 + 40 + 48 + 40, +}; + +/* 11.9inch 320x1480 + * https://www.waveshare.com/product/raspberry-pi/displays/11.9inch-dsi-lcd.htm + */ +static const struct drm_display_mode ws_panel_11_9_mode = { + .clock = 50000, + .hdisplay = 320, + .hsync_start = 320 + 60, + .hsync_end = 320 + 60 + 60, + .htotal = 320 + 60 + 60 + 120, + .vdisplay = 1480, + .vsync_start = 1480 + 60, + .vsync_end = 1480 + 60 + 60, + .vtotal = 1480 + 60 + 60 + 60, +}; + +static struct ws_panel *panel_to_ts(struct drm_panel *panel) +{ + return container_of(panel, struct ws_panel, base); +} + +static void ws_panel_i2c_write(struct ws_panel *ts, u8 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(ts->i2c, reg, val); + if (ret) + dev_err(&ts->i2c->dev, "I2C write failed: %d\n", ret); +} + +static int ws_panel_disable(struct drm_panel *panel) +{ + struct ws_panel *ts = panel_to_ts(panel); + + ws_panel_i2c_write(ts, 0xad, 0x00); + + return 0; +} + +static int ws_panel_unprepare(struct drm_panel *panel) +{ + return 0; +} + +static int ws_panel_prepare(struct drm_panel *panel) +{ + return 0; +} + +static int ws_panel_enable(struct drm_panel *panel) +{ + struct ws_panel *ts = panel_to_ts(panel); + + ws_panel_i2c_write(ts, 0xad, 0x01); + + return 0; +} + +static int ws_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + struct ws_panel *ts = panel_to_ts(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, ts->mode); + if (!mode) { + dev_err(panel->dev, "failed to add mode %ux%u@%u\n", + ts->mode->hdisplay, + ts->mode->vdisplay, + drm_mode_vrefresh(ts->mode)); + } + + mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + + drm_mode_probed_add(connector, mode); + + connector->display_info.bpc = 8; + connector->display_info.width_mm = 154; + connector->display_info.height_mm = 86; + drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + + /* + * TODO: Remove once all drm drivers call + * drm_connector_set_orientation_from_panel() + */ + drm_connector_set_panel_orientation(connector, ts->orientation); + + return 1; +} + +static enum drm_panel_orientation ws_panel_get_orientation(struct drm_panel *panel) +{ + struct ws_panel *ts = panel_to_ts(panel); + + return ts->orientation; +} + +static const struct drm_panel_funcs ws_panel_funcs = { + .disable = ws_panel_disable, + .unprepare = ws_panel_unprepare, + .prepare = ws_panel_prepare, + .enable = ws_panel_enable, + .get_modes = ws_panel_get_modes, + .get_orientation = ws_panel_get_orientation, +}; + +static int ws_panel_bl_update_status(struct backlight_device *bl) +{ + struct ws_panel *ts = bl_get_data(bl); + + ws_panel_i2c_write(ts, 0xab, 0xff - backlight_get_brightness(bl)); + ws_panel_i2c_write(ts, 0xaa, 0x01); + + return 0; +} + +static const struct backlight_ops ws_panel_bl_ops = { + .update_status = ws_panel_bl_update_status, +}; + +static struct backlight_device * +ws_panel_create_backlight(struct ws_panel *ts) +{ + struct device *dev = ts->base.dev; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .brightness = 255, + .max_brightness = 255, + }; + + return devm_backlight_device_register(dev, dev_name(dev), dev, ts, + &ws_panel_bl_ops, &props); +} + +static int ws_panel_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct ws_panel *ts; + struct device_node *endpoint, *dsi_host_node; + struct mipi_dsi_host *host; + struct mipi_dsi_device_info info = { + .type = WS_DSI_DRIVER_NAME, + .channel = 0, + .node = NULL, + }; + int ret; + + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->mode = of_device_get_match_data(dev); + if (!ts->mode) + return -EINVAL; + + i2c_set_clientdata(i2c, ts); + + ts->i2c = i2c; + + ws_panel_i2c_write(ts, 0xc0, 0x01); + ws_panel_i2c_write(ts, 0xc2, 0x01); + ws_panel_i2c_write(ts, 0xac, 0x01); + + ret = of_drm_get_panel_orientation(dev->of_node, &ts->orientation); + if (ret) { + dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret); + return ret; + } + + /* Look up the DSI host. It needs to probe before we do. */ + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) + return -ENODEV; + + dsi_host_node = of_graph_get_remote_port_parent(endpoint); + if (!dsi_host_node) + goto error; + + host = of_find_mipi_dsi_host_by_node(dsi_host_node); + of_node_put(dsi_host_node); + if (!host) { + of_node_put(endpoint); + return -EPROBE_DEFER; + } + + info.node = of_graph_get_remote_port(endpoint); + if (!info.node) + goto error; + + of_node_put(endpoint); + + ts->dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(ts->dsi)) { + dev_err(dev, "DSI device registration failed: %ld\n", + PTR_ERR(ts->dsi)); + return PTR_ERR(ts->dsi); + } + + drm_panel_init(&ts->base, dev, &ws_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + ts->base.backlight = ws_panel_create_backlight(ts); + if (IS_ERR(ts->base.backlight)) { + ret = PTR_ERR(ts->base.backlight); + dev_err(dev, "Failed to create backlight: %d\n", ret); + return ret; + } + + /* This appears last, as it's what will unblock the DSI host + * driver's component bind function. + */ + drm_panel_add(&ts->base); + + ts->dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_LPM); + ts->dsi->format = MIPI_DSI_FMT_RGB888; + ts->dsi->lanes = 2; + + ret = devm_mipi_dsi_attach(dev, ts->dsi); + + if (ret) + dev_err(dev, "failed to attach dsi to host: %d\n", ret); + + return 0; + +error: + of_node_put(endpoint); + return -ENODEV; +} + +static void ws_panel_remove(struct i2c_client *i2c) +{ + struct ws_panel *ts = i2c_get_clientdata(i2c); + + drm_panel_remove(&ts->base); +} + +static const struct of_device_id ws_panel_of_ids[] = { + { + .compatible = "waveshare,2.8inch-panel", + .data = &ws_panel_2_8_mode, + }, { + .compatible = "waveshare,3.4inch-panel", + .data = &ws_panel_3_4_mode, + }, { + .compatible = "waveshare,4.0inch-panel", + .data = &ws_panel_4_0_mode, + }, { + .compatible = "waveshare,7.0inch-c-panel", + .data = &ws_panel_7_0_c_mode, + }, { + .compatible = "waveshare,7.9inch-panel", + .data = &ws_panel_7_9_mode, + }, { + .compatible = "waveshare,8.0inch-panel", + .data = &ws_panel_10_1_mode, + }, { + .compatible = "waveshare,10.1inch-panel", + .data = &ws_panel_10_1_mode, + }, { + .compatible = "waveshare,11.9inch-panel", + .data = &ws_panel_11_9_mode, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, ws_panel_of_ids); + +static struct i2c_driver ws_panel_driver = { + .driver = { + .name = "ws_touchscreen", + .of_match_table = ws_panel_of_ids, + }, + .probe = ws_panel_probe, + .remove = ws_panel_remove, +}; +module_i2c_driver(ws_panel_driver); + +MODULE_AUTHOR("Dave Stevenson "); +MODULE_DESCRIPTION("Waveshare DSI panel driver"); +MODULE_LICENSE("GPL");