From patchwork Tue Jul 17 15:59:44 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxime Ripard X-Patchwork-Id: 1205831 Return-Path: X-Original-To: patchwork-linux-fbdev@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork1.kernel.org (Postfix) with ESMTP id A6A0F3FD9C for ; Tue, 17 Jul 2012 16:09:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753986Ab2GQQJk (ORCPT ); Tue, 17 Jul 2012 12:09:40 -0400 Received: from mail.free-electrons.com ([88.190.12.23]:44946 "EHLO mail.free-electrons.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752764Ab2GQQJh (ORCPT ); Tue, 17 Jul 2012 12:09:37 -0400 Received: by mail.free-electrons.com (Postfix, from userid 106) id 57DD021F; Tue, 17 Jul 2012 18:00:08 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.free-electrons.com X-Spam-Level: X-Spam-Status: No, score=-3.2 required=5.0 tests=ALL_TRUSTED,AWL,BAYES_00 shortcircuit=no autolearn=ham version=3.3.1 Received: from localhost (col31-4-88-188-83-94.fbx.proxad.net [88.188.83.94]) by mail.free-electrons.com (Postfix) with ESMTPSA id 6BA1D17B; Tue, 17 Jul 2012 17:59:55 +0200 (CEST) From: Maxime Ripard To: linux-fbdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org Cc: brian@crystalfontz.com Subject: [PATCH 1/4] video: Add support for the Solomon SSD1307 OLED Controller Date: Tue, 17 Jul 2012 17:59:44 +0200 Message-Id: <1342540787-14930-2-git-send-email-maxime.ripard@free-electrons.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1342540787-14930-1-git-send-email-maxime.ripard@free-electrons.com> References: <1342540787-14930-1-git-send-email-maxime.ripard@free-electrons.com> Sender: linux-fbdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fbdev@vger.kernel.org This patches adds support for the Solomon SSD1307 OLED controller found on the Crystalfontz CFA10036 board. It is a monochrome 128x39 display, that can operate over I2C or SPI. The current driver as only be tested on the CFA-10036, that is using this controller over I2C to driver a 96x16 OLED screen. Signed-off-by: Maxime Ripard Cc: Brian Lilly --- .../devicetree/bindings/video/ssd1307fb.txt | 24 ++ drivers/video/Kconfig | 14 + drivers/video/Makefile | 1 + drivers/video/ssd1307fb.c | 414 ++++++++++++++++++++ 4 files changed, 453 insertions(+) create mode 100644 Documentation/devicetree/bindings/video/ssd1307fb.txt create mode 100644 drivers/video/ssd1307fb.c diff --git a/Documentation/devicetree/bindings/video/ssd1307fb.txt b/Documentation/devicetree/bindings/video/ssd1307fb.txt new file mode 100644 index 0000000..73ba41c --- /dev/null +++ b/Documentation/devicetree/bindings/video/ssd1307fb.txt @@ -0,0 +1,24 @@ +* Solomon SSD1307 Framebuffer Driver + +Required properties: + - compatible: Should be "solomon,ssd1307fb-". The only supported bus for + now is i2c. + - 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] + - oled-reset-gpios: Should contain the GPIO used to reset the OLED display + +Optional properties: + - reset-active-low: Is the reset gpio is active on physical low? + +[0]: Documentation/devicetree/bindings/pwm/pwm.txt + +Examples: +ssd1307: oled@3c { + compatible = "solomon,ssd1307fb-i2c"; + reg = <0x3c>; + pwms = <&pwm 4 3000>; + oled-reset-gpios = <&gpio2 7 1>; + reset-active-low; +}; diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 0217f74..21ae6dd 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2469,4 +2469,18 @@ config FB_SH_MOBILE_MERAM Up to 4 memory channels can be configured, allowing 4 RGB or 2 YCbCr framebuffers to be configured. + +config FB_SSD1307 + tristate "Solomon SSD1307 framebuffer support" + depends on FB && I2C + select FB_SYS_FOPS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_DEFERRED_IO + select PWM + help + This driver implements support for the Solomon SSD1307 + OLED controller over I2C. + endmenu diff --git a/drivers/video/Makefile b/drivers/video/Makefile index ee8dafb..6bbb72c 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -164,6 +164,7 @@ obj-$(CONFIG_FB_BFIN_7393) += bfin_adv7393fb.o obj-$(CONFIG_FB_MX3) += mx3fb.o obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o obj-$(CONFIG_FB_MXS) += mxsfb.o +obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o # the test framebuffer is last obj-$(CONFIG_FB_VIRTUAL) += vfb.o diff --git a/drivers/video/ssd1307fb.c b/drivers/video/ssd1307fb.c new file mode 100644 index 0000000..5d24360 --- /dev/null +++ b/drivers/video/ssd1307fb.c @@ -0,0 +1,414 @@ +/* + * Driver for the Solomon SSD1307 OLED controler + * + * Copyright 2012 Free Electrons + * + * Licensed under the GPLv2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSD1307FB_WIDTH 96 +#define SSD1307FB_HEIGHT 16 + +#define SSD1307FB_DATA 0x40 +#define SSD1307FB_COMMAND 0x80 + +#define SSD1307FB_CONTRAST 0x81 +#define SSD1307FB_SEG_REMAP_ON 0xa1 +#define SSD1307FB_DISPLAY_OFF 0xae +#define SSD1307FB_DISPLAY_ON 0xaf +#define SSD1307FB_START_PAGE_ADDRESS 0xb0 + +struct ssd1307fb_par { + struct i2c_client *client; + struct fb_info *info; + struct pwm_device *pwm; + u32 pwm_period; + int reset; +}; + +static struct fb_fix_screeninfo ssd1307fb_fix __devinitdata = { + .id = "Solomon SSD1307", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_MONO10, + .xpanstep = 0, + .ypanstep = 0, + .ywrapstep = 0, + .line_length = SSD1307FB_WIDTH / 8, + .accel = FB_ACCEL_NONE, +}; + +static struct fb_var_screeninfo ssd1307fb_var __devinitdata = { + .xres = SSD1307FB_WIDTH, + .yres = SSD1307FB_HEIGHT, + .xres_virtual = SSD1307FB_WIDTH, + .yres_virtual = SSD1307FB_HEIGHT, + .bits_per_pixel = 1, +}; + +static int ssd1307fb_write_cmd_array(struct i2c_client *client, u8* cmd, u32 len) +{ + u8 *buf; + int ret; + + buf = kzalloc(len + 1, GFP_KERNEL); + if (!buf) { + dev_err(&client->dev, "Couldn't allocate sending buffer.\n"); + ret = -ENOMEM; + goto error; + } + + buf[0] = SSD1307FB_COMMAND; + memcpy(buf + 1, cmd, len); + + ret = i2c_master_send(client, buf, len + 1); + if (ret != len + 1) { + dev_err(&client->dev, "Couldn't send I2C command.\n"); + goto error; + } + + kfree(buf); + return 0; + +error: + kfree(buf); + return ret; +} + +static inline int ssd1307fb_write_cmd(struct i2c_client *client, u8 cmd) +{ + return ssd1307fb_write_cmd_array(client, &cmd, 1); +} + +static int ssd1307fb_write_data_array(struct i2c_client *client, u8* cmd, u32 len) +{ + u8 *buf; + int ret; + + buf = kzalloc(len + 1, GFP_KERNEL); + if (!buf) { + dev_err(&client->dev, "Couldn't allocate sending buffer.\n"); + ret = -ENOMEM; + goto error; + } + + buf[0] = SSD1307FB_DATA; + memcpy(buf + 1, cmd, len); + + ret = i2c_master_send(client, buf, len + 1); + if (ret != len + 1) { + dev_err(&client->dev, "Couldn't send I2C data.\n"); + goto error; + } + + kfree(buf); + return 0; + +error: + kfree(buf); + return ret; +} + +static inline int ssd1307fb_write_data(struct i2c_client *client, u8 data) +{ + return ssd1307fb_write_data_array(client, &data, 1); +} + +static int ssd1307fb_set(struct i2c_client *client, u8 value) +{ + int i, j, ret; + + for (i = 1; i <= (SSD1307FB_HEIGHT / 8); i++) { + ret = ssd1307fb_write_cmd(client, SSD1307FB_START_PAGE_ADDRESS + i); + if (ret) + goto i2c_error; + + ret = ssd1307fb_write_cmd(client, 0x00); + if (ret) + goto i2c_error; + + ret = ssd1307fb_write_cmd(client, 0x10); + if (ret) + goto i2c_error; + + for (j = 0; j < SSD1307FB_WIDTH; j++) + ssd1307fb_write_data(client, value); + } + + return 0; + +i2c_error: + dev_err(&client->dev, "Couldn't send i2c command: %d\n", ret); + return ret; +} + +static void ssd1307fb_update_display(struct ssd1307fb_par *par) +{ + u8 *vmem = par->info->screen_base; + int i, j, k; + + /* + * A page is 8 bit large and covers all the line. In order to + * update the screen, you have to send a byte, each byte + * containing a bit per pixel to set, on the same column. That + * way, by sending a byte, you will only portion of the screen + * that has a height of 8 bit and a width of 1, the right-most + * bit being on the top of this frame. So we have to do a bit + * of magic here... + */ + + for (i = 0; i < (SSD1307FB_HEIGHT / 8); i++) { + ssd1307fb_write_cmd(par->client, SSD1307FB_START_PAGE_ADDRESS + (i + 1)); + ssd1307fb_write_cmd(par->client, 0x00); + ssd1307fb_write_cmd(par->client, 0x10); + + for (j = 0; j < SSD1307FB_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; + u8 byte = *(vmem + index); + u8 bit = byte & (1 << (7 - (j % 8))); + bit = bit >> (7 - (j % 8)); + buf |= bit << k; + } + ssd1307fb_write_data(par->client, buf); + } + } + + return; +} + + +static ssize_t ssd1307fb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct ssd1307fb_par *par = info->par; + unsigned long p = *ppos; + void *dst; + int err = 0; + + dst = (void __force *) (info->screen_base + p); + + if (copy_from_user(dst, buf, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + ssd1307fb_update_display(par); + + return (err) ? err : count; +} + +static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct ssd1307fb_par *par = info->par; + sys_fillrect(info, rect); + ssd1307fb_update_display(par); +} + +static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct ssd1307fb_par *par = info->par; + sys_copyarea(info, area); + ssd1307fb_update_display(par); +} + +static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct ssd1307fb_par *par = info->par; + sys_imageblit(info, image); + ssd1307fb_update_display(par); +} + +static struct fb_ops ssd1307fb_ops = { + .owner = THIS_MODULE, + .fb_read = fb_sys_read, + .fb_write = ssd1307fb_write, + .fb_fillrect = ssd1307fb_fillrect, + .fb_copyarea = ssd1307fb_copyarea, + .fb_imageblit = ssd1307fb_imageblit, +}; + +static void ssd1307fb_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + ssd1307fb_update_display(info->par); +} + +static struct fb_deferred_io ssd1307fb_defio = { + .delay = HZ, + .deferred_io = ssd1307fb_deferred_io, +}; + +static int __devinit 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 ssd1307fb_par *par; + u8 *vmem; + int ret; + + if (!client->dev.of_node) { + dev_err(&client->dev, "No device tree data found!\n"); + ret = -EINVAL; + goto generic_error; + } + + info = framebuffer_alloc(sizeof(struct ssd1307fb_par), &client->dev); + if (!info) { + ret = -ENOMEM; + goto generic_error; + } + + vmem = devm_kzalloc(&client->dev, vmem_size, GFP_KERNEL); + + info->fbops = &ssd1307fb_ops; + info->fix = ssd1307fb_fix; + info->fbdefio = &ssd1307fb_defio; + + info->var = ssd1307fb_var; + info->var.red.length = 1; + info->var.red.offset = 0; + info->var.green.length = 1; + info->var.green.offset = 0; + info->var.blue.length = 1; + info->var.blue.offset = 0; + + info->screen_base = (u8 __force __iomem *)vmem; + info->fix.smem_len = vmem_size; + + fb_deferred_io_init(info); + + par = info->par; + par->info = info; + par->client = client; + + par->reset = of_get_named_gpio(client->dev.of_node, + "oled-reset-gpios", 0); + if (gpio_is_valid(par->reset)) { + int flags = GPIOF_OUT_INIT_HIGH; + if (of_get_property(client->dev.of_node, + "reset-active-low", NULL)) + flags = GPIOF_OUT_INIT_LOW; + ret = devm_gpio_request_one(&client->dev, par->reset, + flags, "oled-reset"); + if (ret) { + dev_err(&client->dev, + "failed to request gpio %d: %d\n", + par->reset, ret); + 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 */ + gpio_set_value(par->reset, 1); + udelay(4); + gpio_set_value(par->reset, 0); + 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) { + dev_err(&client->dev, "Couldn't remap the screen.\n"); + goto remap_error; + } + + /* Turn on the display */ + ret = ssd1307fb_write_cmd(client, SSD1307FB_DISPLAY_ON); + if (ret) { + dev_err(&client->dev, "Couldn't turn the display on.\n"); + goto remap_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: +reset_oled_error: + fb_deferred_io_cleanup(info); + framebuffer_release(info); +generic_error: + return ret; +} + +static int __devexit ssd1307fb_remove(struct i2c_client *client) +{ + struct fb_info *info = i2c_get_clientdata(client); + struct ssd1307fb_par *par = info->par; + unregister_framebuffer(info); + pwm_disable(par->pwm); + pwm_put(par->pwm); + fb_deferred_io_cleanup(info); + framebuffer_release(info); + + return 0; +} + +static const struct i2c_device_id ssd1307fb_i2c_id[] = { + { "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 = __devexit_p(ssd1307fb_remove), + .id_table = ssd1307fb_i2c_id, + .driver = { + .name = "ssd1307fb", + .of_match_table = of_match_ptr(ssd1307fb_of_match), + .owner = THIS_MODULE, + }, +}; + +module_i2c_driver(ssd1307fb_driver); + +MODULE_DESCRIPTION("FB driver for the Solomon SSD1307 OLED controler"); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_LICENSE("GPL");