> Date: Fri, 27 Jul 2018 17:10:32 +0200
> From: Patrick Wildt <[email protected]>
> 
> Hi,
> 
> I have an SPI-connected SSD1309 OLED display (128x64) that I would like
> to support.  At some point I'd like to attach a graphical program to it,
> so that a userland tool can draw graphics (or maybe something like X11).
> 
> I don't know the display subsystem very well, so this diff attempts to
> attach wsdisplay(4) to ssdfb(4).  Since it's SPI connected and there is
> no memory mapped framebuffer I have to manually update the display.  One
> attempt I tried was hooking up the putchar/col/row functions, but I have
> simplified it into a 100ms periodic timeout.  This seems usable enough.
> Is there a better approach?

You might want to look at what the udl(4) driver does.  The kernel
driver lives in sys/dev/usb/udl.c and implements a "damage" ioctl that
updates a region of the actual framebuffer from the virtual
framebuffer that lives in physical memory.  X supposedly keeps track
of the "damage" and the xf86-video-wsudl driver uses that to transfer
the appropriate pixels.  Maybe this mechanism could be generalized by
implementing a wscons ioctl?

Your approach is a bit wasteful since you're doing work even when the
display is completely static.  Another problem is that your timeout
might run in the middle of an update of the virtual framebuffer.

Not sure how this would work with graphics stuff that doesn't go
through X though.  Something that just draws into a mmap'ed virtual
framebuffer and issues ioctls should work though.

