[PATCH 1/2] video: ssd1307fb: Add support for SSD1306 OLED controller
Jean-Christophe PLAGNIOL-VILLARD
plagnioj at jcrosoft.com
Fri Mar 29 14:38:42 EDT 2013
On 17:44 Wed 06 Mar , Maxime Ripard wrote:
> 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 <maxime.ripard at free-electrons.com>
> ---
> .../devicetree/bindings/video/ssd1307fb.txt | 10 +-
> drivers/video/ssd1307fb.c | 267 ++++++++++++++------
> 2 files changed, 203 insertions(+), 74 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/video/ssd1307fb.txt b/Documentation/devicetree/bindings/video/ssd1307fb.txt
> index 3d0060c..7a12542 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-<bus>". The only supported bus for
> - now is i2c.
> + - compatible: Should be "solomon,<chip>fb-<bus>". 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 395cb6a..95f76e2 100644
> --- a/drivers/video/ssd1307fb.c
> +++ b/drivers/video/ssd1307fb.c
> @@ -16,24 +16,39 @@
> #include <linux/pwm.h>
> #include <linux/delay.h>
>
> -#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,16 @@ 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 +237,137 @@ 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) {
why this will be DT only?
a platform or ARN that does not support DT can not use this driver
this looks not right
> dev_err(&client->dev, "No device tree data found!\n");
> return -EINVAL;
> }
> @@ -247,6 +378,36 @@ 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;
> +
> + printk("Width\t%u\n", par->width);
> +
> + if (of_property_read_u32(node, "solomon,height", &par->height))
> + par->width = 16;
> +
> + printk("Height\t%u\n", par->height);
> +
> + if (of_property_read_u32(node, "solomon,page-offset", &par->page_offset))
> + par->page_offset = 1;
> +
> + printk("Offset\t%u\n", par->page_offset);
> +
> + 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 +417,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 +439,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 +449,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 +457,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 +489,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 +498,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,
> --
> 1.7.10.4
>
> _______________________________________________
> devicetree-discuss mailing list
> devicetree-discuss at lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/devicetree-discuss
More information about the linux-arm-kernel
mailing list