diff --git a/Documentation/devicetree/bindings/video/ssd1307fb.txt b/Documentation/devicetree/bindings/video/ssd1307fb.txt index 7a125427ff4bf88a8ef406222f6d1ce577d4fd6b..635efa3b2b2739624771b600e1da4c49b4364452 100644 --- a/Documentation/devicetree/bindings/video/ssd1307fb.txt +++ b/Documentation/devicetree/bindings/video/ssd1307fb.txt @@ -15,6 +15,16 @@ Required properties: Optional properties: - reset-active-low: Is the reset gpio is active on physical low? + - solomon,segment-no-remap: Display needs normal (non-inverted) data column + to segment mapping + - solomon,com-seq: Display uses sequential COM pin configuration + - solomon,com-lrremap: Display uses left-right COM pin remap + - solomon,com-invdir: Display uses inverted COM pin scan direction + - solomon,com-offset: Number of the COM pin wired to the first display line + - solomon,prechargep1: Length of deselect period (phase 1) in clock cycles. + - solomon,prechargep2: Length of precharge period (phase 2) in clock cycles. + This needs to be the higher, the higher the capacitance + of the OLED's pixels is [0]: Documentation/devicetree/bindings/pwm/pwm.txt @@ -26,3 +36,14 @@ ssd1307: oled@3c { reset-gpios = <&gpio2 7>; reset-active-low; }; + +ssd1306: oled@3c { + compatible = "solomon,ssd1306fb-i2c"; + reg = <0x3c>; + pwms = <&pwm 4 3000>; + reset-gpios = <&gpio2 7>; + reset-active-low; + solomon,com-lrremap; + solomon,com-invdir; + solomon,com-offset = <32>; +}; diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 8d34c5651187f1ab4e75bdb511dccefd76f6b1d5..8667c7769d8a95b5a2b24d8d165cc32a68c9f252 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -40,20 +40,34 @@ struct ssd1307fb_par; -struct ssd1307fb_ops { - int (*init)(struct ssd1307fb_par *); - int (*remove)(struct ssd1307fb_par *); +struct ssd1307fb_deviceinfo { + u32 default_vcomh; + u32 default_dclk_div; + u32 default_dclk_frq; + int need_pwm; + int need_chargepump; }; struct ssd1307fb_par { + u32 com_invdir; + u32 com_lrremap; + u32 com_offset; + u32 com_seq; + u32 contrast; + u32 dclk_div; + u32 dclk_frq; + struct ssd1307fb_deviceinfo *device_info; struct i2c_client *client; u32 height; struct fb_info *info; - struct ssd1307fb_ops *ops; u32 page_offset; + u32 prechargep1; + u32 prechargep2; struct pwm_device *pwm; u32 pwm_period; int reset; + u32 seg_remap; + u32 vcomh; u32 width; }; @@ -254,69 +268,46 @@ static struct fb_deferred_io ssd1307fb_defio = { .deferred_io = ssd1307fb_deferred_io, }; -static int ssd1307fb_ssd1307_init(struct ssd1307fb_par *par) +static int ssd1307fb_init(struct ssd1307fb_par *par) { int ret; + u32 precharge, dclk, com_invdir, compins; - par->pwm = pwm_get(&par->client->dev, NULL); - if (IS_ERR(par->pwm)) { - dev_err(&par->client->dev, "Could not get PWM from device tree!\n"); - return PTR_ERR(par->pwm); - } - - par->pwm_period = pwm_get_period(par->pwm); - /* Enable the PWM */ - pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period); - pwm_enable(par->pwm); - - dev_dbg(&par->client->dev, "Using PWM%d with a %dns period.\n", - par->pwm->pwm, par->pwm_period); - - /* Map column 127 of the OLED to segment 0 */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); - if (ret < 0) - return ret; - - /* Turn on the display */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); - if (ret < 0) - return ret; - - return 0; -} + if (par->device_info->need_pwm) { + par->pwm = pwm_get(&par->client->dev, NULL); + if (IS_ERR(par->pwm)) { + dev_err(&par->client->dev, "Could not get PWM from device tree!\n"); + return PTR_ERR(par->pwm); + } -static int ssd1307fb_ssd1307_remove(struct ssd1307fb_par *par) -{ - pwm_disable(par->pwm); - pwm_put(par->pwm); - return 0; -} + par->pwm_period = pwm_get_period(par->pwm); + /* Enable the PWM */ + pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period); + pwm_enable(par->pwm); -static struct ssd1307fb_ops ssd1307fb_ssd1307_ops = { - .init = ssd1307fb_ssd1307_init, - .remove = ssd1307fb_ssd1307_remove, -}; - -static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) -{ - int ret; + dev_dbg(&par->client->dev, "Using PWM%d with a %dns period.\n", + par->pwm->pwm, par->pwm_period); + }; /* Set initial contrast */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, 0x7f); - if (ret < 0) - return ret; - - /* Set COM direction */ - ret = ssd1307fb_write_cmd(par->client, 0xc8); + ret = ssd1307fb_write_cmd(par->client, par->contrast); if (ret < 0) return ret; /* Set segment re-map */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); + if (par->seg_remap) { + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); + if (ret < 0) + return ret; + }; + + /* Set COM direction */ + com_invdir = 0xc0 | (par->com_invdir & 0x1) << 3; + ret = ssd1307fb_write_cmd(par->client, com_invdir); if (ret < 0) return ret; @@ -334,7 +325,7 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, 0x20); + ret = ssd1307fb_write_cmd(par->client, par->com_offset); if (ret < 0) return ret; @@ -343,7 +334,8 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, 0xf0); + dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4; + ret = ssd1307fb_write_cmd(par->client, dclk); if (ret < 0) return ret; @@ -352,7 +344,8 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, 0x22); + precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4; + ret = ssd1307fb_write_cmd(par->client, precharge); if (ret < 0) return ret; @@ -361,7 +354,9 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, 0x22); + compins = 0x02 | (!par->com_seq & 0x1) << 4 + | (par->com_lrremap & 0x1) << 5; + ret = ssd1307fb_write_cmd(par->client, compins); if (ret < 0) return ret; @@ -370,7 +365,7 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, 0x49); + ret = ssd1307fb_write_cmd(par->client, par->vcomh); if (ret < 0) return ret; @@ -379,7 +374,8 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, 0x14); + ret = ssd1307fb_write_cmd(par->client, + (par->device_info->need_chargepump & 0x1 << 2) & 0x14); if (ret < 0) return ret; @@ -393,6 +389,7 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; + /* Set column range */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); if (ret < 0) return ret; @@ -405,6 +402,7 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; + /* Set page range */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); if (ret < 0) return ret; @@ -426,18 +424,28 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) return 0; } -static struct ssd1307fb_ops ssd1307fb_ssd1306_ops = { - .init = ssd1307fb_ssd1306_init, +static struct ssd1307fb_deviceinfo ssd1307fb_ssd1306_deviceinfo = { + .default_vcomh = 0x20, + .default_dclk_div = 1, + .default_dclk_frq = 8, + .need_chargepump = 1, +}; + +static struct ssd1307fb_deviceinfo ssd1307fb_ssd1307_deviceinfo = { + .default_vcomh = 0x20, + .default_dclk_div = 2, + .default_dclk_frq = 12, + .need_pwm = 1, }; static const struct of_device_id ssd1307fb_of_match[] = { { .compatible = "solomon,ssd1306fb-i2c", - .data = (void *)&ssd1307fb_ssd1306_ops, + .data = (void *)&ssd1307fb_ssd1306_deviceinfo, }, { .compatible = "solomon,ssd1307fb-i2c", - .data = (void *)&ssd1307fb_ssd1307_ops, + .data = (void *)&ssd1307fb_ssd1307_deviceinfo, }, {}, }; @@ -468,8 +476,8 @@ static int ssd1307fb_probe(struct i2c_client *client, par->info = info; par->client = client; - par->ops = (struct ssd1307fb_ops *)of_match_device(ssd1307fb_of_match, - &client->dev)->data; + par->device_info = (struct ssd1307fb_deviceinfo *)of_match_device( + ssd1307fb_of_match, &client->dev)->data; par->reset = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0); @@ -487,6 +495,27 @@ static int ssd1307fb_probe(struct i2c_client *client, if (of_property_read_u32(node, "solomon,page-offset", &par->page_offset)) par->page_offset = 1; + if (of_property_read_u32(node, "solomon,com-offset", &par->com_offset)) + par->com_offset = 0; + + if (of_property_read_u32(node, "solomon,prechargep1", &par->prechargep1)) + par->prechargep1 = 2; + + if (of_property_read_u32(node, "solomon,prechargep2", &par->prechargep2)) + par->prechargep2 = 2; + + par->seg_remap = !of_property_read_bool(node, "solomon,segment-no-remap"); + par->com_seq = of_property_read_bool(node, "solomon,com-seq"); + par->com_lrremap = of_property_read_bool(node, "solomon,com-lrremap"); + par->com_invdir = of_property_read_bool(node, "solomon,com-invdir"); + + par->contrast = 127; + par->vcomh = par->device_info->default_vcomh; + + /* Setup display timing */ + par->dclk_div = par->device_info->default_dclk_div; + par->dclk_frq = par->device_info->default_dclk_frq; + vmem_size = par->width * par->height / 8; vmem = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, @@ -539,11 +568,9 @@ static int ssd1307fb_probe(struct i2c_client *client, gpio_set_value(par->reset, 1); udelay(4); - if (par->ops->init) { - ret = par->ops->init(par); - if (ret) - goto reset_oled_error; - } + ret = ssd1307fb_init(par); + if (ret) + goto reset_oled_error; ret = register_framebuffer(info); if (ret) { @@ -556,8 +583,10 @@ static int ssd1307fb_probe(struct i2c_client *client, return 0; panel_init_error: - if (par->ops->remove) - par->ops->remove(par); + if (par->device_info->need_pwm) { + pwm_disable(par->pwm); + pwm_put(par->pwm); + }; reset_oled_error: fb_deferred_io_cleanup(info); fb_alloc_error: @@ -571,8 +600,10 @@ static int ssd1307fb_remove(struct i2c_client *client) struct ssd1307fb_par *par = info->par; unregister_framebuffer(info); - if (par->ops->remove) - par->ops->remove(par); + if (par->device_info->need_pwm) { + pwm_disable(par->pwm); + pwm_put(par->pwm); + }; fb_deferred_io_cleanup(info); __free_pages(__va(info->fix.smem_start), get_order(info->fix.smem_len)); framebuffer_release(info);