Message ID | 20220125202118.63362-2-andriy.shevchenko@linux.intel.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | fbtft: Unorphan the driver for maintenance | expand |
On Tue, Jan 25, 2022 at 10:21:14PM +0200, Andy Shevchenko wrote: > Let's maintain occasional fixes to the fbtft driver. > > Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > --- > MAINTAINERS | 4 +++- > 1 file changed, 3 insertions(+), 1 deletion(-) > > diff --git a/MAINTAINERS b/MAINTAINERS > index ea3e6c914384..16e614606ac1 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7372,9 +7372,11 @@ F: Documentation/fault-injection/ > F: lib/fault-inject.c > > FBTFT Framebuffer drivers > +M: Andy Shevchenko <andy@kernel.org> > L: dri-devel@lists.freedesktop.org > L: linux-fbdev@vger.kernel.org > -S: Orphan > +S: Maintained > +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-fbtft.git I'm ok with the files moving if the dri developers agree with it. It's up to them, not me. thanks, greg k-h
On Wed, Jan 26, 2022 at 9:31 AM Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote: > > On Tue, Jan 25, 2022 at 10:21:14PM +0200, Andy Shevchenko wrote: > > Let's maintain occasional fixes to the fbtft driver. > > > > Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > > --- > > MAINTAINERS | 4 +++- > > 1 file changed, 3 insertions(+), 1 deletion(-) > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index ea3e6c914384..16e614606ac1 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -7372,9 +7372,11 @@ F: Documentation/fault-injection/ > > F: lib/fault-inject.c > > > > FBTFT Framebuffer drivers > > +M: Andy Shevchenko <andy@kernel.org> > > L: dri-devel@lists.freedesktop.org > > L: linux-fbdev@vger.kernel.org > > -S: Orphan > > +S: Maintained > > +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-fbtft.git > > I'm ok with the files moving if the dri developers agree with it. It's > up to them, not me. On one hand I'm happy anytime someone volunteers to help out. On the other hand ... why does it have to be resurrecting fbdev? There's an entire community of people who really know graphics and display and spent considerable amount of effort on creating useful and documented helpers for pretty much anything you might ever want to do. And somehow we have to go back to typing out things the hard way, with full verbosity, for an uapi that distros are abandoning (e.g. even for sdl the direction is to run it on top of drm with a compat layer, afaiui fedora is completely ditching any userspace that still even uses /dev/fb/0). And yes I know there's still some gaps in drm, largely for display features which were really en vogue about 20 years ago. And we're happy to add that support, if someone who still has such hardware can put in the little bit of work needed ... I don't get this. -Daniel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On 1/26/22 11:31, Daniel Vetter wrote: > On Wed, Jan 26, 2022 at 9:31 AM Greg Kroah-Hartman > <gregkh@linuxfoundation.org> wrote: >> >> On Tue, Jan 25, 2022 at 10:21:14PM +0200, Andy Shevchenko wrote: >>> Let's maintain occasional fixes to the fbtft driver. >>> >>> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> >>> --- >>> MAINTAINERS | 4 +++- >>> 1 file changed, 3 insertions(+), 1 deletion(-) >>> >>> diff --git a/MAINTAINERS b/MAINTAINERS >>> index ea3e6c914384..16e614606ac1 100644 >>> --- a/MAINTAINERS >>> +++ b/MAINTAINERS >>> @@ -7372,9 +7372,11 @@ F: Documentation/fault-injection/ >>> F: lib/fault-inject.c >>> >>> FBTFT Framebuffer drivers >>> +M: Andy Shevchenko <andy@kernel.org> >>> L: dri-devel@lists.freedesktop.org >>> L: linux-fbdev@vger.kernel.org >>> -S: Orphan >>> +S: Maintained >>> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-fbtft.git >> >> I'm ok with the files moving if the dri developers agree with it. It's >> up to them, not me. > > On one hand I'm happy anytime someone volunteers to help out. > > On the other hand ... why does it have to be resurrecting fbdev? > There's an entire community of people who really know graphics and > display and spent considerable amount of effort on creating useful and > documented helpers for pretty much anything you might ever want to do. > And somehow we have to go back to typing out things the hard way, with > full verbosity, for an uapi that distros are abandoning (e.g. even for > sdl the direction is to run it on top of drm with a compat layer, > afaiui fedora is completely ditching any userspace that still even > uses /dev/fb/0). And yes I know there's still some gaps in drm, > largely for display features which were really en vogue about 20 years > ago. And we're happy to add that support, if someone who still has > such hardware can put in the little bit of work needed ... > > I don't get this. You are describing a transitioning over to DRM - which is Ok. But on that way there is no need to ignore, deny or even kill usage scenarios which are different compared to your usage scenarios (e.g. embedded devices, old platforms, slow devices, slow busses, no 3D hardware features, low-color devices, ...). Helge
On Wed, Jan 26, 2022 at 12:17:08PM +0100, Helge Deller wrote: > On 1/26/22 11:31, Daniel Vetter wrote: > > On Wed, Jan 26, 2022 at 9:31 AM Greg Kroah-Hartman > > <gregkh@linuxfoundation.org> wrote: > >> > >> On Tue, Jan 25, 2022 at 10:21:14PM +0200, Andy Shevchenko wrote: > >>> Let's maintain occasional fixes to the fbtft driver. > >>> > >>> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > >>> --- > >>> MAINTAINERS | 4 +++- > >>> 1 file changed, 3 insertions(+), 1 deletion(-) > >>> > >>> diff --git a/MAINTAINERS b/MAINTAINERS > >>> index ea3e6c914384..16e614606ac1 100644 > >>> --- a/MAINTAINERS > >>> +++ b/MAINTAINERS > >>> @@ -7372,9 +7372,11 @@ F: Documentation/fault-injection/ > >>> F: lib/fault-inject.c > >>> > >>> FBTFT Framebuffer drivers > >>> +M: Andy Shevchenko <andy@kernel.org> > >>> L: dri-devel@lists.freedesktop.org > >>> L: linux-fbdev@vger.kernel.org > >>> -S: Orphan > >>> +S: Maintained > >>> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-fbtft.git > >> > >> I'm ok with the files moving if the dri developers agree with it. It's > >> up to them, not me. > > > > On one hand I'm happy anytime someone volunteers to help out. > > > > On the other hand ... why does it have to be resurrecting fbdev? > > There's an entire community of people who really know graphics and > > display and spent considerable amount of effort on creating useful and > > documented helpers for pretty much anything you might ever want to do. > > And somehow we have to go back to typing out things the hard way, with > > full verbosity, for an uapi that distros are abandoning (e.g. even for > > sdl the direction is to run it on top of drm with a compat layer, > > afaiui fedora is completely ditching any userspace that still even > > uses /dev/fb/0). And yes I know there's still some gaps in drm, > > largely for display features which were really en vogue about 20 years > > ago. And we're happy to add that support, if someone who still has > > such hardware can put in the little bit of work needed ... > > > > I don't get this. > > You are describing a transitioning over to DRM - which is Ok. > But on that way there is no need to ignore, deny or even kill usage scenarios > which are different compared to your usage scenarios (e.g. embedded devices, > old platforms, slow devices, slow busses, no 3D hardware features, > low-color devices, ...). All of those should be handled by the drm layer, as Daniel keeps pointing out. If not, then the tinydrm layer needs to be enhanced to do so. Anyone have a pointer to hardware I can buy that is one of these fbtft drivers that I could do a port to drm to see just how much work is really needed here? thanks, greg k-h
On Wed, Jan 26, 2022 at 12:18 PM Helge Deller <deller@gmx.de> wrote: > > On 1/26/22 11:31, Daniel Vetter wrote: > > On Wed, Jan 26, 2022 at 9:31 AM Greg Kroah-Hartman > > <gregkh@linuxfoundation.org> wrote: > >> > >> On Tue, Jan 25, 2022 at 10:21:14PM +0200, Andy Shevchenko wrote: > >>> Let's maintain occasional fixes to the fbtft driver. > >>> > >>> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > >>> --- > >>> MAINTAINERS | 4 +++- > >>> 1 file changed, 3 insertions(+), 1 deletion(-) > >>> > >>> diff --git a/MAINTAINERS b/MAINTAINERS > >>> index ea3e6c914384..16e614606ac1 100644 > >>> --- a/MAINTAINERS > >>> +++ b/MAINTAINERS > >>> @@ -7372,9 +7372,11 @@ F: Documentation/fault-injection/ > >>> F: lib/fault-inject.c > >>> > >>> FBTFT Framebuffer drivers > >>> +M: Andy Shevchenko <andy@kernel.org> > >>> L: dri-devel@lists.freedesktop.org > >>> L: linux-fbdev@vger.kernel.org > >>> -S: Orphan > >>> +S: Maintained > >>> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-fbtft.git > >> > >> I'm ok with the files moving if the dri developers agree with it. It's > >> up to them, not me. > > > > On one hand I'm happy anytime someone volunteers to help out. > > > > On the other hand ... why does it have to be resurrecting fbdev? > > There's an entire community of people who really know graphics and > > display and spent considerable amount of effort on creating useful and > > documented helpers for pretty much anything you might ever want to do. > > And somehow we have to go back to typing out things the hard way, with > > full verbosity, for an uapi that distros are abandoning (e.g. even for > > sdl the direction is to run it on top of drm with a compat layer, > > afaiui fedora is completely ditching any userspace that still even > > uses /dev/fb/0). And yes I know there's still some gaps in drm, > > largely for display features which were really en vogue about 20 years > > ago. And we're happy to add that support, if someone who still has > > such hardware can put in the little bit of work needed ... > > > > I don't get this. > > You are describing a transitioning over to DRM - which is Ok. > But on that way there is no need to ignore, deny or even kill usage scenarios > which are different compared to your usage scenarios (e.g. embedded devices, > old platforms, slow devices, slow busses, no 3D hardware features, > low-color devices, ...). This patchset isn't about killing existing support. This is about adding new drivers to a subsystem where consensus the past 6 years or so was that it's closed for new drivers. -Daniel
Hi Am 26.01.22 um 12:17 schrieb Helge Deller: > On 1/26/22 11:31, Daniel Vetter wrote: >> On Wed, Jan 26, 2022 at 9:31 AM Greg Kroah-Hartman >> <gregkh@linuxfoundation.org> wrote: >>> >>> On Tue, Jan 25, 2022 at 10:21:14PM +0200, Andy Shevchenko wrote: >>>> Let's maintain occasional fixes to the fbtft driver. >>>> >>>> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> >>>> --- >>>> MAINTAINERS | 4 +++- >>>> 1 file changed, 3 insertions(+), 1 deletion(-) >>>> >>>> diff --git a/MAINTAINERS b/MAINTAINERS >>>> index ea3e6c914384..16e614606ac1 100644 >>>> --- a/MAINTAINERS >>>> +++ b/MAINTAINERS >>>> @@ -7372,9 +7372,11 @@ F: Documentation/fault-injection/ >>>> F: lib/fault-inject.c >>>> >>>> FBTFT Framebuffer drivers >>>> +M: Andy Shevchenko <andy@kernel.org> >>>> L: dri-devel@lists.freedesktop.org >>>> L: linux-fbdev@vger.kernel.org >>>> -S: Orphan >>>> +S: Maintained >>>> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-fbtft.git >>> >>> I'm ok with the files moving if the dri developers agree with it. It's >>> up to them, not me. >> >> On one hand I'm happy anytime someone volunteers to help out. >> >> On the other hand ... why does it have to be resurrecting fbdev? >> There's an entire community of people who really know graphics and >> display and spent considerable amount of effort on creating useful and >> documented helpers for pretty much anything you might ever want to do. >> And somehow we have to go back to typing out things the hard way, with >> full verbosity, for an uapi that distros are abandoning (e.g. even for >> sdl the direction is to run it on top of drm with a compat layer, >> afaiui fedora is completely ditching any userspace that still even >> uses /dev/fb/0). And yes I know there's still some gaps in drm, >> largely for display features which were really en vogue about 20 years >> ago. And we're happy to add that support, if someone who still has >> such hardware can put in the little bit of work needed ... >> >> I don't get this. > > You are describing a transitioning over to DRM - which is Ok. > But on that way there is no need to ignore, deny or even kill usage scenarios > which are different compared to your usage scenarios (e.g. embedded devices, > old platforms, slow devices, slow busses, no 3D hardware features, > low-color devices, ...). And none of those examples is out-ruled by DRM. In fact we do support devices that fall in those categories. This is last week's discussion all over again. Best regards Thomas > > Helge
On Wed, Jan 26, 2022 at 11:31:45AM +0100, Daniel Vetter wrote: > On Wed, Jan 26, 2022 at 9:31 AM Greg Kroah-Hartman > <gregkh@linuxfoundation.org> wrote: > > On Tue, Jan 25, 2022 at 10:21:14PM +0200, Andy Shevchenko wrote: ... > > I'm ok with the files moving if the dri developers agree with it. It's > > up to them, not me. > > On one hand I'm happy anytime someone volunteers to help out. > > On the other hand ... why does it have to be resurrecting fbdev? > There's an entire community of people who really know graphics and > display and spent considerable amount of effort on creating useful and > documented helpers for pretty much anything you might ever want to do. Why nobody has converted these drivers to be DRM based? For all these years no new conversion happens except couple, which I don't even have a hardware to see. But I have the hardware that is supported exclusively by fbtft driver. > And somehow we have to go back to typing out things the hard way, with > full verbosity, for an uapi that distros are abandoning (e.g. even for > sdl the direction is to run it on top of drm with a compat layer, > afaiui fedora is completely ditching any userspace that still even > uses /dev/fb/0). And yes I know there's still some gaps in drm, > largely for display features which were really en vogue about 20 years > ago. And we're happy to add that support, if someone who still has > such hardware can put in the little bit of work needed ... > > I don't get this. I don't get how Fedora is related here. It's not useful to bury the /dev/fbX out for the devices that the use of are black-and-white output on small embedded systems.
On Wed, Jan 26, 2022 at 12:17:08PM +0100, Helge Deller wrote: > On 1/26/22 11:31, Daniel Vetter wrote: > > On Wed, Jan 26, 2022 at 9:31 AM Greg Kroah-Hartman > > <gregkh@linuxfoundation.org> wrote: ... > > On the other hand ... why does it have to be resurrecting fbdev? > > There's an entire community of people who really know graphics and > > display and spent considerable amount of effort on creating useful and > > documented helpers for pretty much anything you might ever want to do. > > And somehow we have to go back to typing out things the hard way, with > > full verbosity, for an uapi that distros are abandoning (e.g. even for > > sdl the direction is to run it on top of drm with a compat layer, > > afaiui fedora is completely ditching any userspace that still even > > uses /dev/fb/0). And yes I know there's still some gaps in drm, > > largely for display features which were really en vogue about 20 years > > ago. And we're happy to add that support, if someone who still has > > such hardware can put in the little bit of work needed ... > > > > I don't get this. > > You are describing a transitioning over to DRM - which is Ok. > But on that way there is no need to ignore, deny or even kill usage scenarios > which are different compared to your usage scenarios (e.g. embedded devices, > old platforms, slow devices, slow busses, no 3D hardware features, > low-color devices, ...). Exactly, I am on the same side here.
On Wed, Jan 26, 2022 at 12:26:36PM +0100, Greg Kroah-Hartman wrote: > On Wed, Jan 26, 2022 at 12:17:08PM +0100, Helge Deller wrote: > > On 1/26/22 11:31, Daniel Vetter wrote: ... > > You are describing a transitioning over to DRM - which is Ok. > > But on that way there is no need to ignore, deny or even kill usage scenarios > > which are different compared to your usage scenarios (e.g. embedded devices, > > old platforms, slow devices, slow busses, no 3D hardware features, > > low-color devices, ...). > > All of those should be handled by the drm layer, as Daniel keeps > pointing out. If not, then the tinydrm layer needs to be enhanced to do > so. > > Anyone have a pointer to hardware I can buy that is one of these fbtft > drivers that I could do a port to drm to see just how much work is > really needed here? I have bought myself (for other purposes, I mean not to convert the driver(s)) SSD1306 based display (SPI), SSD1331 (SPI), HX88347d (parallel). Each of them costed less than $10 with delivery to EU (nowadays maybe a bit more expensive). I believe it's very easy to find the links on AliExpress.
On Wed, Jan 26, 2022 at 12:31:40PM +0100, Thomas Zimmermann wrote: > Am 26.01.22 um 12:17 schrieb Helge Deller: ... > And none of those examples is out-ruled by DRM. In fact we do support > devices that fall in those categories. > > This is last week's discussion all over again. Fine, write a driver or accept existing solution. While there is no other solution, let's go with the existing.
On Wed, Jan 26, 2022 at 12:27:19PM +0100, Daniel Vetter wrote: > On Wed, Jan 26, 2022 at 12:18 PM Helge Deller <deller@gmx.de> wrote: > > On 1/26/22 11:31, Daniel Vetter wrote: ... > > You are describing a transitioning over to DRM - which is Ok. > > But on that way there is no need to ignore, deny or even kill usage scenarios > > which are different compared to your usage scenarios (e.g. embedded devices, > > old platforms, slow devices, slow busses, no 3D hardware features, > > low-color devices, ...). > > This patchset isn't about killing existing support. > > This is about adding new drivers to a subsystem where consensus the > past 6 years or so was that it's closed for new drivers. You mean fbdev?
Hi, On Wed, 26 Jan 2022 at 13:08, Andy Shevchenko <andriy.shevchenko@linux.intel.com> wrote: > It's not useful to bury the /dev/fbX out for the devices that > the use of are black-and-white output on small embedded systems. It's not useful to decide that such systems should only be supported by a subsystem which has been deprecated for a long time and which has little userspace support. It's also not useful to spend time working on that subsystem, rather than the one everyone has agreed on for the last 15 years, is supported by userspace, is expressive, and has a kernel subsystem which isn't a forest of CVEs and broken locking. Cheers, Daniel
On 1/26/22 14:12, Andy Shevchenko wrote: > On Wed, Jan 26, 2022 at 12:26:36PM +0100, Greg Kroah-Hartman wrote: >> On Wed, Jan 26, 2022 at 12:17:08PM +0100, Helge Deller wrote: >>> On 1/26/22 11:31, Daniel Vetter wrote: > > ... > >>> You are describing a transitioning over to DRM - which is Ok. >>> But on that way there is no need to ignore, deny or even kill usage scenarios >>> which are different compared to your usage scenarios (e.g. embedded devices, >>> old platforms, slow devices, slow busses, no 3D hardware features, >>> low-color devices, ...). >> >> All of those should be handled by the drm layer, as Daniel keeps >> pointing out. If not, then the tinydrm layer needs to be enhanced to do >> so. >> >> Anyone have a pointer to hardware I can buy that is one of these fbtft >> drivers that I could do a port to drm to see just how much work is >> really needed here? > > I have bought myself (for other purposes, I mean not to convert the driver(s)) > SSD1306 based display (SPI), SSD1331 (SPI), HX88347d (parallel). > I've just bought a SSD1306 (I2C) based one and will attempt to write a DRM driver using drivers/staging/fbtft/fb_ssd1306.c as a reference. I didn't find one with a SPI interface but we can later add a transport for that if I succeed. Best regards,
On Wed, Jan 26, 2022 at 02:46:08PM +0100, Javier Martinez Canillas wrote: > On 1/26/22 14:12, Andy Shevchenko wrote: > > On Wed, Jan 26, 2022 at 12:26:36PM +0100, Greg Kroah-Hartman wrote: > >> On Wed, Jan 26, 2022 at 12:17:08PM +0100, Helge Deller wrote: > >>> On 1/26/22 11:31, Daniel Vetter wrote: > > > > ... > > > >>> You are describing a transitioning over to DRM - which is Ok. > >>> But on that way there is no need to ignore, deny or even kill usage scenarios > >>> which are different compared to your usage scenarios (e.g. embedded devices, > >>> old platforms, slow devices, slow busses, no 3D hardware features, > >>> low-color devices, ...). > >> > >> All of those should be handled by the drm layer, as Daniel keeps > >> pointing out. If not, then the tinydrm layer needs to be enhanced to do > >> so. > >> > >> Anyone have a pointer to hardware I can buy that is one of these fbtft > >> drivers that I could do a port to drm to see just how much work is > >> really needed here? > > > > I have bought myself (for other purposes, I mean not to convert the driver(s)) > > SSD1306 based display (SPI), SSD1331 (SPI), HX88347d (parallel). > > > > I've just bought a SSD1306 (I2C) based one and will attempt to write a DRM > driver using drivers/staging/fbtft/fb_ssd1306.c as a reference. You should take ssd1307fb.c instead. And basically create a MIPI based driver for I2C. Then we won't go same road again for other similar devices. > I didn't find one with a SPI interface but we can later add a transport for > that if I succeed.
On Wed, Jan 26, 2022 at 04:08:32PM +0200, Andy Shevchenko wrote: > On Wed, Jan 26, 2022 at 02:46:08PM +0100, Javier Martinez Canillas wrote: > > On 1/26/22 14:12, Andy Shevchenko wrote: ... > > I've just bought a SSD1306 (I2C) based one and will attempt to write a DRM > > driver using drivers/staging/fbtft/fb_ssd1306.c as a reference. > > You should take ssd1307fb.c instead. And basically create a MIPI based driver > for I2C. Then we won't go same road again for other similar devices. For the record it supports your device: static const struct i2c_device_id ssd1307fb_i2c_id[] = { { "ssd1305fb", 0 }, { "ssd1306fb", 0 }, { "ssd1307fb", 0 }, { "ssd1309fb", 0 },
On 1/26/22 15:10, Andy Shevchenko wrote: > On Wed, Jan 26, 2022 at 04:08:32PM +0200, Andy Shevchenko wrote: >> On Wed, Jan 26, 2022 at 02:46:08PM +0100, Javier Martinez Canillas wrote: >>> On 1/26/22 14:12, Andy Shevchenko wrote: > > ... > >>> I've just bought a SSD1306 (I2C) based one and will attempt to write a DRM >>> driver using drivers/staging/fbtft/fb_ssd1306.c as a reference. >> >> You should take ssd1307fb.c instead. And basically create a MIPI based driver >> for I2C. Then we won't go same road again for other similar devices. > > For the record it supports your device: > > static const struct i2c_device_id ssd1307fb_i2c_id[] = { > { "ssd1305fb", 0 }, > { "ssd1306fb", 0 }, > { "ssd1307fb", 0 }, > { "ssd1309fb", 0 }, > > Thanks a lot for the pointer. I was only looking at drivers/staging and didn't check drivers/video. I'll try to convert that one then once I get the display. Best regards,
On Wed, 26 Jan 2022, Andy Shevchenko <andriy.shevchenko@linux.intel.com> wrote:
> And basically create a MIPI based driver for I2C.
What does that even mean?
BR,
Jani.
On 1/26/22 15:15, Javier Martinez Canillas wrote: > On 1/26/22 15:10, Andy Shevchenko wrote: >> On Wed, Jan 26, 2022 at 04:08:32PM +0200, Andy Shevchenko wrote: >>> On Wed, Jan 26, 2022 at 02:46:08PM +0100, Javier Martinez Canillas wrote: >>>> On 1/26/22 14:12, Andy Shevchenko wrote: >> >> ... >> >>>> I've just bought a SSD1306 (I2C) based one and will attempt to write a DRM >>>> driver using drivers/staging/fbtft/fb_ssd1306.c as a reference. >>> >>> You should take ssd1307fb.c instead. And basically create a MIPI based driver >>> for I2C. Then we won't go same road again for other similar devices. >> >> For the record it supports your device: >> >> static const struct i2c_device_id ssd1307fb_i2c_id[] = { >> { "ssd1305fb", 0 }, >> { "ssd1306fb", 0 }, >> { "ssd1307fb", 0 }, >> { "ssd1309fb", 0 }, >> >> > > Thanks a lot for the pointer. I was only looking at drivers/staging > and didn't check drivers/video. I'll try to convert that one then > once I get the display. > I got some time this weekend and was able to port the ssd1306 fbdev driver to DRM [0]. I'm not that familiar with the simple display pipe helpers, so there may be some wrong things there. But it does work and all the fbtests from https://git.kernel.org/pub/scm/linux/kernel/git/geert/fbtest.git pass. There are some hacks in the driver though. For example it exposes an XRGB8888 format even thought the OLED display is monochromatic and has 1 bit per pixel. The driver then goes and converts the XRGB8888 pixels first to grayscale and then to reverse mono. I took that idea from the repaper driver but that gives us the multiple copies that Geert was complaining about. Another hack is that I am just hardcoding the {width, height}_mm, but I don't know what DPI could be used for these panels nor how I could calculate the mm. Best regards, Javier [0]: From 5ec4b468b66022d4c48ae6bec8a68926a01a6785 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas <javierm@redhat.com> Date: Sun, 30 Jan 2022 12:16:34 +0100 Subject: [RFC] drm/tiny: Add driver for Solomon SSD130X OLED displays Add a DRM driver for SSD1305, SSD1306, SSD1307 and SSD1309 Solomon OLED controllers that can be programmed via an I2C interface. This is a port of the ssd1307fb driver that already supports these devices. A Device Tree binding is not added because the DRM driver is compatible with the existing binding for the ssd1307fb driver. Signed-off-by: Javier Martinez Canillas <javierm@redhat.com> --- MAINTAINERS | 7 + drivers/gpu/drm/tiny/Kconfig | 12 + drivers/gpu/drm/tiny/Makefile | 1 + drivers/gpu/drm/tiny/ssd130x.c | 944 +++++++++++++++++++++++++++++++++ 4 files changed, 964 insertions(+) create mode 100644 drivers/gpu/drm/tiny/ssd130x.c diff --git a/MAINTAINERS b/MAINTAINERS index d03ad8da1f36..87334676ce07 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6102,6 +6102,13 @@ T: git git://anongit.freedesktop.org/drm/drm-misc F: Documentation/devicetree/bindings/display/repaper.txt F: drivers/gpu/drm/tiny/repaper.c +DRM DRIVER FOR SOLOMON SSD130X DISPLAYS +M: Javier Martinez Canillas <javierm@redhat.com> +S: Maintained +T: git git://anongit.freedesktop.org/drm/drm-misc +F: Documentation/devicetree/bindings/display/solomon,ssd1307fb.yaml +F: drivers/gpu/drm/tiny/ssd130x.c + DRM DRIVER FOR QEMU'S CIRRUS DEVICE M: Dave Airlie <airlied@redhat.com> M: Gerd Hoffmann <kraxel@redhat.com> diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index 712e0004e96e..358ceb7354f5 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -157,6 +157,18 @@ config TINYDRM_REPAPER If M is selected the module will be called repaper. +config TINYDRM_SSD130X + tristate "DRM support for Solomon SSD130X OLED displays" + depends on DRM && OF && I2C + select DRM_KMS_HELPER + select DRM_GEM_SHMEM_HELPER + select BACKLIGHT_CLASS_DEVICE + help + DRM driver for the SSD1305, SSD1306, SSD1307 and SSD1309 Solomon + OLED controllers that can be programmed via an I2C interface. + + If M is selected the module will be called ssd130x. + config TINYDRM_ST7586 tristate "DRM support for Sitronix ST7586 display panels" depends on DRM && SPI diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile index 5d5505d40e7b..93a1d70155f0 100644 --- a/drivers/gpu/drm/tiny/Makefile +++ b/drivers/gpu/drm/tiny/Makefile @@ -12,5 +12,6 @@ obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o +obj-$(CONFIG_TINYDRM_SSD130X) += ssd130x.o obj-$(CONFIG_TINYDRM_ST7586) += st7586.o obj-$(CONFIG_TINYDRM_ST7735R) += st7735r.o diff --git a/drivers/gpu/drm/tiny/ssd130x.c b/drivers/gpu/drm/tiny/ssd130x.c new file mode 100644 index 000000000000..88d88caeb37d --- /dev/null +++ b/drivers/gpu/drm/tiny/ssd130x.c @@ -0,0 +1,944 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * DRM driver for Solomon SSD130X OLED displays + * + * Copyright 2022 Red Hat Inc. + * + * Based on drivers/video/fbdev/ssd1307fb.c + * Copyright 2012 Free Electrons + * + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/pwm.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_format_helper.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modes.h> +#include <drm/drm_rect.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#define DRIVER_NAME "ssd130x" +#define DRIVER_DESC "DRM driver for Solomon SSD130X OLED displays" +#define DRIVER_DATE "20220130" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +#define SSD130X_DATA 0x40 +#define SSD130X_COMMAND 0x80 + +#define SSD130X_SET_ADDRESS_MODE 0x20 +#define SSD130X_SET_ADDRESS_MODE_HORIZONTAL (0x00) +#define SSD130X_SET_ADDRESS_MODE_VERTICAL (0x01) +#define SSD130X_SET_ADDRESS_MODE_PAGE (0x02) +#define SSD130X_SET_COL_RANGE 0x21 +#define SSD130X_SET_PAGE_RANGE 0x22 +#define SSD130X_CONTRAST 0x81 +#define SSD130X_SET_LOOKUP_TABLE 0x91 +#define SSD130X_CHARGE_PUMP 0x8d +#define SSD130X_SEG_REMAP_ON 0xa1 +#define SSD130X_DISPLAY_OFF 0xae +#define SSD130X_SET_MULTIPLEX_RATIO 0xa8 +#define SSD130X_DISPLAY_ON 0xaf +#define SSD130X_START_PAGE_ADDRESS 0xb0 +#define SSD130X_SET_DISPLAY_OFFSET 0xd3 +#define SSD130X_SET_CLOCK_FREQ 0xd5 +#define SSD130X_SET_AREA_COLOR_MODE 0xd8 +#define SSD130X_SET_PRECHARGE_PERIOD 0xd9 +#define SSD130X_SET_COM_PINS_CONFIG 0xda +#define SSD130X_SET_VCOMH 0xdb + +#define MAX_CONTRAST 255 + +struct ssd130x_deviceinfo { + u32 default_vcomh; + u32 default_dclk_div; + u32 default_dclk_frq; + int need_pwm; + int need_chargepump; +}; + +struct ssd130x_device { + struct drm_device drm; + struct drm_simple_display_pipe pipe; + struct drm_display_mode mode; + struct drm_connector connector; + struct i2c_client *client; + + const struct ssd130x_deviceinfo *device_info; + + unsigned area_color_enable : 1; + unsigned com_invdir : 1; + unsigned com_lrremap : 1; + unsigned com_seq : 1; + unsigned lookup_table_set : 1; + unsigned low_power : 1; + unsigned seg_remap : 1; + u32 com_offset; + u32 contrast; + u32 dclk_div; + u32 dclk_frq; + u32 height; + u8 lookup_table[4]; + u32 page_offset; + u32 col_offset; + u32 prechargep1; + u32 prechargep2; + + struct backlight_device *bl_dev; + struct pwm_device *pwm; + struct gpio_desc *reset; + struct regulator *vbat_reg; + u32 vcomh; + u32 width; + /* Cached address ranges */ + u8 col_start; + u8 col_end; + u8 page_start; + u8 page_end; +}; + +struct ssd130x_array { + u8 type; + u8 data[]; +}; + +static inline struct ssd130x_device *drm_to_ssd130x(struct drm_device *drm) +{ + return container_of(drm, struct ssd130x_device, drm); +} + +static struct ssd130x_array *ssd130x_alloc_array(u32 len, u8 type) +{ + struct ssd130x_array *array; + + array = kzalloc(sizeof(struct ssd130x_array) + len, GFP_KERNEL); + if (!array) + return NULL; + + array->type = type; + + return array; +} + +static int ssd130x_write_array(struct i2c_client *client, + struct ssd130x_array *array, u32 len) +{ + int ret; + + len += sizeof(struct ssd130x_array); + + ret = i2c_master_send(client, (u8 *)array, len); + if (ret != len) { + dev_err(&client->dev, "Couldn't send I2C command.\n"); + return ret; + } + + return 0; +} + +static inline int ssd130x_write_cmd(struct i2c_client *client, u8 cmd) +{ + struct ssd130x_array *array; + int ret; + + array = ssd130x_alloc_array(1, SSD130X_COMMAND); + if (!array) + return -ENOMEM; + + array->data[0] = cmd; + + ret = ssd130x_write_array(client, array, 1); + kfree(array); + + return ret; +} + +static int ssd130x_set_col_range(struct ssd130x_device *ssd130x, + u8 col_start, u8 cols) +{ + u8 col_end = col_start + cols - 1; + int ret; + + if (col_start == ssd130x->col_start && col_end == ssd130x->col_end) + return 0; + + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_COL_RANGE); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, col_start); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, col_end); + if (ret < 0) + return ret; + + ssd130x->col_start = col_start; + ssd130x->col_end = col_end; + return 0; +} + +static int ssd130x_set_page_range(struct ssd130x_device *ssd130x, + u8 page_start, u8 pages) +{ + u8 page_end = page_start + pages - 1; + int ret; + + if (page_start == ssd130x->page_start && page_end == ssd130x->page_end) + return 0; + + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_PAGE_RANGE); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, page_start); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, page_end); + if (ret < 0) + return ret; + + ssd130x->page_start = page_start; + ssd130x->page_end = page_end; + return 0; +} + +static int ssd130x_update_display(struct ssd130x_device *ssd130x, u8 *buf, + u32 width, u32 height) +{ + struct ssd130x_array *array; + unsigned int line_length = DIV_ROUND_UP(width, 8); + unsigned int pages = DIV_ROUND_UP(height, 8); + u32 array_idx = 0; + int ret, i, j, k; + + array = ssd130x_alloc_array(width * pages, SSD130X_DATA); + if (!array) + return -ENOMEM; + + /* + * The screen is divided in pages, each having a height of 8 + * pixels, and the width of the screen. When sending a byte of + * data to the controller, it gives the 8 bits for the current + * column. I.e, the first byte are the 8 bits of the first + * column, then the 8 bits for the second column, etc. + * + * + * Representation of the screen, assuming it is 5 bits + * wide. Each letter-number combination is a bit that controls + * one pixel. + * + * A0 A1 A2 A3 A4 + * B0 B1 B2 B3 B4 + * C0 C1 C2 C3 C4 + * D0 D1 D2 D3 D4 + * E0 E1 E2 E3 E4 + * F0 F1 F2 F3 F4 + * G0 G1 G2 G3 G4 + * H0 H1 H2 H3 H4 + * + * If you want to update this screen, you need to send 5 bytes: + * (1) A0 B0 C0 D0 E0 F0 G0 H0 + * (2) A1 B1 C1 D1 E1 F1 G1 H1 + * (3) A2 B2 C2 D2 E2 F2 G2 H2 + * (4) A3 B3 C3 D3 E3 F3 G3 H3 + * (5) A4 B4 C4 D4 E4 F4 G4 H4 + */ + + ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset, width); + if (ret < 0) + goto out_free; + + ret = ssd130x_set_page_range(ssd130x, ssd130x->page_offset / 8, pages); + if (ret < 0) + goto out_free; + + for (i = 0; i < pages; i++) { + int m = 8; + + /* Last page may be partial */ + if (8 * (i + 1) > ssd130x->height) + m = ssd130x->height % 8; + for (j = 0; j < width; j++) { + u8 data = 0; + + for (k = 0; k < m; k++) { + u8 byte = buf[(8 * i + k) * line_length + + j / 8]; + u8 bit = (byte >> (j % 8)) & 1; + + data |= bit << k; + } + array->data[array_idx++] = data; + } + } + + ret = ssd130x_write_array(ssd130x->client, array, width * pages); + +out_free: + kfree(array); + return ret; +} + +static void ssd130x_gray8_to_mono_reversed(u8 *buf, u32 width, u32 height) +{ + u8 *gray8 = buf, *mono = buf; + int y, xb, i; + + for (y = 0; y < height; y++) + for (xb = 0; xb < width / 8; xb++) { + u8 byte = 0x00; + + for (i = 0; i < 8; i++) { + int x = xb * 8 + i; + + byte >>= 1; + if (gray8[y * width + x] >> 7) + byte |= BIT(7); + } + *mono++ = byte; + } +} + +static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct dma_buf_map *map, + struct drm_rect *rect) +{ + struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); + void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */ + int idx, ret = 0; + u8 *buf = NULL; + + if (!drm_dev_enter(fb->dev, &idx)) + return -ENODEV; + + buf = kmalloc_array(fb->width, fb->height, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto out_exit; + } + + drm_fb_xrgb8888_to_gray8(buf, 0, vmap, fb, rect); + + ssd130x_gray8_to_mono_reversed(buf, fb->width, fb->height); + + ssd130x_update_display(ssd130x, buf, fb->width, fb->height); + + kfree(buf); +out_exit: + drm_dev_exit(idx); + + return ret; +} + +static void ssd130x_display_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) +{ + struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev); + int idx; + + if (!drm_dev_enter(pipe->crtc.dev, &idx)) + return; + + backlight_enable(ssd130x->bl_dev); + + ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_ON); +} + +static void ssd130x_display_pipe_disable(struct drm_simple_display_pipe *pipe) +{ + struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev); + + ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_OFF); + + backlight_disable(ssd130x->bl_dev); +} + +static void ssd130x_display_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_plane_state) +{ + struct drm_plane_state *state = pipe->plane.state; + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state); + struct drm_rect rect; + + if (!pipe->crtc.state->active) + return; + + if (drm_atomic_helper_damage_merged(old_plane_state, state, &rect)) + ssd130x_fb_blit_rect(state->fb, &shadow_plane_state->data[0], &rect); +} + +static const struct drm_simple_display_pipe_funcs ssd130x_pipe_funcs = { + .enable = ssd130x_display_pipe_enable, + .disable = ssd130x_display_pipe_disable, + .update = ssd130x_display_pipe_update, + DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS, +}; + +static int ssd130x_connector_get_modes(struct drm_connector *connector) +{ + struct ssd130x_device *ssd130x = drm_to_ssd130x(connector->dev); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &ssd130x->mode); + if (!mode) { + DRM_ERROR("Failed to duplicate mode\n"); + return 0; + } + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + connector->display_info.bpc = 32; + + drm_mode_set_name(mode); + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_connector_helper_funcs ssd130x_connector_helper_funcs = { + .get_modes = ssd130x_connector_get_modes, +}; + +static const struct drm_connector_funcs ssd130x_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_mode_config_funcs ssd130x_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 uint32_t ssd130x_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +DEFINE_DRM_GEM_FOPS(ssd130x_fops); + +static const struct drm_driver ssd130x_drm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &ssd130x_fops, +}; + +static int ssd130x_pwm_enable(struct ssd130x_device *ssd130x) +{ + struct pwm_state pwmstate; + + ssd130x->pwm = pwm_get(&ssd130x->client->dev, NULL); + if (IS_ERR(ssd130x->pwm)) { + dev_err(&ssd130x->client->dev, "Could not get PWM from device tree!\n"); + return PTR_ERR(ssd130x->pwm); + } + + pwm_init_state(ssd130x->pwm, &pwmstate); + pwm_set_relative_duty_cycle(&pwmstate, 50, 100); + pwm_apply_state(ssd130x->pwm, &pwmstate); + + /* Enable the PWM */ + pwm_enable(ssd130x->pwm); + + dev_dbg(&ssd130x->client->dev, "Using PWM%d with a %lluns period.\n", + ssd130x->pwm->pwm, pwm_get_period(ssd130x->pwm)); + + return 0; +} + +static int ssd130x_init(struct ssd130x_device *ssd130x) +{ + u32 precharge, dclk, com_invdir, compins; + int ret; + + /* Set initial contrast */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_CONTRAST); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->contrast); + if (ret < 0) + return ret; + + /* Set segment re-map */ + if (ssd130x->seg_remap) { + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SEG_REMAP_ON); + if (ret < 0) + return ret; + } + + /* Set COM direction */ + com_invdir = 0xc0 | ssd130x->com_invdir << 3; + ret = ssd130x_write_cmd(ssd130x->client, com_invdir); + if (ret < 0) + return ret; + + /* Set multiplex ratio value */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_MULTIPLEX_RATIO); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->height - 1); + if (ret < 0) + return ret; + + /* set display offset value */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_DISPLAY_OFFSET); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->com_offset); + if (ret < 0) + return ret; + + /* Set clock frequency */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_CLOCK_FREQ); + if (ret < 0) + return ret; + + dclk = ((ssd130x->dclk_div - 1) & 0xf) | (ssd130x->dclk_frq & 0xf) << 4; + ret = ssd130x_write_cmd(ssd130x->client, dclk); + if (ret < 0) + return ret; + + /* Set Area Color Mode ON/OFF & Low Power Display Mode */ + if (ssd130x->area_color_enable || ssd130x->low_power) { + u32 mode; + + ret = ssd130x_write_cmd(ssd130x->client, + SSD130X_SET_AREA_COLOR_MODE); + if (ret < 0) + return ret; + + mode = (ssd130x->area_color_enable ? 0x30 : 0) | + (ssd130x->low_power ? 5 : 0); + ret = ssd130x_write_cmd(ssd130x->client, mode); + if (ret < 0) + return ret; + } + + /* Set precharge period in number of ticks from the internal clock */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_PRECHARGE_PERIOD); + if (ret < 0) + return ret; + + precharge = (ssd130x->prechargep1 & 0xf) | (ssd130x->prechargep2 & 0xf) << 4; + ret = ssd130x_write_cmd(ssd130x->client, precharge); + if (ret < 0) + return ret; + + /* Set COM pins configuration */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_COM_PINS_CONFIG); + if (ret < 0) + return ret; + + compins = 0x02 | !ssd130x->com_seq << 4 | ssd130x->com_lrremap << 5; + ret = ssd130x_write_cmd(ssd130x->client, compins); + if (ret < 0) + return ret; + + /* Set VCOMH */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_VCOMH); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->vcomh); + if (ret < 0) + return ret; + + /* Turn on the DC-DC Charge Pump */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_CHARGE_PUMP); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, + BIT(4) | (ssd130x->device_info->need_chargepump ? BIT(2) : 0)); + if (ret < 0) + return ret; + + /* Set lookup table */ + if (ssd130x->lookup_table_set) { + int i; + + ret = ssd130x_write_cmd(ssd130x->client, + SSD130X_SET_LOOKUP_TABLE); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ssd130x->lookup_table); ++i) { + u8 val = ssd130x->lookup_table[i]; + + if (val < 31 || val > 63) + dev_warn(&ssd130x->client->dev, + "lookup table index %d value out of range 31 <= %d <= 63\n", + i, val); + ret = ssd130x_write_cmd(ssd130x->client, val); + if (ret < 0) + return ret; + } + } + + /* Switch to horizontal addressing mode */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_ADDRESS_MODE); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, + SSD130X_SET_ADDRESS_MODE_HORIZONTAL); + if (ret < 0) + return ret; + + /* Turn on the display */ + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_ON); + if (ret < 0) + return ret; + + return 0; +} + +static int ssd130x_update_bl(struct backlight_device *bdev) +{ + struct ssd130x_device *ssd130x = bl_get_data(bdev); + int brightness = bdev->props.brightness; + int ret; + + ssd130x->contrast = brightness; + + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_CONTRAST); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->contrast); + if (ret < 0) + return ret; + + return 0; +} + +static int ssd130x_get_brightness(struct backlight_device *bdev) +{ + struct ssd130x_device *ssd130x = bl_get_data(bdev); + + return ssd130x->contrast; +} + +static const struct backlight_ops ssd130xfb_bl_ops = { + .update_status = ssd130x_update_bl, + .get_brightness = ssd130x_get_brightness, +}; + +static void ssd130x_reset(struct ssd130x_device *ssd130x) +{ + /* Reset the screen */ + gpiod_set_value_cansleep(ssd130x->reset, 1); + udelay(4); + gpiod_set_value_cansleep(ssd130x->reset, 0); + udelay(4); +} + +static void ssd130x_parse_properties(struct ssd130x_device *ssd130x) +{ + struct device *dev = &ssd130x->client->dev; + + if (device_property_read_u32(dev, "solomon,width", &ssd130x->width)) + ssd130x->width = 96; + + if (device_property_read_u32(dev, "solomon,height", &ssd130x->height)) + ssd130x->height = 16; + + if (device_property_read_u32(dev, "solomon,page-offset", &ssd130x->page_offset)) + ssd130x->page_offset = 1; + + if (device_property_read_u32(dev, "solomon,col-offset", &ssd130x->col_offset)) + ssd130x->col_offset = 0; + + if (device_property_read_u32(dev, "solomon,com-offset", &ssd130x->com_offset)) + ssd130x->com_offset = 0; + + if (device_property_read_u32(dev, "solomon,prechargep1", &ssd130x->prechargep1)) + ssd130x->prechargep1 = 2; + + if (device_property_read_u32(dev, "solomon,prechargep2", &ssd130x->prechargep2)) + ssd130x->prechargep2 = 2; + + if (!device_property_read_u8_array(dev, "solomon,lookup-table", + ssd130x->lookup_table, + ARRAY_SIZE(ssd130x->lookup_table))) + ssd130x->lookup_table_set = 1; + + ssd130x->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap"); + ssd130x->com_seq = device_property_read_bool(dev, "solomon,com-seq"); + ssd130x->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap"); + ssd130x->com_invdir = device_property_read_bool(dev, "solomon,com-invdir"); + ssd130x->area_color_enable = + device_property_read_bool(dev, "solomon,area-color-enable"); + ssd130x->low_power = device_property_read_bool(dev, "solomon,low-power"); + + ssd130x->contrast = 127; + ssd130x->vcomh = ssd130x->device_info->default_vcomh; + + /* Setup display timing */ + if (device_property_read_u32(dev, "solomon,dclk-div", &ssd130x->dclk_div)) + ssd130x->dclk_div = ssd130x->device_info->default_dclk_div; + if (device_property_read_u32(dev, "solomon,dclk-frq", &ssd130x->dclk_frq)) + ssd130x->dclk_frq = ssd130x->device_info->default_dclk_frq; +} + +static void ssd130x_set_mode(struct ssd130x_device *ssd130x) +{ + struct drm_display_mode *mode = &ssd130x->mode; + struct drm_device *drm = &ssd130x->drm; + + mode->type = DRM_MODE_TYPE_DRIVER; + mode->clock = 1; + mode->hdisplay = mode->htotal = ssd130x->width; + mode->hsync_start = mode->hsync_end = ssd130x->width; + mode->vdisplay = mode->vtotal = ssd130x->height; + mode->vsync_start = mode->vsync_end = ssd130x->height; + mode->width_mm = 27; + mode->height_mm = 27; + + drm->mode_config.min_width = ssd130x->width; + drm->mode_config.max_width = ssd130x->width; + drm->mode_config.min_height = ssd130x->height; + drm->mode_config.max_height = ssd130x->height; + drm->mode_config.preferred_depth = 32; + drm->mode_config.funcs = &ssd130x_mode_config_funcs; +} + +static int ssd130x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ssd130x_device *ssd130x; + struct backlight_device *bl; + struct drm_device *drm; + char bl_name[12]; + int ret; + + ssd130x = devm_drm_dev_alloc(dev, &ssd130x_drm_driver, + struct ssd130x_device, drm); + if (IS_ERR(ssd130x)) + return PTR_ERR(ssd130x); + + drm = &ssd130x->drm; + + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + ssd130x->client = client; + + ssd130x->device_info = device_get_match_data(dev); + + ssd130x_parse_properties(ssd130x); + + ssd130x_set_mode(ssd130x); + + ssd130x->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ssd130x->reset)) { + dev_err(dev, "failed to get reset gpio: %ld\n", + PTR_ERR(ssd130x->reset)); + return PTR_ERR(ssd130x->reset); + } + + ssd130x->vbat_reg = devm_regulator_get_optional(dev, "vbat"); + if (IS_ERR(ssd130x->vbat_reg)) { + ret = PTR_ERR(ssd130x->vbat_reg); + if (ret == -ENODEV) { + ssd130x->vbat_reg = NULL; + } else { + dev_err(dev, "failed to get VBAT regulator: %d\n", ret); + return ret; + } + } + + drm_connector_helper_add(&ssd130x->connector, &ssd130x_connector_helper_funcs); + ret = drm_connector_init(drm, &ssd130x->connector, &ssd130x_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ret; + + i2c_set_clientdata(client, ssd130x); + + if (ssd130x->reset) + ssd130x_reset(ssd130x); + + if (ssd130x->vbat_reg) { + ret = regulator_enable(ssd130x->vbat_reg); + if (ret) { + dev_err(dev, "failed to enable VBAT: %d\n", ret); + return ret; + } + } + + ret = ssd130x_init(ssd130x); + if (ret) + goto regulator_disable; + + if (ssd130x->device_info->need_pwm) { + ret = ssd130x_pwm_enable(ssd130x); + if (ret) + goto regulator_disable; + } + + snprintf(bl_name, sizeof(bl_name), "ssd130x%d", drm->primary->index); + bl = backlight_device_register(bl_name, dev, ssd130x, &ssd130xfb_bl_ops, + NULL); + if (IS_ERR(bl)) { + ret = PTR_ERR(bl); + dev_err(dev, "unable to register backlight device: %d\n", ret); + goto pwm_disable; + } + + bl->props.brightness = ssd130x->contrast; + bl->props.max_brightness = MAX_CONTRAST; + ssd130x->bl_dev = bl; + + ret = drm_simple_display_pipe_init(drm, &ssd130x->pipe, &ssd130x_pipe_funcs, + ssd130x_formats, ARRAY_SIZE(ssd130x_formats), + NULL, &ssd130x->connector); + if (ret) + goto pwm_disable; + + drm_plane_enable_fb_damage_clips(&ssd130x->pipe.plane); + + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto backlight_unregister; + + drm_fbdev_generic_setup(drm, 0); + + return 0; + +backlight_unregister: + backlight_device_unregister(ssd130x->bl_dev); +pwm_disable: + if (ssd130x->device_info->need_pwm) { + pwm_disable(ssd130x->pwm); + pwm_put(ssd130x->pwm); + } +regulator_disable: + if (ssd130x->vbat_reg) + regulator_disable(ssd130x->vbat_reg); + return ret; +} + +static int ssd130x_remove(struct i2c_client *client) +{ + struct ssd130x_device *ssd130x = i2c_get_clientdata(client); + + drm_dev_unplug(&ssd130x->drm); + + ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_OFF); + + backlight_device_unregister(ssd130x->bl_dev); + + if (ssd130x->device_info->need_pwm) { + pwm_disable(ssd130x->pwm); + pwm_put(ssd130x->pwm); + } + if (ssd130x->vbat_reg) + regulator_disable(ssd130x->vbat_reg); + + return 0; +} + +static void ssd130x_shutdown(struct i2c_client *client) +{ + struct ssd130x_device *ssd130x = i2c_get_clientdata(client); + + drm_atomic_helper_shutdown(&ssd130x->drm); +} + +static struct ssd130x_deviceinfo ssd130x_ssd1305_deviceinfo = { + .default_vcomh = 0x34, + .default_dclk_div = 1, + .default_dclk_frq = 7, +}; + +static struct ssd130x_deviceinfo ssd130x_ssd1306_deviceinfo = { + .default_vcomh = 0x20, + .default_dclk_div = 1, + .default_dclk_frq = 8, + .need_chargepump = 1, +}; + +static struct ssd130x_deviceinfo ssd130x_ssd1307_deviceinfo = { + .default_vcomh = 0x20, + .default_dclk_div = 2, + .default_dclk_frq = 12, + .need_pwm = 1, +}; + +static struct ssd130x_deviceinfo ssd130x_ssd1309_deviceinfo = { + .default_vcomh = 0x34, + .default_dclk_div = 1, + .default_dclk_frq = 10, +}; + +static const struct of_device_id ssd130x_of_match[] = { + { + .compatible = "solomon,ssd1305fb-i2c", + .data = (void *)&ssd130x_ssd1305_deviceinfo, + }, + { + .compatible = "solomon,ssd1306fb-i2c", + .data = (void *)&ssd130x_ssd1306_deviceinfo, + }, + { + .compatible = "solomon,ssd1307fb-i2c", + .data = (void *)&ssd130x_ssd1307_deviceinfo, + }, + { + .compatible = "solomon,ssd1309fb-i2c", + .data = (void *)&ssd130x_ssd1309_deviceinfo, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ssd130x_of_match); + + +static struct i2c_driver ssd130x_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = ssd130x_of_match, + }, + .probe_new = ssd130x_probe, + .remove = ssd130x_remove, + .shutdown = ssd130x_shutdown, +}; + +module_i2c_driver(ssd130x_i2c_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Javier Martinez Canillas <javierm@redhat.com>"); +MODULE_LICENSE("GPL v2");
Hi Am 31.01.22 um 09:29 schrieb Javier Martinez Canillas: > On 1/26/22 15:15, Javier Martinez Canillas wrote: >> On 1/26/22 15:10, Andy Shevchenko wrote: >>> On Wed, Jan 26, 2022 at 04:08:32PM +0200, Andy Shevchenko wrote: >>>> On Wed, Jan 26, 2022 at 02:46:08PM +0100, Javier Martinez Canillas wrote: >>>>> On 1/26/22 14:12, Andy Shevchenko wrote: >>> >>> ... >>> >>>>> I've just bought a SSD1306 (I2C) based one and will attempt to write a DRM >>>>> driver using drivers/staging/fbtft/fb_ssd1306.c as a reference. >>>> >>>> You should take ssd1307fb.c instead. And basically create a MIPI based driver >>>> for I2C. Then we won't go same road again for other similar devices. >>> >>> For the record it supports your device: >>> >>> static const struct i2c_device_id ssd1307fb_i2c_id[] = { >>> { "ssd1305fb", 0 }, >>> { "ssd1306fb", 0 }, >>> { "ssd1307fb", 0 }, >>> { "ssd1309fb", 0 }, >>> >>> >> >> Thanks a lot for the pointer. I was only looking at drivers/staging >> and didn't check drivers/video. I'll try to convert that one then >> once I get the display. >> > > I got some time this weekend and was able to port the ssd1306 fbdev driver > to DRM [0]. I'm not that familiar with the simple display pipe helpers, so > there may be some wrong things there. But it does work and all the fbtests > from https://git.kernel.org/pub/scm/linux/kernel/git/geert/fbtest.git pass. Awesome! > > There are some hacks in the driver though. For example it exposes an XRGB8888 > format even thought the OLED display is monochromatic and has 1 bit per pixel. > > The driver then goes and converts the XRGB8888 pixels first to grayscale and > then to reverse mono. I took that idea from the repaper driver but that gives > us the multiple copies that Geert was complaining about. This requires to update the console code for 1-bit BW output. The fbcon side already supports this AFAIK. DRM's fbdev needs a few more branches and something like a DRM_FORMAT_C1 fourcc. The XRGB8888 is really a userspace requirement that is imposed by modern desktops. If DRM's console has been updated, you could leave it out entirely. I could imagine that some simple userspace, such as Weston, comes with support for palette formats and BW. Or there could be an entirely separate program that puts graphics onto these displays. Best regards Thomas > > Another hack is that I am just hardcoding the {width, height}_mm, but I don't > know what DPI could be used for these panels nor how I could calculate the mm. > > Best regards, > Javier > > [0]: > From 5ec4b468b66022d4c48ae6bec8a68926a01a6785 Mon Sep 17 00:00:00 2001 > From: Javier Martinez Canillas <javierm@redhat.com> > Date: Sun, 30 Jan 2022 12:16:34 +0100 > Subject: [RFC] drm/tiny: Add driver for Solomon SSD130X OLED displays > > Add a DRM driver for SSD1305, SSD1306, SSD1307 and SSD1309 Solomon OLED > controllers that can be programmed via an I2C interface. This is a port > of the ssd1307fb driver that already supports these devices. > > A Device Tree binding is not added because the DRM driver is compatible > with the existing binding for the ssd1307fb driver. > > Signed-off-by: Javier Martinez Canillas <javierm@redhat.com> > --- > MAINTAINERS | 7 + > drivers/gpu/drm/tiny/Kconfig | 12 + > drivers/gpu/drm/tiny/Makefile | 1 + > drivers/gpu/drm/tiny/ssd130x.c | 944 +++++++++++++++++++++++++++++++++ > 4 files changed, 964 insertions(+) > create mode 100644 drivers/gpu/drm/tiny/ssd130x.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index d03ad8da1f36..87334676ce07 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -6102,6 +6102,13 @@ T: git git://anongit.freedesktop.org/drm/drm-misc > F: Documentation/devicetree/bindings/display/repaper.txt > F: drivers/gpu/drm/tiny/repaper.c > > +DRM DRIVER FOR SOLOMON SSD130X DISPLAYS > +M: Javier Martinez Canillas <javierm@redhat.com> > +S: Maintained > +T: git git://anongit.freedesktop.org/drm/drm-misc > +F: Documentation/devicetree/bindings/display/solomon,ssd1307fb.yaml > +F: drivers/gpu/drm/tiny/ssd130x.c > + > DRM DRIVER FOR QEMU'S CIRRUS DEVICE > M: Dave Airlie <airlied@redhat.com> > M: Gerd Hoffmann <kraxel@redhat.com> > diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig > index 712e0004e96e..358ceb7354f5 100644 > --- a/drivers/gpu/drm/tiny/Kconfig > +++ b/drivers/gpu/drm/tiny/Kconfig > @@ -157,6 +157,18 @@ config TINYDRM_REPAPER > > If M is selected the module will be called repaper. > > +config TINYDRM_SSD130X > + tristate "DRM support for Solomon SSD130X OLED displays" > + depends on DRM && OF && I2C > + select DRM_KMS_HELPER > + select DRM_GEM_SHMEM_HELPER > + select BACKLIGHT_CLASS_DEVICE > + help > + DRM driver for the SSD1305, SSD1306, SSD1307 and SSD1309 Solomon > + OLED controllers that can be programmed via an I2C interface. > + > + If M is selected the module will be called ssd130x. > + > config TINYDRM_ST7586 > tristate "DRM support for Sitronix ST7586 display panels" > depends on DRM && SPI > diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile > index 5d5505d40e7b..93a1d70155f0 100644 > --- a/drivers/gpu/drm/tiny/Makefile > +++ b/drivers/gpu/drm/tiny/Makefile > @@ -12,5 +12,6 @@ obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o > obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o > obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o > obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o > +obj-$(CONFIG_TINYDRM_SSD130X) += ssd130x.o > obj-$(CONFIG_TINYDRM_ST7586) += st7586.o > obj-$(CONFIG_TINYDRM_ST7735R) += st7735r.o > diff --git a/drivers/gpu/drm/tiny/ssd130x.c b/drivers/gpu/drm/tiny/ssd130x.c > new file mode 100644 > index 000000000000..88d88caeb37d > --- /dev/null > +++ b/drivers/gpu/drm/tiny/ssd130x.c > @@ -0,0 +1,944 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * DRM driver for Solomon SSD130X OLED displays > + * > + * Copyright 2022 Red Hat Inc. > + * > + * Based on drivers/video/fbdev/ssd1307fb.c > + * Copyright 2012 Free Electrons > + * > + */ > + > +#include <linux/backlight.h> > +#include <linux/delay.h> > +#include <linux/gpio/consumer.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/property.h> > +#include <linux/pwm.h> > +#include <linux/regulator/consumer.h> > + > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_damage_helper.h> > +#include <drm/drm_drv.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_format_helper.h> > +#include <drm/drm_gem_atomic_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > +#include <drm/drm_gem_shmem_helper.h> > +#include <drm/drm_managed.h> > +#include <drm/drm_modes.h> > +#include <drm/drm_rect.h> > +#include <drm/drm_probe_helper.h> > +#include <drm/drm_simple_kms_helper.h> > + > +#define DRIVER_NAME "ssd130x" > +#define DRIVER_DESC "DRM driver for Solomon SSD130X OLED displays" > +#define DRIVER_DATE "20220130" > +#define DRIVER_MAJOR 1 > +#define DRIVER_MINOR 0 > + > +#define SSD130X_DATA 0x40 > +#define SSD130X_COMMAND 0x80 > + > +#define SSD130X_SET_ADDRESS_MODE 0x20 > +#define SSD130X_SET_ADDRESS_MODE_HORIZONTAL (0x00) > +#define SSD130X_SET_ADDRESS_MODE_VERTICAL (0x01) > +#define SSD130X_SET_ADDRESS_MODE_PAGE (0x02) > +#define SSD130X_SET_COL_RANGE 0x21 > +#define SSD130X_SET_PAGE_RANGE 0x22 > +#define SSD130X_CONTRAST 0x81 > +#define SSD130X_SET_LOOKUP_TABLE 0x91 > +#define SSD130X_CHARGE_PUMP 0x8d > +#define SSD130X_SEG_REMAP_ON 0xa1 > +#define SSD130X_DISPLAY_OFF 0xae > +#define SSD130X_SET_MULTIPLEX_RATIO 0xa8 > +#define SSD130X_DISPLAY_ON 0xaf > +#define SSD130X_START_PAGE_ADDRESS 0xb0 > +#define SSD130X_SET_DISPLAY_OFFSET 0xd3 > +#define SSD130X_SET_CLOCK_FREQ 0xd5 > +#define SSD130X_SET_AREA_COLOR_MODE 0xd8 > +#define SSD130X_SET_PRECHARGE_PERIOD 0xd9 > +#define SSD130X_SET_COM_PINS_CONFIG 0xda > +#define SSD130X_SET_VCOMH 0xdb > + > +#define MAX_CONTRAST 255 > + > +struct ssd130x_deviceinfo { > + u32 default_vcomh; > + u32 default_dclk_div; > + u32 default_dclk_frq; > + int need_pwm; > + int need_chargepump; > +}; > + > +struct ssd130x_device { > + struct drm_device drm; > + struct drm_simple_display_pipe pipe; > + struct drm_display_mode mode; > + struct drm_connector connector; > + struct i2c_client *client; > + > + const struct ssd130x_deviceinfo *device_info; > + > + unsigned area_color_enable : 1; > + unsigned com_invdir : 1; > + unsigned com_lrremap : 1; > + unsigned com_seq : 1; > + unsigned lookup_table_set : 1; > + unsigned low_power : 1; > + unsigned seg_remap : 1; > + u32 com_offset; > + u32 contrast; > + u32 dclk_div; > + u32 dclk_frq; > + u32 height; > + u8 lookup_table[4]; > + u32 page_offset; > + u32 col_offset; > + u32 prechargep1; > + u32 prechargep2; > + > + struct backlight_device *bl_dev; > + struct pwm_device *pwm; > + struct gpio_desc *reset; > + struct regulator *vbat_reg; > + u32 vcomh; > + u32 width; > + /* Cached address ranges */ > + u8 col_start; > + u8 col_end; > + u8 page_start; > + u8 page_end; > +}; > + > +struct ssd130x_array { > + u8 type; > + u8 data[]; > +}; > + > +static inline struct ssd130x_device *drm_to_ssd130x(struct drm_device *drm) > +{ > + return container_of(drm, struct ssd130x_device, drm); > +} > + > +static struct ssd130x_array *ssd130x_alloc_array(u32 len, u8 type) > +{ > + struct ssd130x_array *array; > + > + array = kzalloc(sizeof(struct ssd130x_array) + len, GFP_KERNEL); > + if (!array) > + return NULL; > + > + array->type = type; > + > + return array; > +} > + > +static int ssd130x_write_array(struct i2c_client *client, > + struct ssd130x_array *array, u32 len) > +{ > + int ret; > + > + len += sizeof(struct ssd130x_array); > + > + ret = i2c_master_send(client, (u8 *)array, len); > + if (ret != len) { > + dev_err(&client->dev, "Couldn't send I2C command.\n"); > + return ret; > + } > + > + return 0; > +} > + > +static inline int ssd130x_write_cmd(struct i2c_client *client, u8 cmd) > +{ > + struct ssd130x_array *array; > + int ret; > + > + array = ssd130x_alloc_array(1, SSD130X_COMMAND); > + if (!array) > + return -ENOMEM; > + > + array->data[0] = cmd; > + > + ret = ssd130x_write_array(client, array, 1); > + kfree(array); > + > + return ret; > +} > + > +static int ssd130x_set_col_range(struct ssd130x_device *ssd130x, > + u8 col_start, u8 cols) > +{ > + u8 col_end = col_start + cols - 1; > + int ret; > + > + if (col_start == ssd130x->col_start && col_end == ssd130x->col_end) > + return 0; > + > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_COL_RANGE); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, col_start); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, col_end); > + if (ret < 0) > + return ret; > + > + ssd130x->col_start = col_start; > + ssd130x->col_end = col_end; > + return 0; > +} > + > +static int ssd130x_set_page_range(struct ssd130x_device *ssd130x, > + u8 page_start, u8 pages) > +{ > + u8 page_end = page_start + pages - 1; > + int ret; > + > + if (page_start == ssd130x->page_start && page_end == ssd130x->page_end) > + return 0; > + > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_PAGE_RANGE); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, page_start); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, page_end); > + if (ret < 0) > + return ret; > + > + ssd130x->page_start = page_start; > + ssd130x->page_end = page_end; > + return 0; > +} > + > +static int ssd130x_update_display(struct ssd130x_device *ssd130x, u8 *buf, > + u32 width, u32 height) > +{ > + struct ssd130x_array *array; > + unsigned int line_length = DIV_ROUND_UP(width, 8); > + unsigned int pages = DIV_ROUND_UP(height, 8); > + u32 array_idx = 0; > + int ret, i, j, k; > + > + array = ssd130x_alloc_array(width * pages, SSD130X_DATA); > + if (!array) > + return -ENOMEM; > + > + /* > + * The screen is divided in pages, each having a height of 8 > + * pixels, and the width of the screen. When sending a byte of > + * data to the controller, it gives the 8 bits for the current > + * column. I.e, the first byte are the 8 bits of the first > + * column, then the 8 bits for the second column, etc. > + * > + * > + * Representation of the screen, assuming it is 5 bits > + * wide. Each letter-number combination is a bit that controls > + * one pixel. > + * > + * A0 A1 A2 A3 A4 > + * B0 B1 B2 B3 B4 > + * C0 C1 C2 C3 C4 > + * D0 D1 D2 D3 D4 > + * E0 E1 E2 E3 E4 > + * F0 F1 F2 F3 F4 > + * G0 G1 G2 G3 G4 > + * H0 H1 H2 H3 H4 > + * > + * If you want to update this screen, you need to send 5 bytes: > + * (1) A0 B0 C0 D0 E0 F0 G0 H0 > + * (2) A1 B1 C1 D1 E1 F1 G1 H1 > + * (3) A2 B2 C2 D2 E2 F2 G2 H2 > + * (4) A3 B3 C3 D3 E3 F3 G3 H3 > + * (5) A4 B4 C4 D4 E4 F4 G4 H4 > + */ > + > + ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset, width); > + if (ret < 0) > + goto out_free; > + > + ret = ssd130x_set_page_range(ssd130x, ssd130x->page_offset / 8, pages); > + if (ret < 0) > + goto out_free; > + > + for (i = 0; i < pages; i++) { > + int m = 8; > + > + /* Last page may be partial */ > + if (8 * (i + 1) > ssd130x->height) > + m = ssd130x->height % 8; > + for (j = 0; j < width; j++) { > + u8 data = 0; > + > + for (k = 0; k < m; k++) { > + u8 byte = buf[(8 * i + k) * line_length + > + j / 8]; > + u8 bit = (byte >> (j % 8)) & 1; > + > + data |= bit << k; > + } > + array->data[array_idx++] = data; > + } > + } > + > + ret = ssd130x_write_array(ssd130x->client, array, width * pages); > + > +out_free: > + kfree(array); > + return ret; > +} > + > +static void ssd130x_gray8_to_mono_reversed(u8 *buf, u32 width, u32 height) > +{ > + u8 *gray8 = buf, *mono = buf; > + int y, xb, i; > + > + for (y = 0; y < height; y++) > + for (xb = 0; xb < width / 8; xb++) { > + u8 byte = 0x00; > + > + for (i = 0; i < 8; i++) { > + int x = xb * 8 + i; > + > + byte >>= 1; > + if (gray8[y * width + x] >> 7) > + byte |= BIT(7); > + } > + *mono++ = byte; > + } > +} > + > +static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct dma_buf_map *map, > + struct drm_rect *rect) > +{ > + struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); > + void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */ > + int idx, ret = 0; > + u8 *buf = NULL; > + > + if (!drm_dev_enter(fb->dev, &idx)) > + return -ENODEV; > + > + buf = kmalloc_array(fb->width, fb->height, GFP_KERNEL); > + if (!buf) { > + ret = -ENOMEM; > + goto out_exit; > + } > + > + drm_fb_xrgb8888_to_gray8(buf, 0, vmap, fb, rect); > + > + ssd130x_gray8_to_mono_reversed(buf, fb->width, fb->height); > + > + ssd130x_update_display(ssd130x, buf, fb->width, fb->height); > + > + kfree(buf); > +out_exit: > + drm_dev_exit(idx); > + > + return ret; > +} > + > +static void ssd130x_display_pipe_enable(struct drm_simple_display_pipe *pipe, > + struct drm_crtc_state *crtc_state, > + struct drm_plane_state *plane_state) > +{ > + struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev); > + int idx; > + > + if (!drm_dev_enter(pipe->crtc.dev, &idx)) > + return; > + > + backlight_enable(ssd130x->bl_dev); > + > + ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_ON); > +} > + > +static void ssd130x_display_pipe_disable(struct drm_simple_display_pipe *pipe) > +{ > + struct ssd130x_device *ssd130x = drm_to_ssd130x(pipe->crtc.dev); > + > + ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_OFF); > + > + backlight_disable(ssd130x->bl_dev); > +} > + > +static void ssd130x_display_pipe_update(struct drm_simple_display_pipe *pipe, > + struct drm_plane_state *old_plane_state) > +{ > + struct drm_plane_state *state = pipe->plane.state; > + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state); > + struct drm_rect rect; > + > + if (!pipe->crtc.state->active) > + return; > + > + if (drm_atomic_helper_damage_merged(old_plane_state, state, &rect)) > + ssd130x_fb_blit_rect(state->fb, &shadow_plane_state->data[0], &rect); > +} > + > +static const struct drm_simple_display_pipe_funcs ssd130x_pipe_funcs = { > + .enable = ssd130x_display_pipe_enable, > + .disable = ssd130x_display_pipe_disable, > + .update = ssd130x_display_pipe_update, > + DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS, > +}; > + > +static int ssd130x_connector_get_modes(struct drm_connector *connector) > +{ > + struct ssd130x_device *ssd130x = drm_to_ssd130x(connector->dev); > + struct drm_display_mode *mode; > + > + mode = drm_mode_duplicate(connector->dev, &ssd130x->mode); > + if (!mode) { > + DRM_ERROR("Failed to duplicate mode\n"); > + return 0; > + } > + > + connector->display_info.width_mm = mode->width_mm; > + connector->display_info.height_mm = mode->height_mm; > + connector->display_info.bpc = 32; > + > + drm_mode_set_name(mode); > + mode->type |= DRM_MODE_TYPE_PREFERRED; > + drm_mode_probed_add(connector, mode); > + > + return 1; > +} > + > +static const struct drm_connector_helper_funcs ssd130x_connector_helper_funcs = { > + .get_modes = ssd130x_connector_get_modes, > +}; > + > +static const struct drm_connector_funcs ssd130x_connector_funcs = { > + .reset = drm_atomic_helper_connector_reset, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = drm_connector_cleanup, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > +}; > + > +static const struct drm_mode_config_funcs ssd130x_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 uint32_t ssd130x_formats[] = { > + DRM_FORMAT_XRGB8888, > +}; > + > +DEFINE_DRM_GEM_FOPS(ssd130x_fops); > + > +static const struct drm_driver ssd130x_drm_driver = { > + DRM_GEM_SHMEM_DRIVER_OPS, > + .name = DRIVER_NAME, > + .desc = DRIVER_DESC, > + .date = DRIVER_DATE, > + .major = DRIVER_MAJOR, > + .minor = DRIVER_MINOR, > + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, > + .fops = &ssd130x_fops, > +}; > + > +static int ssd130x_pwm_enable(struct ssd130x_device *ssd130x) > +{ > + struct pwm_state pwmstate; > + > + ssd130x->pwm = pwm_get(&ssd130x->client->dev, NULL); > + if (IS_ERR(ssd130x->pwm)) { > + dev_err(&ssd130x->client->dev, "Could not get PWM from device tree!\n"); > + return PTR_ERR(ssd130x->pwm); > + } > + > + pwm_init_state(ssd130x->pwm, &pwmstate); > + pwm_set_relative_duty_cycle(&pwmstate, 50, 100); > + pwm_apply_state(ssd130x->pwm, &pwmstate); > + > + /* Enable the PWM */ > + pwm_enable(ssd130x->pwm); > + > + dev_dbg(&ssd130x->client->dev, "Using PWM%d with a %lluns period.\n", > + ssd130x->pwm->pwm, pwm_get_period(ssd130x->pwm)); > + > + return 0; > +} > + > +static int ssd130x_init(struct ssd130x_device *ssd130x) > +{ > + u32 precharge, dclk, com_invdir, compins; > + int ret; > + > + /* Set initial contrast */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_CONTRAST); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->contrast); > + if (ret < 0) > + return ret; > + > + /* Set segment re-map */ > + if (ssd130x->seg_remap) { > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SEG_REMAP_ON); > + if (ret < 0) > + return ret; > + } > + > + /* Set COM direction */ > + com_invdir = 0xc0 | ssd130x->com_invdir << 3; > + ret = ssd130x_write_cmd(ssd130x->client, com_invdir); > + if (ret < 0) > + return ret; > + > + /* Set multiplex ratio value */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_MULTIPLEX_RATIO); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->height - 1); > + if (ret < 0) > + return ret; > + > + /* set display offset value */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_DISPLAY_OFFSET); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->com_offset); > + if (ret < 0) > + return ret; > + > + /* Set clock frequency */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_CLOCK_FREQ); > + if (ret < 0) > + return ret; > + > + dclk = ((ssd130x->dclk_div - 1) & 0xf) | (ssd130x->dclk_frq & 0xf) << 4; > + ret = ssd130x_write_cmd(ssd130x->client, dclk); > + if (ret < 0) > + return ret; > + > + /* Set Area Color Mode ON/OFF & Low Power Display Mode */ > + if (ssd130x->area_color_enable || ssd130x->low_power) { > + u32 mode; > + > + ret = ssd130x_write_cmd(ssd130x->client, > + SSD130X_SET_AREA_COLOR_MODE); > + if (ret < 0) > + return ret; > + > + mode = (ssd130x->area_color_enable ? 0x30 : 0) | > + (ssd130x->low_power ? 5 : 0); > + ret = ssd130x_write_cmd(ssd130x->client, mode); > + if (ret < 0) > + return ret; > + } > + > + /* Set precharge period in number of ticks from the internal clock */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_PRECHARGE_PERIOD); > + if (ret < 0) > + return ret; > + > + precharge = (ssd130x->prechargep1 & 0xf) | (ssd130x->prechargep2 & 0xf) << 4; > + ret = ssd130x_write_cmd(ssd130x->client, precharge); > + if (ret < 0) > + return ret; > + > + /* Set COM pins configuration */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_COM_PINS_CONFIG); > + if (ret < 0) > + return ret; > + > + compins = 0x02 | !ssd130x->com_seq << 4 | ssd130x->com_lrremap << 5; > + ret = ssd130x_write_cmd(ssd130x->client, compins); > + if (ret < 0) > + return ret; > + > + /* Set VCOMH */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_VCOMH); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->vcomh); > + if (ret < 0) > + return ret; > + > + /* Turn on the DC-DC Charge Pump */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_CHARGE_PUMP); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, > + BIT(4) | (ssd130x->device_info->need_chargepump ? BIT(2) : 0)); > + if (ret < 0) > + return ret; > + > + /* Set lookup table */ > + if (ssd130x->lookup_table_set) { > + int i; > + > + ret = ssd130x_write_cmd(ssd130x->client, > + SSD130X_SET_LOOKUP_TABLE); > + if (ret < 0) > + return ret; > + > + for (i = 0; i < ARRAY_SIZE(ssd130x->lookup_table); ++i) { > + u8 val = ssd130x->lookup_table[i]; > + > + if (val < 31 || val > 63) > + dev_warn(&ssd130x->client->dev, > + "lookup table index %d value out of range 31 <= %d <= 63\n", > + i, val); > + ret = ssd130x_write_cmd(ssd130x->client, val); > + if (ret < 0) > + return ret; > + } > + } > + > + /* Switch to horizontal addressing mode */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_SET_ADDRESS_MODE); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, > + SSD130X_SET_ADDRESS_MODE_HORIZONTAL); > + if (ret < 0) > + return ret; > + > + /* Turn on the display */ > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_ON); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static int ssd130x_update_bl(struct backlight_device *bdev) > +{ > + struct ssd130x_device *ssd130x = bl_get_data(bdev); > + int brightness = bdev->props.brightness; > + int ret; > + > + ssd130x->contrast = brightness; > + > + ret = ssd130x_write_cmd(ssd130x->client, SSD130X_CONTRAST); > + if (ret < 0) > + return ret; > + > + ret = ssd130x_write_cmd(ssd130x->client, ssd130x->contrast); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static int ssd130x_get_brightness(struct backlight_device *bdev) > +{ > + struct ssd130x_device *ssd130x = bl_get_data(bdev); > + > + return ssd130x->contrast; > +} > + > +static const struct backlight_ops ssd130xfb_bl_ops = { > + .update_status = ssd130x_update_bl, > + .get_brightness = ssd130x_get_brightness, > +}; > + > +static void ssd130x_reset(struct ssd130x_device *ssd130x) > +{ > + /* Reset the screen */ > + gpiod_set_value_cansleep(ssd130x->reset, 1); > + udelay(4); > + gpiod_set_value_cansleep(ssd130x->reset, 0); > + udelay(4); > +} > + > +static void ssd130x_parse_properties(struct ssd130x_device *ssd130x) > +{ > + struct device *dev = &ssd130x->client->dev; > + > + if (device_property_read_u32(dev, "solomon,width", &ssd130x->width)) > + ssd130x->width = 96; > + > + if (device_property_read_u32(dev, "solomon,height", &ssd130x->height)) > + ssd130x->height = 16; > + > + if (device_property_read_u32(dev, "solomon,page-offset", &ssd130x->page_offset)) > + ssd130x->page_offset = 1; > + > + if (device_property_read_u32(dev, "solomon,col-offset", &ssd130x->col_offset)) > + ssd130x->col_offset = 0; > + > + if (device_property_read_u32(dev, "solomon,com-offset", &ssd130x->com_offset)) > + ssd130x->com_offset = 0; > + > + if (device_property_read_u32(dev, "solomon,prechargep1", &ssd130x->prechargep1)) > + ssd130x->prechargep1 = 2; > + > + if (device_property_read_u32(dev, "solomon,prechargep2", &ssd130x->prechargep2)) > + ssd130x->prechargep2 = 2; > + > + if (!device_property_read_u8_array(dev, "solomon,lookup-table", > + ssd130x->lookup_table, > + ARRAY_SIZE(ssd130x->lookup_table))) > + ssd130x->lookup_table_set = 1; > + > + ssd130x->seg_remap = !device_property_read_bool(dev, "solomon,segment-no-remap"); > + ssd130x->com_seq = device_property_read_bool(dev, "solomon,com-seq"); > + ssd130x->com_lrremap = device_property_read_bool(dev, "solomon,com-lrremap"); > + ssd130x->com_invdir = device_property_read_bool(dev, "solomon,com-invdir"); > + ssd130x->area_color_enable = > + device_property_read_bool(dev, "solomon,area-color-enable"); > + ssd130x->low_power = device_property_read_bool(dev, "solomon,low-power"); > + > + ssd130x->contrast = 127; > + ssd130x->vcomh = ssd130x->device_info->default_vcomh; > + > + /* Setup display timing */ > + if (device_property_read_u32(dev, "solomon,dclk-div", &ssd130x->dclk_div)) > + ssd130x->dclk_div = ssd130x->device_info->default_dclk_div; > + if (device_property_read_u32(dev, "solomon,dclk-frq", &ssd130x->dclk_frq)) > + ssd130x->dclk_frq = ssd130x->device_info->default_dclk_frq; > +} > + > +static void ssd130x_set_mode(struct ssd130x_device *ssd130x) > +{ > + struct drm_display_mode *mode = &ssd130x->mode; > + struct drm_device *drm = &ssd130x->drm; > + > + mode->type = DRM_MODE_TYPE_DRIVER; > + mode->clock = 1; > + mode->hdisplay = mode->htotal = ssd130x->width; > + mode->hsync_start = mode->hsync_end = ssd130x->width; > + mode->vdisplay = mode->vtotal = ssd130x->height; > + mode->vsync_start = mode->vsync_end = ssd130x->height; > + mode->width_mm = 27; > + mode->height_mm = 27; > + > + drm->mode_config.min_width = ssd130x->width; > + drm->mode_config.max_width = ssd130x->width; > + drm->mode_config.min_height = ssd130x->height; > + drm->mode_config.max_height = ssd130x->height; > + drm->mode_config.preferred_depth = 32; > + drm->mode_config.funcs = &ssd130x_mode_config_funcs; > +} > + > +static int ssd130x_probe(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + struct ssd130x_device *ssd130x; > + struct backlight_device *bl; > + struct drm_device *drm; > + char bl_name[12]; > + int ret; > + > + ssd130x = devm_drm_dev_alloc(dev, &ssd130x_drm_driver, > + struct ssd130x_device, drm); > + if (IS_ERR(ssd130x)) > + return PTR_ERR(ssd130x); > + > + drm = &ssd130x->drm; > + > + ret = drmm_mode_config_init(drm); > + if (ret) > + return ret; > + > + ssd130x->client = client; > + > + ssd130x->device_info = device_get_match_data(dev); > + > + ssd130x_parse_properties(ssd130x); > + > + ssd130x_set_mode(ssd130x); > + > + ssd130x->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); > + if (IS_ERR(ssd130x->reset)) { > + dev_err(dev, "failed to get reset gpio: %ld\n", > + PTR_ERR(ssd130x->reset)); > + return PTR_ERR(ssd130x->reset); > + } > + > + ssd130x->vbat_reg = devm_regulator_get_optional(dev, "vbat"); > + if (IS_ERR(ssd130x->vbat_reg)) { > + ret = PTR_ERR(ssd130x->vbat_reg); > + if (ret == -ENODEV) { > + ssd130x->vbat_reg = NULL; > + } else { > + dev_err(dev, "failed to get VBAT regulator: %d\n", ret); > + return ret; > + } > + } > + > + drm_connector_helper_add(&ssd130x->connector, &ssd130x_connector_helper_funcs); > + ret = drm_connector_init(drm, &ssd130x->connector, &ssd130x_connector_funcs, > + DRM_MODE_CONNECTOR_Unknown); > + if (ret) > + return ret; > + > + i2c_set_clientdata(client, ssd130x); > + > + if (ssd130x->reset) > + ssd130x_reset(ssd130x); > + > + if (ssd130x->vbat_reg) { > + ret = regulator_enable(ssd130x->vbat_reg); > + if (ret) { > + dev_err(dev, "failed to enable VBAT: %d\n", ret); > + return ret; > + } > + } > + > + ret = ssd130x_init(ssd130x); > + if (ret) > + goto regulator_disable; > + > + if (ssd130x->device_info->need_pwm) { > + ret = ssd130x_pwm_enable(ssd130x); > + if (ret) > + goto regulator_disable; > + } > + > + snprintf(bl_name, sizeof(bl_name), "ssd130x%d", drm->primary->index); > + bl = backlight_device_register(bl_name, dev, ssd130x, &ssd130xfb_bl_ops, > + NULL); > + if (IS_ERR(bl)) { > + ret = PTR_ERR(bl); > + dev_err(dev, "unable to register backlight device: %d\n", ret); > + goto pwm_disable; > + } > + > + bl->props.brightness = ssd130x->contrast; > + bl->props.max_brightness = MAX_CONTRAST; > + ssd130x->bl_dev = bl; > + > + ret = drm_simple_display_pipe_init(drm, &ssd130x->pipe, &ssd130x_pipe_funcs, > + ssd130x_formats, ARRAY_SIZE(ssd130x_formats), > + NULL, &ssd130x->connector); > + if (ret) > + goto pwm_disable; > + > + drm_plane_enable_fb_damage_clips(&ssd130x->pipe.plane); > + > + drm_mode_config_reset(drm); > + > + ret = drm_dev_register(drm, 0); > + if (ret) > + goto backlight_unregister; > + > + drm_fbdev_generic_setup(drm, 0); > + > + return 0; > + > +backlight_unregister: > + backlight_device_unregister(ssd130x->bl_dev); > +pwm_disable: > + if (ssd130x->device_info->need_pwm) { > + pwm_disable(ssd130x->pwm); > + pwm_put(ssd130x->pwm); > + } > +regulator_disable: > + if (ssd130x->vbat_reg) > + regulator_disable(ssd130x->vbat_reg); > + return ret; > +} > + > +static int ssd130x_remove(struct i2c_client *client) > +{ > + struct ssd130x_device *ssd130x = i2c_get_clientdata(client); > + > + drm_dev_unplug(&ssd130x->drm); > + > + ssd130x_write_cmd(ssd130x->client, SSD130X_DISPLAY_OFF); > + > + backlight_device_unregister(ssd130x->bl_dev); > + > + if (ssd130x->device_info->need_pwm) { > + pwm_disable(ssd130x->pwm); > + pwm_put(ssd130x->pwm); > + } > + if (ssd130x->vbat_reg) > + regulator_disable(ssd130x->vbat_reg); > + > + return 0; > +} > + > +static void ssd130x_shutdown(struct i2c_client *client) > +{ > + struct ssd130x_device *ssd130x = i2c_get_clientdata(client); > + > + drm_atomic_helper_shutdown(&ssd130x->drm); > +} > + > +static struct ssd130x_deviceinfo ssd130x_ssd1305_deviceinfo = { > + .default_vcomh = 0x34, > + .default_dclk_div = 1, > + .default_dclk_frq = 7, > +}; > + > +static struct ssd130x_deviceinfo ssd130x_ssd1306_deviceinfo = { > + .default_vcomh = 0x20, > + .default_dclk_div = 1, > + .default_dclk_frq = 8, > + .need_chargepump = 1, > +}; > + > +static struct ssd130x_deviceinfo ssd130x_ssd1307_deviceinfo = { > + .default_vcomh = 0x20, > + .default_dclk_div = 2, > + .default_dclk_frq = 12, > + .need_pwm = 1, > +}; > + > +static struct ssd130x_deviceinfo ssd130x_ssd1309_deviceinfo = { > + .default_vcomh = 0x34, > + .default_dclk_div = 1, > + .default_dclk_frq = 10, > +}; > + > +static const struct of_device_id ssd130x_of_match[] = { > + { > + .compatible = "solomon,ssd1305fb-i2c", > + .data = (void *)&ssd130x_ssd1305_deviceinfo, > + }, > + { > + .compatible = "solomon,ssd1306fb-i2c", > + .data = (void *)&ssd130x_ssd1306_deviceinfo, > + }, > + { > + .compatible = "solomon,ssd1307fb-i2c", > + .data = (void *)&ssd130x_ssd1307_deviceinfo, > + }, > + { > + .compatible = "solomon,ssd1309fb-i2c", > + .data = (void *)&ssd130x_ssd1309_deviceinfo, > + }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ssd130x_of_match); > + > + > +static struct i2c_driver ssd130x_i2c_driver = { > + .driver = { > + .name = DRIVER_NAME, > + .of_match_table = ssd130x_of_match, > + }, > + .probe_new = ssd130x_probe, > + .remove = ssd130x_remove, > + .shutdown = ssd130x_shutdown, > +}; > + > +module_i2c_driver(ssd130x_i2c_driver); > + > +MODULE_DESCRIPTION(DRIVER_DESC); > +MODULE_AUTHOR("Javier Martinez Canillas <javierm@redhat.com>"); > +MODULE_LICENSE("GPL v2");
Hello Thomas, On 1/31/22 10:18, Thomas Zimmermann wrote: [snip] >> There are some hacks in the driver though. For example it exposes an XRGB8888 >> format even thought the OLED display is monochromatic and has 1 bit per pixel. >> >> The driver then goes and converts the XRGB8888 pixels first to grayscale and >> then to reverse mono. I took that idea from the repaper driver but that gives >> us the multiple copies that Geert was complaining about. > > This requires to update the console code for 1-bit BW output. The fbcon > side already supports this AFAIK. DRM's fbdev needs a few more branches > and something like a DRM_FORMAT_C1 fourcc. The XRGB8888 is really a > userspace requirement that is imposed by modern desktops. If DRM's > console has been updated, you could leave it out entirely. > > I could imagine that some simple userspace, such as Weston, comes with > support for palette formats and BW. Or there could be an entirely > separate program that puts graphics onto these displays. > Yes, I understand the rationale of why the repaper driver is doing that way but was just pointing out because Geert mentioned that is not efficient. Maybe in the meantime we can add a drm_fb_gray8_to_mono_reversed() helper to drivers/gpu/drm/drm_format_helper.c since there is more than one driver that does the same ? It's not a big issue for this device really since the I2C bus is slow anyways and the multiple copies are not a bottleneck AFAICT. I believe is worth to propose this driver as is and then try to optimize later. Another thing that's missing is a DRM_MODE_CONNECTOR_I2C, because I used for now a DRM_MODE_CONNECTOR_Unknown. Best regards,
Hi Am 31.01.22 um 11:18 schrieb Javier Martinez Canillas: > Hello Thomas, > > On 1/31/22 10:18, Thomas Zimmermann wrote: > > [snip] > >>> There are some hacks in the driver though. For example it exposes an XRGB8888 >>> format even thought the OLED display is monochromatic and has 1 bit per pixel. >>> >>> The driver then goes and converts the XRGB8888 pixels first to grayscale and >>> then to reverse mono. I took that idea from the repaper driver but that gives >>> us the multiple copies that Geert was complaining about. >> >> This requires to update the console code for 1-bit BW output. The fbcon >> side already supports this AFAIK. DRM's fbdev needs a few more branches >> and something like a DRM_FORMAT_C1 fourcc. The XRGB8888 is really a >> userspace requirement that is imposed by modern desktops. If DRM's >> console has been updated, you could leave it out entirely. >> >> I could imagine that some simple userspace, such as Weston, comes with >> support for palette formats and BW. Or there could be an entirely >> separate program that puts graphics onto these displays. >> > > Yes, I understand the rationale of why the repaper driver is doing that way > but was just pointing out because Geert mentioned that is not efficient. It's a fair point, I think. People are concerned about resource consumption on low-end devices. > > Maybe in the meantime we can add a drm_fb_gray8_to_mono_reversed() helper to > drivers/gpu/drm/drm_format_helper.c since there is more than one driver that > does the same ? Sure. > > It's not a big issue for this device really since the I2C bus is slow anyways > and the multiple copies are not a bottleneck AFAICT. > > I believe is worth to propose this driver as is and then try to optimize later. Absolutely. If you post a cleaned-up version of the patch, I'd take a look. > > Another thing that's missing is a DRM_MODE_CONNECTOR_I2C, because I used for > now a DRM_MODE_CONNECTOR_Unknown. That might have implications on userspace. Maybe ask around. (Not that we actually run userspace on the device). Best regards Thomas > > Best regards,
On Mon, Jan 31, 2022 at 09:29:33AM +0100, Javier Martinez Canillas wrote: > On 1/26/22 15:15, Javier Martinez Canillas wrote: > > On 1/26/22 15:10, Andy Shevchenko wrote: > >> On Wed, Jan 26, 2022 at 04:08:32PM +0200, Andy Shevchenko wrote: > >>> On Wed, Jan 26, 2022 at 02:46:08PM +0100, Javier Martinez Canillas wrote: > >>>> On 1/26/22 14:12, Andy Shevchenko wrote: > >> > >> ... > >> > >>>> I've just bought a SSD1306 (I2C) based one and will attempt to write a DRM > >>>> driver using drivers/staging/fbtft/fb_ssd1306.c as a reference. > >>> > >>> You should take ssd1307fb.c instead. And basically create a MIPI based driver > >>> for I2C. Then we won't go same road again for other similar devices. > >> > >> For the record it supports your device: > >> > >> static const struct i2c_device_id ssd1307fb_i2c_id[] = { > >> { "ssd1305fb", 0 }, > >> { "ssd1306fb", 0 }, > >> { "ssd1307fb", 0 }, > >> { "ssd1309fb", 0 }, > >> > >> > > > > Thanks a lot for the pointer. I was only looking at drivers/staging > > and didn't check drivers/video. I'll try to convert that one then > > once I get the display. > > > > I got some time this weekend and was able to port the ssd1306 fbdev driver > to DRM [0]. I'm not that familiar with the simple display pipe helpers, so > there may be some wrong things there. But it does work and all the fbtests > from https://git.kernel.org/pub/scm/linux/kernel/git/geert/fbtest.git pass. Thanks! Good news, everybody! > There are some hacks in the driver though. For example it exposes an XRGB8888 > format even thought the OLED display is monochromatic and has 1 bit per pixel. > > The driver then goes and converts the XRGB8888 pixels first to grayscale and > then to reverse mono. I took that idea from the repaper driver but that gives > us the multiple copies that Geert was complaining about. > > Another hack is that I am just hardcoding the {width, height}_mm, but I don't > know what DPI could be used for these panels nor how I could calculate the mm. I think the hacks is the first what should be eliminated, also see below. ... > +config TINYDRM_SSD130X > + tristate "DRM support for Solomon SSD130X OLED displays" > + depends on DRM && OF && I2C Please, make sure that it does NOT dependent on OF. ... > +obj-$(CONFIG_TINYDRM_SSD130X) += ssd130x.o I would keep the original name since we have I2C (fbdev) implementation, SPI and platform (fbtft), and now i2c (drm). I would like to avoid more confusion that we already have.
Hello Andy, Thanks a lot for your feedback. On 1/31/22 12:36, Andy Shevchenko wrote: [snip] >> >> Another hack is that I am just hardcoding the {width, height}_mm, but I don't >> know what DPI could be used for these panels nor how I could calculate the mm. > > I think the hacks is the first what should be eliminated, also see below. > Yes, agreed. But as we discussed with Thomas I'll post anyways since these could be addressed as a follow-up. > ... > >> +config TINYDRM_SSD130X >> + tristate "DRM support for Solomon SSD130X OLED displays" >> + depends on DRM && OF && I2C > > Please, make sure that it does NOT dependent on OF. > I actually added this dependency deliberative. It's true that the driver is using the device properties API and so there isn't anything from the properties parsing point of view that depends on OF. And the original driver didn't depend on OF. But the original driver also only would had worked with Device Trees since the of_device_id table is the only one that contains the device specific data info. The i2c_device_id table only listed the devices supported to match, but then it would only had worked with the default values that are set by the driver. So in practice it *does* depend on OF. I'll be happy to drop that dependency if you provide an acpi_device_id table to match. > ... > >> +obj-$(CONFIG_TINYDRM_SSD130X) += ssd130x.o > > I would keep the original name since we have I2C (fbdev) implementation, SPI > and platform (fbtft), and now i2c (drm). I would like to avoid more confusion > that we already have. > I see. That makes sense. Will I keep the original ssd1307 name then and not rename it to ssd130x (even though it would be more precise since supports a family of displays). Best regards,
On Mon, Jan 31, 2022 at 01:08:32PM +0100, Javier Martinez Canillas wrote: > On 1/31/22 12:36, Andy Shevchenko wrote: ... > >> +config TINYDRM_SSD130X > >> + tristate "DRM support for Solomon SSD130X OLED displays" > >> + depends on DRM && OF && I2C > > > > Please, make sure that it does NOT dependent on OF. > > > > I actually added this dependency deliberative. It's true that the driver is using > the device properties API and so there isn't anything from the properties parsing > point of view that depends on OF. And the original driver didn't depend on OF. > > But the original driver also only would had worked with Device Trees since the > of_device_id table is the only one that contains the device specific data info. > > The i2c_device_id table only listed the devices supported to match, but then it > would only had worked with the default values that are set by the driver. > > So in practice it *does* depend on OF. I'll be happy to drop that dependency if > you provide an acpi_device_id table to match. The code is deceptive and you become to a wrong conclusion. No, the driver does NOT depend on OF as a matter of fact. The tricky part is the PRP0001 ACPI PNP ID that allows to reuse it on ACPI-based platforms. That said, please drop OF dependency.
On Mon, Jan 31, 2022 at 03:23:13PM +0200, Andy Shevchenko wrote: > On Mon, Jan 31, 2022 at 01:08:32PM +0100, Javier Martinez Canillas wrote: > > On 1/31/22 12:36, Andy Shevchenko wrote: ... > > I actually added this dependency deliberative. It's true that the driver is using > > the device properties API and so there isn't anything from the properties parsing > > point of view that depends on OF. And the original driver didn't depend on OF. > > > > But the original driver also only would had worked with Device Trees since the > > of_device_id table is the only one that contains the device specific data info. > > > > The i2c_device_id table only listed the devices supported to match, but then it > > would only had worked with the default values that are set by the driver. > > > > So in practice it *does* depend on OF. I'll be happy to drop that dependency if > > you provide an acpi_device_id table to match. > > The code is deceptive and you become to a wrong conclusion. No, the driver > does NOT depend on OF as a matter of fact. The tricky part is the PRP0001 > ACPI PNP ID that allows to reuse it on ACPI-based platforms. > > That said, please drop OF dependency. Side note: 72915994e028 ("video: ssd1307fb: Make use of device properties")
On 1/31/22 14:23, Andy Shevchenko wrote: > On Mon, Jan 31, 2022 at 01:08:32PM +0100, Javier Martinez Canillas wrote: >> On 1/31/22 12:36, Andy Shevchenko wrote: > > ... > >>>> +config TINYDRM_SSD130X >>>> + tristate "DRM support for Solomon SSD130X OLED displays" >>>> + depends on DRM && OF && I2C >>> >>> Please, make sure that it does NOT dependent on OF. >>> >> >> I actually added this dependency deliberative. It's true that the driver is using >> the device properties API and so there isn't anything from the properties parsing >> point of view that depends on OF. And the original driver didn't depend on OF. >> >> But the original driver also only would had worked with Device Trees since the >> of_device_id table is the only one that contains the device specific data info. >> >> The i2c_device_id table only listed the devices supported to match, but then it >> would only had worked with the default values that are set by the driver. >> >> So in practice it *does* depend on OF. I'll be happy to drop that dependency if >> you provide an acpi_device_id table to match. > > The code is deceptive and you become to a wrong conclusion. No, the driver > does NOT depend on OF as a matter of fact. The tricky part is the PRP0001 > ACPI PNP ID that allows to reuse it on ACPI-based platforms. > Oh, I wasn't aware about PRP0001. I've read about it at: https://www.kernel.org/doc/Documentation/acpi/enumeration.txt > That said, please drop OF dependency. > Yes, got your point now and will drop the dep. Thanks for the explanation. Best regards,
On Mon, Jan 31, 2022 at 02:55:21PM +0100, Javier Martinez Canillas wrote: > On 1/31/22 14:23, Andy Shevchenko wrote: > > On Mon, Jan 31, 2022 at 01:08:32PM +0100, Javier Martinez Canillas wrote: ... > > The tricky part is the PRP0001 > > ACPI PNP ID that allows to reuse it on ACPI-based platforms. > > Oh, I wasn't aware about PRP0001. I've read about it at: > > https://www.kernel.org/doc/Documentation/acpi/enumeration.txt Yep! The idea is that new drivers for discrete (and sometimes even on-SoC) components should be platform-agnostic (means no strict OF / ACPI dependencies), so anybody can prototype devices on either of the platforms. As a matter of fact IIO subsystem is leading in this by cleaning up most of the drivers towards that goal. OF/ACPI explicit dependency makes sense when we 100+% sure that IP in question won't ever appear on the other type of platform (which I believe is very rare nowadays for most of the components).
Hi Thomas, On Tue, Feb 1, 2022 at 5:16 PM Thomas Zimmermann <tzimmermann@suse.de> wrote: > Am 31.01.22 um 11:18 schrieb Javier Martinez Canillas: > > Another thing that's missing is a DRM_MODE_CONNECTOR_I2C, because I used for > > now a DRM_MODE_CONNECTOR_Unknown. > > That might have implications on userspace. Maybe ask around. (Not that > we actually run userspace on the device). Looking at the list of connector types (and wondering if we're gonna need more when converting existing fbdev drivers to drm drivers), there seem to be two different families of connector types, for 1. transports between CRTC and display (e.g. VGA, DVID, HDMI), 2. transports between CPU and CRTC (e.g. SPI, possibly USB, and the proposed I2C)? Gr{oetje,eeting}s, Geert -- Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that. -- Linus Torvalds
On Tue, Feb 1, 2022 at 6:01 PM Geert Uytterhoeven <geert@linux-m68k.org> wrote: > > Hi Thomas, > > On Tue, Feb 1, 2022 at 5:16 PM Thomas Zimmermann <tzimmermann@suse.de> wrote: > > Am 31.01.22 um 11:18 schrieb Javier Martinez Canillas: > > > Another thing that's missing is a DRM_MODE_CONNECTOR_I2C, because I used for > > > now a DRM_MODE_CONNECTOR_Unknown. > > > > That might have implications on userspace. Maybe ask around. (Not that > > we actually run userspace on the device). > > Looking at the list of connector types (and wondering if we're gonna > need more when converting existing fbdev drivers to drm drivers), > there seem to be two different families of connector types, for > 1. transports between CRTC and display (e.g. VGA, DVID, HDMI), > 2. transports between CPU and CRTC (e.g. SPI, possibly USB, and > the proposed I2C)? I was trying to argue for a panel connector type and stop doing all these internal things because like you point out, it kinda doesn't, only the external connectors are relevant to users. But it didn't stick anywhere yet, we keep adding more connector types and then having to update userspace, which should map these all to "it's the panel" or something like that. But also since various technicolor abbreviations are about as useful to end-users as "unknown" it really doesn't matter, so I'm happy to let this bikeshed get a tad fancier every year :-) -Daniel > Gr{oetje,eeting}s, > > Geert > > -- > Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org > > In personal conversations with technical people, I call myself a hacker. But > when I'm talking to journalists I just say "programmer" or something like that. > -- Linus Torvalds
Hi Am 01.02.22 um 18:00 schrieb Geert Uytterhoeven: > Hi Thomas, > > On Tue, Feb 1, 2022 at 5:16 PM Thomas Zimmermann <tzimmermann@suse.de> wrote: >> Am 31.01.22 um 11:18 schrieb Javier Martinez Canillas: >>> Another thing that's missing is a DRM_MODE_CONNECTOR_I2C, because I used for >>> now a DRM_MODE_CONNECTOR_Unknown. >> >> That might have implications on userspace. Maybe ask around. (Not that >> we actually run userspace on the device). > > Looking at the list of connector types (and wondering if we're gonna > need more when converting existing fbdev drivers to drm drivers), > there seem to be two different families of connector types, for > 1. transports between CRTC and display (e.g. VGA, DVID, HDMI), > 2. transports between CPU and CRTC (e.g. SPI, possibly USB, and > the proposed I2C)? I think I had a similar discussion when we merged the gud driver. gud is a driver for a RasPi-based usb display adapter. My point was that USB is just an internal transport bus, like PCI. But that wasn't convincing. So now we have USB and other busses as connector types. My preference would be to use a panel type as Daniel suggested; and maybe 'Unknown' for a few special cases. Best regards Thomas > > Gr{oetje,eeting}s, > > Geert > > -- > Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org > > In personal conversations with technical people, I call myself a hacker. But > when I'm talking to journalists I just say "programmer" or something like that. > -- Linus Torvalds
Hi Daniel, On Tue, Feb 01, 2022 at 06:06:33PM +0100, Daniel Vetter wrote: > On Tue, Feb 1, 2022 at 6:01 PM Geert Uytterhoeven <geert@linux-m68k.org> wrote: > > > > Hi Thomas, > > > > On Tue, Feb 1, 2022 at 5:16 PM Thomas Zimmermann <tzimmermann@suse.de> wrote: > > > Am 31.01.22 um 11:18 schrieb Javier Martinez Canillas: > > > > Another thing that's missing is a DRM_MODE_CONNECTOR_I2C, because I used for > > > > now a DRM_MODE_CONNECTOR_Unknown. > > > > > > That might have implications on userspace. Maybe ask around. (Not that > > > we actually run userspace on the device). > > > > Looking at the list of connector types (and wondering if we're gonna > > need more when converting existing fbdev drivers to drm drivers), > > there seem to be two different families of connector types, for > > 1. transports between CRTC and display (e.g. VGA, DVID, HDMI), > > 2. transports between CPU and CRTC (e.g. SPI, possibly USB, and > > the proposed I2C)? > > I was trying to argue for a panel connector type and stop doing all > these internal things because like you point out, it kinda doesn't, > only the external connectors are relevant to users. But it didn't > stick anywhere yet, we keep adding more connector types and then > having to update userspace, which should map these all to "it's the > panel" or something like that. But also since various technicolor > abbreviations are about as useful to end-users as "unknown" it really > doesn't matter, so I'm happy to let this bikeshed get a tad fancier > every year :-) We discussed DRM_MODE_CONNECTOR_PANEL or some sort - but I recall we ended up with worrying about breaking userspace. See https://lore.kernel.org/dri-devel/?q=DRM_MODE_CONNECTOR_PANEL For this kind of change I chicken out due to lack of understanding of the userspace implications. Typing the patch is simple but taking the correct decision not so. The discussion popped up when we made it mandatory to specify a connector so we could better match up stuff between display drivers/bridges and panel drivers. Sam
diff --git a/MAINTAINERS b/MAINTAINERS index ea3e6c914384..16e614606ac1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7372,9 +7372,11 @@ F: Documentation/fault-injection/ F: lib/fault-inject.c FBTFT Framebuffer drivers +M: Andy Shevchenko <andy@kernel.org> L: dri-devel@lists.freedesktop.org L: linux-fbdev@vger.kernel.org -S: Orphan +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-fbtft.git F: drivers/staging/fbtft/ FC0011 TUNER DRIVER
Let's maintain occasional fixes to the fbtft driver. Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> --- MAINTAINERS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)