diff --git a/drivers/gpu/drm/solomon/ssd130x-spi.c b/drivers/gpu/drm/solomon/ssd130x-spi.c index be50e5aa2c87..c41064aa8221 100644 --- a/drivers/gpu/drm/solomon/ssd130x-spi.c +++ b/drivers/gpu/drm/solomon/ssd130x-spi.c @@ -146,6 +146,11 @@ static const struct of_device_id ssd130x_of_match[] = { .compatible = "solomon,ssd1331", .data = &ssd130x_variants[SSD1331_ID], }, + /* ssd135x family */ + { + .compatible = "solomon,ssd1351", + .data = &ssd130x_variants[SSD1351_ID], + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ssd130x_of_match); @@ -171,6 +176,8 @@ static const struct spi_device_id ssd130x_spi_id[] = { { .name = "ssd1327", .driver_data = SSD1327_ID }, /* ssd133x family */ { .name = "ssd1331", .driver_data = SSD1331_ID }, + /* ssd135x family */ + { .name = "ssd1351", .driver_data = SSD1351_ID }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(spi, ssd130x_spi_id); diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index fd13250ef56a..324467ae9309 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -151,6 +151,37 @@ #define SSD133X_SET_REMAP_COLOR_DEPTH_MASK GENMASK(7, 6) #define SSD133X_COLOR_DEPTH_65K 0x1 +/* ssd135x commands */ +#define SSD135X_SET_COL_RANGE 0x15 +#define SSD135X_WRITE_RAM 0x5c +#define SSD135X_SET_ROW_RANGE 0x75 +#define SSD135X_SET_DISPLAY_START 0xa1 +#define SSD135X_SET_DISPLAY_OFFSET 0xa2 +#define SSD135X_SET_DISPLAY_NORMAL 0xa6 +#define SSD135X_SET_FUNCTION 0xab +#define SSD135X_SET_PHASE_LENGTH 0xb1 +#define SSD135X_SET_CLOCK_FREQ 0xb3 +#define SSD135X_SET_VSL 0xb4 +#define SSD135X_SET_GPIO 0xb5 +#define SSD135X_SET_PRECHARGE2 0xb6 +#define SSD135X_SET_PRECHARGE_VOLTAGE 0xbb +#define SSD135X_SET_VCOMH_VOLTAGE 0xbe +#define SSD135X_SET_CONTRAST 0xc1 +#define SSD135X_SET_CONTRAST_MASTER 0xc7 +#define SSD135X_SET_MUX_RATIO 0xca +#define SSD135X_SET_COMMAND_LOCK 0xfd + +/* ssd135x A/B/C channel contrast at full brightness (white balance) */ +#define SSD135X_DEFAULT_CONTRAST_A 0xc8 +#define SSD135X_DEFAULT_CONTRAST_B 0x80 +#define SSD135X_DEFAULT_CONTRAST_C 0xc8 + +/* ssd135x remap byte (data of SSD13XX_SET_SEG_REMAP) */ +#define SSD135X_SET_REMAP_COLOR_BGR BIT(2) +#define SSD135X_SET_REMAP_COM_SCAN BIT(4) +#define SSD135X_SET_REMAP_COM_SPLIT BIT(5) +#define SSD135X_SET_REMAP_65K BIT(6) + #define MAX_CONTRAST 255 const struct ssd130x_deviceinfo ssd130x_variants[] = { @@ -218,7 +249,14 @@ const struct ssd130x_deviceinfo ssd130x_variants[] = { .default_width = 96, .default_height = 64, .family_id = SSD133X_FAMILY, - } + }, + /* ssd135x family */ + [SSD1351_ID] = { + .default_width = 128, + .default_height = 128, + .family_id = SSD135X_FAMILY, + .cmd_params_are_data = true, + }, }; EXPORT_SYMBOL_NS_GPL(ssd130x_variants, "DRM_SSD130X"); @@ -261,9 +299,10 @@ static int ssd130x_write_data(struct ssd130x_device *ssd130x, const u8 *values, * Helper to write a command (SSD13XX_COMMAND) from a buffer. The first byte * is the command opcode and the following ones are its parameters. * - * Note that the ssd13xx protocol requires the opcode and each parameter to - * be written as a SSD13XX_COMMAND device register value. That is why a call - * to regmap_write(..., SSD13XX_COMMAND, ...) is done for each byte. + * By default every byte is written as a SSD13XX_COMMAND register value, hence + * the regmap_write() per byte. Controllers that set cmd_params_are_data expect + * the parameters on the data path instead, so only the opcode is written as + * SSD13XX_COMMAND and the rest as SSD13XX_DATA. */ static int ssd130x_write_cmds(struct ssd130x_device *ssd130x, const u8 *cmd, size_t len) @@ -271,6 +310,18 @@ static int ssd130x_write_cmds(struct ssd130x_device *ssd130x, const u8 *cmd, unsigned int i; int ret; + if (!len) + return 0; + + if (ssd130x->device_info->cmd_params_are_data) { + ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, cmd[0]); + /* A command with no parameters is complete after its opcode. */ + if (ret || len == 1) + return ret; + + return ssd130x_write_data(ssd130x, cmd + 1, len - 1); + } + for (i = 0; i < len; i++) { ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, cmd[i]); if (ret) @@ -617,6 +668,20 @@ static int ssd133x_set_contrast(struct ssd130x_device *ssd130x, u32 brightness) return ssd130x_run_cmd_seq(ssd130x, cmds); } +/* + * Same white balance calibration as for the ssd133x family, except that the + * ssd135x controllers take the three channels as parameters of a single + * command instead of one command per channel. + */ +static int ssd135x_set_contrast(struct ssd130x_device *ssd130x, u32 brightness) +{ + u8 a = ssd130x_scale_contrast(SSD135X_DEFAULT_CONTRAST_A, brightness); + u8 b = ssd130x_scale_contrast(SSD135X_DEFAULT_CONTRAST_B, brightness); + u8 c = ssd130x_scale_contrast(SSD135X_DEFAULT_CONTRAST_C, brightness); + + return ssd130x_write_cmd(ssd130x, 4, SSD135X_SET_CONTRAST, a, b, c); +} + static int ssd133x_init(struct ssd130x_device *ssd130x) { int ret; @@ -654,6 +719,47 @@ static int ssd133x_init(struct ssd130x_device *ssd130x) return ssd130x_run_cmd_seq(ssd130x, cmds); } +static int ssd135x_init(struct ssd130x_device *ssd130x) +{ + /* + * Horizontal address increment, COM split, reversed COM scan direction, + * BGR sub-pixel order and 65k (RGB565) color depth. Rotation is not + * supported, so the remap byte is fixed. + */ + const u8 remap = SSD135X_SET_REMAP_65K | SSD135X_SET_REMAP_COM_SPLIT | + SSD135X_SET_REMAP_COLOR_BGR | SSD135X_SET_REMAP_COM_SCAN; + const u8 cmds[] = { + /* Unlock the controller, then the extended command set */ + 2, SSD135X_SET_COMMAND_LOCK, 0x12, + 2, SSD135X_SET_COMMAND_LOCK, 0xb1, + 1, SSD13XX_DISPLAY_OFF, + 2, SSD135X_SET_CLOCK_FREQ, 0xf1, + 2, SSD135X_SET_MUX_RATIO, ssd130x->height - 1, + 3, SSD135X_SET_COL_RANGE, 0x00, ssd130x->width - 1, + 3, SSD135X_SET_ROW_RANGE, 0x00, ssd130x->height - 1, + 2, SSD135X_SET_DISPLAY_START, 0x00, + 2, SSD135X_SET_DISPLAY_OFFSET, 0x00, + 2, SSD135X_SET_GPIO, 0x00, + 2, SSD135X_SET_FUNCTION, 0x01, + 2, SSD135X_SET_PHASE_LENGTH, 0x32, + 4, SSD135X_SET_VSL, 0xa0, 0xb5, 0x55, + 2, SSD135X_SET_PRECHARGE_VOLTAGE, 0x17, + 2, SSD135X_SET_VCOMH_VOLTAGE, 0x05, + 2, SSD135X_SET_CONTRAST_MASTER, 0x0f, + 2, SSD135X_SET_PRECHARGE2, 0x01, + 1, SSD135X_SET_DISPLAY_NORMAL, + 2, SSD13XX_SET_SEG_REMAP, remap, + 0, + }; + int ret; + + ret = ssd130x_run_cmd_seq(ssd130x, cmds); + if (ret < 0) + return ret; + + return ssd135x_set_contrast(ssd130x, ssd130x->contrast); +} + static int ssd130x_update_rect(struct ssd130x_device *ssd130x, struct drm_rect *rect, u8 *buf, u8 *data_array) @@ -863,6 +969,44 @@ static int ssd133x_update_rect(struct ssd130x_device *ssd130x, return ret; } +static int ssd135x_update_rect(struct ssd130x_device *ssd130x, + struct drm_rect *rect, u8 *data_array, + unsigned int pitch) +{ + unsigned int x = rect->x1; + unsigned int y = rect->y1; + unsigned int columns = drm_rect_width(rect); + unsigned int rows = drm_rect_height(rect); + int ret; + + /* + * The pixel layout is the same as for the ssd133x family: one 65k + * color (RGB565) pixel per Segment, sent Segment by Segment when the + * (default) horizontal address increment mode is used. + * + * But unlike the ssd133x family, which starts accepting pixel data as + * soon as the address window has been programmed, the ssd135x family + * needs an explicit Write RAM command before the data is written. + */ + + /* Set column start and end */ + ret = ssd130x_write_cmd(ssd130x, 3, SSD135X_SET_COL_RANGE, x, x + columns - 1); + if (ret < 0) + return ret; + + /* Set row start and end */ + ret = ssd130x_write_cmd(ssd130x, 3, SSD135X_SET_ROW_RANGE, y, y + rows - 1); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x, 1, SSD135X_WRITE_RAM); + if (ret < 0) + return ret; + + /* Write out update in one go since horizontal addressing mode is used */ + return ssd130x_write_data(ssd130x, data_array, pitch * rows); +} + static void ssd130x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) { unsigned int pages = DIV_ROUND_UP(ssd130x->height, SSD130X_PAGE_HEIGHT); @@ -932,6 +1076,23 @@ static void ssd133x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) ssd130x_write_data(ssd130x, data_array, pitch * ssd130x->height); } +static void ssd135x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) +{ + struct drm_rect screen = DRM_RECT_INIT(0, 0, ssd130x->width, ssd130x->height); + const struct drm_format_info *fi; + unsigned int pitch; + + fi = drm_format_info(DRM_FORMAT_RGB565); + if (!fi) + return; + + pitch = drm_format_info_min_pitch(fi, 0, ssd130x->width); + + memset(data_array, 0, pitch * ssd130x->height); + + ssd135x_update_rect(ssd130x, &screen, data_array, pitch); +} + static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct iosys_map *vmap, struct drm_rect *rect, @@ -1005,6 +1166,30 @@ static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, return 0; } +static int ssd135x_fb_blit_rect(struct drm_framebuffer *fb, + const struct iosys_map *vmap, + struct drm_rect *rect, u8 *data_array, + struct drm_format_conv_state *fmtcnv_state) +{ + struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); + const struct drm_format_info *fi; + unsigned int dst_pitch; + struct iosys_map dst; + + fi = drm_format_info(DRM_FORMAT_RGB565); + if (!fi) + return -EINVAL; + + dst_pitch = drm_format_info_min_pitch(fi, 0, drm_rect_width(rect)); + + iosys_map_set_vaddr(&dst, data_array); + drm_fb_xrgb8888_to_rgb565be(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); + + ssd135x_update_rect(ssd130x, rect, data_array, dst_pitch); + + return 0; +} + static int ssd130x_primary_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state) { @@ -1247,6 +1432,45 @@ out_drm_dev_exit: drm_dev_exit(idx); } +static void ssd135x_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); + struct ssd130x_crtc_state *ssd130x_crtc_state = to_ssd130x_crtc_state(crtc_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_atomic_helper_damage_iter iter; + struct drm_device *drm = plane->dev; + struct drm_rect dst_clip; + struct drm_rect damage; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + + if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE)) + goto out_drm_dev_exit; + + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + dst_clip = plane_state->dst; + + if (!drm_rect_intersect(&dst_clip, &damage)) + continue; + + ssd135x_fb_blit_rect(fb, &shadow_plane_state->data[0], &dst_clip, + ssd130x_crtc_state->data_array, + &shadow_plane_state->fmtcnv_state); + } + + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); + +out_drm_dev_exit: + drm_dev_exit(idx); +} + static void ssd130x_primary_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_commit *state) { @@ -1319,6 +1543,31 @@ static void ssd133x_primary_plane_atomic_disable(struct drm_plane *plane, drm_dev_exit(idx); } +static void ssd135x_primary_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_device *drm = plane->dev; + struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); + struct drm_plane_state *plane_state; + struct drm_crtc_state *crtc_state; + struct ssd130x_crtc_state *ssd130x_crtc_state; + int idx; + + plane_state = drm_atomic_get_new_plane_state(state, plane); + if (!plane_state->crtc) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); + ssd130x_crtc_state = to_ssd130x_crtc_state(crtc_state); + + if (!drm_dev_enter(drm, &idx)) + return; + + ssd135x_clear_screen(ssd130x, ssd130x_crtc_state->data_array); + + drm_dev_exit(idx); +} + /* Called during init to allocate the plane's atomic state. */ static struct drm_plane_state *ssd130x_primary_plane_create_state(struct drm_plane *plane) { @@ -1387,7 +1636,13 @@ static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs[] .atomic_check = ssd133x_primary_plane_atomic_check, .atomic_update = ssd133x_primary_plane_atomic_update, .atomic_disable = ssd133x_primary_plane_atomic_disable, - } + }, + [SSD135X_FAMILY] = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = ssd133x_primary_plane_atomic_check, + .atomic_update = ssd135x_primary_plane_atomic_update, + .atomic_disable = ssd135x_primary_plane_atomic_disable, + }, }; static const struct drm_plane_funcs ssd130x_primary_plane_funcs = { @@ -1544,6 +1799,10 @@ static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs[] = { .mode_valid = ssd130x_crtc_mode_valid, .atomic_check = ssd133x_crtc_atomic_check, }, + [SSD135X_FAMILY] = { + .mode_valid = ssd130x_crtc_mode_valid, + .atomic_check = ssd133x_crtc_atomic_check, + }, }; static const struct drm_crtc_funcs ssd130x_crtc_funcs = { @@ -1631,6 +1890,31 @@ power_off: ssd130x_power_off(ssd130x); } +static void ssd135x_encoder_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_commit *state) +{ + struct drm_device *drm = encoder->dev; + struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); + int ret; + + ret = ssd130x_power_on(ssd130x); + if (ret) + return; + + ret = ssd135x_init(ssd130x); + if (ret) + goto power_off; + + ssd130x_write_cmd(ssd130x, 1, SSD13XX_DISPLAY_ON); + + backlight_enable(ssd130x->bl_dev); + + return; + +power_off: + ssd130x_power_off(ssd130x); +} + static void ssd130x_encoder_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_commit *state) { @@ -1656,7 +1940,11 @@ static const struct drm_encoder_helper_funcs ssd130x_encoder_helper_funcs[] = { [SSD133X_FAMILY] = { .atomic_enable = ssd133x_encoder_atomic_enable, .atomic_disable = ssd130x_encoder_atomic_disable, - } + }, + [SSD135X_FAMILY] = { + .atomic_enable = ssd135x_encoder_atomic_enable, + .atomic_disable = ssd130x_encoder_atomic_disable, + }, }; static const struct drm_encoder_funcs ssd130x_encoder_funcs = { @@ -1733,6 +2021,15 @@ static int ssd133x_update_bl(struct backlight_device *bdev) return ssd133x_set_contrast(ssd130x, ssd130x->contrast); } +static int ssd135x_update_bl(struct backlight_device *bdev) +{ + struct ssd130x_device *ssd130x = bl_get_data(bdev); + + ssd130x->contrast = backlight_get_brightness(bdev); + + return ssd135x_set_contrast(ssd130x, ssd130x->contrast); +} + static const struct backlight_ops ssd130xfb_bl_ops[] = { [SSD130X_FAMILY] = { .update_status = ssd130x_update_bl, @@ -1743,6 +2040,9 @@ static const struct backlight_ops ssd130xfb_bl_ops[] = { [SSD133X_FAMILY] = { .update_status = ssd133x_update_bl, }, + [SSD135X_FAMILY] = { + .update_status = ssd135x_update_bl, + }, }; static void ssd130x_parse_properties(struct ssd130x_device *ssd130x) diff --git a/drivers/gpu/drm/solomon/ssd130x.h b/drivers/gpu/drm/solomon/ssd130x.h index a4554018bb2a..fd81262f2ceb 100644 --- a/drivers/gpu/drm/solomon/ssd130x.h +++ b/drivers/gpu/drm/solomon/ssd130x.h @@ -26,7 +26,8 @@ enum ssd130x_family_ids { SSD130X_FAMILY, SSD132X_FAMILY, - SSD133X_FAMILY + SSD133X_FAMILY, + SSD135X_FAMILY, }; enum ssd130x_variants { @@ -42,6 +43,8 @@ enum ssd130x_variants { SSD1327_ID, /* ssd133x family */ SSD1331_ID, + /* ssd135x family */ + SSD1351_ID, NR_SSD130X_VARIANTS }; @@ -54,6 +57,11 @@ struct ssd130x_deviceinfo { bool need_pwm; bool need_chargepump; bool page_mode_only; + /* + * Whether the controller takes a command's parameters on the data path + * instead of as further command bytes. + */ + bool cmd_params_are_data; enum ssd130x_family_ids family_id; };