File: hd44780-spi.c

package info (click to toggle)
lcdproc 0.5.9-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 5,064 kB
  • sloc: ansic: 59,645; sh: 1,740; perl: 681; makefile: 417
file content (215 lines) | stat: -rw-r--r-- 6,536 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/** \file server/drivers/hd44780-spi.c
 * \c SPI connection type of \c hd44780 driver for Hitachi HD44780 based LCD displays.
 *
 * The LCD is operated in its serial mode to be connected via the SPI bus
 * using the Linux kernel spidev interface. The LCD SPI device should be
 * declared in your board setup code.
 */

/*-
 * Copyright (C) 2013 Simon Dawson <spdawson@gmail.com>
 *
 * Based mostly on the hd44780-i2c module, see there for a complete history.
 *
 * This file is released under the GNU General Public License. Refer to the
 * COPYING file distributed with this package.
 *
 * Configuration:
 * Device=/dev/spidev0.0   # the device file of the spi bus (spi 0, chipselect 0)
 * BacklightDevice=(undef)  # the device file for the backlight (1 = on, 0 = off)
 */

#include "hd44780-spi.h"
#include "hd44780-low.h"
#include "shared/report.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <linux/spi/spidev.h>

void spi_HD44780_senddata(PrivateData *p, unsigned char displayID, unsigned char flags, unsigned char ch);
void spi_HD44780_backlight(PrivateData *p, unsigned char state);

#define DEFAULT_DEVICE		"/dev/spidev0.0"

/** KS0073 5-bit sync lead-in on SPI interface */
#define SYNC	0xF8u
/** KS0073 Read Write bit: 1 = read selected data/register, 0 = write to selected data/register */
#define RW	0x04u
/** KS0073 Register Select bit: 0 = instruction register follows, 1 = data register follows */
#define RS	0x02u


/**
 * Reverses the bits of \a u8.
 * \param u8  byte of which to reverse the bits.
 * \return Value resulting from \a u8 with reversed bits.
 */
