1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * FB driver for the HX8347D LCD Controller |
4 | * |
5 | * Copyright (C) 2013 Christian Vogelgsang |
6 | * |
7 | * Based on driver code found here: https://github.com/watterott/r61505u-Adapter |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/init.h> |
13 | #include <linux/delay.h> |
14 | |
15 | #include "fbtft.h" |
16 | |
17 | #define DRVNAME "fb_hx8347d" |
18 | #define WIDTH 320 |
19 | #define HEIGHT 240 |
20 | #define DEFAULT_GAMMA "0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" \ |
21 | "0 0 0 0 0 0 0 0 0 0 0 0 0 0" |
22 | |
23 | static int init_display(struct fbtft_par *par) |
24 | { |
25 | par->fbtftops.reset(par); |
26 | |
27 | /* driving ability */ |
28 | write_reg(par, 0xEA, 0x00); |
29 | write_reg(par, 0xEB, 0x20); |
30 | write_reg(par, 0xEC, 0x0C); |
31 | write_reg(par, 0xED, 0xC4); |
32 | write_reg(par, 0xE8, 0x40); |
33 | write_reg(par, 0xE9, 0x38); |
34 | write_reg(par, 0xF1, 0x01); |
35 | write_reg(par, 0xF2, 0x10); |
36 | write_reg(par, 0x27, 0xA3); |
37 | |
38 | /* power voltage */ |
39 | write_reg(par, 0x1B, 0x1B); |
40 | write_reg(par, 0x1A, 0x01); |
41 | write_reg(par, 0x24, 0x2F); |
42 | write_reg(par, 0x25, 0x57); |
43 | |
44 | /* VCOM offset */ |
45 | write_reg(par, 0x23, 0x8D); /* for flicker adjust */ |
46 | |
47 | /* power on */ |
48 | write_reg(par, 0x18, 0x36); |
49 | write_reg(par, 0x19, 0x01); /* start osc */ |
50 | write_reg(par, 0x01, 0x00); /* wakeup */ |
51 | write_reg(par, 0x1F, 0x88); |
52 | mdelay(5); |
53 | write_reg(par, 0x1F, 0x80); |
54 | mdelay(5); |
55 | write_reg(par, 0x1F, 0x90); |
56 | mdelay(5); |
57 | write_reg(par, 0x1F, 0xD0); |
58 | mdelay(5); |
59 | |
60 | /* color selection */ |
61 | write_reg(par, 0x17, 0x05); /* 65k */ |
62 | |
63 | /*panel characteristic */ |
64 | write_reg(par, 0x36, 0x00); |
65 | |
66 | /*display on */ |
67 | write_reg(par, 0x28, 0x38); |
68 | mdelay(40); |
69 | write_reg(par, 0x28, 0x3C); |
70 | |
71 | return 0; |
72 | } |
73 | |
74 | static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) |
75 | { |
76 | write_reg(par, 0x02, (xs >> 8) & 0xFF); |
77 | write_reg(par, 0x03, xs & 0xFF); |
78 | write_reg(par, 0x04, (xe >> 8) & 0xFF); |
79 | write_reg(par, 0x05, xe & 0xFF); |
80 | write_reg(par, 0x06, (ys >> 8) & 0xFF); |
81 | write_reg(par, 0x07, ys & 0xFF); |
82 | write_reg(par, 0x08, (ye >> 8) & 0xFF); |
83 | write_reg(par, 0x09, ye & 0xFF); |
84 | write_reg(par, 0x22); |
85 | } |
86 | |
87 | #define MEM_Y BIT(7) /* MY row address order */ |
88 | #define MEM_X BIT(6) /* MX column address order */ |
89 | #define MEM_V BIT(5) /* MV row / column exchange */ |
90 | #define MEM_L BIT(4) /* ML vertical refresh order */ |
91 | #define MEM_BGR (3) /* RGB-BGR Order */ |
92 | static int set_var(struct fbtft_par *par) |
93 | { |
94 | switch (par->info->var.rotate) { |
95 | case 0: |
96 | write_reg(par, 0x16, MEM_V | MEM_X | (par->bgr << MEM_BGR)); |
97 | break; |
98 | case 270: |
99 | write_reg(par, 0x16, par->bgr << MEM_BGR); |
100 | break; |
101 | case 180: |
102 | write_reg(par, 0x16, MEM_V | MEM_Y | (par->bgr << MEM_BGR)); |
103 | break; |
104 | case 90: |
105 | write_reg(par, 0x16, MEM_X | MEM_Y | (par->bgr << MEM_BGR)); |
106 | break; |
107 | } |
108 | |
109 | return 0; |
110 | } |
111 | |
112 | /* |
113 | * Gamma string format: |
114 | * VRP0 VRP1 VRP2 VRP3 VRP4 VRP5 PRP0 PRP1 PKP0 PKP1 PKP2 PKP3 PKP4 CGM |
115 | * VRN0 VRN1 VRN2 VRN3 VRN4 VRN5 PRN0 PRN1 PKN0 PKN1 PKN2 PKN3 PKN4 CGM |
116 | */ |
117 | #define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)] |
118 | static int set_gamma(struct fbtft_par *par, u32 *curves) |
119 | { |
120 | static const unsigned long mask[] = { |
121 | 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x1f, 0x1f, |
122 | 0x1f, 0x1f, 0x1f, 0x0f, |
123 | }; |
124 | int i, j; |
125 | int acc = 0; |
126 | |
127 | /* apply mask */ |
128 | for (i = 0; i < par->gamma.num_curves; i++) |
129 | for (j = 0; j < par->gamma.num_values; j++) { |
130 | acc += CURVE(i, j); |
131 | CURVE(i, j) &= mask[j]; |
132 | } |
133 | |
134 | if (acc == 0) /* skip if all values are zero */ |
135 | return 0; |
136 | |
137 | for (i = 0; i < par->gamma.num_curves; i++) { |
138 | write_reg(par, 0x40 + (i * 0x10), CURVE(i, 0)); |
139 | write_reg(par, 0x41 + (i * 0x10), CURVE(i, 1)); |
140 | write_reg(par, 0x42 + (i * 0x10), CURVE(i, 2)); |
141 | write_reg(par, 0x43 + (i * 0x10), CURVE(i, 3)); |
142 | write_reg(par, 0x44 + (i * 0x10), CURVE(i, 4)); |
143 | write_reg(par, 0x45 + (i * 0x10), CURVE(i, 5)); |
144 | write_reg(par, 0x46 + (i * 0x10), CURVE(i, 6)); |
145 | write_reg(par, 0x47 + (i * 0x10), CURVE(i, 7)); |
146 | write_reg(par, 0x48 + (i * 0x10), CURVE(i, 8)); |
147 | write_reg(par, 0x49 + (i * 0x10), CURVE(i, 9)); |
148 | write_reg(par, 0x4A + (i * 0x10), CURVE(i, 10)); |
149 | write_reg(par, 0x4B + (i * 0x10), CURVE(i, 11)); |
150 | write_reg(par, 0x4C + (i * 0x10), CURVE(i, 12)); |
151 | } |
152 | write_reg(par, 0x5D, (CURVE(1, 0) << 4) | CURVE(0, 0)); |
153 | |
154 | return 0; |
155 | } |
156 | |
157 | #undef CURVE |
158 | |
159 | static struct fbtft_display display = { |
160 | .regwidth = 8, |
161 | .width = WIDTH, |
162 | .height = HEIGHT, |
163 | .gamma_num = 2, |
164 | .gamma_len = 14, |
165 | .gamma = DEFAULT_GAMMA, |
166 | .fbtftops = { |
167 | .init_display = init_display, |
168 | .set_addr_win = set_addr_win, |
169 | .set_var = set_var, |
170 | .set_gamma = set_gamma, |
171 | }, |
172 | }; |
173 | |
174 | FBTFT_REGISTER_DRIVER(DRVNAME, "himax,hx8347d" , &display); |
175 | |
176 | MODULE_ALIAS("spi:" DRVNAME); |
177 | MODULE_ALIAS("platform:" DRVNAME); |
178 | MODULE_ALIAS("spi:hx8347d" ); |
179 | MODULE_ALIAS("platform:hx8347d" ); |
180 | |
181 | MODULE_DESCRIPTION("FB driver for the HX8347D LCD Controller" ); |
182 | MODULE_AUTHOR("Christian Vogelgsang" ); |
183 | MODULE_LICENSE("GPL" ); |
184 | |