1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * DRM driver for Solomon SSD13xx 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 SSD13xx OLED displays (SPI)" |
15 | |
16 | struct ssd130x_spi_transport { |
17 | struct spi_device *spi; |
18 | struct gpio_desc *dc; |
19 | }; |
20 | |
21 | /* |
22 | * The regmap bus .write handler, it is just a wrapper around spi_write() |
23 | * but toggling the Data/Command control pin (D/C#). Since for 4-wire SPI |
24 | * a D/C# pin is used, in contrast with I2C where a control byte is sent, |
25 | * prior to every data byte, that contains a bit with the D/C# value. |
26 | * |
27 | * These control bytes are considered registers by the ssd130x core driver |
28 | * and can be used by the ssd130x SPI driver to determine if the data sent |
29 | * is for a command register or for the Graphic Display Data RAM (GDDRAM). |
30 | */ |
31 | static int ssd130x_spi_write(void *context, const void *data, size_t count) |
32 | { |
33 | struct ssd130x_spi_transport *t = context; |
34 | struct spi_device *spi = t->spi; |
35 | const u8 *reg = data; |
36 | |
37 | if (*reg == SSD13XX_COMMAND) |
38 | gpiod_set_value_cansleep(desc: t->dc, value: 0); |
39 | |
40 | if (*reg == SSD13XX_DATA) |
41 | gpiod_set_value_cansleep(desc: t->dc, value: 1); |
42 | |
43 | /* Remove control byte since is not used in a 4-wire SPI interface */ |
44 | return spi_write(spi, buf: reg + 1, len: count - 1); |
45 | } |
46 | |
47 | /* The ssd130x driver does not read registers but regmap expects a .read */ |
48 | static int ssd130x_spi_read(void *context, const void *reg, size_t reg_size, |
49 | void *val, size_t val_size) |
50 | { |
51 | return -EOPNOTSUPP; |
52 | } |
53 | |
54 | static const struct regmap_config ssd130x_spi_regmap_config = { |
55 | .reg_bits = 8, |
56 | .val_bits = 8, |
57 | .write = ssd130x_spi_write, |
58 | .read = ssd130x_spi_read, |
59 | .can_multi_write = true, |
60 | }; |
61 | |
62 | static int ssd130x_spi_probe(struct spi_device *spi) |
63 | { |
64 | struct ssd130x_spi_transport *t; |
65 | struct ssd130x_device *ssd130x; |
66 | struct regmap *regmap; |
67 | struct gpio_desc *dc; |
68 | struct device *dev = &spi->dev; |
69 | |
70 | dc = devm_gpiod_get(dev, con_id: "dc" , flags: GPIOD_OUT_LOW); |
71 | if (IS_ERR(ptr: dc)) |
72 | return dev_err_probe(dev, err: PTR_ERR(ptr: dc), |
73 | fmt: "Failed to get dc gpio\n" ); |
74 | |
75 | t = devm_kzalloc(dev, size: sizeof(*t), GFP_KERNEL); |
76 | if (!t) |
77 | return dev_err_probe(dev, err: -ENOMEM, |
78 | fmt: "Failed to allocate SPI transport data\n" ); |
79 | |
80 | t->spi = spi; |
81 | t->dc = dc; |
82 | |
83 | regmap = devm_regmap_init(dev, NULL, t, &ssd130x_spi_regmap_config); |
84 | if (IS_ERR(ptr: regmap)) |
85 | return PTR_ERR(ptr: regmap); |
86 | |
87 | ssd130x = ssd130x_probe(dev, regmap); |
88 | if (IS_ERR(ptr: ssd130x)) |
89 | return PTR_ERR(ptr: ssd130x); |
90 | |
91 | spi_set_drvdata(spi, data: ssd130x); |
92 | |
93 | return 0; |
94 | } |
95 | |
96 | static void ssd130x_spi_remove(struct spi_device *spi) |
97 | { |
98 | struct ssd130x_device *ssd130x = spi_get_drvdata(spi); |
99 | |
100 | ssd130x_remove(ssd130x); |
101 | } |
102 | |
103 | static void ssd130x_spi_shutdown(struct spi_device *spi) |
104 | { |
105 | struct ssd130x_device *ssd130x = spi_get_drvdata(spi); |
106 | |
107 | ssd130x_shutdown(ssd130x); |
108 | } |
109 | |
110 | static const struct of_device_id ssd130x_of_match[] = { |
111 | /* ssd130x family */ |
112 | { |
113 | .compatible = "sinowealth,sh1106" , |
114 | .data = &ssd130x_variants[SH1106_ID], |
115 | }, |
116 | { |
117 | .compatible = "solomon,ssd1305" , |
118 | .data = &ssd130x_variants[SSD1305_ID], |
119 | }, |
120 | { |
121 | .compatible = "solomon,ssd1306" , |
122 | .data = &ssd130x_variants[SSD1306_ID], |
123 | }, |
124 | { |
125 | .compatible = "solomon,ssd1307" , |
126 | .data = &ssd130x_variants[SSD1307_ID], |
127 | }, |
128 | { |
129 | .compatible = "solomon,ssd1309" , |
130 | .data = &ssd130x_variants[SSD1309_ID], |
131 | }, |
132 | /* ssd132x family */ |
133 | { |
134 | .compatible = "solomon,ssd1322" , |
135 | .data = &ssd130x_variants[SSD1322_ID], |
136 | }, |
137 | { |
138 | .compatible = "solomon,ssd1325" , |
139 | .data = &ssd130x_variants[SSD1325_ID], |
140 | }, |
141 | { |
142 | .compatible = "solomon,ssd1327" , |
143 | .data = &ssd130x_variants[SSD1327_ID], |
144 | }, |
145 | /* ssd133x family */ |
146 | { |
147 | .compatible = "solomon,ssd1331" , |
148 | .data = &ssd130x_variants[SSD1331_ID], |
149 | }, |
150 | { /* sentinel */ } |
151 | }; |
152 | MODULE_DEVICE_TABLE(of, ssd130x_of_match); |
153 | |
154 | #if IS_MODULE(CONFIG_DRM_SSD130X_SPI) |
155 | /* |
156 | * The SPI core always reports a MODALIAS uevent of the form "spi:<dev>", even |
157 | * if the device was registered via OF. This means that the module will not be |
158 | * auto loaded, unless it contains an alias that matches the MODALIAS reported. |
159 | * |
160 | * To workaround this issue, add a SPI device ID table. Even when this should |
161 | * not be needed for this driver to match the registered SPI devices. |
162 | */ |
163 | static const struct spi_device_id ssd130x_spi_table[] = { |
164 | /* ssd130x family */ |
165 | { "sh1106" , SH1106_ID }, |
166 | { "ssd1305" , SSD1305_ID }, |
167 | { "ssd1306" , SSD1306_ID }, |
168 | { "ssd1307" , SSD1307_ID }, |
169 | { "ssd1309" , SSD1309_ID }, |
170 | /* ssd132x family */ |
171 | { "ssd1322" , SSD1322_ID }, |
172 | { "ssd1325" , SSD1325_ID }, |
173 | { "ssd1327" , SSD1327_ID }, |
174 | /* ssd133x family */ |
175 | { "ssd1331" , SSD1331_ID }, |
176 | { /* sentinel */ } |
177 | }; |
178 | MODULE_DEVICE_TABLE(spi, ssd130x_spi_table); |
179 | #endif |
180 | |
181 | static struct spi_driver ssd130x_spi_driver = { |
182 | .driver = { |
183 | .name = DRIVER_NAME, |
184 | .of_match_table = ssd130x_of_match, |
185 | }, |
186 | .probe = ssd130x_spi_probe, |
187 | .remove = ssd130x_spi_remove, |
188 | .shutdown = ssd130x_spi_shutdown, |
189 | }; |
190 | module_spi_driver(ssd130x_spi_driver); |
191 | |
192 | MODULE_DESCRIPTION(DRIVER_DESC); |
193 | MODULE_AUTHOR("Javier Martinez Canillas <javierm@redhat.com>" ); |
194 | MODULE_LICENSE("GPL" ); |
195 | MODULE_IMPORT_NS(DRM_SSD130X); |
196 | |