static inline uint8_t
bit_reverse8(uint8_t u8)
{
	/*
	 * See
	 * http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits
	 * for how this works and. This code snippet is in the public domain.
	 */
	return (uint8_t) (((u8 * 0x0802LU & 0x22110LU) | (u8 * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16);
}


/**
 * Do a SPI transfer by sending \c length bytes of \c outbuf and read the same
 * number of bytes into \c inbuf.
 * \param p       Pointer to driver's private data structure.
 * \param outbuf  Buffer holding data to send.
 * \param inbuf   Buffer to store incoming data (or NULL).
 * \param length  Length of buffers (both).
 * \return  Status from ioctl.
 */
static int
spi_transfer(PrivateData *p, const unsigned char *outbuf, unsigned char *inbuf, unsigned length)
{
	struct spi_ioc_transfer xfer;
	int status;

	static unsigned char no_more_errormsgs = 0;

	memset(&xfer, 0, sizeof(xfer));
	xfer.tx_buf = (unsigned long) outbuf;
	xfer.rx_buf = (unsigned long) inbuf;
	xfer.len = length;

	p->hd44780_functions->drv_debug(RPT_DEBUG, "SPI sending %02x %02x %02x",
					outbuf[0], outbuf[1], length > 2 ? outbuf[2] : 0xFFu);
	status = ioctl(p->fd, SPI_IOC_MESSAGE(1), &xfer);
	if (status < 0) {
		p->hd44780_functions->drv_report(no_more_errormsgs ? RPT_DEBUG : RPT_ERR,
						 "HD44780: SPI: spidev write data %u failed: %s",
						 status, strerror(errno));
		no_more_errormsgs = 1;
	}
#ifdef DEBUG
	if (inbuf)
		p->hd44780_functions->drv_debug(RPT_DEBUG, "SPI returned data %02x %02x %02x",
						inbuf[0], inbuf[1], length > 2 ? inbuf[2] : 0xFFu);
#endif

	return status;
}


/**
 * Initialize the driver.
 * \param drvthis  Pointer to driver structure.
 * \retval 0       Success.
 * \retval -1      Error.
 */
int
hd_init_spi(Driver *drvthis)
{
	PrivateData *p = (PrivateData *) drvthis->private_data;
	HD44780_functions *hd44780_functions = p->hd44780_functions;

	char device[256] = DEFAULT_DEVICE;
	char backlight_device[256] = "";

	/* READ CONFIG FILE */

	/* Get and open SPI device to use */
	strncpy(device, drvthis->config_get_string(drvthis->name, "Device", 0, DEFAULT_DEVICE),
		sizeof(device));
	device[sizeof(device) - 1] = '\0';
	report(RPT_INFO, "HD44780: SPI: Using device '%s'", device);

	p->fd = open(device, O_RDWR);
	if (p->fd < 0) {
		report(RPT_ERR, "HD44780: SPI: open spidev device '%s' failed: %s", device,
		       strerror(errno));
		return -1;
	}

	/* Get and open the backlight device */
	p->backlight_bit = -1;
	strncpy(backlight_device,
		drvthis->config_get_string(drvthis->name, "BacklightDevice", 0, ""),
		sizeof(backlight_device));
	backlight_device[sizeof(backlight_device) - 1] = '\0';

	if (strlen(backlight_device) > 0) {
		report(RPT_INFO, "HD44780: SPI: Using backlight_device '%s'", backlight_device);

		p->backlight_bit = open(backlight_device, O_RDWR);
		if (p->backlight_bit < 0) {
			report(RPT_ERR, "HD44780: SPI: open backlight_device '%s' failed: %s",
			       backlight_device, strerror(errno));
		}
		else {
			hd44780_functions->backlight = spi_HD44780_backlight;
		}
	}

	hd44780_functions->senddata = spi_HD44780_senddata;
	common_init(p, IF_8BIT);

	return 0;
}


/**
 * Send data or commands to the display.
 * \param p          Pointer to driver's private data structure.
 * \param displayID  ID of the display (or 0 for all) to send data to.
 * \param flags      Defines whether to end a command or data.
 * \param ch         The value to send.
 */
void
spi_HD44780_senddata(PrivateData *p, unsigned char displayID, unsigned char flags, unsigned char ch)
{
	unsigned char buf[3];
	unsigned char reverse;

	p->hd44780_functions->drv_report(RPT_DEBUG, "HD44780: SPI: sending %s %02x",
					 RS_INSTR == flags ? "CMD" : "DATA", ch);

	if (flags == RS_INSTR)
		buf[0] = SYNC;
	else
		buf[0] = SYNC | RS;

	/* KS0073 wants Least Significant Bit first, with the added twist of
	 * peculiar splitting of a byte across 4 nibbles. If we ever need to
	 * read from the device, remember to bit_reverse8() each byte (note
	 * that replies aren't split into nibbles). */
	reverse = bit_reverse8(ch);
	buf[1] = reverse & 0xF0;
	buf[2] = (reverse & 0x0F) << 4;

	spi_transfer(p, buf, NULL, sizeof(buf));
}


/**
 * Turn display backlight on or off.
 * \param p      Pointer to driver's private data structure.
 * \param state  New backlight status.
 *
 * KS0073 in SPI mode does not have a backlight control function.  We
 * assume instead that there is some device mapped to a file which we can
 * write to.
 */
void
spi_HD44780_backlight(PrivateData *p, unsigned char state)
{
	if (p->backlight_bit != -1) {
		unsigned char byte = (state == BACKLIGHT_ON) ? '1' : '0';
		if (write(p->backlight_bit, &byte, sizeof(byte)) < 0)
			p->hd44780_functions->drv_report(RPT_ERR,
							 "HD44780: SPI: Cannot write to backlight device: %d (%s)",
							 errno, strerror(errno));
	}
}