fb_uc1611.c (8455B)
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 fbtft_par_dbg(DEBUG_BLANK, par, "(%s=%s)\n", 139 __func__, on ? "true" : "false"); 140 141 if (on) 142 write_reg(par, 0xA8 | 0x00); 143 else 144 write_reg(par, 0xA8 | 0x07); 145 return 0; 146} 147 148static int set_var(struct fbtft_par *par) 149{ 150 /* par->info->fix.visual = FB_VISUAL_PSEUDOCOLOR; */ 151 par->info->var.grayscale = 1; 152 par->info->var.red.offset = 0; 153 par->info->var.red.length = 8; 154 par->info->var.green.offset = 0; 155 par->info->var.green.length = 8; 156 par->info->var.blue.offset = 0; 157 par->info->var.blue.length = 8; 158 par->info->var.transp.offset = 0; 159 par->info->var.transp.length = 0; 160 161 switch (par->info->var.rotate) { 162 case 90: 163 /* Set RAM address control */ 164 write_reg(par, 0x88 165 | (0x0 & 0x1) << 2 /* Increment positively */ 166 | (0x1 << 1) /* Increment page first */ 167 | 0x1); /* Wrap around (default) */ 168 169 /* Set LCD mapping */ 170 write_reg(par, 0xC0 171 | (0x0 & 0x1) << 2 /* Mirror Y OFF */ 172 | (0x0 & 0x1) << 1 /* Mirror X OFF */ 173 | (0x0 & 0x1)); /* MS nibble last (default) */ 174 break; 175 case 180: 176 /* Set RAM address control */ 177 write_reg(par, 0x88 178 | (0x0 & 0x1) << 2 /* Increment positively */ 179 | (0x0 & 0x1) << 1 /* Increment column first */ 180 | 0x1); /* Wrap around (default) */ 181 182 /* Set LCD mapping */ 183 write_reg(par, 0xC0 184 | (0x1 << 2) /* Mirror Y ON */ 185 | (0x0 & 0x1) << 1 /* Mirror X OFF */ 186 | (0x0 & 0x1)); /* MS nibble last (default) */ 187 break; 188 case 270: 189 /* Set RAM address control */ 190 write_reg(par, 0x88 191 | (0x0 & 0x1) << 2 /* Increment positively */ 192 | (0x1 << 1) /* Increment page first */ 193 | 0x1); /* Wrap around (default) */ 194 195 /* Set LCD mapping */ 196 write_reg(par, 0xC0 197 | (0x1 << 2) /* Mirror Y ON */ 198 | (0x1 << 1) /* Mirror X ON */ 199 | (0x0 & 0x1)); /* MS nibble last (default) */ 200 break; 201 default: 202 /* Set RAM address control */ 203 write_reg(par, 0x88 204 | (0x0 & 0x1) << 2 /* Increment positively */ 205 | (0x0 & 0x1) << 1 /* Increment column first */ 206 | 0x1); /* Wrap around (default) */ 207 208 /* Set LCD mapping */ 209 write_reg(par, 0xC0 210 | (0x0 & 0x1) << 2 /* Mirror Y OFF */ 211 | (0x1 << 1) /* Mirror X ON */ 212 | (0x0 & 0x1)); /* MS nibble last (default) */ 213 break; 214 } 215 216 return 0; 217} 218 219static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) 220{ 221 u8 *vmem8 = (u8 *)(par->info->screen_buffer); 222 u8 *buf8 = par->txbuf.buf; 223 u16 *buf16 = par->txbuf.buf; 224 int line_length = par->info->fix.line_length; 225 int y_start = offset / line_length; 226 int y_end = (offset + len - 1) / line_length; 227 int x, y, i; 228 int ret = 0; 229 230 switch (par->pdata->display.buswidth) { 231 case 8: 232 switch (par->info->var.rotate) { 233 case 90: 234 case 270: 235 i = y_start * line_length; 236 for (y = y_start; y <= y_end; y++) { 237 for (x = 0; x < line_length; x += 2) { 238 *buf8 = vmem8[i] >> 4; 239 *buf8 |= vmem8[i + 1] & 0xF0; 240 buf8++; 241 i += 2; 242 } 243 } 244 break; 245 default: 246 /* Must be even because pages are two lines */ 247 y_start &= 0xFE; 248 i = y_start * line_length; 249 for (y = y_start; y <= y_end; y += 2) { 250 for (x = 0; x < line_length; x++) { 251 *buf8 = vmem8[i] >> 4; 252 *buf8 |= vmem8[i + line_length] & 0xF0; 253 buf8++; 254 i++; 255 } 256 i += line_length; 257 } 258 break; 259 } 260 gpiod_set_value(par->gpio.dc, 1); 261 262 /* Write data */ 263 ret = par->fbtftops.write(par, par->txbuf.buf, len / 2); 264 break; 265 case 9: 266 switch (par->info->var.rotate) { 267 case 90: 268 case 270: 269 i = y_start * line_length; 270 for (y = y_start; y <= y_end; y++) { 271 for (x = 0; x < line_length; x += 2) { 272 *buf16 = 0x100; 273 *buf16 |= vmem8[i] >> 4; 274 *buf16 |= vmem8[i + 1] & 0xF0; 275 buf16++; 276 i += 2; 277 } 278 } 279 break; 280 default: 281 /* Must be even because pages are two lines */ 282 y_start &= 0xFE; 283 i = y_start * line_length; 284 for (y = y_start; y <= y_end; y += 2) { 285 for (x = 0; x < line_length; x++) { 286 *buf16 = 0x100; 287 *buf16 |= vmem8[i] >> 4; 288 *buf16 |= vmem8[i + line_length] & 0xF0; 289 buf16++; 290 i++; 291 } 292 i += line_length; 293 } 294 break; 295 } 296 297 /* Write data */ 298 ret = par->fbtftops.write(par, par->txbuf.buf, len); 299 break; 300 default: 301 dev_err(par->info->device, "unsupported buswidth %d\n", 302 par->pdata->display.buswidth); 303 } 304 305 if (ret < 0) 306 dev_err(par->info->device, "write failed and returned: %d\n", 307 ret); 308 309 return ret; 310} 311 312static struct fbtft_display display = { 313 .txbuflen = -1, 314 .regwidth = 8, 315 .width = WIDTH, 316 .height = HEIGHT, 317 .bpp = BPP, 318 .fps = FPS, 319 .fbtftops = { 320 .write_vmem = write_vmem, 321 .init_display = init_display, 322 .set_addr_win = set_addr_win, 323 .set_var = set_var, 324 .blank = blank, 325 }, 326}; 327 328FBTFT_REGISTER_DRIVER(DRVNAME, "ultrachip,uc1611", &display); 329 330MODULE_ALIAS("spi:" DRVNAME); 331MODULE_ALIAS("platform:" DRVNAME); 332MODULE_ALIAS("spi:uc1611"); 333MODULE_ALIAS("platform:uc1611"); 334 335MODULE_DESCRIPTION("FB driver for the UC1611 LCD controller"); 336MODULE_AUTHOR("Henri Chain"); 337MODULE_LICENSE("GPL");