]>
Commit | Line | Data |
---|---|---|
b66d7af4 MS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * FB driver for the WiseChip Semiconductor Inc. (UG-6028GDEBF02) display | |
4 | * using the SEPS525 (Syncoam) LCD Controller | |
5 | * | |
6 | * Copyright (C) 2020 Xilinx Inc. | |
7 | */ | |
8 | ||
d678a59d | 9 | #include <common.h> |
b66d7af4 MS |
10 | #include <command.h> |
11 | #include <cpu_func.h> | |
12 | #include <dm.h> | |
13 | #include <errno.h> | |
14 | #include <spi.h> | |
15 | #include <video.h> | |
16 | #include <asm/gpio.h> | |
17 | #include <dm/device_compat.h> | |
18 | #include <linux/delay.h> | |
19 | ||
20 | #define WIDTH 160 | |
21 | #define HEIGHT 128 | |
22 | ||
23 | #define SEPS525_INDEX 0x00 | |
24 | #define SEPS525_STATUS_RD 0x01 | |
25 | #define SEPS525_OSC_CTL 0x02 | |
26 | #define SEPS525_IREF 0x80 | |
27 | #define SEPS525_CLOCK_DIV 0x03 | |
28 | #define SEPS525_REDUCE_CURRENT 0x04 | |
29 | #define SEPS525_SOFT_RST 0x05 | |
30 | #define SEPS525_DISP_ONOFF 0x06 | |
31 | #define SEPS525_PRECHARGE_TIME_R 0x08 | |
32 | #define SEPS525_PRECHARGE_TIME_G 0x09 | |
33 | #define SEPS525_PRECHARGE_TIME_B 0x0A | |
34 | #define SEPS525_PRECHARGE_CURRENT_R 0x0B | |
35 | #define SEPS525_PRECHARGE_CURRENT_G 0x0C | |
36 | #define SEPS525_PRECHARGE_CURRENT_B 0x0D | |
37 | #define SEPS525_DRIVING_CURRENT_R 0x10 | |
38 | #define SEPS525_DRIVING_CURRENT_G 0x11 | |
39 | #define SEPS525_DRIVING_CURRENT_B 0x12 | |
40 | #define SEPS525_DISPLAYMODE_SET 0x13 | |
41 | #define SEPS525_RGBIF 0x14 | |
42 | #define SEPS525_RGB_POL 0x15 | |
43 | #define SEPS525_MEMORY_WRITEMODE 0x16 | |
44 | #define SEPS525_MX1_ADDR 0x17 | |
45 | #define SEPS525_MX2_ADDR 0x18 | |
46 | #define SEPS525_MY1_ADDR 0x19 | |
47 | #define SEPS525_MY2_ADDR 0x1A | |
48 | #define SEPS525_MEMORY_ACCESS_POINTER_X 0x20 | |
49 | #define SEPS525_MEMORY_ACCESS_POINTER_Y 0x21 | |
50 | #define SEPS525_DDRAM_DATA_ACCESS_PORT 0x22 | |
51 | #define SEPS525_GRAY_SCALE_TABLE_INDEX 0x50 | |
52 | #define SEPS525_GRAY_SCALE_TABLE_DATA 0x51 | |
53 | #define SEPS525_DUTY 0x28 | |
54 | #define SEPS525_DSL 0x29 | |
55 | #define SEPS525_D1_DDRAM_FAC 0x2E | |
56 | #define SEPS525_D1_DDRAM_FAR 0x2F | |
57 | #define SEPS525_D2_DDRAM_SAC 0x31 | |
58 | #define SEPS525_D2_DDRAM_SAR 0x32 | |
59 | #define SEPS525_SCR1_FX1 0x33 | |
60 | #define SEPS525_SCR1_FX2 0x34 | |
61 | #define SEPS525_SCR1_FY1 0x35 | |
62 | #define SEPS525_SCR1_FY2 0x36 | |
63 | #define SEPS525_SCR2_SX1 0x37 | |
64 | #define SEPS525_SCR2_SX2 0x38 | |
65 | #define SEPS525_SCR2_SY1 0x39 | |
66 | #define SEPS525_SCR2_SY2 0x3A | |
67 | #define SEPS525_SCREEN_SAVER_CONTEROL 0x3B | |
68 | #define SEPS525_SS_SLEEP_TIMER 0x3C | |
69 | #define SEPS525_SCREEN_SAVER_MODE 0x3D | |
70 | #define SEPS525_SS_SCR1_FU 0x3E | |
71 | #define SEPS525_SS_SCR1_MXY 0x3F | |
72 | #define SEPS525_SS_SCR2_FU 0x40 | |
73 | #define SEPS525_SS_SCR2_MXY 0x41 | |
74 | #define SEPS525_MOVING_DIRECTION 0x42 | |
75 | #define SEPS525_SS_SCR2_SX1 0x47 | |
76 | #define SEPS525_SS_SCR2_SX2 0x48 | |
77 | #define SEPS525_SS_SCR2_SY1 0x49 | |
78 | #define SEPS525_SS_SCR2_SY2 0x4A | |
79 | ||
80 | /* SEPS525_DISPLAYMODE_SET */ | |
81 | #define MODE_SWAP_BGR BIT(7) | |
82 | #define MODE_SM BIT(6) | |
83 | #define MODE_RD BIT(5) | |
84 | #define MODE_CD BIT(4) | |
85 | ||
86 | /** | |
87 | * struct seps525_priv - Private structure | |
88 | * @reset_gpio: Reset gpio pin | |
89 | * @dc_gpio: Data/command control gpio pin | |
90 | * @dev: Device uclass for video_ops | |
91 | */ | |
92 | struct seps525_priv { | |
93 | struct gpio_desc reset_gpio; | |
94 | struct gpio_desc dc_gpio; | |
95 | struct udevice *dev; | |
96 | }; | |
97 | ||
98 | static int seps525_spi_write_cmd(struct udevice *dev, u32 reg) | |
99 | { | |
100 | struct seps525_priv *priv = dev_get_priv(dev); | |
101 | u8 buf8 = reg; | |
102 | int ret; | |
103 | ||
104 | ret = dm_gpio_set_value(&priv->dc_gpio, 0); | |
105 | if (ret) { | |
106 | dev_dbg(dev, "Failed to handle dc\n"); | |
107 | return ret; | |
108 | } | |
109 | ||
110 | ret = dm_spi_xfer(dev, 8, &buf8, NULL, SPI_XFER_BEGIN | SPI_XFER_END); | |
111 | if (ret) | |
112 | dev_dbg(dev, "Failed to write command\n"); | |
113 | ||
114 | return ret; | |
115 | } | |
116 | ||
117 | static int seps525_spi_write_data(struct udevice *dev, u32 val) | |
118 | { | |
119 | struct seps525_priv *priv = dev_get_priv(dev); | |
120 | u8 buf8 = val; | |
121 | int ret; | |
122 | ||
123 | ret = dm_gpio_set_value(&priv->dc_gpio, 1); | |
124 | if (ret) { | |
125 | dev_dbg(dev, "Failed to handle dc\n"); | |
126 | return ret; | |
127 | } | |
128 | ||
129 | ret = dm_spi_xfer(dev, 8, &buf8, NULL, SPI_XFER_BEGIN | SPI_XFER_END); | |
130 | if (ret) | |
131 | dev_dbg(dev, "Failed to write data\n"); | |
132 | ||
133 | return ret; | |
134 | } | |
135 | ||
136 | static void seps525_spi_write(struct udevice *dev, u32 reg, u32 val) | |
137 | { | |
138 | (void)seps525_spi_write_cmd(dev, reg); | |
139 | (void)seps525_spi_write_data(dev, val); | |
140 | } | |
141 | ||
142 | static int seps525_display_init(struct udevice *dev) | |
143 | { | |
144 | /* Disable Oscillator Power Down */ | |
145 | seps525_spi_write(dev, SEPS525_REDUCE_CURRENT, 0x03); | |
146 | mdelay(5); | |
147 | ||
148 | /* Set Normal Driving Current */ | |
149 | seps525_spi_write(dev, SEPS525_REDUCE_CURRENT, 0x00); | |
150 | mdelay(5); | |
151 | ||
152 | seps525_spi_write(dev, SEPS525_SCREEN_SAVER_CONTEROL, 0x00); | |
153 | /* Set EXPORT1 Pin at Internal Clock */ | |
154 | seps525_spi_write(dev, SEPS525_OSC_CTL, 0x01); | |
155 | /* Set Clock as 120 Frames/Sec */ | |
156 | seps525_spi_write(dev, SEPS525_CLOCK_DIV, 0x90); | |
157 | /* Set Reference Voltage Controlled by External Resister */ | |
158 | seps525_spi_write(dev, SEPS525_IREF, 0x01); | |
159 | ||
160 | /* precharge time R G B */ | |
161 | seps525_spi_write(dev, SEPS525_PRECHARGE_TIME_R, 0x04); | |
162 | seps525_spi_write(dev, SEPS525_PRECHARGE_TIME_G, 0x05); | |
163 | seps525_spi_write(dev, SEPS525_PRECHARGE_TIME_B, 0x05); | |
164 | ||
165 | /* precharge current R G B (uA) */ | |
166 | seps525_spi_write(dev, SEPS525_PRECHARGE_CURRENT_R, 0x9D); | |
167 | seps525_spi_write(dev, SEPS525_PRECHARGE_CURRENT_G, 0x8C); | |
168 | seps525_spi_write(dev, SEPS525_PRECHARGE_CURRENT_B, 0x57); | |
169 | ||
170 | /* driving current R G B (uA) */ | |
171 | seps525_spi_write(dev, SEPS525_DRIVING_CURRENT_R, 0x56); | |
172 | seps525_spi_write(dev, SEPS525_DRIVING_CURRENT_G, 0x4D); | |
173 | seps525_spi_write(dev, SEPS525_DRIVING_CURRENT_B, 0x46); | |
174 | /* Set Color Sequence */ | |
175 | seps525_spi_write(dev, SEPS525_DISPLAYMODE_SET, 0x00); | |
176 | /* Set MCU Interface Mode */ | |
177 | seps525_spi_write(dev, SEPS525_RGBIF, 0x01); | |
178 | /* Set Memory Write Mode */ | |
179 | seps525_spi_write(dev, SEPS525_MEMORY_WRITEMODE, 0x66); | |
180 | /* 1/128 Duty (0x0F~0x7F) */ | |
181 | seps525_spi_write(dev, SEPS525_DUTY, 0x7F); | |
182 | /* Set Mapping RAM Display Start Line (0x00~0x7F) */ | |
183 | seps525_spi_write(dev, SEPS525_DSL, 0x00); | |
184 | /* Display On (0x00/0x01) */ | |
185 | seps525_spi_write(dev, SEPS525_DISP_ONOFF, 0x01); | |
186 | /* Set All Internal Register Value as Normal Mode */ | |
187 | seps525_spi_write(dev, SEPS525_SOFT_RST, 0x00); | |
188 | /* Set RGB Interface Polarity as Active Low */ | |
189 | seps525_spi_write(dev, SEPS525_RGB_POL, 0x00); | |
190 | ||
191 | /* Enable access for data */ | |
192 | (void)seps525_spi_write_cmd(dev, SEPS525_DDRAM_DATA_ACCESS_PORT); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | static int seps525_spi_startup(struct udevice *dev) | |
198 | { | |
199 | struct seps525_priv *priv = dev_get_priv(dev); | |
200 | int ret; | |
201 | ||
202 | ret = dm_gpio_set_value(&priv->reset_gpio, 1); | |
203 | if (ret) | |
204 | return ret; | |
205 | ||
206 | ret = dm_gpio_set_value(&priv->reset_gpio, 0); | |
207 | if (ret) | |
208 | return ret; | |
209 | ||
210 | ret = dm_spi_claim_bus(dev); | |
211 | if (ret) { | |
212 | dev_err(dev, "Failed to claim SPI bus: %d\n", ret); | |
213 | return ret; | |
214 | } | |
215 | ||
216 | ret = seps525_display_init(dev); | |
217 | if (ret) | |
218 | return ret; | |
219 | ||
220 | dm_spi_release_bus(dev); | |
221 | ||
222 | return 0; | |
223 | } | |
224 | ||
225 | static int seps525_sync(struct udevice *vid) | |
226 | { | |
227 | struct video_priv *uc_priv = dev_get_uclass_priv(vid); | |
228 | struct seps525_priv *priv = dev_get_priv(vid); | |
229 | struct udevice *dev = priv->dev; | |
230 | int i, ret; | |
231 | u8 data1, data2; | |
232 | u8 *start = uc_priv->fb; | |
233 | ||
234 | ret = dm_spi_claim_bus(dev); | |
235 | if (ret) { | |
236 | dev_err(dev, "Failed to claim SPI bus: %d\n", ret); | |
237 | return ret; | |
238 | } | |
239 | ||
240 | /* start position X,Y */ | |
241 | seps525_spi_write(dev, SEPS525_MEMORY_ACCESS_POINTER_X, 0); | |
242 | seps525_spi_write(dev, SEPS525_MEMORY_ACCESS_POINTER_Y, 0); | |
243 | ||
244 | /* Enable access for data */ | |
245 | (void)seps525_spi_write_cmd(dev, SEPS525_DDRAM_DATA_ACCESS_PORT); | |
246 | ||
247 | for (i = 0; i < (uc_priv->xsize * uc_priv->ysize); i++) { | |
248 | data2 = *start++; | |
249 | data1 = *start++; | |
250 | (void)seps525_spi_write_data(dev, data1); | |
251 | (void)seps525_spi_write_data(dev, data2); | |
252 | } | |
253 | ||
254 | dm_spi_release_bus(dev); | |
255 | ||
256 | return 0; | |
257 | } | |
258 | ||
259 | static int seps525_probe(struct udevice *dev) | |
260 | { | |
261 | struct video_priv *uc_priv = dev_get_uclass_priv(dev); | |
262 | struct seps525_priv *priv = dev_get_priv(dev); | |
263 | u32 buswidth; | |
264 | int ret; | |
265 | ||
266 | buswidth = dev_read_u32_default(dev, "buswidth", 0); | |
267 | if (buswidth != 8) { | |
268 | dev_err(dev, "Only 8bit buswidth is supported now"); | |
269 | return -EINVAL; | |
270 | } | |
271 | ||
272 | ret = gpio_request_by_name(dev, "reset-gpios", 0, | |
273 | &priv->reset_gpio, GPIOD_IS_OUT); | |
274 | if (ret) { | |
275 | dev_err(dev, "missing reset GPIO\n"); | |
276 | return ret; | |
277 | } | |
278 | ||
279 | ret = gpio_request_by_name(dev, "dc-gpios", 0, | |
280 | &priv->dc_gpio, GPIOD_IS_OUT); | |
281 | if (ret) { | |
282 | dev_err(dev, "missing dc GPIO\n"); | |
283 | return ret; | |
284 | } | |
285 | ||
286 | uc_priv->bpix = VIDEO_BPP16; | |
287 | uc_priv->xsize = WIDTH; | |
288 | uc_priv->ysize = HEIGHT; | |
289 | uc_priv->rot = 0; | |
290 | ||
291 | priv->dev = dev; | |
292 | ||
293 | ret = seps525_spi_startup(dev); | |
294 | if (ret) | |
295 | return ret; | |
296 | ||
297 | return 0; | |
298 | } | |
299 | ||
300 | static int seps525_bind(struct udevice *dev) | |
301 | { | |
b0db69b4 | 302 | struct video_uc_plat *plat = dev_get_uclass_plat(dev); |
b66d7af4 MS |
303 | |
304 | plat->size = WIDTH * HEIGHT * 16; | |
305 | ||
306 | return 0; | |
307 | } | |
308 | ||
309 | static const struct video_ops seps525_ops = { | |
310 | .video_sync = seps525_sync, | |
311 | }; | |
312 | ||
313 | static const struct udevice_id seps525_ids[] = { | |
314 | { .compatible = "syncoam,seps525" }, | |
315 | { } | |
316 | }; | |
317 | ||
318 | U_BOOT_DRIVER(seps525_video) = { | |
319 | .name = "seps525_video", | |
320 | .id = UCLASS_VIDEO, | |
321 | .of_match = seps525_ids, | |
322 | .ops = &seps525_ops, | |
b0db69b4 | 323 | .plat_auto = sizeof(struct video_uc_plat), |
b66d7af4 MS |
324 | .bind = seps525_bind, |
325 | .probe = seps525_probe, | |
b0db69b4 | 326 | .priv_auto = sizeof(struct seps525_priv), |
b66d7af4 | 327 | }; |