1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #include <linux/backlight.h> |
4 | #include <linux/module.h> |
5 | #include <linux/kernel.h> |
6 | #include <linux/init.h> |
7 | #include <linux/spi/spi.h> |
8 | #include <linux/delay.h> |
9 | |
10 | #include "fbtft.h" |
11 | |
12 | #define DRVNAME "fb_ssd1351" |
13 | #define WIDTH 128 |
14 | #define HEIGHT 128 |
15 | #define GAMMA_NUM 1 |
16 | #define GAMMA_LEN 63 |
17 | #define DEFAULT_GAMMA "0 2 2 2 2 2 2 2 " \ |
18 | "2 2 2 2 2 2 2 2 " \ |
19 | "2 2 2 2 2 2 2 2 " \ |
20 | "2 2 2 2 2 2 2 2 " \ |
21 | "2 2 2 2 2 2 2 2 " \ |
22 | "2 2 2 2 2 2 2 2 " \ |
23 | "2 2 2 2 2 2 2 2 " \ |
24 | "2 2 2 2 2 2 2" \ |
25 | |
26 | static void register_onboard_backlight(struct fbtft_par *par); |
27 | |
28 | static int init_display(struct fbtft_par *par) |
29 | { |
30 | if (par->pdata && |
31 | par->pdata->display.backlight == FBTFT_ONBOARD_BACKLIGHT) { |
32 | /* module uses onboard GPIO for panel power */ |
33 | par->fbtftops.register_backlight = register_onboard_backlight; |
34 | } |
35 | |
36 | par->fbtftops.reset(par); |
37 | |
38 | write_reg(par, 0xfd, 0x12); /* Command Lock */ |
39 | write_reg(par, 0xfd, 0xb1); /* Command Lock */ |
40 | write_reg(par, 0xae); /* Display Off */ |
41 | write_reg(par, 0xb3, 0xf1); /* Front Clock Div */ |
42 | write_reg(par, 0xca, 0x7f); /* Set Mux Ratio */ |
43 | write_reg(par, 0x15, 0x00, 0x7f); /* Set Column Address */ |
44 | write_reg(par, 0x75, 0x00, 0x7f); /* Set Row Address */ |
45 | write_reg(par, 0xa1, 0x00); /* Set Display Start Line */ |
46 | write_reg(par, 0xa2, 0x00); /* Set Display Offset */ |
47 | write_reg(par, 0xb5, 0x00); /* Set GPIO */ |
48 | write_reg(par, 0xab, 0x01); /* Set Function Selection */ |
49 | write_reg(par, 0xb1, 0x32); /* Set Phase Length */ |
50 | write_reg(par, 0xb4, 0xa0, 0xb5, 0x55); /* Set Segment Low Voltage */ |
51 | write_reg(par, 0xbb, 0x17); /* Set Precharge Voltage */ |
52 | write_reg(par, 0xbe, 0x05); /* Set VComH Voltage */ |
53 | write_reg(par, 0xc1, 0xc8, 0x80, 0xc8); /* Set Contrast */ |
54 | write_reg(par, 0xc7, 0x0f); /* Set Master Contrast */ |
55 | write_reg(par, 0xb6, 0x01); /* Set Second Precharge Period */ |
56 | write_reg(par, 0xa6); /* Set Display Mode Reset */ |
57 | write_reg(par, 0xaf); /* Set Sleep Mode Display On */ |
58 | |
59 | return 0; |
60 | } |
61 | |
62 | static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) |
63 | { |
64 | write_reg(par, 0x15, xs, xe); |
65 | write_reg(par, 0x75, ys, ye); |
66 | write_reg(par, 0x5c); |
67 | } |
68 | |
69 | static int set_var(struct fbtft_par *par) |
70 | { |
71 | unsigned int remap; |
72 | |
73 | if (par->fbtftops.init_display != init_display) { |
74 | /* don't risk messing up register A0h */ |
75 | fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, |
76 | "%s: skipping since custom init_display() is used\n" , |
77 | __func__); |
78 | return 0; |
79 | } |
80 | |
81 | remap = 0x60 | (par->bgr << 2); /* Set Colour Depth */ |
82 | |
83 | switch (par->info->var.rotate) { |
84 | case 0: |
85 | write_reg(par, 0xA0, remap | 0x00 | BIT(4)); |
86 | break; |
87 | case 270: |
88 | write_reg(par, 0xA0, remap | 0x03 | BIT(4)); |
89 | break; |
90 | case 180: |
91 | write_reg(par, 0xA0, remap | 0x02); |
92 | break; |
93 | case 90: |
94 | write_reg(par, 0xA0, remap | 0x01); |
95 | break; |
96 | } |
97 | |
98 | return 0; |
99 | } |
100 | |
101 | /* |
102 | * Grayscale Lookup Table |
103 | * GS1 - GS63 |
104 | * The driver Gamma curve contains the relative values between the entries |
105 | * in the Lookup table. |
106 | * |
107 | * From datasheet: |
108 | * 8.8 Gray Scale Decoder |
109 | * |
110 | * there are total 180 Gamma Settings (Setting 0 to Setting 180) |
111 | * available for the Gray Scale table. |
112 | * |
113 | * The gray scale is defined in incremental way, with reference |
114 | * to the length of previous table entry: |
115 | * Setting of GS1 has to be >= 0 |
116 | * Setting of GS2 has to be > Setting of GS1 +1 |
117 | * Setting of GS3 has to be > Setting of GS2 +1 |
118 | * : |
119 | * Setting of GS63 has to be > Setting of GS62 +1 |
120 | * |
121 | */ |
122 | static int set_gamma(struct fbtft_par *par, u32 *curves) |
123 | { |
124 | unsigned long tmp[GAMMA_NUM * GAMMA_LEN]; |
125 | int i, acc = 0; |
126 | |
127 | for (i = 0; i < 63; i++) { |
128 | if (i > 0 && curves[i] < 2) { |
129 | dev_err(par->info->device, |
130 | "Illegal value in Grayscale Lookup Table at index %d : %d. Must be greater than 1\n" , |
131 | i, curves[i]); |
132 | return -EINVAL; |
133 | } |
134 | acc += curves[i]; |
135 | tmp[i] = acc; |
136 | if (acc > 180) { |
137 | dev_err(par->info->device, |
138 | "Illegal value(s) in Grayscale Lookup Table. At index=%d : %d, the accumulated value has exceeded 180\n" , |
139 | i, acc); |
140 | return -EINVAL; |
141 | } |
142 | } |
143 | |
144 | write_reg(par, 0xB8, |
145 | tmp[0], tmp[1], tmp[2], tmp[3], |
146 | tmp[4], tmp[5], tmp[6], tmp[7], |
147 | tmp[8], tmp[9], tmp[10], tmp[11], |
148 | tmp[12], tmp[13], tmp[14], tmp[15], |
149 | tmp[16], tmp[17], tmp[18], tmp[19], |
150 | tmp[20], tmp[21], tmp[22], tmp[23], |
151 | tmp[24], tmp[25], tmp[26], tmp[27], |
152 | tmp[28], tmp[29], tmp[30], tmp[31], |
153 | tmp[32], tmp[33], tmp[34], tmp[35], |
154 | tmp[36], tmp[37], tmp[38], tmp[39], |
155 | tmp[40], tmp[41], tmp[42], tmp[43], |
156 | tmp[44], tmp[45], tmp[46], tmp[47], |
157 | tmp[48], tmp[49], tmp[50], tmp[51], |
158 | tmp[52], tmp[53], tmp[54], tmp[55], |
159 | tmp[56], tmp[57], tmp[58], tmp[59], |
160 | tmp[60], tmp[61], tmp[62]); |
161 | |
162 | return 0; |
163 | } |
164 | |
165 | static int blank(struct fbtft_par *par, bool on) |
166 | { |
167 | fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n" , |
168 | __func__, on ? "true" : "false" ); |
169 | if (on) |
170 | write_reg(par, 0xAE); |
171 | else |
172 | write_reg(par, 0xAF); |
173 | return 0; |
174 | } |
175 | |
176 | static struct fbtft_display display = { |
177 | .regwidth = 8, |
178 | .width = WIDTH, |
179 | .height = HEIGHT, |
180 | .gamma_num = GAMMA_NUM, |
181 | .gamma_len = GAMMA_LEN, |
182 | .gamma = DEFAULT_GAMMA, |
183 | .fbtftops = { |
184 | .init_display = init_display, |
185 | .set_addr_win = set_addr_win, |
186 | .set_var = set_var, |
187 | .set_gamma = set_gamma, |
188 | .blank = blank, |
189 | }, |
190 | }; |
191 | |
192 | static int update_onboard_backlight(struct backlight_device *bd) |
193 | { |
194 | struct fbtft_par *par = bl_get_data(bl_dev: bd); |
195 | bool on; |
196 | |
197 | fbtft_par_dbg(DEBUG_BACKLIGHT, par, |
198 | "%s: power=%d, fb_blank=%d\n" , |
199 | __func__, bd->props.power, bd->props.fb_blank); |
200 | |
201 | on = !backlight_is_blank(bd); |
202 | /* Onboard backlight connected to GPIO0 on SSD1351, GPIO1 unused */ |
203 | write_reg(par, 0xB5, on ? 0x03 : 0x02); |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static const struct backlight_ops bl_ops = { |
209 | .update_status = update_onboard_backlight, |
210 | }; |
211 | |
212 | static void register_onboard_backlight(struct fbtft_par *par) |
213 | { |
214 | struct backlight_device *bd; |
215 | struct backlight_properties bl_props = { 0, }; |
216 | |
217 | bl_props.type = BACKLIGHT_RAW; |
218 | bl_props.power = FB_BLANK_POWERDOWN; |
219 | |
220 | bd = backlight_device_register(name: dev_driver_string(dev: par->info->device), |
221 | dev: par->info->device, devdata: par, ops: &bl_ops, |
222 | props: &bl_props); |
223 | if (IS_ERR(ptr: bd)) { |
224 | dev_err(par->info->device, |
225 | "cannot register backlight device (%ld)\n" , |
226 | PTR_ERR(bd)); |
227 | return; |
228 | } |
229 | par->info->bl_dev = bd; |
230 | |
231 | if (!par->fbtftops.unregister_backlight) |
232 | par->fbtftops.unregister_backlight = fbtft_unregister_backlight; |
233 | } |
234 | |
235 | FBTFT_REGISTER_DRIVER(DRVNAME, "solomon,ssd1351" , &display); |
236 | |
237 | MODULE_ALIAS("spi:" DRVNAME); |
238 | MODULE_ALIAS("platform:" DRVNAME); |
239 | MODULE_ALIAS("spi:ssd1351" ); |
240 | MODULE_ALIAS("platform:ssd1351" ); |
241 | |
242 | MODULE_DESCRIPTION("SSD1351 OLED Driver" ); |
243 | MODULE_AUTHOR("James Davies" ); |
244 | MODULE_LICENSE("GPL" ); |
245 | |