1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * work_92105 display support
4  *
5  * (C) Copyright 2014  DENX Software Engineering GmbH
6  * Written-by: Albert ARIBAUD <albert.aribaud@3adev.fr>
7  *
8  * The work_92105 display is a HD44780-compatible module
9  * controlled through a MAX6957AAX SPI port expander, two
10  * MAX518 I2C DACs and native LPC32xx GPO 15.
11  */
12 
13 #include <common.h>
14 #include <asm/arch/sys_proto.h>
15 #include <asm/arch/cpu.h>
16 #include <asm/arch/emc.h>
17 #include <asm/gpio.h>
18 #include <spi.h>
19 #include <i2c.h>
20 #include <version.h>
21 #include <vsprintf.h>
22 
23 /*
24  * GPO 15 in port 3 is gpio 3*32+15 = 111
25  */
26 
27 #define GPO_15 111
28 
29 /**
30  * MAX6957AAX registers that we will be using
31  */
32 
33 #define MAX6957_CONF		0x04
34 
35 #define MAX6957_CONF_08_11	0x0A
36 #define MAX6957_CONF_12_15	0x0B
37 #define MAX6957_CONF_16_19	0x0C
38 
39 /**
40  * Individual gpio ports (one per gpio) to HD44780
41  */
42 
43 #define MAX6957AAX_HD44780_RS	0x29
44 #define MAX6957AAX_HD44780_R_W	0x2A
45 #define MAX6957AAX_HD44780_EN	0x2B
46 #define MAX6957AAX_HD44780_DATA	0x4C
47 
48 /**
49  * Display controller instructions
50  */
51 
52 /* Function set: eight bits, two lines, 8-dot font */
53 #define HD44780_FUNCTION_SET		0x38
54 
55 /* Display ON / OFF: turn display on */
56 #define HD44780_DISPLAY_ON_OFF_CONTROL	0x0C
57 
58 /* Entry mode: increment */
59 #define HD44780_ENTRY_MODE_SET		0x06
60 
61 /* Clear */
62 #define HD44780_CLEAR_DISPLAY		0x01
63 
64 /* Set DDRAM addr (to be ORed with exact address) */
65 #define HD44780_SET_DDRAM_ADDR		0x80
66 
67 /* Set CGRAM addr (to be ORed with exact address) */
68 #define HD44780_SET_CGRAM_ADDR		0x40
69 
70 /**
71  * Default value for contrats
72  */
73 
74 #define CONTRAST_DEFAULT  25
75 
76 /**
77  * Define slave as a module-wide local to save passing it around,
78  * plus we will need it after init for the "hd44780" command.
79  */
80 
81 static struct spi_slave *slave;
82 
83 /*
84  * Write a value into a MAX6957AAX register.
85  */
86 
87 static void max6957aax_write(uint8_t reg, uint8_t value)
88 {
89 	uint8_t dout[2];
90 
91 	dout[0] = reg;
92 	dout[1] = value;
93 	gpio_set_value(GPO_15, 0);
94 	/* do SPI read/write (passing din==dout is OK) */
95 	spi_xfer(slave, 16, dout, dout, SPI_XFER_BEGIN | SPI_XFER_END);
96 	gpio_set_value(GPO_15, 1);
97 }
98 
99 /*
100  * Read a value from a MAX6957AAX register.
101  *
102  * According to the MAX6957AAX datasheet, we should release the chip
103  * select halfway through the read sequence, when the actual register
104  * value is read; but the WORK_92105 hardware prevents the MAX6957AAX
105  * SPI OUT from reaching the LPC32XX SIP MISO if chip is not selected.
106  * so let's release the CS an hold it again while reading the result.
107  */
108 
109 static uint8_t max6957aax_read(uint8_t reg)
110 {
111 	uint8_t dout[2], din[2];
112 
113 	/* send read command */
114 	dout[0] = reg | 0x80; /* set bit 7 to indicate read */
115 	dout[1] = 0;
116 	gpio_set_value(GPO_15, 0);
117 	/* do SPI read/write (passing din==dout is OK) */
118 	spi_xfer(slave, 16, dout, dout, SPI_XFER_BEGIN | SPI_XFER_END);
119 	/* latch read command */
120 	gpio_set_value(GPO_15, 1);
121 	/* read register -- din = noop on xmit, din[1] = reg on recv */
122 	din[0] = 0;
123 	din[1] = 0;
124 	gpio_set_value(GPO_15, 0);
125 	/* do SPI read/write (passing din==dout is OK) */
126 	spi_xfer(slave, 16, din, din, SPI_XFER_BEGIN | SPI_XFER_END);
127 	/* end of read. */
128 	gpio_set_value(GPO_15, 1);
129 	return din[1];
130 }
131 
132 static void hd44780_instruction(unsigned long instruction)
133 {
134 	max6957aax_write(MAX6957AAX_HD44780_RS, 0);
135 	max6957aax_write(MAX6957AAX_HD44780_R_W, 0);
136 	max6957aax_write(MAX6957AAX_HD44780_EN, 1);
137 	max6957aax_write(MAX6957AAX_HD44780_DATA, instruction);
138 	max6957aax_write(MAX6957AAX_HD44780_EN, 0);
139 	/* HD44780 takes 37 us for most instructions, 1520 for clear */
140 	if (instruction == HD44780_CLEAR_DISPLAY)
141 		udelay(2000);
142 	else
143 		udelay(100);
144 }
145 
146 static void hd44780_write_char(char c)
147 {
148 	max6957aax_write(MAX6957AAX_HD44780_RS, 1);
149 	max6957aax_write(MAX6957AAX_HD44780_R_W, 0);
150 	max6957aax_write(MAX6957AAX_HD44780_EN, 1);
151 	max6957aax_write(MAX6957AAX_HD44780_DATA, c);
152 	max6957aax_write(MAX6957AAX_HD44780_EN, 0);
153 	/* HD44780 takes 37 us to write to DDRAM or CGRAM */
154 	udelay(100);
155 }
156 
157 static void hd44780_write_str(char *s)
158 {
159 	max6957aax_write(MAX6957AAX_HD44780_RS, 1);
160 	max6957aax_write(MAX6957AAX_HD44780_R_W, 0);
161 	while (*s) {
162 		max6957aax_write(MAX6957AAX_HD44780_EN, 1);
163 		max6957aax_write(MAX6957AAX_HD44780_DATA, *s);
164 		max6957aax_write(MAX6957AAX_HD44780_EN, 0);
165 		s++;
166 		/* HD44780 takes 37 us to write to DDRAM or CGRAM */
167 		udelay(100);
168 	}
169 }
170 
171 /*
172  * Existing user code might expect these custom characters to be
173  * recognized and displayed on the LCD
174  */
175 
176 static u8 char_gen_chars[] = {
177 	/* #8, empty rectangle */
178 	0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1F,
179 	/* #9, filled right arrow */
180 	0x10, 0x18, 0x1C, 0x1E, 0x1C, 0x18, 0x10, 0x00,
181 	/* #10, filled left arrow */
182 	0x01, 0x03, 0x07, 0x0F, 0x07, 0x03, 0x01, 0x00,
183 	/* #11, up and down arrow */
184 	0x04, 0x0E, 0x1F, 0x00, 0x00, 0x1F, 0x0E, 0x04,
185 	/* #12, plus/minus */
186 	0x04, 0x04, 0x1F, 0x04, 0x04, 0x00, 0x1F, 0x00,
187 	/* #13, fat exclamation mark */
188 	0x06, 0x06, 0x06, 0x06, 0x00, 0x06, 0x06, 0x00,
189 	/* #14, empty square */
190 	0x00, 0x1F, 0x11, 0x11, 0x11, 0x1F, 0x00, 0x00,
191 	/* #15, struck out square */
192 	0x00, 0x1F, 0x19, 0x15, 0x13, 0x1F, 0x00, 0x00,
193 };
194 
195 static void hd44780_init_char_gen(void)
196 {
197 	int i;
198 
199 	hd44780_instruction(HD44780_SET_CGRAM_ADDR);
200 
201 	for (i = 0; i < sizeof(char_gen_chars); i++)
202 		hd44780_write_char(char_gen_chars[i]);
203 
204 	hd44780_instruction(HD44780_SET_DDRAM_ADDR);
205 }
206 
207 void work_92105_display_init(void)
208 {
209 	int claim_err;
210 	char *display_contrast_str;
211 	uint8_t display_contrast = CONTRAST_DEFAULT;
212 	uint8_t enable_backlight = 0x96;
213 
214 	slave = spi_setup_slave(0, 0, 500000, 0);
215 
216 	if (!slave) {
217 		printf("Failed to set up SPI slave\n");
218 		return;
219 	}
220 
221 	claim_err = spi_claim_bus(slave);
222 
223 	if (claim_err)
224 		debug("Failed to claim SPI bus: %d\n", claim_err);
225 
226 	/* enable backlight */
227 	i2c_write(0x2c, 0x01, 1, &enable_backlight, 1);
228 
229 	/* set display contrast */
230 	display_contrast_str = env_get("fwopt_dispcontrast");
231 	if (display_contrast_str)
232 		display_contrast = simple_strtoul(display_contrast_str,
233 			NULL, 10);
234 	i2c_write(0x2c, 0x00, 1, &display_contrast, 1);
235 
236 	/* request GPO_15 as an output initially set to 1 */
237 	gpio_request(GPO_15, "MAX6957_nCS");
238 	gpio_direction_output(GPO_15, 1);
239 
240 	/* enable MAX6957 portexpander */
241 	max6957aax_write(MAX6957_CONF, 0x01);
242 	/* configure pin 8 as input, pins 9..19 as outputs */
243 	max6957aax_write(MAX6957_CONF_08_11, 0x56);
244 	max6957aax_write(MAX6957_CONF_12_15, 0x55);
245 	max6957aax_write(MAX6957_CONF_16_19, 0x55);
246 
247 	/* initialize HD44780 */
248 	max6957aax_write(MAX6957AAX_HD44780_EN, 0);
249 	hd44780_instruction(HD44780_FUNCTION_SET);
250 	hd44780_instruction(HD44780_DISPLAY_ON_OFF_CONTROL);
251 	hd44780_instruction(HD44780_ENTRY_MODE_SET);
252 
253 	/* write custom character glyphs */
254 	hd44780_init_char_gen();
255 
256 	/* Show U-Boot version, date and time as a sign-of-life */
257 	hd44780_instruction(HD44780_CLEAR_DISPLAY);
258 	hd44780_instruction(HD44780_SET_DDRAM_ADDR | 0);
259 	hd44780_write_str(U_BOOT_VERSION);
260 	hd44780_instruction(HD44780_SET_DDRAM_ADDR | 64);
261 	hd44780_write_str(U_BOOT_DATE);
262 	hd44780_instruction(HD44780_SET_DDRAM_ADDR | 64 | 20);
263 	hd44780_write_str(U_BOOT_TIME);
264 }
265 
266 #ifdef CONFIG_CMD_MAX6957
267 
268 static int do_max6957aax(cmd_tbl_t *cmdtp, int flag, int argc,
269 			 char *const argv[])
270 {
271 	int reg, val;
272 
273 	if (argc != 3)
274 		return CMD_RET_USAGE;
275 	switch (argv[1][0]) {
276 	case 'r':
277 	case 'R':
278 		reg = simple_strtoul(argv[2], NULL, 0);
279 		val = max6957aax_read(reg);
280 		printf("MAX6957 reg 0x%02x read 0x%02x\n", reg, val);
281 		return 0;
282 	default:
283 		reg = simple_strtoul(argv[1], NULL, 0);
284 		val = simple_strtoul(argv[2], NULL, 0);
285 		max6957aax_write(reg, val);
286 		printf("MAX6957 reg 0x%02x wrote 0x%02x\n", reg, val);
287 		return 0;
288 	}
289 	return 1;
290 }
291 
292 #ifdef CONFIG_SYS_LONGHELP
293 static char max6957aax_help_text[] =
294 	"max6957aax - write or read display register:\n"
295 		"\tmax6957aax R|r reg - read display register;\n"
296 		"\tmax6957aax reg val - write display register.";
297 #endif
298 
299 U_BOOT_CMD(
300 	max6957aax, 6, 1, do_max6957aax,
301 	"SPI MAX6957 display write/read",
302 	max6957aax_help_text
303 );
304 #endif /* CONFIG_CMD_MAX6957 */
305 
306 #ifdef CONFIG_CMD_HD44760
307 
308 /*
309  * We need the HUSH parser because we need string arguments, and
310  * only HUSH can understand them.
311  */
312 
313 #if !defined(CONFIG_HUSH_PARSER)
314 #error CONFIG_CMD_HD44760 requires CONFIG_HUSH_PARSER
315 #endif
316 
317 static int do_hd44780(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
318 {
319 	char *cmd;
320 
321 	if (argc != 3)
322 		return CMD_RET_USAGE;
323 
324 	cmd = argv[1];
325 
326 	if (strcasecmp(cmd, "cmd") == 0)
327 		hd44780_instruction(simple_strtol(argv[2], NULL, 0));
328 	else if (strcasecmp(cmd, "data") == 0)
329 		hd44780_write_char(simple_strtol(argv[2], NULL, 0));
330 	else if (strcasecmp(cmd, "str") == 0)
331 		hd44780_write_str(argv[2]);
332 	return 0;
333 }
334 
335 #ifdef CONFIG_SYS_LONGHELP
336 static char hd44780_help_text[] =
337 	"hd44780 - control LCD driver:\n"
338 		"\thd44780 cmd <val> - send command <val> to driver;\n"
339 		"\thd44780 data <val> - send data <val> to driver;\n"
340 		"\thd44780 str \"<text>\" - send \"<text>\" to driver.";
341 #endif
342 
343 U_BOOT_CMD(
344 	hd44780, 6, 1, do_hd44780,
345 	"HD44780 LCD driver control",
346 	hd44780_help_text
347 );
348 #endif /* CONFIG_CMD_HD44760 */
349