Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/solomon: Add SSD130x OLED displays SPI support

The ssd130x driver only provides the core support for these devices but it
does not have any bus transport logic. Add a driver to interface over SPI.

There is a difference in the communication protocol when using 4-wire SPI
instead of I2C. For the latter, a control byte that contains a D/C# field
has to be sent. This field tells the controller whether the data has to be
written to the command register or to the graphics display data memory.

But for 4-wire SPI that control byte is not used, instead a real D/C# line
must be pulled HIGH for commands data and LOW for graphics display data.

For this reason the standard SPI regmap can't be used and a custom .write
bus handler is needed.

Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
Acked-by: Mark Brown <broonie@kernel.org>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Link: https://patchwork.freedesktop.org/patch/msgid/20220419214824.335075-6-javierm@redhat.com

+188
+9
drivers/gpu/drm/solomon/Kconfig
··· 20 20 I2C bus. 21 21 22 22 If M is selected the module will be called ssd130x-i2c. 23 + 24 + config DRM_SSD130X_SPI 25 + tristate "DRM support for Solomon SSD130X OLED displays (SPI bus)" 26 + depends on DRM_SSD130X && SPI 27 + select REGMAP 28 + help 29 + Say Y here if the SSD130x OLED display is connected via SPI bus. 30 + 31 + If M is selected the module will be called ssd130x-spi.
+1
drivers/gpu/drm/solomon/Makefile
··· 1 1 obj-$(CONFIG_DRM_SSD130X) += ssd130x.o 2 2 obj-$(CONFIG_DRM_SSD130X_I2C) += ssd130x-i2c.o 3 + obj-$(CONFIG_DRM_SSD130X_SPI) += ssd130x-spi.o
+178
drivers/gpu/drm/solomon/ssd130x-spi.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * DRM driver for Solomon SSD130X OLED displays (SPI bus) 4 + * 5 + * Copyright 2022 Red Hat Inc. 6 + * Authors: Javier Martinez Canillas <javierm@redhat.com> 7 + */ 8 + #include <linux/spi/spi.h> 9 + #include <linux/module.h> 10 + 11 + #include "ssd130x.h" 12 + 13 + #define DRIVER_NAME "ssd130x-spi" 14 + #define DRIVER_DESC "DRM driver for Solomon SSD130X OLED displays (SPI)" 15 + 16 + struct ssd130x_spi_transport { 17 + struct spi_device *spi; 18 + struct gpio_desc *dc; 19 + }; 20 + 21 + static const struct regmap_config ssd130x_spi_regmap_config = { 22 + .reg_bits = 8, 23 + .val_bits = 8, 24 + }; 25 + 26 + /* 27 + * The regmap bus .write handler, it is just a wrapper around spi_write() 28 + * but toggling the Data/Command control pin (D/C#). Since for 4-wire SPI 29 + * a D/C# pin is used, in contrast with I2C where a control byte is sent, 30 + * prior to every data byte, that contains a bit with the D/C# value. 31 + * 32 + * These control bytes are considered registers by the ssd130x core driver 33 + * and can be used by the ssd130x SPI driver to determine if the data sent 34 + * is for a command register or for the Graphic Display Data RAM (GDDRAM). 35 + */ 36 + static int ssd130x_spi_write(void *context, const void *data, size_t count) 37 + { 38 + struct ssd130x_spi_transport *t = context; 39 + struct spi_device *spi = t->spi; 40 + const u8 *reg = data; 41 + 42 + if (*reg == SSD130X_COMMAND) 43 + gpiod_set_value_cansleep(t->dc, 0); 44 + 45 + if (*reg == SSD130X_DATA) 46 + gpiod_set_value_cansleep(t->dc, 1); 47 + 48 + /* Remove control byte since is not used in a 4-wire SPI interface */ 49 + return spi_write(spi, reg + 1, count - 1); 50 + } 51 + 52 + /* The ssd130x driver does not read registers but regmap expects a .read */ 53 + static int ssd130x_spi_read(void *context, const void *reg, size_t reg_size, 54 + void *val, size_t val_size) 55 + { 56 + return -EOPNOTSUPP; 57 + } 58 + 59 + /* 60 + * A custom bus is needed due the special write that toggles a D/C# pin, 61 + * another option could be to just have a .reg_write() callback but that 62 + * will prevent to do data writes in bulk. 63 + * 64 + * Once the regmap API is extended to support defining a bulk write handler 65 + * in the struct regmap_config, this can be simplified and the bus dropped. 66 + */ 67 + static struct regmap_bus regmap_ssd130x_spi_bus = { 68 + .write = ssd130x_spi_write, 69 + .read = ssd130x_spi_read, 70 + }; 71 + 72 + static int ssd130x_spi_probe(struct spi_device *spi) 73 + { 74 + struct ssd130x_spi_transport *t; 75 + struct ssd130x_device *ssd130x; 76 + struct regmap *regmap; 77 + struct gpio_desc *dc; 78 + struct device *dev = &spi->dev; 79 + 80 + dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW); 81 + if (IS_ERR(dc)) 82 + return dev_err_probe(dev, PTR_ERR(dc), 83 + "Failed to get dc gpio\n"); 84 + 85 + t = devm_kzalloc(dev, sizeof(*t), GFP_KERNEL); 86 + if (!t) 87 + return dev_err_probe(dev, -ENOMEM, 88 + "Failed to allocate SPI transport data\n"); 89 + 90 + t->spi = spi; 91 + t->dc = dc; 92 + 93 + regmap = devm_regmap_init(dev, &regmap_ssd130x_spi_bus, t, 94 + &ssd130x_spi_regmap_config); 95 + if (IS_ERR(regmap)) 96 + return PTR_ERR(regmap); 97 + 98 + ssd130x = ssd130x_probe(dev, regmap); 99 + if (IS_ERR(ssd130x)) 100 + return PTR_ERR(ssd130x); 101 + 102 + spi_set_drvdata(spi, ssd130x); 103 + 104 + return 0; 105 + } 106 + 107 + static void ssd130x_spi_remove(struct spi_device *spi) 108 + { 109 + struct ssd130x_device *ssd130x = spi_get_drvdata(spi); 110 + 111 + ssd130x_remove(ssd130x); 112 + } 113 + 114 + static void ssd130x_spi_shutdown(struct spi_device *spi) 115 + { 116 + struct ssd130x_device *ssd130x = spi_get_drvdata(spi); 117 + 118 + ssd130x_shutdown(ssd130x); 119 + } 120 + 121 + static const struct of_device_id ssd130x_of_match[] = { 122 + { 123 + .compatible = "sinowealth,sh1106", 124 + .data = &ssd130x_variants[SH1106_ID], 125 + }, 126 + { 127 + .compatible = "solomon,ssd1305", 128 + .data = &ssd130x_variants[SSD1305_ID], 129 + }, 130 + { 131 + .compatible = "solomon,ssd1306", 132 + .data = &ssd130x_variants[SSD1306_ID], 133 + }, 134 + { 135 + .compatible = "solomon,ssd1307", 136 + .data = &ssd130x_variants[SSD1307_ID], 137 + }, 138 + { 139 + .compatible = "solomon,ssd1309", 140 + .data = &ssd130x_variants[SSD1309_ID], 141 + }, 142 + { /* sentinel */ } 143 + }; 144 + MODULE_DEVICE_TABLE(of, ssd130x_of_match); 145 + 146 + /* 147 + * The SPI core always reports a MODALIAS uevent of the form "spi:<dev>", even 148 + * if the device was registered via OF. This means that the module will not be 149 + * auto loaded, unless it contains an alias that matches the MODALIAS reported. 150 + * 151 + * To workaround this issue, add a SPI device ID table. Even when this should 152 + * not be needed for this driver to match the registered SPI devices. 153 + */ 154 + static const struct spi_device_id ssd130x_spi_table[] = { 155 + { "sh1106", SH1106_ID }, 156 + { "ssd1305", SSD1305_ID }, 157 + { "ssd1306", SSD1306_ID }, 158 + { "ssd1307", SSD1307_ID }, 159 + { "ssd1309", SSD1309_ID }, 160 + { /* sentinel */ } 161 + }; 162 + MODULE_DEVICE_TABLE(spi, ssd130x_spi_table); 163 + 164 + static struct spi_driver ssd130x_spi_driver = { 165 + .driver = { 166 + .name = DRIVER_NAME, 167 + .of_match_table = ssd130x_of_match, 168 + }, 169 + .probe = ssd130x_spi_probe, 170 + .remove = ssd130x_spi_remove, 171 + .shutdown = ssd130x_spi_shutdown, 172 + }; 173 + module_spi_driver(ssd130x_spi_driver); 174 + 175 + MODULE_DESCRIPTION(DRIVER_DESC); 176 + MODULE_AUTHOR("Javier Martinez Canillas <javierm@redhat.com>"); 177 + MODULE_LICENSE("GPL"); 178 + MODULE_IMPORT_NS(DRM_SSD130X);