diff options
Diffstat (limited to 'drivers/gpu/drm/solomon/ssd130x.c')
-rw-r--r-- | drivers/gpu/drm/solomon/ssd130x.c | 154 |
1 files changed, 126 insertions, 28 deletions
diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index ce4dc20412e06..08394444dd6e3 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -39,16 +39,15 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 0 -#define SSD130X_DATA 0x40 -#define SSD130X_COMMAND 0x80 - +#define SSD130X_PAGE_COL_START_LOW 0x00 +#define SSD130X_PAGE_COL_START_HIGH 0x10 #define SSD130X_SET_ADDRESS_MODE 0x20 #define SSD130X_SET_COL_RANGE 0x21 #define SSD130X_SET_PAGE_RANGE 0x22 #define SSD130X_CONTRAST 0x81 #define SSD130X_SET_LOOKUP_TABLE 0x91 #define SSD130X_CHARGE_PUMP 0x8d -#define SSD130X_SEG_REMAP_ON 0xa1 +#define SSD130X_SET_SEG_REMAP 0xa0 #define SSD130X_DISPLAY_OFF 0xae #define SSD130X_SET_MULTIPLEX_RATIO 0xa8 #define SSD130X_DISPLAY_ON 0xaf @@ -61,7 +60,14 @@ #define SSD130X_SET_COM_PINS_CONFIG 0xda #define SSD130X_SET_VCOMH 0xdb -#define SSD130X_SET_COM_SCAN_DIR_MASK GENMASK(3, 2) +#define SSD130X_PAGE_COL_START_MASK GENMASK(3, 0) +#define SSD130X_PAGE_COL_START_HIGH_SET(val) FIELD_PREP(SSD130X_PAGE_COL_START_MASK, (val) >> 4) +#define SSD130X_PAGE_COL_START_LOW_SET(val) FIELD_PREP(SSD130X_PAGE_COL_START_MASK, (val)) +#define SSD130X_START_PAGE_ADDRESS_MASK GENMASK(2, 0) +#define SSD130X_START_PAGE_ADDRESS_SET(val) FIELD_PREP(SSD130X_START_PAGE_ADDRESS_MASK, (val)) +#define SSD130X_SET_SEG_REMAP_MASK GENMASK(0, 0) +#define SSD130X_SET_SEG_REMAP_SET(val) FIELD_PREP(SSD130X_SET_SEG_REMAP_MASK, (val)) +#define SSD130X_SET_COM_SCAN_DIR_MASK GENMASK(3, 3) #define SSD130X_SET_COM_SCAN_DIR_SET(val) FIELD_PREP(SSD130X_SET_COM_SCAN_DIR_MASK, (val)) #define SSD130X_SET_CLOCK_DIV_MASK GENMASK(3, 0) #define SSD130X_SET_CLOCK_DIV_SET(val) FIELD_PREP(SSD130X_SET_CLOCK_DIV_MASK, (val)) @@ -85,6 +91,38 @@ #define MAX_CONTRAST 255 +const struct ssd130x_deviceinfo ssd130x_variants[] = { + [SH1106_ID] = { + .default_vcomh = 0x40, + .default_dclk_div = 1, + .default_dclk_frq = 5, + .page_mode_only = 1, + }, + [SSD1305_ID] = { + .default_vcomh = 0x34, + .default_dclk_div = 1, + .default_dclk_frq = 7, + }, + [SSD1306_ID] = { + .default_vcomh = 0x20, + .default_dclk_div = 1, + .default_dclk_frq = 8, + .need_chargepump = 1, + }, + [SSD1307_ID] = { + .default_vcomh = 0x20, + .default_dclk_div = 2, + .default_dclk_frq = 12, + .need_pwm = 1, + }, + [SSD1309_ID] = { + .default_vcomh = 0x34, + .default_dclk_div = 1, + .default_dclk_frq = 10, + } +}; +EXPORT_SYMBOL_NS_GPL(ssd130x_variants, DRM_SSD130X); + static inline struct ssd130x_device *drm_to_ssd130x(struct drm_device *drm) { return container_of(drm, struct ssd130x_device, drm); @@ -128,6 +166,7 @@ out_end: return ret; } +/* Set address range for horizontal/vertical addressing modes */ static int ssd130x_set_col_range(struct ssd130x_device *ssd130x, u8 col_start, u8 cols) { @@ -164,6 +203,26 @@ static int ssd130x_set_page_range(struct ssd130x_device *ssd130x, return 0; } +/* Set page and column start address for page addressing mode */ +static int ssd130x_set_page_pos(struct ssd130x_device *ssd130x, + u8 page_start, u8 col_start) +{ + int ret; + u32 page, col_low, col_high; + + page = SSD130X_START_PAGE_ADDRESS | + SSD130X_START_PAGE_ADDRESS_SET(page_start); + col_low = SSD130X_PAGE_COL_START_LOW | + SSD130X_PAGE_COL_START_LOW_SET(col_start); + col_high = SSD130X_PAGE_COL_START_HIGH | + SSD130X_PAGE_COL_START_HIGH_SET(col_start); + ret = ssd130x_write_cmd(ssd130x, 3, page, col_low, col_high); + if (ret < 0) + return ret; + + return 0; +} + static int ssd130x_pwm_enable(struct ssd130x_device *ssd130x) { struct device *dev = ssd130x->dev; @@ -235,7 +294,7 @@ static void ssd130x_power_off(struct ssd130x_device *ssd130x) static int ssd130x_init(struct ssd130x_device *ssd130x) { - u32 precharge, dclk, com_invdir, compins, chargepump; + u32 precharge, dclk, com_invdir, compins, chargepump, seg_remap; int ret; /* Set initial contrast */ @@ -244,11 +303,11 @@ static int ssd130x_init(struct ssd130x_device *ssd130x) return ret; /* Set segment re-map */ - if (ssd130x->seg_remap) { - ret = ssd130x_write_cmd(ssd130x, 1, SSD130X_SEG_REMAP_ON); - if (ret < 0) - return ret; - } + seg_remap = (SSD130X_SET_SEG_REMAP | + SSD130X_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 | @@ -340,6 +399,11 @@ static int ssd130x_init(struct ssd130x_device *ssd130x) } } + /* 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); + /* Switch to horizontal addressing mode */ return ssd130x_write_cmd(ssd130x, 2, SSD130X_SET_ADDRESS_MODE, SSD130X_SET_ADDRESS_MODE_HORIZONTAL); @@ -353,11 +417,14 @@ static int ssd130x_update_rect(struct ssd130x_device *ssd130x, u8 *buf, unsigned int width = drm_rect_width(rect); unsigned int height = drm_rect_height(rect); unsigned int line_length = DIV_ROUND_UP(width, 8); - unsigned int pages = DIV_ROUND_UP(y % 8 + height, 8); + unsigned int pages = DIV_ROUND_UP(height, 8); + struct drm_device *drm = &ssd130x->drm; u32 array_idx = 0; int ret, i, j, k; u8 *data_array = NULL; + drm_WARN_ONCE(drm, y % 8 != 0, "y must be aligned to screen page\n"); + data_array = kcalloc(width, pages, GFP_KERNEL); if (!data_array) return -ENOMEM; @@ -391,21 +458,24 @@ static int ssd130x_update_rect(struct ssd130x_device *ssd130x, u8 *buf, * (5) A4 B4 C4 D4 E4 F4 G4 H4 */ - ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset + x, width); - if (ret < 0) - goto out_free; + if (!ssd130x->page_address_mode) { + /* Set address range for horizontal addressing mode */ + ret = ssd130x_set_col_range(ssd130x, ssd130x->col_offset + x, width); + if (ret < 0) + goto out_free; - ret = ssd130x_set_page_range(ssd130x, ssd130x->page_offset + y / 8, pages); - if (ret < 0) - goto out_free; + ret = ssd130x_set_page_range(ssd130x, ssd130x->page_offset + y / 8, pages); + if (ret < 0) + goto out_free; + } - for (i = y / 8; i < y / 8 + pages; i++) { + for (i = 0; i < pages; i++) { int m = 8; /* Last page may be partial */ - if (8 * (i + 1) > ssd130x->height) + if (8 * (y / 8 + i + 1) > ssd130x->height) m = ssd130x->height % 8; - for (j = x; j < x + width; j++) { + for (j = 0; j < width; j++) { u8 data = 0; for (k = 0; k < m; k++) { @@ -416,9 +486,29 @@ static int ssd130x_update_rect(struct ssd130x_device *ssd130x, u8 *buf, } 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) + goto out_free; + + ret = ssd130x_write_data(ssd130x, data_array, width); + if (ret < 0) + goto out_free; + + array_idx = 0; + } } - ret = ssd130x_write_data(ssd130x, data_array, width * pages); + /* 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); out_free: kfree(data_array); @@ -435,7 +525,8 @@ static void ssd130x_clear_screen(struct ssd130x_device *ssd130x) .y2 = ssd130x->height, }; - buf = kcalloc(ssd130x->width, ssd130x->height, GFP_KERNEL); + buf = kcalloc(DIV_ROUND_UP(ssd130x->width, 8), ssd130x->height, + GFP_KERNEL); if (!buf) return; @@ -449,14 +540,20 @@ static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct iosys_m { struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */ + unsigned int dst_pitch; int ret = 0; u8 *buf = NULL; - buf = kcalloc(fb->width, fb->height, GFP_KERNEL); + /* Align y to display page boundaries */ + rect->y1 = round_down(rect->y1, 8); + rect->y2 = min_t(unsigned int, round_up(rect->y2, 8), ssd130x->height); + + dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 8); + buf = kcalloc(dst_pitch, drm_rect_height(rect), GFP_KERNEL); if (!buf) return -ENOMEM; - drm_fb_xrgb8888_to_mono_reversed(buf, 0, vmap, fb, rect); + drm_fb_xrgb8888_to_mono(buf, dst_pitch, vmap, fb, rect); ssd130x_update_rect(ssd130x, buf, rect); @@ -794,6 +891,9 @@ struct ssd130x_device *ssd130x_probe(struct device *dev, struct regmap *regmap) ssd130x->regmap = regmap; ssd130x->device_info = device_get_match_data(dev); + if (ssd130x->device_info->page_mode_only) + ssd130x->page_address_mode = 1; + ssd130x_parse_properties(ssd130x); ret = ssd130x_get_resources(ssd130x); @@ -824,11 +924,9 @@ struct ssd130x_device *ssd130x_probe(struct device *dev, struct regmap *regmap) } EXPORT_SYMBOL_GPL(ssd130x_probe); -int ssd130x_remove(struct ssd130x_device *ssd130x) +void ssd130x_remove(struct ssd130x_device *ssd130x) { drm_dev_unplug(&ssd130x->drm); - - return 0; } EXPORT_SYMBOL_GPL(ssd130x_remove); |