> With the integrated 8x16 font I do get 4 rows with 16 characters per
> row.  This is rather hard to use, a 5x8 font would make some more room.
> But at the moment I don't intend to change that.
> 
> Feedback?
> 
> Patrick
> 
> diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC
> index d93417f4cce..144e54bd1c8 100644
> --- a/sys/arch/arm64/conf/GENERIC
> +++ b/sys/arch/arm64/conf/GENERIC
> @@ -112,6 +112,8 @@ sdmmc*            at imxesdhc?
>  imxpd*               at fdt?
>  imxdwusb*    at fdt?
>  imxspi*              at fdt?
> +ssdfb*               at spi?
> +wsdisplay*   at ssdfb?
>  
>  # Raspberry Pi 3
>  bcmaux*              at fdt?
> diff --git a/sys/arch/armv7/conf/GENERIC b/sys/arch/armv7/conf/GENERIC
> index 6f43b436c54..7a8cea0c4d0 100644
> --- a/sys/arch/armv7/conf/GENERIC
> +++ b/sys/arch/armv7/conf/GENERIC
> @@ -60,6 +60,8 @@ imxehci*    at fdt?                 # EHCI
>  usb*         at imxehci?
>  imxrtc*              at fdt?                 # SNVS RTC
>  imxspi*              at fdt?
> +ssdfb*               at spi?
> +wsdisplay*   at ssdfb?
>  
>  # OMAP3xxx/OMAP4xxx SoC
>  omap0                at mainbus?
> diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt
> index f65d9c3a5b8..52851b404d9 100644
> --- a/sys/dev/fdt/files.fdt
> +++ b/sys/dev/fdt/files.fdt
> @@ -260,3 +260,7 @@ file      dev/fdt/ccp_fdt.c               ccp_fdt
>  
>  attach       com at fdt with com_fdt
>  file dev/fdt/com_fdt.c               com_fdt
> +
> +device       ssdfb: wsemuldisplaydev, rasops1
> +attach       ssdfb at spi
> +file dev/fdt/ssdfb.c                 ssdfb
> diff --git a/sys/dev/fdt/ssdfb.c b/sys/dev/fdt/ssdfb.c
> new file mode 100644
> index 00000000000..ca136e0a28f
> --- /dev/null
> +++ b/sys/dev/fdt/ssdfb.c
> @@ -0,0 +1,441 @@
> +/* $OpenBSD$ */
> +/*
> + * Copyright (c) 2018 Patrick Wildt <[email protected]>
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <sys/param.h>
> +#include <sys/systm.h>
> +#include <sys/kernel.h>
> +#include <sys/device.h>
> +#include <sys/malloc.h>
> +#include <sys/stdint.h>
> +#include <sys/timeout.h>
> +#include <sys/task.h>
> +
> +#include <dev/spi/spivar.h>
> +
> +#include <dev/ofw/openfirm.h>
> +#include <dev/ofw/ofw_gpio.h>
> +#include <dev/ofw/ofw_pinctrl.h>
> +
> +#include <dev/wscons/wsconsio.h>
> +#include <dev/wscons/wsdisplayvar.h>
> +#include <dev/rasops/rasops.h>
> +
> +#define SSDFB_SET_LOWER_COLUMN_START_ADRESS  0x00
> +#define SSDFB_SET_HIGHER_COLUMN_START_ADRESS 0x10
> +#define SSDFB_SET_MEMORY_ADRESSING_MODE              0x20
> +#define SSDFB_SET_START_LINE                 0x40
> +#define SSDFB_SET_CONTRAST_CONTROL           0x81
> +#define SSDFB_SET_COLUMN_DIRECTION_REVERSE   0xa1
> +#define SSDFB_SET_MULTIPLEX_RATIO            0xa8
> +#define SSDFB_SET_COM_OUTPUT_DIRECTION               0xc0
> +#define SSDFB_ENTIRE_DISPLAY_ON                      0xa4
> +#define SSDFB_SET_DISPLAY_MODE_NORMAL                0xa6
> +#define SSDFB_SET_DISPLAY_MODE_INVERS                0xa7
> +#define SSDFB_SET_DISPLAY_OFF                        0xae
> +#define SSDFB_SET_DISPLAY_ON                 0xaf
> +#define SSDFB_SET_DISPLAY_OFFSET             0xd3
> +#define SSDFB_SET_DISPLAY_CLOCK_DIVIDE_RATIO 0xd5
> +#define SSDFB_SET_PRE_CHARGE_PERIOD          0xd9
> +#define SSDFB_SET_COM_PINS_HARD_CONF         0xda
> +#define SSDFB_SET_VCOM_DESELECT_LEVEL                0xdb
> +#define SSDFB_SET_PAGE_START_ADRESS          0xb0
> +
> +#define SSDFB_WIDTH  128
> +#define SSDFB_HEIGHT 64
> +
> +struct ssdfb_softc {
> +     struct device            sc_dev;
> +     spi_tag_t                sc_tag;
> +     int                      sc_node;
> +
> +     struct spi_config        sc_conf;
> +     uint32_t                *sc_gpio;
> +     size_t                   sc_gpiolen;
> +     int                      sc_cd;
> +
> +     uint8_t                 *sc_fb;
> +     size_t                   sc_fbsize;
> +     struct rasops_info       sc_rinfo;
> +
> +     struct task              sc_task;
> +     struct timeout           sc_to;
> +};
> +
> +int   ssdfb_match(struct device *, void *, void *);
> +void  ssdfb_attach(struct device *, struct device *, void *);
> +int   ssdfb_detach(struct device *, int);
> +
> +void  ssdfb_write_command(struct ssdfb_softc *, char *, size_t);
> +void  ssdfb_write_data(struct ssdfb_softc *, char *, size_t);
> +
> +void  ssdfb_init(struct ssdfb_softc *);
> +void  ssdfb_update(void *);
> +void  ssdfb_timeout(void *);
> +
> +int   ssdfb_ioctl(void *, u_long, caddr_t, int, struct proc *);
> +paddr_t       ssdfb_mmap(void *, off_t, int);
> +int   ssdfb_alloc_screen(void *, const struct wsscreen_descr *, void **,
> +         int *, int *, long *);
> +void  ssdfb_free_screen(void *, void *);
> +int   ssdfb_show_screen(void *, void *, int, void (*cb) (void *, int, int),
> +         void *);
> +int   ssdfb_list_font(void *, struct wsdisplay_font *);
> +int   ssdfb_load_font(void *, void *, struct wsdisplay_font *);
> +
> +struct cfattach ssdfb_ca = {
> +     sizeof(struct ssdfb_softc),
> +     ssdfb_match,
> +     ssdfb_attach,
> +     ssdfb_detach,
> +};
> +
> +struct cfdriver ssdfb_cd = {
> +     NULL, "ssdfb", DV_DULL
> +};
> +
> +struct wsscreen_descr ssdfb_std_descr = { "std" };
> +struct wsdisplay_charcell ssdfb_bs[SSDFB_WIDTH * SSDFB_HEIGHT];
> +
> +const struct wsscreen_descr *ssdfb_descrs[] = {
> +     &ssdfb_std_descr
> +};
> +
> +const struct wsscreen_list ssdfb_screen_list = {
> +     nitems(ssdfb_descrs), ssdfb_descrs
> +};
> +
> +struct wsdisplay_accessops ssdfb_accessops = {
> +     .ioctl = ssdfb_ioctl,
> +     .mmap = ssdfb_mmap,
> +     .alloc_screen = ssdfb_alloc_screen,
> +     .free_screen = ssdfb_free_screen,
> +     .show_screen = ssdfb_show_screen,
> +     .load_font = ssdfb_load_font,
> +     .list_font = ssdfb_list_font
> +};
> +
> +int
> +ssdfb_match(struct device *parent, void *match, void *aux)
> +{
> +     struct spi_attach_args *sa = aux;
> +
> +     if (strcmp(sa->sa_name, "solomon,ssd1309fb-spi") == 0)
> +             return 1;
> +
> +     return 0;
> +}
> +
> +void
> +ssdfb_attach(struct device *parent, struct device *self, void *aux)
> +{
> +     struct ssdfb_softc *sc = (struct ssdfb_softc *)self;
> +     struct spi_attach_args *sa = aux;
> +     struct wsemuldisplaydev_attach_args aa;
> +     struct rasops_info *ri;
> +     size_t len;
> +
> +     sc->sc_tag = sa->sa_tag;
> +     sc->sc_node = *(int *)sa->sa_cookie;
> +
> +     pinctrl_byname(sc->sc_node, "default");
> +
> +     sc->sc_conf.sc_bpw = 8;
> +     sc->sc_conf.sc_freq = 1000 * 1000;
> +     sc->sc_conf.sc_cs = OF_getpropint(sc->sc_node, "reg", 0);
> +     if (OF_getproplen(sc->sc_node, "spi-cpol") == 0)
> +             sc->sc_conf.sc_flags |= SPI_CONFIG_CPOL;
> +     if (OF_getproplen(sc->sc_node, "spi-cpha") == 0)
> +             sc->sc_conf.sc_flags |= SPI_CONFIG_CPHA;
> +     if (OF_getproplen(sc->sc_node, "spi-cs-high") == 0)
> +             sc->sc_conf.sc_flags |= SPI_CONFIG_CS_HIGH;
> +
> +     len = OF_getproplen(sc->sc_node, "reset-gpio");
> +     if (len > 0) {
> +             sc->sc_gpio = malloc(len, M_DEVBUF, M_WAITOK);
> +             OF_getpropintarray(sc->sc_node, "reset-gpio",
> +                 sc->sc_gpio, len);
> +             gpio_controller_config_pin(sc->sc_gpio, GPIO_CONFIG_OUTPUT);
> +             gpio_controller_set_pin(sc->sc_gpio, 1);
> +             delay(100 * 1000);
> +             gpio_controller_set_pin(sc->sc_gpio, 0);
> +             delay(1000 * 1000);
> +             free(sc->sc_gpio, M_DEVBUF, len);
> +     }
> +
> +     len = OF_getproplen(sc->sc_node, "cd-gpio");
> +     if (len <= 0)
> +             return;
> +
> +     sc->sc_gpio = malloc(len, M_DEVBUF, M_WAITOK);
> +     OF_getpropintarray(sc->sc_node, "cd-gpio", sc->sc_gpio, len);
> +     sc->sc_gpiolen = len;
> +     gpio_controller_config_pin(sc->sc_gpio, GPIO_CONFIG_OUTPUT);
> +     gpio_controller_set_pin(sc->sc_gpio, 0);
> +
> +     sc->sc_fbsize = (SSDFB_WIDTH * SSDFB_HEIGHT) / 8;
> +     sc->sc_fb = malloc(sc->sc_fbsize, M_DEVBUF, M_WAITOK | M_ZERO);
> +
> +     ri = &sc->sc_rinfo;
> +     ri->ri_bits = malloc(sc->sc_fbsize, M_DEVBUF, M_WAITOK | M_ZERO);
> +     ri->ri_bs = ssdfb_bs;
> +     ri->ri_flg = RI_CLEAR | RI_VCONS;
> +     ri->ri_depth = 1;
> +     ri->ri_width = SSDFB_WIDTH;
> +     ri->ri_height = SSDFB_HEIGHT;
> +     ri->ri_stride = ri->ri_width * ri->ri_depth / 8;
> +
> +     rasops_init(ri, SSDFB_HEIGHT, SSDFB_WIDTH);
> +     ssdfb_std_descr.ncols = ri->ri_cols;
> +     ssdfb_std_descr.nrows = ri->ri_rows;
> +     ssdfb_std_descr.textops = &ri->ri_ops;
> +     ssdfb_std_descr.fontwidth = ri->ri_font->fontwidth;
> +     ssdfb_std_descr.fontheight = ri->ri_font->fontheight;
> +     ssdfb_std_descr.capabilities = ri->ri_caps;
> +
> +     task_set(&sc->sc_task, ssdfb_update, sc);
> +     timeout_set(&sc->sc_to, ssdfb_timeout, sc);
> +
> +     printf(": %dx%d, %dbpp\n", ri->ri_width, ri->ri_height, ri->ri_depth);
> +
> +     memset(&aa, 0, sizeof(aa));
> +     aa.console = 0;
> +     aa.scrdata = &ssdfb_screen_list;
> +     aa.accessops = &ssdfb_accessops;
> +     aa.accesscookie = sc;
> +     aa.defaultscreens = 0;
> +
> +     config_found_sm(self, &aa, wsemuldisplaydevprint,
> +         wsemuldisplaydevsubmatch);
> +     ssdfb_init(sc);
> +}
> +
> +int
> +ssdfb_detach(struct device *self, int flags)
> +{
> +     struct ssdfb_softc *sc = (struct ssdfb_softc *)self;
> +     struct rasops_info *ri = &sc->sc_rinfo;
> +     free(ri->ri_bits, M_DEVBUF, sc->sc_fbsize);
> +     free(sc->sc_fb, M_DEVBUF, sc->sc_fbsize);
> +     free(sc->sc_gpio, M_DEVBUF, sc->sc_gpiolen);
> +     return 0;
> +}
> +
> +void
> +ssdfb_init(struct ssdfb_softc *sc)
> +{
> +     uint8_t reg[2];
> +
> +     reg[0] = SSDFB_SET_DISPLAY_OFF;
> +     ssdfb_write_command(sc, reg, 1);
> +
> +     reg[0] = SSDFB_SET_MEMORY_ADRESSING_MODE;
> +     reg[1] = 0x10; /* Page Adressing Mode */
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_SET_PAGE_START_ADRESS;
> +     ssdfb_write_command(sc, reg, 1);
> +     reg[0] = SSDFB_SET_DISPLAY_CLOCK_DIVIDE_RATIO;
> +     reg[1] = 0xa0;
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_SET_MULTIPLEX_RATIO;
> +     reg[1] = 0x3f;
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_SET_DISPLAY_OFFSET;
> +     reg[1] = 0x00;
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_SET_START_LINE | 0x00;
> +     ssdfb_write_command(sc, reg, 1);
> +     reg[0] = SSDFB_SET_COLUMN_DIRECTION_REVERSE;
> +     ssdfb_write_command(sc, reg, 1);
> +     reg[0] = SSDFB_SET_COM_OUTPUT_DIRECTION | 0x08;
> +     ssdfb_write_command(sc, reg, 1);
> +     reg[0] = SSDFB_SET_COM_PINS_HARD_CONF;
> +     reg[1] = 0x12;
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_SET_CONTRAST_CONTROL;
> +     reg[1] = 223;
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_SET_PRE_CHARGE_PERIOD;
> +     reg[1] = 0x82;
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_SET_VCOM_DESELECT_LEVEL;
> +     reg[1] = 0x34;
> +     ssdfb_write_command(sc, reg, 2);
> +     reg[0] = SSDFB_ENTIRE_DISPLAY_ON;
> +     ssdfb_write_command(sc, reg, 1);
> +     reg[0] = SSDFB_SET_DISPLAY_MODE_NORMAL;
> +     ssdfb_write_command(sc, reg, 1);
> +
> +     ssdfb_update(sc);
> +
> +     reg[0] = SSDFB_SET_DISPLAY_ON;
> +     ssdfb_write_command(sc, reg, 1);
> +}
> +
> +void
> +ssdfb_update(void *v)
> +{
> +     struct ssdfb_softc *sc = v;
> +     struct rasops_info *ri = &sc->sc_rinfo;
> +     uint8_t *bit, val;
> +     uint32_t off;
> +     int i, j, k;
> +
> +     memset(sc->sc_fb, 0, sc->sc_fbsize);
> +
> +     for (i = 0; i < ri->ri_height; i += 8) {
> +             for (j = 0; j < ri->ri_width; j++) {
> +                     bit = &sc->sc_fb[(i / 8) * ri->ri_width + j];
> +                     for (k = 0; k < 8; k++) {
> +                             off = ri->ri_stride * (i + k) + j / 8;
> +                             val = *(ri->ri_bits + off);
> +                             val &= (1 << (8 - (j % 8)));
> +                             *bit |= !!val << k;
> +                     }
> +             }
> +     }
> +
> +     ssdfb_write_data(sc, sc->sc_fb, sc->sc_fbsize);
> +     timeout_add_msec(&sc->sc_to, 100);
> +}
> +
> +void
> +ssdfb_timeout(void *v)
> +{
> +     struct ssdfb_softc *sc = v;
> +     task_add(systq, &sc->sc_task);
> +}
> +
> +void
> +ssdfb_write_command(struct ssdfb_softc *sc, char *buf, size_t len)
> +{
> +     if (sc->sc_cd != 0) {
> +             gpio_controller_set_pin(sc->sc_gpio, 0);
> +             sc->sc_cd = 0;
> +             delay(1);
> +     }
> +
> +     spi_acquire_bus(sc->sc_tag, 0);
> +     spi_config(sc->sc_tag, &sc->sc_conf);
> +     if (spi_write(sc->sc_tag, buf, len))
> +             printf("%s: cannot write\n", sc->sc_dev.dv_xname);
> +     spi_release_bus(sc->sc_tag, 0);
> +}
> +
> +void
> +ssdfb_write_data(struct ssdfb_softc *sc, char *buf, size_t len)
> +{
> +     if (sc->sc_cd != 1) {
> +             gpio_controller_set_pin(sc->sc_gpio, 1);
> +             sc->sc_cd = 1;
> +             delay(1);
> +     }
> +
> +     spi_acquire_bus(sc->sc_tag, 0);
> +     spi_config(sc->sc_tag, &sc->sc_conf);
> +     if (spi_write(sc->sc_tag, buf, len))
> +             printf("%s: cannot write\n", sc->sc_dev.dv_xname);
> +     spi_release_bus(sc->sc_tag, 0);
> +}
> +
> +int
> +ssdfb_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
> +{
> +     struct ssdfb_softc      *sc = v;
> +     struct rasops_info      *ri = &sc->sc_rinfo;
> +     struct wsdisplay_fbinfo *wdf;
> +
> +     switch (cmd) {
> +     case WSDISPLAYIO_GETPARAM:
> +     case WSDISPLAYIO_SETPARAM:
> +             return (-1);
> +     case WSDISPLAYIO_GTYPE:
> +             *(u_int *)data = WSDISPLAY_TYPE_UNKNOWN;
> +             break;
> +     case WSDISPLAYIO_GINFO:
> +             wdf = (struct wsdisplay_fbinfo *)data;
> +             wdf->width = ri->ri_width;
> +             wdf->height = ri->ri_height;
> +             wdf->depth = ri->ri_depth;
> +             wdf->cmsize = 0;        /* color map is unavailable */
> +             break;
> +     case WSDISPLAYIO_LINEBYTES:
> +             *(u_int *)data = ri->ri_stride;
> +             break;
> +     case WSDISPLAYIO_SMODE:
> +             break;
> +     case WSDISPLAYIO_GETSUPPORTEDDEPTH:
> +             *(u_int *)data = WSDISPLAYIO_DEPTH_1;
> +             break;
> +     default:
> +             return (-1);
> +     }
> +
> +     return (0);
> +}
> +
> +paddr_t
> +ssdfb_mmap(void *v, off_t off, int prot)
> +{
> +     return -1;
> +}
> +
> +int
> +ssdfb_alloc_screen(void *v, const struct wsscreen_descr *descr,
> +    void **cookiep, int *curxp, int *curyp, long *attrp)
> +{
> +     struct ssdfb_softc      *sc = v;
> +     struct rasops_info      *ri = &sc->sc_rinfo;
> +
> +     return rasops_alloc_screen(ri, cookiep, curxp, curyp, attrp);
> +}
> +
> +void
> +ssdfb_free_screen(void *v, void *cookie)
> +{
> +     struct ssdfb_softc      *sc = v;
> +     struct rasops_info      *ri = &sc->sc_rinfo;
> +
> +     rasops_free_screen(ri, cookie);
> +}
> +
> +int
> +ssdfb_show_screen(void *v, void *cookie, int waitok,
> +    void (*cb) (void *, int, int), void *cb_arg)
> +{
> +     struct ssdfb_softc      *sc = v;
> +     struct rasops_info      *ri = &sc->sc_rinfo;
> +
> +     return rasops_show_screen(ri, cookie, waitok, cb, cb_arg);
> +}
> +
> +int
> +ssdfb_load_font(void *v, void *cookie, struct wsdisplay_font *font)
> +{
> +     struct ssdfb_softc      *sc = v;
> +     struct rasops_info      *ri = &sc->sc_rinfo;
> +
> +     return (rasops_load_font(ri, cookie, font));
> +}
> +
> +int
> +ssdfb_list_font(void *v, struct wsdisplay_font *font)
> +{
> +     struct ssdfb_softc      *sc = v;
> +     struct rasops_info      *ri = &sc->sc_rinfo;
> +
> +     return (rasops_list_font(ri, font));
> +}
> 
> 

Reply via email to