1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * FB driver for the SSD1325 OLED Controller |
4 | */ |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/kernel.h> |
8 | #include <linux/init.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/delay.h> |
11 | |
12 | #include "fbtft.h" |
13 | |
14 | #define DRVNAME "fb_ssd1325" |
15 | |
16 | #define WIDTH 128 |
17 | #define HEIGHT 64 |
18 | #define GAMMA_NUM 1 |
19 | #define GAMMA_LEN 15 |
20 | #define DEFAULT_GAMMA "7 1 1 1 1 2 2 3 3 4 4 5 5 6 6" |
21 | |
22 | /* |
23 | * write_reg() caveat: |
24 | * |
25 | * This doesn't work because D/C has to be LOW for both values: |
26 | * write_reg(par, val1, val2); |
27 | * |
28 | * Do it like this: |
29 | * write_reg(par, val1); |
30 | * write_reg(par, val2); |
31 | */ |
32 | |
33 | /* Init sequence taken from the Adafruit SSD1306 Arduino library */ |
34 | static int init_display(struct fbtft_par *par) |
35 | { |
36 | par->fbtftops.reset(par); |
37 | |
38 | write_reg(par, 0xb3); |
39 | write_reg(par, 0xf0); |
40 | write_reg(par, 0xae); |
41 | write_reg(par, 0xa1); |
42 | write_reg(par, 0x00); |
43 | write_reg(par, 0xa8); |
44 | write_reg(par, 0x3f); |
45 | write_reg(par, 0xa0); |
46 | write_reg(par, 0x45); |
47 | write_reg(par, 0xa2); |
48 | write_reg(par, 0x40); |
49 | write_reg(par, 0x75); |
50 | write_reg(par, 0x00); |
51 | write_reg(par, 0x3f); |
52 | write_reg(par, 0x15); |
53 | write_reg(par, 0x00); |
54 | write_reg(par, 0x7f); |
55 | write_reg(par, 0xa4); |
56 | write_reg(par, 0xaf); |
57 | |
58 | return 0; |
59 | } |
60 | |
61 | static uint8_t rgb565_to_g16(u16 pixel) |
62 | { |
63 | u16 b = pixel & 0x1f; |
64 | u16 g = (pixel & (0x3f << 5)) >> 5; |
65 | u16 r = (pixel & (0x1f << (5 + 6))) >> (5 + 6); |
66 | |
67 | pixel = (299 * r + 587 * g + 114 * b) / 195; |
68 | if (pixel > 255) |
69 | pixel = 255; |
70 | return (uint8_t)pixel / 16; |
71 | } |
72 | |
73 | static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) |
74 | { |
75 | fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, |
76 | "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n" , __func__, xs, ys, xe, |
77 | ye); |
78 | |
79 | write_reg(par, 0x75); |
80 | write_reg(par, 0x00); |
81 | write_reg(par, 0x3f); |
82 | write_reg(par, 0x15); |
83 | write_reg(par, 0x00); |
84 | write_reg(par, 0x7f); |
85 | } |
86 | |
87 | static int blank(struct fbtft_par *par, bool on) |
88 | { |
89 | fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n" , |
90 | __func__, on ? "true" : "false" ); |
91 | |
92 | if (on) |
93 | write_reg(par, 0xAE); |
94 | else |
95 | write_reg(par, 0xAF); |
96 | return 0; |
97 | } |
98 | |
99 | /* |
100 | * Grayscale Lookup Table |
101 | * GS1 - GS15 |
102 | * The "Gamma curve" contains the relative values between the entries |
103 | * in the Lookup table. |
104 | * |
105 | * 0 = Setting of GS1 < Setting of GS2 < Setting of GS3.....< |
106 | * Setting of GS14 < Setting of GS15 |
107 | */ |
108 | static int set_gamma(struct fbtft_par *par, u32 *curves) |
109 | { |
110 | int i; |
111 | |
112 | fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n" , __func__); |
113 | |
114 | for (i = 0; i < GAMMA_LEN; i++) { |
115 | if (i > 0 && curves[i] < 1) { |
116 | dev_err(par->info->device, |
117 | "Illegal value in Grayscale Lookup Table at index %d.\n" |
118 | "Must be greater than 0\n" , i); |
119 | return -EINVAL; |
120 | } |
121 | if (curves[i] > 7) { |
122 | dev_err(par->info->device, |
123 | "Illegal value(s) in Grayscale Lookup Table.\n" |
124 | "At index=%d, the accumulated value has exceeded 7\n" , |
125 | i); |
126 | return -EINVAL; |
127 | } |
128 | } |
129 | write_reg(par, 0xB8); |
130 | for (i = 0; i < 8; i++) |
131 | write_reg(par, (curves[i] & 0xFF)); |
132 | return 0; |
133 | } |
134 | |
135 | static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) |
136 | { |
137 | u16 *vmem16 = (u16 *)par->info->screen_buffer; |
138 | u8 *buf = par->txbuf.buf; |
139 | u8 n1; |
140 | u8 n2; |
141 | int y, x; |
142 | int ret; |
143 | |
144 | for (x = 0; x < par->info->var.xres; x++) { |
145 | if (x % 2) |
146 | continue; |
147 | for (y = 0; y < par->info->var.yres; y++) { |
148 | n1 = rgb565_to_g16(pixel: vmem16[y * par->info->var.xres + x]); |
149 | n2 = rgb565_to_g16(pixel: vmem16 |
150 | [y * par->info->var.xres + x + 1]); |
151 | *buf = (n1 << 4) | n2; |
152 | buf++; |
153 | } |
154 | } |
155 | |
156 | gpiod_set_value(desc: par->gpio.dc, value: 1); |
157 | |
158 | /* Write data */ |
159 | ret = par->fbtftops.write(par, par->txbuf.buf, |
160 | par->info->var.xres * par->info->var.yres / 2); |
161 | if (ret < 0) |
162 | dev_err(par->info->device, |
163 | "%s: write failed and returned: %d\n" , __func__, ret); |
164 | |
165 | return ret; |
166 | } |
167 | |
168 | static struct fbtft_display display = { |
169 | .regwidth = 8, |
170 | .width = WIDTH, |
171 | .height = HEIGHT, |
172 | .txbuflen = WIDTH * HEIGHT / 2, |
173 | .gamma_num = GAMMA_NUM, |
174 | .gamma_len = GAMMA_LEN, |
175 | .gamma = DEFAULT_GAMMA, |
176 | .fbtftops = { |
177 | .write_vmem = write_vmem, |
178 | .init_display = init_display, |
179 | .set_addr_win = set_addr_win, |
180 | .blank = blank, |
181 | .set_gamma = set_gamma, |
182 | }, |
183 | }; |
184 | |
185 | FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1325" , &display); |
186 | |
187 | MODULE_ALIAS("spi:" DRVNAME); |
188 | MODULE_ALIAS("platform:" DRVNAME); |
189 | MODULE_ALIAS("spi:ssd1325" ); |
190 | MODULE_ALIAS("platform:ssd1325" ); |
191 | |
192 | MODULE_DESCRIPTION("SSD1325 OLED Driver" ); |
193 | MODULE_AUTHOR("Alexey Mednyy" ); |
194 | MODULE_LICENSE("GPL" ); |
195 | |