/* * Helper to write data (SSD13XX_DATA) to the device.
*/ staticint ssd130x_write_data(struct ssd130x_device *ssd130x, u8 *values, int count)
{ return regmap_bulk_write(ssd130x->regmap, SSD13XX_DATA, values, count);
}
/* * Helper to write command (SSD13XX_COMMAND). The fist variadic argument * is the command to write and the following are the command options. * * Note that the ssd13xx protocol requires each command and option to be * written as a SSD13XX_COMMAND device register value. That is why a call * to regmap_write(..., SSD13XX_COMMAND, ...) is done for each argument.
*/ staticint ssd130x_write_cmd(struct ssd130x_device *ssd130x, int count, /* u8 cmd, u8 option, ... */...)
{
va_list ap;
u8 value; int ret;
va_start(ap, count);
do {
value = va_arg(ap, int);
ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, value); if (ret) goto out_end;
} while (--count);
out_end:
va_end(ap);
return ret;
}
/* Set address range for horizontal/vertical addressing modes */ staticint ssd130x_set_col_range(struct ssd130x_device *ssd130x,
u8 col_start, u8 cols)
{
u8 col_end = col_start + cols - 1; int ret;
if (col_start == ssd130x->col_start && col_end == ssd130x->col_end) return 0;
ret = ssd130x_write_cmd(ssd130x, 3, SSD130X_SET_COL_RANGE, col_start, col_end); if (ret < 0) return ret;
ssd130x->pwm = pwm_get(dev, NULL); if (IS_ERR(ssd130x->pwm)) {
dev_err(dev, "Could not get PWM from firmware description!\n"); return PTR_ERR(ssd130x->pwm);
}
/* Set initial contrast */
ret = ssd130x_write_cmd(ssd130x, 2, SSD13XX_CONTRAST, ssd130x->contrast); if (ret < 0) return ret;
/* Set segment re-map */
seg_remap = (SSD13XX_SET_SEG_REMAP |
SSD13XX_SET_SEG_REMAP_SET(ssd130x->seg_remap));
ret = ssd130x_write_cmd(ssd130x, 1, seg_remap); if (ret < 0) return ret;
/* Set COM direction */
com_invdir = (SSD130X_SET_COM_SCAN_DIR |
SSD130X_SET_COM_SCAN_DIR_SET(ssd130x->com_invdir));
ret = ssd130x_write_cmd(ssd130x, 1, com_invdir); if (ret < 0) return ret;
/* Set multiplex ratio value */
ret = ssd130x_write_cmd(ssd130x, 2, SSD13XX_SET_MULTIPLEX_RATIO, ssd130x->height - 1); if (ret < 0) return ret;
/* set display offset value */
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_DISPLAY_OFFSET, ssd130x->com_offset); if (ret < 0) return ret;
/* Set clock frequency */
dclk = (SSD130X_SET_CLOCK_DIV_SET(ssd130x->dclk_div - 1) |
SSD130X_SET_CLOCK_FREQ_SET(ssd130x->dclk_frq));
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_CLOCK_FREQ, dclk); if (ret < 0) return ret;
/* Set Area Color Mode ON/OFF & Low Power Display Mode */ if (ssd130x->area_color_enable || ssd130x->low_power) {
u32 mode = 0;
if (ssd130x->area_color_enable)
mode |= SSD130X_SET_AREA_COLOR_MODE_ENABLE;
if (ssd130x->low_power)
mode |= SSD130X_SET_AREA_COLOR_MODE_LOW_POWER;
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_AREA_COLOR_MODE, mode); if (ret < 0) return ret;
}
/* Set precharge period in number of ticks from the internal clock */
precharge = (SSD130X_SET_PRECHARGE_PERIOD1_SET(ssd130x->prechargep1) |
SSD130X_SET_PRECHARGE_PERIOD2_SET(ssd130x->prechargep2));
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_PRECHARGE_PERIOD, precharge); if (ret < 0) return ret;
/* Set COM pins configuration */
compins = BIT(1); /* * The COM scan mode field values are the inverse of the boolean DT * property "solomon,com-seq". The value 0b means scan from COM0 to * COM[N - 1] while 1b means scan from COM[N - 1] to COM0.
*/
scan_mode = !ssd130x->com_seq;
compins |= (SSD130X_SET_COM_PINS_CONFIG1_SET(scan_mode) |
SSD130X_SET_COM_PINS_CONFIG2_SET(ssd130x->com_lrremap));
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_COM_PINS_CONFIG, compins); if (ret < 0) return ret;
/* Set VCOMH */
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_VCOMH, ssd130x->vcomh); if (ret < 0) return ret;
/* Turn on the DC-DC Charge Pump */
chargepump = BIT(4);
if (ssd130x->device_info->need_chargepump)
chargepump |= BIT(2);
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_CHARGE_PUMP, chargepump); if (ret < 0) return ret;
/* Set lookup table */ if (ssd130x->lookup_table_set) { int i;
ret = ssd130x_write_cmd(ssd130x, 1, SSD130X_SET_LOOKUP_TABLE); if (ret < 0) return ret;
for (i = 0; i < ARRAY_SIZE(ssd130x->lookup_table); i++) {
u8 val = ssd130x->lookup_table[i];
if (val < 31 || val > 63)
dev_warn(ssd130x->dev, "lookup table index %d value out of range 31 <= %d <= 63\n",
i, val);
ret = ssd130x_write_cmd(ssd130x, 1, val); if (ret < 0) return ret;
}
}
/* Switch to page addressing mode */ if (ssd130x->page_address_mode) return ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_ADDRESS_MODE,
SSD130X_SET_ADDRESS_MODE_PAGE);
staticint ssd132x_init(struct ssd130x_device *ssd130x)
{ int ret;
/* Set initial contrast */
ret = ssd130x_write_cmd(ssd130x, 2, SSD13XX_CONTRAST, 0x80); if (ret < 0) return ret;
/* Set column start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD132X_SET_COL_RANGE, 0x00,
ssd130x->width / SSD132X_SEGMENT_WIDTH - 1); if (ret < 0) return ret;
/* Set row start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD132X_SET_ROW_RANGE, 0x00, ssd130x->height - 1); if (ret < 0) return ret; /* * Horizontal Address Increment * Re-map for Column Address, Nibble and COM * COM Split Odd Even
*/
ret = ssd130x_write_cmd(ssd130x, 2, SSD13XX_SET_SEG_REMAP, 0x53); if (ret < 0) return ret;
/* Set display start and offset */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_DISPLAY_START, 0x00); if (ret < 0) return ret;
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_DISPLAY_OFFSET, 0x00); if (ret < 0) return ret;
/* Set display mode normal */
ret = ssd130x_write_cmd(ssd130x, 1, SSD132X_SET_DISPLAY_NORMAL); if (ret < 0) return ret;
/* Set multiplex ratio value */
ret = ssd130x_write_cmd(ssd130x, 2, SSD13XX_SET_MULTIPLEX_RATIO, ssd130x->height - 1); if (ret < 0) return ret;
/* Set phase length */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_PHASE_LENGTH, 0x55); if (ret < 0) return ret;
/* Select default linear gray scale table */
ret = ssd130x_write_cmd(ssd130x, 1, SSD132X_SELECT_DEFAULT_TABLE); if (ret < 0) return ret;
/* Set clock frequency */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_CLOCK_FREQ, 0x01); if (ret < 0) return ret;
/* Enable internal VDD regulator */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_FUNCTION_SELECT_A, 0x1); if (ret < 0) return ret;
/* Set pre-charge period */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_PRECHARGE_PERIOD, 0x01); if (ret < 0) return ret;
/* Set pre-charge voltage */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_PRECHARGE_VOLTAGE, 0x08); if (ret < 0) return ret;
/* Set VCOMH voltage */
ret = ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_VCOMH_VOLTAGE, 0x07); if (ret < 0) return ret;
/* Enable second pre-charge and internal VSL */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_FUNCTION_SELECT_B, 0x62); if (ret < 0) return ret;
return 0;
}
staticint ssd133x_init(struct ssd130x_device *ssd130x)
{ int ret;
/* Set color A contrast */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_CONTRAST_A, 0x91); if (ret < 0) return ret;
/* Set color B contrast */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_CONTRAST_B, 0x50); if (ret < 0) return ret;
/* Set color C contrast */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_CONTRAST_C, 0x7d); if (ret < 0) return ret;
/* Set master current */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_SET_MASTER_CURRENT, 0x06); if (ret < 0) return ret;
/* Set column start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD133X_SET_COL_RANGE, 0x00, ssd130x->width - 1); if (ret < 0) return ret;
/* Set row start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD133X_SET_ROW_RANGE, 0x00, ssd130x->height - 1); if (ret < 0) return ret;
/* * Horizontal Address Increment * Normal order SA,SB,SC (e.g. RGB) * COM Split Odd Even * 256 color format
*/
ret = ssd130x_write_cmd(ssd130x, 2, SSD13XX_SET_SEG_REMAP, 0x20); if (ret < 0) return ret;
/* Set display start and offset */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_SET_DISPLAY_START, 0x00); if (ret < 0) return ret;
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_SET_DISPLAY_OFFSET, 0x00); if (ret < 0) return ret;
/* Set display mode normal */
ret = ssd130x_write_cmd(ssd130x, 1, SSD133X_SET_DISPLAY_NORMAL); if (ret < 0) return ret;
/* Set multiplex ratio value */
ret = ssd130x_write_cmd(ssd130x, 2, SSD13XX_SET_MULTIPLEX_RATIO, ssd130x->height - 1); if (ret < 0) return ret;
/* Set master configuration */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_SET_MASTER_CONFIG, 0x8e); if (ret < 0) return ret;
/* Set power mode */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_POWER_SAVE_MODE, 0x0b); if (ret < 0) return ret;
/* Set Phase 1 and 2 period */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_PHASES_PERIOD, 0x31); if (ret < 0) return ret;
/* Set clock divider */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_SET_CLOCK_FREQ, 0xf0); if (ret < 0) return ret;
/* Set pre-charge A */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_PRECHARGE_A, 0x64); if (ret < 0) return ret;
/* Set pre-charge B */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_PRECHARGE_B, 0x78); if (ret < 0) return ret;
/* Set pre-charge C */
ret = ssd130x_write_cmd(ssd130x, 2, SSD132X_SET_PRECHARGE_C, 0x64); if (ret < 0) return ret;
/* Set pre-charge level */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_SET_PRECHARGE_VOLTAGE, 0x3a); if (ret < 0) return ret;
/* Set VCOMH voltage */
ret = ssd130x_write_cmd(ssd130x, 2, SSD133X_SET_VCOMH_VOLTAGE, 0x3e); if (ret < 0) return ret;
drm_WARN_ONCE(drm, y % page_height != 0, "y must be aligned to screen page\n");
/* * The screen is divided in pages, each having a height of 8 * pixels, and the width of the screen. When sending a byte of * data to the controller, it gives the 8 bits for the current * column. I.e, the first byte are the 8 bits of the first * column, then the 8 bits for the second column, etc. * * * Representation of the screen, assuming it is 5 bits * wide. Each letter-number combination is a bit that controls * one pixel. * * A0 A1 A2 A3 A4 * B0 B1 B2 B3 B4 * C0 C1 C2 C3 C4 * D0 D1 D2 D3 D4 * E0 E1 E2 E3 E4 * F0 F1 F2 F3 F4 * G0 G1 G2 G3 G4 * H0 H1 H2 H3 H4 * * If you want to update this screen, you need to send 5 bytes: * (1) A0 B0 C0 D0 E0 F0 G0 H0 * (2) A1 B1 C1 D1 E1 F1 G1 H1 * (3) A2 B2 C2 D2 E2 F2 G2 H2 * (4) A3 B3 C3 D3 E3 F3 G3 H3 * (5) A4 B4 C4 D4 E4 F4 G4 H4
*/
if (!ssd130x->page_address_mode) {
u8 page_start;
/* Set address range for horizontal addressing mode */
ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset + x, width); if (ret < 0) return ret;
page_start = ssd130x->page_offset + y / page_height;
ret = ssd130x_set_page_range(ssd130x, page_start, pages); if (ret < 0) return ret;
}
for (i = 0; i < pages; i++) { int m = page_height;
/* Last page may be partial */ if (page_height * (y / page_height + i + 1) > ssd130x->height)
m = ssd130x->height % page_height;
for (j = 0; j < width; j++) {
u8 data = 0;
for (k = 0; k < m; k++) {
u32 idx = (page_height * i + k) * line_length + j / 8;
u8 byte = buf[idx];
u8 bit = (byte >> (j % 8)) & 1;
data |= bit << k;
}
data_array[array_idx++] = data;
}
/* * In page addressing mode, the start address needs to be reset, * and each page then needs to be written out separately.
*/ if (ssd130x->page_address_mode) {
ret = ssd130x_set_page_pos(ssd130x,
ssd130x->page_offset + i,
ssd130x->col_offset + x); if (ret < 0) return ret;
ret = ssd130x_write_data(ssd130x, data_array, width); if (ret < 0) return ret;
array_idx = 0;
}
}
/* Write out update in one go if we aren't using page addressing mode */ if (!ssd130x->page_address_mode)
ret = ssd130x_write_data(ssd130x, data_array, width * pages);
drm_WARN_ONCE(drm, x % segment_width != 0, "x must be aligned to screen segment\n");
/* * The screen is divided in Segment and Common outputs, where * COM0 to COM[N - 1] are the rows and SEG0 to SEG[M - 1] are * the columns. * * Each Segment has a 4-bit pixel and each Common output has a * row of pixels. When using the (default) horizontal address * increment mode, each byte of data sent to the controller has * two Segments (e.g: SEG0 and SEG1) that are stored in the lower * and higher nibbles of a single byte representing one column. * That is, the first byte are SEG0 (D0[3:0]) and SEG1 (D0[7:4]), * the second byte are SEG2 (D1[3:0]) and SEG3 (D1[7:4]) and so on.
*/
/* Set column start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD132X_SET_COL_RANGE, x / segment_width, columns - 1); if (ret < 0) return ret;
/* Set row start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD132X_SET_ROW_RANGE, y, rows - 1); if (ret < 0) return ret;
for (i = 0; i < height; i++) { /* Process pair of pixels and combine them into a single byte */ for (j = 0; j < width; j += segment_width) {
u8 n1 = buf[i * width + j];
u8 n2 = buf[i * width + j + 1];
/* Write out update in one go since horizontal addressing mode is used */
ret = ssd130x_write_data(ssd130x, data_array, columns * rows);
return ret;
}
staticint ssd133x_update_rect(struct ssd130x_device *ssd130x, struct drm_rect *rect, u8 *data_array, unsignedint pitch)
{ unsignedint x = rect->x1; unsignedint y = rect->y1; unsignedint columns = drm_rect_width(rect); unsignedint rows = drm_rect_height(rect); int ret;
/* * The screen is divided in Segment and Common outputs, where * COM0 to COM[N - 1] are the rows and SEG0 to SEG[M - 1] are * the columns. * * Each Segment has a 8-bit pixel and each Common output has a * row of pixels. When using the (default) horizontal address * increment mode, each byte of data sent to the controller has * a Segment (e.g: SEG0). * * When using the 256 color depth format, each pixel contains 3 * sub-pixels for color A, B and C. These have 3 bit, 3 bit and * 2 bits respectively.
*/
/* Set column start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD133X_SET_COL_RANGE, x, columns - 1); if (ret < 0) return ret;
/* Set row start and end */
ret = ssd130x_write_cmd(ssd130x, 3, SSD133X_SET_ROW_RANGE, y, rows - 1); if (ret < 0) return ret;
/* Write out update in one go since horizontal addressing mode is used */
ret = ssd130x_write_data(ssd130x, data_array, pitch * rows);
if (!ssd130x->page_address_mode) {
memset(data_array, 0, width * pages);
/* Set address range for horizontal addressing mode */
ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset, width); if (ret < 0) return;
ret = ssd130x_set_page_range(ssd130x, ssd130x->page_offset, pages); if (ret < 0) return;
/* Write out update in one go if we aren't using page addressing mode */
ssd130x_write_data(ssd130x, data_array, width * pages);
} else { /* * In page addressing mode, the start address needs to be reset, * and each page then needs to be written out separately.
*/
memset(data_array, 0, width);
for (i = 0; i < pages; i++) {
ret = ssd130x_set_page_pos(ssd130x,
ssd130x->page_offset + i,
ssd130x->col_offset); if (ret < 0) return;
ret = ssd130x_write_data(ssd130x, data_array, width); if (ret < 0) return;
}
}
}
/* Called during init to allocate the plane's atomic state. */ staticvoid ssd130x_primary_plane_reset(struct drm_plane *plane)
{ struct ssd130x_plane_state *ssd130x_state;
WARN_ON(plane->state);
ssd130x_state = kzalloc(sizeof(*ssd130x_state), GFP_KERNEL); if (!ssd130x_state) return;
ssd130x_state->data_array = kmalloc(pitch * ssd130x->height, GFP_KERNEL); if (!ssd130x_state->data_array) return -ENOMEM;
return 0;
}
/* Called during init to allocate the CRTC's atomic state. */ staticvoid ssd130x_crtc_reset(struct drm_crtc *crtc)
{ struct ssd130x_crtc_state *ssd130x_state;
WARN_ON(crtc->state);
ssd130x_state = kzalloc(sizeof(*ssd130x_state), GFP_KERNEL); if (!ssd130x_state) return;
ssd130x->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ssd130x->reset)) return dev_err_probe(dev, PTR_ERR(ssd130x->reset), "Failed to get reset gpio\n");
ssd130x->vcc_reg = devm_regulator_get(dev, "vcc"); if (IS_ERR(ssd130x->vcc_reg)) return dev_err_probe(dev, PTR_ERR(ssd130x->vcc_reg), "Failed to get VCC regulator\n");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.