FILE
fb_uc1611.c
File preview with syntax highlighting.
linux-6.19.14/drivers/staging/fbtft/fb_uc1611.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.