From: patrick Date: Mon, 30 Jul 2018 08:14:45 +0000 (+0000) Subject: Add ssdfb(4), a driver for the SSD1309 controller that drives an X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=dab1a98445f44b8b2534ed2c2b8521cd1aa8784e;p=openbsd Add ssdfb(4), a driver for the SSD1309 controller that drives an 128x64 OLED display. With the typical 8x16 font we get 4 rows with 16 characters each on it. The controller can be driven using I2C, 3-wire and 4-wire SPI. This commit includes support for the 4-wire protocol. ok deraadt@ --- diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index d93417f4cce..517df52fa0a 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.78 2018/07/26 10:59:07 patrick Exp $ +# $OpenBSD: GENERIC,v 1.79 2018/07/30 08:14:45 patrick Exp $ # # GENERIC machine description file # @@ -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..e697def5b8c 100644 --- a/sys/arch/armv7/conf/GENERIC +++ b/sys/arch/armv7/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.114 2018/07/26 10:59:07 patrick Exp $ +# $OpenBSD: GENERIC,v 1.115 2018/07/30 08:14:45 patrick Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -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..6bbf3c852f7 100644 --- a/sys/dev/fdt/files.fdt +++ b/sys/dev/fdt/files.fdt @@ -1,4 +1,4 @@ -# $OpenBSD: files.fdt,v 1.66 2018/07/26 10:59:07 patrick Exp $ +# $OpenBSD: files.fdt,v 1.67 2018/07/30 08:14:45 patrick Exp $ # # Config file and device description for machine-independent FDT code. # Included by ports that need it. @@ -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..69ee603cf9b --- /dev/null +++ b/sys/dev/fdt/ssdfb.c @@ -0,0 +1,441 @@ +/* $OpenBSD: ssdfb.c,v 1.1 2018/07/30 08:14:45 patrick Exp $ */ +/* + * Copyright (c) 2018 Patrick Wildt + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#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)); +}