From bbc79089ae2bd0306db7f8dce85d56f9be65b205 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 22 Apr 2013 11:55:54 +0200 Subject: [PATCH 1/6] video: ssd1307fb: Add support for SSD1306 OLED controller The Solomon SSD1306 OLED controller is very similar to the SSD1307, except for the fact that the power is given through an external PWM for the 1307, and while the 1306 can generate its own power without any PWM. Signed-off-by: Maxime Ripard Signed-off-by: Tomi Valkeinen --- .../devicetree/bindings/video/ssd1307fb.txt | 10 +- drivers/video/ssd1307fb.c | 273 +++++++++++++----- 2 files changed, 209 insertions(+), 74 deletions(-) diff --git a/Documentation/devicetree/bindings/video/ssd1307fb.txt b/Documentation/devicetree/bindings/video/ssd1307fb.txt index 3d0060cff062..7a125427ff4b 100644 --- a/Documentation/devicetree/bindings/video/ssd1307fb.txt +++ b/Documentation/devicetree/bindings/video/ssd1307fb.txt @@ -1,13 +1,17 @@ * Solomon SSD1307 Framebuffer Driver Required properties: - - compatible: Should be "solomon,ssd1307fb-". The only supported bus for - now is i2c. + - compatible: Should be "solomon,fb-". The only supported bus for + now is i2c, and the supported chips are ssd1306 and ssd1307. - reg: Should contain address of the controller on the I2C bus. Most likely 0x3c or 0x3d - pwm: Should contain the pwm to use according to the OF device tree PWM - specification [0] + specification [0]. Only required for the ssd1307. - reset-gpios: Should contain the GPIO used to reset the OLED display + - solomon,height: Height in pixel of the screen driven by the controller + - solomon,width: Width in pixel of the screen driven by the controller + - solomon,page-offset: Offset of pages (band of 8 pixels) that the screen is + mapped to. Optional properties: - reset-active-low: Is the reset gpio is active on physical low? diff --git a/drivers/video/ssd1307fb.c b/drivers/video/ssd1307fb.c index 9ef05d3ef68a..a0d6f96ec4e4 100644 --- a/drivers/video/ssd1307fb.c +++ b/drivers/video/ssd1307fb.c @@ -16,24 +16,39 @@ #include #include -#define SSD1307FB_WIDTH 96 -#define SSD1307FB_HEIGHT 16 - #define SSD1307FB_DATA 0x40 #define SSD1307FB_COMMAND 0x80 #define SSD1307FB_CONTRAST 0x81 +#define SSD1307FB_CHARGE_PUMP 0x8d #define SSD1307FB_SEG_REMAP_ON 0xa1 #define SSD1307FB_DISPLAY_OFF 0xae +#define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8 #define SSD1307FB_DISPLAY_ON 0xaf #define SSD1307FB_START_PAGE_ADDRESS 0xb0 +#define SSD1307FB_SET_DISPLAY_OFFSET 0xd3 +#define SSD1307FB_SET_CLOCK_FREQ 0xd5 +#define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9 +#define SSD1307FB_SET_COM_PINS_CONFIG 0xda +#define SSD1307FB_SET_VCOMH 0xdb + +struct ssd1307fb_par; + +struct ssd1307fb_ops { + int (*init)(struct ssd1307fb_par *); + int (*remove)(struct ssd1307fb_par *); +}; struct ssd1307fb_par { struct i2c_client *client; + u32 height; struct fb_info *info; + struct ssd1307fb_ops *ops; + u32 page_offset; struct pwm_device *pwm; u32 pwm_period; int reset; + u32 width; }; static struct fb_fix_screeninfo ssd1307fb_fix = { @@ -43,15 +58,10 @@ static struct fb_fix_screeninfo ssd1307fb_fix = { .xpanstep = 0, .ypanstep = 0, .ywrapstep = 0, - .line_length = SSD1307FB_WIDTH / 8, .accel = FB_ACCEL_NONE, }; static struct fb_var_screeninfo ssd1307fb_var = { - .xres = SSD1307FB_WIDTH, - .yres = SSD1307FB_HEIGHT, - .xres_virtual = SSD1307FB_WIDTH, - .yres_virtual = SSD1307FB_HEIGHT, .bits_per_pixel = 1, }; @@ -134,16 +144,17 @@ static void ssd1307fb_update_display(struct ssd1307fb_par *par) * (5) A4 B4 C4 D4 E4 F4 G4 H4 */ - for (i = 0; i < (SSD1307FB_HEIGHT / 8); i++) { - ssd1307fb_write_cmd(par->client, SSD1307FB_START_PAGE_ADDRESS + (i + 1)); + for (i = 0; i < (par->height / 8); i++) { + ssd1307fb_write_cmd(par->client, + SSD1307FB_START_PAGE_ADDRESS + i + par->page_offset); ssd1307fb_write_cmd(par->client, 0x00); ssd1307fb_write_cmd(par->client, 0x10); - for (j = 0; j < SSD1307FB_WIDTH; j++) { + for (j = 0; j < par->width; j++) { u8 buf = 0; for (k = 0; k < 8; k++) { - u32 page_length = SSD1307FB_WIDTH * i; - u32 index = page_length + (SSD1307FB_WIDTH * k + j) / 8; + u32 page_length = par->width * i; + u32 index = page_length + (par->width * k + j) / 8; u8 byte = *(vmem + index); u8 bit = byte & (1 << (j % 8)); bit = bit >> (j % 8); @@ -227,16 +238,147 @@ static struct fb_deferred_io ssd1307fb_defio = { .deferred_io = ssd1307fb_deferred_io, }; +static int ssd1307fb_ssd1307_init(struct ssd1307fb_par *par) +{ + int ret; + + 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; +} + +static int ssd1307fb_ssd1307_remove(struct ssd1307fb_par *par) +{ + pwm_disable(par->pwm); + pwm_put(par->pwm); + return 0; +} + +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; + + /* Set initial contrast */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); + ret = ret & ssd1307fb_write_cmd(par->client, 0x7f); + if (ret < 0) + return ret; + + /* Set COM direction */ + ret = ssd1307fb_write_cmd(par->client, 0xc8); + if (ret < 0) + return ret; + + /* Set segment re-map */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); + if (ret < 0) + return ret; + + /* Set multiplex ratio value */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO); + ret = ret & ssd1307fb_write_cmd(par->client, par->height - 1); + if (ret < 0) + return ret; + + /* set display offset value */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_DISPLAY_OFFSET); + ret = ssd1307fb_write_cmd(par->client, 0x20); + if (ret < 0) + return ret; + + /* Set clock frequency */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_CLOCK_FREQ); + ret = ret & ssd1307fb_write_cmd(par->client, 0xf0); + if (ret < 0) + return ret; + + /* Set precharge period in number of ticks from the internal clock */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PRECHARGE_PERIOD); + ret = ret & ssd1307fb_write_cmd(par->client, 0x22); + if (ret < 0) + return ret; + + /* Set COM pins configuration */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COM_PINS_CONFIG); + ret = ret & ssd1307fb_write_cmd(par->client, 0x22); + if (ret < 0) + return ret; + + /* Set VCOMH */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_VCOMH); + ret = ret & ssd1307fb_write_cmd(par->client, 0x49); + if (ret < 0) + return ret; + + /* Turn on the DC-DC Charge Pump */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CHARGE_PUMP); + ret = ret & ssd1307fb_write_cmd(par->client, 0x14); + 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; +} + +static struct ssd1307fb_ops ssd1307fb_ssd1306_ops = { + .init = ssd1307fb_ssd1306_init, +}; + +static const struct of_device_id ssd1307fb_of_match[] = { + { + .compatible = "solomon,ssd1306fb-i2c", + .data = (void *)&ssd1307fb_ssd1306_ops, + }, + { + .compatible = "solomon,ssd1307fb-i2c", + .data = (void *)&ssd1307fb_ssd1307_ops, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ssd1307fb_of_match); + static int ssd1307fb_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct fb_info *info; - u32 vmem_size = SSD1307FB_WIDTH * SSD1307FB_HEIGHT / 8; + struct device_node *node = client->dev.of_node; + u32 vmem_size; struct ssd1307fb_par *par; u8 *vmem; int ret; - if (!client->dev.of_node) { + if (!node) { dev_err(&client->dev, "No device tree data found!\n"); return -EINVAL; } @@ -247,6 +389,31 @@ static int ssd1307fb_probe(struct i2c_client *client, return -ENOMEM; } + par = info->par; + par->info = info; + par->client = client; + + par->ops = (struct ssd1307fb_ops *)of_match_device(ssd1307fb_of_match, + &client->dev)->data; + + par->reset = of_get_named_gpio(client->dev.of_node, + "reset-gpios", 0); + if (!gpio_is_valid(par->reset)) { + ret = -EINVAL; + goto fb_alloc_error; + } + + if (of_property_read_u32(node, "solomon,width", &par->width)) + par->width = 96; + + if (of_property_read_u32(node, "solomon,height", &par->height)) + par->width = 16; + + if (of_property_read_u32(node, "solomon,page-offset", &par->page_offset)) + par->page_offset = 1; + + vmem_size = par->width * par->height / 8; + vmem = devm_kzalloc(&client->dev, vmem_size, GFP_KERNEL); if (!vmem) { dev_err(&client->dev, "Couldn't allocate graphical memory.\n"); @@ -256,9 +423,15 @@ static int ssd1307fb_probe(struct i2c_client *client, info->fbops = &ssd1307fb_ops; info->fix = ssd1307fb_fix; + info->fix.line_length = par->width / 8; info->fbdefio = &ssd1307fb_defio; info->var = ssd1307fb_var; + info->var.xres = par->width; + info->var.xres_virtual = par->width; + info->var.yres = par->height; + info->var.yres_virtual = par->height; + info->var.red.length = 1; info->var.red.offset = 0; info->var.green.length = 1; @@ -272,17 +445,6 @@ static int ssd1307fb_probe(struct i2c_client *client, fb_deferred_io_init(info); - par = info->par; - par->info = info; - par->client = client; - - par->reset = of_get_named_gpio(client->dev.of_node, - "reset-gpios", 0); - if (!gpio_is_valid(par->reset)) { - ret = -EINVAL; - goto reset_oled_error; - } - ret = devm_gpio_request_one(&client->dev, par->reset, GPIOF_OUT_INIT_HIGH, "oled-reset"); @@ -293,23 +455,6 @@ static int ssd1307fb_probe(struct i2c_client *client, goto reset_oled_error; } - par->pwm = pwm_get(&client->dev, NULL); - if (IS_ERR(par->pwm)) { - dev_err(&client->dev, "Could not get PWM from device tree!\n"); - ret = PTR_ERR(par->pwm); - goto pwm_error; - } - - par->pwm_period = pwm_get_period(par->pwm); - - dev_dbg(&client->dev, "Using PWM%d with a %dns period.\n", par->pwm->pwm, par->pwm_period); - - ret = register_framebuffer(info); - if (ret) { - dev_err(&client->dev, "Couldn't register the framebuffer\n"); - goto fbreg_error; - } - i2c_set_clientdata(client, info); /* Reset the screen */ @@ -318,34 +463,25 @@ static int ssd1307fb_probe(struct i2c_client *client, gpio_set_value(par->reset, 1); udelay(4); - /* Enable the PWM */ - pwm_config(par->pwm, par->pwm_period / 2, par->pwm_period); - pwm_enable(par->pwm); - - /* Map column 127 of the OLED to segment 0 */ - ret = ssd1307fb_write_cmd(client, SSD1307FB_SEG_REMAP_ON); - if (ret < 0) { - dev_err(&client->dev, "Couldn't remap the screen.\n"); - goto remap_error; + if (par->ops->init) { + ret = par->ops->init(par); + if (ret) + goto reset_oled_error; } - /* Turn on the display */ - ret = ssd1307fb_write_cmd(client, SSD1307FB_DISPLAY_ON); - if (ret < 0) { - dev_err(&client->dev, "Couldn't turn the display on.\n"); - goto remap_error; + ret = register_framebuffer(info); + if (ret) { + dev_err(&client->dev, "Couldn't register the framebuffer\n"); + goto panel_init_error; } dev_info(&client->dev, "fb%d: %s framebuffer device registered, using %d bytes of video memory\n", info->node, info->fix.id, vmem_size); return 0; -remap_error: - unregister_framebuffer(info); - pwm_disable(par->pwm); -fbreg_error: - pwm_put(par->pwm); -pwm_error: +panel_init_error: + if (par->ops->remove) + par->ops->remove(par); reset_oled_error: fb_deferred_io_cleanup(info); fb_alloc_error: @@ -359,8 +495,8 @@ static int ssd1307fb_remove(struct i2c_client *client) struct ssd1307fb_par *par = info->par; unregister_framebuffer(info); - pwm_disable(par->pwm); - pwm_put(par->pwm); + if (par->ops->remove) + par->ops->remove(par); fb_deferred_io_cleanup(info); framebuffer_release(info); @@ -368,17 +504,12 @@ static int ssd1307fb_remove(struct i2c_client *client) } static const struct i2c_device_id ssd1307fb_i2c_id[] = { + { "ssd1306fb", 0 }, { "ssd1307fb", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id); -static const struct of_device_id ssd1307fb_of_match[] = { - { .compatible = "solomon,ssd1307fb-i2c" }, - {}, -}; -MODULE_DEVICE_TABLE(of, ssd1307fb_of_match); - static struct i2c_driver ssd1307fb_driver = { .probe = ssd1307fb_probe, .remove = ssd1307fb_remove, From 9f7714d4638382d5f84fefd322983925935da742 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 22 Apr 2013 12:02:23 +0200 Subject: [PATCH 2/6] video: ssd1307fb: Rework the communication functions To efficiently send a whole page to the display, we need to be able to manipulate more easily the data arrays that has to be sent to the OLED controller. As such, this patch introduces a ssd1307fb_array structure that handles both the small header to be sent over i2c, which contains the type of information sent, and the raw bytes after that. Signed-off-by: Maxime Ripard Signed-off-by: Tomi Valkeinen --- drivers/video/ssd1307fb.c | 77 +++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/drivers/video/ssd1307fb.c b/drivers/video/ssd1307fb.c index a0d6f96ec4e4..9daf058917a7 100644 --- a/drivers/video/ssd1307fb.c +++ b/drivers/video/ssd1307fb.c @@ -51,6 +51,11 @@ struct ssd1307fb_par { u32 width; }; +struct ssd1307fb_array { + u8 type; + u8 data[0]; +}; + static struct fb_fix_screeninfo ssd1307fb_fix = { .id = "Solomon SSD1307", .type = FB_TYPE_PACKED_PIXELS, @@ -65,49 +70,67 @@ static struct fb_var_screeninfo ssd1307fb_var = { .bits_per_pixel = 1, }; -static int ssd1307fb_write_array(struct i2c_client *client, u8 type, u8 *cmd, u32 len) +static struct ssd1307fb_array *ssd1307fb_alloc_array(u32 len, u8 type) { - u8 *buf; - int ret = 0; + struct ssd1307fb_array *array; - buf = kzalloc(len + 1, GFP_KERNEL); - if (!buf) { - dev_err(&client->dev, "Couldn't allocate sending buffer.\n"); - return -ENOMEM; - } + array = kzalloc(sizeof(struct ssd1307fb_array) + len, GFP_KERNEL); + if (!array) + return NULL; - buf[0] = type; - memcpy(buf + 1, cmd, len); + array->type = type; - ret = i2c_master_send(client, buf, len + 1); - if (ret != len + 1) { - dev_err(&client->dev, "Couldn't send I2C command.\n"); - goto error; - } - -error: - kfree(buf); - return ret; + return array; } -static inline int ssd1307fb_write_cmd_array(struct i2c_client *client, u8 *cmd, u32 len) +static int ssd1307fb_write_array(struct i2c_client *client, + struct ssd1307fb_array *array, u32 len) { - return ssd1307fb_write_array(client, SSD1307FB_COMMAND, cmd, len); + int ret; + + len += sizeof(struct ssd1307fb_array); + + ret = i2c_master_send(client, (u8 *)array, len); + if (ret != len) { + dev_err(&client->dev, "Couldn't send I2C command.\n"); + return ret; + } + + return 0; } static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd) { - return ssd1307fb_write_cmd_array(client, &cmd, 1); -} + struct ssd1307fb_array *array; + int ret; -static inline int ssd1307fb_write_data_array(struct i2c_client *client, u8 *cmd, u32 len) -{ - return ssd1307fb_write_array(client, SSD1307FB_DATA, cmd, len); + array = ssd1307fb_alloc_array(1, SSD1307FB_COMMAND); + if (!array) + return -ENOMEM; + + array->data[0] = cmd; + + ret = ssd1307fb_write_array(client, array, 1); + kfree(array); + + return ret; } static inline int ssd1307fb_write_data(struct i2c_client *client, u8 data) { - return ssd1307fb_write_data_array(client, &data, 1); + struct ssd1307fb_array *array; + int ret; + + array = ssd1307fb_alloc_array(1, SSD1307FB_DATA); + if (!array) + return -ENOMEM; + + array->data[0] = data; + + ret = ssd1307fb_write_array(client, array, 1); + kfree(array); + + return ret; } static void ssd1307fb_update_display(struct ssd1307fb_par *par) From 3394e645a88c722396fc1b03c31a3ffc158744ad Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 22 Apr 2013 12:02:24 +0200 Subject: [PATCH 3/6] video: ssd1307fb: Speed up the communication with the controller The code until now was sending only 1pixel-wide page segment at once, and started a new transfer every time. It has proven very inefficient, because for one byte to display on the screen, we had to actually send 3 bytes over I2C: the address, the type of data that was going to the controller, and then the actual data. This patches changes that by sending a whole page at once, avoiding most of this expensive overhead. Signed-off-by: Maxime Ripard Signed-off-by: Tomi Valkeinen --- drivers/video/ssd1307fb.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/video/ssd1307fb.c b/drivers/video/ssd1307fb.c index 9daf058917a7..04a435886855 100644 --- a/drivers/video/ssd1307fb.c +++ b/drivers/video/ssd1307fb.c @@ -168,23 +168,28 @@ static void ssd1307fb_update_display(struct ssd1307fb_par *par) */ for (i = 0; i < (par->height / 8); i++) { + struct ssd1307fb_array *array; ssd1307fb_write_cmd(par->client, SSD1307FB_START_PAGE_ADDRESS + i + par->page_offset); ssd1307fb_write_cmd(par->client, 0x00); ssd1307fb_write_cmd(par->client, 0x10); + array = ssd1307fb_alloc_array(par->width, SSD1307FB_DATA); + for (j = 0; j < par->width; j++) { - u8 buf = 0; + array->data[j] = 0; for (k = 0; k < 8; k++) { u32 page_length = par->width * i; u32 index = page_length + (par->width * k + j) / 8; u8 byte = *(vmem + index); u8 bit = byte & (1 << (j % 8)); bit = bit >> (j % 8); - buf |= bit << k; + array->data[j] |= bit << k; } - ssd1307fb_write_data(par->client, buf); } + + ssd1307fb_write_array(par->client, array, par->width); + kfree(array); } } From 301bc0675b677a98475187050d56cd2b39ff0acf Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 22 Apr 2013 12:02:25 +0200 Subject: [PATCH 4/6] video: ssd1307fb: Make use of horizontal addressing mode By default, the ssd1307 controller uses an addressing mode called page addressing. This mode only increments the column cursor in memory when writing data but will not increments the page cursor when we are at the end of the page. However, the controller supports another addressing mode, called horizontal addressing, that will maintain both the page and column cursors when writing data to the controller. That means that we can just remove the code that increments the current page address and reset the column cursor when reaching the end of the line, allowing to have a lower data overhead, and a simpler driver. Signed-off-by: Maxime Ripard Signed-off-by: Tomi Valkeinen --- drivers/video/ssd1307fb.c | 51 +++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/drivers/video/ssd1307fb.c b/drivers/video/ssd1307fb.c index 04a435886855..44967c8fef2b 100644 --- a/drivers/video/ssd1307fb.c +++ b/drivers/video/ssd1307fb.c @@ -19,6 +19,12 @@ #define SSD1307FB_DATA 0x40 #define SSD1307FB_COMMAND 0x80 +#define SSD1307FB_SET_ADDRESS_MODE 0x20 +#define SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL (0x00) +#define SSD1307FB_SET_ADDRESS_MODE_VERTICAL (0x01) +#define SSD1307FB_SET_ADDRESS_MODE_PAGE (0x02) +#define SSD1307FB_SET_COL_RANGE 0x21 +#define SSD1307FB_SET_PAGE_RANGE 0x22 #define SSD1307FB_CONTRAST 0x81 #define SSD1307FB_CHARGE_PUMP 0x8d #define SSD1307FB_SEG_REMAP_ON 0xa1 @@ -135,9 +141,15 @@ static inline int ssd1307fb_write_data(struct i2c_client *client, u8 data) static void ssd1307fb_update_display(struct ssd1307fb_par *par) { + struct ssd1307fb_array *array; u8 *vmem = par->info->screen_base; int i, j, k; + array = ssd1307fb_alloc_array(par->width * par->height / 8, + SSD1307FB_DATA); + if (!array) + return; + /* * The screen is divided in pages, each having a height of 8 * pixels, and the width of the screen. When sending a byte of @@ -168,29 +180,22 @@ static void ssd1307fb_update_display(struct ssd1307fb_par *par) */ for (i = 0; i < (par->height / 8); i++) { - struct ssd1307fb_array *array; - ssd1307fb_write_cmd(par->client, - SSD1307FB_START_PAGE_ADDRESS + i + par->page_offset); - ssd1307fb_write_cmd(par->client, 0x00); - ssd1307fb_write_cmd(par->client, 0x10); - - array = ssd1307fb_alloc_array(par->width, SSD1307FB_DATA); - for (j = 0; j < par->width; j++) { - array->data[j] = 0; + u32 array_idx = i * par->width + j; + array->data[array_idx] = 0; for (k = 0; k < 8; k++) { u32 page_length = par->width * i; u32 index = page_length + (par->width * k + j) / 8; u8 byte = *(vmem + index); u8 bit = byte & (1 << (j % 8)); bit = bit >> (j % 8); - array->data[j] |= bit << k; + array->data[array_idx] |= bit << k; } } - - ssd1307fb_write_array(par->client, array, par->width); - kfree(array); } + + ssd1307fb_write_array(par->client, array, par->width * par->height / 8); + kfree(array); } @@ -371,6 +376,26 @@ static int ssd1307fb_ssd1306_init(struct ssd1307fb_par *par) if (ret < 0) return ret; + /* Switch to horizontal addressing mode */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); + ret = ret & ssd1307fb_write_cmd(par->client, + SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); + ret = ret & ssd1307fb_write_cmd(par->client, 0x0); + ret = ret & ssd1307fb_write_cmd(par->client, par->width - 1); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); + ret = ret & ssd1307fb_write_cmd(par->client, 0x0); + ret = ret & ssd1307fb_write_cmd(par->client, + par->page_offset + (par->height / 8) - 1); + if (ret < 0) + return ret; + /* Turn on the display */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); if (ret < 0) From fcf7e6e5bd84b561eca4f7977c2a547f724f5942 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Thu, 16 May 2013 15:29:06 +0300 Subject: [PATCH 5/6] videomode: don't allocate mem in of_get_display_timing() Move the allocation of display_timing memory from of_get_display_timing() to of_get_display_timings(). This allows us to use of_get_display_timing() in a way that doesn't require dynamic memory allocation. Signed-off-by: Tomi Valkeinen Cc: Steffen Trumtrar Cc: Laurent Pinchart Cc: Philipp Zabel --- drivers/video/of_display_timing.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/video/of_display_timing.c b/drivers/video/of_display_timing.c index 56009bc02b02..0e8102344b2e 100644 --- a/drivers/video/of_display_timing.c +++ b/drivers/video/of_display_timing.c @@ -56,18 +56,13 @@ static int parse_timing_property(struct device_node *np, const char *name, * of_get_display_timing - parse display_timing entry from device_node * @np: device_node with the properties **/ -static struct display_timing *of_get_display_timing(struct device_node *np) +static int of_get_display_timing(struct device_node *np, + struct display_timing *dt) { - struct display_timing *dt; u32 val = 0; int ret = 0; - dt = kzalloc(sizeof(*dt), GFP_KERNEL); - if (!dt) { - pr_err("%s: could not allocate display_timing struct\n", - of_node_full_name(np)); - return NULL; - } + memset(dt, 0, sizeof(*dt)); ret |= parse_timing_property(np, "hback-porch", &dt->hback_porch); ret |= parse_timing_property(np, "hfront-porch", &dt->hfront_porch); @@ -101,11 +96,10 @@ static struct display_timing *of_get_display_timing(struct device_node *np) if (ret) { pr_err("%s: error reading timing properties\n", of_node_full_name(np)); - kfree(dt); - return NULL; + return -EINVAL; } - return dt; + return 0; } /** @@ -174,9 +168,17 @@ struct display_timings *of_get_display_timings(struct device_node *np) for_each_child_of_node(timings_np, entry) { struct display_timing *dt; + int r; - dt = of_get_display_timing(entry); + dt = kzalloc(sizeof(*dt), GFP_KERNEL); if (!dt) { + pr_err("%s: could not allocate display_timing struct\n", + of_node_full_name(np)); + goto timingfail; + } + + r = of_get_display_timing(entry, dt); + if (r) { /* * to not encourage wrong devicetrees, fail in case of * an error From ffa3fd21de8ab0db7962b612d4c6e17c0d88e9c2 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Thu, 16 May 2013 15:36:38 +0300 Subject: [PATCH 6/6] videomode: implement public of_get_display_timing() The current of_get_display_timings() reads multiple display timings, allocating memory for the entries. However, most of the time when parsing display timings from DT data is needed, there's only one display timing as it's not common for a LCD panel to support multiple videomodes. This patch creates a new function: int of_get_display_timing(struct device_node *np, const char *name, struct display_timing *dt); which can be used to parse a single display timing entry from the given node name. Signed-off-by: Tomi Valkeinen Cc: Steffen Trumtrar Cc: Laurent Pinchart Cc: Philipp Zabel --- drivers/video/of_display_timing.c | 33 ++++++++++++++++++++++++++++--- include/video/of_display_timing.h | 2 ++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/video/of_display_timing.c b/drivers/video/of_display_timing.c index 0e8102344b2e..9c0f17b2e6fb 100644 --- a/drivers/video/of_display_timing.c +++ b/drivers/video/of_display_timing.c @@ -53,10 +53,10 @@ static int parse_timing_property(struct device_node *np, const char *name, } /** - * of_get_display_timing - parse display_timing entry from device_node + * of_parse_display_timing - parse display_timing entry from device_node * @np: device_node with the properties **/ -static int of_get_display_timing(struct device_node *np, +static int of_parse_display_timing(struct device_node *np, struct display_timing *dt) { u32 val = 0; @@ -102,6 +102,33 @@ static int of_get_display_timing(struct device_node *np, return 0; } +/** + * of_get_display_timing - parse a display_timing entry + * @np: device_node with the timing subnode + * @name: name of the timing node + * @dt: display_timing struct to fill + **/ +int of_get_display_timing(struct device_node *np, const char *name, + struct display_timing *dt) +{ + struct device_node *timing_np; + + if (!np) { + pr_err("%s: no devicenode given\n", of_node_full_name(np)); + return -EINVAL; + } + + timing_np = of_find_node_by_name(np, name); + if (!timing_np) { + pr_err("%s: could not find node '%s'\n", + of_node_full_name(np), name); + return -ENOENT; + } + + return of_parse_display_timing(timing_np, dt); +} +EXPORT_SYMBOL_GPL(of_get_display_timing); + /** * of_get_display_timings - parse all display_timing entries from a device_node * @np: device_node with the subnodes @@ -177,7 +204,7 @@ struct display_timings *of_get_display_timings(struct device_node *np) goto timingfail; } - r = of_get_display_timing(entry, dt); + r = of_parse_display_timing(entry, dt); if (r) { /* * to not encourage wrong devicetrees, fail in case of diff --git a/include/video/of_display_timing.h b/include/video/of_display_timing.h index 8016eb727cf3..6562ad965889 100644 --- a/include/video/of_display_timing.h +++ b/include/video/of_display_timing.h @@ -14,6 +14,8 @@ struct display_timings; #define OF_USE_NATIVE_MODE -1 +int of_get_display_timing(struct device_node *np, const char *name, + struct display_timing *dt); struct display_timings *of_get_display_timings(struct device_node *np); int of_display_timings_exist(struct device_node *np);