Hi,
A while back I had the idea to turn a Raspberry Pi Zero into a $5 USB to HDMI/SDTV/DSI/DPI display adapter.
The reason for calling it 'Generic' is so anyone can make a USB display/adapter against this driver, all that's needed is to add a USB vid:pid.
Unfortunately I've had some compounding health problems that have severally limited the time I can spend in front of a computer. For this reason I've decided to keep the gadget driver out-of-tree and focus on getting the host driver merged first.
See the wiki[1] for more information and images for the Raspberry Pi Zero/4.
One big change this time is that I've followed Peter Stuge's advice to not let DRM stuff leak into the USB protocol. This has made the protocol easier to understand just from reading the header file.
Noralf.
[1] https://github.com/notro/gud/wiki
Noralf Trønnes (3): drm/uapi: Add USB connector type drm/probe-helper: Check epoch counter in output_poll_execute() drm: Add Generic USB Display driver
MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_probe_helper.c | 7 +- drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 722 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 620 ++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 148 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 472 ++++++++++++++++++ include/drm/gud.h | 356 ++++++++++++++ include/uapi/drm/drm_mode.h | 1 + 12 files changed, 2354 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
Add a connector type for USB connected display panels.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
/** * struct drm_mode_get_connector - Get connector metadata.
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote:
Add a connector type for USB connected display panels.
Signed-off-by: Noralf Trønnes noralf@tronnes.org
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
/**
- struct drm_mode_get_connector - Get connector metadata.
-- 2.23.0
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi Noralf,
glad to hear from you! Welcome back!
Am 20.01.21 um 18:42 schrieb Daniel Vetter:
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote:
Add a connector type for USB connected display panels.
Signed-off-by: Noralf Trønnes noralf@tronnes.org
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
I would not call it USB. I could imagine that at some point a generic USB protocol could serve simple displays (i.e. in the sense of USB HID or data or imaging). (Maybe Thunderbold already counts.) Anyway, USB should be reserved for this case.
Best regards Thomas
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
/**
- struct drm_mode_get_connector - Get connector metadata.
-- 2.23.0
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Jan 21, 2021 at 8:45 AM Thomas Zimmermann tzimmermann@suse.de wrote:
Hi Noralf,
glad to hear from you! Welcome back!
Am 20.01.21 um 18:42 schrieb Daniel Vetter:
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote:
Add a connector type for USB connected display panels.
Signed-off-by: Noralf Trønnes noralf@tronnes.org
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
I would not call it USB. I could imagine that at some point a generic USB protocol could serve simple displays (i.e. in the sense of USB HID or data or imaging). (Maybe Thunderbold already counts.) Anyway, USB should be reserved for this case.
We end up calling those DisplayPort, since that's what's being transported over thunderbolt or usb-C. So the usb connector would be called usb-C. I think the reason we don't do fancy connector names is that adding them is a bit a pain. Plus drm/i915 specifically has some very quirky connector enumerating that doesn't match much with reality unfortunately anyway :-/ -Daniel
Best regards Thomas
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
/**
- struct drm_mode_get_connector - Get connector metadata.
-- 2.23.0
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
On Thursday, January 21st, 2021 at 9:27 AM, Daniel Vetter daniel@ffwll.ch wrote:
I would not call it USB. I could imagine that at some point a generic USB protocol could serve simple displays (i.e. in the sense of USB HID or data or imaging). (Maybe Thunderbold already counts.) Anyway, USB should be reserved for this case.
We end up calling those DisplayPort, since that's what's being transported over thunderbolt or usb-C. So the usb connector would be called usb-C. I think the reason we don't do fancy connector names is that adding them is a bit a pain. Plus drm/i915 specifically has some very quirky connector enumerating that doesn't match much with reality unfortunately anyway :-/
Maybe could add "USB-C" to the list of subconnector types and use that?
Hi
Am 21.01.21 um 09:27 schrieb Daniel Vetter:
On Thu, Jan 21, 2021 at 8:45 AM Thomas Zimmermann tzimmermann@suse.de wrote:
Hi Noralf,
glad to hear from you! Welcome back!
Am 20.01.21 um 18:42 schrieb Daniel Vetter:
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote:
Add a connector type for USB connected display panels.
Signed-off-by: Noralf Trønnes noralf@tronnes.org
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
I would not call it USB. I could imagine that at some point a generic USB protocol could serve simple displays (i.e. in the sense of USB HID or data or imaging). (Maybe Thunderbold already counts.) Anyway, USB should be reserved for this case.
We end up calling those DisplayPort, since that's what's being transported over thunderbolt or usb-C. So the usb connector would be called usb-C. I think the reason we don't do fancy connector names is that adding them is a bit a pain. Plus drm/i915 specifically has some very quirky connector enumerating that doesn't match much with reality unfortunately anyway :-/
In the case of the other USB drivers, IIRC we use the connector type that is at the output (i.e., HDMI in the case of udl). I think we should do the same here. Or use 'Unknown'.
Best regards Thomas
-Daniel
Best regards Thomas
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
/** * struct drm_mode_get_connector - Get connector metadata. -- 2.23.0
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
Den 21.01.2021 11.01, skrev Thomas Zimmermann:
Hi
Am 21.01.21 um 09:27 schrieb Daniel Vetter:
On Thu, Jan 21, 2021 at 8:45 AM Thomas Zimmermann tzimmermann@suse.de wrote:
Hi Noralf,
glad to hear from you! Welcome back!
Thanks Thomas!
Am 20.01.21 um 18:42 schrieb Daniel Vetter:
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote:
Add a connector type for USB connected display panels.
Signed-off-by: Noralf Trønnes noralf@tronnes.org
I have forgotten to update drm_connector_enum_list which maps type to name.
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
I would not call it USB. I could imagine that at some point a generic USB protocol could serve simple displays (i.e. in the sense of USB HID or data or imaging). (Maybe Thunderbold already counts.) Anyway, USB should be reserved for this case.
We end up calling those DisplayPort, since that's what's being transported over thunderbolt or usb-C. So the usb connector would be called usb-C. I think the reason we don't do fancy connector names is that adding them is a bit a pain. Plus drm/i915 specifically has some very quirky connector enumerating that doesn't match much with reality unfortunately anyway :-/
In the case of the other USB drivers, IIRC we use the connector type that is at the output (i.e., HDMI in the case of udl). I think we should do the same here. Or use 'Unknown'.
There are 2 DRM USB drivers and they use: - udl: DRM_MODE_CONNECTOR_DVII - gm12u320: DRM_MODE_CONNECTOR_VGA
gm12u320 is a mini projector so it doesn't actually have a VGA connector. I have never seen a udl device but I assume it has a DVII connector?
For display adapters it makes sense to use the connector on the adapter as the reported connector, but for display panels that don't have any connector except for the cable that is connected to the hosts USB connector, why can't it be called a USB connector? That's the connector the user sees.
Ofc as Daniel mentions it's a downside that userspace doesn't know about the connector type, and who knows when it will updated (if I don't do it). Weston will name it: "UNNAMED-%d" Mutter: "Unknown%d-%d" X: "Unknown%d-%d"
Sam and Laurent has discussed adding a PANEL connector type instead of adding more connector types for panel connectors. I think that would have been a better choice instead of the SPI connector type that I added in 2019. But I think PANEL was meant for panels connected to an internal connector.
Here's my protocol connector types and how it's mapped to DRM:
#define GUD_CONNECTOR_TYPE_PANEL 0 #define GUD_CONNECTOR_TYPE_VGA 1 #define GUD_CONNECTOR_TYPE_COMPOSITE 2 #define GUD_CONNECTOR_TYPE_SVIDEO 3 #define GUD_CONNECTOR_TYPE_COMPONENT 4 #define GUD_CONNECTOR_TYPE_DVI 5 #define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 #define GUD_CONNECTOR_TYPE_HDMI 7
static int gud_gadget_ctrl_get_connector(struct gud_gadget *gdg, unsigned int index, struct gud_connector_descriptor_req *desc) { ... gconn = &gdg->connectors[index];
switch (gconn->connector->connector_type) { case DRM_MODE_CONNECTOR_VGA: desc->connector_type = GUD_CONNECTOR_TYPE_VGA; break; case DRM_MODE_CONNECTOR_DVII: fallthrough; case DRM_MODE_CONNECTOR_DVID: fallthrough; case DRM_MODE_CONNECTOR_DVIA: desc->connector_type = GUD_CONNECTOR_TYPE_DVI; break; case DRM_MODE_CONNECTOR_Composite: desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; break; case DRM_MODE_CONNECTOR_SVIDEO: desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; break; case DRM_MODE_CONNECTOR_Component: desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; break; case DRM_MODE_CONNECTOR_DisplayPort: desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; break; case DRM_MODE_CONNECTOR_HDMIA: fallthrough; case DRM_MODE_CONNECTOR_HDMIB: desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; break; default: desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; break; };
int gud_connector_create(struct gud_device *gdrm, unsigned int index) { ... switch (desc.connector_type) { case GUD_CONNECTOR_TYPE_PANEL: connector_type = DRM_MODE_CONNECTOR_USB; break; case GUD_CONNECTOR_TYPE_VGA: connector_type = DRM_MODE_CONNECTOR_VGA; break; case GUD_CONNECTOR_TYPE_DVI: connector_type = DRM_MODE_CONNECTOR_DVID; break; case GUD_CONNECTOR_TYPE_COMPOSITE: connector_type = DRM_MODE_CONNECTOR_Composite; break; case GUD_CONNECTOR_TYPE_SVIDEO: connector_type = DRM_MODE_CONNECTOR_SVIDEO; break; case GUD_CONNECTOR_TYPE_COMPONENT: connector_type = DRM_MODE_CONNECTOR_Component; break; case GUD_CONNECTOR_TYPE_DISPLAYPORT: connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; case GUD_CONNECTOR_TYPE_HDMI: connector_type = DRM_MODE_CONNECTOR_HDMIA; break; default: /* future types */ connector_type = DRM_MODE_CONNECTOR_USB; break; };
Noralf.
Best regards Thomas
-Daniel
Best regards Thomas
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
/** * struct drm_mode_get_connector - Get connector metadata. -- 2.23.0
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi
Am 21.01.21 um 19:07 schrieb Noralf Trønnes:
Den 21.01.2021 11.01, skrev Thomas Zimmermann:
Hi
Am 21.01.21 um 09:27 schrieb Daniel Vetter:
On Thu, Jan 21, 2021 at 8:45 AM Thomas Zimmermann tzimmermann@suse.de wrote:
Hi Noralf,
glad to hear from you! Welcome back!
Thanks Thomas!
Am 20.01.21 um 18:42 schrieb Daniel Vetter:
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote:
Add a connector type for USB connected display panels.
Signed-off-by: Noralf Trønnes noralf@tronnes.org
I have forgotten to update drm_connector_enum_list which maps type to name.
include/uapi/drm/drm_mode.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index fed66a03c7ae..33024cc5d26e 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -367,6 +367,7 @@ enum drm_mode_subconnector { #define DRM_MODE_CONNECTOR_DPI 17 #define DRM_MODE_CONNECTOR_WRITEBACK 18 #define DRM_MODE_CONNECTOR_SPI 19 +#define DRM_MODE_CONNECTOR_USB 20
I would not call it USB. I could imagine that at some point a generic USB protocol could serve simple displays (i.e. in the sense of USB HID or data or imaging). (Maybe Thunderbold already counts.) Anyway, USB should be reserved for this case.
We end up calling those DisplayPort, since that's what's being transported over thunderbolt or usb-C. So the usb connector would be called usb-C. I think the reason we don't do fancy connector names is that adding them is a bit a pain. Plus drm/i915 specifically has some very quirky connector enumerating that doesn't match much with reality unfortunately anyway :-/
In the case of the other USB drivers, IIRC we use the connector type that is at the output (i.e., HDMI in the case of udl). I think we should do the same here. Or use 'Unknown'.
There are 2 DRM USB drivers and they use:
- udl: DRM_MODE_CONNECTOR_DVII
Mine has plain old VGA. Maybe we should change generally this to Unknown.
- gm12u320: DRM_MODE_CONNECTOR_VGA
gm12u320 is a mini projector so it doesn't actually have a VGA connector. I have never seen a udl device but I assume it has a DVII connector?
For display adapters it makes sense to use the connector on the adapter as the reported connector, but for display panels that don't have any connector except for the cable that is connected to the hosts USB connector, why can't it be called a USB connector? That's the connector the user sees.
It's not the relevant connector for the display output. USB is the bus system. (Making your argument in terms of discrete GPUs, the connector would always be PCI then.)
Ofc as Daniel mentions it's a downside that userspace doesn't know about the connector type, and who knows when it will updated (if I don't do it). Weston will name it: "UNNAMED-%d" Mutter: "Unknown%d-%d" X: "Unknown%d-%d"
Sam and Laurent has discussed adding a PANEL connector type instead of adding more connector types for panel connectors. I think that would have been a better choice instead of the SPI connector type that I added in 2019. But I think PANEL was meant for panels connected to an internal connector.
Here's my protocol connector types and how it's mapped to DRM:
#define GUD_CONNECTOR_TYPE_PANEL 0 #define GUD_CONNECTOR_TYPE_VGA 1 #define GUD_CONNECTOR_TYPE_COMPOSITE 2 #define GUD_CONNECTOR_TYPE_SVIDEO 3 #define GUD_CONNECTOR_TYPE_COMPONENT 4 #define GUD_CONNECTOR_TYPE_DVI 5 #define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 #define GUD_CONNECTOR_TYPE_HDMI 7
static int gud_gadget_ctrl_get_connector(struct gud_gadget *gdg, unsigned int index, struct gud_connector_descriptor_req *desc) { ... gconn = &gdg->connectors[index];
switch (gconn->connector->connector_type) { case DRM_MODE_CONNECTOR_VGA: desc->connector_type = GUD_CONNECTOR_TYPE_VGA; break; case DRM_MODE_CONNECTOR_DVII: fallthrough; case DRM_MODE_CONNECTOR_DVID: fallthrough; case DRM_MODE_CONNECTOR_DVIA: desc->connector_type = GUD_CONNECTOR_TYPE_DVI; break; case DRM_MODE_CONNECTOR_Composite: desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; break; case DRM_MODE_CONNECTOR_SVIDEO: desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; break; case DRM_MODE_CONNECTOR_Component: desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; break; case DRM_MODE_CONNECTOR_DisplayPort: desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; break; case DRM_MODE_CONNECTOR_HDMIA: fallthrough; case DRM_MODE_CONNECTOR_HDMIB: desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; break; default: desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; break; };
int gud_connector_create(struct gud_device *gdrm, unsigned int index) { ... switch (desc.connector_type) { case GUD_CONNECTOR_TYPE_PANEL: connector_type = DRM_MODE_CONNECTOR_USB; break; case GUD_CONNECTOR_TYPE_VGA: connector_type = DRM_MODE_CONNECTOR_VGA; break; case GUD_CONNECTOR_TYPE_DVI: connector_type = DRM_MODE_CONNECTOR_DVID; break; case GUD_CONNECTOR_TYPE_COMPOSITE: connector_type = DRM_MODE_CONNECTOR_Composite; break; case GUD_CONNECTOR_TYPE_SVIDEO: connector_type = DRM_MODE_CONNECTOR_SVIDEO; break; case GUD_CONNECTOR_TYPE_COMPONENT: connector_type = DRM_MODE_CONNECTOR_Component; break; case GUD_CONNECTOR_TYPE_DISPLAYPORT: connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; case GUD_CONNECTOR_TYPE_HDMI: connector_type = DRM_MODE_CONNECTOR_HDMIA; break; default: /* future types */ connector_type = DRM_MODE_CONNECTOR_USB;
The more I look at it the more I think it should be 'Unknown' here.
BTW, can I try this out somehow? I do have an RPi3. Do I need a special disk image?
Best regards Thomas
break;
};
Noralf.
Best regards Thomas
-Daniel
Best regards Thomas
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch
/** * struct drm_mode_get_connector - Get connector metadata. -- 2.23.0
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Am 22.01.21 um 08:54 schrieb Thomas Zimmermann: The more I look at it the more I think it should be 'Unknown' here.
BTW, can I try this out somehow? I do have an RPi3. Do I need a special disk image?
Oh, I saw that wiki url now. I'll check this out.
Best regards Thomas
break; };
Noralf.
Best regards Thomas
-Daniel
Best regards Thomas
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch > > /** > * struct drm_mode_get_connector - Get connector metadata. > -- > 2.23.0 > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Den 22.01.2021 08.54, skrev Thomas Zimmermann:
Hi
Am 21.01.21 um 19:07 schrieb Noralf Trønnes:
Den 21.01.2021 11.01, skrev Thomas Zimmermann:
Hi
Am 21.01.21 um 09:27 schrieb Daniel Vetter:
On Thu, Jan 21, 2021 at 8:45 AM Thomas Zimmermann tzimmermann@suse.de wrote:
Hi Noralf,
glad to hear from you! Welcome back!
Thanks Thomas!
Am 20.01.21 um 18:42 schrieb Daniel Vetter:
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote: > > Add a connector type for USB connected display panels. > > Signed-off-by: Noralf Trønnes noralf@tronnes.org > ---
I have forgotten to update drm_connector_enum_list which maps type to name.
> include/uapi/drm/drm_mode.h | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/include/uapi/drm/drm_mode.h > b/include/uapi/drm/drm_mode.h > index fed66a03c7ae..33024cc5d26e 100644 > --- a/include/uapi/drm/drm_mode.h > +++ b/include/uapi/drm/drm_mode.h > @@ -367,6 +367,7 @@ enum drm_mode_subconnector { > #define DRM_MODE_CONNECTOR_DPI 17 > #define DRM_MODE_CONNECTOR_WRITEBACK 18 > #define DRM_MODE_CONNECTOR_SPI 19 > +#define DRM_MODE_CONNECTOR_USB 20
I would not call it USB. I could imagine that at some point a generic USB protocol could serve simple displays (i.e. in the sense of USB HID or data or imaging). (Maybe Thunderbold already counts.) Anyway, USB should be reserved for this case.
We end up calling those DisplayPort, since that's what's being transported over thunderbolt or usb-C. So the usb connector would be called usb-C. I think the reason we don't do fancy connector names is that adding them is a bit a pain. Plus drm/i915 specifically has some very quirky connector enumerating that doesn't match much with reality unfortunately anyway :-/
In the case of the other USB drivers, IIRC we use the connector type that is at the output (i.e., HDMI in the case of udl). I think we should do the same here. Or use 'Unknown'.
There are 2 DRM USB drivers and they use:
- udl: DRM_MODE_CONNECTOR_DVII
Mine has plain old VGA.
Ok, maybe the Displaylink protocol doesn't provide info about the connector type or if it does the driver doesn't know about it.
Maybe we should change generally this to Unknown.
- gm12u320: DRM_MODE_CONNECTOR_VGA
gm12u320 is a mini projector so it doesn't actually have a VGA connector. I have never seen a udl device but I assume it has a DVII connector?
For display adapters it makes sense to use the connector on the adapter as the reported connector, but for display panels that don't have any connector except for the cable that is connected to the hosts USB connector, why can't it be called a USB connector? That's the connector the user sees.
It's not the relevant connector for the display output. USB is the bus system. (Making your argument in terms of discrete GPUs, the connector would always be PCI then.)
Yes strictly speaking USB is the bus and the connectors have other names: USB (type)-A, USB-C etc., but I don't understand the problem here. Why does it matter that it is a bus?
And wrt PCI it wouldn't be a PCI connector if the card has some other connector for the display, but if it was possible to connect a display directly to the PCI connector, then yes I would call that a PCI connector.
This begs the question: Why does the kernel provide info to userspace about the connector type?
My take is that it is so the user can know which display is connected to which port on the computer.
What's your opinion?
Ofc as Daniel mentions it's a downside that userspace doesn't know about the connector type, and who knows when it will updated (if I don't do it). Weston will name it: "UNNAMED-%d" Mutter: "Unknown%d-%d" X: "Unknown%d-%d"
Sam and Laurent has discussed adding a PANEL connector type instead of adding more connector types for panel connectors. I think that would have been a better choice instead of the SPI connector type that I added in 2019. But I think PANEL was meant for panels connected to an internal connector.
Here's my protocol connector types and how it's mapped to DRM:
#define GUD_CONNECTOR_TYPE_PANEL 0 #define GUD_CONNECTOR_TYPE_VGA 1 #define GUD_CONNECTOR_TYPE_COMPOSITE 2 #define GUD_CONNECTOR_TYPE_SVIDEO 3 #define GUD_CONNECTOR_TYPE_COMPONENT 4 #define GUD_CONNECTOR_TYPE_DVI 5 #define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 #define GUD_CONNECTOR_TYPE_HDMI 7
static int gud_gadget_ctrl_get_connector(struct gud_gadget *gdg, unsigned int index, struct gud_connector_descriptor_req *desc) { ... gconn = &gdg->connectors[index];
switch (gconn->connector->connector_type) { case DRM_MODE_CONNECTOR_VGA: desc->connector_type = GUD_CONNECTOR_TYPE_VGA; break; case DRM_MODE_CONNECTOR_DVII: fallthrough; case DRM_MODE_CONNECTOR_DVID: fallthrough; case DRM_MODE_CONNECTOR_DVIA: desc->connector_type = GUD_CONNECTOR_TYPE_DVI; break; case DRM_MODE_CONNECTOR_Composite: desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; break; case DRM_MODE_CONNECTOR_SVIDEO: desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; break; case DRM_MODE_CONNECTOR_Component: desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; break; case DRM_MODE_CONNECTOR_DisplayPort: desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; break; case DRM_MODE_CONNECTOR_HDMIA: fallthrough; case DRM_MODE_CONNECTOR_HDMIB: desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; break; default: desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; break; };
int gud_connector_create(struct gud_device *gdrm, unsigned int index) { ... switch (desc.connector_type) { case GUD_CONNECTOR_TYPE_PANEL: connector_type = DRM_MODE_CONNECTOR_USB; break; case GUD_CONNECTOR_TYPE_VGA: connector_type = DRM_MODE_CONNECTOR_VGA; break; case GUD_CONNECTOR_TYPE_DVI: connector_type = DRM_MODE_CONNECTOR_DVID; break; case GUD_CONNECTOR_TYPE_COMPOSITE: connector_type = DRM_MODE_CONNECTOR_Composite; break; case GUD_CONNECTOR_TYPE_SVIDEO: connector_type = DRM_MODE_CONNECTOR_SVIDEO; break; case GUD_CONNECTOR_TYPE_COMPONENT: connector_type = DRM_MODE_CONNECTOR_Component; break; case GUD_CONNECTOR_TYPE_DISPLAYPORT: connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; case GUD_CONNECTOR_TYPE_HDMI: connector_type = DRM_MODE_CONNECTOR_HDMIA; break; default: /* future types */ connector_type = DRM_MODE_CONNECTOR_USB;
The more I look at it the more I think it should be 'Unknown' here.
I don't understand this, how will that be better for the user?
BTW, can I try this out somehow? I do have an RPi3. Do I need a special disk image?
The Pi3 doesn'have a USB device/otg connector so I haven't made an image for that one. Only the Pi Zero, model A and Pi 4 have that.
The Pi2 and Pi3 have a USB hub on the soc's single USB port.
Noralf.
Best regards Thomas
break; };
Noralf.
Best regards Thomas
-Daniel
Best regards Thomas
Beware, new connector types have in the past resulted in userspace burning&crashing. Maybe it's become better ...
Acked-by: Daniel Vetter daniel.vetter@ffwll.ch > > /** > * struct drm_mode_get_connector - Get connector metadata. > -- > 2.23.0 > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi
Am 22.01.21 um 12:44 schrieb Noralf Trønnes:
And wrt PCI it wouldn't be a PCI connector if the card has some other connector for the display, but if it was possible to connect a display directly to the PCI connector, then yes I would call that a PCI connector.
You're not connecting a display to the computer. You're connecting an RPi and then connect the display to the RPi. The RPi acts like an external graphics card.
This begs the question: Why does the kernel provide info to userspace about the connector type?
My take is that it is so the user can know which display is connected to which port on the computer.
This exactly illustrates the problem with the current naming. For a single output the distinction between bus and connector might be fuzzy. As soon as a connected SoC contains multiple connectors. The user then sees names such as card1-USB-0 and card1-USB-1, which makes no sense.
What's your opinion?
Ofc as Daniel mentions it's a downside that userspace doesn't know about the connector type, and who knows when it will updated (if I don't do it). Weston will name it: "UNNAMED-%d" Mutter: "Unknown%d-%d" X: "Unknown%d-%d"
Sam and Laurent has discussed adding a PANEL connector type instead of adding more connector types for panel connectors. I think that would have been a better choice instead of the SPI connector type that I added in 2019. But I think PANEL was meant for panels connected to an internal connector.
Here's my protocol connector types and how it's mapped to DRM:
#define GUD_CONNECTOR_TYPE_PANEL 0 #define GUD_CONNECTOR_TYPE_VGA 1 #define GUD_CONNECTOR_TYPE_COMPOSITE 2 #define GUD_CONNECTOR_TYPE_SVIDEO 3 #define GUD_CONNECTOR_TYPE_COMPONENT 4 #define GUD_CONNECTOR_TYPE_DVI 5 #define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 #define GUD_CONNECTOR_TYPE_HDMI 7
static int gud_gadget_ctrl_get_connector(struct gud_gadget *gdg, unsigned int index, struct gud_connector_descriptor_req *desc) { ... gconn = &gdg->connectors[index];
switch (gconn->connector->connector_type) { case DRM_MODE_CONNECTOR_VGA: desc->connector_type = GUD_CONNECTOR_TYPE_VGA; break; case DRM_MODE_CONNECTOR_DVII: fallthrough; case DRM_MODE_CONNECTOR_DVID: fallthrough; case DRM_MODE_CONNECTOR_DVIA: desc->connector_type = GUD_CONNECTOR_TYPE_DVI; break; case DRM_MODE_CONNECTOR_Composite: desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; break; case DRM_MODE_CONNECTOR_SVIDEO: desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; break; case DRM_MODE_CONNECTOR_Component: desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; break; case DRM_MODE_CONNECTOR_DisplayPort: desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; break; case DRM_MODE_CONNECTOR_HDMIA: fallthrough; case DRM_MODE_CONNECTOR_HDMIB: desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; break; default: desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; break; };
int gud_connector_create(struct gud_device *gdrm, unsigned int index) { ... switch (desc.connector_type) { case GUD_CONNECTOR_TYPE_PANEL: connector_type = DRM_MODE_CONNECTOR_USB; break; case GUD_CONNECTOR_TYPE_VGA: connector_type = DRM_MODE_CONNECTOR_VGA; break; case GUD_CONNECTOR_TYPE_DVI: connector_type = DRM_MODE_CONNECTOR_DVID; break; case GUD_CONNECTOR_TYPE_COMPOSITE: connector_type = DRM_MODE_CONNECTOR_Composite; break; case GUD_CONNECTOR_TYPE_SVIDEO: connector_type = DRM_MODE_CONNECTOR_SVIDEO; break; case GUD_CONNECTOR_TYPE_COMPONENT: connector_type = DRM_MODE_CONNECTOR_Component; break; case GUD_CONNECTOR_TYPE_DISPLAYPORT: connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; case GUD_CONNECTOR_TYPE_HDMI: connector_type = DRM_MODE_CONNECTOR_HDMIA; break; default: /* future types */ connector_type = DRM_MODE_CONNECTOR_USB;
The more I look at it the more I think it should be 'Unknown' here.
I don't understand this, how will that be better for the user?
As I said before, the display is not connected via USB. The RPi (i.e., graphics card) is. The naming would be off.
Best regards Thomas
BTW, can I try this out somehow? I do have an RPi3. Do I need a special disk image?
The Pi3 doesn'have a USB device/otg connector so I haven't made an image for that one. Only the Pi Zero, model A and Pi 4 have that.
The Pi2 and Pi3 have a USB hub on the soc's single USB port.
Noralf.
Best regards Thomas
break; };
Noralf.
Best regards Thomas
-Daniel
Best regards Thomas
> > Beware, new connector types have in the past resulted in userspace > burning&crashing. Maybe it's become better ... > > Acked-by: Daniel Vetter daniel.vetter@ffwll.ch >> >> /** >> * struct drm_mode_get_connector - Get connector metadata. >> -- >> 2.23.0 >> >> _______________________________________________ >> dri-devel mailing list >> dri-devel@lists.freedesktop.org >> https://lists.freedesktop.org/mailman/listinfo/dri-devel > > >
-- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Felix Imendörffer
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Den 22.01.2021 13.47, skrev Thomas Zimmermann:
Hi
Am 22.01.21 um 12:44 schrieb Noralf Trønnes:
And wrt PCI it wouldn't be a PCI connector if the card has some other connector for the display, but if it was possible to connect a display directly to the PCI connector, then yes I would call that a PCI connector.
You're not connecting a display to the computer. You're connecting an RPi and then connect the display to the RPi. The RPi acts like an external graphics card.
This begs the question: Why does the kernel provide info to userspace about the connector type?
My take is that it is so the user can know which display is connected to which port on the computer.
This exactly illustrates the problem with the current naming. For a single output the distinction between bus and connector might be fuzzy. As soon as a connected SoC contains multiple connectors. The user then sees names such as card1-USB-0 and card1-USB-1, which makes no sense.
If you look at the code I pasted in, you'll see that the SoC connector types are passed through to the host driver as-is unless they are panel connectors like DSI/DPI, which will be interpreted as USB (the protocol does support multiple connectors, but only one can be used at a time).
So for the Pi4 as a display adapter, the host will see card1-HDMI-0 and card1-HDMI-1, the same is true for the composite output (if enabled) it shows up as card1-Composite-0 (can't be enabled together with HDMI).
If the Pi4 is used together with a DSI connected touchscreen, it makes sense to disable the SoC HDMI outputs and the host only will see the board as card1-USB-0 (I haven't done this exercise yet since there's problems with getting the official Pi touchscreen to work with vc4 on Pi4).
The USB connector type is most important for tiny displays that is microcontroller based without Linux running. There are lots of tiny SPI displays and I expect this market to shift towards USB because it much easier to connect and the display will be useable on a desktop/server computer as status displays perhaps. But embedded will also benefit from having these displays USB interfaced.
Another use case I see is repurposing old Android tablets as USB displays that can be connected to any computer and become a touchscreen. In this case I also want the connector to be called card1-USB-0 (I haven't done any work in this area, old Android is fbdev so some work is needed for this to happen).
Noralf.
What's your opinion?
Ofc as Daniel mentions it's a downside that userspace doesn't know about the connector type, and who knows when it will updated (if I don't do it). Weston will name it: "UNNAMED-%d" Mutter: "Unknown%d-%d" X: "Unknown%d-%d"
Sam and Laurent has discussed adding a PANEL connector type instead of adding more connector types for panel connectors. I think that would have been a better choice instead of the SPI connector type that I added in 2019. But I think PANEL was meant for panels connected to an internal connector.
Here's my protocol connector types and how it's mapped to DRM:
#define GUD_CONNECTOR_TYPE_PANEL 0 #define GUD_CONNECTOR_TYPE_VGA 1 #define GUD_CONNECTOR_TYPE_COMPOSITE 2 #define GUD_CONNECTOR_TYPE_SVIDEO 3 #define GUD_CONNECTOR_TYPE_COMPONENT 4 #define GUD_CONNECTOR_TYPE_DVI 5 #define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 #define GUD_CONNECTOR_TYPE_HDMI 7
static int gud_gadget_ctrl_get_connector(struct gud_gadget *gdg, unsigned int index, struct gud_connector_descriptor_req *desc) { ... gconn = &gdg->connectors[index];
switch (gconn->connector->connector_type) { case DRM_MODE_CONNECTOR_VGA: desc->connector_type = GUD_CONNECTOR_TYPE_VGA; break; case DRM_MODE_CONNECTOR_DVII: fallthrough; case DRM_MODE_CONNECTOR_DVID: fallthrough; case DRM_MODE_CONNECTOR_DVIA: desc->connector_type = GUD_CONNECTOR_TYPE_DVI; break; case DRM_MODE_CONNECTOR_Composite: desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; break; case DRM_MODE_CONNECTOR_SVIDEO: desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; break; case DRM_MODE_CONNECTOR_Component: desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; break; case DRM_MODE_CONNECTOR_DisplayPort: desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; break; case DRM_MODE_CONNECTOR_HDMIA: fallthrough; case DRM_MODE_CONNECTOR_HDMIB: desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; break; default: desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; break; };
int gud_connector_create(struct gud_device *gdrm, unsigned int index) { ... switch (desc.connector_type) { case GUD_CONNECTOR_TYPE_PANEL: connector_type = DRM_MODE_CONNECTOR_USB; break; case GUD_CONNECTOR_TYPE_VGA: connector_type = DRM_MODE_CONNECTOR_VGA; break; case GUD_CONNECTOR_TYPE_DVI: connector_type = DRM_MODE_CONNECTOR_DVID; break; case GUD_CONNECTOR_TYPE_COMPOSITE: connector_type = DRM_MODE_CONNECTOR_Composite; break; case GUD_CONNECTOR_TYPE_SVIDEO: connector_type = DRM_MODE_CONNECTOR_SVIDEO; break; case GUD_CONNECTOR_TYPE_COMPONENT: connector_type = DRM_MODE_CONNECTOR_Component; break; case GUD_CONNECTOR_TYPE_DISPLAYPORT: connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; case GUD_CONNECTOR_TYPE_HDMI: connector_type = DRM_MODE_CONNECTOR_HDMIA; break; default: /* future types */ connector_type = DRM_MODE_CONNECTOR_USB;
The more I look at it the more I think it should be 'Unknown' here.
I don't understand this, how will that be better for the user?
As I said before, the display is not connected via USB. The RPi (i.e., graphics card) is. The naming would be off.
Best regards Thomas
BTW, can I try this out somehow? I do have an RPi3. Do I need a special disk image?
The Pi3 doesn'have a USB device/otg connector so I haven't made an image for that one. Only the Pi Zero, model A and Pi 4 have that.
The Pi2 and Pi3 have a USB hub on the soc's single USB port.
Noralf.
Best regards Thomas
break; };
Noralf.
Best regards Thomas
-Daniel
> > Best regards > Thomas > >> >> Beware, new connector types have in the past resulted in userspace >> burning&crashing. Maybe it's become better ... >> >> Acked-by: Daniel Vetter daniel.vetter@ffwll.ch >>> >>> /** >>> * struct drm_mode_get_connector - Get connector metadata. >>> -- >>> 2.23.0 >>> >>> _______________________________________________ >>> dri-devel mailing list >>> dri-devel@lists.freedesktop.org >>> https://lists.freedesktop.org/mailman/listinfo/dri-devel >> >> >> > > -- > Thomas Zimmermann > Graphics Driver Developer > SUSE Software Solutions Germany GmbH > Maxfeldstr. 5, 90409 Nürnberg, Germany > (HRB 36809, AG Nürnberg) > Geschäftsführer: Felix Imendörffer >
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Hi
Am 22.01.21 um 15:35 schrieb Noralf Trønnes:
Den 22.01.2021 13.47, skrev Thomas Zimmermann:
Hi
Am 22.01.21 um 12:44 schrieb Noralf Trønnes:
And wrt PCI it wouldn't be a PCI connector if the card has some other connector for the display, but if it was possible to connect a display directly to the PCI connector, then yes I would call that a PCI connector.
You're not connecting a display to the computer. You're connecting an RPi and then connect the display to the RPi. The RPi acts like an external graphics card.
This begs the question: Why does the kernel provide info to userspace about the connector type?
My take is that it is so the user can know which display is connected to which port on the computer.
This exactly illustrates the problem with the current naming. For a single output the distinction between bus and connector might be fuzzy. As soon as a connected SoC contains multiple connectors. The user then sees names such as card1-USB-0 and card1-USB-1, which makes no sense.
If you look at the code I pasted in, you'll see that the SoC connector types are passed through to the host driver as-is unless they are panel connectors like DSI/DPI, which will be interpreted as USB (the protocol does support multiple connectors, but only one can be used at a time).
So for the Pi4 as a display adapter, the host will see card1-HDMI-0 and card1-HDMI-1, the same is true for the composite output (if enabled) it shows up as card1-Composite-0 (can't be enabled together with HDMI).
If the Pi4 is used together with a DSI connected touchscreen, it makes sense to disable the SoC HDMI outputs and the host only will see the board as card1-USB-0 (I haven't done this exercise yet since there's problems with getting the official Pi touchscreen to work with vc4 on Pi4).
I saw that. I can even get your point about using USB for panel (still don't agree). But you're also using USB as default case.
Best regards Thomas
The USB connector type is most important for tiny displays that is microcontroller based without Linux running. There are lots of tiny SPI displays and I expect this market to shift towards USB because it much easier to connect and the display will be useable on a desktop/server computer as status displays perhaps. But embedded will also benefit from having these displays USB interfaced.
Another use case I see is repurposing old Android tablets as USB displays that can be connected to any computer and become a touchscreen. In this case I also want the connector to be called card1-USB-0 (I haven't done any work in this area, old Android is fbdev so some work is needed for this to happen).
Noralf.
What's your opinion?
Ofc as Daniel mentions it's a downside that userspace doesn't know about the connector type, and who knows when it will updated (if I don't do it). Weston will name it: "UNNAMED-%d" Mutter: "Unknown%d-%d" X: "Unknown%d-%d"
Sam and Laurent has discussed adding a PANEL connector type instead of adding more connector types for panel connectors. I think that would have been a better choice instead of the SPI connector type that I added in 2019. But I think PANEL was meant for panels connected to an internal connector.
Here's my protocol connector types and how it's mapped to DRM:
#define GUD_CONNECTOR_TYPE_PANEL 0 #define GUD_CONNECTOR_TYPE_VGA 1 #define GUD_CONNECTOR_TYPE_COMPOSITE 2 #define GUD_CONNECTOR_TYPE_SVIDEO 3 #define GUD_CONNECTOR_TYPE_COMPONENT 4 #define GUD_CONNECTOR_TYPE_DVI 5 #define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 #define GUD_CONNECTOR_TYPE_HDMI 7
static int gud_gadget_ctrl_get_connector(struct gud_gadget *gdg, unsigned int index, struct gud_connector_descriptor_req *desc) { ... gconn = &gdg->connectors[index];
switch (gconn->connector->connector_type) { case DRM_MODE_CONNECTOR_VGA: desc->connector_type = GUD_CONNECTOR_TYPE_VGA; break; case DRM_MODE_CONNECTOR_DVII: fallthrough; case DRM_MODE_CONNECTOR_DVID: fallthrough; case DRM_MODE_CONNECTOR_DVIA: desc->connector_type = GUD_CONNECTOR_TYPE_DVI; break; case DRM_MODE_CONNECTOR_Composite: desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; break; case DRM_MODE_CONNECTOR_SVIDEO: desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; break; case DRM_MODE_CONNECTOR_Component: desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; break; case DRM_MODE_CONNECTOR_DisplayPort: desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; break; case DRM_MODE_CONNECTOR_HDMIA: fallthrough; case DRM_MODE_CONNECTOR_HDMIB: desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; break; default: desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; break; };
int gud_connector_create(struct gud_device *gdrm, unsigned int index) { ... switch (desc.connector_type) { case GUD_CONNECTOR_TYPE_PANEL: connector_type = DRM_MODE_CONNECTOR_USB; break; case GUD_CONNECTOR_TYPE_VGA: connector_type = DRM_MODE_CONNECTOR_VGA; break; case GUD_CONNECTOR_TYPE_DVI: connector_type = DRM_MODE_CONNECTOR_DVID; break; case GUD_CONNECTOR_TYPE_COMPOSITE: connector_type = DRM_MODE_CONNECTOR_Composite; break; case GUD_CONNECTOR_TYPE_SVIDEO: connector_type = DRM_MODE_CONNECTOR_SVIDEO; break; case GUD_CONNECTOR_TYPE_COMPONENT: connector_type = DRM_MODE_CONNECTOR_Component; break; case GUD_CONNECTOR_TYPE_DISPLAYPORT: connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; case GUD_CONNECTOR_TYPE_HDMI: connector_type = DRM_MODE_CONNECTOR_HDMIA; break; default: /* future types */ connector_type = DRM_MODE_CONNECTOR_USB;
The more I look at it the more I think it should be 'Unknown' here.
I don't understand this, how will that be better for the user?
As I said before, the display is not connected via USB. The RPi (i.e., graphics card) is. The naming would be off.
Best regards Thomas
BTW, can I try this out somehow? I do have an RPi3. Do I need a special disk image?
The Pi3 doesn'have a USB device/otg connector so I haven't made an image for that one. Only the Pi Zero, model A and Pi 4 have that.
The Pi2 and Pi3 have a USB hub on the soc's single USB port.
Noralf.
Best regards Thomas
break; };
Noralf.
Best regards Thomas
> -Daniel > >> >> Best regards >> Thomas >> >>> >>> Beware, new connector types have in the past resulted in userspace >>> burning&crashing. Maybe it's become better ... >>> >>> Acked-by: Daniel Vetter daniel.vetter@ffwll.ch >>>> >>>> /** >>>> * struct drm_mode_get_connector - Get connector metadata. >>>> -- >>>> 2.23.0 >>>> >>>> _______________________________________________ >>>> dri-devel mailing list >>>> dri-devel@lists.freedesktop.org >>>> https://lists.freedesktop.org/mailman/listinfo/dri-devel >>> >>> >>> >> >> -- >> Thomas Zimmermann >> Graphics Driver Developer >> SUSE Software Solutions Germany GmbH >> Maxfeldstr. 5, 90409 Nürnberg, Germany >> (HRB 36809, AG Nürnberg) >> Geschäftsführer: Felix Imendörffer >> > >
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
Den 22.01.2021 15.55, skrev Thomas Zimmermann:
Hi
Am 22.01.21 um 15:35 schrieb Noralf Trønnes:
Den 22.01.2021 13.47, skrev Thomas Zimmermann:
Hi
Am 22.01.21 um 12:44 schrieb Noralf Trønnes:
And wrt PCI it wouldn't be a PCI connector if the card has some other connector for the display, but if it was possible to connect a display directly to the PCI connector, then yes I would call that a PCI connector.
You're not connecting a display to the computer. You're connecting an RPi and then connect the display to the RPi. The RPi acts like an external graphics card.
This begs the question: Why does the kernel provide info to userspace about the connector type?
My take is that it is so the user can know which display is connected to which port on the computer.
This exactly illustrates the problem with the current naming. For a single output the distinction between bus and connector might be fuzzy. As soon as a connected SoC contains multiple connectors. The user then sees names such as card1-USB-0 and card1-USB-1, which makes no sense.
If you look at the code I pasted in, you'll see that the SoC connector types are passed through to the host driver as-is unless they are panel connectors like DSI/DPI, which will be interpreted as USB (the protocol does support multiple connectors, but only one can be used at a time).
So for the Pi4 as a display adapter, the host will see card1-HDMI-0 and card1-HDMI-1, the same is true for the composite output (if enabled) it shows up as card1-Composite-0 (can't be enabled together with HDMI).
If the Pi4 is used together with a DSI connected touchscreen, it makes sense to disable the SoC HDMI outputs and the host only will see the board as card1-USB-0 (I haven't done this exercise yet since there's problems with getting the official Pi touchscreen to work with vc4 on Pi4).
I saw that. I can even get your point about using USB for panel (still don't agree). But you're also using USB as default case.
In the host driver the default case is to catch future additions to the protocol. In this case the driver doesn't know the connector type, but by using USB at least the user knows where to look.
On the gadget side these are the current types that will hit the default case:
These are panels: DRM_MODE_CONNECTOR_LVDS DRM_MODE_CONNECTOR_eDP DRM_MODE_CONNECTOR_DSI DRM_MODE_CONNECTOR_DPI DRM_MODE_CONNECTOR_SPI
This also seems to be used primarily by panels: DRM_MODE_CONNECTOR_Unknown
Only used by i915, amd, radeon, very unlikely on a gadget: DRM_MODE_CONNECTOR_9PinDIN
This one I should probably map to GUD_CONNECTOR_TYPE_COMPOSITE: DRM_MODE_CONNECTOR_TV
Very unlikely on a gadget: DRM_MODE_CONNECTOR_VIRTUAL
Future connector types will also be reported in the protocol as GUD_CONNECTOR_TYPE_PANEL and thus on the host as DRM_MODE_CONNECTOR_USB. The gadget driver will ofc be updated when new non-panel connector types show up.
And this makes sense to me :-)
Giving the user a connector named "Unknown-1" does not make sense to me.
Noralf.
Best regards Thomas
The USB connector type is most important for tiny displays that is microcontroller based without Linux running. There are lots of tiny SPI displays and I expect this market to shift towards USB because it much easier to connect and the display will be useable on a desktop/server computer as status displays perhaps. But embedded will also benefit from having these displays USB interfaced.
Another use case I see is repurposing old Android tablets as USB displays that can be connected to any computer and become a touchscreen. In this case I also want the connector to be called card1-USB-0 (I haven't done any work in this area, old Android is fbdev so some work is needed for this to happen).
Noralf.
What's your opinion?
Ofc as Daniel mentions it's a downside that userspace doesn't know about the connector type, and who knows when it will updated (if I don't do it). Weston will name it: "UNNAMED-%d" Mutter: "Unknown%d-%d" X: "Unknown%d-%d"
Sam and Laurent has discussed adding a PANEL connector type instead of adding more connector types for panel connectors. I think that would have been a better choice instead of the SPI connector type that I added in 2019. But I think PANEL was meant for panels connected to an internal connector.
Here's my protocol connector types and how it's mapped to DRM:
#define GUD_CONNECTOR_TYPE_PANEL 0 #define GUD_CONNECTOR_TYPE_VGA 1 #define GUD_CONNECTOR_TYPE_COMPOSITE 2 #define GUD_CONNECTOR_TYPE_SVIDEO 3 #define GUD_CONNECTOR_TYPE_COMPONENT 4 #define GUD_CONNECTOR_TYPE_DVI 5 #define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 #define GUD_CONNECTOR_TYPE_HDMI 7
static int gud_gadget_ctrl_get_connector(struct gud_gadget *gdg, unsigned int index, struct gud_connector_descriptor_req *desc) { ... gconn = &gdg->connectors[index];
switch (gconn->connector->connector_type) { case DRM_MODE_CONNECTOR_VGA: desc->connector_type = GUD_CONNECTOR_TYPE_VGA; break; case DRM_MODE_CONNECTOR_DVII: fallthrough; case DRM_MODE_CONNECTOR_DVID: fallthrough; case DRM_MODE_CONNECTOR_DVIA: desc->connector_type = GUD_CONNECTOR_TYPE_DVI; break; case DRM_MODE_CONNECTOR_Composite: desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE; break; case DRM_MODE_CONNECTOR_SVIDEO: desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO; break; case DRM_MODE_CONNECTOR_Component: desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT; break; case DRM_MODE_CONNECTOR_DisplayPort: desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT; break; case DRM_MODE_CONNECTOR_HDMIA: fallthrough; case DRM_MODE_CONNECTOR_HDMIB: desc->connector_type = GUD_CONNECTOR_TYPE_HDMI; break; default: desc->connector_type = GUD_CONNECTOR_TYPE_PANEL; break; };
int gud_connector_create(struct gud_device *gdrm, unsigned int index) { ... switch (desc.connector_type) { case GUD_CONNECTOR_TYPE_PANEL: connector_type = DRM_MODE_CONNECTOR_USB; break; case GUD_CONNECTOR_TYPE_VGA: connector_type = DRM_MODE_CONNECTOR_VGA; break; case GUD_CONNECTOR_TYPE_DVI: connector_type = DRM_MODE_CONNECTOR_DVID; break; case GUD_CONNECTOR_TYPE_COMPOSITE: connector_type = DRM_MODE_CONNECTOR_Composite; break; case GUD_CONNECTOR_TYPE_SVIDEO: connector_type = DRM_MODE_CONNECTOR_SVIDEO; break; case GUD_CONNECTOR_TYPE_COMPONENT: connector_type = DRM_MODE_CONNECTOR_Component; break; case GUD_CONNECTOR_TYPE_DISPLAYPORT: connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; case GUD_CONNECTOR_TYPE_HDMI: connector_type = DRM_MODE_CONNECTOR_HDMIA; break; default: /* future types */ connector_type = DRM_MODE_CONNECTOR_USB;
The more I look at it the more I think it should be 'Unknown' here.
I don't understand this, how will that be better for the user?
As I said before, the display is not connected via USB. The RPi (i.e., graphics card) is. The naming would be off.
Best regards Thomas
BTW, can I try this out somehow? I do have an RPi3. Do I need a special disk image?
The Pi3 doesn'have a USB device/otg connector so I haven't made an image for that one. Only the Pi Zero, model A and Pi 4 have that.
The Pi2 and Pi3 have a USB hub on the soc's single USB port.
Noralf.
Best regards Thomas
break; };
Noralf.
> Best regards > Thomas > >> -Daniel >> >>> >>> Best regards >>> Thomas >>> >>>> >>>> Beware, new connector types have in the past resulted in >>>> userspace >>>> burning&crashing. Maybe it's become better ... >>>> >>>> Acked-by: Daniel Vetter daniel.vetter@ffwll.ch >>>>> >>>>> /** >>>>> * struct drm_mode_get_connector - Get connector metadata. >>>>> -- >>>>> 2.23.0 >>>>> >>>>> _______________________________________________ >>>>> dri-devel mailing list >>>>> dri-devel@lists.freedesktop.org >>>>> https://lists.freedesktop.org/mailman/listinfo/dri-devel >>>> >>>> >>>> >>> >>> -- >>> Thomas Zimmermann >>> Graphics Driver Developer >>> SUSE Software Solutions Germany GmbH >>> Maxfeldstr. 5, 90409 Nürnberg, Germany >>> (HRB 36809, AG Nürnberg) >>> Geschäftsführer: Felix Imendörffer >>> >> >> > > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel > _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
drm_helper_hpd_irq_event() checks the epoch counter to determine connector status change. This was introduced in commit 5186421cbfe2 ("drm: Introduce epoch counter to drm_connector"). Do the same for output_poll_execute() so it can detect other changes beside connection status value changes.
Signed-off-by: Noralf Trønnes noralf@tronnes.org --- drivers/gpu/drm/drm_probe_helper.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index d6017726cc2a..e5432dcf6999 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -623,6 +623,7 @@ static void output_poll_execute(struct work_struct *work) struct drm_connector_list_iter conn_iter; enum drm_connector_status old_status; bool repoll = false, changed; + u64 old_epoch_counter;
if (!dev->mode_config.poll_enabled) return; @@ -659,8 +660,9 @@ static void output_poll_execute(struct work_struct *work)
repoll = true;
+ old_epoch_counter = connector->epoch_counter; connector->status = drm_helper_probe_detect(connector, NULL, false); - if (old_status != connector->status) { + if (old_epoch_counter != connector->epoch_counter) { const char *old, *new;
/* @@ -689,6 +691,9 @@ static void output_poll_execute(struct work_struct *work) connector->base.id, connector->name, old, new); + DRM_DEBUG_KMS("[CONNECTOR:%d:%s] epoch counter %llu -> %llu\n", + connector->base.id, connector->name, + old_epoch_counter, connector->epoch_counter);
changed = true; }
On Wed, Jan 20, 2021 at 6:10 PM Noralf Trønnes noralf@tronnes.org wrote:
drm_helper_hpd_irq_event() checks the epoch counter to determine connector status change. This was introduced in commit 5186421cbfe2 ("drm: Introduce epoch counter to drm_connector"). Do the same for output_poll_execute() so it can detect other changes beside connection status value changes.
Signed-off-by: Noralf Trønnes noralf@tronnes.org
drivers/gpu/drm/drm_probe_helper.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index d6017726cc2a..e5432dcf6999 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -623,6 +623,7 @@ static void output_poll_execute(struct work_struct *work) struct drm_connector_list_iter conn_iter; enum drm_connector_status old_status; bool repoll = false, changed;
u64 old_epoch_counter; if (!dev->mode_config.poll_enabled) return;
@@ -659,8 +660,9 @@ static void output_poll_execute(struct work_struct *work)
repoll = true;
old_epoch_counter = connector->epoch_counter; connector->status = drm_helper_probe_detect(connector, NULL, false);
if (old_status != connector->status) {
Was about to ask whether we're not missing connector status changes here, but that's already handled. Not sure why this wasn't done as part of the original patch, I'd include a cc: stable here.
Reviewed-by: Daniel Vetter daniel.vetter@ffwll.ch
if (old_epoch_counter != connector->epoch_counter) { const char *old, *new; /*
@@ -689,6 +691,9 @@ static void output_poll_execute(struct work_struct *work) connector->base.id, connector->name, old, new);
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] epoch counter %llu -> %llu\n",
connector->base.id, connector->name,
old_epoch_counter, connector->epoch_counter); changed = true; }
-- 2.23.0
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
This adds a generic USB display driver with the intention that it can be used with future USB interfaced low end displays/adapters. The Linux gadget device driver will serve as the canonical device implementation.
The following DRM properties are supported: - Plane rotation - Connector TV properties
There is also support for backlight brightness exposed as a backlight device.
Display modes can be made available to the host driver either as DRM display modes or through EDID. If both are present, EDID is just passed on to userspace.
Performance is preferred over color depth, so if the device supports RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16.
If the device transfer buffer can't fit an uncompressed framebuffer update, the update is split up into parts that do fit.
Optimal user experience is achieved by providing damage reports either by setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB.
LZ4 compression is used if the device supports it.
The driver supports a one bit monochrome transfer format: R1. This is not implemented in the gadget driver. It is added in preparation for future monochrome e-ink displays.
The driver is MIT licensed to smooth the path for any BSD port of the driver.
v2: - Use devm_drm_dev_alloc() and drmm_mode_config_init() - drm_fbdev_generic_setup: Use preferred_bpp=0, 16 was a copy paste error - The drm_backlight_helper is dropped, copy in the code - Support protocol version backwards compatibility for device
v3: - Use donated Openmoko USB pid - Use direct compression from framebuffer when pitch matches, not only on full frames, so split updates can benefit - Use __le16 in struct gud_drm_req_get_connector_status - Set edid property when the device only provides edid - Clear compression fields in struct gud_drm_req_set_buffer - Fix protocol version negotiation - Remove mode->vrefresh, it's calculated
v4: - Drop the status req polling which was a workaround for something that turned out to be a dwc2 udc driver problem - Add a flag for the Linux gadget to require a status request on SET operations. Other devices will only get status req on STALL errors - Use protocol specific error codes (Peter) - Add a flag for devices that want to receive the entire framebuffer on each flush (Lubomir) - Retry a failed framebuffer flush - If mode has changed wait for worker and clear pending damage before queuing up new damage, fb width/height might have changed - Increase error counter on bulk transfer failures - Use DRM_MODE_CONNECTOR_USB - Handle R1 kmalloc error (Peter) - Don't try and replicate the USB get descriptor request standard for the display descriptor (Peter) - Make max_buffer_size optional (Peter), drop the pow2 requirement since it's not necessary anymore. - Don't pre-alloc a control request buffer, it was only 4k - Let gud.h describe the whole protocol explicitly and don't let DRM leak into it (Peter) - Drop display mode .hskew and .vscan from the protocol - Shorten names: s/GUD_DRM_/GUD_/ s/gud_drm_/gud_/ (Peter) - Fix gud_pipe_check() connector picking when switching connector - Drop gud_drm_driver_gem_create_object() cached is default now - Retrieve USB device from struct drm_device.dev instead of keeping a pointer - Honour fb->offsets[0] - Fix mode fetching when connector status is forced - Check EDID length reported by the device - Use drm_do_get_edid() so userspace can overrride EDID - Set epoch counter to signal connector status change - gud_drm_driver can be const now
Cc: Peter Stuge peter@stuge.se Cc: Lubomir Rintel lkundrak@v3.sk Signed-off-by: Noralf Trønnes noralf@tronnes.org --- MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 722 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 620 ++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 148 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 472 ++++++++++++++++++ include/drm/gud.h | 356 ++++++++++++++ 10 files changed, 2347 insertions(+) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
diff --git a/MAINTAINERS b/MAINTAINERS index 00112c044608..e7f71ac55f08 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5525,6 +5525,14 @@ S: Maintained F: Documentation/devicetree/bindings/display/panel/feiyang,fy07024di26a30d.yaml F: drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c
+DRM DRIVER FOR GENERIC USB DISPLAY +M: Noralf Trønnes noralf@tronnes.org +S: Maintained +W: https://github.com/notro/gud/wiki +T: git git://anongit.freedesktop.org/drm/drm-misc +F: drivers/gpu/drm/gud/ +F: include/drm/gud.h + DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS M: Hans de Goede hdegoede@redhat.com S: Maintained diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 147d61b9674e..6d3f47933e51 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -388,6 +388,8 @@ source "drivers/gpu/drm/tidss/Kconfig"
source "drivers/gpu/drm/xlnx/Kconfig"
+source "drivers/gpu/drm/gud/Kconfig" + # Keep legacy drivers last
menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 81569009f884..78dd8e12525d 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -124,3 +124,4 @@ obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ obj-$(CONFIG_DRM_MCDE) += mcde/ obj-$(CONFIG_DRM_TIDSS) += tidss/ obj-y += xlnx/ +obj-y += gud/ diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig new file mode 100644 index 000000000000..d832cd63687c --- /dev/null +++ b/drivers/gpu/drm/gud/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DRM_GUD + tristate "Generic USB Display" + depends on DRM && USB + select LZ4_COMPRESS + select DRM_KMS_HELPER + select DRM_GEM_SHMEM_HELPER + select BACKLIGHT_CLASS_DEVICE + help + This is a DRM display driver for Generic USB Displays or display + adapters. + + If M is selected the module will be called gud. diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile new file mode 100644 index 000000000000..d2bb53a65de6 --- /dev/null +++ b/drivers/gpu/drm/gud/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +gud-objs := gud_drv.o gud_pipe.o gud_connector.o +obj-$(CONFIG_DRM_GUD) += gud.o diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c new file mode 100644 index 000000000000..a4b9bbf48e19 --- /dev/null +++ b/drivers/gpu/drm/gud/gud_connector.c @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2020 Noralf Trønnes + */ + +#include <linux/backlight.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_drv.h> +#include <drm/drm_encoder.h> +#include <drm/drm_file.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/gud.h> + +#include "gud_internal.h" + +struct gud_connector { + struct drm_connector connector; + struct drm_encoder encoder; + struct backlight_device *backlight; + + /* Supported properties */ + u16 *properties; + unsigned int num_properties; + + /* Initial gadget tv state if applicable, applied on state reset */ + struct drm_tv_connector_state initial_tv_state; + + /* + * Initial gadget backlight brightness if applicable, applied on state reset. + * The value -ENODEV is used to signal no backlight. + */ + int initial_brightness; + + unsigned int num_modes; + size_t edid_len; +}; + +static inline struct gud_connector *to_gud_connector(struct drm_connector *connector) +{ + return container_of(connector, struct gud_connector, connector); +} + +static int gud_connector_backlight_update_status(struct backlight_device *bd) +{ + struct drm_connector *connector = bl_get_data(bd); + struct drm_connector_state *connector_state; + struct drm_device *dev = connector->dev; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_state *state; + int ret; + + state = drm_atomic_state_alloc(dev); + if (!state) + return -ENOMEM; + + drm_modeset_acquire_init(&ctx, 0); + state->acquire_ctx = &ctx; +retry: + connector_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(connector_state)) { + ret = PTR_ERR(connector_state); + goto out; + } + + /* Reuse tv.brightness to avoid having to subclass */ + connector_state->tv.brightness = bd->props.brightness; + + ret = drm_atomic_commit(state); +out: + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + + drm_atomic_state_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return ret; +} + +static int gud_connector_backlight_get_brightness(struct backlight_device *bd) +{ + struct drm_connector *connector = bl_get_data(bd); + struct drm_device *dev = connector->dev; + int brightness; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + brightness = connector->state->tv.brightness; + drm_modeset_unlock(&dev->mode_config.connection_mutex); + + return brightness; +} + +static const struct backlight_ops gud_connector_backlight_ops = { + .get_brightness = gud_connector_backlight_get_brightness, + .update_status = gud_connector_backlight_update_status, +}; + +static int gud_connector_backlight_register(struct gud_connector *gconn) +{ + struct drm_connector *connector = &gconn->connector; + struct backlight_device *bd; + const char *name; + const struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .scale = BACKLIGHT_SCALE_NON_LINEAR, + .max_brightness = 100, + }; + + name = kasprintf(GFP_KERNEL, "card%d-%s-backlight", + connector->dev->primary->index, connector->name); + if (!name) + return -ENOMEM; + + bd = backlight_device_register(name, connector->kdev, connector, + &gud_connector_backlight_ops, &props); + kfree(name); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + gconn->backlight = bd; + + return 0; +} + +static int gud_connector_status_request(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + struct gud_device *gdrm = to_gud_device(connector->dev); + struct gud_connector_status_req req; + u16 num_modes, edid_len; + int ret; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_STATUS, + connector->index, &req, sizeof(req)); + if (ret) + return ret; + + switch (req.status & GUD_CONNECTOR_STATUS_CONNECTED_MASK) { + case GUD_CONNECTOR_STATUS_DISCONNECTED: + ret = connector_status_disconnected; + break; + case GUD_CONNECTOR_STATUS_CONNECTED: + ret = connector_status_connected; + break; + default: + ret = connector_status_unknown; + break; + }; + + num_modes = le16_to_cpu(req.num_modes); + edid_len = le16_to_cpu(req.edid_len); + + if (edid_len % EDID_LENGTH) { + drm_err(connector->dev, "%s: Invalid EDID size: %u\n", connector->name, edid_len); + edid_len = 0; + } + + if (req.status & GUD_CONNECTOR_STATUS_CHANGED || + gconn->num_modes != num_modes || gconn->edid_len != edid_len) + connector->epoch_counter += 1; + + gconn->num_modes = num_modes; + gconn->edid_len = edid_len; + + if (!num_modes && !edid_len && ret != connector_status_disconnected) + drm_dbg_kms(connector->dev, "%s: No modes or EDID.\n", connector->name); + + return ret; +} + +static int gud_connector_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, bool force) +{ + struct gud_device *gdrm = to_gud_device(connector->dev); + int idx, ret; + + if (!drm_dev_enter(connector->dev, &idx)) + return connector_status_disconnected; + + if (force) { + ret = gud_usb_set(gdrm, GUD_REQ_SET_CONNECTOR_FORCE_DETECT, + connector->index, NULL, 0); + if (ret) { + ret = connector_status_unknown; + goto exit; + } + } + + ret = gud_connector_status_request(connector); + if (ret < 0) + ret = connector_status_unknown; +exit: + drm_dev_exit(idx); + + return ret; +} + +struct gud_connector_get_edid_ctx { + struct gud_connector *gconn; + void *buf; +}; + +static int gud_connector_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct gud_connector_get_edid_ctx *ctx = data; + struct gud_connector *gconn = ctx->gconn; + size_t start = block * EDID_LENGTH; + + if (start + len > gconn->edid_len) + return -1; + + if (!block) { + struct gud_device *gdrm = to_gud_device(gconn->connector.dev); + int ret; + + /* Check because drm_do_get_edid() will retry on failure */ + if (!ctx->buf) + ctx->buf = kmalloc(gconn->edid_len, GFP_KERNEL); + if (!ctx->buf) + return -1; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_EDID, gconn->connector.index, + ctx->buf, gconn->edid_len); + if (ret) + return -1; + } + + memcpy(buf, ctx->buf + start, len); + + return 0; +} + +static int gud_connector_get_modes(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + struct gud_device *gdrm = to_gud_device(connector->dev); + struct gud_connector_get_edid_ctx edid_ctx = { + .gconn = gconn, + }; + struct gud_display_mode_req *reqmodes = NULL; + unsigned int i, num_modes = 0; + struct edid *edid = NULL; + bool edid_override; + int idx, ret; + + if (!drm_dev_enter(connector->dev, &idx)) + return 0; + + if (connector->force) { + ret = gud_connector_status_request(connector); + if (ret < 0) + goto out; + } + + edid = drm_do_get_edid(connector, gud_connector_get_edid_block, &edid_ctx); + edid_override = edid && !edid_ctx.buf; + kfree(edid_ctx.buf); + drm_connector_update_edid_property(connector, edid); + + if (!gconn->num_modes || edid_override) { + num_modes = drm_add_edid_modes(connector, edid); + goto out; + } + + reqmodes = kmalloc_array(gconn->num_modes, sizeof(*reqmodes), GFP_KERNEL); + if (!reqmodes) + goto out; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_MODES, connector->index, + reqmodes, gconn->num_modes * sizeof(*reqmodes)); + if (ret) + goto out; + + for (i = 0; i < gconn->num_modes; i++) { + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (!mode) + goto out; + + gud_to_display_mode(mode, &reqmodes[i]); + drm_mode_probed_add(connector, mode); + num_modes++; + } +out: + kfree(reqmodes); + kfree(edid); + drm_dev_exit(idx); + + return num_modes; +} + +static int gud_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *new_state; + struct drm_crtc_state *new_crtc_state; + struct drm_connector_state *old_state; + + new_state = drm_atomic_get_new_connector_state(state, connector); + if (!new_state->crtc) + return 0; + + old_state = drm_atomic_get_old_connector_state(state, connector); + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc); + + if (old_state->tv.margins.left != new_state->tv.margins.left || + old_state->tv.margins.right != new_state->tv.margins.right || + old_state->tv.margins.top != new_state->tv.margins.top || + old_state->tv.margins.bottom != new_state->tv.margins.bottom || + old_state->tv.mode != new_state->tv.mode || + old_state->tv.brightness != new_state->tv.brightness || + old_state->tv.contrast != new_state->tv.contrast || + old_state->tv.flicker_reduction != new_state->tv.flicker_reduction || + old_state->tv.overscan != new_state->tv.overscan || + old_state->tv.saturation != new_state->tv.saturation || + old_state->tv.hue != new_state->tv.hue) + new_crtc_state->connectors_changed = true; + + return 0; +} + +static const struct drm_connector_helper_funcs gud_connector_helper_funcs = { + .detect_ctx = gud_connector_detect, + .get_modes = gud_connector_get_modes, + .atomic_check = gud_connector_atomic_check, +}; + +static int gud_connector_late_register(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + if (gconn->initial_brightness < 0) + return 0; + + return gud_connector_backlight_register(gconn); +} + +static void gud_connector_early_unregister(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + backlight_device_unregister(gconn->backlight); +} + +static void gud_connector_destroy(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + drm_connector_cleanup(connector); + kfree(gconn->properties); + kfree(gconn); +} + +static void gud_connector_reset(struct drm_connector *connector) +{ + struct gud_connector *gconn = to_gud_connector(connector); + + drm_atomic_helper_connector_reset(connector); + connector->state->tv = gconn->initial_tv_state; + /* Set margins from command line */ + drm_atomic_helper_connector_tv_reset(connector); + if (gconn->initial_brightness >= 0) + connector->state->tv.brightness = gconn->initial_brightness; +} + +static const struct drm_connector_funcs gud_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .late_register = gud_connector_late_register, + .early_unregister = gud_connector_early_unregister, + .destroy = gud_connector_destroy, + .reset = gud_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/* + * The tv.mode property is shared among the connectors and its enum names are + * driver specific. This means that if more than one connector uses tv.mode, + * the enum names has to be the same. + */ +static int gud_connector_add_tv_mode(struct gud_device *gdrm, + struct drm_connector *connector, u64 val) +{ + unsigned int i, num_modes; + const char **modes; + size_t buf_len; + char *buf; + int ret; + + num_modes = val >> GUD_CONNECTOR_TV_MODE_NUM_SHIFT; + + if (!num_modes) + return -EINVAL; + + buf_len = num_modes * GUD_CONNECTOR_TV_MODE_NAME_LEN; + modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL); + buf = kmalloc(buf_len, GFP_KERNEL); + if (!modes || !buf) { + ret = -ENOMEM; + goto free; + } + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES, + connector->index, buf, buf_len); + if (ret) + goto free; + + for (i = 0; i < num_modes; i++) + modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; + + ret = drm_mode_create_tv_properties(connector->dev, num_modes, modes); +free: + kfree(modes); + kfree(buf); + + return ret; +} + +static struct drm_property * +gud_connector_property_lookup(struct drm_connector *connector, u16 prop) +{ + struct drm_mode_config *config = &connector->dev->mode_config; + + switch (prop) { + case GUD_PROPERTY_TV_LEFT_MARGIN: + return config->tv_left_margin_property; + case GUD_PROPERTY_TV_RIGHT_MARGIN: + return config->tv_right_margin_property; + case GUD_PROPERTY_TV_TOP_MARGIN: + return config->tv_top_margin_property; + case GUD_PROPERTY_TV_BOTTOM_MARGIN: + return config->tv_bottom_margin_property; + case GUD_PROPERTY_TV_MODE: + return config->tv_mode_property; + case GUD_PROPERTY_TV_BRIGHTNESS: + return config->tv_brightness_property; + case GUD_PROPERTY_TV_CONTRAST: + return config->tv_contrast_property; + case GUD_PROPERTY_TV_FLICKER_REDUCTION: + return config->tv_flicker_reduction_property; + case GUD_PROPERTY_TV_OVERSCAN: + return config->tv_overscan_property; + case GUD_PROPERTY_TV_SATURATION: + return config->tv_saturation_property; + case GUD_PROPERTY_TV_HUE: + return config->tv_hue_property; + default: + return ERR_PTR(-EINVAL); + } +} + +static unsigned int *gud_connector_tv_state_val(u16 prop, struct drm_tv_connector_state *state) +{ + switch (prop) { + case GUD_PROPERTY_TV_LEFT_MARGIN: + return &state->margins.left; + case GUD_PROPERTY_TV_RIGHT_MARGIN: + return &state->margins.right; + case GUD_PROPERTY_TV_TOP_MARGIN: + return &state->margins.top; + case GUD_PROPERTY_TV_BOTTOM_MARGIN: + return &state->margins.bottom; + case GUD_PROPERTY_TV_MODE: + return &state->mode; + case GUD_PROPERTY_TV_BRIGHTNESS: + return &state->brightness; + case GUD_PROPERTY_TV_CONTRAST: + return &state->contrast; + case GUD_PROPERTY_TV_FLICKER_REDUCTION: + return &state->flicker_reduction; + case GUD_PROPERTY_TV_OVERSCAN: + return &state->overscan; + case GUD_PROPERTY_TV_SATURATION: + return &state->saturation; + case GUD_PROPERTY_TV_HUE: + return &state->hue; + default: + return ERR_PTR(-EINVAL); + } +} + +static int gud_connector_add_properties(struct gud_device *gdrm, struct gud_connector *gconn, + unsigned int num_properties) +{ + struct drm_device *drm = &gdrm->drm; + struct drm_connector *connector = &gconn->connector; + struct gud_property_req *properties; + unsigned int i; + int ret; + + gconn->properties = kcalloc(num_properties, sizeof(*gconn->properties), GFP_KERNEL); + if (!gconn->properties) + return -ENOMEM; + + properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL); + if (!properties) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR_PROPERTIES, connector->index, + properties, num_properties * sizeof(*properties)); + if (ret) + goto out; + + for (i = 0; i < num_properties; i++) { + u16 prop = le16_to_cpu(properties[i].prop); + u64 val = le64_to_cpu(properties[i].val); + struct drm_property *property; + unsigned int *state_val; + + drm_dbg(drm, "property: %u = %llu(0x%llx)\n", prop, val, val); + + switch (prop) { + case GUD_PROPERTY_TV_LEFT_MARGIN: + fallthrough; + case GUD_PROPERTY_TV_RIGHT_MARGIN: + fallthrough; + case GUD_PROPERTY_TV_TOP_MARGIN: + fallthrough; + case GUD_PROPERTY_TV_BOTTOM_MARGIN: + ret = drm_mode_create_tv_margin_properties(drm); + if (ret) + goto out; + break; + case GUD_PROPERTY_TV_MODE: + ret = gud_connector_add_tv_mode(gdrm, connector, val); + if (ret) + goto out; + val = val & (BIT(GUD_CONNECTOR_TV_MODE_NUM_SHIFT) - 1); + break; + case GUD_PROPERTY_TV_BRIGHTNESS: + fallthrough; + case GUD_PROPERTY_TV_CONTRAST: + fallthrough; + case GUD_PROPERTY_TV_FLICKER_REDUCTION: + fallthrough; + case GUD_PROPERTY_TV_OVERSCAN: + fallthrough; + case GUD_PROPERTY_TV_SATURATION: + fallthrough; + case GUD_PROPERTY_TV_HUE: + /* This is a no-op if already added. */ + ret = drm_mode_create_tv_properties(drm, 0, NULL); + if (ret) + goto out; + break; + case GUD_PROPERTY_BACKLIGHT_BRIGHTNESS: + if (val > 100) { + ret = -EINVAL; + goto out; + } + gconn->initial_brightness = val; + break; + default: + /* New ones might show up in future devices, skip those we don't know. */ + drm_dbg(drm, "Unknown property: %u\n", prop); + continue; + } + + gconn->properties[gconn->num_properties++] = prop; + + if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) + continue; /* not a DRM property */ + + property = gud_connector_property_lookup(connector, prop); + if (WARN_ON(IS_ERR(property))) + continue; + + state_val = gud_connector_tv_state_val(prop, &gconn->initial_tv_state); + if (WARN_ON(IS_ERR(state_val))) + continue; + + *state_val = val; + drm_object_attach_property(&connector->base, property, 0); + } +out: + kfree(properties); + + return ret; +} + +int gud_connector_fill_properties(struct drm_connector *connector, + struct drm_connector_state *connector_state, + struct gud_property_req *properties) +{ + struct gud_connector *gconn; + unsigned int i; + + gconn = to_gud_connector(connector); + + /* Only interested in the count? */ + if (!connector_state) + return gconn->num_properties; + + for (i = 0; i < gconn->num_properties; i++) { + u16 prop = gconn->properties[i]; + u64 val; + + if (prop == GUD_PROPERTY_BACKLIGHT_BRIGHTNESS) { + val = connector_state->tv.brightness; + } else { + unsigned int *state_val; + + state_val = gud_connector_tv_state_val(prop, &connector_state->tv); + if (WARN_ON_ONCE(IS_ERR(state_val))) + return PTR_ERR(state_val); + + val = *state_val; + } + + properties[i].prop = cpu_to_le16(prop); + properties[i].val = cpu_to_le64(val); + } + + return gconn->num_properties; +} + +int gud_connector_create(struct gud_device *gdrm, unsigned int index) +{ + struct gud_connector_descriptor_req desc; + struct drm_device *drm = &gdrm->drm; + struct gud_connector *gconn; + struct drm_connector *connector; + struct drm_encoder *encoder; + int ret, connector_type; + u32 flags; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_CONNECTOR, index, &desc, sizeof(desc)); + if (ret) + return ret; + + gconn = kzalloc(sizeof(*gconn), GFP_KERNEL); + if (!gconn) + return -ENOMEM; + + gconn->initial_brightness = -ENODEV; + flags = le32_to_cpu(desc.flags); + connector = &gconn->connector; + + drm_dbg(drm, "Connector: index=%u type=%u flags=0x%x num_properties=%u\n", + index, desc.connector_type, flags, desc.num_properties); + + switch (desc.connector_type) { + case GUD_CONNECTOR_TYPE_PANEL: + connector_type = DRM_MODE_CONNECTOR_USB; + break; + case GUD_CONNECTOR_TYPE_VGA: + connector_type = DRM_MODE_CONNECTOR_VGA; + break; + case GUD_CONNECTOR_TYPE_DVI: + connector_type = DRM_MODE_CONNECTOR_DVID; + break; + case GUD_CONNECTOR_TYPE_COMPOSITE: + connector_type = DRM_MODE_CONNECTOR_Composite; + break; + case GUD_CONNECTOR_TYPE_SVIDEO: + connector_type = DRM_MODE_CONNECTOR_SVIDEO; + break; + case GUD_CONNECTOR_TYPE_COMPONENT: + connector_type = DRM_MODE_CONNECTOR_Component; + break; + case GUD_CONNECTOR_TYPE_DISPLAYPORT: + connector_type = DRM_MODE_CONNECTOR_DisplayPort; + break; + case GUD_CONNECTOR_TYPE_HDMI: + connector_type = DRM_MODE_CONNECTOR_HDMIA; + break; + default: /* future types */ + connector_type = DRM_MODE_CONNECTOR_USB; + break; + }; + + drm_connector_helper_add(connector, &gud_connector_helper_funcs); + ret = drm_connector_init(drm, connector, &gud_connector_funcs, connector_type); + if (ret) { + kfree(connector); + return ret; + } + + if (WARN_ON(connector->index != index)) + return -EINVAL; + + if (flags & GUD_CONNECTOR_FLAGS_POLL_STATUS) + connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT); + if (flags & GUD_CONNECTOR_FLAGS_INTERLACE) + connector->interlace_allowed = true; + if (flags & GUD_CONNECTOR_FLAGS_DOUBLESCAN) + connector->doublescan_allowed = true; + + if (desc.num_properties) { + ret = gud_connector_add_properties(gdrm, gconn, desc.num_properties); + if (ret) { + dev_err(drm->dev, "Failed to add connector/%u properties\n", index); + return ret; + } + } + + /* The first connector is attached to the existing simple pipe encoder */ + if (!connector->index) { + encoder = &gdrm->pipe.encoder; + } else { + encoder = &gconn->encoder; + + ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE); + if (ret) + return ret; + + encoder->possible_crtcs = 1; + } + + return drm_connector_attach_encoder(connector, encoder); +} diff --git a/drivers/gpu/drm/gud/gud_drv.c b/drivers/gpu/drm/gud/gud_drv.c new file mode 100644 index 000000000000..6c5e9eeb8cdc --- /dev/null +++ b/drivers/gpu/drm/gud/gud_drv.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2020 Noralf Trønnes + */ + +#include <linux/dma-buf.h> +#include <linux/lz4.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/string_helpers.h> +#include <linux/usb.h> +#include <linux/vmalloc.h> +#include <linux/workqueue.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_debugfs.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/gud.h> + +#include "gud_internal.h" + +/* Only used internally */ +static const struct drm_format_info gud_drm_format_r1 = { + .format = GUD_DRM_FORMAT_R1, + .num_planes = 1, + .char_per_block = { 1, 0, 0 }, + .block_w = { 8, 0, 0 }, + .block_h = { 1, 0, 0 }, + .hsub = 1, + .vsub = 1, +}; + +static int gud_usb_control_msg(struct usb_device *usb, u8 ifnum, bool in, + u8 request, u16 value, void *buf, size_t len) +{ + u8 requesttype = USB_TYPE_VENDOR | USB_RECIP_INTERFACE; + unsigned int pipe; + int ret; + + if (in) { + pipe = usb_rcvctrlpipe(usb, 0); + requesttype |= USB_DIR_IN; + } else { + pipe = usb_sndctrlpipe(usb, 0); + requesttype |= USB_DIR_OUT; + } + + ret = usb_control_msg(usb, pipe, request, requesttype, value, + ifnum, buf, len, USB_CTRL_GET_TIMEOUT); + if (ret < 0) + return ret; + if (ret != len) + return -EIO; + + return 0; +} + +static int gud_get_display_descriptor(struct usb_interface *interface, + struct gud_display_descriptor_req *desc) +{ + u8 ifnum = interface->cur_altsetting->desc.bInterfaceNumber; + struct usb_device *usb = interface_to_usbdev(interface); + void *buf; + int ret; + + buf = kmalloc(sizeof(*desc), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = gud_usb_control_msg(usb, ifnum, true, GUD_REQ_GET_DESCRIPTOR, 0, buf, sizeof(*desc)); + memcpy(desc, buf, sizeof(*desc)); + kfree(buf); + if (ret) + return ret; + + if (desc->magic != GUD_DISPLAY_MAGIC) + return -ENODATA; + + DRM_DEV_DEBUG_DRIVER(&interface->dev, + "version=%u flags=0x%x compression=0x%x num_formats=%u num_connectors=%u max_buffer_size=%u\n", + desc->version, le32_to_cpu(desc->flags), desc->compression, + desc->num_formats, desc->num_connectors, + le32_to_cpu(desc->max_buffer_size)); + + if (!desc->version || !desc->num_formats || !desc->num_connectors || + !desc->max_width || !desc->max_height || + le32_to_cpu(desc->min_width) > le32_to_cpu(desc->max_width) || + le32_to_cpu(desc->min_height) > le32_to_cpu(desc->max_height)) + return -EINVAL; + + return 0; +} + +static int gud_usb_get_status(struct usb_device *usb, u8 ifnum, u8 *status) +{ + u8 *buf; + int ret; + + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = gud_usb_control_msg(usb, ifnum, true, GUD_REQ_GET_STATUS, 0, buf, sizeof(*buf)); + *status = *buf; + kfree(buf); + + return ret; +} + +static int gud_status_to_errno(u8 status) +{ + switch (status) { + case GUD_STATUS_OK: + return 0; + case GUD_STATUS_BUSY: + return -EBUSY; + case GUD_STATUS_REQUEST_NOT_SUPPORTED: + return -EOPNOTSUPP; + case GUD_STATUS_PROTOCOL_ERROR: + return -EPROTO; + case GUD_STATUS_INVALID_PARAMETER: + return -EINVAL; + case GUD_STATUS_ERROR: + return -EREMOTEIO; + default: + return -EREMOTEIO; + } +} + +static int gud_usb_transfer(struct gud_device *gdrm, bool in, u8 request, u16 index, + void *buf, size_t len) +{ + struct usb_device *usb = gud_to_usb_device(gdrm); + void *trbuf = NULL; + int idx, ret; + + drm_dbg(&gdrm->drm, "%s: request=0x%x index=%u len=%zu\n", + in ? "get" : "set", request, index, len); + + if (!drm_dev_enter(&gdrm->drm, &idx)) + return -ENODEV; + + mutex_lock(&gdrm->ctrl_lock); + + if (buf) { + if (in) + trbuf = kmalloc(len, GFP_KERNEL); + else + trbuf = kmemdup(buf, len, GFP_KERNEL); + if (!trbuf) { + ret = -ENOMEM; + goto unlock; + } + } + + ret = gud_usb_control_msg(usb, gdrm->ifnum, in, request, index, trbuf, len); + if (ret == -EPIPE || (!ret && !in && (gdrm->flags & GUD_DISPLAY_FLAG_STATUS_ON_SET))) { + bool error = ret; + u8 status; + + ret = gud_usb_get_status(usb, gdrm->ifnum, &status); + if (!ret) { + if (error && status == GUD_STATUS_OK) { + dev_err_once(gdrm->drm.dev, + "Unexpected status OK for failed transfer\n"); + ret = -EPIPE; + } else { + ret = gud_status_to_errno(status); + } + } + } + + if (!ret && in && buf) + memcpy(buf, trbuf, len); + + if (ret) { + drm_dbg(&gdrm->drm, "ret=%d\n", ret); + gdrm->stats_num_errors++; + } + + kfree(trbuf); +unlock: + mutex_unlock(&gdrm->ctrl_lock); + drm_dev_exit(idx); + + return ret; +} + +int gud_usb_get(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len) +{ + return gud_usb_transfer(gdrm, true, request, index, buf, len); +} + +int gud_usb_set(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len) +{ + return gud_usb_transfer(gdrm, false, request, index, buf, len); +} + +int gud_usb_write8(struct gud_device *gdrm, u8 request, u8 val) +{ + return gud_usb_set(gdrm, request, 0, &val, sizeof(val)); +} + +static int gud_set_version(struct usb_device *usb, u8 ifnum, u32 flags, u8 version) +{ + u8 *buf; + int ret; + + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + *buf = version; + ret = gud_usb_control_msg(usb, ifnum, false, GUD_REQ_SET_VERSION, 0, buf, sizeof(*buf)); + kfree(buf); + if (ret == -EPIPE) + return -EPROTONOSUPPORT; + if (ret) + return ret; + + if (flags & GUD_DISPLAY_FLAG_STATUS_ON_SET) { + u8 status; + + ret = gud_usb_get_status(usb, ifnum, &status); + if (!ret && status != GUD_STATUS_OK) + ret = -EPROTONOSUPPORT; + } + + return ret; +} + +static int gud_get_properties(struct gud_device *gdrm, unsigned int num_properties) +{ + struct gud_property_req *properties; + unsigned int i; + int ret; + + if (!num_properties) + return 0; + + gdrm->properties = drmm_kcalloc(&gdrm->drm, num_properties, sizeof(*gdrm->properties), + GFP_KERNEL); + if (!gdrm->properties) + return -ENOMEM; + + properties = kcalloc(num_properties, sizeof(*properties), GFP_KERNEL); + if (!properties) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_PROPERTIES, 0, + properties, num_properties * sizeof(*properties)); + if (ret) + goto out; + + for (i = 0; i < num_properties; i++) { + u16 prop = le16_to_cpu(properties[i].prop); + u64 val = le64_to_cpu(properties[i].val); + + switch (prop) { + case GUD_PROPERTY_ROTATION: + /* + * DRM UAPI matches the protocol so use the value directly, + * but mask out any additions on future devices. + */ + val &= GUD_ROTATION_MASK; + ret = drm_plane_create_rotation_property(&gdrm->pipe.plane, + DRM_MODE_ROTATE_0, val); + break; + default: + /* New ones might show up in future devices, skip those we don't know. */ + drm_dbg(&gdrm->drm, "Unknown property: %u\n", prop); + continue; + } + + if (ret) + goto out; + + gdrm->properties[gdrm->num_properties++] = prop; + } +out: + kfree(properties); + + return ret; +} + +static int gud_stats_debugfs(struct seq_file *m, void *data) +{ + struct drm_info_node *node = m->private; + struct gud_device *gdrm = to_gud_device(node->minor->dev); + char buf[10]; + + string_get_size(gdrm->bulk_len, 1, STRING_UNITS_2, buf, sizeof(buf)); + seq_printf(m, "Max buffer size: %s\n", buf); + seq_printf(m, "Number of errors: %u\n", gdrm->stats_num_errors); + + seq_puts(m, "Compression: "); + if (gdrm->compression & GUD_COMPRESSION_LZ4) + seq_puts(m, " lz4"); + seq_puts(m, "\n"); + + if (gdrm->compression) { + u64 remainder; + u64 ratio = div64_u64_rem(gdrm->stats_length, gdrm->stats_actual_length, + &remainder); + u64 ratio_frac = div64_u64(remainder * 10, gdrm->stats_actual_length); + + seq_printf(m, "Compression ratio: %llu.%llu\n", ratio, ratio_frac); + } + + return 0; +} + +static const struct drm_info_list gud_debugfs_list[] = { + { "stats", gud_stats_debugfs, 0, NULL }, +}; + +static void gud_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(gud_debugfs_list, ARRAY_SIZE(gud_debugfs_list), + minor->debugfs_root, minor); +} + +static const struct drm_simple_display_pipe_funcs gud_pipe_funcs = { + .check = gud_pipe_check, + .update = gud_pipe_update, + .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, +}; + +static const struct drm_mode_config_funcs gud_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const u64 gud_pipe_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +DEFINE_DRM_GEM_FOPS(gud_fops); + +static const struct drm_driver gud_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &gud_fops, + DRM_GEM_SHMEM_DRIVER_OPS, + .debugfs_init = gud_debugfs_init, + + .name = "gud", + .desc = "Generic USB Display", + .date = "20200422", + .major = 1, + .minor = 0, +}; + +static void gud_free_buffers_and_mutex(struct drm_device *drm, void *unused) +{ + struct gud_device *gdrm = to_gud_device(drm); + + vfree(gdrm->compress_buf); + kfree(gdrm->bulk_buf); + mutex_destroy(&gdrm->ctrl_lock); + mutex_destroy(&gdrm->damage_lock); +} + +static int gud_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + u8 ifnum = interface->cur_altsetting->desc.bInterfaceNumber; + struct usb_device *usb = interface_to_usbdev(interface); + struct device *dev = &interface->dev; + const struct drm_format_info *xrgb8888_emulation_format = NULL; + bool rgb565_supported = false, xrgb8888_supported = false; + struct usb_endpoint_descriptor *bulk_out; + struct gud_display_descriptor_req desc; + unsigned int num_formats = 0; + struct gud_device *gdrm; + size_t max_buffer_size = 0; + struct drm_device *drm; + u8 *formats_dev; + u32 *formats; + int ret, i; + + ret = usb_find_bulk_out_endpoint(interface->cur_altsetting, &bulk_out); + if (ret) + return ret; + + ret = gud_get_display_descriptor(interface, &desc); + if (ret) { + DRM_DEV_DEBUG_DRIVER(dev, "Not a display interface: ret=%d\n", ret); + return -ENODEV; + } + + if (desc.version > 1) { + ret = gud_set_version(usb, ifnum, le32_to_cpu(desc.flags), 1); + if (ret) { + if (ret == -EPROTONOSUPPORT) + dev_err(dev, "Protocol version %u is not supported\n", + desc.version); + return ret; + } + } + + gdrm = devm_drm_dev_alloc(dev, &gud_drm_driver, struct gud_device, drm); + if (IS_ERR(gdrm)) + return PTR_ERR(gdrm); + + drm = &gdrm->drm; + drm->mode_config.funcs = &gud_mode_config_funcs; + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + gdrm->ifnum = ifnum; + gdrm->flags = le32_to_cpu(desc.flags); + gdrm->compression = desc.compression & GUD_COMPRESSION_LZ4; + + if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE && gdrm->compression) + return -EINVAL; + + mutex_init(&gdrm->ctrl_lock); + mutex_init(&gdrm->damage_lock); + INIT_WORK(&gdrm->work, gud_flush_work); + gud_clear_damage(gdrm); + + ret = drmm_add_action_or_reset(drm, gud_free_buffers_and_mutex, NULL); + if (ret) + return ret; + + drm->mode_config.min_width = le32_to_cpu(desc.min_width); + drm->mode_config.max_width = le32_to_cpu(desc.max_width); + drm->mode_config.min_height = le32_to_cpu(desc.min_height); + drm->mode_config.max_height = le32_to_cpu(desc.max_height); + + formats_dev = devm_kmalloc(dev, desc.num_formats, GFP_KERNEL); + /* Add room for emulated XRGB8888 */ + formats = devm_kmalloc_array(dev, desc.num_formats + 1, sizeof(*formats), GFP_KERNEL); + if (!formats_dev || !formats) + return -ENOMEM; + + ret = gud_usb_get(gdrm, GUD_REQ_GET_FORMATS, 0, formats_dev, desc.num_formats); + if (ret) + return ret; + + for (i = 0; i < desc.num_formats; i++) { + const struct drm_format_info *info; + size_t fmt_buf_size; + u32 format; + + format = gud_to_fourcc(formats_dev[i]); + if (!format) { + drm_dbg(drm, "Unsupported format: 0x%02x\n", formats_dev[i]); + continue; + } + + if (format == GUD_DRM_FORMAT_R1) + info = &gud_drm_format_r1; + else + info = drm_format_info(format); + + switch (format) { + case GUD_DRM_FORMAT_R1: + xrgb8888_emulation_format = info; + break; + case DRM_FORMAT_RGB565: + rgb565_supported = true; + if (!xrgb8888_emulation_format) + xrgb8888_emulation_format = info; + break; + case DRM_FORMAT_XRGB8888: + xrgb8888_supported = true; + break; + }; + + fmt_buf_size = drm_format_info_min_pitch(info, 0, drm->mode_config.max_width) * + drm->mode_config.max_height; + max_buffer_size = max(max_buffer_size, fmt_buf_size); + + if (format == GUD_DRM_FORMAT_R1) + continue; /* Internal not for userspace */ + + formats[num_formats++] = format; + } + + if (!num_formats && !xrgb8888_emulation_format) { + dev_err(dev, "No supported pixel formats found\n"); + return -EINVAL; + } + + /* Prefer speed over color depth */ + if (rgb565_supported) + drm->mode_config.preferred_depth = 16; + + if (!xrgb8888_supported && xrgb8888_emulation_format) { + gdrm->xrgb8888_emulation_format = xrgb8888_emulation_format; + formats[num_formats++] = DRM_FORMAT_XRGB8888; + } + + if (desc.max_buffer_size) + max_buffer_size = le32_to_cpu(desc.max_buffer_size); +retry: + /* + * Use plain kmalloc here since devm_kmalloc() places struct devres at the beginning + * of the buffer it allocates. This wastes a lot of memory when allocating big buffers. + * Asking for 2M would actually allocate 4M. This would also prevent getting the biggest + * possible buffer potentially leading to split transfers. + */ + gdrm->bulk_buf = kmalloc(max_buffer_size, GFP_KERNEL | __GFP_NOWARN); + if (!gdrm->bulk_buf) { + max_buffer_size = roundup_pow_of_two(max_buffer_size) / 2; + if (max_buffer_size < SZ_512K) + return -ENOMEM; + goto retry; + } + + gdrm->bulk_pipe = usb_sndbulkpipe(usb, usb_endpoint_num(bulk_out)); + gdrm->bulk_len = max_buffer_size; + + if (gdrm->compression & GUD_COMPRESSION_LZ4) { + gdrm->lz4_comp_mem = devm_kmalloc(dev, LZ4_MEM_COMPRESS, GFP_KERNEL); + if (!gdrm->lz4_comp_mem) + return -ENOMEM; + + gdrm->compress_buf = vmalloc(gdrm->bulk_len); + if (!gdrm->compress_buf) + return -ENOMEM; + } + + ret = drm_simple_display_pipe_init(drm, &gdrm->pipe, &gud_pipe_funcs, + formats, num_formats, + gud_pipe_modifiers, NULL); + if (ret) + return ret; + + devm_kfree(dev, formats); + devm_kfree(dev, formats_dev); + + ret = gud_get_properties(gdrm, desc.num_properties); + if (ret) + return ret; + + drm_plane_enable_fb_damage_clips(&gdrm->pipe.plane); + + for (i = 0; i < desc.num_connectors; i++) { + ret = gud_connector_create(gdrm, i); + if (ret) + return ret; + } + + drm_mode_config_reset(drm); + + usb_set_intfdata(interface, gdrm); + + ret = drm_dev_register(drm, 0); + if (ret) + return ret; + + drm_kms_helper_poll_init(drm); + + drm_fbdev_generic_setup(drm, 0); + + return 0; +} + +static void gud_disconnect(struct usb_interface *interface) +{ + struct gud_device *gdrm = usb_get_intfdata(interface); + struct drm_device *drm = &gdrm->drm; + + drm_dbg(drm, "%s:\n", __func__); + + drm_kms_helper_poll_fini(drm); + drm_dev_unplug(drm); + drm_atomic_helper_shutdown(drm); +} + +static int gud_suspend(struct usb_interface *interface, pm_message_t message) +{ + struct gud_device *gdrm = usb_get_intfdata(interface); + + return drm_mode_config_helper_suspend(&gdrm->drm); +} + +static int gud_resume(struct usb_interface *interface) +{ + struct gud_device *gdrm = usb_get_intfdata(interface); + + drm_mode_config_helper_resume(&gdrm->drm); + + return 0; +} + +static const struct usb_device_id gud_id_table[] = { + { USB_DEVICE_INTERFACE_CLASS(0x1d50, 0x614d, USB_CLASS_VENDOR_SPEC) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, gud_id_table); + +static struct usb_driver gud_usb_driver = { + .name = "gud", + .probe = gud_probe, + .disconnect = gud_disconnect, + .id_table = gud_id_table, + .suspend = gud_suspend, + .resume = gud_resume, + .reset_resume = gud_resume, +}; + +module_usb_driver(gud_usb_driver); + +MODULE_AUTHOR("Noralf Trønnes"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/gud/gud_internal.h b/drivers/gpu/drm/gud/gud_internal.h new file mode 100644 index 000000000000..46e7d7ed2c69 --- /dev/null +++ b/drivers/gpu/drm/gud/gud_internal.h @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef __LINUX_GUD_INTERNAL_H +#define __LINUX_GUD_INTERNAL_H + +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <uapi/drm/drm_fourcc.h> + +#include <drm/drm_modes.h> +#include <drm/drm_simple_kms_helper.h> + +struct gud_device { + struct drm_device drm; + struct drm_simple_display_pipe pipe; + struct work_struct work; + u8 ifnum; + u32 flags; + const struct drm_format_info *xrgb8888_emulation_format; + + u16 *properties; + unsigned int num_properties; + + unsigned int bulk_pipe; + void *bulk_buf; + size_t bulk_len; + + u8 compression; + void *lz4_comp_mem; + void *compress_buf; + + u64 stats_length; + u64 stats_actual_length; + unsigned int stats_num_errors; + + struct mutex ctrl_lock; /* Serialize get/set and status transfers */ + + struct mutex damage_lock; /* Protects the following members: */ + struct drm_framebuffer *fb; + struct drm_rect damage; + bool prev_flush_failed; +}; + +static inline struct gud_device *to_gud_device(struct drm_device *drm) +{ + return container_of(drm, struct gud_device, drm); +} + +static inline struct usb_device *gud_to_usb_device(struct gud_device *gdrm) +{ + return interface_to_usbdev(to_usb_interface(gdrm->drm.dev)); +} + +int gud_usb_get(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len); +int gud_usb_set(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len); +int gud_usb_write8(struct gud_device *gdrm, u8 request, u8 val); + +void gud_clear_damage(struct gud_device *gdrm); +void gud_flush_work(struct work_struct *work); +int gud_pipe_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *new_plane_state, + struct drm_crtc_state *new_crtc_state); +void gud_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state); +int gud_connector_fill_properties(struct drm_connector *connector, + struct drm_connector_state *connector_state, + struct gud_property_req *properties); +int gud_connector_create(struct gud_device *gdrm, unsigned int index); + +#define GUD_DRM_FORMAT_R1 fourcc_code('R', '1', ' ', ' ') + +static inline u8 gud_from_fourcc(u32 fourcc) +{ + switch (fourcc) { + case GUD_DRM_FORMAT_R1: + return GUD_PIXEL_FORMAT_R1; + case DRM_FORMAT_RGB565: + return GUD_PIXEL_FORMAT_RGB565; + case DRM_FORMAT_XRGB8888: + return GUD_PIXEL_FORMAT_XRGB8888; + case DRM_FORMAT_ARGB8888: + return GUD_PIXEL_FORMAT_ARGB8888; + }; + + return 0; +} + +static inline u32 gud_to_fourcc(u8 format) +{ + switch (format) { + case GUD_PIXEL_FORMAT_R1: + return GUD_DRM_FORMAT_R1; + case GUD_PIXEL_FORMAT_RGB565: + return DRM_FORMAT_RGB565; + case GUD_PIXEL_FORMAT_XRGB8888: + return DRM_FORMAT_XRGB8888; + case GUD_PIXEL_FORMAT_ARGB8888: + return DRM_FORMAT_ARGB8888; + }; + + return 0; +} + +static inline void gud_from_display_mode(struct gud_display_mode_req *dst, + const struct drm_display_mode *src) +{ + u32 flags = src->flags & GUD_DISPLAY_MODE_FLAG_USER_MASK; + + if (src->type & DRM_MODE_TYPE_PREFERRED) + flags |= GUD_DISPLAY_MODE_FLAG_PREFERRED; + + dst->clock = cpu_to_le32(src->clock); + dst->hdisplay = cpu_to_le16(src->hdisplay); + dst->hsync_start = cpu_to_le16(src->hsync_start); + dst->hsync_end = cpu_to_le16(src->hsync_end); + dst->htotal = cpu_to_le16(src->htotal); + dst->vdisplay = cpu_to_le16(src->vdisplay); + dst->vsync_start = cpu_to_le16(src->vsync_start); + dst->vsync_end = cpu_to_le16(src->vsync_end); + dst->vtotal = cpu_to_le16(src->vtotal); + dst->flags = cpu_to_le32(flags); +} + +static inline void gud_to_display_mode(struct drm_display_mode *dst, + const struct gud_display_mode_req *src) +{ + u32 flags = le32_to_cpu(src->flags); + + memset(dst, 0, sizeof(*dst)); + dst->clock = le32_to_cpu(src->clock); + dst->hdisplay = le16_to_cpu(src->hdisplay); + dst->hsync_start = le16_to_cpu(src->hsync_start); + dst->hsync_end = le16_to_cpu(src->hsync_end); + dst->htotal = le16_to_cpu(src->htotal); + dst->vdisplay = le16_to_cpu(src->vdisplay); + dst->vsync_start = le16_to_cpu(src->vsync_start); + dst->vsync_end = le16_to_cpu(src->vsync_end); + dst->vtotal = le16_to_cpu(src->vtotal); + dst->flags = flags & GUD_DISPLAY_MODE_FLAG_USER_MASK; + dst->type = DRM_MODE_TYPE_DRIVER; + if (flags & GUD_DISPLAY_MODE_FLAG_PREFERRED) + dst->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(dst); +} + +#endif diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c new file mode 100644 index 000000000000..1eef930bcf9b --- /dev/null +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2020 Noralf Trønnes + */ + +#include <linux/dma-buf.h> +#include <linux/lz4.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_format_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_rect.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/gud.h> + +#include "gud_internal.h" + +static bool gud_is_big_endian(void) +{ +#if defined(__BIG_ENDIAN) + return true; +#else + return false; +#endif +} + +static size_t gud_xrgb8888_to_r124(u8 *dst, const struct drm_format_info *format, + void *src, struct drm_framebuffer *fb, + struct drm_rect *rect) +{ + unsigned int block_width = drm_format_info_block_width(format, 0); + unsigned int bits_per_pixel = 8 / block_width; + unsigned int x, y, width, height; + u8 *p, *block = dst; /* Assign to silence compiler warning */ + size_t len; + void *buf; + + WARN_ON_ONCE(format->char_per_block[0] != 1); + + /* Start on a byte boundary */ + rect->x1 = ALIGN_DOWN(rect->x1, block_width); + width = drm_rect_width(rect); + height = drm_rect_height(rect); + len = drm_format_info_min_pitch(format, 0, width) * height; + + buf = kmalloc(width * height, GFP_KERNEL); + if (!buf) + return 0; + + drm_fb_xrgb8888_to_gray8(buf, src, fb, rect); + + p = buf; + for (y = 0; y < drm_rect_height(rect); y++) { + for (x = 0; x < drm_rect_width(rect); x++) { + if (!(x % block_width)) { + block = dst++; + *block = 0; + } + + *block <<= bits_per_pixel; + *block |= (*p++) >> (8 - bits_per_pixel); + } + } + + kfree(buf); + + return len; +} + +static int gud_prep_flush(struct gud_device *gdrm, struct drm_framebuffer *fb, + const struct drm_format_info *format, struct drm_rect *rect, + struct gud_set_buffer_req *req) +{ + struct dma_buf_attachment *import_attach = fb->obj[0]->import_attach; + u8 compression = gdrm->compression; + struct dma_buf_map map; + void *vaddr, *buf; + size_t pitch, len; + int ret = 0; + + pitch = drm_format_info_min_pitch(format, 0, drm_rect_width(rect)); + len = pitch * drm_rect_height(rect); + if (len > gdrm->bulk_len) + return -E2BIG; + + ret = drm_gem_shmem_vmap(fb->obj[0], &map); + if (ret) + return ret; + + vaddr = map.vaddr + fb->offsets[0]; + + if (import_attach) { + ret = dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); + if (ret) + goto vunmap; + } +retry: + if (compression) + buf = gdrm->compress_buf; + else + buf = gdrm->bulk_buf; + + /* + * Imported buffers are assumed to be write-combined and thus uncached + * with slow reads (at least on ARM). + */ + if (format != fb->format) { + if (format->format == GUD_DRM_FORMAT_R1) { + len = gud_xrgb8888_to_r124(buf, format, vaddr, fb, rect); + if (!len) { + ret = -ENOMEM; + goto end_cpu_access; + } + } else if (format->format == DRM_FORMAT_RGB565) { + drm_fb_xrgb8888_to_rgb565(buf, vaddr, fb, rect, gud_is_big_endian()); + } + } else if (gud_is_big_endian() && format->cpp[0] > 1) { + drm_fb_swab(buf, vaddr, fb, rect, !import_attach); + } else if (compression && !import_attach && pitch == fb->pitches[0]) { + /* can compress directly from the framebuffer */ + buf = vaddr + rect->y1 * pitch; + } else { + drm_fb_memcpy(buf, vaddr, fb, rect); + } + + memset(req, 0, sizeof(*req)); + req->x = cpu_to_le32(rect->x1); + req->y = cpu_to_le32(rect->y1); + req->width = cpu_to_le32(drm_rect_width(rect)); + req->height = cpu_to_le32(drm_rect_height(rect)); + req->length = cpu_to_le32(len); + + if (compression & GUD_COMPRESSION_LZ4) { + int complen; + + complen = LZ4_compress_default(buf, gdrm->bulk_buf, len, len, gdrm->lz4_comp_mem); + if (complen < 0) { + compression = 0; + goto retry; + } + + req->compression = GUD_COMPRESSION_LZ4; + req->compressed_length = cpu_to_le32(complen); + } + +end_cpu_access: + if (import_attach) + dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); +vunmap: + drm_gem_shmem_vunmap(fb->obj[0], &map); + + return ret; +} + +static int gud_flush_rect(struct gud_device *gdrm, struct drm_framebuffer *fb, + const struct drm_format_info *format, struct drm_rect *rect) +{ + struct usb_device *usb = gud_to_usb_device(gdrm); + struct gud_set_buffer_req req; + int ret, actual_length; + size_t len, trlen; + + drm_dbg(&gdrm->drm, "Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect)); + + ret = gud_prep_flush(gdrm, fb, format, rect, &req); + if (ret) + return ret; + + len = le32_to_cpu(req.length); + + if (req.compression) + trlen = le32_to_cpu(req.compressed_length); + else + trlen = len; + + gdrm->stats_length += len; + /* Did it wrap around? */ + if (gdrm->stats_length <= len && gdrm->stats_actual_length) { + gdrm->stats_length = len; + gdrm->stats_actual_length = 0; + } + gdrm->stats_actual_length += trlen; + + if (!(gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) || gdrm->prev_flush_failed) { + ret = gud_usb_set(gdrm, GUD_REQ_SET_BUFFER, 0, &req, sizeof(req)); + if (ret) + return ret; + } + + ret = usb_bulk_msg(usb, gdrm->bulk_pipe, gdrm->bulk_buf, trlen, + &actual_length, msecs_to_jiffies(3000)); + if (!ret && trlen != actual_length) + ret = -EIO; + if (ret) + gdrm->stats_num_errors++; + + return ret; +} + +void gud_clear_damage(struct gud_device *gdrm) +{ + gdrm->damage.x1 = INT_MAX; + gdrm->damage.y1 = INT_MAX; + gdrm->damage.x2 = 0; + gdrm->damage.y2 = 0; +} + +static void gud_add_damage(struct gud_device *gdrm, struct drm_rect *damage) +{ + gdrm->damage.x1 = min(gdrm->damage.x1, damage->x1); + gdrm->damage.y1 = min(gdrm->damage.y1, damage->y1); + gdrm->damage.x2 = max(gdrm->damage.x2, damage->x2); + gdrm->damage.y2 = max(gdrm->damage.y2, damage->y2); +} + +static void gud_retry_failed_flush(struct gud_device *gdrm, struct drm_framebuffer *fb, + struct drm_rect *damage) +{ + /* + * pipe_update waits for the worker when the display mode is going to change. + * This ensures that the width and height is still the same making it safe to + * add back the damage. + */ + + mutex_lock(&gdrm->damage_lock); + if (!gdrm->fb) { + drm_framebuffer_get(fb); + gdrm->fb = fb; + } + gud_add_damage(gdrm, damage); + mutex_unlock(&gdrm->damage_lock); + + /* Retry only once to avoid a possible storm in case of continues errors. */ + if (!gdrm->prev_flush_failed) + queue_work(system_long_wq, &gdrm->work); + gdrm->prev_flush_failed = true; +} + +void gud_flush_work(struct work_struct *work) +{ + struct gud_device *gdrm = container_of(work, struct gud_device, work); + const struct drm_format_info *format; + struct drm_framebuffer *fb; + struct drm_rect damage; + unsigned int i, lines; + int idx, ret = 0; + size_t pitch; + + if (!drm_dev_enter(&gdrm->drm, &idx)) + return; + + mutex_lock(&gdrm->damage_lock); + fb = gdrm->fb; + gdrm->fb = NULL; + damage = gdrm->damage; + gud_clear_damage(gdrm); + mutex_unlock(&gdrm->damage_lock); + + if (!fb) + goto out; + + format = fb->format; + if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format) + format = gdrm->xrgb8888_emulation_format; + + /* Split update if it's too big */ + pitch = drm_format_info_min_pitch(format, 0, drm_rect_width(&damage)); + lines = drm_rect_height(&damage); + + if (gdrm->bulk_len < lines * pitch) + lines = gdrm->bulk_len / pitch; + + for (i = 0; i < DIV_ROUND_UP(drm_rect_height(&damage), lines); i++) { + struct drm_rect rect = damage; + + rect.y1 += i * lines; + rect.y2 = min_t(u32, rect.y1 + lines, damage.y2); + + ret = gud_flush_rect(gdrm, fb, format, &rect); + if (ret) { + if (ret != -ENODEV && ret != -ECONNRESET && + ret != -ESHUTDOWN && ret != -EPROTO) { + gud_retry_failed_flush(gdrm, fb, &damage); + dev_err_once(fb->dev->dev, + "Failed to flush framebuffer: error=%d\n", ret); + } + break; + } + + gdrm->prev_flush_failed = false; + } + + drm_framebuffer_put(fb); +out: + drm_dev_exit(idx); +} + +static void gud_fb_queue_damage(struct gud_device *gdrm, struct drm_framebuffer *fb, + struct drm_rect *damage) +{ + struct drm_framebuffer *old_fb = NULL; + + mutex_lock(&gdrm->damage_lock); + + if (fb != gdrm->fb) { + old_fb = gdrm->fb; + drm_framebuffer_get(fb); + gdrm->fb = fb; + } + + gud_add_damage(gdrm, damage); + + mutex_unlock(&gdrm->damage_lock); + + queue_work(system_long_wq, &gdrm->work); + + if (old_fb) + drm_framebuffer_put(old_fb); +} + +int gud_pipe_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *new_plane_state, + struct drm_crtc_state *new_crtc_state) +{ + struct gud_device *gdrm = to_gud_device(pipe->crtc.dev); + struct drm_plane_state *old_plane_state = pipe->plane.state; + const struct drm_display_mode *mode = &new_crtc_state->mode; + struct drm_atomic_state *state = new_plane_state->state; + struct drm_framebuffer *old_fb = old_plane_state->fb; + struct drm_connector_state *connector_state = NULL; + struct drm_framebuffer *fb = new_plane_state->fb; + const struct drm_format_info *format = fb->format; + struct drm_connector *connector; + int idx, ret, num_properties; + struct gud_state_req *req; + unsigned int i; + size_t len; + + if (WARN_ON_ONCE(!fb)) + return -EINVAL; + + if (old_plane_state->rotation != new_plane_state->rotation) + new_crtc_state->mode_changed = true; + + if (old_fb && old_fb->format != format) + new_crtc_state->mode_changed = true; + + if (!new_crtc_state->mode_changed && !new_crtc_state->connectors_changed) + return 0; + + /* Only one connector is supported */ + if (hweight32(new_crtc_state->connector_mask) != 1) + return -EINVAL; + + if (format->format == DRM_FORMAT_XRGB8888 && gdrm->xrgb8888_emulation_format) + format = gdrm->xrgb8888_emulation_format; + + for_each_new_connector_in_state(state, connector, connector_state, i) { + if (connector_state->crtc) + break; + } + + if (WARN_ON_ONCE(!connector_state)) + return -ENOENT; + + num_properties = gud_connector_fill_properties(connector, NULL, NULL); + if (num_properties < 0) + return num_properties; + + num_properties += gdrm->num_properties; + + len = struct_size(req, properties, num_properties); + req = kzalloc(len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + gud_from_display_mode(&req->mode, mode); + + req->format = gud_from_fourcc(format->format); + if (WARN_ON_ONCE(!req->format)) + return -EINVAL; + + req->connector = drm_connector_index(connector); + req->num_properties = num_properties; + + num_properties = gud_connector_fill_properties(connector, connector_state, + req->properties); + + for (i = 0; i < gdrm->num_properties; i++) { + u16 prop = gdrm->properties[i]; + u64 val; + + switch (prop) { + case GUD_PROPERTY_ROTATION: + /* DRM UAPI matches the protocol so use value directly */ + val = new_plane_state->rotation; + break; + default: + WARN_ON_ONCE(1); + ret = -EINVAL; + goto out; + } + + req->properties[num_properties + i].prop = cpu_to_le16(prop); + req->properties[num_properties + i].val = cpu_to_le64(val); + } + + if (drm_dev_enter(fb->dev, &idx)) { + ret = gud_usb_set(gdrm, GUD_REQ_SET_STATE_CHECK, 0, req, len); + drm_dev_exit(idx); + } else { + ret = -ENODEV; + } +out: + kfree(req); + + return ret; +} + +void gud_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct drm_device *drm = pipe->crtc.dev; + struct gud_device *gdrm = to_gud_device(drm); + struct drm_plane_state *state = pipe->plane.state; + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = &pipe->crtc; + struct drm_rect damage; + int idx; + + if (crtc->state->mode_changed || !crtc->state->enable) { + cancel_work_sync(&gdrm->work); + mutex_lock(&gdrm->damage_lock); + if (gdrm->fb) { + drm_framebuffer_put(gdrm->fb); + gdrm->fb = NULL; + } + gud_clear_damage(gdrm); + mutex_unlock(&gdrm->damage_lock); + } + + if (!drm_dev_enter(drm, &idx)) + return; + + if (!old_state->fb) + gud_usb_write8(gdrm, GUD_REQ_SET_CONTROLLER_ENABLE, 1); + + if (fb && (crtc->state->mode_changed || crtc->state->connectors_changed)) + gud_usb_set(gdrm, GUD_REQ_SET_STATE_COMMIT, 0, NULL, 0); + + if (crtc->state->active_changed) + gud_usb_write8(gdrm, GUD_REQ_SET_DISPLAY_ENABLE, crtc->state->active); + + if (drm_atomic_helper_damage_merged(old_state, state, &damage)) { + if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) + drm_rect_init(&damage, 0, 0, fb->width, fb->height); + gud_fb_queue_damage(gdrm, fb, &damage); + } + + if (!crtc->state->enable) + gud_usb_write8(gdrm, GUD_REQ_SET_CONTROLLER_ENABLE, 0); + + drm_dev_exit(idx); +} diff --git a/include/drm/gud.h b/include/drm/gud.h new file mode 100644 index 000000000000..ebf19f50f780 --- /dev/null +++ b/include/drm/gud.h @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2020 Noralf Trønnes + */ + +#ifndef __LINUX_GUD_H +#define __LINUX_GUD_H + +#include <linux/types.h> + +/* + * struct gud_display_descriptor_req - Display descriptor + * @magic: Magic value GUD_DISPLAY_MAGIC + * @version: Protocol version + * @flags: Flags + * - STATUS_ON_SET: Always do a status request after a SET request. + * This is used by the Linux gadget driver since it has + * no way to control the status stage of a control OUT + * request that has a payload. + * - FULL_UPDATE: Always send the entire framebuffer when flushing changes. + * The GUD_REQ_SET_BUFFER request will not be sent + * before each bulk transfer, it will only be sent if the + * previous bulk transfer had failed. This gives the device + * a chance to reset its state machine if needed. + * This flag can not be used in combination with compression. + * @compression: Supported compression types + * - GUD_COMPRESSION_LZ4: LZ4 lossless compression. + * @max_buffer_size: Maximum buffer size the device can handle (optional). + * This is useful for devices that don't have a big enough + * buffer to decompress the entire framebuffer in one go. + * @min_width: Minimum pixel width the controller can handle + * @max_width: Maximum width + * @min_height: Minimum height + * @max_height: Maximum height + * @num_formats: Number of supported pixel formats + * @num_properties: Number of properties that are not connector properties + * @num_connectors: Number of connectors + * + * Devices that have only one display mode will have min_width == max_width + * and min_height == max_height. + */ +struct gud_display_descriptor_req { + __u32 magic; +#define GUD_DISPLAY_MAGIC 0x1d50614d + __u8 version; + __le32 flags; +#define GUD_DISPLAY_FLAG_STATUS_ON_SET BIT(0) +#define GUD_DISPLAY_FLAG_FULL_UPDATE BIT(1) + __u8 compression; +#define GUD_COMPRESSION_LZ4 BIT(0) + __le32 max_buffer_size; + __le32 min_width; + __le32 max_width; + __le32 min_height; + __le32 max_height; + __u8 num_formats; + __u8 num_properties; + __u8 num_connectors; +} __packed; + +/* + * struct gud_property_req - Property + * @prop: Property + * @val: Value + */ +struct gud_property_req { + __le16 prop; + __le64 val; +} __packed; + +/* + * struct gud_display_mode_req - Display mode + * @clock: Pixel clock in kHz + * @hdisplay: Horizontal display size + * @hsync_start: Horizontal sync start + * @hsync_end: Horizontal sync end + * @htotal: Horizontal total size + * @vdisplay: Vertical display size + * @vsync_start: Vertical sync start + * @vsync_end: Vertical sync end + * @vtotal: Vertical total size + * @flags: Bits 0-13 are the same as in the RandR protocol and also what DRM uses. + * The deprecated bits are reused for internal protocol flags leaving us + * free to follow DRM for the other bits in the future. + * - FLAG_PREFERRED: Set on the preferred display mode. + */ +struct gud_display_mode_req { + __le32 clock; + __le16 hdisplay; + __le16 hsync_start; + __le16 hsync_end; + __le16 htotal; + __le16 vdisplay; + __le16 vsync_start; + __le16 vsync_end; + __le16 vtotal; + __le32 flags; +#define GUD_DISPLAY_MODE_FLAG_PHSYNC BIT(0) +#define GUD_DISPLAY_MODE_FLAG_NHSYNC BIT(1) +#define GUD_DISPLAY_MODE_FLAG_PVSYNC BIT(2) +#define GUD_DISPLAY_MODE_FLAG_NVSYNC BIT(3) +#define GUD_DISPLAY_MODE_FLAG_INTERLACE BIT(4) +#define GUD_DISPLAY_MODE_FLAG_DBLSCAN BIT(5) +#define GUD_DISPLAY_MODE_FLAG_CSYNC BIT(6) +#define GUD_DISPLAY_MODE_FLAG_PCSYNC BIT(7) +#define GUD_DISPLAY_MODE_FLAG_NCSYNC BIT(8) +#define GUD_DISPLAY_MODE_FLAG_HSKEW BIT(9) +/* BCast and PixelMultiplex are deprecated */ +#define GUD_DISPLAY_MODE_FLAG_DBLCLK BIT(12) +#define GUD_DISPLAY_MODE_FLAG_CLKDIV2 BIT(13) +#define GUD_DISPLAY_MODE_FLAG_USER_MASK \ + (GUD_DISPLAY_MODE_FLAG_PHSYNC | GUD_DISPLAY_MODE_FLAG_NHSYNC | \ + GUD_DISPLAY_MODE_FLAG_PVSYNC | GUD_DISPLAY_MODE_FLAG_NVSYNC | \ + GUD_DISPLAY_MODE_FLAG_INTERLACE | GUD_DISPLAY_MODE_FLAG_DBLSCAN | \ + GUD_DISPLAY_MODE_FLAG_CSYNC | GUD_DISPLAY_MODE_FLAG_PCSYNC | \ + GUD_DISPLAY_MODE_FLAG_NCSYNC | GUD_DISPLAY_MODE_FLAG_HSKEW | \ + GUD_DISPLAY_MODE_FLAG_DBLCLK | GUD_DISPLAY_MODE_FLAG_CLKDIV2) +/* Internal protocol flags */ +#define GUD_DISPLAY_MODE_FLAG_PREFERRED BIT(10) +} __packed; + +/* + * struct gud_connector_descriptor_req - Connector descriptor + * @connector_type: Connector type (GUD_CONNECTOR_TYPE_*). + * If the host doesn't support the type it should fall back to PANEL. + * @flags: Flags + * - POLL_STATUS: Connector status can change (polled every 10 seconds) + * - INTERLACE: Interlaced modes are supported + * - DOUBLESCAN: Doublescan modes are supported + * @num_properties: Number of supported properties + */ +struct gud_connector_descriptor_req { + __u8 connector_type; +#define GUD_CONNECTOR_TYPE_PANEL 0 +#define GUD_CONNECTOR_TYPE_VGA 1 +#define GUD_CONNECTOR_TYPE_COMPOSITE 2 +#define GUD_CONNECTOR_TYPE_SVIDEO 3 +#define GUD_CONNECTOR_TYPE_COMPONENT 4 +#define GUD_CONNECTOR_TYPE_DVI 5 +#define GUD_CONNECTOR_TYPE_DISPLAYPORT 6 +#define GUD_CONNECTOR_TYPE_HDMI 7 + __le32 flags; +#define GUD_CONNECTOR_FLAGS_POLL_STATUS BIT(0) +#define GUD_CONNECTOR_FLAGS_INTERLACE BIT(1) +#define GUD_CONNECTOR_FLAGS_DOUBLESCAN BIT(2) + __u8 num_properties; +} __packed; + +/* + * struct gud_connector_status_req - Connector status + * @status: Status + * - DISCONNECTED: Connector is disconnected + * - CONNECTED: Connector is connected + * - UNKNOWN: Connection status is unknown + * Flags: + * - CHANGED: A change has happened since the last request + * @num_modes: Number of available display modes + * @edid_len: Length of EDID data + * + * If @num_modes is zero, EDID is used to create display modes. + * If both @num_modes and @edid_len are set, EDID is just passed on to userspace + * in the EDID connector property. + * + * Userspace will get a HOTPLUG uevent if one of the following is true: + * - Connection status has changed + * - @num_modes or @edid_len has changed + * - CHANGED is set + */ +struct gud_connector_status_req { + __u8 status; +#define GUD_CONNECTOR_STATUS_DISCONNECTED 0x00 +#define GUD_CONNECTOR_STATUS_CONNECTED 0x01 +#define GUD_CONNECTOR_STATUS_UNKNOWN 0x02 +#define GUD_CONNECTOR_STATUS_CONNECTED_MASK 0x03 +#define GUD_CONNECTOR_STATUS_CHANGED BIT(7) + __le16 num_modes; + __le16 edid_len; +} __packed; + +/* + * struct gud_set_buffer_req - Set buffer transfer info + * @x: X position of rectangle + * @y: Y position + * @width: Pixel width of rectangle + * @height: Pixel height + * @length: Buffer length in bytes + * @compression: Transfer compression + * @compressed_length: Compressed buffer length + * + * This request is issued right before the bulk transfer. + * @x, @y, @width and @height specifies the rectangle where the buffer should be + * placed inside the framebuffer. + */ +struct gud_set_buffer_req { + __le32 x; + __le32 y; + __le32 width; + __le32 height; + __le32 length; + __u8 compression; + __le32 compressed_length; +} __packed; + +/* + * struct gud_state_req - Display state + * @mode: Display mode + * @format: Pixel format GUD_PIXEL_FORMAT_* + * @connector: Connector index + * @num_properties: Number of properties in the state + * @properties: Array of properties + * + * The entire state is transferred each time there's a change. + */ +struct gud_state_req { + struct gud_display_mode_req mode; + __u8 format; + __u8 connector; + __u8 num_properties; + struct gud_property_req properties[]; +} __packed; + +/* List of supported connector properties: */ + +/* Margins in pixels to deal with overscan, range 0-100 */ +#define GUD_PROPERTY_TV_LEFT_MARGIN 1 +#define GUD_PROPERTY_TV_RIGHT_MARGIN 2 +#define GUD_PROPERTY_TV_TOP_MARGIN 3 +#define GUD_PROPERTY_TV_BOTTOM_MARGIN 4 +/* Number of modes are placed at _SHIFT in val on retrieval */ +#define GUD_PROPERTY_TV_MODE 5 + #define GUD_CONNECTOR_TV_MODE_NUM_SHIFT 16 +/* Brightness in percent, range 0-100 */ +#define GUD_PROPERTY_TV_BRIGHTNESS 6 +/* Contrast in percent, range 0-100 */ +#define GUD_PROPERTY_TV_CONTRAST 7 +/* Flicker reduction in percent, range 0-100 */ +#define GUD_PROPERTY_TV_FLICKER_REDUCTION 8 +/* Overscan in percent, range 0-100 */ +#define GUD_PROPERTY_TV_OVERSCAN 9 +/* Saturation in percent, range 0-100 */ +#define GUD_PROPERTY_TV_SATURATION 10 +/* Hue in percent, range 0-100 */ +#define GUD_PROPERTY_TV_HUE 11 + +/* + * Backlight brightness is in the range 0-100 inclusive. The value represents the human perceptual + * brightness and not a linear PWM value. 0 is minimum brightness which should not turn the + * backlight completely off. The DPMS connector property should be used to control power which will + * trigger a GUD_REQ_SET_DISPLAY_ENABLE request. + * + * This does not map to a DRM property, it is used with the backlight device. + */ +#define GUD_PROPERTY_BACKLIGHT_BRIGHTNESS 12 + +/* List of supported properties that are not connector propeties: */ + +/* + * Plane rotation. Should return the supported bitmask on + * GUD_REQ_GET_PROPERTIES. GUD_ROTATION_0 is mandatory. + * + * Note: This is not display rotation so 90/270 will need scaling to make it fit (unless squared). + */ +#define GUD_PROPERTY_ROTATION 50 + #define GUD_ROTATION_0 BIT(0) + #define GUD_ROTATION_90 BIT(1) + #define GUD_ROTATION_180 BIT(2) + #define GUD_ROTATION_270 BIT(3) + #define GUD_ROTATION_REFLECT_X BIT(4) + #define GUD_ROTATION_REFLECT_Y BIT(5) + #define GUD_ROTATION_MASK (GUD_ROTATION_0 | GUD_ROTATION_90 | \ + GUD_ROTATION_180 | GUD_ROTATION_270 | \ + GUD_ROTATION_REFLECT_X | GUD_ROTATION_REFLECT_Y) + +/* USB Control requests: */ + +/* Get status from the last GET/SET control request. Value is u8. */ +#define GUD_REQ_GET_STATUS 0x00 + /* Status values: */ + #define GUD_STATUS_OK 0x00 + #define GUD_STATUS_BUSY 0x01 + #define GUD_STATUS_REQUEST_NOT_SUPPORTED 0x02 + #define GUD_STATUS_PROTOCOL_ERROR 0x03 + #define GUD_STATUS_INVALID_PARAMETER 0x04 + #define GUD_STATUS_ERROR 0x05 + +/* Get display descriptor as a &gud_display_descriptor_req */ +#define GUD_REQ_GET_DESCRIPTOR 0x01 + +/* + * If the host driver doesn't support the device protocol version it will send the versions it + * supports starting with the latest. If the device isn't backwards compatible or doesn't support + * the version the host suggests, it shall stall the request. The version is sent as u8. + */ +#define GUD_REQ_SET_VERSION 0x30 + +/* Get supported pixel formats as a byte array of GUD_PIXEL_FORMAT_* */ +#define GUD_REQ_GET_FORMATS 0x40 + /* R1 is a 1-bit monochrome transfer format presented to userspace as XRGB8888 */ + #define GUD_PIXEL_FORMAT_R1 0x01 + #define GUD_PIXEL_FORMAT_RGB565 0x40 + #define GUD_PIXEL_FORMAT_XRGB8888 0x80 + #define GUD_PIXEL_FORMAT_ARGB8888 0x81 + +/* + * Get supported properties that are not connector propeties as a &gud_property_req array. + * gud_property_req.val often contains the initial value for the property. + */ +#define GUD_REQ_GET_PROPERTIES 0x41 + +/* Connector requests have the connector index passed in the wValue field */ + +/* Get connector descriptor as a &gud_connector_descriptor_req */ +#define GUD_REQ_GET_CONNECTOR 0x50 + +/* + * Get properties supported by the connector as a &gud_property_req array. + * gud_property_req.val often contains the initial value for the property. + */ +#define GUD_REQ_GET_CONNECTOR_PROPERTIES 0x51 + +/* + * Issued when there's a TV_MODE property present. + * Gets an array of the supported TV_MODE names each entry of length + * GUD_CONNECTOR_TV_MODE_NAME_LEN. Names must be NUL-terminated. + */ +#define GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES 0x52 + #define GUD_CONNECTOR_TV_MODE_NAME_LEN 16 + +/* When userspace checks connector status, this is issued first, not used for poll requests. */ +#define GUD_REQ_SET_CONNECTOR_FORCE_DETECT 0x53 + +/* Get connector status as &gud_connector_status_req. */ +#define GUD_REQ_GET_CONNECTOR_STATUS 0x54 + +/* Get &gud_display_mode_req array of supported display modes */ +#define GUD_REQ_GET_CONNECTOR_MODES 0x55 + +/* Get Extended Display Identification Data */ +#define GUD_REQ_GET_CONNECTOR_EDID 0x56 + +/* Set buffer properties before bulk transfer as &gud_set_buffer_req */ +#define GUD_REQ_SET_BUFFER 0x60 + +/* Check display configuration as &gud_state_req */ +#define GUD_REQ_SET_STATE_CHECK 0x61 + +/* Apply the previous STATE_CHECK configuration */ +#define GUD_REQ_SET_STATE_COMMIT 0x62 + +/* Enable/disable the display controller, value is u8: 0/1 */ +#define GUD_REQ_SET_CONTROLLER_ENABLE 0x63 + +/* Enable/disable display/output (DPMS), value is u8: 0/1 */ +#define GUD_REQ_SET_DISPLAY_ENABLE 0x64 + +#endif
Hi
Am 20.01.21 um 18:00 schrieb Noralf Trønnes:
Hi,
A while back I had the idea to turn a Raspberry Pi Zero into a $5 USB to HDMI/SDTV/DSI/DPI display adapter.
The reason for calling it 'Generic' is so anyone can make a USB display/adapter against this driver, all that's needed is to add a USB vid:pid.
Well, I'd strongly ask to not call it "generic", because it isn't. We have other USB drivers and anyone can make a USB display with these protocols as well. That doesn't make them generic. A USB-standardized protocol would be generic. Maybe call it custom, or home-made.
Best regards Thomas
Unfortunately I've had some compounding health problems that have severally limited the time I can spend in front of a computer. For this reason I've decided to keep the gadget driver out-of-tree and focus on getting the host driver merged first.
See the wiki[1] for more information and images for the Raspberry Pi Zero/4.
One big change this time is that I've followed Peter Stuge's advice to not let DRM stuff leak into the USB protocol. This has made the protocol easier to understand just from reading the header file.
Noralf.
[1] https://github.com/notro/gud/wiki
Noralf Trønnes (3): drm/uapi: Add USB connector type drm/probe-helper: Check epoch counter in output_poll_execute() drm: Add Generic USB Display driver
MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_probe_helper.c | 7 +- drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 722 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 620 ++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 148 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 472 ++++++++++++++++++ include/drm/gud.h | 356 ++++++++++++++ include/uapi/drm/drm_mode.h | 1 + 12 files changed, 2354 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
On Thursday, January 21st, 2021 at 10:59 AM, Thomas Zimmermann tzimmermann@suse.de wrote:
Well, I'd strongly ask to not call it "generic", because it isn't. We have other USB drivers and anyone can make a USB display with these protocols as well. That doesn't make them generic. A USB-standardized protocol would be generic. Maybe call it custom, or home-made.
Maybe rename it to "GUD USB Display driver"? :P
Den 21.01.2021 11.20, skrev Simon Ser:
On Thursday, January 21st, 2021 at 10:59 AM, Thomas Zimmermann tzimmermann@suse.de wrote:
Well, I'd strongly ask to not call it "generic", because it isn't. We have other USB drivers and anyone can make a USB display with these protocols as well. That doesn't make them generic. A USB-standardized protocol would be generic. Maybe call it custom, or home-made.
I agree that Generic probably isn't the best term to use here. Naming stuff is hard. Maybe: Open USB Display Protocol - oudp?
Maybe rename it to "GUD USB Display driver"? :P
Oh that's a recursive acronym :-) That would save me all the work of renaming. I think I'll go with that, thanks.
Noralf.
On Wed, Jan 20, 2021 at 06:00:30PM +0100, Noralf Trønnes wrote:
Hi,
A while back I had the idea to turn a Raspberry Pi Zero into a $5 USB to HDMI/SDTV/DSI/DPI display adapter.
The reason for calling it 'Generic' is so anyone can make a USB display/adapter against this driver, all that's needed is to add a USB vid:pid.
Unfortunately I've had some compounding health problems that have severally limited the time I can spend in front of a computer. For this reason I've decided to keep the gadget driver out-of-tree and focus on getting the host driver merged first.
See the wiki[1] for more information and images for the Raspberry Pi Zero/4.
One big change this time is that I've followed Peter Stuge's advice to not let DRM stuff leak into the USB protocol. This has made the protocol easier to understand just from reading the header file.
Welcome back! I'm happy to see this patch set back on track. I've now started putting together something that would work with GUD on Lilygo T-Display-GD32, but it blew up in my face immediately (I had CONFIG_DEBUG_SPINLOCK turned on):
[ 42.064362] usb 1-1.2: new full-speed USB device number 6 using mv-ehci [ 42.228048] usb 1-1.2: New USB device found, idVendor=1d50, idProduct=614d, bcdDevice= 1.00 [ 42.236812] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ 42.254298] usb 1-1.2: Product: LCD Display Interface [ 42.259771] usb 1-1.2: Manufacturer: Lubomir Rintel lkundrak@v3.sk [ 42.266395] usb 1-1.2: SerialNumber: FFFFFFFF3736030061294139 [ 42.284771] BUG: spinlock bad magic on CPU#0, kworker/0:0/5 [ 42.290394] 8<--- cut here --- [ 42.293415] Unable to handle kernel NULL pointer dereference at virtual address 0000031c [ 42.301417] pgd = e351f484 [ 42.304184] [0000031c] *pgd=00000000 [ 42.307826] Internal error: Oops: 15 [#1] PREEMPT SMP ARM [ 42.313250] Modules linked in: [ 42.316208] CPU: 0 PID: 5 Comm: kworker/0:0 Not tainted 5.11.0-rc4-00113-g8c3d05f1939a #1274 [ 42.324729] Hardware name: Marvell MMP3 [ 42.328540] Workqueue: usb_hub_wq hub_event [ 42.332759] PC is at spin_dump+0x5c/0x88 [ 42.336556] LR is at spin_dump+0x48/0x88 [ 42.340644] pc : [<c0c3da6c>] lr : [<c0c3da58>] psr: 200f0193 [ 42.346895] sp : c810b910 ip : 00000000 fp : c1107b40 [ 42.352039] r10: ffffc005 r9 : 00000000 r8 : 001fffff [ 42.357238] r7 : 00000000 r6 : 00000438 r5 : c14456f8 r4 : 00000014 [ 42.363837] r3 : 0000044c r2 : c1091500 r1 : eefb8540 r0 : 0000002f [ 42.370351] Flags: nzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment none [ 42.377456] Control: 10c5387d Table: 09f0806a DAC: 00000051 [ 42.383286] Process kworker/0:0 (pid: 5, stack limit = 0xda2575c6) [ 42.389392] Stack: (0xc810b910 to 0xc810c000) [ 42.393801] b900: 00000005 c14456f8 c14456f8 c058f244 [ 42.401916] b920: a00f0113 c018b1cc c14456f8 c058f244 a00f0113 c0c4e454 00000001 00000000 [ 42.410117] b940: c058f244 00000003 c9d1d800 ca01f000 7fffffff c058f244 200f0113 00000001 [ 42.418267] b960: 00000cc0 c810a000 00000dc0 00000000 c1053148 00000023 c9d1d814 c14456f8 [ 42.426399] b980: 00000000 00000000 00000003 00000000 00000000 fcee162a 00000cc0 c9d1d800 [ 42.434607] b9a0: ca01f000 00000000 c810a000 00000014 c9d1d814 c14452e0 ca01f42c c0669f84 [ 42.442749] b9c0: c0668d98 00000000 00000dc0 00000dc0 00000000 c126dd90 00000dc0 ca01f24c [ 42.450887] b9e0: 00000410 c14456f8 ca01f000 fcee162a 00000000 c9d1d800 00000000 00000000 [ 42.459087] ba00: 00000000 00000001 ca01f000 ca01fac8 00000002 c06965e0 00000000 00000000 [ 42.467220] ba20: 00000000 c03027b0 c8001600 00000001 c810a000 fcee162a 00000000 fcee162a [ 42.475373] ba40: a00f0113 c9d25880 c0694894 a00f0113 c8000000 00007820 00000000 fcee162a [ 42.483470] ba60: 00000002 00000000 ca01f000 c9ae7800 c810a000 00000001 c9d258c0 ca01fac8 [ 42.491685] ba80: 00000002 c0694a14 00000002 c0d68608 00000000 00000001 00000000 c0186460 [ 42.499882] baa0: c9d65878 c9d25940 c0d51198 00000001 00000000 36314752 34325241 00000000 [ 42.508038] bac0: 00000001 c9ae7820 00000000 34325258 c9ae7800 c810a000 00000022 c0c4e6b8 [ 42.516202] bae0: c98b1a00 fcee162a c9d65878 c9ae7820 c9d65878 c1446198 c9ae7800 c9d65800 [ 42.524347] bb00: 00000001 c0d68618 ffffffed c07eae44 c07ead58 c9ae7820 c18004e0 00000000 [ 42.532534] bb20: c18004e8 00000000 c1446198 c14ab308 00000022 c069f124 c9ae79cc 00000001 [ 42.540557] bb40: c9ae78dc 00000000 c14555b0 c9ae7820 c14ab308 c1446198 c069fba4 00000001 [ 42.548847] bb60: c1800494 00000000 c14555b0 c069f5bc 00000001 c069fb78 00000000 c810bbc4 [ 42.556874] bb80: c810a000 00000001 c1800494 00000000 c14555b0 c069cefc c14555b0 c8a964e4 [ 42.565179] bba0: c8e90d58 fcee162a c9d65934 c9ae7820 c810a000 c9ae7820 c9ae7864 c069f8f4 [ 42.573316] bbc0: c109aed0 c9ae7820 00000001 fcee162a c9ae7820 c14555c8 c9ae7820 c9d65878 [ 42.581503] bbe0: c1446248 c069e06c c9ae7820 00000000 c810a000 c069b76c 00000000 c0c49b8c [ 42.589664] bc00: c9d65800 c9d65800 c9079800 00000000 c9d65800 00000001 c810bc14 fcee162a [ 42.597796] bc20: 00000000 c9ae7800 c9bbb050 c14af400 c9d65800 c9bbb050 c10b6f28 00000001 [ 42.605979] bc40: c10b6f50 c07e9120 00000001 00000000 00000000 00000000 00001388 00000001 [ 42.614148] bc60: c810bc5c c98b1844 00000001 c9079800 c9d65878 c9d65804 c98b1844 c14557bc [ 42.622314] bc80: c14555c8 c07e77e0 c9bbb00c c9bbb000 c98b1840 00000001 00000001 00000001 [ 42.630470] bca0: 00000004 c9bbb04c c10b6eac c9d72dd0 00000000 c9d65800 00000001 c145615c [ 42.638629] bcc0: c9d65800 00000000 c145615c c14ab308 00000022 c07f4f34 c9d65878 c9d65878 [ 42.646777] bce0: c145615c c07ea6d4 c9d65878 c18004e0 00000000 c18004e8 00000000 c069f124 [ 42.654833] bd00: c9d65a24 00000001 c9d65934 00000000 c14552d0 c9d65878 c14ab308 c145615c [ 42.663083] bd20: c069fba4 00000001 c1800494 00000000 c14552d0 c069f5bc 00000001 c069fb78 [ 42.671276] bd40: 00000000 c810bd8c c810a000 00000001 c1800494 00000000 c14552d0 c069cefc [ 42.679443] bd60: c14552d0 c8a964e4 c8a98dd8 fcee162a c90af134 c9d65878 c810a000 c9d65878 [ 42.687596] bd80: c9d658bc c069f8f4 c109aed0 c9d65878 00000001 fcee162a c9d65878 c14555c8 [ 42.695758] bda0: c9d65878 c90af078 c1446248 c069e06c c9d65878 00000000 c810a000 c069b76c [ 42.703831] bdc0: 60030013 c810a000 c98b1a80 c0631c34 c1096e00 39383128 c900353a c810bdec [ 42.712078] bde0: ffff9b37 fcee162a c810be08 c9d65800 c9d65878 c98b1a80 c815d7c0 c90af000 [ 42.720211] be00: c90dcdbc 00000002 00000000 c07ddee8 00000003 00000000 00000002 c9d65800 [ 42.728403] be20: 00000002 00000000 c815d7c0 c90af000 c90dcdbc c07e0054 0000000a 00000002 [ 42.736572] be40: 00000000 00000000 000003e8 c8108e40 c81093a0 c90af000 c90dfc00 c90dccf6 [ 42.744732] be60: c90dcc6c c8162800 c90dcc70 c90dfc08 c90dcc00 c90dfeac c90dcc78 c90af0bc [ 42.752829] be80: c810a000 00000006 c90dfeac c14aea10 c8162820 c9079800 c90af000 c10b5eb8 [ 42.761064] bea0: c1801ff0 00000064 00010101 c12554f8 00000000 c0185b20 00000000 00000000 [ 42.769217] bec0: c0143a20 00000000 00000000 fcee162a c80b6100 c90dcdbc c80b6100 eefc3680 [ 42.777340] bee0: ff7c9800 c149f400 c810a000 00000000 c1307fd0 c0143adc 00000001 00000000 [ 42.785540] bf00: c0143a20 00000000 00000000 00000000 00000001 eefc3680 c1802040 c15025a4 [ 42.793706] bf20: 00000000 c10b60a8 00000000 fcee162a eefc3680 c80b6100 eefc3680 c80b6114 [ 42.801867] bf40: eefc36b8 c1304d00 00000008 c810a000 eefc3680 c0144444 c80b60c4 c149e9e7 [ 42.809958] bf60: c810a000 c80b6080 c80ba500 c810a000 00000000 c80fde74 c01443e4 c80b6100 [ 42.818193] bf80: c80b60c4 c014932c 00000000 c80ba500 c01491c4 00000000 00000000 00000000 [ 42.826341] bfa0: 00000000 00000000 00000000 c0100150 00000000 00000000 00000000 00000000 [ 42.834464] bfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 [ 42.842587] bfe0: 00000000 00000000 00000000 00000000 00000013 00000000 00000000 00000000 [ 42.850770] [<c0c3da6c>] (spin_dump) from [<c018b1cc>] (do_raw_spin_lock+0xc4/0x120) [ 42.858556] [<c018b1cc>] (do_raw_spin_lock) from [<c0c4e454>] (_raw_spin_lock_irqsave+0x48/0x54) [ 42.867394] [<c0c4e454>] (_raw_spin_lock_irqsave) from [<c058f244>] (ida_alloc_range+0xb0/0x3e4) [ 42.876092] [<c058f244>] (ida_alloc_range) from [<c0669f84>] (drm_connector_init+0x13c/0x484) [ 42.884667] [<c0669f84>] (drm_connector_init) from [<c06965e0>] (gud_connector_create+0x100/0x5f0) [ 42.893673] [<c06965e0>] (gud_connector_create) from [<c0694a14>] (gud_probe+0x884/0x930) [ 42.901860] [<c0694a14>] (gud_probe) from [<c07eae44>] (usb_probe_interface+0xec/0x2cc) [ 42.909857] [<c07eae44>] (usb_probe_interface) from [<c069f124>] (really_probe+0x108/0x528) [ 42.918208] [<c069f124>] (really_probe) from [<c069f5bc>] (driver_probe_device+0x78/0x1d8) [ 42.926468] [<c069f5bc>] (driver_probe_device) from [<c069cefc>] (bus_for_each_drv+0x84/0xc8) [ 42.935005] [<c069cefc>] (bus_for_each_drv) from [<c069f8f4>] (__device_attach+0xd0/0x1d0) [ 42.943226] [<c069f8f4>] (__device_attach) from [<c069e06c>] (bus_probe_device+0x88/0x90) [ 42.951424] [<c069e06c>] (bus_probe_device) from [<c069b76c>] (device_add+0x330/0x808) [ 42.959342] [<c069b76c>] (device_add) from [<c07e9120>] (usb_set_configuration+0x4b0/0x8b4) [ 42.967684] [<c07e9120>] (usb_set_configuration) from [<c07f4f34>] (usb_generic_driver_probe+0x50/0x8c) [ 42.977007] [<c07f4f34>] (usb_generic_driver_probe) from [<c07ea6d4>] (usb_probe_device+0x44/0xf8) [ 42.986016] [<c07ea6d4>] (usb_probe_device) from [<c069f124>] (really_probe+0x108/0x528) [ 42.994109] [<c069f124>] (really_probe) from [<c069f5bc>] (driver_probe_device+0x78/0x1d8) [ 43.002364] [<c069f5bc>] (driver_probe_device) from [<c069cefc>] (bus_for_each_drv+0x84/0xc8) [ 43.010874] [<c069cefc>] (bus_for_each_drv) from [<c069f8f4>] (__device_attach+0xd0/0x1d0) [ 43.019142] [<c069f8f4>] (__device_attach) from [<c069e06c>] (bus_probe_device+0x88/0x90) [ 43.027252] [<c069e06c>] (bus_probe_device) from [<c069b76c>] (device_add+0x330/0x808) [ 43.035225] [<c069b76c>] (device_add) from [<c07ddee8>] (usb_new_device+0x254/0x4ac) [ 43.042972] [<c07ddee8>] (usb_new_device) from [<c07e0054>] (hub_event+0xd7c/0x1998) [ 43.050707] [<c07e0054>] (hub_event) from [<c0143adc>] (process_one_work+0x2ac/0x7c8) [ 43.058526] [<c0143adc>] (process_one_work) from [<c0144444>] (worker_thread+0x60/0x568) [ 43.066597] [<c0144444>] (worker_thread) from [<c014932c>] (kthread+0x168/0x184) [ 43.074009] [<c014932c>] (kthread) from [<c0100150>] (ret_from_fork+0x14/0x24) [ 43.081238] Exception stack(0xc810bfb0 to 0xc810bff8) [ 43.086278] bfa0: 00000000 00000000 00000000 00000000 [ 43.094436] bfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 [ 43.102593] bfe0: 00000000 00000000 00000000 00000000 00000013 00000000 [ 43.109198] Code: e5952004 10843006 e595c008 03e00000 (15940308) [ 43.115208] ---[ end trace aa41dc7cdb58205e ]--- [ 43.119821] note: kworker/0:0[5] exited with preempt_count 1
No big deal though it seems: just that connector_ida for then newly added USB connector is not set up. This fixes the crash:
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 98b6ec45ef967..7631f76e7f345 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -94,6 +94,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = { { DRM_MODE_CONNECTOR_DPI, "DPI" }, { DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" }, { DRM_MODE_CONNECTOR_SPI, "SPI" }, + { DRM_MODE_CONNECTOR_USB, "USB" }, };
Hopefully I'll be able to follow up with a Tested-by: once I'll make the display side work on my MCU.
Take care Lubo
Noralf.
[1] https://github.com/notro/gud/wiki
Noralf Trønnes (3): drm/uapi: Add USB connector type drm/probe-helper: Check epoch counter in output_poll_execute() drm: Add Generic USB Display driver
MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_probe_helper.c | 7 +- drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 722 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 620 ++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 148 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 472 ++++++++++++++++++ include/drm/gud.h | 356 ++++++++++++++ include/uapi/drm/drm_mode.h | 1 + 12 files changed, 2354 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
-- 2.23.0
On Wed, Jan 20, 2021 at 06:00:30PM +0100, Noralf Trønnes wrote:
Hi,
A while back I had the idea to turn a Raspberry Pi Zero into a $5 USB to HDMI/SDTV/DSI/DPI display adapter.
The reason for calling it 'Generic' is so anyone can make a USB display/adapter against this driver, all that's needed is to add a USB vid:pid.
Unfortunately I've had some compounding health problems that have severally limited the time I can spend in front of a computer. For this reason I've decided to keep the gadget driver out-of-tree and focus on getting the host driver merged first.
See the wiki[1] for more information and images for the Raspberry Pi Zero/4.
One big change this time is that I've followed Peter Stuge's advice to not let DRM stuff leak into the USB protocol. This has made the protocol easier to understand just from reading the header file.
Noralf.
The patch set:
Tested-by: Lubomir Rintel lkundrak@v3.sk
Works like a charm with this board [1], though it didn't impress the girls as much as I hoped. Code here [2], picture here [3].
[1] https://www.banggood.com/LILYGO-TTGO-T-Display-GD32-RISC-V-32-bit-Core-Minim... [2] https://github.com/hackerspace/libopencm3-gf32v-examples/commit/7ef51b31b9 [3] https://people.freedesktop.org/~lkundrak/lilygo.jpeg
Had to apply a fix for the drm_connector_enum_list[] ommission I mentioned elsewhere, and that I've now noticed you've noted previously.
Take care Lubo
Noralf Trønnes (3): drm/uapi: Add USB connector type drm/probe-helper: Check epoch counter in output_poll_execute() drm: Add Generic USB Display driver
MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_probe_helper.c | 7 +- drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 722 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 620 ++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 148 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 472 ++++++++++++++++++ include/drm/gud.h | 356 ++++++++++++++ include/uapi/drm/drm_mode.h | 1 + 12 files changed, 2354 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
-- 2.23.0
Den 24.01.2021 19.38, skrev Lubomir Rintel:
On Wed, Jan 20, 2021 at 06:00:30PM +0100, Noralf Trønnes wrote:
Hi,
A while back I had the idea to turn a Raspberry Pi Zero into a $5 USB to HDMI/SDTV/DSI/DPI display adapter.
The reason for calling it 'Generic' is so anyone can make a USB display/adapter against this driver, all that's needed is to add a USB vid:pid.
Unfortunately I've had some compounding health problems that have severally limited the time I can spend in front of a computer. For this reason I've decided to keep the gadget driver out-of-tree and focus on getting the host driver merged first.
See the wiki[1] for more information and images for the Raspberry Pi Zero/4.
One big change this time is that I've followed Peter Stuge's advice to not let DRM stuff leak into the USB protocol. This has made the protocol easier to understand just from reading the header file.
Noralf.
The patch set:
Tested-by: Lubomir Rintel lkundrak@v3.sk
Works like a charm with this board [1], though it didn't impress the girls as much as I hoped. Code here [2], picture here [3].
I have wondered what color display resolution it is possible to drive over USB full speed. I can understand that your PoC wasn't that impressive since it doesn't use DMA to drive the SPI bus.
The new $4 Raspberry Pi Pico that came out this week looks interesting as a USB interface board for tiny panels. It can drive DPI panels directly, has 2 cores @133MHz, 264K SRAM and USB full speed. Maybe lz4 decompression is even possible. Another good thing is that the board will be around for a long time.
Thanks for testing, I have limited bandwith these days so I couldn't do a test on an MCU myself.
Noralf.
[1] https://www.banggood.com/LILYGO-TTGO-T-Display-GD32-RISC-V-32-bit-Core-Minim... [2] https://github.com/hackerspace/libopencm3-gf32v-examples/commit/7ef51b31b9 [3] https://people.freedesktop.org/~lkundrak/lilygo.jpeg
Had to apply a fix for the drm_connector_enum_list[] ommission I mentioned elsewhere, and that I've now noticed you've noted previously.
Take care Lubo
Noralf Trønnes (3): drm/uapi: Add USB connector type drm/probe-helper: Check epoch counter in output_poll_execute() drm: Add Generic USB Display driver
MAINTAINERS | 8 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/drm_probe_helper.c | 7 +- drivers/gpu/drm/gud/Kconfig | 14 + drivers/gpu/drm/gud/Makefile | 4 + drivers/gpu/drm/gud/gud_connector.c | 722 ++++++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_drv.c | 620 ++++++++++++++++++++++++ drivers/gpu/drm/gud/gud_internal.h | 148 ++++++ drivers/gpu/drm/gud/gud_pipe.c | 472 ++++++++++++++++++ include/drm/gud.h | 356 ++++++++++++++ include/uapi/drm/drm_mode.h | 1 + 12 files changed, 2354 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/gud/Kconfig create mode 100644 drivers/gpu/drm/gud/Makefile create mode 100644 drivers/gpu/drm/gud/gud_connector.c create mode 100644 drivers/gpu/drm/gud/gud_drv.c create mode 100644 drivers/gpu/drm/gud/gud_internal.h create mode 100644 drivers/gpu/drm/gud/gud_pipe.c create mode 100644 include/drm/gud.h
-- 2.23.0
Den 24.01.2021 21.51, skrev Noralf Trønnes:
Den 24.01.2021 19.38, skrev Lubomir Rintel:
On Wed, Jan 20, 2021 at 06:00:30PM +0100, Noralf Trønnes wrote:
Hi,
A while back I had the idea to turn a Raspberry Pi Zero into a $5 USB to HDMI/SDTV/DSI/DPI display adapter.
The reason for calling it 'Generic' is so anyone can make a USB display/adapter against this driver, all that's needed is to add a USB vid:pid.
Unfortunately I've had some compounding health problems that have severally limited the time I can spend in front of a computer. For this reason I've decided to keep the gadget driver out-of-tree and focus on getting the host driver merged first.
See the wiki[1] for more information and images for the Raspberry Pi Zero/4.
One big change this time is that I've followed Peter Stuge's advice to not let DRM stuff leak into the USB protocol. This has made the protocol easier to understand just from reading the header file.
Noralf.
The patch set:
Tested-by: Lubomir Rintel lkundrak@v3.sk
Works like a charm with this board [1], though it didn't impress the girls as much as I hoped. Code here [2], picture here [3].
I have wondered what color display resolution it is possible to drive over USB full speed. I can understand that your PoC wasn't that impressive since it doesn't use DMA to drive the SPI bus.
I have now done a Raspberry Pi Pico implementation and driving SPI using DMA was just marginally faster than letting the CPU fill the FIFO. Maybe I shouldn't be so suprised since the CPU has nothing else to do, but even so I didn't expect this. But then again I have very little experience with microcontrollers.
I have the same size display[1] as you 240x135 and my display was quite snappy (using X windows!), I even added lz4 decompression support. I haven't done much testing so I can't say how much the actual improvement is with the compression. The USB double buffering I was hoping for didn't pan out, the bulk endpoint can only do 64 byte packest (ISO is 512), so I ended up storing the packets and then push the frame in its entirety to the display. The Pico has 264kB of ram so I can afford to have a framebuffer and a decompression buffer for this tiny display. My target display is 320x240 and that means I can't use 2 buffers, so not sure how that goes.
[1] https://shop.pimoroni.com/products/pico-display-pack
Noralf.
The new $4 Raspberry Pi Pico that came out this week looks interesting as a USB interface board for tiny panels. It can drive DPI panels directly, has 2 cores @133MHz, 264K SRAM and USB full speed. Maybe lz4 decompression is even possible. Another good thing is that the board will be around for a long time.
Thanks for testing, I have limited bandwith these days so I couldn't do a test on an MCU myself.
Noralf.
[1] https://www.banggood.com/LILYGO-TTGO-T-Display-GD32-RISC-V-32-bit-Core-Minim... [2] https://github.com/hackerspace/libopencm3-gf32v-examples/commit/7ef51b31b9 [3] https://people.freedesktop.org/~lkundrak/lilygo.jpeg
Had to apply a fix for the drm_connector_enum_list[] ommission I mentioned elsewhere, and that I've now noticed you've noted previously.
Take care Lubo
dri-devel@lists.freedesktop.org