The Solomon SSD132x controllers (such as the SSD1322, SSD1325 and SSD1327) are used by 16 grayscale dot matrix OLED panels, extend the driver to also support this chip family. Signed-off-by: Javier Martinez Canillas <javierm@xxxxxxxxxx> --- drivers/gpu/drm/solomon/ssd13xx-i2c.c | 13 ++ drivers/gpu/drm/solomon/ssd13xx-spi.c | 13 ++ drivers/gpu/drm/solomon/ssd13xx.c | 206 +++++++++++++++++++++++++- drivers/gpu/drm/solomon/ssd13xx.h | 5 + 4 files changed, 234 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd13xx-i2c.c b/drivers/gpu/drm/solomon/ssd13xx-i2c.c index d9cece374331..9cf78d206c6e 100644 --- a/drivers/gpu/drm/solomon/ssd13xx-i2c.c +++ b/drivers/gpu/drm/solomon/ssd13xx-i2c.c @@ -92,6 +92,19 @@ static const struct of_device_id ssd13xx_of_match[] = { .compatible = "solomon,ssd1309fb-i2c", .data = &ssd13xx_variants[SSD1309_ID], }, + /* ssd1302x family */ + { + .compatible = "solomon,ssd1322", + .data = &ssd13xx_variants[SSD1322_ID], + }, + { + .compatible = "solomon,ssd1325", + .data = &ssd13xx_variants[SSD1325_ID], + }, + { + .compatible = "solomon,ssd1327", + .data = &ssd13xx_variants[SSD1327_ID], + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ssd13xx_of_match); diff --git a/drivers/gpu/drm/solomon/ssd13xx-spi.c b/drivers/gpu/drm/solomon/ssd13xx-spi.c index 2416756686cc..55162e49f037 100644 --- a/drivers/gpu/drm/solomon/ssd13xx-spi.c +++ b/drivers/gpu/drm/solomon/ssd13xx-spi.c @@ -129,6 +129,19 @@ static const struct of_device_id ssd13xx_of_match[] = { .compatible = "solomon,ssd1309", .data = &ssd13xx_variants[SSD1309_ID], }, + /* ssd1302x family */ + { + .compatible = "solomon,ssd1322", + .data = &ssd13xx_variants[SSD1322_ID], + }, + { + .compatible = "solomon,ssd1325", + .data = &ssd13xx_variants[SSD1325_ID], + }, + { + .compatible = "solomon,ssd1327", + .data = &ssd13xx_variants[SSD1327_ID], + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ssd13xx_of_match); diff --git a/drivers/gpu/drm/solomon/ssd13xx.c b/drivers/gpu/drm/solomon/ssd13xx.c index b30224856518..bc53e7c80ffe 100644 --- a/drivers/gpu/drm/solomon/ssd13xx.c +++ b/drivers/gpu/drm/solomon/ssd13xx.c @@ -99,6 +99,24 @@ #define SSD130X_SET_AREA_COLOR_MODE_ENABLE 0x1e #define SSD130X_SET_AREA_COLOR_MODE_LOW_POWER 0x05 +/* ssd132x commands */ +#define SSD132X_SET_COL_RANGE 0x15 +#define SSD132X_SET_DEACTIVATE_SCROLL 0x2e +#define SSD132X_SET_ROW_RANGE 0x75 +#define SSD132X_SET_DISPLAY_START 0xa1 +#define SSD132X_SET_DISPLAY_OFFSET 0xa2 +#define SSD132X_SET_DISPLAY_NORMAL 0xa4 +#define SSD132X_SET_FUNCTION_SELECT_A 0xab +#define SSD132X_SET_PHASE_LENGTH 0xb1 +#define SSD132X_SET_CLOCK_FREQ 0xb3 +#define SSD132X_SET_GPIO 0xb5 +#define SSD132X_SET_PRECHARGE_PERIOD 0xb6 +#define SSD132X_SET_GRAY_SCALE_TABLE 0xb8 +#define SSD132X_SELECT_DEFAULT_TABLE 0xb9 +#define SSD132X_SET_PRECHARGE_VOLTAGE 0xbc +#define SSD130X_SET_VCOMH_VOLTAGE 0xbe +#define SSD132X_SET_FUNCTION_SELECT_B 0xd5 + #define MAX_CONTRAST 255 const struct ssd13xx_deviceinfo ssd13xx_variants[] = { @@ -144,6 +162,22 @@ const struct ssd13xx_deviceinfo ssd13xx_variants[] = { .default_width = 128, .default_height = 64, .family_id = SSD130X_FAMILY, + }, + /* ssd132x family */ + [SSD1322_ID] = { + .default_width = 480, + .default_height = 128, + .family_id = SSD132X_FAMILY, + }, + [SSD1325_ID] = { + .default_width = 128, + .default_height = 80, + .family_id = SSD132X_FAMILY, + }, + [SSD1327_ID] = { + .default_width = 128, + .default_height = 128, + .family_id = SSD132X_FAMILY, } }; EXPORT_SYMBOL_NS_GPL(ssd13xx_variants, DRM_SSD13XX); @@ -610,6 +644,156 @@ static void ssd130x_clear_screen(struct ssd13xx_device *ssd13xx, u8 *data_array) } } +static int ssd132x_init(struct ssd13xx_device *ssd13xx) +{ + int ret; + + /* Set initial contrast */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD13XX_CONTRAST, 0x80); + if (ret < 0) + return ret; + + /* Set column start and end */ + ret = ssd13xx_write_cmd(ssd13xx, 3, SSD132X_SET_COL_RANGE, 0x00, ssd13xx->width / 2 - 1); + if (ret < 0) + return ret; + + /* Set row start and end */ + ret = ssd13xx_write_cmd(ssd13xx, 3, SSD132X_SET_ROW_RANGE, 0x00, ssd13xx->height - 1); + if (ret < 0) + return ret; + /* + * Horizontal Address Increment + * Re-map for Column Address, Nibble and COM + * COM Split Odd Even + */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD13XX_SET_SEG_REMAP, 0x53); + if (ret < 0) + return ret; + + /* Set display start and offset */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_DISPLAY_START, 0x00); + if (ret < 0) + return ret; + + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_DISPLAY_OFFSET, 0x00); + if (ret < 0) + return ret; + + /* Set display mode normal */ + ret = ssd13xx_write_cmd(ssd13xx, 1, SSD132X_SET_DISPLAY_NORMAL); + if (ret < 0) + return ret; + + /* Set multiplex ratio value */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD13XX_SET_MULTIPLEX_RATIO, ssd13xx->height - 1); + if (ret < 0) + return ret; + + /* Set phase length */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_PHASE_LENGTH, 0x55); + if (ret < 0) + return ret; + + /* Select default linear gray scale table */ + ret = ssd13xx_write_cmd(ssd13xx, 1, SSD132X_SELECT_DEFAULT_TABLE); + if (ret < 0) + return ret; + + /* Set clock frequency */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_CLOCK_FREQ, 0x01); + if (ret < 0) + return ret; + + /* Enable internal VDD regulator */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_FUNCTION_SELECT_A, 0x1); + if (ret < 0) + return ret; + + /* Set pre-charge period */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_PRECHARGE_PERIOD, 0x01); + if (ret < 0) + return ret; + + /* Set pre-charge voltage */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_PRECHARGE_VOLTAGE, 0x08); + if (ret < 0) + return ret; + + /* Set VCOMH voltage */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD130X_SET_VCOMH_VOLTAGE, 0x07); + if (ret < 0) + return ret; + + /* Enable second pre-charge and internal VSL */ + ret = ssd13xx_write_cmd(ssd13xx, 2, SSD132X_SET_FUNCTION_SELECT_B, 0x62); + if (ret < 0) + return ret; + + return 0; +} + +static int ssd132x_update_rect(struct ssd13xx_device *ssd13xx, + struct drm_rect *rect, u8 *buf, + u8 *data_array) +{ + unsigned int x = rect->x1 / 2; + unsigned int y = rect->y1; + unsigned int width = drm_rect_width(rect); + unsigned int height = drm_rect_height(rect); + unsigned int columns = DIV_ROUND_UP(width, 2); + unsigned int rows = height; + u32 array_idx = 0; + int ret, i, j; + + /* + * 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 = ssd13xx_write_cmd(ssd13xx, 3, SSD132X_SET_COL_RANGE, x, columns - 1); + if (ret < 0) + return ret; + + /* Set row start and end */ + ret = ssd13xx_write_cmd(ssd13xx, 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 += 2) { + u8 n1 = buf[i * width + j]; + u8 n2 = buf[i * width + j + 1]; + + data_array[array_idx++] = (n2 << 4) | n1; + } + } + + /* Write out update in one go since horizontal addressing mode is used */ + ret = ssd13xx_write_data(ssd13xx, data_array, columns * rows); + + return ret; +} + +static void ssd132x_clear_screen(struct ssd13xx_device *ssd13xx, u8 *data_array) +{ + memset(data_array, 0, ssd13xx->data_array_size); + + /* Write out update in one go since horizontal addressing mode is used */ + ssd13xx_write_data(ssd13xx, data_array, ssd13xx->data_array_size); +} + static const struct ssd13xx_funcs ssd13xx_family_funcs[] = { [SSD130X_FAMILY] = { .init = ssd130x_init, @@ -617,6 +801,12 @@ static const struct ssd13xx_funcs ssd13xx_family_funcs[] = { .clear_screen = ssd130x_clear_screen, .fmt_convert = drm_fb_xrgb8888_to_mono, }, + [SSD132X_FAMILY] = { + .init = ssd132x_init, + .update_rect = ssd132x_update_rect, + .clear_screen = ssd132x_clear_screen, + .fmt_convert = drm_fb_xrgb8888_to_gray8, + } }; static int ssd13xx_fb_blit_rect(struct drm_framebuffer *fb, @@ -631,9 +821,12 @@ static int ssd13xx_fb_blit_rect(struct drm_framebuffer *fb, unsigned int dst_pitch; int ret = 0; - /* Align y to display page boundaries */ - rect->y1 = round_down(rect->y1, SSD130X_PAGE_HEIGHT); - rect->y2 = min_t(unsigned int, round_up(rect->y2, SSD130X_PAGE_HEIGHT), ssd13xx->height); + if (ssd13xx->device_info->family_id == SSD130X_FAMILY) { + /* Align y to display page boundaries */ + rect->y1 = round_down(rect->y1, SSD130X_PAGE_HEIGHT); + rect->y2 = min_t(unsigned int, round_up(rect->y2, SSD130X_PAGE_HEIGHT), + ssd13xx->height); + } dst_pitch = drm_format_info_min_pitch(fi, 0, drm_rect_width(rect)); @@ -1217,6 +1410,13 @@ static int ssd13xx_set_buffer_sizes(struct ssd13xx_device *ssd13xx, fi = drm_format_info(DRM_FORMAT_R1); break; + case SSD132X_FAMILY: + unsigned int columns = DIV_ROUND_UP(ssd13xx->width, 2); + unsigned int rows = ssd13xx->height; + + ssd13xx->data_array_size = columns * rows; + + fi = drm_format_info(DRM_FORMAT_R8); } if (!fi) diff --git a/drivers/gpu/drm/solomon/ssd13xx.h b/drivers/gpu/drm/solomon/ssd13xx.h index 399b0c8b5680..58083c7e08c8 100644 --- a/drivers/gpu/drm/solomon/ssd13xx.h +++ b/drivers/gpu/drm/solomon/ssd13xx.h @@ -27,6 +27,7 @@ enum ssd13xx_family_ids { SSD130X_FAMILY, + SSD132X_FAMILY, }; enum ssd13xx_variants { @@ -36,6 +37,10 @@ enum ssd13xx_variants { SSD1306_ID, SSD1307_ID, SSD1309_ID, + /* ssd132x family */ + SSD1322_ID, + SSD1325_ID, + SSD1327_ID, NR_SSD13XX_VARIANTS }; -- 2.41.0