File Index
Files and directories
Sign in
FILE

fb_uc1611.c

File preview with syntax highlighting.

linux-6.19.14/drivers/staging/fbtft/fb_uc1611.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * FB driver for the UltraChip UC1611 LCD controller
  4 *
  5 * The display is 4-bit grayscale (16 shades) 240x160.
  6 *
  7 * Copyright (C) 2015 Henri Chain
  8 */
  9
 10#include <linux/module.h>
 11#include <linux/kernel.h>
 12#include <linux/init.h>
 13#include <linux/gpio/consumer.h>
 14#include <linux/spi/spi.h>
 15#include <linux/delay.h>
 16
 17#include "fbtft.h"
 18
 19#define DRVNAME		"fb_uc1611"
 20#define WIDTH		240
 21#define HEIGHT		160
 22#define BPP		8
 23#define FPS		40
 24
 25/*
 26 * LCD voltage is a combination of ratio, gain, pot and temp
 27 *
 28 * V_LCD = V_BIAS * ratio
 29 * V_LCD = (C_V0 + C_PM × pot) * (1 + (T - 25) * temp)
 30 * C_V0 and C_PM depend on ratio and gain
 31 * T is ambient temperature
 32 */
 33
 34/* BR -> actual ratio: 0-3 -> 5, 10, 11, 13 */
 35static unsigned int ratio = 2;
 36module_param(ratio, uint, 0000);
 37MODULE_PARM_DESC(ratio, "BR[1:0] Bias voltage ratio: 0-3 (default: 2)");
 38
 39static unsigned int gain = 3;
 40module_param(gain, uint, 0000);
 41MODULE_PARM_DESC(gain, "GN[1:0] Bias voltage gain: 0-3 (default: 3)");
 42
 43static unsigned int pot = 16;
 44module_param(pot, uint, 0000);
 45MODULE_PARM_DESC(pot, "PM[6:0] Bias voltage pot.: 0-63 (default: 16)");
 46
 47/* TC -> % compensation per deg C: 0-3 -> -.05, -.10, -.015, -.20 */
 48static unsigned int temp;
 49module_param(temp, uint, 0000);
 50MODULE_PARM_DESC(temp, "TC[1:0] Temperature compensation: 0-3 (default: 0)");
 51
 52/* PC[1:0] -> LCD capacitance: 0-3 -> <20nF, 20-28 nF, 29-40 nF, 40-56 nF */
 53static unsigned int load = 1;
 54module_param(load, uint, 0000);
 55MODULE_PARM_DESC(load, "PC[1:0] Panel Loading: 0-3 (default: 1)");
 56
 57/* PC[3:2] -> V_LCD: 0, 1, 3 -> ext., int. with ratio = 5, int. standard */
 58static unsigned int pump = 3;
 59module_param(pump, uint, 0000);
 60MODULE_PARM_DESC(pump, "PC[3:2] Pump control: 0,1,3 (default: 3)");
 61
 62static int init_display(struct fbtft_par *par)
 63{
 64	int ret;
 65
 66	/*
 67	 * Set CS active inverse polarity: just setting SPI_CS_HIGH does not
 68	 * work with GPIO based chip selects that are logically active high
 69	 * but inverted inside the GPIO library, so enforce inverted
 70	 * semantics.
 71	 */
 72	par->spi->mode ^= SPI_CS_HIGH;
 73	ret = spi_setup(par->spi);
 74	if (ret) {
 75		dev_err(par->info->device,
 76			"Could not set inverse CS polarity\n");
 77		return ret;
 78	}
 79
 80	/* Reset controller */
 81	write_reg(par, 0xE2);
 82
 83	/* Set bias ratio */
 84	write_reg(par, 0xE8 | (ratio & 0x03));
 85
 86	/* Set bias gain and potentiometer */
 87	write_reg(par, 0x81);
 88	write_reg(par, (gain & 0x03) << 6 | (pot & 0x3F));
 89
 90	/* Set temperature compensation */
 91	write_reg(par, 0x24 | (temp & 0x03));
 92
 93	/* Set panel loading */
 94	write_reg(par, 0x28 | (load & 0x03));
 95
 96	/* Set pump control */
 97	write_reg(par, 0x2C | (pump & 0x03));
 98
 99	/* Set inverse display */
100	write_reg(par, 0xA6 | 0x01);
101
102	/* Set 4-bit grayscale mode */
103	write_reg(par, 0xD0 | (0x02 & 0x03));
104
105	/* Set Display enable */
106	write_reg(par, 0xA8 | 0x07);
107
108	return 0;
109}
110
111static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
112{
113	switch (par->info->var.rotate) {
114	case 90:
115	case 270:
116		/* Set column address */
117		write_reg(par, ys & 0x0F);
118		write_reg(par, 0x10 | (ys >> 4));
119
120		/* Set page address (divide xs by 2) (not used by driver) */
121		write_reg(par, 0x60 | ((xs >> 1) & 0x0F));
122		write_reg(par, 0x70 | (xs >> 5));
123		break;
124	default:
125		/* Set column address (not used by driver) */
126		write_reg(par, xs & 0x0F);
127		write_reg(par, 0x10 | (xs >> 4));
128
129		/* Set page address (divide ys by 2) */
130		write_reg(par, 0x60 | ((ys >> 1) & 0x0F));
131		write_reg(par, 0x70 | (ys >> 5));
132		break;
133	}
134}
135
136static int blank(struct fbtft_par *par, bool on)
137{
138	if (on)
139		write_reg(par, 0xA8 | 0x00);
140	else
141		write_reg(par, 0xA8 | 0x07);
142	return 0;
143}
144
145static int set_var(struct fbtft_par *par)
146{
147	/* par->info->fix.visual = FB_VISUAL_PSEUDOCOLOR; */
148	par->info->var.grayscale = 1;
149	par->info->var.red.offset    = 0;
150	par->info->var.red.length    = 8;
151	par->info->var.green.offset  = 0;
152	par->info->var.green.length  = 8;
153	par->info->var.blue.offset   = 0;
154	par->info->var.blue.length   = 8;
155	par->info->var.transp.offset = 0;
156	par->info->var.transp.length = 0;
157
158	switch (par->info->var.rotate) {
159	case 90:
160		/* Set RAM address control */
161		write_reg(par, 0x88
162			| (0x0 & 0x1) << 2 /* Increment positively */
163			| (0x1 << 1)	   /* Increment page first */
164			| 0x1);		   /* Wrap around (default) */
165
166		/* Set LCD mapping */
167		write_reg(par, 0xC0
168			| (0x0 & 0x1) << 2 /* Mirror Y OFF */
169			| (0x0 & 0x1) << 1 /* Mirror X OFF */
170			| (0x0 & 0x1));    /* MS nibble last (default) */
171		break;
172	case 180:
173		/* Set RAM address control */
174		write_reg(par, 0x88
175			| (0x0 & 0x1) << 2 /* Increment positively */
176			| (0x0 & 0x1) << 1 /* Increment column first */
177			| 0x1);		   /* Wrap around (default) */
178
179		/* Set LCD mapping */
180		write_reg(par, 0xC0
181			| (0x1 << 2)	   /* Mirror Y ON */
182			| (0x0 & 0x1) << 1 /* Mirror X OFF */
183			| (0x0 & 0x1));    /* MS nibble last (default) */
184		break;
185	case 270:
186		/* Set RAM address control */
187		write_reg(par, 0x88
188			| (0x0 & 0x1) << 2 /* Increment positively */
189			| (0x1 << 1)	   /* Increment page first */
190			| 0x1);		   /* Wrap around (default) */
191
192		/* Set LCD mapping */
193		write_reg(par, 0xC0
194			| (0x1 << 2)	   /* Mirror Y ON */
195			| (0x1 << 1)	   /* Mirror X ON */
196			| (0x0 & 0x1));    /* MS nibble last (default) */
197		break;
198	default:
199		/* Set RAM address control */
200		write_reg(par, 0x88
201			| (0x0 & 0x1) << 2 /* Increment positively */
202			| (0x0 & 0x1) << 1 /* Increment column first */
203			| 0x1);		   /* Wrap around (default) */
204
205		/* Set LCD mapping */
206		write_reg(par, 0xC0
207			| (0x0 & 0x1) << 2 /* Mirror Y OFF */
208			| (0x1 << 1)	   /* Mirror X ON */
209			| (0x0 & 0x1));    /* MS nibble last (default) */
210		break;
211	}
212
213	return 0;
214}
215
216static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
217{
218	u8 *vmem8 = (u8 *)(par->info->screen_buffer);
219	u8 *buf8 = par->txbuf.buf;
220	u16 *buf16 = par->txbuf.buf;
221	int line_length = par->info->fix.line_length;
222	int y_start = offset / line_length;
223	int y_end = (offset + len - 1) / line_length;
224	int x, y, i;
225	int ret = 0;
226
227	switch (par->pdata->display.buswidth) {
228	case 8:
229		switch (par->info->var.rotate) {
230		case 90:
231		case 270:
232			i = y_start * line_length;
233			for (y = y_start; y <= y_end; y++) {
234				for (x = 0; x < line_length; x += 2) {
235					*buf8 = vmem8[i] >> 4;
236					*buf8 |= vmem8[i + 1] & 0xF0;
237					buf8++;
238					i += 2;
239				}
240			}
241			break;
242		default:
243			/* Must be even because pages are two lines */
244			y_start &= 0xFE;
245			i = y_start * line_length;
246			for (y = y_start; y <= y_end; y += 2) {
247				for (x = 0; x < line_length; x++) {
248					*buf8 = vmem8[i] >> 4;
249					*buf8 |= vmem8[i + line_length] & 0xF0;
250					buf8++;
251					i++;
252				}
253				i += line_length;
254			}
255			break;
256		}
257		gpiod_set_value(par->gpio.dc, 1);
258
259		/* Write data */
260		ret = par->fbtftops.write(par, par->txbuf.buf, len / 2);
261		break;
262	case 9:
263		switch (par->info->var.rotate) {
264		case 90:
265		case 270:
266			i = y_start * line_length;
267			for (y = y_start; y <= y_end; y++) {
268				for (x = 0; x < line_length; x += 2) {
269					*buf16 = 0x100;
270					*buf16 |= vmem8[i] >> 4;
271					*buf16 |= vmem8[i + 1] & 0xF0;
272					buf16++;
273					i += 2;
274				}
275			}
276			break;
277		default:
278			/* Must be even because pages are two lines */
279			y_start &= 0xFE;
280			i = y_start * line_length;
281			for (y = y_start; y <= y_end; y += 2) {
282				for (x = 0; x < line_length; x++) {
283					*buf16 = 0x100;
284					*buf16 |= vmem8[i] >> 4;
285					*buf16 |= vmem8[i + line_length] & 0xF0;
286					buf16++;
287					i++;
288				}
289				i += line_length;
290			}
291			break;
292		}
293
294		/* Write data */
295		ret = par->fbtftops.write(par, par->txbuf.buf, len);
296		break;
297	default:
298		dev_err(par->info->device, "unsupported buswidth %d\n",
299			par->pdata->display.buswidth);
300	}
301
302	if (ret < 0)
303		dev_err(par->info->device, "write failed and returned: %d\n",
304			ret);
305
306	return ret;
307}
308
309static struct fbtft_display display = {
310	.txbuflen = -1,
311	.regwidth = 8,
312	.width = WIDTH,
313	.height = HEIGHT,
314	.bpp = BPP,
315	.fps = FPS,
316	.fbtftops = {
317		.write_vmem = write_vmem,
318		.init_display = init_display,
319		.set_addr_win = set_addr_win,
320		.set_var = set_var,
321		.blank = blank,
322	},
323};
324
325FBTFT_REGISTER_DRIVER(DRVNAME, "ultrachip,uc1611", &display);
326
327MODULE_ALIAS("spi:" DRVNAME);
328MODULE_ALIAS("platform:" DRVNAME);
329MODULE_ALIAS("spi:uc1611");
330MODULE_ALIAS("platform:uc1611");
331
332MODULE_DESCRIPTION("FB driver for the UC1611 LCD controller");
333MODULE_AUTHOR("Henri Chain");
334MODULE_LICENSE("GPL");
~/ Read-only file preview.