From 07e77172e73516d1c5e7c2ea77dbb5766bbdba65 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 9 Oct 2017 15:53:03 +0200 Subject: [PATCH 01/93] ASoC: sun4i-is: also check for NULL on reset pin request Commit 2ad6f30de7087 ("ASoC: sun4i-i2s: Add quirks to handle a31 compatible") added support for reset control but support for sun4i was overlooked. For these older SoC's it is quite common not have this reset control. The reset control is first requested, and later checked if it IS_ERR. If however there is no reset control configured, we can get NULL and thus the checks pass with a NULL pointer passed the functions afterwards. Lets use IS_ERR_OR_NULL to ensure the reset control is only (de)asserted when we actually have a valid reset control. Signed-off-by: Olliver Schinagl --- sound/soc/sunxi/sun4i-i2s.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index b4af5ce78ecbde..0e4489dde2177d 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -1033,13 +1033,13 @@ static int sun4i_i2s_probe(struct platform_device *pdev) if (i2s->variant->has_reset) { i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); - if (IS_ERR(i2s->rst)) { + if (IS_ERR_OR_NULL(i2s->rst)) { dev_err(&pdev->dev, "Failed to get reset control\n"); return PTR_ERR(i2s->rst); } } - if (!IS_ERR(i2s->rst)) { + if (!IS_ERR_OR_NULL(i2s->rst)) { ret = reset_control_deassert(i2s->rst); if (ret) { dev_err(&pdev->dev, @@ -1089,7 +1089,7 @@ static int sun4i_i2s_probe(struct platform_device *pdev) sun4i_i2s_runtime_suspend(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); - if (!IS_ERR(i2s->rst)) + if (!IS_ERR_OR_NULL(i2s->rst)) reset_control_assert(i2s->rst); return ret; From 774609f1a489cac7559b3ec2796a2197cb1e1310 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 9 Mar 2018 09:47:26 +0100 Subject: [PATCH 02/93] fbdev: Whitespace fixing in the Makefile The current Makefile has mixed tabs and spaces. This patch causes no binary changes. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/core/Makefile | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile index d34fd182ca6806..c49cd3900af705 100644 --- a/drivers/video/fbdev/core/Makefile +++ b/drivers/video/fbdev/core/Makefile @@ -1,32 +1,32 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_FB_CMDLINE) += fb_cmdline.o -obj-$(CONFIG_FB_NOTIFY) += fb_notify.o -obj-$(CONFIG_FB) += fb.o -fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ - modedb.o fbcvt.o -fb-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o +obj-$(CONFIG_FB_CMDLINE) += fb_cmdline.o +obj-$(CONFIG_FB_NOTIFY) += fb_notify.o +obj-$(CONFIG_FB) += fb.o +fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ + modedb.o fbcvt.o +fb-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE),y) -fb-y += fbcon.o bitblit.o softcursor.o +fb-y += fbcon.o bitblit.o softcursor.o ifeq ($(CONFIG_FB_TILEBLITTING),y) -fb-y += tileblit.o +fb-y += tileblit.o endif ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE_ROTATION),y) -fb-y += fbcon_rotate.o fbcon_cw.o fbcon_ud.o \ - fbcon_ccw.o +fb-y += fbcon_rotate.o fbcon_cw.o \ + fbcon_ud.o fbcon_ccw.o endif ifeq ($(CONFIG_DMI),y) fb-y += fbcon_dmi_quirks.o endif endif -fb-objs := $(fb-y) +fb-objs := $(fb-y) -obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o -obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o -obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o -obj-$(CONFIG_FB_SYS_FILLRECT) += sysfillrect.o -obj-$(CONFIG_FB_SYS_COPYAREA) += syscopyarea.o -obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o -obj-$(CONFIG_FB_SYS_FOPS) += fb_sys_fops.o -obj-$(CONFIG_FB_SVGALIB) += svgalib.o -obj-$(CONFIG_FB_DDC) += fb_ddc.o +obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o +obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o +obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o +obj-$(CONFIG_FB_SYS_FILLRECT) += sysfillrect.o +obj-$(CONFIG_FB_SYS_COPYAREA) += syscopyarea.o +obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o +obj-$(CONFIG_FB_SYS_FOPS) += fb_sys_fops.o +obj-$(CONFIG_FB_SVGALIB) += svgalib.o +obj-$(CONFIG_FB_DDC) += fb_ddc.o From a8b336c8cd5d39836e94c676cd794e4eb9557496 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 9 Mar 2018 17:58:24 +0100 Subject: [PATCH 03/93] fbdev: of: Add of parsing helper This patch was inspired by touchscreen_of where a few helper functions are used to help in parsing various devicetree parameters and thus offing a standard way for common items. Here we add support for the first common parameter, 'rotate' which determines the initial rotation of a screen. In the future, this can be expanded to also add support for width/height for example and others as well. Signed-off-by: Olliver Schinagl --- .../devicetree/bindings/display/fbdev.txt | 5 +++ drivers/video/fbdev/core/Makefile | 4 ++ drivers/video/fbdev/core/fb_of.c | 41 +++++++++++++++++++ include/linux/fb.h | 6 +++ 4 files changed, 56 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/fbdev.txt create mode 100644 drivers/video/fbdev/core/fb_of.c diff --git a/Documentation/devicetree/bindings/display/fbdev.txt b/Documentation/devicetree/bindings/display/fbdev.txt new file mode 100644 index 00000000000000..b59ed74d5ebc51 --- /dev/null +++ b/Documentation/devicetree/bindings/display/fbdev.txt @@ -0,0 +1,5 @@ +General framebuffer properties: + +Optional properties for framebuffers: + - rotate : rotate a framebuffer over 0, 1, 2 and 3 + degree's (integer) diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile index c49cd3900af705..7691cd82cbb647 100644 --- a/drivers/video/fbdev/core/Makefile +++ b/drivers/video/fbdev/core/Makefile @@ -6,6 +6,10 @@ fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ modedb.o fbcvt.o fb-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o +ifeq ($(CONFIG_OF),y) +fb-y += fb_of.o +endif + ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE),y) fb-y += fbcon.o bitblit.o softcursor.o ifeq ($(CONFIG_FB_TILEBLITTING),y) diff --git a/drivers/video/fbdev/core/fb_of.c b/drivers/video/fbdev/core/fb_of.c new file mode 100644 index 00000000000000..95783c4a87ac12 --- /dev/null +++ b/drivers/video/fbdev/core/fb_of.c @@ -0,0 +1,41 @@ +/* + * Generic DT helper functions for fbdev devices + * + * Copyright (C) 2018 Olliver Schinagl + * + * Olliver Schinagl + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#include +#include +#include +#include +#include + +void fb_parse_properties(struct device *dev, struct fb_of_properties *prop) +{ + u16 rotate; + + if (device_property_read_u16(dev, "rotate", &rotate)) + rotate = FB_ROTATE_UR; + + switch (rotate) { + case 3: + prop->rotate = FB_ROTATE_CCW; + break; + case 2: + prop->rotate = FB_ROTATE_UD; + break; + case 1: + prop->rotate = FB_ROTATE_CW; + break; + case 0: /* fall through */ + default: + prop->rotate = FB_ROTATE_UR; + break; + } +} +EXPORT_SYMBOL_GPL(fb_parse_properties); diff --git a/include/linux/fb.h b/include/linux/fb.h index bc24e48e396d0b..5ae22553278a0b 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -120,6 +120,12 @@ struct fb_cursor_user { struct fb_image_user image; /* Cursor image */ }; +struct fb_of_properties { + __u8 rotate; +}; + +void fb_parse_properties(struct device *dev, struct fb_of_properties *prop); + /* * Register/unregister for framebuffer events */ From c5eb0f8c0367d0b6f5c474ffbcc639d54dd92842 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 22 Mar 2018 11:02:38 +0100 Subject: [PATCH 04/93] fbdev: of: Add parameter to not clear screen on probe There are situations, where the bootloader is relied on to prepare the display. For example to show a splash screen. Some drivers however clear the framebuffer during probing and it may be a while before linux has the chance to show something on the screen. This is experienced by the user as a blank screen for potentially long seconds. This patch adds a generic parameter to the framebuffer devicetree parser to allow the user to explicitly avoid clearing the framebuffer during load. Signed-off-by: Olliver Schinagl --- Documentation/devicetree/bindings/display/fbdev.txt | 1 + drivers/video/fbdev/core/fb_of.c | 3 +++ include/linux/fb.h | 1 + 3 files changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/display/fbdev.txt b/Documentation/devicetree/bindings/display/fbdev.txt index b59ed74d5ebc51..a35b01b9f0edb9 100644 --- a/Documentation/devicetree/bindings/display/fbdev.txt +++ b/Documentation/devicetree/bindings/display/fbdev.txt @@ -1,5 +1,6 @@ General framebuffer properties: Optional properties for framebuffers: + - clear-on-probe : clear the framebuffer during probe (bool) - rotate : rotate a framebuffer over 0, 1, 2 and 3 degree's (integer) diff --git a/drivers/video/fbdev/core/fb_of.c b/drivers/video/fbdev/core/fb_of.c index 95783c4a87ac12..1f3c5e412d4165 100644 --- a/drivers/video/fbdev/core/fb_of.c +++ b/drivers/video/fbdev/core/fb_of.c @@ -17,8 +17,11 @@ void fb_parse_properties(struct device *dev, struct fb_of_properties *prop) { + bool clear; u16 rotate; + clear = device_property_read_bool(dev, "clear-on-probe"); + if (device_property_read_u16(dev, "rotate", &rotate)) rotate = FB_ROTATE_UR; diff --git a/include/linux/fb.h b/include/linux/fb.h index 5ae22553278a0b..08b6825b00152e 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -121,6 +121,7 @@ struct fb_cursor_user { }; struct fb_of_properties { + bool clear_on_probe; __u8 rotate; }; From eeda78335825cb900f05a0c00beb13cdd6d5956d Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 21 Mar 2018 10:02:08 +0100 Subject: [PATCH 05/93] fbdev: sysfs: Sort headers Alphabetize headers in fbsysfs.c, no functional changes where performed. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/core/fbsysfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c index 15755ce1d26c81..8ba33452110563 100644 --- a/drivers/video/fbdev/core/fbsysfs.c +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -15,11 +15,11 @@ * are converted to use it a sysfsification will open OOPSable races. */ -#include -#include -#include #include +#include +#include #include +#include #define FB_SYSFS_FLAG_ATTR 1 From fa96d9b858bb403900eddb2a68304de219e3741e Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 21 Mar 2018 11:39:33 +0100 Subject: [PATCH 06/93] fbdev: sysfs: Replace simple_strtoul with kstrtoul The simple_strtoul function appears to have been deprecated, replace the easy invocations of simple_strtoul. Two invocations remain which uses the end pointer of simple_strtoul. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/core/fbsysfs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c index 8ba33452110563..d6494b9ed253ff 100644 --- a/drivers/video/fbdev/core/fbsysfs.c +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -244,11 +244,13 @@ static ssize_t store_rotate(struct device *device, { struct fb_info *fb_info = dev_get_drvdata(device); struct fb_var_screeninfo var; - char **last = NULL; int err; var = fb_info->var; - var.rotate = simple_strtoul(buf, last, 0); + + err = kstrtoul(buf, 0, &var.rotate); + if (err) + return err; if ((err = activate(fb_info, &var))) return err; From dcefc72785445c42533a7efac7cf5769f28a67b9 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 9 Mar 2018 17:58:56 +0100 Subject: [PATCH 07/93] fbdev: sysfs: Input validation Currently, we pass any value received from sysfs along to a driver though it is only claimed that 0 - 3 are valid choices. This patch adds input validation herein to ensure only the allowed parameters are actually pushed along the chain. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/core/fbsysfs.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c index d6494b9ed253ff..4534b2fbc85368 100644 --- a/drivers/video/fbdev/core/fbsysfs.c +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -244,14 +244,32 @@ static ssize_t store_rotate(struct device *device, { struct fb_info *fb_info = dev_get_drvdata(device); struct fb_var_screeninfo var; + unsigned long rotate; int err; var = fb_info->var; - err = kstrtoul(buf, 0, &var.rotate); + err = kstrtoul(buf, 0, &rotate); if (err) return err; + switch (rotate) { + case 3: + var.rotate = FB_ROTATE_CCW; + break; + case 2: + var.rotate = FB_ROTATE_UD; + break; + case 1: + var.rotate = FB_ROTATE_CW; + break; + case 0: + var.rotate = FB_ROTATE_UR; + break; + default: + return -EINVAL; + } + if ((err = activate(fb_info, &var))) return err; From 90cc3b8601710fa88d395700935c9b3039ca4675 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 12 Mar 2018 15:57:27 +0100 Subject: [PATCH 08/93] fbdev: sysfs: Expand input validation to degree's The rotation interface supports rotation angles from 0 - 3. While this works great for machines, under normal circumstances, humans prefer to use 0, 90, 180 and 270 degree's as parameters for their rotation. This patch allows the human readable factors as input to the sysfs interface and translates them to their rotation counterparts. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/core/fbsysfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c index 4534b2fbc85368..97178b6b9820c4 100644 --- a/drivers/video/fbdev/core/fbsysfs.c +++ b/drivers/video/fbdev/core/fbsysfs.c @@ -254,12 +254,15 @@ static ssize_t store_rotate(struct device *device, return err; switch (rotate) { + case 270: /* fall through */ case 3: var.rotate = FB_ROTATE_CCW; break; + case 180: /* fall through */ case 2: var.rotate = FB_ROTATE_UD; break; + case 90: /* fall through */ case 1: var.rotate = FB_ROTATE_CW; break; From 68b614b8185d69d36429824976553a18594ad221 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 12 Mar 2018 10:06:50 +0100 Subject: [PATCH 09/93] fbdev: sysfs: Add rotation in degrees support to validation While the documented parameters, 0, 1, 2 and 3 for rotation are well understood, it makes sense to also support the 'human' parameters, 90, 180 and 270 degree's of rotation support. Signed-off-by: Olliver Schinagl --- Documentation/devicetree/bindings/display/fbdev.txt | 4 ++-- drivers/video/fbdev/core/fb_of.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/display/fbdev.txt b/Documentation/devicetree/bindings/display/fbdev.txt index a35b01b9f0edb9..d3ed6ef846f614 100644 --- a/Documentation/devicetree/bindings/display/fbdev.txt +++ b/Documentation/devicetree/bindings/display/fbdev.txt @@ -2,5 +2,5 @@ General framebuffer properties: Optional properties for framebuffers: - clear-on-probe : clear the framebuffer during probe (bool) - - rotate : rotate a framebuffer over 0, 1, 2 and 3 - degree's (integer) + - rotate : rotate a framebuffer over 0, 1, 2 and 3 (or 0, 90, + 180 and 270) degree's (integer) diff --git a/drivers/video/fbdev/core/fb_of.c b/drivers/video/fbdev/core/fb_of.c index 1f3c5e412d4165..57bc3dc30bf3d5 100644 --- a/drivers/video/fbdev/core/fb_of.c +++ b/drivers/video/fbdev/core/fb_of.c @@ -26,12 +26,15 @@ void fb_parse_properties(struct device *dev, struct fb_of_properties *prop) rotate = FB_ROTATE_UR; switch (rotate) { + case 270: /* fall through */ case 3: prop->rotate = FB_ROTATE_CCW; break; + case 180: /* fall through */ case 2: prop->rotate = FB_ROTATE_UD; break; + case 90: /* fall through */ case 1: prop->rotate = FB_ROTATE_CW; break; From b51874ea26255bf4e9b6917dd15de2c350356eb9 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 8 Aug 2017 15:23:29 +0200 Subject: [PATCH 10/93] fbdev: ssd1307fb: Abort probe if we cannot talk to the display We should abort probe if we cannot talk to the display. This prevents creating a fb device which does not work and prevents a lot of i2c communication error messages as a consequence. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index f599520374ddf5..438f0086abea10 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -290,6 +290,7 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) int ret; u32 precharge, dclk, com_invdir, compins; struct pwm_args pargs; + char status; if (par->device_info->need_pwm) { par->pwm = pwm_get(&par->client->dev, NULL); @@ -315,6 +316,13 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) par->pwm->pwm, par->pwm_period); }; + /* Check if we can talk to the display */ + ret = i2c_master_recv(par->client, &status, 1); + if (ret < 0) { + dev_err(&par->client->dev, "controller not found\n"); + return ret; + } + /* Set initial contrast */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_CONTRAST); if (ret < 0) From 19e45324517bbdffa857a50b249e18736cc30ecc Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 19 Dec 2017 16:46:15 +0100 Subject: [PATCH 11/93] fbdev: ssd1307fb: Rename i2c device ids For autoprobing to work on devicetree based systems, the compatible name is used when probing i2c devices. Because the device id is now not the same, the ssd1307 does not get autoprobed. By renaming the device id, autoprobing is now functional on devicetree based systems. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 438f0086abea10..93912ba01da194 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -756,10 +756,10 @@ static int ssd1307fb_remove(struct i2c_client *client) } static const struct i2c_device_id ssd1307fb_i2c_id[] = { - { "ssd1305fb", 0 }, - { "ssd1306fb", 0 }, - { "ssd1307fb", 0 }, - { "ssd1309fb", 0 }, + { "ssd1305fb-i2c", 0 }, + { "ssd1306fb-i2c", 0 }, + { "ssd1307fb-i2c", 0 }, + { "ssd1309fb-i2c", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ssd1307fb_i2c_id); From 8b0cab84e1304e30f866fffd647a0035495cbb18 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 11:29:47 +0100 Subject: [PATCH 12/93] fbdev: ssd1307fb: Make output a little more consistent The ssd1307fb driver is a little inconsistent in the debug messages. Common is to not capitalize the first letter and to not add any periods etc at the end. Also add a quick shortcut to access the device structure shortening our error messages. Finally take this cleanup opportunity to add the missing device.h header. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 93912ba01da194..4f3eab6d465785 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -291,11 +292,12 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) u32 precharge, dclk, com_invdir, compins; struct pwm_args pargs; char status; + struct device *dev = &par->client->dev; if (par->device_info->need_pwm) { - par->pwm = pwm_get(&par->client->dev, NULL); + par->pwm = pwm_get(dev, NULL); if (IS_ERR(par->pwm)) { - dev_err(&par->client->dev, "Could not get PWM from device tree!\n"); + dev_err(dev, "could not get PWM from device tree\n"); return PTR_ERR(par->pwm); } @@ -312,14 +314,14 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) 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", + dev_dbg(dev, "using PWM%d with a %dns period\n", par->pwm->pwm, par->pwm_period); }; /* Check if we can talk to the display */ ret = i2c_master_recv(par->client, &status, 1); if (ret < 0) { - dev_err(&par->client->dev, "controller not found\n"); + dev_err(dev, "controller not found\n"); return ret; } From 1dc264d60334c0637c53bf63266856500c47f594 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 11:33:00 +0100 Subject: [PATCH 13/93] fbdev: ssd1307fb: Use ratelimited error printing When we fail to send messages to our display, we print an error. However this 'spam' can continue heavily and add a heavy load on the system just to print these messages. Lets use the ratelimited variant of dev_err to lessen the system load. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 4f3eab6d465785..7fa613f55cdc58 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -124,7 +124,7 @@ static int ssd1307fb_write_array(struct i2c_client *client, ret = i2c_master_send(client, (u8 *)array, len); if (ret != len) { - dev_err(&client->dev, "Couldn't send I2C command.\n"); + dev_err_ratelimited(&client->dev, "couldn't send I2C command\n"); return ret; } From 7c6105c03901425304b09de9c86c1b617cd25e44 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 9 Mar 2018 11:58:11 +0100 Subject: [PATCH 14/93] fbdev: ssd1307fb: cleanup: Group fb_info.fix Some minor cleanup to group the fb_info.fix entries together. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 7fa613f55cdc58..d403adac910d23 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -649,8 +649,6 @@ static int ssd1307fb_probe(struct i2c_client *client, ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io; info->fbops = &ssd1307fb_ops; - info->fix = ssd1307fb_fix; - info->fix.line_length = par->width / 8; info->fbdefio = ssd1307fb_defio; info->var = ssd1307fb_var; @@ -667,8 +665,10 @@ static int ssd1307fb_probe(struct i2c_client *client, info->var.blue.offset = 0; info->screen_base = (u8 __force __iomem *)vmem; + info->fix = ssd1307fb_fix; info->fix.smem_start = __pa(vmem); info->fix.smem_len = vmem_size; + info->fix.line_length = par->width / 8; fb_deferred_io_init(info); From eaa8663e5c63b0eb8c0b569af5f1ee12e0ea4ee1 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 9 Mar 2018 11:59:15 +0100 Subject: [PATCH 15/93] fbdev: ssd1307fb: Use resolution instead of dimensions THe virtual resolution and line-length are attributes of the X and Y resolution, which are determined by the width/height of the display. Currently it is all directly determined from the width/height parameter. The width and height never change of the display, but the X, Y resolution can change when rotating the display. It thus makes more sense to determine the line length and virtual parameters from their resolution counterparts and not the display dimensions. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index d403adac910d23..f8f0677b2d4307 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -653,9 +653,9 @@ static int ssd1307fb_probe(struct i2c_client *client, info->var = ssd1307fb_var; info->var.xres = par->width; - info->var.xres_virtual = par->width; + info->var.xres_virtual = info->var.xres; info->var.yres = par->height; - info->var.yres_virtual = par->height; + info->var.yres_virtual = info->var.yres; info->var.red.length = 1; info->var.red.offset = 0; @@ -668,7 +668,7 @@ static int ssd1307fb_probe(struct i2c_client *client, info->fix = ssd1307fb_fix; info->fix.smem_start = __pa(vmem); info->fix.smem_len = vmem_size; - info->fix.line_length = par->width / 8; + info->fix.line_length = info->var.xres / 8; fb_deferred_io_init(info); From b1a1ce2192fccdd4d8071a186cd93115872f752c Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 9 Mar 2018 18:03:37 +0100 Subject: [PATCH 16/93] fbdev: ssd1307fb: Reduce magic values with defines The ssd1307fb driver has a few magic values that an happily be moved to some common defines improving readability. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 36 ++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index f8f0677b2d4307..0ba04c8227c810 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -6,6 +6,7 @@ * Licensed under the GPLv2 or later. */ +#include #include #include #include @@ -32,14 +33,30 @@ #define SSD1307FB_CONTRAST 0x81 #define SSD1307FB_CHARGE_PUMP 0x8d #define SSD1307FB_SEG_REMAP_ON 0xa1 +#define _SSD1307FB_CHARGE_PUMP_SET 0x10 +#define SSD1307FB_CHARGE_PUMP_SET(pump) \ + ((_SSD1307FB_CHARGE_PUMP_SET) | \ + ((pump) ? BIT(2) : 0)) #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_CLOCK_FREQ(freq, div) \ + ((((freq) << 4) & GENMASK(7, 4)) | \ + (((((div) - 1) << 0) & GENMASK(3, 0)))) #define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9 +#define SSD1307FB_PRECHARGE_PERIOD(period1, period2) \ + ((((period2 << 4) & GENMASK(7, 4) )) | \ + (((period1) << 0) & GENMASK(3, 0))) #define SSD1307FB_SET_COM_PINS_CONFIG 0xda +#define _SSD1307FB_COM_PINS_CONFIG 0x02 +#define SSD1307FB_COM_PINS_CONFIG(com_seq, com_lrremap) \ + ((_SSD1307FB_COM_PINS_CONFIG) | \ + ((com_lrremap) ? BIT(5) : 0) | \ + ((com_seq) ? 0 : BIT(4))) + #define SSD1307FB_SET_VCOMH 0xdb #define MAX_CONTRAST 255 @@ -289,7 +306,7 @@ static void ssd1307fb_deferred_io(struct fb_info *info, static int ssd1307fb_init(struct ssd1307fb_par *par) { int ret; - u32 precharge, dclk, com_invdir, compins; + u8 cmd; struct pwm_args pargs; char status; struct device *dev = &par->client->dev; @@ -370,8 +387,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4; - ret = ssd1307fb_write_cmd(par->client, dclk); + cmd = SSD1307FB_CLOCK_FREQ(par->dclk_frq, par->dclk_div); + ret = ssd1307fb_write_cmd(par->client, cmd); if (ret < 0) return ret; @@ -380,8 +397,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4; - ret = ssd1307fb_write_cmd(par->client, precharge); + cmd = SSD1307FB_PRECHARGE_PERIOD(par->prechargep1, par->prechargep2); + ret = ssd1307fb_write_cmd(par->client, cmd); if (ret < 0) return ret; @@ -390,9 +407,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - compins = 0x02 | !(par->com_seq & 0x1) << 4 - | (par->com_lrremap & 0x1) << 5; - ret = ssd1307fb_write_cmd(par->client, compins); + cmd = SSD1307FB_COM_PINS_CONFIG(par->com_seq, par->com_lrremap); + ret = ssd1307fb_write_cmd(par->client, cmd); if (ret < 0) return ret; @@ -410,8 +426,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, - BIT(4) | (par->device_info->need_chargepump ? BIT(2) : 0)); + cmd = SSD1307FB_CHARGE_PUMP_SET(par->device_info->need_chargepump); + ret = ssd1307fb_write_cmd(par->client, cmd); if (ret < 0) return ret; From 927589c4d96dec40226048828d51a12c03aae922 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 9 Mar 2018 18:06:59 +0100 Subject: [PATCH 17/93] fbdev: ssd1307fb: Remove unused forward declaration In commit c89eacfc70067 ("fbdev: ssd1307fb: Unify init code and obtain hw specific bits from DT") the need for the forward declaration struct ssd1307fb_par was removed with the removal of the init/remove ssd1307fb_ops. Clear up the lingering unused forward declaration. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 0ba04c8227c810..00f81bb5c81cc6 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -66,8 +66,6 @@ static u_int refreshrate = REFRESHRATE; module_param(refreshrate, uint, 0); -struct ssd1307fb_par; - struct ssd1307fb_deviceinfo { u32 default_vcomh; u32 default_dclk_div; From 8ae279641088cf14593e47edcf4be5bfe0e2df1c Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Sat, 10 Mar 2018 10:18:28 +0100 Subject: [PATCH 18/93] fbdev: ssd1307fb: Ensure set_par can do bring-up To support rotation from sysfs, we need to implement set_par at the least. According to the skeletonfb driver the set_par: Again if you can't change the resolution you don't need this function. However, even if your hardware does not support mode changing, a set_par might be needed to at least initialize the hardware to a known working state, especially if it came back from another process that also modifies the same hardware, such as X. Further more, after heavy usage in the last 3 years, we noticed that the scanout related registers can become corrupted by the hardware under certain conditions resulting in shifted scanouts. We thus concluded that we need to re-set the scan-addressing registers after certain operations, such as rotation. This patch thus moves these critical settings from init to set_par. Since we do not need to validate any 'var' parameters, the check_var function has been omitted for now. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 102 ++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 00f81bb5c81cc6..ccba70c6039de7 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -264,6 +264,64 @@ static int ssd1307fb_blank(int blank_mode, struct fb_info *info) return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); } +static int ssd1307fb_set_par(struct fb_info *info) +{ + struct ssd1307fb_par *par = info->par; + int ret; + + /* Set segment re-map */ + 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; + + /* Switch to horizontal addressing mode */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, + SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); + if (ret < 0) + return ret; + + /* Set column range */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, 0x0); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, par->width - 1); + if (ret < 0) + return ret; + + /* Set page range */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, 0x0); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, + par->page_offset + (par->height / 8) - 1); + if (ret < 0) + return ret; + + return 0; +} + static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { struct ssd1307fb_par *par = info->par; @@ -290,6 +348,7 @@ static struct fb_ops ssd1307fb_ops = { .fb_read = fb_sys_read, .fb_write = ssd1307fb_write, .fb_blank = ssd1307fb_blank, + .fb_set_par = ssd1307fb_set_par, .fb_fillrect = ssd1307fb_fillrect, .fb_copyarea = ssd1307fb_copyarea, .fb_imageblit = ssd1307fb_imageblit, @@ -429,43 +488,6 @@ static int ssd1307fb_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); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, - SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); - if (ret < 0) - return ret; - - /* Set column range */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, 0x0); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, par->width - 1); - if (ret < 0) - return ret; - - /* Set page range */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, 0x0); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, - par->page_offset + (par->height / 8) - 1); - if (ret < 0) - return ret; - /* Clear the screen */ ssd1307fb_update_display(par); @@ -709,6 +731,12 @@ static int ssd1307fb_probe(struct i2c_client *client, if (ret) goto regulator_enable_error; + ret = ssd1307fb_set_par(info); + if (ret) { + dev_err(&client->dev, "unable to setup parameters\n"); + goto bl_init_error; + } + ret = register_framebuffer(info); if (ret) { dev_err(&client->dev, "Couldn't register the framebuffer\n"); From ee7ff4cf9e19b2c9a71ca29b92c3456823e83f65 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Sat, 10 Mar 2018 10:26:36 +0100 Subject: [PATCH 19/93] fbdev: ssd1307fb: Ensure display is toggled properly Currently, we 'enable' the display in our last step of init. The enable mend of the display however should be the very last step of our probing sequence, e.g. just before saying 'driver loaded successfully', as this is the time we are 'ready'. This patch uses the later introduced ssd1307fb_(un)blank function to achieve this. Further more, we want to guarantee that the display is in its off state during initialization, so after reset, we guarantee that the display is off. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index ccba70c6039de7..1ed2bfd516452c 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -491,11 +491,6 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) /* Clear the screen */ ssd1307fb_update_display(par); - /* Turn on the display */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); - if (ret < 0) - return ret; - return 0; } @@ -718,6 +713,13 @@ static int ssd1307fb_probe(struct i2c_client *client, udelay(4); } + /* Ensure display is turned off while initializing */ + ret = ssd1307fb_blank(FB_BLANK_NORMAL, info); + if (ret) { + dev_err(&client->dev, "unable to blank screen\n"); + goto bl_init_error; + } + if (par->vbat_reg) { ret = regulator_enable(par->vbat_reg); if (ret) { @@ -757,6 +759,13 @@ static int ssd1307fb_probe(struct i2c_client *client, bl->props.max_brightness = MAX_CONTRAST; info->bl_dev = bl; + /* Turn on the display */ + ret = ssd1307fb_blank(FB_BLANK_UNBLANK, info); + if (ret) { + dev_err(&client->dev, "unable to unblank screen\n"); + goto bl_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; From e8ada8957c823c03b53c0c55cb1a5e44edce429d Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Sat, 10 Mar 2018 10:43:57 +0100 Subject: [PATCH 20/93] fbdev: ssd1307fb: Add hardware accelerated rotation support The ssd130x series of chips are very flexible, allowing customers to use any layout to connect the LCD to the PCB. We can use this in combination with the different scan-out parameters to flip and rotate the display at will. E.g. A X and Y flip results in a 180 degree rotation. Changing the scanout from horizontal to vertical rotates by 90 degree's. This thus gives us hardware accelerated rotation support. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 147 +++++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 30 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 1ed2bfd516452c..199e5979e638a0 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -20,6 +20,7 @@ #include #include #include +#include #define SSD1307FB_DATA 0x40 #define SSD1307FB_COMMAND 0x80 @@ -32,15 +33,22 @@ #define SSD1307FB_SET_PAGE_RANGE 0x22 #define SSD1307FB_CONTRAST 0x81 #define SSD1307FB_CHARGE_PUMP 0x8d -#define SSD1307FB_SEG_REMAP_ON 0xa1 #define _SSD1307FB_CHARGE_PUMP_SET 0x10 #define SSD1307FB_CHARGE_PUMP_SET(pump) \ ((_SSD1307FB_CHARGE_PUMP_SET) | \ ((pump) ? BIT(2) : 0)) +#define _SSD1307FB_SEG_REMAP 0xa0 +#define SSD1307FB_SEG_REMAP(seg_remap) \ + (_SSD1307FB_SEG_REMAP | \ + ((seg_remap) ? BIT(0) : 0x0)) #define SSD1307FB_DISPLAY_OFF 0xae #define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8 #define SSD1307FB_DISPLAY_ON 0xaf #define SSD1307FB_START_PAGE_ADDRESS 0xb0 +#define _SSD1307FB_COM_INVDIR 0xc0 +#define SSD1307FB_COM_INVDIR(com_invdir)\ + (_SSD1307FB_COM_INVDIR | \ + ((com_invdir) ? BIT(3) : 0x0)) #define SSD1307FB_SET_DISPLAY_OFFSET 0xd3 #define SSD1307FB_SET_CLOCK_FREQ 0xd5 #define SSD1307FB_CLOCK_FREQ(freq, div) \ @@ -74,6 +82,12 @@ struct ssd1307fb_deviceinfo { int need_chargepump; }; +struct ssd1307fb_rot_lut { + u8 seg_remap; + u8 com_invdir; + u8 addr_mode; +}; + struct ssd1307fb_par { u32 com_invdir; u32 com_lrremap; @@ -92,6 +106,9 @@ struct ssd1307fb_par { struct pwm_device *pwm; u32 pwm_period; struct gpio_desc *reset; + bool no_clear_on_probe; + u8 rotate; + struct ssd1307fb_rot_lut rot_lut[FB_ROTATE_CCW + 1]; struct regulator *vbat_reg; u32 seg_remap; u32 vcomh; @@ -264,31 +281,53 @@ static int ssd1307fb_blank(int blank_mode, struct fb_info *info) return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); } -static int ssd1307fb_set_par(struct fb_info *info) +static int ssd1307fb_rotate(struct fb_info *info) { struct ssd1307fb_par *par = info->par; + u32 rot = info->var.rotate; int ret; - /* Set segment re-map */ - if (par->seg_remap) { - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); - if (ret < 0) - return ret; - }; + if (par->rotate == rot) + return 0; - /* Set COM direction */ - com_invdir = 0xc0 | (par->com_invdir & 0x1) << 3; - ret = ssd1307fb_write_cmd(par->client, com_invdir); + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); if (ret < 0) return ret; - /* Switch to horizontal addressing mode */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); + ret = ssd1307fb_write_cmd(par->client, par->rot_lut[rot].addr_mode); if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, - SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); + ret = ssd1307fb_write_cmd(par->client, par->rot_lut[rot].seg_remap); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, par->rot_lut[rot].com_invdir); + if (ret < 0) + return ret; + + if ((rot == FB_ROTATE_CW) || (rot == FB_ROTATE_CCW)) { + info->var.xres = par->height; + info->var.yres = par->width; + } else { + info->var.xres = par->width; + info->var.yres = par->height; + } + info->var.xres_virtual = info->var.xres; + info->var.yres_virtual = info->var.yres; + info->fix.line_length = info->var.xres_virtual / 8; + + par->rotate = rot; + + return 0; +} + +static int ssd1307fb_set_par(struct fb_info *info) +{ + struct ssd1307fb_par *par = info->par; + int ret; + + ret = ssd1307fb_rotate(info); if (ret < 0) return ret; @@ -336,6 +375,20 @@ static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *a ssd1307fb_update_display(par); } +static int ssd1307fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct ssd1307fb_par *par = info->par; + + if (par->rotate != var->rotate) { + if (var->rotate > FB_ROTATE_CCW) { + var->rotate = par->rotate; + return -EINVAL; + } + } + + return 0; +} + static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image) { struct ssd1307fb_par *par = info->par; @@ -351,6 +404,7 @@ static struct fb_ops ssd1307fb_ops = { .fb_set_par = ssd1307fb_set_par, .fb_fillrect = ssd1307fb_fillrect, .fb_copyarea = ssd1307fb_copyarea, + .fb_check_var = ssd1307fb_check_var, .fb_imageblit = ssd1307fb_imageblit, }; @@ -408,19 +462,6 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - /* Set segment re-map */ - 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; - /* Set multiplex ratio value */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO); if (ret < 0) @@ -588,6 +629,8 @@ static int ssd1307fb_probe(struct i2c_client *client, struct fb_deferred_io *ssd1307fb_defio; u32 vmem_size; struct ssd1307fb_par *par; + struct fb_of_properties prop; + bool seg_remap, com_invdir; u8 *vmem; int ret; @@ -629,6 +672,10 @@ static int ssd1307fb_probe(struct i2c_client *client, } } + fb_parse_properties(&client->dev, &prop); + par->rotate = prop.rotate; + par->no_clear_on_probe = prop.no_clear_on_probe; + if (of_property_read_u32(node, "solomon,width", &par->width)) par->width = 96; @@ -647,10 +694,10 @@ static int ssd1307fb_probe(struct i2c_client *client, 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"); + 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"); + com_invdir = of_property_read_bool(node, "solomon,com-invdir"); par->contrast = 127; par->vcomh = par->device_info->default_vcomh; @@ -680,13 +727,47 @@ static int ssd1307fb_probe(struct i2c_client *client, ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io; info->fbops = &ssd1307fb_ops; + info->flags = FBINFO_HWACCEL_ROTATE; info->fbdefio = ssd1307fb_defio; info->var = ssd1307fb_var; + + /* + * We really only support flipping of the display and so we use flip + * an both x and y flip to rotate from 0 -> 180 degree's. + */ + par->rot_lut[FB_ROTATE_UR].addr_mode = SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL; + par->rot_lut[FB_ROTATE_UR].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? true : false); + par->rot_lut[FB_ROTATE_UR].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? true : false); + par->rot_lut[FB_ROTATE_UD].addr_mode = SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL; + par->rot_lut[FB_ROTATE_UD].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? false : true); + par->rot_lut[FB_ROTATE_UD].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? false : true); + + /* + * The same trick is used to flip from 90 -> 270 degree's. + * To rotate 0 -> 90 degree's we tell the controller to use vertical + * scan-out rather then horizontal. + */ + par->rot_lut[FB_ROTATE_CW].addr_mode = SSD1307FB_SET_ADDRESS_MODE_VERTICAL; + par->rot_lut[FB_ROTATE_CW].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? true : false); + par->rot_lut[FB_ROTATE_CW].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? true : false); + par->rot_lut[FB_ROTATE_CCW].addr_mode = SSD1307FB_SET_ADDRESS_MODE_VERTICAL; + par->rot_lut[FB_ROTATE_CCW].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? false : true); + par->rot_lut[FB_ROTATE_CCW].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? false : true); + + if ((par->rotate == FB_ROTATE_CW) || (par->rotate == FB_ROTATE_CCW)) { + info->var.xres = par->height; + info->var.yres = par->width; + } else { + info->var.xres = par->width; + info->var.yres = par->height; + } + info->var.xres = par->width; info->var.xres_virtual = info->var.xres; info->var.yres = par->height; info->var.yres_virtual = info->var.yres; + info->var.rotate = par->rotate; info->var.red.length = 1; info->var.red.offset = 0; @@ -733,6 +814,12 @@ static int ssd1307fb_probe(struct i2c_client *client, if (ret) goto regulator_enable_error; + ret = ssd1307fb_check_var(&info->var, info); + if (ret) { + dev_err(&client->dev, "unable to check parameters\n"); + goto bl_init_error; + } + ret = ssd1307fb_set_par(info); if (ret) { dev_err(&client->dev, "unable to setup parameters\n"); From 9f2ede45174b111ed3adb686828cf79098444554 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 12 Mar 2018 09:34:59 +0100 Subject: [PATCH 21/93] fbdev: ssd1307fb: Limit to support 0 - 180 rotation only The ssd1307fb can in theory support 90 and 270 degree rotations. However this has not been thoroughly tested yet and there is more to it then initially assumed (just changing the scanout to vertical may not be enough). Due to the lack of time to properly research and implement this, only allow 0 and 180 rotations for now. Signed-off-by: Olliver Schinagl --- drivers/video/fbdev/ssd1307fb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 199e5979e638a0..a36a8b0788cbc4 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -379,6 +379,10 @@ static int ssd1307fb_check_var(struct fb_var_screeninfo *var, struct fb_info *in { struct ssd1307fb_par *par = info->par; + /* Only up right and upside down rotations are supported */ + if ((var->rotate != FB_ROTATE_UR) && (var->rotate != FB_ROTATE_UD)) + return -EINVAL; + if (par->rotate != var->rotate) { if (var->rotate > FB_ROTATE_CCW) { var->rotate = par->rotate; From 83e7d988370b863d998d24ea7d4f24eb9c9d39ba Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 22 Mar 2018 11:27:59 +0100 Subject: [PATCH 22/93] fbdev: ssd1307fb: Do not always clear the display on boot Commit 6e376822ee9e06c5 ("fbdev/ssd1307fb: clear screen in probe") in combination with commit fdde1a8148d81617 ("fbdev: ssd1307fb: Make reset gpio devicetree property optiona") changed the previous default behavior of keeping the display active. This for example is a feature that is used to show a splash screen from u-boot and leave it on the controller until linux/an application is ready to show its own information. We thus let the devicetree control this behavior via the no-clear-on-probe flag, which skips the clearing of the framebuffer and thus keeps a splash screen on the device. The default is left to clear the screen (unset in the devicetree) as the new default does make sense. If u-boot does not initialize the screen and does not show a splash-screen garbled displays can occur. Signed-off-by: Olliver Schinagl --- Documentation/devicetree/bindings/display/fbdev.txt | 2 +- drivers/video/fbdev/core/fb_of.c | 3 +-- drivers/video/fbdev/ssd1307fb.c | 3 ++- include/linux/fb.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/display/fbdev.txt b/Documentation/devicetree/bindings/display/fbdev.txt index d3ed6ef846f614..5c572a123e6a61 100644 --- a/Documentation/devicetree/bindings/display/fbdev.txt +++ b/Documentation/devicetree/bindings/display/fbdev.txt @@ -1,6 +1,6 @@ General framebuffer properties: Optional properties for framebuffers: - - clear-on-probe : clear the framebuffer during probe (bool) + - no-clear-on-probe : Do not clear the framebuffer during probe (bool) - rotate : rotate a framebuffer over 0, 1, 2 and 3 (or 0, 90, 180 and 270) degree's (integer) diff --git a/drivers/video/fbdev/core/fb_of.c b/drivers/video/fbdev/core/fb_of.c index 57bc3dc30bf3d5..cb2de340d614c9 100644 --- a/drivers/video/fbdev/core/fb_of.c +++ b/drivers/video/fbdev/core/fb_of.c @@ -17,10 +17,9 @@ void fb_parse_properties(struct device *dev, struct fb_of_properties *prop) { - bool clear; u16 rotate; - clear = device_property_read_bool(dev, "clear-on-probe"); + prop->no_clear_on_probe = device_property_read_bool(dev, "no-clear-on-probe"); if (device_property_read_u16(dev, "rotate", &rotate)) rotate = FB_ROTATE_UR; diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index a36a8b0788cbc4..d7b6cc384492ac 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -534,7 +534,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) return ret; /* Clear the screen */ - ssd1307fb_update_display(par); + if (!par->no_clear_on_probe) + ssd1307fb_update_display(par); return 0; } diff --git a/include/linux/fb.h b/include/linux/fb.h index 08b6825b00152e..b62113926c5703 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -121,7 +121,7 @@ struct fb_cursor_user { }; struct fb_of_properties { - bool clear_on_probe; + bool no_clear_on_probe; __u8 rotate; }; From 1cd397f0a6f24e26085fc9b69c0a91f9efd06dd8 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 11:58:32 +0100 Subject: [PATCH 23/93] gpio: pca953x: add support for the NXP pca9570 The PCA9570 is the most basic of the pca957x series. It is a 4 bit i2c gpio expander without an interrupt line. Signed-off-by: Olliver Schinagl --- Documentation/devicetree/bindings/gpio/gpio-pca953x.txt | 1 + drivers/gpio/gpio-pca953x.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/gpio-pca953x.txt b/Documentation/devicetree/bindings/gpio/gpio-pca953x.txt index 7f57271df2bc96..78511d54cafb88 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-pca953x.txt +++ b/Documentation/devicetree/bindings/gpio/gpio-pca953x.txt @@ -13,6 +13,7 @@ Required properties: nxp,pca9555 nxp,pca9556 nxp,pca9557 + nxp,pca9570 nxp,pca9574 nxp,pca9575 nxp,pca9698 diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index 1b9dbf691ae7a8..1e82f956a7d40e 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -66,6 +66,7 @@ static const struct i2c_device_id pca953x_id[] = { { "pca9555", 16 | PCA953X_TYPE | PCA_INT, }, { "pca9556", 8 | PCA953X_TYPE, }, { "pca9557", 8 | PCA953X_TYPE, }, + { "pca9570", 4 | PCA957X_TYPE, }, { "pca9574", 8 | PCA957X_TYPE | PCA_INT, }, { "pca9575", 16 | PCA957X_TYPE | PCA_INT, }, { "pca9698", 40 | PCA953X_TYPE, }, @@ -931,6 +932,7 @@ static const struct of_device_id pca953x_dt_ids[] = { { .compatible = "nxp,pca9555", .data = OF_953X(16, PCA_INT), }, { .compatible = "nxp,pca9556", .data = OF_953X( 8, 0), }, { .compatible = "nxp,pca9557", .data = OF_953X( 8, 0), }, + { .compatible = "nxp,pca9570", .data = OF_957X( 4, 0), }, { .compatible = "nxp,pca9574", .data = OF_957X( 8, PCA_INT), }, { .compatible = "nxp,pca9575", .data = OF_957X(16, PCA_INT), }, { .compatible = "nxp,pca9698", .data = OF_953X(40, 0), }, From c8388f76694366fbd8cec708a4d335c4afe0b899 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 8 Aug 2017 11:32:40 +0200 Subject: [PATCH 24/93] leds: pca963x: abort probe if device is not connected Currently, the driver always successfully manages to load, even if there is no actual chip present (or communication is not possible over i2c). This has the side-effect, that the node is always available in sysfs, and writing data to it which results in i2c errors. To improve behavior here, we try to read the first byte from the device, and if this was okay, we at least know there is some device on the bus, even though we do not know which one. Signed-off-by: Olliver Schinagl --- drivers/leds/leds-pca963x.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 3bf9a127181927..e5af8db0339ea8 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -412,6 +412,13 @@ static int pca963x_probe(struct i2c_client *client, if (!pca963x) return -ENOMEM; + /* Check if chip actually exists on the bus */ + err = i2c_smbus_read_byte_data(client, PCA963X_MODE1); + if (err < 0) { + dev_err(&client->dev, "controller not found"); + return err; + } + i2c_set_clientdata(client, pca963x_chip); mutex_init(&pca963x_chip->mutex); From 5e9b4e31916c52499a08db505c59cfa29d472972 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 29 May 2015 16:09:43 +0200 Subject: [PATCH 25/93] leds: pca963x: alphabetize headers Re-order headers so they are in alphabetical order. Signed-off-by: Olliver Schinagl --- drivers/leds/leds-pca963x.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index e5af8db0339ea8..a62416038f4a8c 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -26,16 +26,16 @@ */ #include -#include -#include -#include #include -#include +#include #include #include -#include +#include +#include #include #include +#include +#include /* LED select registers determine the source that drives LED outputs */ #define PCA963X_LED_OFF 0x0 /* LED driver off */ From b322f0db84c647fdc78777861cc206b6b23a06c6 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 16 Dec 2015 13:42:25 +0100 Subject: [PATCH 26/93] leds: pca963x: add defines and remove some magic values This patch adds some more defines so that the driver can receive a little more future work. These new defines are then used throughout the existing code the remove some magic values. This patch does not produce any binary changes. Signed-off-by: Olliver Schinagl --- drivers/leds/leds-pca963x.c | 149 ++++++++++++++++++++++++------------ 1 file changed, 102 insertions(+), 47 deletions(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index a62416038f4a8c..e420a270a3c985 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -37,17 +37,68 @@ #include #include -/* LED select registers determine the source that drives LED outputs */ -#define PCA963X_LED_OFF 0x0 /* LED driver off */ -#define PCA963X_LED_ON 0x1 /* LED driver on */ -#define PCA963X_LED_PWM 0x2 /* Controlled through PWM */ -#define PCA963X_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ - -#define PCA963X_MODE2_DMBLNK 0x20 /* Enable blinking */ - -#define PCA963X_MODE1 0x00 -#define PCA963X_MODE2 0x01 -#define PCA963X_PWM_BASE 0x02 +#define PCA963X_MAX_NAME_LEN 32 + +#define PCA963X_MODE1 0x00 /* mode 1 register addr */ +#define PCA963X_MODE1_ALLCALL_ON BIT(0) /* respond to LED All Call */ +#define PCA963X_MODE1_RESPOND_SUB3 BIT(1) /* respond to Sub address 3 */ +#define PCA963X_MODE1_RESPOND_SUB2 BIT(2) /* respond to Sub address 2 */ +#define PCA963X_MODE1_RESPOND_SUB1 BIT(3) /* respond to Sub address 1 */ +#define PCA963X_MODE1_SLEEP BIT(4) /* put in low power mode */ +#define PCA963X_MODE1_AI_ROLL_PWM BIT(5) /* auto-increment only PWM's */ +#define PCA963X_MODE1_AI_ROLL_GRP BIT(6) /* AI only group-controls */ +#define PCA963X_MODE1_AI_EN BIT(7) /* enable Auto-Increment */ + +#define PCA963X_MODE2 0x01 /* mode 2 register addr */ +#define PCA963X_MODE2_OUTNE_OUTDRV BIT(0) /* outdrv determines LED state */ +#define PCA963X_MODE2_OUTNE_HIZ BIT(1) /* LED-state in Hi-Z */ +#define PCA963X_MODE2_OUTDRV_TOTEM_POLE BIT(2) /* outputs are totem-pole'd */ +#define PCA963X_MODE2_OCH_ACK BIT(3) /* out change on ACK else STOP */ +#define PCA963X_MODE2_INVRT BIT(4) /* output logic state inverted */ +#define PCA963X_MODE2_DMBLNK BIT(5) /* grp-ctrl blink else dimming */ + +#define PCA963X_PWM_ADDR(led) (0x02 + (led)) + +#define PCA9633_GRPPWM 0x06 /* group PWM duty cycle ctrl for PCA9633 */ +#define PCA9634_GRPPWM 0x0a /* group PWM duty cycle ctrl for PCA9634 */ +#define PCA9635_GRPPWM 0x12 /* group PWM duty cycle ctrl for PCA9635 */ +#define PCA9633_GRPFREQ 0x07 /* group frequency control for PCA9633 */ +#define PCA9634_GRPFREQ 0x0b /* group frequency control for PCA9634 */ +#define PCA9635_GRPFREQ 0x13 /* group frequency control for PCA9635 */ + +#define PCA9633_LEDOUT_BASE 0x08 /* LED output state 0 reg for PCA9633 */ +#define PCA9634_LEDOUT_BASE 0x0c /* LED output state 0 reg for PCA9635 */ +#define PCA9635_LEDOUT_BASE 0x14 /* LED output state 0 reg for PCA9634 */ +#define PCA963X_LEDOUT_ADDR(ledout_base, led_num) \ + ((ledout_base) + ((led_num) / 4)) + +#define PCA963X_LEDOUT_LED_OFF 0x0 /* LED off */ +#define PCA963X_LEDOUT_LED_ON 0x1 /* LED on */ +#define PCA963X_LEDOUT_LED_PWM 0x2 /* LED PWM mode */ +#define PCA963X_LEDOUT_LED_GRP_PWM 0x3 /* LED PWM + group PWM mode */ +#define PCA963X_LEDOUT_MASK GENMASK(1, 0) + +#define PCA963X_LEDOUT_LDR(drive, led_num) \ + (((drive) & PCA963X_LEDOUT_MASK) << (((led_num) % 4) << 1)) +#define PCA963X_LEDOUT_LDR_INV(drive, led_num) \ + (((drive) >> (((led_num) % 4) << 1)) & PCA963X_LEDOUT_MASK) + +#define PCA9633_SUBADDR(x) \ + ((((x) - 1) % 0x3) + 0x09) /* I2C subaddr for PCA9633 */ +#define PCA9634_SUBADDR(x) \ + ((((x) - 1) % 0x3) + 0x0e) /* I2C subaddr for PCA9634 */ +#define PCA9635_SUBADDR(x) \ + ((((x) - 1) % 0x3) + 0x18) /* I2C subaddr for PCA9635 */ +#define PCA963X_SUBADDR_SET(x) (((x) << 1) & 0xfe) + +#define PCA9633_ALLCALLADDR 0x0c /* I2C Led all call address for PCA9633 */ +#define PCA9634_ALLCALLADDR 0x11 /* I2C Led all call address for PCA9634 */ +#define PCA9635_ALLCALLADDR 0x1b /* I2C Led all call address for PCA9635 */ +#define PCA963X_ALLCALLADDR_SET(x) (((x) << 1) & 0xfe) + +/* Software reset password */ +#define PCA963X_PASSKEY1 0xa5 +#define PCA963X_PASSKEY2 0x5a enum pca963x_type { pca9633, @@ -65,21 +116,21 @@ struct pca963x_chipdef { static struct pca963x_chipdef pca963x_chipdefs[] = { [pca9633] = { - .grppwm = 0x6, - .grpfreq = 0x7, - .ledout_base = 0x8, + .grppwm = PCA9633_GRPPWM, + .grpfreq = PCA9633_GRPFREQ, + .ledout_base = PCA9633_LEDOUT_BASE, .n_leds = 4, }, [pca9634] = { - .grppwm = 0xa, - .grpfreq = 0xb, - .ledout_base = 0xc, + .grppwm = PCA9634_GRPPWM, + .grpfreq = PCA9634_GRPFREQ, + .ledout_base = PCA9634_LEDOUT_BASE, .n_leds = 8, }, [pca9635] = { - .grppwm = 0x12, - .grpfreq = 0x13, - .ledout_base = 0x14, + .grppwm = PCA9635_GRPPWM, + .grpfreq = PCA9635_GRPFREQ, + .ledout_base = PCA9635_LEDOUT_BASE, .n_leds = 16, }, }; @@ -120,7 +171,7 @@ struct pca963x_led { struct pca963x *chip; struct led_classdev led_cdev; int led_num; /* 0 .. 15 potentially */ - char name[32]; + char name[PCA963X_MAX_NAME_LEN]; u8 gdc; u8 gfrq; }; @@ -128,48 +179,44 @@ struct pca963x_led { static int pca963x_brightness(struct pca963x_led *pca963x, enum led_brightness brightness) { - u8 ledout_addr = pca963x->chip->chipdef->ledout_base - + (pca963x->led_num / 4); + u8 ledout_addr; u8 ledout; - int shift = 2 * (pca963x->led_num % 4); - u8 mask = 0x3 << shift; int ret; + ledout_addr = PCA963X_LEDOUT_ADDR(pca963x->chip->chipdef->ledout_base, + pca963x->led_num); ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); + ledout &= ~PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_MASK, pca963x->led_num); switch (brightness) { case LED_FULL: - ret = i2c_smbus_write_byte_data(pca963x->chip->client, - ledout_addr, - (ledout & ~mask) | (PCA963X_LED_ON << shift)); + ledout |= PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_ON, + pca963x->led_num); break; case LED_OFF: - ret = i2c_smbus_write_byte_data(pca963x->chip->client, - ledout_addr, ledout & ~mask); + ledout |= PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_OFF, + pca963x->led_num); break; default: ret = i2c_smbus_write_byte_data(pca963x->chip->client, - PCA963X_PWM_BASE + pca963x->led_num, + PCA963X_PWM_ADDR(pca963x->led_num), brightness); if (ret < 0) return ret; - ret = i2c_smbus_write_byte_data(pca963x->chip->client, - ledout_addr, - (ledout & ~mask) | (PCA963X_LED_PWM << shift)); + ledout |= PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_PWM, + pca963x->led_num); break; } - return ret; + return i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + ledout); } static void pca963x_blink(struct pca963x_led *pca963x) { - u8 ledout_addr = pca963x->chip->chipdef->ledout_base + - (pca963x->led_num / 4); + u8 ledout_addr; u8 ledout; u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, PCA963X_MODE2); - int shift = 2 * (pca963x->led_num % 4); - u8 mask = 0x3 << shift; i2c_smbus_write_byte_data(pca963x->chip->client, pca963x->chip->chipdef->grppwm, pca963x->gdc); @@ -181,11 +228,17 @@ static void pca963x_blink(struct pca963x_led *pca963x) i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2, mode2 | PCA963X_MODE2_DMBLNK); + ledout_addr = PCA963X_LEDOUT_ADDR(pca963x->chip->chipdef->ledout_base, + pca963x->led_num); mutex_lock(&pca963x->chip->mutex); ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); - if ((ledout & mask) != (PCA963X_LED_GRP_PWM << shift)) + if (PCA963X_LEDOUT_LDR_INV(ledout, pca963x->led_num) != + PCA963X_LEDOUT_LED_GRP_PWM) { + ledout |= PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_GRP_PWM, + pca963x->led_num); i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, - (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift)); + ledout); + } mutex_unlock(&pca963x->chip->mutex); } @@ -201,7 +254,7 @@ static int pca963x_power_state(struct pca963x_led *pca963x) if (!(*leds_on) != !cached_leds) return i2c_smbus_write_byte_data(pca963x->chip->client, - PCA963X_MODE1, *leds_on ? 0 : BIT(4)); + PCA963X_MODE1, *leds_on ? 0 : PCA963X_MODE1_SLEEP); return 0; } @@ -428,7 +481,8 @@ static int pca963x_probe(struct i2c_client *client, /* Turn off LEDs by default*/ for (i = 0; i < chip->n_leds / 4; i++) - i2c_smbus_write_byte_data(client, chip->ledout_base + i, 0x00); + i2c_smbus_write_byte_data(client, chip->ledout_base + i, + PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_OFF, i)); for (i = 0; i < chip->n_leds; i++) { pca963x[i].led_num = i; @@ -462,19 +516,20 @@ static int pca963x_probe(struct i2c_client *client, } /* Disable LED all-call address, and power down initially */ - i2c_smbus_write_byte_data(client, PCA963X_MODE1, BIT(4)); + i2c_smbus_write_byte_data(client, PCA963X_MODE1, PCA963X_MODE1_SLEEP); if (pdata) { u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, PCA963X_MODE2); /* Configure output: open-drain or totem pole (push-pull) */ if (pdata->outdrv == PCA963X_OPEN_DRAIN) - mode2 |= 0x01; + mode2 |= PCA963X_MODE2_OUTNE_OUTDRV; else - mode2 |= 0x05; + mode2 |= PCA963X_MODE2_OUTNE_OUTDRV | + PCA963X_MODE2_OUTDRV_TOTEM_POLE; /* Configure direction: normal or inverted */ if (pdata->dir == PCA963X_INVERTED) - mode2 |= 0x10; + mode2 |= PCA963X_MODE2_INVRT; i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2, mode2); } From c8f817ef7d07e6dc9208d363362e70ba7604067b Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 4 Oct 2017 13:30:56 +0200 Subject: [PATCH 27/93] leds: pca963x: save mode when setting power_state Currently, we blast sleep mode or 0 into the mode1 register, without taking notice of whatever was in it before. Lets be a bit more proper here and read the register and modify only the bit which we are concerned. Signed-off-by: Olliver Schinagl --- drivers/leds/leds-pca963x.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index e420a270a3c985..559332cc4ec861 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -252,9 +252,17 @@ static int pca963x_power_state(struct pca963x_led *pca963x) else clear_bit(pca963x->led_num, leds_on); - if (!(*leds_on) != !cached_leds) + if (!(*leds_on) != !cached_leds) { + u8 mode1 = i2c_smbus_read_byte_data(pca963x->chip->client, + PCA963X_MODE1); + + if (*leds_on) + mode1 &= ~PCA963X_MODE1_SLEEP; + else + mode1 |= PCA963X_MODE1_SLEEP; return i2c_smbus_write_byte_data(pca963x->chip->client, - PCA963X_MODE1, *leds_on ? 0 : PCA963X_MODE1_SLEEP); + PCA963X_MODE1, mode1); + } return 0; } From 00537b85d032edd0be5ebac9a787feb0cd2530f5 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 4 Oct 2017 13:58:30 +0200 Subject: [PATCH 28/93] leds: pca963x: refactor initial led output a little Let's improve readability a little bit by reordering initial LED output configuration. Signed-off-by: Olliver Schinagl --- drivers/leds/leds-pca963x.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 559332cc4ec861..ba7cea1dd3db0c 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -529,12 +529,12 @@ static int pca963x_probe(struct i2c_client *client, if (pdata) { u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, PCA963X_MODE2); + + /* Always enable LED output */ + mode2 |= PCA963X_MODE2_OUTNE_OUTDRV; /* Configure output: open-drain or totem pole (push-pull) */ - if (pdata->outdrv == PCA963X_OPEN_DRAIN) - mode2 |= PCA963X_MODE2_OUTNE_OUTDRV; - else - mode2 |= PCA963X_MODE2_OUTNE_OUTDRV | - PCA963X_MODE2_OUTDRV_TOTEM_POLE; + if (pdata->outdrv == PCA963X_TOTEM_POLE) + mode2 |= PCA963X_MODE2_OUTDRV_TOTEM_POLE; /* Configure direction: normal or inverted */ if (pdata->dir == PCA963X_INVERTED) mode2 |= PCA963X_MODE2_INVRT; From f309bbe62aa7fa0eca680bcba42cb54d43fb5d5d Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 5 Apr 2016 14:03:14 +0200 Subject: [PATCH 29/93] leds: pca963x: remove whitespace and checkpatch problems This patch does some whitespace fixing to make the entire driver more consistent, especially with regards to alignment. Because of this it also reduces quite some of the checkpatch warnings. This did came at the cost of some minor 80 char warnings however to satisfy the alignment and improve readability. This patch does not introduce any binary changes. Signed-off-by: Olliver Schinagl --- drivers/leds/leds-pca963x.c | 58 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index ba7cea1dd3db0c..93f9a6f741b6f1 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -144,7 +144,7 @@ static const struct i2c_device_id pca963x_id[] = { { "pca9633", pca9633 }, { "pca9634", pca9634 }, { "pca9635", pca9635 }, - { } + { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, pca963x_id); @@ -153,7 +153,7 @@ static const struct acpi_device_id pca963x_acpi_ids[] = { { "PCA9633", pca9633 }, { "PCA9634", pca9634 }, { "PCA9635", pca9635 }, - { } + { /* sentinel */ } }; MODULE_DEVICE_TABLE(acpi, pca963x_acpi_ids); @@ -161,7 +161,7 @@ struct pca963x_led; struct pca963x { struct pca963x_chipdef *chipdef; - struct mutex mutex; + struct mutex mutex; /* lock around parallel i2c access */ struct i2c_client *client; struct pca963x_led *leds; unsigned long leds_on; @@ -177,7 +177,7 @@ struct pca963x_led { }; static int pca963x_brightness(struct pca963x_led *pca963x, - enum led_brightness brightness) + enum led_brightness brightness) { u8 ledout_addr; u8 ledout; @@ -198,8 +198,8 @@ static int pca963x_brightness(struct pca963x_led *pca963x, break; default: ret = i2c_smbus_write_byte_data(pca963x->chip->client, - PCA963X_PWM_ADDR(pca963x->led_num), - brightness); + PCA963X_PWM_ADDR(pca963x->led_num), + brightness); if (ret < 0) return ret; ledout |= PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_PWM, @@ -215,18 +215,20 @@ static void pca963x_blink(struct pca963x_led *pca963x) { u8 ledout_addr; u8 ledout; - u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, - PCA963X_MODE2); + u8 mode2; i2c_smbus_write_byte_data(pca963x->chip->client, - pca963x->chip->chipdef->grppwm, pca963x->gdc); + pca963x->chip->chipdef->grppwm, + pca963x->gdc); i2c_smbus_write_byte_data(pca963x->chip->client, - pca963x->chip->chipdef->grpfreq, pca963x->gfrq); + pca963x->chip->chipdef->grpfreq, + pca963x->gfrq); + mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, PCA963X_MODE2); if (!(mode2 & PCA963X_MODE2_DMBLNK)) i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2, - mode2 | PCA963X_MODE2_DMBLNK); + mode2 | PCA963X_MODE2_DMBLNK); ledout_addr = PCA963X_LEDOUT_ADDR(pca963x->chip->chipdef->ledout_base, pca963x->led_num); @@ -261,14 +263,14 @@ static int pca963x_power_state(struct pca963x_led *pca963x) else mode1 |= PCA963X_MODE1_SLEEP; return i2c_smbus_write_byte_data(pca963x->chip->client, - PCA963X_MODE1, mode1); + PCA963X_MODE1, mode1); } return 0; } static int pca963x_led_set(struct led_classdev *led_cdev, - enum led_brightness value) + enum led_brightness value) { struct pca963x_led *pca963x; int ret; @@ -288,7 +290,7 @@ static int pca963x_led_set(struct led_classdev *led_cdev, } static unsigned int pca963x_period_scale(struct pca963x_led *pca963x, - unsigned int val) + unsigned int val) { unsigned int scaling = pca963x->chip->chipdef->scaling; @@ -296,7 +298,8 @@ static unsigned int pca963x_period_scale(struct pca963x_led *pca963x, } static int pca963x_blink_set(struct led_classdev *led_cdev, - unsigned long *delay_on, unsigned long *delay_off) + unsigned long *delay_on, + unsigned long *delay_off) { struct pca963x_led *pca963x; unsigned long time_on, time_off, period; @@ -315,7 +318,7 @@ static int pca963x_blink_set(struct led_classdev *led_cdev, period = pca963x_period_scale(pca963x, time_on + time_off); - /* If period not supported by hardware, default to someting sane. */ + /* If period not supported by hardware, default to something sane. */ if ((period < PCA963X_BLINK_PERIOD_MIN) || (period > PCA963X_BLINK_PERIOD_MAX)) { time_on = 500; @@ -374,10 +377,8 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) res = of_property_read_u32(child, "reg", ®); if ((res != 0) || (reg >= chip->n_leds)) continue; - led.name = - of_get_property(child, "label", NULL) ? : child->name; - led.default_trigger = - of_get_property(child, "linux,default-trigger", NULL); + led.name = of_get_property(child, "label", NULL) ? : child->name; + led.default_trigger = of_get_property(child, "linux,default-trigger", NULL); pca963x_leds[reg] = led; } pdata = devm_kzalloc(&client->dev, @@ -429,7 +430,7 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) #endif static int pca963x_probe(struct i2c_client *client, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { struct pca963x *pca963x_chip; struct pca963x_led *pca963x; @@ -458,18 +459,15 @@ static int pca963x_probe(struct i2c_client *client, } if (pdata && (pdata->leds.num_leds < 1 || - pdata->leds.num_leds > chip->n_leds)) { - dev_err(&client->dev, "board info must claim 1-%d LEDs", - chip->n_leds); + pdata->leds.num_leds > chip->n_leds)) { + dev_err(&client->dev, "board info must claim 1-%d LEDs", chip->n_leds); return -EINVAL; } - pca963x_chip = devm_kzalloc(&client->dev, sizeof(*pca963x_chip), - GFP_KERNEL); + pca963x_chip = devm_kzalloc(&client->dev, sizeof(*pca963x_chip), GFP_KERNEL); if (!pca963x_chip) return -ENOMEM; - pca963x = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca963x), - GFP_KERNEL); + pca963x = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca963x), GFP_KERNEL); if (!pca963x) return -ENOMEM; @@ -490,7 +488,7 @@ static int pca963x_probe(struct i2c_client *client, /* Turn off LEDs by default*/ for (i = 0; i < chip->n_leds / 4; i++) i2c_smbus_write_byte_data(client, chip->ledout_base + i, - PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_OFF, i)); + PCA963X_LEDOUT_LDR(PCA963X_LEDOUT_LED_OFF, i)); for (i = 0; i < chip->n_leds; i++) { pca963x[i].led_num = i; @@ -507,7 +505,7 @@ static int pca963x_probe(struct i2c_client *client, pdata->leds.leds[i].default_trigger; } if (!pdata || i >= pdata->leds.num_leds || - !pdata->leds.leds[i].name) + !pdata->leds.leds[i].name) snprintf(pca963x[i].name, sizeof(pca963x[i].name), "pca963x:%d:%.2x:%d", client->adapter->nr, client->addr, i); From 3a31f9a3ca5fdb3f061a092b6d656d522b75a8de Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 17 Mar 2017 23:00:27 +0100 Subject: [PATCH 30/93] leds: pca963x: set DMBLNK as default during probe The PCA963x series of chips have several operating modes controlled via the LEDOUT register. LDR controls whether the leds are completely off (0x0), completely on (0x1), only controlled via the PWM registers (0x2) or a special 'grouped' mode (0x3). In the grouped mode, the leds can either blink, or PWM in synchronization. Since the LED framework currently does not support linked LEDs and thus we cannot PWM in synchronization, set the default group to blink, to ensure that hardware based blinking at least is synchronized when blinking. Signed-off-by: Olliver Schinagl --- drivers/leds/leds-pca963x.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 93f9a6f741b6f1..49cb9ba633a80f 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -215,7 +215,6 @@ static void pca963x_blink(struct pca963x_led *pca963x) { u8 ledout_addr; u8 ledout; - u8 mode2; i2c_smbus_write_byte_data(pca963x->chip->client, pca963x->chip->chipdef->grppwm, @@ -225,11 +224,6 @@ static void pca963x_blink(struct pca963x_led *pca963x) pca963x->chip->chipdef->grpfreq, pca963x->gfrq); - mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, PCA963X_MODE2); - if (!(mode2 & PCA963X_MODE2_DMBLNK)) - i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2, - mode2 | PCA963X_MODE2_DMBLNK); - ledout_addr = PCA963X_LEDOUT_ADDR(pca963x->chip->chipdef->ledout_base, pca963x->led_num); mutex_lock(&pca963x->chip->mutex); @@ -528,8 +522,8 @@ static int pca963x_probe(struct i2c_client *client, u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, PCA963X_MODE2); - /* Always enable LED output */ - mode2 |= PCA963X_MODE2_OUTNE_OUTDRV; + /* Always enable LED output and group blink mode */ + mode2 |= PCA963X_MODE2_OUTNE_OUTDRV | PCA963X_MODE2_DMBLNK; /* Configure output: open-drain or totem pole (push-pull) */ if (pdata->outdrv == PCA963X_TOTEM_POLE) mode2 |= PCA963X_MODE2_OUTDRV_TOTEM_POLE; From 5fc70f8e53fb1adcfd72565230e06d522dae2c86 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 8 Mar 2017 10:34:49 +0100 Subject: [PATCH 31/93] pwm: core: do not block apply->state on period The old pwm_config call, did some parameter sanitation such as preventing to change the pwm config if the period is 0. With the new atomic framework, we now not only apply configuration parameters, but also the polarity setting. The polarity setting however can be applied both with an enabled or disabled PWM, in other words, setting the polarity is perfectly acceptable when the period is 0. This patch removes the check if the period is valid and instead, sets the state disabled if the period is 0. It now thus becomes possible to set the polarity, before changing any other PWM parameters. The reason this is needed is that if someone calls pwm_set_polarity with the period set to 0, pwm_set_polarity would fail. Having period of 0 is however perfectly acceptable here. Signed-off-by: Olliver Schinagl --- drivers/pwm/core.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 1581f6ab1b1f42..ecca745dce8e24 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -468,13 +468,15 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state) { int err; - if (!pwm || !state || !state->period || - state->duty_cycle > state->period) + if (!pwm || !state || state->duty_cycle > state->period) return -EINVAL; if (!memcmp(state, &pwm->state, sizeof(*state))) return 0; + if (state->period == 0) + state->enabled = false; + if (pwm->chip->ops->apply) { err = pwm->chip->ops->apply(pwm->chip, pwm, state); if (err) @@ -507,8 +509,9 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state) pwm->state.polarity = state->polarity; } - if (state->period != pwm->state.period || - state->duty_cycle != pwm->state.duty_cycle) { + if (state->period && + (state->period != pwm->state.period || + state->duty_cycle != pwm->state.duty_cycle)) { err = pwm->chip->ops->config(pwm->chip, pwm, state->duty_cycle, state->period); From a3d98013222a8c485fe6e02c28586c58f22c00d4 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 6 Feb 2018 11:17:34 +0100 Subject: [PATCH 32/93] rtc: sun6i: add missing header Signed-off-by: Olliver Schinagl --- drivers/rtc/rtc-sun6i.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rtc/rtc-sun6i.c b/drivers/rtc/rtc-sun6i.c index 3d2216ccd860c6..d941c66138df0d 100644 --- a/drivers/rtc/rtc-sun6i.c +++ b/drivers/rtc/rtc-sun6i.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include From 3c739f43a34c27bf687632d67e4787a71d415aa5 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 3 Jan 2017 09:36:06 +0100 Subject: [PATCH 33/93] regulator: axp20x: use defines for masks The AXP20X driver currently has several masks defined throughout the code. Use nice defines to make them clean and more descriptive. Additionally include bitops.h, which was missing before, and sort headers. Signed-off-by: Olliver Schinagl --- drivers/regulator/axp20x-regulator.c | 635 +++++++++++++++++++++------ 1 file changed, 493 insertions(+), 142 deletions(-) diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c index 376a99b7cf5da1..3f14ddd376840f 100644 --- a/drivers/regulator/axp20x-regulator.c +++ b/drivers/regulator/axp20x-regulator.c @@ -13,31 +13,245 @@ * GNU General Public License for more details. */ +#include #include #include +#include #include #include #include #include #include -#include #include #include +#define AXP20X_GPIO0_FUNC_MASK GENMASK(3, 0) +#define AXP20X_GPIO1_FUNC_MASK GENMASK(3, 0) + #define AXP20X_IO_ENABLED 0x03 #define AXP20X_IO_DISABLED 0x07 +#define AXP20X_WORKMODE_DCDC2_MASK BIT_MASK(2) +#define AXP20X_WORKMODE_DCDC3_MASK BIT_MASK(1) + +#define AXP20X_FREQ_DCDC_MASK GENMASK(3, 0) + +#define AXP20X_VBUS_IPSOUT_MGMT_MASK BIT_MASK(2) + +#define AXP20X_DCDC2_V_OUT_MASK GENMASK(5, 0) +#define AXP20X_DCDC3_V_OUT_MASK GENMASK(7, 0) +#define AXP20X_LDO24_V_OUT_MASK GENMASK(7, 4) +#define AXP20X_LDO3_V_OUT_MASK GENMASK(6, 0) +#define AXP20X_LDO5_V_OUT_MASK GENMASK(7, 4) + +#define AXP20X_PWR_OUT_EXTEN_MASK BIT_MASK(0) +#define AXP20X_PWR_OUT_DCDC3_MASK BIT_MASK(1) +#define AXP20X_PWR_OUT_LDO2_MASK BIT_MASK(2) +#define AXP20X_PWR_OUT_LDO4_MASK BIT_MASK(3) +#define AXP20X_PWR_OUT_DCDC2_MASK BIT_MASK(4) +#define AXP20X_PWR_OUT_LDO3_MASK BIT_MASK(6) + +#define AXP20X_LDO4_V_OUT_1250mV_START 0x0 +#define AXP20X_LDO4_V_OUT_1250mV_STEPS 0 +#define AXP20X_LDO4_V_OUT_1250mV_END \ + (AXP20X_LDO4_V_OUT_1250mV_START + AXP20X_LDO4_V_OUT_1250mV_STEPS) +#define AXP20X_LDO4_V_OUT_1300mV_START 0x1 +#define AXP20X_LDO4_V_OUT_1300mV_STEPS 7 +#define AXP20X_LDO4_V_OUT_1300mV_END \ + (AXP20X_LDO4_V_OUT_1300mV_START + AXP20X_LDO4_V_OUT_1300mV_STEPS) +#define AXP20X_LDO4_V_OUT_2500mV_START 0x9 +#define AXP20X_LDO4_V_OUT_2500mV_STEPS 0 +#define AXP20X_LDO4_V_OUT_2500mV_END \ + (AXP20X_LDO4_V_OUT_2500mV_START + AXP20X_LDO4_V_OUT_2500mV_STEPS) +#define AXP20X_LDO4_V_OUT_2700mV_START 0xa +#define AXP20X_LDO4_V_OUT_2700mV_STEPS 1 +#define AXP20X_LDO4_V_OUT_2700mV_END \ + (AXP20X_LDO4_V_OUT_2700mV_START + AXP20X_LDO4_V_OUT_2700mV_STEPS) +#define AXP20X_LDO4_V_OUT_3000mV_START 0xc +#define AXP20X_LDO4_V_OUT_3000mV_STEPS 3 +#define AXP20X_LDO4_V_OUT_3000mV_END \ + (AXP20X_LDO4_V_OUT_3000mV_START + AXP20X_LDO4_V_OUT_3000mV_STEPS) +#define AXP20X_LDO4_V_OUT_NUM_VOLTAGES 16 + #define AXP22X_IO_ENABLED 0x03 #define AXP22X_IO_DISABLED 0x04 -#define AXP20X_WORKMODE_DCDC2_MASK BIT(2) -#define AXP20X_WORKMODE_DCDC3_MASK BIT(1) -#define AXP22X_WORKMODE_DCDCX_MASK(x) BIT(x) - -#define AXP20X_FREQ_DCDC_MASK 0x0f +#define AXP22X_WORKMODE_DCDCX_MASK(x) BIT_MASK(x) #define AXP22X_MISC_N_VBUSEN_FUNC BIT(4) +#define AXP22X_DCDC1_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_DCDC2_V_OUT_MASK GENMASK(5, 0) +#define AXP22X_DCDC3_V_OUT_MASK GENMASK(5, 0) +#define AXP22X_DCDC4_V_OUT_MASK GENMASK(5, 0) +#define AXP22X_DCDC5_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_DC5LDO_V_OUT_MASK GENMASK(2, 0) +#define AXP22X_ALDO1_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_ALDO2_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_ALDO3_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_DLDO1_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_DLDO2_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_DLDO3_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_DLDO4_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_ELDO1_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_ELDO2_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_ELDO3_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_LDO_IO0_V_OUT_MASK GENMASK(4, 0) +#define AXP22X_LDO_IO1_V_OUT_MASK GENMASK(4, 0) + +#define AXP22X_PWR_OUT_DC5LDO_MASK BIT_MASK(0) +#define AXP22X_PWR_OUT_DCDC1_MASK BIT_MASK(1) +#define AXP22X_PWR_OUT_DCDC2_MASK BIT_MASK(2) +#define AXP22X_PWR_OUT_DCDC3_MASK BIT_MASK(3) +#define AXP22X_PWR_OUT_DCDC4_MASK BIT_MASK(4) +#define AXP22X_PWR_OUT_DCDC5_MASK BIT_MASK(5) +#define AXP22X_PWR_OUT_ALDO1_MASK BIT_MASK(6) +#define AXP22X_PWR_OUT_ALDO2_MASK BIT_MASK(7) + +#define AXP22X_PWR_OUT_SW_MASK BIT_MASK(6) +#define AXP22X_PWR_OUT_DC1SW_MASK BIT_MASK(7) + +#define AXP22X_PWR_OUT_ELDO1_MASK BIT_MASK(0) +#define AXP22X_PWR_OUT_ELDO2_MASK BIT_MASK(1) +#define AXP22X_PWR_OUT_ELDO3_MASK BIT_MASK(2) +#define AXP22X_PWR_OUT_DLDO1_MASK BIT_MASK(3) +#define AXP22X_PWR_OUT_DLDO2_MASK BIT_MASK(4) +#define AXP22X_PWR_OUT_DLDO3_MASK BIT_MASK(5) +#define AXP22X_PWR_OUT_DLDO4_MASK BIT_MASK(6) +#define AXP22X_PWR_OUT_ALDO3_MASK BIT_MASK(7) + +#define AXP803_PWR_OUT_DCDC1_MASK BIT_MASK(0) +#define AXP803_PWR_OUT_DCDC2_MASK BIT_MASK(1) +#define AXP803_PWR_OUT_DCDC3_MASK BIT_MASK(2) +#define AXP803_PWR_OUT_DCDC4_MASK BIT_MASK(3) +#define AXP803_PWR_OUT_DCDC5_MASK BIT_MASK(4) +#define AXP803_PWR_OUT_DCDC6_MASK BIT_MASK(5) + +#define AXP803_PWR_OUT_FLDO1_MASK BIT_MASK(2) +#define AXP803_PWR_OUT_FLDO2_MASK BIT_MASK(3) + +#define AXP803_DCDC1_V_OUT_MASK GENMASK(4, 0) +#define AXP803_DCDC2_V_OUT_MASK GENMASK(6, 0) +#define AXP803_DCDC3_V_OUT_MASK GENMASK(6, 0) +#define AXP803_DCDC4_V_OUT_MASK GENMASK(6, 0) +#define AXP803_DCDC5_V_OUT_MASK GENMASK(6, 0) +#define AXP803_DCDC6_V_OUT_MASK GENMASK(6, 0) + +#define AXP803_FLDO1_V_OUT_MASK GENMASK(3, 0) +#define AXP803_FLDO2_V_OUT_MASK GENMASK(3, 0) + +#define AXP803_DCDC23_POLYPHASE_DUAL BIT(6) +#define AXP803_DCDC56_POLYPHASE_DUAL BIT(5) + +#define AXP803_DCDC234_500mV_START 0x00 +#define AXP803_DCDC234_500mV_STEPS 70 +#define AXP803_DCDC234_500mV_END \ + (AXP803_DCDC234_500mV_START + AXP803_DCDC234_500mV_STEPS) +#define AXP803_DCDC234_1220mV_START 0x47 +#define AXP803_DCDC234_1220mV_STEPS 4 +#define AXP803_DCDC234_1220mV_END \ + (AXP803_DCDC234_1220mV_START + AXP803_DCDC234_1220mV_STEPS) +#define AXP803_DCDC234_NUM_VOLTAGES 76 + +#define AXP803_DCDC5_800mV_START 0x00 +#define AXP803_DCDC5_800mV_STEPS 32 +#define AXP803_DCDC5_800mV_END \ + (AXP803_DCDC5_800mV_START + AXP803_DCDC5_800mV_STEPS) +#define AXP803_DCDC5_1140mV_START 0x21 +#define AXP803_DCDC5_1140mV_STEPS 35 +#define AXP803_DCDC5_1140mV_END \ + (AXP803_DCDC5_1140mV_START + AXP803_DCDC5_1140mV_STEPS) +#define AXP803_DCDC5_NUM_VOLTAGES 68 + +#define AXP803_DCDC6_600mV_START 0x00 +#define AXP803_DCDC6_600mV_STEPS 50 +#define AXP803_DCDC6_600mV_END \ + (AXP803_DCDC6_600mV_START + AXP803_DCDC6_600mV_STEPS) +#define AXP803_DCDC6_1120mV_START 0x33 +#define AXP803_DCDC6_1120mV_STEPS 14 +#define AXP803_DCDC6_1120mV_END \ + (AXP803_DCDC6_1120mV_START + AXP803_DCDC6_1120mV_STEPS) +#define AXP803_DCDC6_NUM_VOLTAGES 72 + +#define AXP803_DLDO2_700mV_START 0x00 +#define AXP803_DLDO2_700mV_STEPS 26 +#define AXP803_DLDO2_700mV_END \ + (AXP803_DLDO2_700mV_START + AXP803_DLDO2_700mV_STEPS) +#define AXP803_DLDO2_3400mV_START 0x1b +#define AXP803_DLDO2_3400mV_STEPS 4 +#define AXP803_DLDO2_3400mV_END \ + (AXP803_DLDO2_3400mV_START + AXP803_DLDO2_3400mV_STEPS) +#define AXP803_DLDO2_NUM_VOLTAGES 32 + +#define AXP806_DCDCA_V_CTRL_MASK GENMASK(6, 0) +#define AXP806_DCDCB_V_CTRL_MASK GENMASK(4, 0) +#define AXP806_DCDCC_V_CTRL_MASK GENMASK(6, 0) +#define AXP806_DCDCD_V_CTRL_MASK GENMASK(5, 0) +#define AXP806_DCDCE_V_CTRL_MASK GENMASK(4, 0) +#define AXP806_ALDO1_V_CTRL_MASK GENMASK(4, 0) +#define AXP806_ALDO2_V_CTRL_MASK GENMASK(4, 0) +#define AXP806_ALDO3_V_CTRL_MASK GENMASK(4, 0) +#define AXP806_BLDO1_V_CTRL_MASK GENMASK(3, 0) +#define AXP806_BLDO2_V_CTRL_MASK GENMASK(3, 0) +#define AXP806_BLDO3_V_CTRL_MASK GENMASK(3, 0) +#define AXP806_BLDO4_V_CTRL_MASK GENMASK(3, 0) +#define AXP806_CLDO1_V_CTRL_MASK GENMASK(4, 0) +#define AXP806_CLDO2_V_CTRL_MASK GENMASK(4, 0) +#define AXP806_CLDO3_V_CTRL_MASK GENMASK(4, 0) + +#define AXP806_PWR_OUT_DCDCA_MASK BIT_MASK(0) +#define AXP806_PWR_OUT_DCDCB_MASK BIT_MASK(1) +#define AXP806_PWR_OUT_DCDCC_MASK BIT_MASK(2) +#define AXP806_PWR_OUT_DCDCD_MASK BIT_MASK(3) +#define AXP806_PWR_OUT_DCDCE_MASK BIT_MASK(4) +#define AXP806_PWR_OUT_ALDO1_MASK BIT_MASK(5) +#define AXP806_PWR_OUT_ALDO2_MASK BIT_MASK(6) +#define AXP806_PWR_OUT_ALDO3_MASK BIT_MASK(7) +#define AXP806_PWR_OUT_BLDO1_MASK BIT_MASK(0) +#define AXP806_PWR_OUT_BLDO2_MASK BIT_MASK(1) +#define AXP806_PWR_OUT_BLDO3_MASK BIT_MASK(2) +#define AXP806_PWR_OUT_BLDO4_MASK BIT_MASK(3) +#define AXP806_PWR_OUT_CLDO1_MASK BIT_MASK(4) +#define AXP806_PWR_OUT_CLDO2_MASK BIT_MASK(5) +#define AXP806_PWR_OUT_CLDO3_MASK BIT_MASK(6) +#define AXP806_PWR_OUT_SW_MASK BIT_MASK(7) + +#define AXP806_DCDCAB_POLYPHASE_DUAL 0x40 +#define AXP806_DCDCABC_POLYPHASE_TRI 0x80 +#define AXP806_DCDCABC_POLYPHASE_MASK GENMASK(7, 6) + +#define AXP806_DCDCDE_POLYPHASE_DUAL BIT(5) + +#define AXP806_DCDCA_600mV_START 0x00 +#define AXP806_DCDCA_600mV_STEPS 50 +#define AXP806_DCDCA_600mV_END \ + (AXP806_DCDCA_600mV_START + AXP806_DCDCA_600mV_STEPS) +#define AXP806_DCDCA_1120mV_START 0x33 +#define AXP806_DCDCA_1120mV_STEPS 14 +#define AXP806_DCDCA_1120mV_END \ + (AXP806_DCDCA_1120mV_START + AXP806_DCDCA_1120mV_STEPS) +#define AXP806_DCDCA_NUM_VOLTAGES 72 + +#define AXP806_DCDCD_600mV_START 0x00 +#define AXP806_DCDCD_600mV_STEPS 45 +#define AXP806_DCDCD_600mV_END \ + (AXP806_DCDCD_600mV_START + AXP806_DCDCD_600mV_STEPS) +#define AXP806_DCDCD_1600mV_START 0x2e +#define AXP806_DCDCD_1600mV_STEPS 17 +#define AXP806_DCDCD_1600mV_END \ + (AXP806_DCDCD_1600mV_START + AXP806_DCDCD_1600mV_STEPS) +#define AXP806_DCDCD_NUM_VOLTAGES 64 + +#define AXP809_DCDC4_600mV_START 0x00 +#define AXP809_DCDC4_600mV_STEPS 47 +#define AXP809_DCDC4_600mV_END \ + (AXP809_DCDC4_600mV_START + AXP809_DCDC4_600mV_STEPS) +#define AXP809_DCDC4_1800mV_START 0x30 +#define AXP809_DCDC4_1800mV_STEPS 8 +#define AXP809_DCDC4_1800mV_END \ + (AXP809_DCDC4_1800mV_START + AXP809_DCDC4_1800mV_STEPS) +#define AXP809_DCDC4_NUM_VOLTAGES 57 + #define AXP_DESC_IO(_family, _id, _match, _supply, _min, _max, _step, _vreg, \ _vmask, _ereg, _emask, _enable_val, _disable_val) \ [_family##_##_id] = { \ @@ -157,77 +371,116 @@ static const struct regulator_ops axp20x_ops_sw = { }; static const struct regulator_linear_range axp20x_ldo4_ranges[] = { - REGULATOR_LINEAR_RANGE(1250000, 0x0, 0x0, 0), - REGULATOR_LINEAR_RANGE(1300000, 0x1, 0x8, 100000), - REGULATOR_LINEAR_RANGE(2500000, 0x9, 0x9, 0), - REGULATOR_LINEAR_RANGE(2700000, 0xa, 0xb, 100000), - REGULATOR_LINEAR_RANGE(3000000, 0xc, 0xf, 100000), + REGULATOR_LINEAR_RANGE(1250000, + AXP20X_LDO4_V_OUT_1250mV_START, + AXP20X_LDO4_V_OUT_1250mV_END, + 0), + REGULATOR_LINEAR_RANGE(1300000, + AXP20X_LDO4_V_OUT_1300mV_START, + AXP20X_LDO4_V_OUT_1300mV_END, + 100000), + REGULATOR_LINEAR_RANGE(2500000, + AXP20X_LDO4_V_OUT_2500mV_START, + AXP20X_LDO4_V_OUT_2500mV_END, + 0), + REGULATOR_LINEAR_RANGE(2700000, + AXP20X_LDO4_V_OUT_2700mV_START, + AXP20X_LDO4_V_OUT_2700mV_END, + 100000), + REGULATOR_LINEAR_RANGE(3000000, + AXP20X_LDO4_V_OUT_3000mV_START, + AXP20X_LDO4_V_OUT_3000mV_END, + 100000), }; static const struct regulator_desc axp20x_regulators[] = { AXP_DESC(AXP20X, DCDC2, "dcdc2", "vin2", 700, 2275, 25, - AXP20X_DCDC2_V_OUT, 0x3f, AXP20X_PWR_OUT_CTRL, 0x10), + AXP20X_DCDC2_V_OUT, AXP20X_DCDC2_V_OUT_MASK, + AXP20X_PWR_OUT_CTRL, AXP20X_PWR_OUT_DCDC2_MASK), AXP_DESC(AXP20X, DCDC3, "dcdc3", "vin3", 700, 3500, 25, - AXP20X_DCDC3_V_OUT, 0x7f, AXP20X_PWR_OUT_CTRL, 0x02), + AXP20X_DCDC3_V_OUT, AXP20X_DCDC3_V_OUT_MASK, + AXP20X_PWR_OUT_CTRL, AXP20X_PWR_OUT_DCDC3_MASK), AXP_DESC_FIXED(AXP20X, LDO1, "ldo1", "acin", 1300), AXP_DESC(AXP20X, LDO2, "ldo2", "ldo24in", 1800, 3300, 100, - AXP20X_LDO24_V_OUT, 0xf0, AXP20X_PWR_OUT_CTRL, 0x04), + AXP20X_LDO24_V_OUT, AXP20X_LDO24_V_OUT_MASK, + AXP20X_PWR_OUT_CTRL, AXP20X_PWR_OUT_LDO2_MASK), AXP_DESC(AXP20X, LDO3, "ldo3", "ldo3in", 700, 3500, 25, - AXP20X_LDO3_V_OUT, 0x7f, AXP20X_PWR_OUT_CTRL, 0x40), - AXP_DESC_RANGES(AXP20X, LDO4, "ldo4", "ldo24in", axp20x_ldo4_ranges, - 16, AXP20X_LDO24_V_OUT, 0x0f, AXP20X_PWR_OUT_CTRL, - 0x08), + AXP20X_LDO3_V_OUT, AXP20X_LDO3_V_OUT_MASK, + AXP20X_PWR_OUT_CTRL, AXP20X_PWR_OUT_LDO3_MASK), + AXP_DESC_RANGES(AXP20X, LDO4, "ldo4", "ldo24in", + axp20x_ldo4_ranges, AXP20X_LDO4_V_OUT_NUM_VOLTAGES, + AXP20X_LDO24_V_OUT, AXP20X_LDO24_V_OUT_MASK, + AXP20X_PWR_OUT_CTRL, AXP20X_PWR_OUT_LDO4_MASK), AXP_DESC_IO(AXP20X, LDO5, "ldo5", "ldo5in", 1800, 3300, 100, - AXP20X_LDO5_V_OUT, 0xf0, AXP20X_GPIO0_CTRL, 0x07, + AXP20X_LDO5_V_OUT, AXP20X_LDO5_V_OUT_MASK, + AXP20X_GPIO0_CTRL, AXP20X_GPIO0_FUNC_MASK, AXP20X_IO_ENABLED, AXP20X_IO_DISABLED), }; static const struct regulator_desc axp22x_regulators[] = { AXP_DESC(AXP22X, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, - AXP22X_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(1)), + AXP22X_DCDC1_V_OUT, AXP22X_DCDC1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC1_MASK), AXP_DESC(AXP22X, DCDC2, "dcdc2", "vin2", 600, 1540, 20, - AXP22X_DCDC2_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(2)), + AXP22X_DCDC2_V_OUT, AXP22X_DCDC2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC2_MASK), AXP_DESC(AXP22X, DCDC3, "dcdc3", "vin3", 600, 1860, 20, - AXP22X_DCDC3_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(3)), + AXP22X_DCDC3_V_OUT, AXP22X_DCDC3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC3_MASK), AXP_DESC(AXP22X, DCDC4, "dcdc4", "vin4", 600, 1540, 20, - AXP22X_DCDC4_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(4)), + AXP22X_DCDC4_V_OUT, AXP22X_DCDC4_V_OUT, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC4_MASK), AXP_DESC(AXP22X, DCDC5, "dcdc5", "vin5", 1000, 2550, 50, - AXP22X_DCDC5_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(5)), + AXP22X_DCDC5_V_OUT, AXP22X_DCDC5_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC5_MASK), /* secondary switchable output of DCDC1 */ - AXP_DESC_SW(AXP22X, DC1SW, "dc1sw", NULL, AXP22X_PWR_OUT_CTRL2, - BIT(7)), + AXP_DESC_SW(AXP22X, DC1SW, "dc1sw", NULL, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DC1SW_MASK), /* LDO regulator internally chained to DCDC5 */ AXP_DESC(AXP22X, DC5LDO, "dc5ldo", NULL, 700, 1400, 100, - AXP22X_DC5LDO_V_OUT, 0x7, AXP22X_PWR_OUT_CTRL1, BIT(0)), + AXP22X_DC5LDO_V_OUT, AXP22X_DC5LDO_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DC5LDO_MASK), AXP_DESC(AXP22X, ALDO1, "aldo1", "aldoin", 700, 3300, 100, - AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(6)), + AXP22X_ALDO1_V_OUT, AXP22X_ALDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_ALDO1_MASK), AXP_DESC(AXP22X, ALDO2, "aldo2", "aldoin", 700, 3300, 100, - AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(7)), + AXP22X_ALDO2_V_OUT, AXP22X_ALDO2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_ALDO2_MASK), AXP_DESC(AXP22X, ALDO3, "aldo3", "aldoin", 700, 3300, 100, - AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(7)), + AXP22X_ALDO3_V_OUT, AXP22X_ALDO3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL3, AXP22X_PWR_OUT_ALDO3_MASK), AXP_DESC(AXP22X, DLDO1, "dldo1", "dldoin", 700, 3300, 100, - AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(3)), + AXP22X_DLDO1_V_OUT, AXP22X_DLDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO1_MASK), AXP_DESC(AXP22X, DLDO2, "dldo2", "dldoin", 700, 3300, 100, - AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(4)), + AXP22X_DLDO2_V_OUT, AXP22X_PWR_OUT_DLDO2_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO2_MASK), AXP_DESC(AXP22X, DLDO3, "dldo3", "dldoin", 700, 3300, 100, - AXP22X_DLDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), + AXP22X_DLDO3_V_OUT, AXP22X_DLDO3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO3_MASK), AXP_DESC(AXP22X, DLDO4, "dldo4", "dldoin", 700, 3300, 100, - AXP22X_DLDO4_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(6)), + AXP22X_DLDO4_V_OUT, AXP22X_DLDO4_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO4_MASK), AXP_DESC(AXP22X, ELDO1, "eldo1", "eldoin", 700, 3300, 100, - AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP22X_ELDO1_V_OUT, AXP22X_ELDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO1_MASK), AXP_DESC(AXP22X, ELDO2, "eldo2", "eldoin", 700, 3300, 100, - AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP22X_ELDO2_V_OUT, AXP22X_ELDO2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO1_MASK), AXP_DESC(AXP22X, ELDO3, "eldo3", "eldoin", 700, 3300, 100, - AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + AXP22X_ELDO3_V_OUT, AXP22X_ELDO3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO3_MASK), /* Note the datasheet only guarantees reliable operation up to * 3.3V, this needs to be enforced via dts provided constraints */ AXP_DESC_IO(AXP22X, LDO_IO0, "ldo_io0", "ips", 700, 3800, 100, - AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_LDO_IO0_V_OUT, AXP22X_LDO_IO0_V_OUT_MASK, + AXP20X_GPIO0_CTRL, AXP20X_GPIO0_FUNC_MASK, AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), /* Note the datasheet only guarantees reliable operation up to * 3.3V, this needs to be enforced via dts provided constraints */ AXP_DESC_IO(AXP22X, LDO_IO1, "ldo_io1", "ips", 700, 3800, 100, - AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_LDO_IO1_V_OUT, AXP22X_LDO_IO1_V_OUT_MASK, + AXP20X_GPIO1_CTRL, AXP20X_GPIO1_FUNC_MASK, AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), AXP_DESC_FIXED(AXP22X, RTC_LDO, "rtc_ldo", "ips", 3000), }; @@ -240,190 +493,285 @@ static const struct regulator_desc axp22x_drivevbus_regulator = { .type = REGULATOR_VOLTAGE, .owner = THIS_MODULE, .enable_reg = AXP20X_VBUS_IPSOUT_MGMT, - .enable_mask = BIT(2), + .enable_mask = AXP20X_VBUS_IPSOUT_MGMT_MASK, .ops = &axp20x_ops_sw, }; static const struct regulator_linear_range axp803_dcdc234_ranges[] = { - REGULATOR_LINEAR_RANGE(500000, 0x0, 0x46, 10000), - REGULATOR_LINEAR_RANGE(1220000, 0x47, 0x4b, 20000), + REGULATOR_LINEAR_RANGE(500000, + AXP803_DCDC234_500mV_START, + AXP803_DCDC234_500mV_END, + 10000), + REGULATOR_LINEAR_RANGE(1220000, + AXP803_DCDC234_1220mV_START, + AXP803_DCDC234_1220mV_END, + 20000), }; static const struct regulator_linear_range axp803_dcdc5_ranges[] = { - REGULATOR_LINEAR_RANGE(800000, 0x0, 0x20, 10000), - REGULATOR_LINEAR_RANGE(1140000, 0x21, 0x44, 20000), + REGULATOR_LINEAR_RANGE(800000, + AXP803_DCDC5_800mV_START, + AXP803_DCDC5_800mV_END, + 10000), + REGULATOR_LINEAR_RANGE(1140000, + AXP803_DCDC5_1140mV_START, + AXP803_DCDC5_1140mV_END, + 20000), }; static const struct regulator_linear_range axp803_dcdc6_ranges[] = { - REGULATOR_LINEAR_RANGE(600000, 0x0, 0x32, 10000), - REGULATOR_LINEAR_RANGE(1120000, 0x33, 0x47, 20000), + REGULATOR_LINEAR_RANGE(600000, + AXP803_DCDC6_600mV_START, + AXP803_DCDC6_600mV_END, + 10000), + REGULATOR_LINEAR_RANGE(1120000, + AXP803_DCDC6_1120mV_START, + AXP803_DCDC6_1120mV_END, + 20000), }; -/* AXP806's CLDO2 and AXP809's DLDO1 shares the same range */ +/* AXP806's CLDO2 and AXP809's DLDO1 share the same range */ static const struct regulator_linear_range axp803_dldo2_ranges[] = { - REGULATOR_LINEAR_RANGE(700000, 0x0, 0x1a, 100000), - REGULATOR_LINEAR_RANGE(3400000, 0x1b, 0x1f, 200000), + REGULATOR_LINEAR_RANGE(700000, + AXP803_DLDO2_700mV_START, + AXP803_DLDO2_700mV_END, + 100000), + REGULATOR_LINEAR_RANGE(3400000, + AXP803_DLDO2_3400mV_START, + AXP803_DLDO2_3400mV_END, + 200000), }; static const struct regulator_desc axp803_regulators[] = { AXP_DESC(AXP803, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, - AXP803_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(0)), - AXP_DESC_RANGES(AXP803, DCDC2, "dcdc2", "vin2", axp803_dcdc234_ranges, - 76, AXP803_DCDC2_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, - BIT(1)), - AXP_DESC_RANGES(AXP803, DCDC3, "dcdc3", "vin3", axp803_dcdc234_ranges, - 76, AXP803_DCDC3_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, - BIT(2)), - AXP_DESC_RANGES(AXP803, DCDC4, "dcdc4", "vin4", axp803_dcdc234_ranges, - 76, AXP803_DCDC4_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, - BIT(3)), - AXP_DESC_RANGES(AXP803, DCDC5, "dcdc5", "vin5", axp803_dcdc5_ranges, - 68, AXP803_DCDC5_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, - BIT(4)), - AXP_DESC_RANGES(AXP803, DCDC6, "dcdc6", "vin6", axp803_dcdc6_ranges, - 72, AXP803_DCDC6_V_OUT, 0x7f, AXP22X_PWR_OUT_CTRL1, - BIT(5)), + AXP803_DCDC1_V_OUT, AXP803_DCDC1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP803_PWR_OUT_DCDC1_MASK), + AXP_DESC_RANGES(AXP803, DCDC2, "dcdc2", "vin2", + axp803_dcdc234_ranges, AXP803_DCDC234_NUM_VOLTAGES, + AXP803_DCDC2_V_OUT, AXP803_DCDC2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP803_PWR_OUT_DCDC2_MASK), + AXP_DESC_RANGES(AXP803, DCDC3, "dcdc3", "vin3", + axp803_dcdc234_ranges, AXP803_DCDC234_NUM_VOLTAGES, + AXP803_DCDC3_V_OUT, AXP803_DCDC3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP803_PWR_OUT_DCDC3_MASK), + AXP_DESC_RANGES(AXP803, DCDC4, "dcdc4", "vin4", + axp803_dcdc234_ranges, AXP803_DCDC234_NUM_VOLTAGES, + AXP803_DCDC4_V_OUT, AXP803_DCDC4_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP803_PWR_OUT_DCDC4_MASK), + AXP_DESC_RANGES(AXP803, DCDC5, "dcdc5", "vin5", + axp803_dcdc5_ranges, AXP803_DCDC5_NUM_VOLTAGES, + AXP803_DCDC5_V_OUT, AXP803_DCDC5_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP803_PWR_OUT_DCDC5_MASK), + AXP_DESC_RANGES(AXP803, DCDC6, "dcdc6", "vin6", + axp803_dcdc6_ranges, AXP803_DCDC6_NUM_VOLTAGES, + AXP803_DCDC6_V_OUT, AXP803_DCDC6_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP803_PWR_OUT_DCDC6_MASK), /* secondary switchable output of DCDC1 */ - AXP_DESC_SW(AXP803, DC1SW, "dc1sw", NULL, AXP22X_PWR_OUT_CTRL2, - BIT(7)), + AXP_DESC_SW(AXP803, DC1SW, "dc1sw", NULL, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DC1SW_MASK), AXP_DESC(AXP803, ALDO1, "aldo1", "aldoin", 700, 3300, 100, - AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(5)), + AXP22X_ALDO1_V_OUT, AXP22X_ALDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL3, AXP806_PWR_OUT_ALDO1_MASK), AXP_DESC(AXP803, ALDO2, "aldo2", "aldoin", 700, 3300, 100, - AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(6)), + AXP22X_ALDO2_V_OUT, AXP22X_ALDO2_V_OUT, + AXP22X_PWR_OUT_CTRL3, AXP806_PWR_OUT_ALDO2_MASK), AXP_DESC(AXP803, ALDO3, "aldo3", "aldoin", 700, 3300, 100, - AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL3, BIT(7)), + AXP22X_ALDO3_V_OUT, AXP22X_ALDO3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL3, AXP806_PWR_OUT_ALDO3_MASK), AXP_DESC(AXP803, DLDO1, "dldo1", "dldoin", 700, 3300, 100, - AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(3)), - AXP_DESC_RANGES(AXP803, DLDO2, "dldo2", "dldoin", axp803_dldo2_ranges, - 32, AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, - BIT(4)), + AXP22X_DLDO1_V_OUT, AXP22X_DLDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO1_MASK), + AXP_DESC_RANGES(AXP803, DLDO2, "dldo2", "dldoin", + axp803_dldo2_ranges, AXP803_DLDO2_NUM_VOLTAGES, + AXP22X_DLDO2_V_OUT, AXP22X_DLDO2_V_OUT, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO2_MASK), AXP_DESC(AXP803, DLDO3, "dldo3", "dldoin", 700, 3300, 100, - AXP22X_DLDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), + AXP22X_DLDO3_V_OUT, AXP22X_DLDO3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO3_MASK), AXP_DESC(AXP803, DLDO4, "dldo4", "dldoin", 700, 3300, 100, - AXP22X_DLDO4_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(6)), + AXP22X_DLDO4_V_OUT, AXP22X_DLDO4_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO4_MASK), AXP_DESC(AXP803, ELDO1, "eldo1", "eldoin", 700, 1900, 50, - AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP22X_ELDO1_V_OUT, AXP22X_ELDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO1_MASK), AXP_DESC(AXP803, ELDO2, "eldo2", "eldoin", 700, 1900, 50, - AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP22X_ELDO2_V_OUT, AXP22X_ELDO2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO2_MASK), AXP_DESC(AXP803, ELDO3, "eldo3", "eldoin", 700, 1900, 50, - AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + AXP22X_ELDO3_V_OUT, AXP22X_ELDO3_V_OUT, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO3_MASK), AXP_DESC(AXP803, FLDO1, "fldo1", "fldoin", 700, 1450, 50, - AXP803_FLDO1_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(2)), + AXP803_FLDO1_V_OUT, AXP803_FLDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL3, AXP803_PWR_OUT_FLDO1_MASK), AXP_DESC(AXP803, FLDO2, "fldo2", "fldoin", 700, 1450, 50, - AXP803_FLDO2_V_OUT, 0x0f, AXP22X_PWR_OUT_CTRL3, BIT(3)), + AXP803_FLDO2_V_OUT, AXP803_FLDO2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL3, AXP803_PWR_OUT_FLDO2_MASK), AXP_DESC_IO(AXP803, LDO_IO0, "ldo-io0", "ips", 700, 3300, 100, - AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_LDO_IO0_V_OUT, AXP22X_LDO_IO0_V_OUT_MASK, + AXP20X_GPIO0_CTRL, AXP20X_GPIO0_FUNC_MASK, AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), AXP_DESC_IO(AXP803, LDO_IO1, "ldo-io1", "ips", 700, 3300, 100, - AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_LDO_IO1_V_OUT, AXP22X_LDO_IO1_V_OUT_MASK, + AXP20X_GPIO1_CTRL, AXP20X_GPIO1_FUNC_MASK, AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), AXP_DESC_FIXED(AXP803, RTC_LDO, "rtc-ldo", "ips", 3000), }; static const struct regulator_linear_range axp806_dcdca_ranges[] = { - REGULATOR_LINEAR_RANGE(600000, 0x0, 0x32, 10000), - REGULATOR_LINEAR_RANGE(1120000, 0x33, 0x47, 20000), + REGULATOR_LINEAR_RANGE(600000, + AXP806_DCDCA_600mV_START, + AXP806_DCDCA_600mV_END, + 10000), + REGULATOR_LINEAR_RANGE(1120000, + AXP806_DCDCA_1120mV_START, + AXP806_DCDCA_1120mV_END, + 20000), }; static const struct regulator_linear_range axp806_dcdcd_ranges[] = { - REGULATOR_LINEAR_RANGE(600000, 0x0, 0x2d, 20000), - REGULATOR_LINEAR_RANGE(1600000, 0x2e, 0x3f, 100000), + REGULATOR_LINEAR_RANGE(600000, + AXP806_DCDCD_600mV_START, + AXP806_DCDCD_600mV_END, + 20000), + REGULATOR_LINEAR_RANGE(1600000, + AXP806_DCDCD_600mV_START, + AXP806_DCDCD_600mV_END, + 100000), }; static const struct regulator_desc axp806_regulators[] = { - AXP_DESC_RANGES(AXP806, DCDCA, "dcdca", "vina", axp806_dcdca_ranges, - 72, AXP806_DCDCA_V_CTRL, 0x7f, AXP806_PWR_OUT_CTRL1, - BIT(0)), + AXP_DESC_RANGES(AXP806, DCDCA, "dcdca", "vina", + axp806_dcdca_ranges, AXP806_DCDCA_NUM_VOLTAGES, + AXP806_DCDCA_V_CTRL, AXP806_DCDCA_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_DCDCA_MASK), AXP_DESC(AXP806, DCDCB, "dcdcb", "vinb", 1000, 2550, 50, - AXP806_DCDCB_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(1)), - AXP_DESC_RANGES(AXP806, DCDCC, "dcdcc", "vinc", axp806_dcdca_ranges, - 72, AXP806_DCDCC_V_CTRL, 0x7f, AXP806_PWR_OUT_CTRL1, - BIT(2)), - AXP_DESC_RANGES(AXP806, DCDCD, "dcdcd", "vind", axp806_dcdcd_ranges, - 64, AXP806_DCDCD_V_CTRL, 0x3f, AXP806_PWR_OUT_CTRL1, - BIT(3)), + AXP806_DCDCB_V_CTRL, AXP806_DCDCB_V_CTRL, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_DCDCB_MASK), + AXP_DESC_RANGES(AXP806, DCDCC, "dcdcc", "vinc", + axp806_dcdca_ranges, AXP806_DCDCA_NUM_VOLTAGES, + AXP806_DCDCC_V_CTRL, AXP806_DCDCC_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_DCDCC_MASK), + AXP_DESC_RANGES(AXP806, DCDCD, "dcdcd", "vind", + axp806_dcdcd_ranges, AXP806_DCDCD_NUM_VOLTAGES, + AXP806_DCDCD_V_CTRL, AXP806_DCDCD_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_DCDCD_MASK), AXP_DESC(AXP806, DCDCE, "dcdce", "vine", 1100, 3400, 100, - AXP806_DCDCE_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(4)), + AXP806_DCDCE_V_CTRL, AXP806_DCDCE_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_DCDCE_MASK), AXP_DESC(AXP806, ALDO1, "aldo1", "aldoin", 700, 3300, 100, - AXP806_ALDO1_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(5)), + AXP806_ALDO1_V_CTRL, AXP806_ALDO1_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_ALDO1_MASK), AXP_DESC(AXP806, ALDO2, "aldo2", "aldoin", 700, 3400, 100, - AXP806_ALDO2_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(6)), + AXP806_ALDO2_V_CTRL, AXP806_ALDO2_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_ALDO2_MASK), AXP_DESC(AXP806, ALDO3, "aldo3", "aldoin", 700, 3300, 100, - AXP806_ALDO3_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL1, BIT(7)), + AXP806_ALDO3_V_CTRL, AXP806_ALDO3_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL1, AXP806_PWR_OUT_ALDO3_MASK), AXP_DESC(AXP806, BLDO1, "bldo1", "bldoin", 700, 1900, 100, - AXP806_BLDO1_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(0)), + AXP806_BLDO1_V_CTRL, AXP806_BLDO1_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_BLDO1_MASK), AXP_DESC(AXP806, BLDO2, "bldo2", "bldoin", 700, 1900, 100, - AXP806_BLDO2_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(1)), + AXP806_BLDO2_V_CTRL, AXP806_BLDO2_V_CTRL, + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_BLDO2_MASK), AXP_DESC(AXP806, BLDO3, "bldo3", "bldoin", 700, 1900, 100, - AXP806_BLDO3_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(2)), + AXP806_BLDO3_V_CTRL, AXP806_BLDO3_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_BLDO3_MASK), AXP_DESC(AXP806, BLDO4, "bldo4", "bldoin", 700, 1900, 100, - AXP806_BLDO4_V_CTRL, 0x0f, AXP806_PWR_OUT_CTRL2, BIT(3)), + AXP806_BLDO4_V_CTRL, AXP806_BLDO4_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_BLDO4_MASK), AXP_DESC(AXP806, CLDO1, "cldo1", "cldoin", 700, 3300, 100, - AXP806_CLDO1_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2, BIT(4)), - AXP_DESC_RANGES(AXP806, CLDO2, "cldo2", "cldoin", axp803_dldo2_ranges, - 32, AXP806_CLDO2_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2, - BIT(5)), + AXP806_CLDO1_V_CTRL, AXP806_CLDO1_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_CLDO1_MASK), + AXP_DESC_RANGES(AXP806, CLDO2, "cldo2", "cldoin", + axp803_dldo2_ranges, AXP803_DLDO2_NUM_VOLTAGES, + AXP806_CLDO2_V_CTRL, AXP806_CLDO2_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_CLDO2_MASK), AXP_DESC(AXP806, CLDO3, "cldo3", "cldoin", 700, 3300, 100, - AXP806_CLDO3_V_CTRL, 0x1f, AXP806_PWR_OUT_CTRL2, BIT(6)), - AXP_DESC_SW(AXP806, SW, "sw", "swin", AXP806_PWR_OUT_CTRL2, BIT(7)), + AXP806_CLDO3_V_CTRL, AXP806_CLDO3_V_CTRL_MASK, + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_CLDO3_MASK), + AXP_DESC_SW(AXP806, SW, "sw", "swin", + AXP806_PWR_OUT_CTRL2, AXP806_PWR_OUT_SW_MASK), }; static const struct regulator_linear_range axp809_dcdc4_ranges[] = { - REGULATOR_LINEAR_RANGE(600000, 0x0, 0x2f, 20000), - REGULATOR_LINEAR_RANGE(1800000, 0x30, 0x38, 100000), + REGULATOR_LINEAR_RANGE(600000, + AXP809_DCDC4_600mV_START, + AXP809_DCDC4_600mV_END, + 20000), + REGULATOR_LINEAR_RANGE(1800000, + AXP809_DCDC4_1800mV_START, + AXP809_DCDC4_1800mV_END, + 100000), }; static const struct regulator_desc axp809_regulators[] = { AXP_DESC(AXP809, DCDC1, "dcdc1", "vin1", 1600, 3400, 100, - AXP22X_DCDC1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(1)), + AXP22X_DCDC1_V_OUT, AXP22X_DCDC1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC1_MASK), AXP_DESC(AXP809, DCDC2, "dcdc2", "vin2", 600, 1540, 20, - AXP22X_DCDC2_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(2)), + AXP22X_DCDC2_V_OUT, AXP22X_DCDC2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC2_MASK), AXP_DESC(AXP809, DCDC3, "dcdc3", "vin3", 600, 1860, 20, - AXP22X_DCDC3_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, BIT(3)), - AXP_DESC_RANGES(AXP809, DCDC4, "dcdc4", "vin4", axp809_dcdc4_ranges, - 57, AXP22X_DCDC4_V_OUT, 0x3f, AXP22X_PWR_OUT_CTRL1, - BIT(4)), + AXP22X_DCDC3_V_OUT, AXP22X_DCDC3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC3_MASK), + AXP_DESC_RANGES(AXP809, DCDC4, "dcdc4", "vin4", + axp809_dcdc4_ranges, AXP809_DCDC4_NUM_VOLTAGES, + AXP22X_DCDC4_V_OUT, AXP22X_DCDC4_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC4_MASK), AXP_DESC(AXP809, DCDC5, "dcdc5", "vin5", 1000, 2550, 50, - AXP22X_DCDC5_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(5)), + AXP22X_DCDC5_V_OUT, AXP22X_DCDC5_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DCDC5_MASK), /* secondary switchable output of DCDC1 */ - AXP_DESC_SW(AXP809, DC1SW, "dc1sw", NULL, AXP22X_PWR_OUT_CTRL2, - BIT(7)), + AXP_DESC_SW(AXP809, DC1SW, "dc1sw", NULL, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DC1SW_MASK), /* LDO regulator internally chained to DCDC5 */ AXP_DESC(AXP809, DC5LDO, "dc5ldo", NULL, 700, 1400, 100, - AXP22X_DC5LDO_V_OUT, 0x7, AXP22X_PWR_OUT_CTRL1, BIT(0)), + AXP22X_DC5LDO_V_OUT, AXP22X_DC5LDO_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_DC5LDO_MASK), AXP_DESC(AXP809, ALDO1, "aldo1", "aldoin", 700, 3300, 100, - AXP22X_ALDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(6)), + AXP22X_ALDO1_V_OUT, AXP22X_ALDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_ALDO1_MASK), AXP_DESC(AXP809, ALDO2, "aldo2", "aldoin", 700, 3300, 100, - AXP22X_ALDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL1, BIT(7)), + AXP22X_ALDO2_V_OUT, AXP22X_ALDO2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL1, AXP22X_PWR_OUT_ALDO2_MASK), AXP_DESC(AXP809, ALDO3, "aldo3", "aldoin", 700, 3300, 100, - AXP22X_ALDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(5)), - AXP_DESC_RANGES(AXP809, DLDO1, "dldo1", "dldoin", axp803_dldo2_ranges, - 32, AXP22X_DLDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, - BIT(3)), + AXP22X_ALDO3_V_OUT, AXP22X_ALDO3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ALDO3_MASK), + AXP_DESC_RANGES(AXP809, DLDO1, "dldo1", "dldoin", + axp803_dldo2_ranges, AXP803_DLDO2_NUM_VOLTAGES, + AXP22X_DLDO1_V_OUT, AXP22X_DLDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO1_MASK), AXP_DESC(AXP809, DLDO2, "dldo2", "dldoin", 700, 3300, 100, - AXP22X_DLDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(4)), + AXP22X_DLDO2_V_OUT, AXP22X_DLDO2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_DLDO2_MASK), AXP_DESC(AXP809, ELDO1, "eldo1", "eldoin", 700, 3300, 100, - AXP22X_ELDO1_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(0)), + AXP22X_ELDO1_V_OUT, AXP22X_ELDO1_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO1_MASK), AXP_DESC(AXP809, ELDO2, "eldo2", "eldoin", 700, 3300, 100, - AXP22X_ELDO2_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(1)), + AXP22X_ELDO2_V_OUT, AXP22X_ELDO2_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO2_MASK), AXP_DESC(AXP809, ELDO3, "eldo3", "eldoin", 700, 3300, 100, - AXP22X_ELDO3_V_OUT, 0x1f, AXP22X_PWR_OUT_CTRL2, BIT(2)), + AXP22X_ELDO3_V_OUT, AXP22X_ELDO3_V_OUT_MASK, + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_ELDO3_MASK), /* * Note the datasheet only guarantees reliable operation up to * 3.3V, this needs to be enforced via dts provided constraints */ AXP_DESC_IO(AXP809, LDO_IO0, "ldo_io0", "ips", 700, 3800, 100, - AXP22X_LDO_IO0_V_OUT, 0x1f, AXP20X_GPIO0_CTRL, 0x07, + AXP22X_LDO_IO0_V_OUT, AXP22X_LDO_IO0_V_OUT_MASK, + AXP20X_GPIO0_CTRL, AXP20X_GPIO0_FUNC_MASK, AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), /* * Note the datasheet only guarantees reliable operation up to * 3.3V, this needs to be enforced via dts provided constraints */ AXP_DESC_IO(AXP809, LDO_IO1, "ldo_io1", "ips", 700, 3800, 100, - AXP22X_LDO_IO1_V_OUT, 0x1f, AXP20X_GPIO1_CTRL, 0x07, + AXP22X_LDO_IO1_V_OUT, AXP22X_LDO_IO1_V_OUT_MASK, + AXP20X_GPIO1_CTRL, AXP20X_GPIO1_FUNC_MASK, AXP22X_IO_ENABLED, AXP22X_IO_DISABLED), AXP_DESC_FIXED(AXP809, RTC_LDO, "rtc_ldo", "ips", 1800), - AXP_DESC_SW(AXP809, SW, "sw", "swin", AXP22X_PWR_OUT_CTRL2, BIT(6)), + AXP_DESC_SW(AXP809, SW, "sw", "swin", + AXP22X_PWR_OUT_CTRL2, AXP22X_PWR_OUT_SW_MASK), }; static int axp20x_set_dcdc_freq(struct platform_device *pdev, u32 dcdcfreq) @@ -588,9 +936,9 @@ static bool axp20x_is_polyphase_slave(struct axp20x_dev *axp20x, int id) switch (id) { case AXP803_DCDC3: - return !!(reg & BIT(6)); + return !!(reg & AXP803_DCDC23_POLYPHASE_DUAL); case AXP803_DCDC6: - return !!(reg & BIT(5)); + return !!(reg & AXP803_DCDC56_POLYPHASE_DUAL); } break; @@ -599,12 +947,15 @@ static bool axp20x_is_polyphase_slave(struct axp20x_dev *axp20x, int id) switch (id) { case AXP806_DCDCB: - return (((reg & GENMASK(7, 6)) == BIT(6)) || - ((reg & GENMASK(7, 6)) == BIT(7))); + return (((reg & AXP806_DCDCABC_POLYPHASE_MASK) == + AXP806_DCDCAB_POLYPHASE_DUAL) || + ((reg & AXP806_DCDCABC_POLYPHASE_MASK) == + AXP806_DCDCABC_POLYPHASE_TRI)); case AXP806_DCDCC: - return ((reg & GENMASK(7, 6)) == BIT(7)); + return ((reg & AXP806_DCDCABC_POLYPHASE_MASK) == + AXP806_DCDCABC_POLYPHASE_TRI); case AXP806_DCDCE: - return !!(reg & BIT(5)); + return !!(reg & AXP806_DCDCDE_POLYPHASE_DUAL); } break; From bd7f5b4b205328636390f4a573aaf12047b2d0f5 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 9 Oct 2017 11:38:00 +0200 Subject: [PATCH 34/93] regulator: axp20x: name voltage ramping define properly The current axp20x names the ramping register 'scal' which probably means scaling. Since the register really has nothing to do with scaling, but really is the voltage ramp we rename it appropriately. Signed-off-by: Olliver Schinagl --- include/linux/mfd/axp20x.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h index e9c908c4fba8a2..fa52c2b37dd6fc 100644 --- a/include/linux/mfd/axp20x.h +++ b/include/linux/mfd/axp20x.h @@ -35,7 +35,7 @@ enum axp20x_variants { #define AXP152_ALDO_OP_MODE 0x13 #define AXP152_LDO0_CTRL 0x15 #define AXP152_DCDC2_V_OUT 0x23 -#define AXP152_DCDC2_V_SCAL 0x25 +#define AXP152_DCDC2_V_RAMP 0x25 #define AXP152_DCDC1_V_OUT 0x26 #define AXP152_DCDC3_V_OUT 0x27 #define AXP152_ALDO12_V_OUT 0x28 @@ -53,7 +53,7 @@ enum axp20x_variants { #define AXP20X_USB_OTG_STATUS 0x02 #define AXP20X_PWR_OUT_CTRL 0x12 #define AXP20X_DCDC2_V_OUT 0x23 -#define AXP20X_DCDC2_LDO3_V_SCAL 0x25 +#define AXP20X_DCDC2_LDO3_V_RAMP 0x25 #define AXP20X_DCDC3_V_OUT 0x27 #define AXP20X_LDO24_V_OUT 0x28 #define AXP20X_LDO3_V_OUT 0x29 From 2211688ad296b259e9ddcc0b80365dabfef724c0 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 6 Mar 2017 09:29:39 +0100 Subject: [PATCH 35/93] regulator: core: enable power when setting up constraints When a regulator is marked as always on, it is enabled early on, when checking and setting up constraints. It makes the assumption that the bootloader properly initialized the regulator, and just in case enables the regulator anyway. Some constraints however currently get missed, such as the soft-start and ramp-delay. This causes the regulator to be enabled, without the soft-start and ramp-delay being applied, which in turn can cause high-currents or other start-up problems. By moving the always-enabled constraints later in the constraints check, we can at least ensure all constraints for the regulator are followed. Signed-off-by: Olliver Schinagl --- drivers/regulator/core.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index b64b7916507f28..13466d07a7f896 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1088,17 +1088,6 @@ static int set_machine_constraints(struct regulator_dev *rdev, } } - /* If the constraints say the regulator should be on at this point - * and we have control then make sure it is enabled. - */ - if (rdev->constraints->always_on || rdev->constraints->boot_on) { - ret = _regulator_do_enable(rdev); - if (ret < 0 && ret != -EINVAL) { - rdev_err(rdev, "failed to enable\n"); - return ret; - } - } - if ((rdev->constraints->ramp_delay || rdev->constraints->ramp_disable) && ops->set_ramp_delay) { ret = ops->set_ramp_delay(rdev, rdev->constraints->ramp_delay); @@ -1144,6 +1133,17 @@ static int set_machine_constraints(struct regulator_dev *rdev, } } + /* If the constraints say the regulator should be on at this point + * and we have control then make sure it is enabled. + */ + if (rdev->constraints->always_on || rdev->constraints->boot_on) { + ret = _regulator_do_enable(rdev); + if (ret < 0 && ret != -EINVAL) { + rdev_err(rdev, "failed to enable\n"); + return ret; + } + } + print_constraints(rdev); return 0; } From 0b9c63feefeadc9d8536fae9320dad33fc73a4df Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 2 Mar 2017 16:30:37 +0100 Subject: [PATCH 36/93] regulator: axp20x: add support for set_ramp_delay The AXP209 supports ramping up voltages on several regulators such as DCDC2 and LDO3. This patch adds preliminary support for the regulator-ramp-delay property these 2 regulators. Note that the voltage ramp only works when the regulator is already enabled. E.g. when going from say 0.7 V to 3.6 V. When turning on the regulator, no voltage ramp is performed in hardware. What this means, is that if the bootloader brings up the voltage at 0.7 V, the ramp delay property is properly applied. If however, the bootloader leaves the power off, no ramp delay is applied when the power is enabled by the regulator framework. Signed-off-by: Olliver Schinagl --- .../devicetree/bindings/mfd/axp20x.txt | 5 ++ drivers/regulator/axp20x-regulator.c | 86 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/axp20x.txt b/Documentation/devicetree/bindings/mfd/axp20x.txt index 9455503b029931..741017c7e14b67 100644 --- a/Documentation/devicetree/bindings/mfd/axp20x.txt +++ b/Documentation/devicetree/bindings/mfd/axp20x.txt @@ -33,6 +33,11 @@ Required properties: - interrupt-controller: The PMIC has its own internal IRQs - #interrupt-cells: Should be set to 1 +Supported common regulator properties, see regulator.txt for more information: +- regulator-ramp-delay: sets the ramp up delay in uV/us + AXP20x/DCDC2: 1600, 800 + AXP20x/LDO3: 1600, 800 + Optional properties: - x-powers,dcdc-freq: defines the work frequency of DC-DC in KHz AXP152/20X: range: 750-1875, Default: 1.5 MHz diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c index 3f14ddd376840f..5f0b391359e621 100644 --- a/drivers/regulator/axp20x-regulator.c +++ b/drivers/regulator/axp20x-regulator.c @@ -51,6 +51,17 @@ #define AXP20X_PWR_OUT_DCDC2_MASK BIT_MASK(4) #define AXP20X_PWR_OUT_LDO3_MASK BIT_MASK(6) +#define AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_RATE_MASK BIT_MASK(0) +#define AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_RATE(x) \ + ((x) << 0) +#define AXP20X_DCDC2_LDO3_V_RAMP_LDO3_RATE_MASK BIT_MASK(1) +#define AXP20X_DCDC2_LDO3_V_RAMP_LDO3_RATE(x) \ + ((x) << 1) +#define AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN_MASK BIT_MASK(2) +#define AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN BIT(2) +#define AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN_MASK BIT_MASK(3) +#define AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN BIT(3) + #define AXP20X_LDO4_V_OUT_1250mV_START 0x0 #define AXP20X_LDO4_V_OUT_1250mV_STEPS 0 #define AXP20X_LDO4_V_OUT_1250mV_END \ @@ -342,6 +353,80 @@ .ops = &axp20x_ops_range, \ } +static const int axp209_dcdc2_ldo3_slew_rates[] = { + 1600, + 800, +}; + +static int axp20x_set_ramp_delay(struct regulator_dev *rdev, int ramp) +{ + struct axp20x_dev *axp20x = rdev_get_drvdata(rdev); + const struct regulator_desc *desc = rdev->desc; + u8 reg, mask, enable, cfg = 0xff; + const int *slew_rates; + int slew_rates_cnt = 0; + + if (!rdev) + return -EINVAL; + + switch (axp20x->variant) { + case AXP209_ID: + if (desc->id == AXP20X_DCDC2) { + slew_rates_cnt = ARRAY_SIZE(axp209_dcdc2_ldo3_slew_rates); + reg = AXP20X_DCDC2_LDO3_V_RAMP; + mask = AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_RATE_MASK | + AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN_MASK; + enable = (ramp > 0) ? + AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN : + !AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN; + break; + } + + if (desc->id == AXP20X_LDO3) { + slew_rates = axp209_dcdc2_ldo3_slew_rates; + slew_rates = axp209_dcdc2_ldo3_slew_rates; + slew_rates_cnt = ARRAY_SIZE(axp209_dcdc2_ldo3_slew_rates); + reg = AXP20X_DCDC2_LDO3_V_RAMP; + mask = AXP20X_DCDC2_LDO3_V_RAMP_LDO3_RATE_MASK | + AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN_MASK; + enable = (ramp > 0) ? + AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN : + !AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN; + break; + } + + if (slew_rates_cnt > 0) + break; + + /* fall through */ + default: + /* Not supported for this regulator */ + return -ENOTSUPP; + } + + if (ramp == 0) { + cfg = enable; + } else { + int i; + + for (i = 0; i < slew_rates_cnt; i++) { + if (ramp <= slew_rates[i]) + cfg = AXP20X_DCDC2_LDO3_V_RAMP_LDO3_RATE(i); + else + break; + } + + if (cfg == 0xff) { + dev_err(axp20x->dev, "unsupported ramp value %d", ramp); + return -EINVAL; + } + + cfg |= enable; + } + + return regmap_update_bits(axp20x->regmap, reg, mask, cfg); +} + static const struct regulator_ops axp20x_ops_fixed = { .list_voltage = regulator_list_voltage_linear, }; @@ -362,6 +447,7 @@ static const struct regulator_ops axp20x_ops = { .enable = regulator_enable_regmap, .disable = regulator_disable_regmap, .is_enabled = regulator_is_enabled_regmap, + .set_ramp_delay = axp20x_set_ramp_delay, }; static const struct regulator_ops axp20x_ops_sw = { From b6ae32bbc74e946ff4d81981596992ce395100d2 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 2 Mar 2017 16:37:07 +0100 Subject: [PATCH 37/93] regulator: axp20x: add software based soft_start for LDO3 In the past, there have been words on various lists that if LDO3 is disabled in u-boot, but enabled in the DTS, the axp driver would fail to continue/hang. Several enable/disable patches have been issues to devicetree's in both the kernel and u-boot to address this issue. What really happened however, was that the AXP shuts down, without notice, without setting an interrupt. This is caused when LDO3 gets overloaded, for example with large capacitors on the LDO3 output. Normally, we would expect that the AXP would source 200 mA as per datasheet and set and trigger an interrupt when being overloaded. For some reason however, this does not happen. As a work-around, we use the soft-start constraint of the regulator node to first bring up the LDO3 to the lowest possible voltage and then enable the LDO. After that, we can set the requested voltage as per normal. Combining this setting with the regulator-ramp-delay allows LDO3 to come up slowly and staggered, potentially reducing overall inrush current. Signed-off-by: Olliver Schinagl --- .../devicetree/bindings/mfd/axp20x.txt | 3 + drivers/regulator/axp20x-regulator.c | 57 ++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mfd/axp20x.txt b/Documentation/devicetree/bindings/mfd/axp20x.txt index 741017c7e14b67..582dcc2a3b289c 100644 --- a/Documentation/devicetree/bindings/mfd/axp20x.txt +++ b/Documentation/devicetree/bindings/mfd/axp20x.txt @@ -37,6 +37,9 @@ Supported common regulator properties, see regulator.txt for more information: - regulator-ramp-delay: sets the ramp up delay in uV/us AXP20x/DCDC2: 1600, 800 AXP20x/LDO3: 1600, 800 +- regulator-soft-start: enable the output at the lowest possible voltage and + only then set the desired voltage + AXP20x/LDO3 Optional properties: - x-powers,dcdc-freq: defines the work frequency of DC-DC in KHz diff --git a/drivers/regulator/axp20x-regulator.c b/drivers/regulator/axp20x-regulator.c index 5f0b391359e621..992c5ff7155963 100644 --- a/drivers/regulator/axp20x-regulator.c +++ b/drivers/regulator/axp20x-regulator.c @@ -14,6 +14,7 @@ */ #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include #define AXP20X_GPIO0_FUNC_MASK GENMASK(3, 0) @@ -427,6 +429,59 @@ static int axp20x_set_ramp_delay(struct regulator_dev *rdev, int ramp) return regmap_update_bits(axp20x->regmap, reg, mask, cfg); } +static int axp20x_regulator_enable_regmap(struct regulator_dev *rdev) +{ + struct axp20x_dev *axp20x = rdev_get_drvdata(rdev); + const struct regulator_desc *desc = rdev->desc; + + if (!rdev) + return -EINVAL; + + switch (axp20x->variant) { + case AXP209_ID: + if ((desc->id == AXP20X_LDO3) && + rdev->constraints && rdev->constraints->soft_start) { + int v_out; + int ret; + + /* + * On some boards, the LDO3 can be overloaded when + * turning on, causing the entire PMIC to shutdown + * without warning. Turning it on at the minimal voltage + * and then setting the voltage to the requested value + * works reliably. + */ + if (regulator_is_enabled_regmap(rdev)) + break; + + v_out = regulator_get_voltage_sel_regmap(rdev); + if (v_out < 0) + return v_out; + + if (v_out == 0) + break; + + ret = regulator_set_voltage_sel_regmap(rdev, 0x00); + /* + * A small pause is needed between + * setting the voltage and enabling the LDO to give the + * internal state machine time to process the request. + */ + usleep_range(1000, 5000); + ret |= regulator_enable_regmap(rdev); + ret |= regulator_set_voltage_sel_regmap(rdev, v_out); + + return ret; + } + break; + default: + /* No quirks */ + break; + } + + return regulator_enable_regmap(rdev); +}; + static const struct regulator_ops axp20x_ops_fixed = { .list_voltage = regulator_list_voltage_linear, }; @@ -444,7 +499,7 @@ static const struct regulator_ops axp20x_ops = { .set_voltage_sel = regulator_set_voltage_sel_regmap, .get_voltage_sel = regulator_get_voltage_sel_regmap, .list_voltage = regulator_list_voltage_linear, - .enable = regulator_enable_regmap, + .enable = axp20x_regulator_enable_regmap, .disable = regulator_disable_regmap, .is_enabled = regulator_is_enabled_regmap, .set_ramp_delay = axp20x_set_ramp_delay, From 4df9bf0b2a1689ff03f0a591a865466e8e23910c Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 3 Mar 2017 10:03:45 +0100 Subject: [PATCH 38/93] regulator: dts: enable soft-start and ramp delay for the OLinuXino Lime2 The OLinuXino Lime2 has a big capacitor on its LDO3 output. It is actually too large, causing the PMIC to shutdown when toggling the LDO3. By enabling soft-start and ramp delay we increase the time for the capacitor to charge lowering the current drain on the power regulator. Signed-off-by: Olliver Schinagl --- arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts index 097bd755764cfb..b0ae1f3d9ee65d 100644 --- a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts +++ b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts @@ -221,6 +221,8 @@ regulator-min-microvolt = <2800000>; regulator-max-microvolt = <2800000>; regulator-name = "vddio-csi0"; + regulator-soft-start; + regulator-ramp-delay = <1600>; }; ®_ldo4 { From 50ae56862c340d245bd0db4fba7c9fa7d5b22040 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 11 Dec 2017 14:57:13 +0100 Subject: [PATCH 39/93] regulator: dts: add full voltage range to LDO4 on the Lime2 With commit b43776d65a33b46092 ("ARM: dts: sunxi: Use axp209.dtsi for Olinuxino Lime2") we force them an arbitrary 2.8 volts. Granted, for LDO3 this may be less arbitrary, but for LDO4 this is just wrong. In the defense of LDO3, LDO3 is the regulator that feeds port bank E, which has no other purpose then a CSI/TS interface, however the case may still be, that the connected IO may be just as well be 3.3 volts. The big misnomer is however, that the schematic names GPIO-2 pin4 LDO3_2.8V, rather then VDD-CSI0 or similar. This is much worse for LDO4 however, which is not referenced on any pin, is now set to 2.8 volts, but port bank G can also support various other peripherals such as UARTS etc. By having 2.8 volts however for LDO4, we thus now have peripherals that no longer function properly all of the time. Ideally, we want to set a supply voltage for each port bank, but the monolithic nature of the sunxi pinctroller currently prevents this and as such, the board should at least configure the LDO4 with the proper ranges. Until we can set the consumer at the port bank level, a child device-tree has to do something like: ®_ldo4 { regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; }; While doing this the same way results in the same solution currently, we force the hack into the final devicetree rather then having it wrong at the board level. Signed-off-by: Olliver Schinagl --- arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts index b0ae1f3d9ee65d..05ceaab2ecb1e4 100644 --- a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts +++ b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts @@ -226,9 +226,10 @@ }; ®_ldo4 { - regulator-min-microvolt = <2800000>; - regulator-max-microvolt = <2800000>; - regulator-name = "vddio-csi1"; + regulator-always-on; + regulator-min-microvolt = <1250000>; + regulator-max-microvolt = <3300000>; + regulator-name = "vdd-io-pg"; }; ®_usb0_vbus { From 5202883cc861101c8cfe8dc671932f264317911d Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 15:29:33 +0100 Subject: [PATCH 40/93] regulator: dts: set proper lradc vref on OLinuXino Lime2 The lradc's analog reference voltage is set to 3.0 volt in the hardware. This is more or less set in copper for at least lradc0. Set the property in the dts to ensure the lradc is referenced properly. Signed-off-by: Olliver Schinagl --- arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts index 05ceaab2ecb1e4..27ca9e69187b26 100644 --- a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts +++ b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts @@ -129,6 +129,10 @@ }; }; +&lradc { + vref-supply = <®_vcc3v0>; +}; + &mmc0 { pinctrl-names = "default"; pinctrl-0 = <&mmc0_pins_a>; From 4cb76bd59c6ff263380303c2ed94663d7bf8ab5e Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 3 Jan 2017 09:31:59 +0100 Subject: [PATCH 41/93] mfd: axp20x: fixup includes Add the bitops.h header as we need it, alphabetize header order. Signed-off-by: Olliver Schinagl --- drivers/mfd/axp20x.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index 336de66ca40807..6dc77d5f4eff60 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -16,18 +16,19 @@ * published by the Free Software Foundation. */ -#include +#include +#include #include +#include #include #include +#include +#include #include +#include #include #include #include -#include -#include -#include -#include #define AXP20X_OFF 0x80 From 29b11137ceb08dd240530702f0efd3b6873fca43 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 4 Oct 2017 15:33:23 +0200 Subject: [PATCH 42/93] mfd: axp20x: use explicit bit defines The AXP20X_OFF define is an actual specific bit, define it as such. Signed-off-by: Olliver Schinagl --- drivers/mfd/axp20x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index 6dc77d5f4eff60..66be1b6d83eb8e 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -30,7 +30,7 @@ #include #include -#define AXP20X_OFF 0x80 +#define AXP20X_OFF BIT(7) #define AXP806_REG_ADDR_EXT_ADDR_MASTER_MODE 0 #define AXP806_REG_ADDR_EXT_ADDR_SLAVE_MODE BIT(4) From 182856fb1ce990280e8c4992b8af5a816c3be8eb Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 3 Jan 2017 09:37:37 +0100 Subject: [PATCH 43/93] power: supply: axp20x: add missing include bitops.h The axp20x_usb_power driver uses BIT() operations but lacks the include for it. Include the bitops.h header file. Signed-off-by: Olliver Schinagl --- drivers/power/supply/axp20x_usb_power.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 44f70dcea61e3c..56970eb4339ff1 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -10,6 +10,7 @@ * option) any later version. */ +#include #include #include #include From 47b3c8fce2fba3a52ef363b4495c54216674d54f Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 3 Jan 2017 09:38:33 +0100 Subject: [PATCH 44/93] power: supply: axp288: use the BIT() macro Make use of the recommended BIT() macro for bit defines. Signed-off-by: Olliver Schinagl --- drivers/power/supply/axp288_charger.c | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index d51ebd1da65e77..2fd48ad9fdfcc5 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -28,17 +29,17 @@ #include #include -#define PS_STAT_VBUS_TRIGGER (1 << 0) -#define PS_STAT_BAT_CHRG_DIR (1 << 2) -#define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3) -#define PS_STAT_VBUS_VALID (1 << 4) -#define PS_STAT_VBUS_PRESENT (1 << 5) +#define PS_STAT_VBUS_TRIGGER BIT(0) +#define PS_STAT_BAT_CHRG_DIR BIT(2) +#define PS_STAT_VBAT_ABOVE_VHOLD BIT(3) +#define PS_STAT_VBUS_VALID BIT(4) +#define PS_STAT_VBUS_PRESENT BIT(5) -#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) -#define CHRG_STAT_BAT_VALID (1 << 4) -#define CHRG_STAT_BAT_PRESENT (1 << 5) -#define CHRG_STAT_CHARGING (1 << 6) -#define CHRG_STAT_PMIC_OTP (1 << 7) +#define CHRG_STAT_BAT_SAFE_MODE BIT(3) +#define CHRG_STAT_BAT_VALID BIT(4) +#define CHRG_STAT_BAT_PRESENT BIT(5) +#define CHRG_STAT_CHARGING BIT(6) +#define CHRG_STAT_PMIC_OTP BIT(7) #define VBUS_ISPOUT_CUR_LIM_MASK 0x03 #define VBUS_ISPOUT_CUR_LIM_BIT_POS 0 @@ -51,33 +52,33 @@ #define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ #define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ #define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */ -#define VBUS_ISPOUT_VBUS_PATH_DIS (1 << 7) +#define VBUS_ISPOUT_VBUS_PATH_DIS BIT(7) #define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ #define CHRG_CCCV_CC_BIT_POS 0 #define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ #define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ -#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ +#define CHRG_CCCV_ITERM_20P BIT(4) /* 20% of CC */ #define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ #define CHRG_CCCV_CV_BIT_POS 5 #define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ #define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ #define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ #define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ -#define CHRG_CCCV_CHG_EN (1 << 7) +#define CHRG_CCCV_CHG_EN BIT(7) #define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */ #define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */ #define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */ #define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */ -#define CNTL2_CHGLED_TYPEB (1 << 4) -#define CNTL2_CHG_OUT_TURNON (1 << 5) +#define CNTL2_CHGLED_TYPEB BIT(4) +#define CNTL2_CHG_OUT_TURNON BIT(5) #define CNTL2_PC_TIMEOUT_MASK 0xC0 #define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */ #define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */ #define CNTL2_PC_TIMEOUT_70MINS 0x3 -#define CHRG_ILIM_TEMP_LOOP_EN (1 << 3) +#define CHRG_ILIM_TEMP_LOOP_EN BIT(3) #define CHRG_VBUS_ILIM_MASK 0xf0 #define CHRG_VBUS_ILIM_BIT_POS 4 #define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */ @@ -91,7 +92,7 @@ #define CHRG_VLTFC_0C 0xA5 /* 0 DegC */ #define CHRG_VHTFC_45C 0x1F /* 45 DegC */ -#define FG_CNTL_OCV_ADJ_EN (1 << 3) +#define FG_CNTL_OCV_ADJ_EN BIT(3) #define CV_4100MV 4100 /* 4100mV */ #define CV_4150MV 4150 /* 4150mV */ From c70a8debe65daf19667c03472779daaa42c32343 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 19 Feb 2018 09:57:29 +0100 Subject: [PATCH 45/93] serial: 8250: Replace asm/irq with linux/irqreturn, linux/interrupt Currently, the 8250_core.c does not seem to use anything included from asm/irq.h, The only potential legacy reference may stem from irqreturn_t, spin_unlok_irq* and IRQ_RETVAL. Either that or asm/irq.h is now unused. Signed-off-by: Olliver Schinagl --- drivers/tty/serial/8250/8250_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index d29b512a7d9fac..c6463a33284a84 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -41,8 +43,6 @@ #include #endif -#include - #include "8250.h" /* From 9f9980b054d67d848120441297a66d35312cf648 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 29 Mar 2017 20:29:58 +0200 Subject: [PATCH 46/93] serial: 8250: Minor cleanups Some very minor code cleanups, such as including the bitops header for DW_UART_MCR_SIRE, use the BIT() macro, add missing and sort the headers, replace a magic value with a define and clean some whitespace as suggested by checkpatch. This patch does not perform any code changes. Signed-off-by: Olliver Schinagl --- drivers/tty/serial/8250/8250_core.c | 29 +++++++++-------- drivers/tty/serial/8250/8250_dw.c | 49 ++++++++++++++++------------- include/uapi/linux/serial_reg.h | 17 +++++----- 3 files changed, 52 insertions(+), 43 deletions(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index c6463a33284a84..9f1c01c0c99e38 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -18,30 +18,33 @@ * (at your option) any later version. */ -#include -#include -#include +#include +#include #include #include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include #include -#include +#include #include -#include #include #include -#include -#include +#include #include -#include -#include -#include +#include +#include #ifdef CONFIG_SPARC #include #endif +#include +#include +#include +#include #include "8250.h" diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 3015789265dd05..85fca62576008c 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -13,22 +13,25 @@ * LCR is written whilst busy. If it is, then a busy detect interrupt is * raised, the LCR needs to be rewritten and the uart status register read. */ +#include + +#include +#include +#include +#include #include #include #include -#include -#include #include #include #include #include -#include -#include -#include -#include #include - -#include +#include +#include +#include +#include +#include #include "8250.h" @@ -37,18 +40,21 @@ #define DW_UART_CPR 0xf4 /* Component Parameter Register */ #define DW_UART_UCV 0xf8 /* UART Component Version */ +/* Offsets for the Octeon specific registers */ +#define OCTEON_UART_USR 0x27 /* UART Status Register */ + /* Component Parameter Register bits */ #define DW_UART_CPR_ABP_DATA_WIDTH (3 << 0) -#define DW_UART_CPR_AFCE_MODE (1 << 4) -#define DW_UART_CPR_THRE_MODE (1 << 5) -#define DW_UART_CPR_SIR_MODE (1 << 6) -#define DW_UART_CPR_SIR_LP_MODE (1 << 7) -#define DW_UART_CPR_ADDITIONAL_FEATURES (1 << 8) -#define DW_UART_CPR_FIFO_ACCESS (1 << 9) -#define DW_UART_CPR_FIFO_STAT (1 << 10) -#define DW_UART_CPR_SHADOW (1 << 11) -#define DW_UART_CPR_ENCODED_PARMS (1 << 12) -#define DW_UART_CPR_DMA_EXTRA (1 << 13) +#define DW_UART_CPR_AFCE_MODE BIT(4) +#define DW_UART_CPR_THRE_MODE BIT(5) +#define DW_UART_CPR_SIR_MODE BIT(6) +#define DW_UART_CPR_SIR_LP_MODE BIT(7) +#define DW_UART_CPR_ADDITIONAL_FEATURES BIT(8) +#define DW_UART_CPR_FIFO_ACCESS BIT(9) +#define DW_UART_CPR_FIFO_STAT BIT(10) +#define DW_UART_CPR_SHADOW BIT(11) +#define DW_UART_CPR_ENCODED_PARMS BIT(12) +#define DW_UART_CPR_DMA_EXTRA BIT(13) #define DW_UART_CPR_FIFO_MODE (0xff << 16) /* Helper for fifo size calculation */ #define DW_UART_CPR_FIFO_SIZE(a) (((a >> 16) & 0xff) * 16) @@ -193,12 +199,11 @@ static void dw8250_serial_out32be(struct uart_port *p, int offset, int value) static unsigned int dw8250_serial_in32be(struct uart_port *p, int offset) { - unsigned int value = ioread32be(p->membase + (offset << p->regshift)); + unsigned int value = ioread32be(p->membase + (offset << p->regshift)); - return dw8250_modify_msr(p, offset, value); + return dw8250_modify_msr(p, offset, value); } - static int dw8250_handle_irq(struct uart_port *p) { struct uart_8250_port *up = up_to_u8250p(p); @@ -334,7 +339,7 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data) p->serial_out = dw8250_serial_outq; p->flags = UPF_SKIP_TEST | UPF_SHARE_IRQ | UPF_FIXED_TYPE; p->type = PORT_OCTEON; - data->usr_reg = 0x27; + data->usr_reg = OCTEON_UART_USR; data->skip_autocfg = true; } #endif diff --git a/include/uapi/linux/serial_reg.h b/include/uapi/linux/serial_reg.h index 619fe6111dc942..e54800b22c4c52 100644 --- a/include/uapi/linux/serial_reg.h +++ b/include/uapi/linux/serial_reg.h @@ -32,18 +32,19 @@ #define UART_IERX_SLEEP 0x10 /* Enable sleep mode */ #define UART_IIR 2 /* In: Interrupt ID Register */ -#define UART_IIR_NO_INT 0x01 /* No interrupts pending */ -#define UART_IIR_ID 0x0e /* Mask for the interrupt ID */ #define UART_IIR_MSI 0x00 /* Modem status interrupt */ +#define UART_IIR_NO_INT 0x01 /* No interrupts pending */ #define UART_IIR_THRI 0x02 /* Transmitter holding register empty */ #define UART_IIR_RDI 0x04 /* Receiver data interrupt */ #define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */ - -#define UART_IIR_BUSY 0x07 /* DesignWare APB Busy Detect */ - -#define UART_IIR_RX_TIMEOUT 0x0c /* OMAP RX Timeout interrupt */ -#define UART_IIR_XOFF 0x10 /* OMAP XOFF/Special Character */ -#define UART_IIR_CTS_RTS_DSR 0x20 /* OMAP CTS/RTS/DSR Change */ +#define UART_IIR_BUSY 0x07 /* Busy Detect */ +#define UART_IIR_RX_TIMEOUT 0x0c /* RX Timeout interrupt */ +#define UART_IIR_ID 0x0e /* Legacy mask for the interrupt ID */ +#define UART_IIR_MASK 0x0f /* Generic IIR mask */ + +#define UART_IIR_XOFF 0x10 /* XOFF/Special Character */ +#define UART_IIR_CTS_RTS_DSR 0x20 /* CTS/RTS/DSR Change */ +#define UART_IIR_EXT_MASK 0x30 /* Extended IIR mask */ #define UART_FCR 2 /* Out: FIFO Control Register */ #define UART_FCR_ENABLE_FIFO 0x01 /* Enable the FIFO */ From 588b9bc8d77104ed7553eb3c8964a1cea76ec7f6 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 16 Feb 2018 09:55:17 +0100 Subject: [PATCH 47/93] serial: 8250: Do not treat the IIR register as a bitfield In the original 8250 bits 0, 1, 2 and 3 where used together as the Interrupt Identification Register. Looking at various implementations and tables it could be argued that bits 1, 2 and 3 where the interrupt ID and bit 0 was a bit field. This worked for a while, albeit a little bit messy, but got more confusing as manufactures added more features to their 8250 cores, such as the designware UART IP, where the entire nibble was used as an identifier. The previous premises that bit 0 is special and that we are dealing with a bit field register is thus no longer true (for certain implementations). The IIR register thus always requires a mask to be read on the 8250. To make this easier we add a helper, as recommended by Andy Shevchenko, which takes care of the generic 8250 masking. By also allowing an additional mask, for the peripherals to use more (the reserved) bits, the helper optionally allows for additional masking. We currently only do this for the 8250 specific driver, though it is very likely this applies to others as well. Signed-off-by: Olliver Schinagl --- drivers/tty/serial/8250/8250.h | 11 +++++++++++ drivers/tty/serial/8250/8250_core.c | 9 +++++---- drivers/tty/serial/8250/8250_dw.c | 6 +++--- drivers/tty/serial/8250/8250_fsl.c | 4 ++-- drivers/tty/serial/8250/8250_mid.c | 4 ++-- drivers/tty/serial/8250/8250_omap.c | 8 ++++---- drivers/tty/serial/8250/8250_port.c | 23 ++++++++++++----------- 7 files changed, 39 insertions(+), 26 deletions(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index b2bdc35f74955f..69e5a5307388a0 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -115,6 +115,17 @@ static inline int serial_in(struct uart_8250_port *up, int offset) return up->port.serial_in(&up->port, offset); } +/** + * serial_in_iir - Helper function to obtain the interrupt ID from the + * Interrupt Identification Register. + * @up: UART port to get the interrupt ID from + * @mask: Additional mask to filter against + */ +static inline int serial_in_iir(struct uart_8250_port *up, int mask) +{ + return serial_in(up, UART_IIR) & (UART_IIR_MASK | mask); +} + static inline void serial_out(struct uart_8250_port *up, int offset, int value) { up->port.serial_out(&up->port, offset, value); diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 9f1c01c0c99e38..701fb44ae8e8b5 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -290,7 +290,7 @@ static void serial8250_backup_timeout(unsigned long data) serial_out(up, UART_IER, 0); } - iir = serial_in(up, UART_IIR); + iir = serial_in_iir(up, 0); /* * This should be a safe test for anyone who doesn't trust the @@ -300,14 +300,15 @@ static void serial8250_backup_timeout(unsigned long data) */ lsr = serial_in(up, UART_LSR); up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS; - if ((iir & UART_IIR_NO_INT) && (up->ier & UART_IER_THRI) && + if ((iir == UART_IIR_NO_INT) && + (up->ier & UART_IER_THRI) && (!uart_circ_empty(&up->port.state->xmit) || up->port.x_char) && (lsr & UART_LSR_THRE)) { - iir &= ~(UART_IIR_ID | UART_IIR_NO_INT); + iir &= ~(UART_IIR_MASK); iir |= UART_IIR_THRI; } - if (!(iir & UART_IIR_NO_INT)) + if (iir != UART_IIR_NO_INT) serial8250_tx_chars(up); if (up->port.irq) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 85fca62576008c..f7db84b513ffb1 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -208,7 +208,7 @@ static int dw8250_handle_irq(struct uart_port *p) { struct uart_8250_port *up = up_to_u8250p(p); struct dw8250_data *d = p->private_data; - unsigned int iir = p->serial_in(p, UART_IIR); + unsigned int iir = serial_in_iir(up, 0); unsigned int status; unsigned long flags; @@ -222,7 +222,7 @@ static int dw8250_handle_irq(struct uart_port *p) * This problem has only been observed so far when not in DMA mode * so we limit the workaround only to non-DMA mode. */ - if (!up->dma && ((iir & 0x3f) == UART_IIR_RX_TIMEOUT)) { + if (!up->dma && (iir == UART_IIR_RX_TIMEOUT)) { spin_lock_irqsave(&p->lock, flags); status = p->serial_in(p, UART_LSR); @@ -235,7 +235,7 @@ static int dw8250_handle_irq(struct uart_port *p) if (serial8250_handle_irq(p, iir)) return 1; - if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) { + if (iir == UART_IIR_BUSY) { /* Clear the USR */ (void)p->serial_in(p, d->usr_reg); diff --git a/drivers/tty/serial/8250/8250_fsl.c b/drivers/tty/serial/8250/8250_fsl.c index 910bfee5a88b7f..29bb55db8b967b 100644 --- a/drivers/tty/serial/8250/8250_fsl.c +++ b/drivers/tty/serial/8250/8250_fsl.c @@ -32,8 +32,8 @@ int fsl8250_handle_irq(struct uart_port *port) spin_lock_irqsave(&up->port.lock, flags); - iir = port->serial_in(port, UART_IIR); - if (iir & UART_IIR_NO_INT) { + iir = serial_in_iir(up, 0); + if (iir == UART_IIR_NO_INT) { spin_unlock_irqrestore(&up->port.lock, flags); return 0; } diff --git a/drivers/tty/serial/8250/8250_mid.c b/drivers/tty/serial/8250/8250_mid.c index ec957cce8c9a72..28f81321aa0edf 100644 --- a/drivers/tty/serial/8250/8250_mid.c +++ b/drivers/tty/serial/8250/8250_mid.c @@ -102,7 +102,7 @@ static int tng_handle_irq(struct uart_port *p) ret |= hsu_dma_do_irq(chip, mid->dma_index * 2, status); /* UART */ - ret |= serial8250_handle_irq(p, serial_port_in(p, UART_IIR)); + ret |= serial8250_handle_irq(p, serial_in_iir(up, 0)); return IRQ_RETVAL(ret); } @@ -151,7 +151,7 @@ static int dnv_handle_irq(struct uart_port *p) ret |= hsu_dma_do_irq(&mid->dma_chip, 0, status); } if (fisr & BIT(0)) - ret |= serial8250_handle_irq(p, serial_port_in(p, UART_IIR)); + ret |= serial8250_handle_irq(p, serial_in_iir(up, 0)); return IRQ_RETVAL(ret); } diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 833771bca0a593..d2661c4a2ce21a 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -584,7 +584,7 @@ static irqreturn_t omap8250_irq(int irq, void *dev_id) #endif serial8250_rpm_get(up); - iir = serial_port_in(port, UART_IIR); + iir = serial_in_iir(up, 0); ret = serial8250_handle_irq(port, iir); serial8250_rpm_put(up); @@ -1019,7 +1019,7 @@ static int omap_8250_tx_dma(struct uart_8250_port *p) static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir) { - switch (iir & 0x3f) { + switch (iir & (UART_IIR_MASK | UART_IIR_EXT_MASK)) { case UART_IIR_RLSI: case UART_IIR_RX_TIMEOUT: case UART_IIR_RDI: @@ -1043,8 +1043,8 @@ static int omap_8250_dma_handle_irq(struct uart_port *port) serial8250_rpm_get(up); - iir = serial_port_in(port, UART_IIR); - if (iir & UART_IIR_NO_INT) { + iir = serial_in_iir(up, UART_IIR_EXT_MASK); + if (iir == UART_IIR_NO_INT) { serial8250_rpm_put(up); return 0; } diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 8dcfd4978a036c..cacaaa7145e95d 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -1836,7 +1836,7 @@ EXPORT_SYMBOL_GPL(serial8250_modem_status); static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir) { - switch (iir & 0x3f) { + switch (iir & UART_IIR_MASK) { case UART_IIR_RX_TIMEOUT: serial8250_rx_dma_flush(up); /* fall-through */ @@ -1855,7 +1855,7 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir) unsigned long flags; struct uart_8250_port *up = up_to_u8250p(port); - if (iir & UART_IIR_NO_INT) + if (iir == UART_IIR_NO_INT) return 0; spin_lock_irqsave(&port->lock, flags); @@ -1883,7 +1883,7 @@ static int serial8250_default_handle_irq(struct uart_port *port) serial8250_rpm_get(up); - iir = serial_port_in(port, UART_IIR); + iir = serial_in_iir(up, 0); ret = serial8250_handle_irq(port, iir); serial8250_rpm_put(up); @@ -1919,10 +1919,11 @@ static int exar_handle_irq(struct uart_port *port) static int serial8250_tx_threshold_handle_irq(struct uart_port *port) { unsigned long flags; - unsigned int iir = serial_port_in(port, UART_IIR); + struct uart_8250_port *up = up_to_u8250p(port); + unsigned int iir = serial_in_iir(up, 0); /* TX Threshold IRQ triggered so load up FIFO */ - if ((iir & UART_IIR_ID) == UART_IIR_THRI) { + if (iir == UART_IIR_THRI) { struct uart_8250_port *up = up_to_u8250p(port); spin_lock_irqsave(&port->lock, flags); @@ -1930,7 +1931,7 @@ static int serial8250_tx_threshold_handle_irq(struct uart_port *port) spin_unlock_irqrestore(&port->lock, flags); } - iir = serial_port_in(port, UART_IIR); + iir = serial_in(up, 0); return serial8250_handle_irq(port, iir); } @@ -2274,11 +2275,11 @@ int serial8250_do_startup(struct uart_port *port) wait_for_xmitr(up, UART_LSR_THRE); serial_port_out_sync(port, UART_IER, UART_IER_THRI); udelay(1); /* allow THRE to set */ - iir1 = serial_port_in(port, UART_IIR); + iir1 = serial_in_iir(up, 0); serial_port_out(port, UART_IER, 0); serial_port_out_sync(port, UART_IER, UART_IER_THRI); udelay(1); /* allow a working UART time to re-assert THRE */ - iir = serial_port_in(port, UART_IIR); + iir = serial_in_iir(up, 0); serial_port_out(port, UART_IER, 0); if (port->irqflags & IRQF_SHARED) @@ -2290,7 +2291,7 @@ int serial8250_do_startup(struct uart_port *port) * don't trust the iir, setup a timer to kick the UART * on a regular basis. */ - if ((!(iir1 & UART_IIR_NO_INT) && (iir & UART_IIR_NO_INT)) || + if (((iir1 != UART_IIR_NO_INT) && (iir == UART_IIR_NO_INT)) || up->port.flags & UPF_BUG_THRE) { up->bugs |= UART_BUG_THRE; } @@ -2338,10 +2339,10 @@ int serial8250_do_startup(struct uart_port *port) */ serial_port_out(port, UART_IER, UART_IER_THRI); lsr = serial_port_in(port, UART_LSR); - iir = serial_port_in(port, UART_IIR); + iir = serial_in_iir(up, 0); serial_port_out(port, UART_IER, 0); - if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) { + if (lsr & UART_LSR_TEMT && (iir == UART_IIR_NO_INT)) { if (!(up->bugs & UART_BUG_TXEN)) { up->bugs |= UART_BUG_TXEN; pr_debug("ttyS%d - enabling bad tx status workarounds\n", From c888f04cfcef4df712ada60e25c47b3f6743e2ea Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 15 Feb 2018 16:38:13 +0100 Subject: [PATCH 48/93] serial: 8250_dw: Use serial port assessor wrapper Currently, we directly access the function pointer from the uart_*port structure, but the recommended method is to use the *port_in helper functions. This patch replaces these in the 8250_dw glue driver. Signed-off-by: Olliver Schinagl --- drivers/tty/serial/8250/8250_dw.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index f7db84b513ffb1..27161207dec439 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -94,7 +94,7 @@ static void dw8250_force_idle(struct uart_port *p) struct uart_8250_port *up = up_to_u8250p(p); serial8250_clear_and_reinit_fifos(up); - (void)p->serial_in(p, UART_RX); + (void)serial_port_in(p, UART_RX); } static void dw8250_check_lcr(struct uart_port *p, int value) @@ -104,7 +104,7 @@ static void dw8250_check_lcr(struct uart_port *p, int value) /* Make sure LCR write wasn't ignored */ while (tries--) { - unsigned int lcr = p->serial_in(p, UART_LCR); + unsigned int lcr = serial_port_in(p, UART_LCR); if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR)) return; @@ -224,10 +224,10 @@ static int dw8250_handle_irq(struct uart_port *p) */ if (!up->dma && (iir == UART_IIR_RX_TIMEOUT)) { spin_lock_irqsave(&p->lock, flags); - status = p->serial_in(p, UART_LSR); + status = serial_port_in(p, UART_LSR); if (!(status & (UART_LSR_DR | UART_LSR_BI))) - (void) p->serial_in(p, UART_RX); + (void)serial_port_in(p, UART_RX); spin_unlock_irqrestore(&p->lock, flags); } @@ -237,7 +237,7 @@ static int dw8250_handle_irq(struct uart_port *p) if (iir == UART_IIR_BUSY) { /* Clear the USR */ - (void)p->serial_in(p, d->usr_reg); + (void)serial_port_in(p, d->usr_reg); return 1; } From 683cd0e355871da7f7a0dc1bd5514aa5d04a1fab Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 16 Feb 2018 08:51:50 +0100 Subject: [PATCH 49/93] serial: 8250_dw: Make register access consistent Almost all register access in the 8250_dw glue is performed on the byte aligned register address and has a regshift applied in the serial_in assessor helper. In dw8250_setup_port however we directly write the register, ignoring the register shift. While it is very likely that all devices using these offsets are word aligned, it is far more consistent and readable with the rest of the code base to apply the register shift and define the registers as their counterparts, byte aligned. Signed-off-by: Olliver Schinagl --- drivers/tty/serial/8250/8250_dw.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 27161207dec439..825ff5a8808711 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -35,10 +35,10 @@ #include "8250.h" -/* Offsets for the DesignWare specific registers */ +/* Byte aligned offsets for the DesignWare specific registers */ #define DW_UART_USR 0x1f /* UART Status Register */ -#define DW_UART_CPR 0xf4 /* Component Parameter Register */ -#define DW_UART_UCV 0xf8 /* UART Component Version */ +#define DW_UART_CPR 0x3d /* Component Parameter Register */ +#define DW_UART_UCV 0x3e /* UART Component Version */ /* Offsets for the Octeon specific registers */ #define OCTEON_UART_USR 0x27 /* UART Status Register */ @@ -380,9 +380,9 @@ static void dw8250_setup_port(struct uart_port *p) * ADDITIONAL_FEATURES are not enabled. No need to go any further. */ if (p->iotype == UPIO_MEM32BE) - reg = ioread32be(p->membase + DW_UART_UCV); + reg = ioread32be(p->membase + (DW_UART_UCV << p->regshift)); else - reg = readl(p->membase + DW_UART_UCV); + reg = readl(p->membase + (DW_UART_UCV << p->regshift)); if (!reg) return; @@ -390,9 +390,9 @@ static void dw8250_setup_port(struct uart_port *p) (reg >> 24) & 0xff, (reg >> 16) & 0xff, (reg >> 8) & 0xff); if (p->iotype == UPIO_MEM32BE) - reg = ioread32be(p->membase + DW_UART_CPR); + reg = ioread32be(p->membase + (DW_UART_CPR << p->regshift)); else - reg = readl(p->membase + DW_UART_CPR); + reg = readl(p->membase + (DW_UART_CPR << p->regshift)); if (!reg) return; From b699f8f27797eb1197f3828f4cd28d4667729166 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 16 Feb 2018 09:18:22 +0100 Subject: [PATCH 50/93] serial: 8250: Only confirm handled interrupts on supported ones The serial8250_handle_irq function handles many of the possible interrupts, but none of the newer interrupts. Currently, the function only notifies of this for the case of the 'no interrupt'. All other cases are pretended to be handled and the caller has to figure out what could or is not handled. Instead, we should check which of the requested interrupts we actually do handle, and return false otherwise, allowing the caller to take further measures. Signed-off-by: Olliver Schinagl --- drivers/tty/serial/8250/8250_port.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index cacaaa7145e95d..d0e1c1ea60ba52 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -1846,8 +1846,22 @@ static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir) return up->dma->rx_dma(up); } -/* - * This handles the interrupt from one port. +/** + * serial8250_handle_irq - Handles the generic 8250 interrupts + * @port: UART port structure holding the IRQ generating port + * @iir: Interrupt Identification Register containing the masked + * interrupt source, such as supplied via serial_in_IIR() + * + * Handle the following generic 8250 common interrupts + * o Modem Status Interrupt + * o Transmitter holding register empty + * o Receiver Data Interrupt + * o Receiver Line Status Interrupt + * o RX Timeout Interrupt + * + * Other interrupts are expected to be handled by the caller where needed. + * + * Returns 0 when an interrupt was not handled, 1 otherwise. */ int serial8250_handle_irq(struct uart_port *port, unsigned int iir) { @@ -1855,7 +1869,11 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir) unsigned long flags; struct uart_8250_port *up = up_to_u8250p(port); - if (iir == UART_IIR_NO_INT) + if ((iir != UART_IIR_MSI) && + (iir != UART_IIR_THRI) && + (iir != UART_IIR_RDI) && + (iir != UART_IIR_RLSI) && + (iir != UART_IIR_RX_TIMEOUT)) return 0; spin_lock_irqsave(&port->lock, flags); From 4fdf7650166a96b8d9ee1b35ad6b11fdcaae1515 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 16 Feb 2018 09:35:58 +0100 Subject: [PATCH 51/93] serial: 8250: expand debug ability of the irq handler The 'too much work for irq' error (has) happened often without understanding whats going on. Over the years several fixes have been added to address this issue, but even the latest fix is from not so long ago. To get just a little bit more information from the system when this happens, also print the IIR register in the debug statement to help debuggers understand what could possibly go on. Signed-off-by: Olliver Schinagl --- drivers/tty/serial/8250/8250_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 701fb44ae8e8b5..1b18d929e19b25 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -140,7 +140,8 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) if (l == i->head && pass_counter++ > PASS_LIMIT) { /* If we hit this, we're dead. */ printk_ratelimited(KERN_ERR - "serial8250: too much work for irq%d\n", irq); + "serial8250: too much work for irq%d (iir: 0x%02x)\n", + irq, serial_port_in(port, UART_IIR)); break; } } while (l != end); From 4c7e6a01b31f9b45212c37fbd449c18db9f479cc Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 11 Dec 2017 16:20:21 +0100 Subject: [PATCH 52/93] input: of_touchscreen: shorten variable names To clean up some whitespace issues and make room for future additions some variable names where shortened. This patch does not invoke any code changes. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/of_touchscreen.c | 56 ++++++++++------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/drivers/input/touchscreen/of_touchscreen.c b/drivers/input/touchscreen/of_touchscreen.c index 8d7f9c8f2771c7..7c278fe033c77e 100644 --- a/drivers/input/touchscreen/of_touchscreen.c +++ b/drivers/input/touchscreen/of_touchscreen.c @@ -68,46 +68,42 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, { struct device *dev = input->dev.parent; unsigned int axis; - unsigned int maximum, fuzz; - bool data_present; + unsigned int max, fuzz; + bool ret; input_alloc_absinfo(input); if (!input->absinfo) return; axis = multitouch ? ABS_MT_POSITION_X : ABS_X; - data_present = touchscreen_get_prop_u32(dev, "touchscreen-size-x", - input_abs_get_max(input, - axis) + 1, - &maximum) | - touchscreen_get_prop_u32(dev, "touchscreen-fuzz-x", - input_abs_get_fuzz(input, axis), - &fuzz); - if (data_present) - touchscreen_set_params(input, axis, maximum - 1, fuzz); + ret = touchscreen_get_prop_u32(dev, "touchscreen-size-x", + input_abs_get_max(input, axis) + 1, + &max) | + touchscreen_get_prop_u32(dev, "touchscreen-fuzz-x", + input_abs_get_fuzz(input, axis), + &fuzz); + if (ret) + touchscreen_set_params(input, axis, max - 1, fuzz); axis = multitouch ? ABS_MT_POSITION_Y : ABS_Y; - data_present = touchscreen_get_prop_u32(dev, "touchscreen-size-y", - input_abs_get_max(input, - axis) + 1, - &maximum) | - touchscreen_get_prop_u32(dev, "touchscreen-fuzz-y", - input_abs_get_fuzz(input, axis), - &fuzz); - if (data_present) - touchscreen_set_params(input, axis, maximum - 1, fuzz); + ret = touchscreen_get_prop_u32(dev, "touchscreen-size-y", + input_abs_get_max(input, axis) + 1, + &max) | + touchscreen_get_prop_u32(dev, "touchscreen-fuzz-y", + input_abs_get_fuzz(input, axis), + &fuzz); + if (ret) + touchscreen_set_params(input, axis, max - 1, fuzz); axis = multitouch ? ABS_MT_PRESSURE : ABS_PRESSURE; - data_present = touchscreen_get_prop_u32(dev, - "touchscreen-max-pressure", - input_abs_get_max(input, axis), - &maximum) | - touchscreen_get_prop_u32(dev, - "touchscreen-fuzz-pressure", - input_abs_get_fuzz(input, axis), - &fuzz); - if (data_present) - touchscreen_set_params(input, axis, maximum, fuzz); + ret = touchscreen_get_prop_u32(dev, "touchscreen-max-pressure", + input_abs_get_max(input, axis), + &max) | + touchscreen_get_prop_u32(dev, "touchscreen-fuzz-pressure", + input_abs_get_fuzz(input, axis), + &fuzz); + if (ret) + touchscreen_set_params(input, axis, max, fuzz); if (!prop) return; From 63f53da3a93736a94d699fb6e00b4d5bb3687418 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 11 Dec 2017 16:26:41 +0100 Subject: [PATCH 53/93] input: of_touchscreen: rename touchscreen-size-[xy] The parameter touchscreen-size-x sits oddly next to touchscreen-max-pressure for example. Further more when considering that the actual parameter modified in absinfo is the 'maximum'. Let us thus rename the touchscreen-max-size-[xy] property in the devicetree. The new name takes precedence over the earlier touchscreen-size-[xy] in case both are found. The former is now deprecated. Signed-off-by: Olliver Schinagl --- .../bindings/input/touchscreen/touchscreen.txt | 10 ++++++---- drivers/input/touchscreen/of_touchscreen.c | 6 ++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt index 537643e86f6186..499e8630968d7f 100644 --- a/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt +++ b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt @@ -1,10 +1,10 @@ General Touchscreen Properties: Optional properties for Touchscreens: - - touchscreen-size-x : horizontal resolution of touchscreen - (in pixels) - - touchscreen-size-y : vertical resolution of touchscreen - (in pixels) + - touchscreen-max-size-x : maximal reported horizontal resolution of + touchscreen (in pixels) + - touchscreen-max-size-y : maximal reported vertical resolution of touchscreen + touchscreen (in pixels) - touchscreen-max-pressure : maximum reported pressure (arbitrary range dependent on the controller) - touchscreen-fuzz-x : horizontal noise value of the absolute input @@ -25,6 +25,8 @@ Optional properties for Touchscreens: - touchscreen-y-mm : vertical length in mm of the touchscreen Deprecated properties for Touchscreens: + - touchscreen-size-x : deprecated name for touchscreen-size-x + - touchscreen-size-y : deprecated name for touchscreen-size-y - x-size : deprecated name for touchscreen-size-x - y-size : deprecated name for touchscreen-size-y - moving-threshold : deprecated name for a combination of diff --git a/drivers/input/touchscreen/of_touchscreen.c b/drivers/input/touchscreen/of_touchscreen.c index 7c278fe033c77e..9872c31cea1bd3 100644 --- a/drivers/input/touchscreen/of_touchscreen.c +++ b/drivers/input/touchscreen/of_touchscreen.c @@ -79,6 +79,9 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, ret = touchscreen_get_prop_u32(dev, "touchscreen-size-x", input_abs_get_max(input, axis) + 1, &max) | + touchscreen_get_prop_u32(dev, "touchscreen-max-size-x", + input_abs_get_max(input, axis) + 1, + &max) | touchscreen_get_prop_u32(dev, "touchscreen-fuzz-x", input_abs_get_fuzz(input, axis), &fuzz); @@ -89,6 +92,9 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, ret = touchscreen_get_prop_u32(dev, "touchscreen-size-y", input_abs_get_max(input, axis) + 1, &max) | + touchscreen_get_prop_u32(dev, "touchscreen-max-size-y", + input_abs_get_max(input, axis) + 1, + &max) | touchscreen_get_prop_u32(dev, "touchscreen-fuzz-y", input_abs_get_fuzz(input, axis), &fuzz); From 92b0e09bad4f19e33fb8e0c9a7dcae041f5cb82f Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 13 Dec 2017 13:45:08 +0100 Subject: [PATCH 54/93] input: of_touchscreen: add support for minimum sizes In certain configurations, touchscreens are bigger then the underlying display. This can happen for various reasons, but an interesting design choice is to improve the edge detection of touch events. To make sure we don't have strange offsets we want to pass this information along to the input framework via the of_touchscreen layer. To this, we need to also parse the 'minimum' property. Signed-off-by: Olliver Schinagl --- .../input/touchscreen/touchscreen.txt | 4 ++++ drivers/input/touchscreen/of_touchscreen.c | 21 ++++++++++++------- include/linux/input/touchscreen.h | 2 ++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt index 499e8630968d7f..3420a816543bde 100644 --- a/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt +++ b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt @@ -1,6 +1,10 @@ General Touchscreen Properties: Optional properties for Touchscreens: + - touchscreen-min-size-x : minimal reported horizontal resolution of + touchscreen (in pixels) + - touchscreen-min-size-y : minimal reported vertical resolution of + touchscreen (in pixels) - touchscreen-max-size-x : maximal reported horizontal resolution of touchscreen (in pixels) - touchscreen-max-size-y : maximal reported vertical resolution of touchscreen diff --git a/drivers/input/touchscreen/of_touchscreen.c b/drivers/input/touchscreen/of_touchscreen.c index 9872c31cea1bd3..c12f3c863feb3a 100644 --- a/drivers/input/touchscreen/of_touchscreen.c +++ b/drivers/input/touchscreen/of_touchscreen.c @@ -34,7 +34,7 @@ static bool touchscreen_get_prop_u32(struct device *dev, static void touchscreen_set_params(struct input_dev *dev, unsigned long axis, - int max, int fuzz) + int min, int max, int fuzz) { struct input_absinfo *absinfo; @@ -46,6 +46,7 @@ static void touchscreen_set_params(struct input_dev *dev, } absinfo = &dev->absinfo[axis]; + absinfo->minimum = min; absinfo->maximum = max; absinfo->fuzz = fuzz; } @@ -68,7 +69,7 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, { struct device *dev = input->dev.parent; unsigned int axis; - unsigned int max, fuzz; + unsigned int min, max, fuzz; bool ret; input_alloc_absinfo(input); @@ -76,7 +77,10 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, return; axis = multitouch ? ABS_MT_POSITION_X : ABS_X; - ret = touchscreen_get_prop_u32(dev, "touchscreen-size-x", + ret = touchscreen_get_prop_u32(dev, "touchscreen-min-size-x", + input_abs_get_min(input, axis), + &min) | + touchscreen_get_prop_u32(dev, "touchscreen-size-x", input_abs_get_max(input, axis) + 1, &max) | touchscreen_get_prop_u32(dev, "touchscreen-max-size-x", @@ -86,10 +90,13 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, input_abs_get_fuzz(input, axis), &fuzz); if (ret) - touchscreen_set_params(input, axis, max - 1, fuzz); + touchscreen_set_params(input, axis, min, max - 1, fuzz); axis = multitouch ? ABS_MT_POSITION_Y : ABS_Y; - ret = touchscreen_get_prop_u32(dev, "touchscreen-size-y", + ret = touchscreen_get_prop_u32(dev, "touchscreen-min-size-y", + input_abs_get_min(input, axis), + &min) | + touchscreen_get_prop_u32(dev, "touchscreen-size-y", input_abs_get_max(input, axis) + 1, &max) | touchscreen_get_prop_u32(dev, "touchscreen-max-size-y", @@ -99,7 +106,7 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, input_abs_get_fuzz(input, axis), &fuzz); if (ret) - touchscreen_set_params(input, axis, max - 1, fuzz); + touchscreen_set_params(input, axis, min, max - 1, fuzz); axis = multitouch ? ABS_MT_PRESSURE : ABS_PRESSURE; ret = touchscreen_get_prop_u32(dev, "touchscreen-max-pressure", @@ -109,7 +116,7 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch, input_abs_get_fuzz(input, axis), &fuzz); if (ret) - touchscreen_set_params(input, axis, max, fuzz); + touchscreen_set_params(input, axis, 0, max, fuzz); if (!prop) return; diff --git a/include/linux/input/touchscreen.h b/include/linux/input/touchscreen.h index 09d22ccb9e415e..3c94403183b14b 100644 --- a/include/linux/input/touchscreen.h +++ b/include/linux/input/touchscreen.h @@ -13,7 +13,9 @@ struct input_dev; struct input_mt_pos; struct touchscreen_properties { + unsigned int min_x; unsigned int max_x; + unsigned int min_y; unsigned int max_y; bool invert_x; bool invert_y; From 136c3a0109d0dbf28ee169a00ce815e11a34b7f6 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 19 Dec 2017 16:24:11 +0100 Subject: [PATCH 55/93] input: edt-ft5x06: group r, w and rw functions Move the read and write functions higher and next to the read-write function. This makes adding features easier without introducing prototypes and keeps the relevant functions together. This patch introduces no code changes. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 140 ++++++++++++------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 1e18ca0d1b4e1e..8564f2d1bf46ba 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -151,6 +151,76 @@ static int edt_ft5x06_ts_readwrite(struct i2c_client *client, return 0; } +static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata, + u8 addr, u8 value) +{ + u8 wrbuf[4]; + + switch (tsdata->version) { + case EDT_M06: + wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc; + wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f; + wrbuf[2] = value; + wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2]; + return edt_ft5x06_ts_readwrite(tsdata->client, 4, + wrbuf, 0, NULL); + case EDT_M09: + case EDT_M12: + case GENERIC_FT: + wrbuf[0] = addr; + wrbuf[1] = value; + + return edt_ft5x06_ts_readwrite(tsdata->client, 2, + wrbuf, 0, NULL); + + default: + return -EINVAL; + } +} + +static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata, + u8 addr) +{ + u8 wrbuf[2], rdbuf[2]; + int error; + + switch (tsdata->version) { + case EDT_M06: + wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc; + wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f; + wrbuf[1] |= tsdata->factory_mode ? 0x80 : 0x40; + + error = edt_ft5x06_ts_readwrite(tsdata->client, 2, wrbuf, 2, + rdbuf); + if (error) + return error; + + if ((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) { + dev_err(&tsdata->client->dev, + "crc error: 0x%02x expected, got 0x%02x\n", + wrbuf[0] ^ wrbuf[1] ^ rdbuf[0], + rdbuf[1]); + return -EIO; + } + break; + + case EDT_M09: + case EDT_M12: + case GENERIC_FT: + wrbuf[0] = addr; + error = edt_ft5x06_ts_readwrite(tsdata->client, 1, + wrbuf, 1, rdbuf); + if (error) + return error; + break; + + default: + return -EINVAL; + } + + return rdbuf[0]; +} + static bool edt_ft5x06_ts_check_crc(struct edt_ft5x06_ts_data *tsdata, u8 *buf, int buflen) { @@ -262,76 +332,6 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static int edt_ft5x06_register_write(struct edt_ft5x06_ts_data *tsdata, - u8 addr, u8 value) -{ - u8 wrbuf[4]; - - switch (tsdata->version) { - case EDT_M06: - wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc; - wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f; - wrbuf[2] = value; - wrbuf[3] = wrbuf[0] ^ wrbuf[1] ^ wrbuf[2]; - return edt_ft5x06_ts_readwrite(tsdata->client, 4, - wrbuf, 0, NULL); - case EDT_M09: - case EDT_M12: - case GENERIC_FT: - wrbuf[0] = addr; - wrbuf[1] = value; - - return edt_ft5x06_ts_readwrite(tsdata->client, 2, - wrbuf, 0, NULL); - - default: - return -EINVAL; - } -} - -static int edt_ft5x06_register_read(struct edt_ft5x06_ts_data *tsdata, - u8 addr) -{ - u8 wrbuf[2], rdbuf[2]; - int error; - - switch (tsdata->version) { - case EDT_M06: - wrbuf[0] = tsdata->factory_mode ? 0xf3 : 0xfc; - wrbuf[1] = tsdata->factory_mode ? addr & 0x7f : addr & 0x3f; - wrbuf[1] |= tsdata->factory_mode ? 0x80 : 0x40; - - error = edt_ft5x06_ts_readwrite(tsdata->client, 2, wrbuf, 2, - rdbuf); - if (error) - return error; - - if ((wrbuf[0] ^ wrbuf[1] ^ rdbuf[0]) != rdbuf[1]) { - dev_err(&tsdata->client->dev, - "crc error: 0x%02x expected, got 0x%02x\n", - wrbuf[0] ^ wrbuf[1] ^ rdbuf[0], - rdbuf[1]); - return -EIO; - } - break; - - case EDT_M09: - case EDT_M12: - case GENERIC_FT: - wrbuf[0] = addr; - error = edt_ft5x06_ts_readwrite(tsdata->client, 1, - wrbuf, 1, rdbuf); - if (error) - return error; - break; - - default: - return -EINVAL; - } - - return rdbuf[0]; -} - struct edt_ft5x06_attribute { struct device_attribute dattr; size_t field_offset; From dc254645b70fabb1b7535506f481e3a5c6c7fee8 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 13 Dec 2017 14:52:51 +0100 Subject: [PATCH 56/93] input: edt-ft5x06: add support for the ft5426 controller The current ft5x06 driver happily supports the ft5426 controller chip. Lets add a compatible for it. Signed-off-by: Olliver Schinagl --- .../devicetree/bindings/input/touchscreen/edt-ft5x06.txt | 1 + drivers/input/touchscreen/edt-ft5x06.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt index 025cf8c9324ac3..a86b3dd13cb27d 100644 --- a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt +++ b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt @@ -18,6 +18,7 @@ Required properties: - compatible: "edt,edt-ft5206" or: "edt,edt-ft5306" or: "edt,edt-ft5406" + or: "edt,edt-ft5426" or: "edt,edt-ft5506" or: "focaltech,ft6236" diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 8564f2d1bf46ba..9f1ba83761cb23 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -1154,6 +1154,7 @@ static const struct edt_i2c_chip_data edt_ft6236_data = { static const struct i2c_device_id edt_ft5x06_ts_id[] = { { .name = "edt-ft5x06", .driver_data = (long)&edt_ft5x06_data }, + { .name = "edt-ft5426", .driver_data = (long)&edt_ft5506_data }, { .name = "edt-ft5506", .driver_data = (long)&edt_ft5506_data }, /* Note no edt- prefix for compatibility with the ft6236.c driver */ { .name = "ft6236", .driver_data = (long)&edt_ft6236_data }, @@ -1166,6 +1167,7 @@ static const struct of_device_id edt_ft5x06_of_match[] = { { .compatible = "edt,edt-ft5206", .data = &edt_ft5x06_data }, { .compatible = "edt,edt-ft5306", .data = &edt_ft5x06_data }, { .compatible = "edt,edt-ft5406", .data = &edt_ft5x06_data }, + { .compatible = "edt,edt-ft5426", .data = &edt_ft5506_data }, { .compatible = "edt,edt-ft5506", .data = &edt_ft5506_data }, /* Note focaltech vendor prefix for compatibility with ft6236.c */ { .compatible = "focaltech,ft6236", .data = &edt_ft6236_data }, From 1d27b307f0082ce8dc129bcc85bb578fb0705e02 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 13 Dec 2017 14:57:52 +0100 Subject: [PATCH 57/93] input: edt-ft5x06: cleanup headers Alphabetize and add missing headers to the edt-ft5x06 driver. No code changes where done in this commit. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 9f1ba83761cb23..c406867bb757bf 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -25,20 +25,28 @@ * http://www.glyn.com/Products/Displays */ -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #define WORK_REGISTER_THRESHOLD 0x00 #define WORK_REGISTER_REPORT_RATE 0x08 From 85912c6f82f82db6bc384761e78dae0cbb28a8c8 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Wed, 13 Dec 2017 15:42:27 +0100 Subject: [PATCH 58/93] input: edt-ft5x06: only enable the irq when needed The input framework has callbacks for when an input device gets opened/closed to do certain actions. Use these to enable/disable interrupts when they are not in use. This can then be improved in the future by doing power savings when the device is not in use. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index c406867bb757bf..222a786821f072 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -778,6 +778,22 @@ edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata) #endif /* CONFIG_DEBUGFS */ +static int edt_ft5x06_open(struct input_dev *dev) +{ + struct edt_ft5x06_ts_data *tsdata = input_get_drvdata(dev); + + enable_irq(tsdata->client->irq); + + return 0; +} + +static void edt_ft5x06_close(struct input_dev *dev) +{ + struct edt_ft5x06_ts_data *tsdata = input_get_drvdata(dev); + + disable_irq(tsdata->client->irq); +} + static int edt_ft5x06_ts_identify(struct i2c_client *client, struct edt_ft5x06_ts_data *tsdata, char *fw_version) @@ -1056,6 +1072,8 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, input->name = tsdata->name; input->id.bustype = BUS_I2C; input->dev.parent = &client->dev; + input->open = edt_ft5x06_open; + input->close = edt_ft5x06_close; if (tsdata->version == EDT_M06 || tsdata->version == EDT_M09 || @@ -1081,6 +1099,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, return error; } + input_set_drvdata(input, tsdata); i2c_set_clientdata(client, tsdata); irq_flags = irq_get_trigger_type(client->irq); @@ -1095,6 +1114,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); return error; } + disable_irq(client->irq); error = devm_device_add_group(&client->dev, &edt_ft5x06_attr_group); if (error) From 8a10c29846ea6403936a7f7ce566978f8dc6a54f Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 14 Dec 2017 09:55:44 +0100 Subject: [PATCH 59/93] input: edt-ft5x06: fix some whitespace/ident issues This patch just cleans up some ident/whitespacing issues. No code changes where performed. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 222a786821f072..7059bcc8761b29 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -657,7 +657,8 @@ DEFINE_SIMPLE_ATTRIBUTE(debugfs_mode_fops, edt_ft5x06_debugfs_mode_get, edt_ft5x06_debugfs_mode_set, "%llu\n"); static ssize_t edt_ft5x06_debugfs_raw_data_read(struct file *file, - char __user *buf, size_t count, loff_t *off) + char __user *buf, + size_t count, loff_t *off) { struct edt_ft5x06_ts_data *tsdata = file->private_data; struct i2c_client *client = tsdata->client; @@ -795,17 +796,18 @@ static void edt_ft5x06_close(struct input_dev *dev) } static int edt_ft5x06_ts_identify(struct i2c_client *client, - struct edt_ft5x06_ts_data *tsdata, - char *fw_version) + struct edt_ft5x06_ts_data *tsdata, + char *fw_version) { u8 rdbuf[EDT_NAME_LEN]; char *p; int error; char *model_name = tsdata->name; - /* see what we find if we assume it is a M06 * - * if we get less than EDT_NAME_LEN, we don't want - * to have garbage in there + /* + * See what we find if we assume it is a M06. + * If we get less than EDT_NAME_LEN, we don't want + * to have garbage in there. */ memset(rdbuf, 0, sizeof(rdbuf)); error = edt_ft5x06_ts_readwrite(client, 1, "\xBB", @@ -813,7 +815,8 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, if (error) return error; - /* Probe content for something consistent. + /* + * Probe content for something consistent. * M06 starts with a response byte, M12 gives the data directly. * M09/Generic does not provide model number information. */ @@ -846,9 +849,10 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, strlcpy(model_name, rdbuf, EDT_NAME_LEN); strlcpy(fw_version, p ? p : "", EDT_NAME_LEN); } else { - /* If it is not an EDT M06/M12 touchscreen, then the model + /* + * If it is not an EDT M06/M12 touchscreen, then the model * detection is a bit hairy. The different ft5x06 - * firmares around don't reliably implement the + * firmware's around don't reliably implement the * identification registers. Well, we'll take a shot. * * The main difference between generic focaltec based @@ -869,7 +873,8 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, if (error) return error; - /* This "model identification" is not exact. Unfortunately + /* + * This "model identification" is not exact. Unfortunately * not all firmwares for the ft5x06 put useful values in * the identification registers. */ @@ -988,7 +993,7 @@ edt_ft5x06_ts_set_regs(struct edt_ft5x06_ts_data *tsdata) } static int edt_ft5x06_ts_probe(struct i2c_client *client, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { const struct edt_i2c_chip_data *chip_data; struct edt_ft5x06_ts_data *tsdata; @@ -1093,7 +1098,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, touchscreen_parse_properties(input, true, &tsdata->prop); error = input_mt_init_slots(input, tsdata->max_support_points, - INPUT_MT_DIRECT); + INPUT_MT_DIRECT); if (error) { dev_err(&client->dev, "Unable to init MT slots.\n"); return error; From 0088d4a5a913e20414ab9563d8ee650805ad53fc Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 14 Dec 2017 10:04:23 +0100 Subject: [PATCH 60/93] input: edt-ft5x06: minor consistency cleanup/line length reduction The struct device is often referenced in the probe function via &client->dev. We can shorten this by introducing a compile time removed pointer to be able to just type 'dev'. This reduces some of our longer function line lengths improving readability just a little bit. While touching said lines, (mostly dev_*() print strings) make the strings more consistent (with itself and the kernel output) by dropping caps at the start and periods at the end having at least a consistent style within the same function. No logical changes where performed. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 57 +++++++++++++------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 7059bcc8761b29..49a7d5101778f4 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -996,45 +996,45 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) { const struct edt_i2c_chip_data *chip_data; + struct device *dev = &client->dev; struct edt_ft5x06_ts_data *tsdata; struct input_dev *input; unsigned long irq_flags; int error; char fw_version[EDT_NAME_LEN]; - dev_dbg(&client->dev, "probing for EDT FT5x06 I2C\n"); + dev_dbg(dev, "probing for EDT FT5x06 I2C\n"); - tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL); + tsdata = devm_kzalloc(dev, sizeof(*tsdata), GFP_KERNEL); if (!tsdata) { - dev_err(&client->dev, "failed to allocate driver data.\n"); + dev_err(dev, "failed to allocate driver data\n"); return -ENOMEM; } - chip_data = of_device_get_match_data(&client->dev); + chip_data = of_device_get_match_data(dev); if (!chip_data) chip_data = (const struct edt_i2c_chip_data *)id->driver_data; if (!chip_data || !chip_data->max_support_points) { - dev_err(&client->dev, "invalid or missing chip data\n"); + dev_err(dev, "invalid or missing chip data\n"); return -EINVAL; } tsdata->max_support_points = chip_data->max_support_points; - tsdata->reset_gpio = devm_gpiod_get_optional(&client->dev, + tsdata->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(tsdata->reset_gpio)) { error = PTR_ERR(tsdata->reset_gpio); - dev_err(&client->dev, - "Failed to request GPIO reset pin, error %d\n", error); + dev_err(dev, "failed to request GPIO reset pin, error %d\n", + error); return error; } - tsdata->wake_gpio = devm_gpiod_get_optional(&client->dev, - "wake", GPIOD_OUT_LOW); + tsdata->wake_gpio = devm_gpiod_get_optional(dev, "wake", GPIOD_OUT_LOW); if (IS_ERR(tsdata->wake_gpio)) { error = PTR_ERR(tsdata->wake_gpio); - dev_err(&client->dev, - "Failed to request GPIO wake pin, error %d\n", error); + dev_err(dev, "failed to request GPIO wake pin, error %d\n", + error); return error; } @@ -1049,9 +1049,9 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, msleep(300); } - input = devm_input_allocate_device(&client->dev); + input = devm_input_allocate_device(dev); if (!input) { - dev_err(&client->dev, "failed to allocate input device.\n"); + dev_err(dev, "failed to allocate input device\n"); return -ENOMEM; } @@ -1062,21 +1062,20 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, error = edt_ft5x06_ts_identify(client, tsdata, fw_version); if (error) { - dev_err(&client->dev, "touchscreen probe failed\n"); + dev_err(dev, "touchscreen probe failed\n"); return error; } edt_ft5x06_ts_set_regs(tsdata); - edt_ft5x06_ts_get_defaults(&client->dev, tsdata); + edt_ft5x06_ts_get_defaults(dev, tsdata); edt_ft5x06_ts_get_parameters(tsdata); - dev_dbg(&client->dev, - "Model \"%s\", Rev. \"%s\", %dx%d sensors\n", + dev_dbg(dev, "model \"%s\", Rev. \"%s\", %dx%d sensors\n", tsdata->name, fw_version, tsdata->num_x, tsdata->num_y); input->name = tsdata->name; input->id.bustype = BUS_I2C; - input->dev.parent = &client->dev; + input->dev.parent = dev; input->open = edt_ft5x06_open; input->close = edt_ft5x06_close; @@ -1100,7 +1099,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, error = input_mt_init_slots(input, tsdata->max_support_points, INPUT_MT_DIRECT); if (error) { - dev_err(&client->dev, "Unable to init MT slots.\n"); + dev_err(dev, "unable to init MT slots\n"); return error; } @@ -1112,16 +1111,16 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, irq_flags = IRQF_TRIGGER_FALLING; irq_flags |= IRQF_ONESHOT; - error = devm_request_threaded_irq(&client->dev, client->irq, - NULL, edt_ft5x06_ts_isr, irq_flags, - client->name, tsdata); + error = devm_request_threaded_irq(dev, client->irq, NULL, + edt_ft5x06_ts_isr, irq_flags, + client->name, tsdata); if (error) { - dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); + dev_err(dev, "unable to request touchscreen IRQ\n"); return error; } disable_irq(client->irq); - error = devm_device_add_group(&client->dev, &edt_ft5x06_attr_group); + error = devm_device_add_group(dev, &edt_ft5x06_attr_group); if (error) return error; @@ -1129,11 +1128,11 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, if (error) return error; - edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(&client->dev)); - device_init_wakeup(&client->dev, 1); + edt_ft5x06_ts_prepare_debugfs(tsdata, dev_driver_string(dev)); + device_init_wakeup(dev, 1); - dev_dbg(&client->dev, - "EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d.\n", + dev_dbg(dev, + "EDT FT5x06 initialized: IRQ %d, WAKE pin %d, Reset pin %d\n", client->irq, tsdata->wake_gpio ? desc_to_gpio(tsdata->wake_gpio) : -1, tsdata->reset_gpio ? desc_to_gpio(tsdata->reset_gpio) : -1); From 334186049c8e40340826d0393ec9f0cc4a2fac43 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 14 Dec 2017 11:02:00 +0100 Subject: [PATCH 61/93] input: edt-ft5x06: shorten defines Drop the long _REGISTER_ string in the M09_ macro's. It makes our register defines really long and doesn't add anything. By shortening the defines we can introduce more macro's that are more descriptive and potentially longer. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 35 ++++++++++++-------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 49a7d5101778f4..754455eab58e3e 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -55,11 +55,11 @@ #define WORK_REGISTER_NUM_X 0x33 #define WORK_REGISTER_NUM_Y 0x34 -#define M09_REGISTER_THRESHOLD 0x80 -#define M09_REGISTER_GAIN 0x92 -#define M09_REGISTER_OFFSET 0x93 -#define M09_REGISTER_NUM_X 0x94 -#define M09_REGISTER_NUM_Y 0x95 +#define M09_THRESHOLD 0x80 +#define M09_GAIN 0x92 +#define M09_OFFSET 0x93 +#define M09_NUM_X 0x94 +#define M09_NUM_Y 0x95 #define NO_REGISTER 0xff @@ -488,14 +488,11 @@ static ssize_t edt_ft5x06_setting_store(struct device *dev, /* m06, m09: range 0-31, m12: range 0-5 */ static EDT_ATTR(gain, S_IWUSR | S_IRUGO, WORK_REGISTER_GAIN, - M09_REGISTER_GAIN, 0, 31); -/* m06, m09: range 0-31, m12: range 0-16 */ + M09_GAIN, 0, 31); static EDT_ATTR(offset, S_IWUSR | S_IRUGO, WORK_REGISTER_OFFSET, - M09_REGISTER_OFFSET, 0, 31); -/* m06: range 20 to 80, m09: range 0 to 30, m12: range 1 to 255... */ + M09_OFFSET, 0, 31); static EDT_ATTR(threshold, S_IWUSR | S_IRUGO, WORK_REGISTER_THRESHOLD, - M09_REGISTER_THRESHOLD, 0, 255); -/* m06: range 3 to 14, m12: (0x64: 100Hz) */ + M09_THRESHOLD, 0, 80); static EDT_ATTR(report_rate, S_IWUSR | S_IRUGO, WORK_REGISTER_REPORT_RATE, NO_REGISTER, 0, 255); @@ -975,19 +972,19 @@ edt_ft5x06_ts_set_regs(struct edt_ft5x06_ts_data *tsdata) case EDT_M09: case EDT_M12: - reg_addr->reg_threshold = M09_REGISTER_THRESHOLD; + reg_addr->reg_threshold = M09_THRESHOLD; reg_addr->reg_report_rate = NO_REGISTER; - reg_addr->reg_gain = M09_REGISTER_GAIN; - reg_addr->reg_offset = M09_REGISTER_OFFSET; - reg_addr->reg_num_x = M09_REGISTER_NUM_X; - reg_addr->reg_num_y = M09_REGISTER_NUM_Y; + reg_addr->reg_gain = M09_GAIN; + reg_addr->reg_offset = M09_OFFSET; + reg_addr->reg_num_x = M09_NUM_X; + reg_addr->reg_num_y = M09_NUM_Y; break; case GENERIC_FT: /* this is a guesswork */ - reg_addr->reg_threshold = M09_REGISTER_THRESHOLD; - reg_addr->reg_gain = M09_REGISTER_GAIN; - reg_addr->reg_offset = M09_REGISTER_OFFSET; + reg_addr->reg_threshold = M09_THRESHOLD; + reg_addr->reg_gain = M09_GAIN; + reg_addr->reg_offset = M09_OFFSET; break; } } From 94337c293d4090ac129da341585320d4aafd3910 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 15 Dec 2017 14:12:33 +0100 Subject: [PATCH 62/93] input: edt-ft5x06: use less magic and more defines We can simply add some more defines to remove some magic values and thus create more readable code. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 136 ++++++++++++++++++------- 1 file changed, 99 insertions(+), 37 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 754455eab58e3e..af17f46f76cacc 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -25,6 +25,7 @@ * http://www.glyn.com/Products/Displays */ +#include #include #include #include @@ -48,6 +49,11 @@ #include #include +#define NIBBLE_LOWER(x) \ + ((uint8_t)((x) & GENMASK(3, 0))) +#define NIBBLE_UPPER(x) \ + ((uint8_t)(((x) & GENMASK(7, 4)) >> 4)) + #define WORK_REGISTER_THRESHOLD 0x00 #define WORK_REGISTER_REPORT_RATE 0x08 #define WORK_REGISTER_GAIN 0x30 @@ -55,28 +61,69 @@ #define WORK_REGISTER_NUM_X 0x33 #define WORK_REGISTER_NUM_Y 0x34 +#define M06_TOUCH_REPORT 0x05 +#define M06_TOUCH_REPORT_LEN 4 +#define M06_TOUCH_REPORT_CRC_LEN 1 +#define M06_TOUCH_REPORT_HEADER_H 0x00 +#define M06_TOUCH_REPORT_HEADER_L 0x01 +#define M06_TOUCH_REPORT_DATALEN 0x02 +#define M06_TOUCH_REPORT_MAGIC 0xaa + +#define M09_TD_STATUS 0x02 +#define M09_TOUCH_REPORT 0x03 +#define M09_TOUCH_REPORT_LEN 6 + +#define EDT_TOUCH_XH_EVENT 0x00 +#define EDT_TOUCH_XH_MASK GENMASK(3, 0) +#define EDT_TOUCH_EVENT_DOWN 0x00 +#define EDT_TOUCH_EVENT_UP BIT(6) +#define EDT_TOUCH_EVENT_ON BIT(7) +#define EDT_TOUCH_EVENT_RESERVED \ + (EDT_TOUCH_EVENT_UP | EDT_TOUCH_EVENT_ON) +#define EDT_TOUCH_EVENT_FLAG_MASK GENMASK(7, 6) +#define EDT_TOUCH_XL 0x01 +#define EDT_TOUCH_ID_YH 0x02 +#define EDT_TOUCH_YH_MASK GENMASK(3, 0) +#define EDT_TOUCH_ID_MASK GENMASK(7, 4) +#define EDT_TOUCH_ID_OFFSET 4 +#define EDT_TOUCH_YL 0x03 + #define M09_THRESHOLD 0x80 #define M09_GAIN 0x92 #define M09_OFFSET 0x93 #define M09_NUM_X 0x94 #define M09_NUM_Y 0x95 +#define EDT_FW_VERSION 0xa6 +#define EDT_PANEL_ID 0xa8 +#define EDT_PANEL_ID_EP0350M09 0x35 +#define EDT_PANEL_ID_EP0430M09 0x43 +#define EDT_PANEL_ID_EP0500M09 0x50 +#define EDT_PANEL_ID_EP0570M09 0x57 +#define EDT_PANEL_ID_EP0700M09 0x70 +#define EDT_PANEL_ID_EP1010ML00 0xa1 +#define SOL_PANEL_ID_GKTW50SCED1R0 0x5a + +#define EDT_CUSTOM_DATA 0xbb +#define EDT_NAME_LEN 23 + +#define M06_TOUCH_REPORT_REQ 0xf9 + #define NO_REGISTER 0xff #define WORK_REGISTER_OPMODE 0x3c #define FACTORY_REGISTER_OPMODE 0x01 -#define TOUCH_EVENT_DOWN 0x00 -#define TOUCH_EVENT_UP 0x01 -#define TOUCH_EVENT_ON 0x02 -#define TOUCH_EVENT_RESERVED 0x03 - -#define EDT_NAME_LEN 23 #define EDT_SWITCH_MODE_RETRIES 10 #define EDT_SWITCH_MODE_DELAY 5 /* msec */ #define EDT_RAW_DATA_RETRIES 100 #define EDT_RAW_DATA_DELAY 1000 /* usec */ +#define EDT_TOUCH_REPORT_MAX_SIZE ((10 * M09_TOUCH_REPORT_LEN) + \ + M09_TOUCH_REPORT_LEN + \ + M06_TOUCH_REPORT + \ + M06_TOUCH_REPORT_CRC_LEN) + enum edt_ver { EDT_M06, EDT_M09, @@ -253,25 +300,25 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) struct edt_ft5x06_ts_data *tsdata = dev_id; struct device *dev = &tsdata->client->dev; u8 cmd; - u8 rdbuf[63]; + u8 rdbuf[EDT_TOUCH_REPORT_MAX_SIZE]; int i, type, x, y, id; int offset, tplen, datalen, crclen; int error; switch (tsdata->version) { case EDT_M06: - cmd = 0xf9; /* tell the controller to send touch data */ - offset = 5; /* where the actual touch data starts */ - tplen = 4; /* data comes in so called frames */ - crclen = 1; /* length of the crc data */ + cmd = M06_TOUCH_REPORT_REQ; /* tell the controller to send touch data */ + offset = M06_TOUCH_REPORT; /* where the actual touch data starts */ + tplen = M06_TOUCH_REPORT_LEN; /* data comes in so called frames */ + crclen = M06_TOUCH_REPORT_CRC_LEN; /* length of the crc data */ break; case EDT_M09: case EDT_M12: case GENERIC_FT: cmd = 0x0; - offset = 3; - tplen = 6; + offset = M09_TOUCH_REPORT; + tplen = M09_TOUCH_REPORT_LEN; crclen = 0; break; @@ -293,12 +340,15 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) /* M09/M12 does not send header or CRC */ if (tsdata->version == EDT_M06) { - if (rdbuf[0] != 0xaa || rdbuf[1] != 0xaa || - rdbuf[2] != datalen) { + if (rdbuf[M06_TOUCH_REPORT_HEADER_H] != M06_TOUCH_REPORT_MAGIC || + rdbuf[M06_TOUCH_REPORT_HEADER_L] != M06_TOUCH_REPORT_MAGIC || + rdbuf[M06_TOUCH_REPORT_DATALEN] != datalen) { dev_err_ratelimited(dev, - "Unexpected header: %02x%02x%02x!\n", - rdbuf[0], rdbuf[1], rdbuf[2]); - goto out; + "Unexpected header: %02x%02x%02x!\n", + rdbuf[M06_TOUCH_REPORT_HEADER_H], + rdbuf[M06_TOUCH_REPORT_HEADER_L], + rdbuf[M06_TOUCH_REPORT_DATALEN]); + goto: out; } if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, datalen)) @@ -309,19 +359,27 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) u8 *buf = &rdbuf[i * tplen + offset]; bool down; - type = buf[0] >> 6; + type = buf[EDT_TOUCH_XH_EVENT] & EDT_TOUCH_EVENT_FLAG_MASK; /* ignore Reserved events */ - if (type == TOUCH_EVENT_RESERVED) + if (type == EDT_TOUCH_EVENT_RESERVED) continue; /* M06 sometimes sends bogus coordinates in TOUCH_DOWN */ - if (tsdata->version == EDT_M06 && type == TOUCH_EVENT_DOWN) + if (tsdata->version == EDT_M06 && type == EDT_TOUCH_EVENT_DOWN) continue; - x = ((buf[0] << 8) | buf[1]) & 0x0fff; - y = ((buf[2] << 8) | buf[3]) & 0x0fff; - id = (buf[2] >> 4) & 0x0f; - down = type != TOUCH_EVENT_UP; + x = buf[EDT_TOUCH_XH_EVENT] & EDT_TOUCH_XH_MASK; + x <<= BITS_PER_BYTE; + x |= buf[EDT_TOUCH_XL]; + + y = buf[EDT_TOUCH_ID_YH] & EDT_TOUCH_YH_MASK; + y <<= BITS_PER_BYTE; + y |= buf[EDT_TOUCH_YL]; + + id = (buf[EDT_TOUCH_ID_YH] & EDT_TOUCH_ID_MASK); + id >>= EDT_TOUCH_ID_OFFSET; + + down = type != EDT_TOUCH_EVENT_UP; input_mt_slot(tsdata->input, id); input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down); @@ -797,6 +855,7 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, char *fw_version) { u8 rdbuf[EDT_NAME_LEN]; + u8 cmd; char *p; int error; char *model_name = tsdata->name; @@ -807,7 +866,8 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, * to have garbage in there. */ memset(rdbuf, 0, sizeof(rdbuf)); - error = edt_ft5x06_ts_readwrite(client, 1, "\xBB", + cmd = EDT_CUSTOM_DATA; + error = edt_ft5x06_ts_readwrite(client, sizeof(cmd), &cmd, EDT_NAME_LEN - 1, rdbuf); if (error) return error; @@ -858,14 +918,16 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, */ tsdata->version = GENERIC_FT; - error = edt_ft5x06_ts_readwrite(client, 1, "\xA6", + cmd = EDT_FW_VERSION; + error = edt_ft5x06_ts_readwrite(client, sizeof(cmd), &cmd, 2, rdbuf); if (error) return error; strlcpy(fw_version, rdbuf, 2); - error = edt_ft5x06_ts_readwrite(client, 1, "\xA8", + cmd = EDT_PANEL_ID; + error = edt_ft5x06_ts_readwrite(client, sizeof(cmd), &cmd, 1, rdbuf); if (error) return error; @@ -876,21 +938,21 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client, * the identification registers. */ switch (rdbuf[0]) { - case 0x35: /* EDT EP0350M09 */ - case 0x43: /* EDT EP0430M09 */ - case 0x50: /* EDT EP0500M09 */ - case 0x57: /* EDT EP0570M09 */ - case 0x70: /* EDT EP0700M09 */ + case EDT_PANEL_ID_EP0350M09: + case EDT_PANEL_ID_EP0430M09: + case EDT_PANEL_ID_EP0500M09: + case EDT_PANEL_ID_EP0570M09: + case EDT_PANEL_ID_EP0700M09: tsdata->version = EDT_M09; snprintf(model_name, EDT_NAME_LEN, "EP0%i%i0M09", - rdbuf[0] >> 4, rdbuf[0] & 0x0F); + NIBBLE_UPPER(rdbuf[0]), NIBBLE_LOWER(rdbuf[0])); break; - case 0xa1: /* EDT EP1010ML00 */ + case EDT_PANEL_ID_EP1010ML00: tsdata->version = EDT_M09; snprintf(model_name, EDT_NAME_LEN, "EP%i%i0ML00", - rdbuf[0] >> 4, rdbuf[0] & 0x0F); + NIBBLE_UPPER(rdbuf[0]), NIBBLE_LOWER(rdbuf[0])); break; - case 0x5a: /* Solomon Goldentek Display */ + case SOL_PANEL_ID_GKTW50SCED1R0: /* Solomon Goldentek Display */ snprintf(model_name, EDT_NAME_LEN, "GKTW50SCED1R0"); break; default: From 0bdd8a5cc00dd7796c6123a56a94bc8bf46e836e Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 15 Dec 2017 14:13:34 +0100 Subject: [PATCH 63/93] input: edt-ft5x06: silence deferral error Currently when we get a deferral request on an optional GPIO, we print out a failure message making the user think something bad has happened. Instead, as commonly done, silence the -EPROBE_DEFER message as it is an acceptable 'error'. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index af17f46f76cacc..7cc773a0552dd8 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -1084,16 +1084,18 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, "reset", GPIOD_OUT_HIGH); if (IS_ERR(tsdata->reset_gpio)) { error = PTR_ERR(tsdata->reset_gpio); - dev_err(dev, "failed to request GPIO reset pin, error %d\n", - error); + if (error != -EPROBE_DEFER) + dev_err(dev, "failed to request GPIO reset pin: %d\n", + error); return error; } tsdata->wake_gpio = devm_gpiod_get_optional(dev, "wake", GPIOD_OUT_LOW); if (IS_ERR(tsdata->wake_gpio)) { error = PTR_ERR(tsdata->wake_gpio); - dev_err(dev, "failed to request GPIO wake pin, error %d\n", - error); + if (error != -EPROBE_DEFER) + dev_err(dev, "failed to request GPIO wake pin: %d\n", + error); return error; } From 66ab38d850be7167bb29a0cb5c22a9b855e32e3a Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 10:36:45 +0100 Subject: [PATCH 64/93] input: edt-ft5x06: sanity check on input It may happen, that tsdata or tsdata->input are NULL. One observed example was that when requesting the IRQ, but not having all structures setup properly. Add a simple sanity check for NULL, just in case. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 7cc773a0552dd8..8ba44a4b405bc3 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -305,6 +305,9 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) int offset, tplen, datalen, crclen; int error; + if ((!tsdata) || (!tsdata->input)) + return; + switch (tsdata->version) { case EDT_M06: cmd = M06_TOUCH_REPORT_REQ; /* tell the controller to send touch data */ From 76762fabd858e88cfcdd3483cd7176f49894d47a Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 10:39:18 +0100 Subject: [PATCH 65/93] input: edt-ft5x06: fix multi-touch handling Multi-touch handling is currently incorrectly handled by the edt-ft5x06 driver. According to Documentation/input/multi-touch-protocol.rst, we are supposed to: ABS_MT_SLOT 0 ABS_MT_TRACKING_ID 45 ABS_MT_POSITION_X x[0] ABS_MT_POSITION_Y y[0] ABS_MT_SLOT 1 ABS_MT_TRACKING_ID 46 ABS_MT_POSITION_X x[1] ABS_MT_POSITION_Y y[1] SYN_REPORT and: Here is the sequence after lifting the contact in slot 0:: ABS_MT_TRACKING_ID -1 SYN_REPORT What happens in our driver however, is: ABS_MT_SLOT 45 ABS_MT_POSITION_X x[0] ABS_MT_POSITION_Y y[0] ABS_MT_SLOT 46 ABS_MT_POSITION_X x[1] ABS_MT_POSITION_Y y[1] SYN_REPORT and for lifting nothing is done. This is obviously wrong, as we are allocating a slot based on the tracking id, not based on the input (finger) and worse, never reset tracking id to -1 to 'free' them. Refactor the ISR to follow the spec by allocating a slot based on finger and set/clear the tracking ID once the finger is no longer down. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 8ba44a4b405bc3..cddde273d98b95 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -362,7 +362,16 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) u8 *buf = &rdbuf[i * tplen + offset]; bool down; + input_mt_slot(tsdata->input, i); + type = buf[EDT_TOUCH_XH_EVENT] & EDT_TOUCH_EVENT_FLAG_MASK; + + down = type != EDT_TOUCH_EVENT_UP; + if (!down) { + input_report_abs(tsdata->input, ABS_MT_TRACKING_ID, -1); + continue; + } + /* ignore Reserved events */ if (type == EDT_TOUCH_EVENT_RESERVED) continue; @@ -371,6 +380,12 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) if (tsdata->version == EDT_M06 && type == EDT_TOUCH_EVENT_DOWN) continue; + input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down); + + id = (buf[EDT_TOUCH_ID_YH] & EDT_TOUCH_ID_MASK); + id >>= EDT_TOUCH_ID_OFFSET; + input_report_abs(tsdata->input, ABS_MT_TRACKING_ID, id); + x = buf[EDT_TOUCH_XH_EVENT] & EDT_TOUCH_XH_MASK; x <<= BITS_PER_BYTE; x |= buf[EDT_TOUCH_XL]; @@ -379,17 +394,6 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) y <<= BITS_PER_BYTE; y |= buf[EDT_TOUCH_YL]; - id = (buf[EDT_TOUCH_ID_YH] & EDT_TOUCH_ID_MASK); - id >>= EDT_TOUCH_ID_OFFSET; - - down = type != EDT_TOUCH_EVENT_UP; - - input_mt_slot(tsdata->input, id); - input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER, down); - - if (!down) - continue; - touchscreen_report_pos(tsdata->input, &tsdata->prop, x, y, true); } From a554a3a5622aec68a481dee7e0113dc1fb951669 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 11:07:40 +0100 Subject: [PATCH 66/93] input: edt-ft5x06: take into account the number of fingers It may happen that we miss a touch !down event, for example the interrupt fires, but we are to late to read the correct data. When this happens, we may wrongfully set/clear ABS_MT_TRACKING_ID to -1. The M09 firmware actually tells us how many fingers are being touched. By using that, we also thus know how many events to focus on and to clear unused tracking id's. This also has as a boon that we save some processing time by not evaluating the registers for unused touches. Note: this should not be used to reduce the number of bytes read, e.g. read the number of touches register, followed by a read for the actual registers, as that would result in a non-atomic read, and our data could be out of sync. E.g. we are told to 4 inputs have been pressed, but by the time we actually read these registers, 5 inputs are actually being touched. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index cddde273d98b95..d5a19b4862af6b 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -302,7 +302,7 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) u8 cmd; u8 rdbuf[EDT_TOUCH_REPORT_MAX_SIZE]; int i, type, x, y, id; - int offset, tplen, datalen, crclen; + int offset, touch_cnt, tplen, datalen, crclen; int error; if ((!tsdata) || (!tsdata->input)) @@ -310,6 +310,7 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) switch (tsdata->version) { case EDT_M06: + touch_cnt = tsdata->max_support_points; cmd = M06_TOUCH_REPORT_REQ; /* tell the controller to send touch data */ offset = M06_TOUCH_REPORT; /* where the actual touch data starts */ tplen = M06_TOUCH_REPORT_LEN; /* data comes in so called frames */ @@ -319,6 +320,7 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) case EDT_M09: case EDT_M12: case GENERIC_FT: + touch_cnt = 0; cmd = 0x0; offset = M09_TOUCH_REPORT; tplen = M09_TOUCH_REPORT_LEN; @@ -356,6 +358,8 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, datalen)) goto out; + } else { + touch_cnt = rdbuf[M09_TD_STATUS]; } for (i = 0; i < tsdata->max_support_points; i++) { @@ -367,7 +371,7 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) type = buf[EDT_TOUCH_XH_EVENT] & EDT_TOUCH_EVENT_FLAG_MASK; down = type != EDT_TOUCH_EVENT_UP; - if (!down) { + if (!down || i > touch_cnt) { input_report_abs(tsdata->input, ABS_MT_TRACKING_ID, -1); continue; } From 32842c08fb4d95c8cfd9a9203c8370fe0a0ebe18 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 18 Dec 2017 11:16:12 +0100 Subject: [PATCH 67/93] input: edt-ft5x06: add polldev as an option The edt-ft5x06 is not required to be used with an interrupt. We can just as easily poll the edt-ft5x06 using the polldev framework. This is needed as some electrical designs may omit the IRQ line. Signed-off-by: Olliver Schinagl --- .../bindings/input/touchscreen/edt-ft5x06.txt | 5 +- drivers/input/touchscreen/edt-ft5x06.c | 146 +++++++++++++----- 2 files changed, 114 insertions(+), 37 deletions(-) diff --git a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt index a86b3dd13cb27d..ab01900ea73ad2 100644 --- a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt +++ b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.txt @@ -23,12 +23,12 @@ Required properties: or: "focaltech,ft6236" - reg: I2C slave address of the chip (0x38) + +Optional properties: - interrupt-parent: a phandle pointing to the interrupt controller serving the interrupt for this chip - interrupts: interrupt specification for the touchdetect interrupt - -Optional properties: - reset-gpios: GPIO specification for the RESET input - wake-gpios: GPIO specification for the WAKE input @@ -45,6 +45,7 @@ Optional properties: - offset: allows setting the edge compensation in the range from 0 to 31. + - poll-interval: Poll interval time in milliseconds - touchscreen-size-x : See touchscreen.txt - touchscreen-size-y : See touchscreen.txt - touchscreen-fuzz-x : See touchscreen.txt diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index d5a19b4862af6b..068bc4f4076433 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -34,12 +34,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -47,6 +49,7 @@ #include #include #include +#include #include #define NIBBLE_LOWER(x) \ @@ -131,6 +134,11 @@ enum edt_ver { GENERIC_FT, }; +enum readout_mode { + EDT_READOUT_MODE_POLL, + EDT_READOUT_MODE_IRQ, +}; + struct edt_reg_addr { int reg_threshold; int reg_report_rate; @@ -143,6 +151,9 @@ struct edt_reg_addr { struct edt_ft5x06_ts_data { struct i2c_client *client; struct input_dev *input; +#if IS_ENABLED(CONFIG_INPUT_POLLDEV) + struct input_polled_dev *polldev; +#endif struct touchscreen_properties prop; u16 num_x; u16 num_y; @@ -295,9 +306,8 @@ static bool edt_ft5x06_ts_check_crc(struct edt_ft5x06_ts_data *tsdata, return true; } -static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) +static void edt_ft5x06_report(struct edt_ft5x06_ts_data *tsdata) { - struct edt_ft5x06_ts_data *tsdata = dev_id; struct device *dev = &tsdata->client->dev; u8 cmd; u8 rdbuf[EDT_TOUCH_REPORT_MAX_SIZE]; @@ -328,7 +338,7 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) break; default: - goto out; + return; } memset(rdbuf, 0, sizeof(rdbuf)); @@ -340,7 +350,7 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) if (error) { dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n", error); - goto out; + return; } /* M09/M12 does not send header or CRC */ @@ -353,11 +363,11 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) rdbuf[M06_TOUCH_REPORT_HEADER_H], rdbuf[M06_TOUCH_REPORT_HEADER_L], rdbuf[M06_TOUCH_REPORT_DATALEN]); - goto: out; + return; } if (!edt_ft5x06_ts_check_crc(tsdata, rdbuf, datalen)) - goto out; + return; } else { touch_cnt = rdbuf[M09_TD_STATUS]; } @@ -404,11 +414,24 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) input_mt_report_pointer_emulation(tsdata->input, true); input_sync(tsdata->input); +} + +static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) +{ + struct edt_ft5x06_ts_data *tsdata = dev_id; + + edt_ft5x06_report(tsdata); -out: return IRQ_HANDLED; } +static void edt_ft5x06_poll(struct input_polled_dev *polldev) +{ + struct edt_ft5x06_ts_data *tsdata = polldev->private; + + edt_ft5x06_report(tsdata); +} + struct edt_ft5x06_attribute { struct device_attribute dattr; size_t field_offset; @@ -1028,6 +1051,26 @@ edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata) } } +static void edt_ft5x06_ts_set_readout_mode(struct edt_ft5x06_ts_data *tsdata, + enum readout_mode mode) +{ + uint8_t readout_mode; + + switch (tsdata->version) { + case EDT_M06: + break; + case EDT_M09: /* fall through */ + case EDT_M12: /* fall through */ + case GENERIC_FT: + readout_mode = (mode == EDT_READOUT_MODE_POLL) ? + M09_ID_G_MODE_POLL : + M09_ID_G_MODE_IRQ; + edt_ft5x06_register_write(tsdata, M09_ID_G_MODE, readout_mode); + break; + } +} + + static void edt_ft5x06_ts_set_regs(struct edt_ft5x06_ts_data *tsdata) { @@ -1069,7 +1112,6 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, struct device *dev = &client->dev; struct edt_ft5x06_ts_data *tsdata; struct input_dev *input; - unsigned long irq_flags; int error; char fw_version[EDT_NAME_LEN]; @@ -1121,15 +1163,8 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, msleep(300); } - input = devm_input_allocate_device(dev); - if (!input) { - dev_err(dev, "failed to allocate input device\n"); - return -ENOMEM; - } - mutex_init(&tsdata->mutex); tsdata->client = client; - tsdata->input = input; tsdata->factory_mode = false; error = edt_ft5x06_ts_identify(client, tsdata, fw_version); @@ -1145,11 +1180,66 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, dev_dbg(dev, "model \"%s\", Rev. \"%s\", %dx%d sensors\n", tsdata->name, fw_version, tsdata->num_x, tsdata->num_y); + i2c_set_clientdata(client, tsdata); + + if (client->irq) { + unsigned long irq_flags; + + irq_flags = irq_get_trigger_type(client->irq); + if (irq_flags == IRQF_TRIGGER_NONE) + irq_flags = IRQF_TRIGGER_FALLING; + irq_flags |= IRQF_ONESHOT; + + error = devm_request_threaded_irq(dev, client->irq, NULL, + edt_ft5x06_ts_isr, irq_flags, + client->name, tsdata); + if (error) { + dev_err(dev, "unable to request touchscreen IRQ\n"); + return error; + } + + disable_irq(client->irq); + + input = devm_input_allocate_device(dev); + if (!input) { + dev_err(dev, "failed to allocate input device\n"); + return -ENOMEM; + } + input->open = edt_ft5x06_open; + input->close = edt_ft5x06_close; + + edt_ft5x06_ts_set_readout_mode(tsdata, EDT_READOUT_MODE_IRQ); + } else { +#if !IS_ENABLED(CONFIG_INPUT_POLLDEV) + dev_err(dev, "no IRQ setup and built without INPUT_POLLDEV\n"); + return -ENODEV; +#else + uint32_t poll_interval; + + dev_warn(dev, "no IRQ setup, using polled input\n"); + + tsdata->polldev = devm_input_allocate_polled_device(dev); + if (!tsdata->polldev) { + dev_err(dev, "failed to allocate polldev\n"); + return -ENOMEM; + } + + if (!device_property_read_u32(dev, "poll-interval", &poll_interval)) + tsdata->polldev->poll_interval = poll_interval; + + tsdata->polldev->private = tsdata; + tsdata->polldev->poll = edt_ft5x06_poll; + input = tsdata->polldev->input; + + edt_ft5x06_ts_set_readout_mode(tsdata, EDT_READOUT_MODE_POLL); +#endif + } + input->name = tsdata->name; input->id.bustype = BUS_I2C; input->dev.parent = dev; - input->open = edt_ft5x06_open; - input->close = edt_ft5x06_close; + input_set_drvdata(input, tsdata); + tsdata->input = input; if (tsdata->version == EDT_M06 || tsdata->version == EDT_M09 || @@ -1175,28 +1265,14 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, return error; } - input_set_drvdata(input, tsdata); - i2c_set_clientdata(client, tsdata); - - irq_flags = irq_get_trigger_type(client->irq); - if (irq_flags == IRQF_TRIGGER_NONE) - irq_flags = IRQF_TRIGGER_FALLING; - irq_flags |= IRQF_ONESHOT; - - error = devm_request_threaded_irq(dev, client->irq, NULL, - edt_ft5x06_ts_isr, irq_flags, - client->name, tsdata); - if (error) { - dev_err(dev, "unable to request touchscreen IRQ\n"); - return error; - } - disable_irq(client->irq); - error = devm_device_add_group(dev, &edt_ft5x06_attr_group); if (error) return error; - error = input_register_device(input); + if (client->irq) + error = input_register_device(input); + else + error = input_register_polled_device(tsdata->polldev); if (error) return error; From d619e9de787ff5493dadedba783e5b2569e6793a Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 19 Dec 2017 16:28:50 +0100 Subject: [PATCH 68/93] input: edt-ft5x06: force touchscreen to remain active With the M09, the touchscreen enters sleep mode after a few seconds. During this time the controller responds to all i2c commands normally, but no input data is generated. This patch forces active mode during each poll interval, to prevent sleep mode (monitor mode) to be activated during normal operation. This can be further improved by setting/clearing the bit when going into suspend/resume mode. Some extra logic would be needed there however to only go into suspend mode if a reset/wake line is available. Signed-off-by: Olliver Schinagl --- drivers/input/touchscreen/edt-ft5x06.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 068bc4f4076433..dd5f2db2a260f5 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -97,6 +97,15 @@ #define M09_NUM_X 0x94 #define M09_NUM_Y 0x95 +#define M09_ID_G_MODE 0xa4 +#define M09_ID_G_MODE_POLL 0x00 +#define M09_ID_G_MODE_IRQ 0x01 + +#define M09_ID_G_PMODE 0xa5 +#define M09_ID_G_PMODE_ACTIVE 0x00 +#define M09_ID_G_PMODE_MONITOR 0x01 +#define M09_ID_G_PMODE_HIBERNATE 0x03 + #define EDT_FW_VERSION 0xa6 #define EDT_PANEL_ID 0xa8 #define EDT_PANEL_ID_EP0350M09 0x35 @@ -429,6 +438,18 @@ static void edt_ft5x06_poll(struct input_polled_dev *polldev) { struct edt_ft5x06_ts_data *tsdata = polldev->private; + /* Ensure display is always awake */ + switch (tsdata->version) { + case EDT_M06: + break; + case EDT_M09: /* fall through */ + case EDT_M12: /* fall through */ + case GENERIC_FT: + edt_ft5x06_register_write(tsdata, M09_ID_G_PMODE, + M09_ID_G_PMODE_ACTIVE); + break; + } + edt_ft5x06_report(tsdata); } From a083ea65498b6841c0db73f02f329aa6f545232a Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 2 Mar 2018 10:57:06 +0100 Subject: [PATCH 69/93] drm/edid: Prevent NULL pointer deref when there is no I2C adapter When drm_do_probe_ddc_edid is called without a valid i2c adapter, a NULL pointer is passed onto i2c_transfer which causes a NULL pointer dereference error and a stack trace. Prevent this by adding a NULL pointer check. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/drm_edid.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 1f1fd3139c5b8c..c7718d0ecbf931 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1451,6 +1451,9 @@ drm_do_probe_ddc_edid(void *data, u8 *buf, unsigned int block, size_t len) unsigned char xfers = segment ? 3 : 2; int ret, retries = 5; + if (!adapter) + return -ENODEV; + /* * The core I2C driver will automatically retry the transfer if the * adapter reports EAGAIN. However, we find that bit-banging transfers From 4005af39dcc4789ea41c1321119f6cfff77af8ae Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 10 Oct 2017 16:12:34 +0200 Subject: [PATCH 70/93] drm/sun4i: Fix Kconfig indenting No codechanges where done with this change, just some whitespace (tabs vs spaces) cleanup. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/Kconfig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 882d85db90539a..7b6d6930530837 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -16,18 +16,18 @@ config DRM_SUN4I if DRM_SUN4I config DRM_SUN4I_HDMI - tristate "Allwinner A10 HDMI Controller Support" - default DRM_SUN4I - help + tristate "Allwinner A10 HDMI Controller Support" + default DRM_SUN4I + help Choose this option if you have an Allwinner SoC with an HDMI controller. config DRM_SUN4I_HDMI_CEC - bool "Allwinner A10 HDMI CEC Support" - depends on DRM_SUN4I_HDMI - select CEC_CORE - select CEC_PIN - help + bool "Allwinner A10 HDMI CEC Support" + depends on DRM_SUN4I_HDMI + select CEC_CORE + select CEC_PIN + help Choose this option if you have an Allwinner SoC with an HDMI controller and want to use CEC. From 123fb0f043f2fe346eafd4b055439264828ca184 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 1 Mar 2018 08:03:34 +0100 Subject: [PATCH 71/93] drm/sun4i: improve header readability By replacing the magic hardcoded bit masks with GENMASK we can improve readability quite a bit. By also changing the order of shifting and masking, we can actually use a GENMASK as described in the datasheet making the masks even more obvious and easier to read. Furthermore we replace some magic values in the code with defined a getter in turn use it to improve readability. This patch does not have any functional changes. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/sun4i_hdmi.h | 68 +++++++++++++-------- drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 4 +- drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 3 +- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index b685ee11623d10..c6eaa5182f9e22 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -22,7 +22,7 @@ #define SUN4I_HDMI_CTRL_ENABLE BIT(31) #define SUN4I_HDMI_IRQ_REG 0x008 -#define SUN4I_HDMI_IRQ_STA_MASK 0x73 +#define SUN4I_HDMI_IRQ_STA_MASK (GENMASK(6, 4) | GENMASK(1, 0)) #define SUN4I_HDMI_IRQ_STA_FIFO_OF BIT(1) #define SUN4I_HDMI_IRQ_STA_FIFO_UF BIT(0) @@ -68,9 +68,9 @@ #define SUN4I_HDMI_PAD_CTRL1_PWSDT BIT(17) #define SUN4I_HDMI_PAD_CTRL1_REG_DEN BIT(15) #define SUN4I_HDMI_PAD_CTRL1_REG_DENCK BIT(14) -#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) & 7) << 10) +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n) (((n) << 10) & GENMASK(12, 10)) #define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK BIT(6) -#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n) (((n) & 7) << 3) +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n) (((n) << 3) & GENMASK(5, 3)) /* These bits seem to invert the TMDS data channels */ #define SUN4I_HDMI_PAD_CTRL1_INVERT_R BIT(2) @@ -84,18 +84,23 @@ #define SUN4I_HDMI_PLL_CTRL_LDO1_EN BIT(28) #define SUN4I_HDMI_PLL_CTRL_LDO2_EN BIT(27) #define SUN4I_HDMI_PLL_CTRL_SDIV2 BIT(25) -#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n) (((n) & 7) << 20) -#define SUN4I_HDMI_PLL_CTRL_S(n) (((n) & 7) << 17) -#define SUN4I_HDMI_PLL_CTRL_CP_S(n) (((n) & 0x1f) << 12) -#define SUN4I_HDMI_PLL_CTRL_CS(n) (((n) & 0xf) << 8) -#define SUN4I_HDMI_PLL_CTRL_DIV(n) (((n) & 0xf) << 4) +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n) (((n) << 20) & GENMASK(22, 20)) +#define SUN4I_HDMI_PLL_CTRL_S(n) (((n) << 17) & GENMASK(19, 17)) +#define SUN4I_HDMI_PLL_CTRL_CP_S(n) (((n) << 12) & GENMASK(16, 12)) +#define SUN4I_HDMI_PLL_CTRL_CS(n) (((n) << 8) & GENMASK(11, 8)) +#define SUN4I_HDMI_PLL_CTRL_DIV(n) (((n) << 4) & GENMASK(7, 4)) #define SUN4I_HDMI_PLL_CTRL_DIV_MASK GENMASK(7, 4) -#define SUN4I_HDMI_PLL_CTRL_VCO_S(n) ((n) & 0xf) +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n) ((n) & GENMASK(3, 0)) #define SUN4I_HDMI_PLL_DBG0_REG 0x20c -#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n) (((n) & 1) << 21) #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21) -#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21 +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_OFFSET 21 +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n) \ + (((n) << SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_OFFSET) & \ + SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_GET(n) \ + (((n) & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >> \ + SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_OFFSET) #define SUN4I_HDMI_CEC 0x214 #define SUN4I_HDMI_CEC_ENABLE BIT(11) @@ -117,10 +122,10 @@ #define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) #define SUN4I_HDMI_DDC_ADDR_REG 0x504 -#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24) -#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16) -#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) -#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff) +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & GENMASK(7, 0)) #define SUN4I_HDMI_DDC_INT_STATUS_REG 0x50c #define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7) @@ -134,11 +139,13 @@ #define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 #define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) -#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) (((n) & 0xf) << 4) #define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) \ + (((n) << 4) & SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK) #define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1) -#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) ((n) & 0xf) #define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) \ + ((n) & SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK) #define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1) #define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 @@ -147,13 +154,22 @@ #define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1) #define SUN4I_HDMI_DDC_CMD_REG 0x520 -#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6 -#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 5 -#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 3 +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 0x6 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 0x5 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 0x3 #define SUN4I_HDMI_DDC_CLK_REG 0x528 -#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3) -#define SUN4I_HDMI_DDC_CLK_N(n) ((n) & 0x7) +#define SUN4I_HDMI_DDC_CLK_M_OFFSET 3 +#define SUN4I_HDMI_DDC_CLK_M_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_CLK_N_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_CLK_M(m) \ + (((m) << SUN4I_HDMI_DDC_CLK_M_OFFSET) & SUN4I_HDMI_DDC_CLK_M_MASK) +#define SUN4I_HDMI_DDC_CLK_N(n) \ + ((n) & SUN4I_HDMI_DDC_CLK_N_MASK) +#define SUN4I_HDMI_DDC_CLK_M_GET(reg) \ + (((reg) & SUN4I_HDMI_DDC_CLK_M_MASK) >> SUN4I_HDMI_DDC_CLK_M_OFFSET) +#define SUN4I_HDMI_DDC_CLK_N_GET(reg) \ + ((reg) & SUN4I_HDMI_DDC_CLK_N_MASK) #define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540 #define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9) @@ -174,10 +190,10 @@ /* command types in lower 3 bits are the same as sun4i */ #define SUN6I_HDMI_DDC_ADDR_REG 0x50c -#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24) -#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16) -#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8) -#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) & 0xff) << 1) +#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) +#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) +#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) +#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) << 1) & GENMASK(7, 1)) #define SUN6I_HDMI_DDC_INT_STATUS_REG 0x514 #define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c index e826da34e9191f..d057d7c0f60462 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c @@ -80,8 +80,8 @@ static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw, u8 m, n; regmap_field_read(ddc->reg, ®); - m = (reg >> 3) & 0xf; - n = reg & 0x7; + m = SUN4I_HDMI_DDC_CLK_M_GET(reg); + n = SUN4I_HDMI_DDC_CLK_N_GET(reg); return (((parent_rate / ddc->pre_div) / 10) >> n) / (m + ddc->m_offset); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c index dc332ea56f6c75..11e59f91777420 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c @@ -171,8 +171,7 @@ static u8 sun4i_tmds_get_parent(struct clk_hw *hw) u32 reg; reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG); - return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >> - SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT); + return SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_GET(reg); } static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index) From a0c9f90d91374e2bee865d60123b7e87db09b851 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 27 Feb 2018 17:05:13 +0100 Subject: [PATCH 72/93] drm/sun4i: Fix NULL pointer dereference at unbind In sun4i_drv_unbind the drm struct is retrieved and passed to drm_dev_unregister. Since however the drm structure is never set in during bind, we get a NULL pointer dereference error and the following stacktrace. [] (drm_dev_unregister) from [] (sun4i_drv_unbind+0xc/0x2c [sun4i_drm]) [] (sun4i_drv_unbind [sun4i_drm]) from [] (take_down_master.part.0+0xd/0x1c) [] (take_down_master.part.0) from [] (component_del+0xb7/0xbc) [] (component_del) from [] (sun4i_backend_remove+0xa/0x14 [sun4i_backend]) [] (sun4i_backend_remove [sun4i_backend]) from [] (platform_drv_remove+0x11/0x20) [] (platform_drv_remove) from [] (device_release_driver_internal+0xd1/0x134) [] (device_release_driver_internal) from [] (driver_detach+0x2f/0x5c) [] (driver_detach) from [] (bus_remove_driver+0x31/0x88) [] (bus_remove_driver) from [] (sun4i_backend_platform_driver_exit+0x8/0x5a3 [sun4i_backend]) [] (sun4i_backend_platform_driver_exit [sun4i_backend]) from [] (SyS_delete_module+0x117/0x154) [] (SyS_delete_module) from [] (ret_fast_syscall+0x1/0x5a) Code: f2cb 007e f5ea fbc5 (696b) f8d3 ---[ end trace c92c06a17cdbc6e9 ]--- By setting the drm data into the device structure we fix this error. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/sun4i_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 75c76cdd82bc6e..eb02ff48cda316 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -140,6 +140,8 @@ static int sun4i_drv_bind(struct device *dev) if (ret) goto finish_poll; + dev_set_drvdata(dev, drm); + return 0; finish_poll: From cd5b3da80dbb09dd7deb13975bb1cd650e71f86d Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 1 Mar 2018 08:14:48 +0100 Subject: [PATCH 73/93] drm/sun4i: enable/disable the tmds clock on use While the tmds clock is configured using set_rate whenever we use/change it, we never properly 'enable' or 'disable' it throwing off our ref-counters. The reason it works is because it is an ungated clock and thus always runs whenever its parent runs. Add the enable/disable calls to the tmds clock to ensure our ref-counters play nicely. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index dda904ec0534cd..f4c57a025742ac 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -92,6 +92,8 @@ static void sun4i_hdmi_disable(struct drm_encoder *encoder) val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG); val &= ~SUN4I_HDMI_VID_CTRL_ENABLE; writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); + + clk_disable_unprepare(hdmi->tmds_clk); } static void sun4i_hdmi_enable(struct drm_encoder *encoder) @@ -102,6 +104,8 @@ static void sun4i_hdmi_enable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("Enabling the HDMI Output\n"); + clk_prepare_enable(hdmi->tmds_clk); + sun4i_hdmi_setup_avi_infoframes(hdmi, mode); val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); From 180c93c15e104f5be4485ba27cbf1e5e1a6a708b Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 1 Mar 2018 08:06:58 +0100 Subject: [PATCH 74/93] drm/sun4i: make bool assignment explicit In the tmds clock driver, we current assign the counter value to a Boolean check. When reading this code as an outsider it is not immediately obvious what is happening, as the counter (d) initially appears to never become 0. After closer inspection it becomes obvious that the always positive counter is used to toggle the Boolean only at a certain case. Unfortunately there does not seem to be any added benefit from using the more confusing value of d over using 'true' since there is no direct relation to 'd'. This now makes the code a little more obvious and easier to read. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c index 11e59f91777420..6e3293beff0d57 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c @@ -52,7 +52,7 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate, (rate - tmp_rate) < (rate - best_rate)) { best_rate = tmp_rate; best_m = m; - is_double = d; + is_double = true; } } } From c88bcd63eb3fd10413849b1c1ef8c376b7ab4ecd Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Mon, 4 Sep 2017 10:03:43 +0200 Subject: [PATCH 75/93] drm/sun4i: i2c: consolidate ddc items The ddc items can be consolidated more into one place. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/sun4i_hdmi.h | 95 ----- drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 1 + drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 61 +--- drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 404 ++++++++++++++------- drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h | 113 ++++++ 5 files changed, 379 insertions(+), 295 deletions(-) create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index c6eaa5182f9e22..a63f6ad8103aa2 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -113,101 +113,6 @@ #define SUN4I_HDMI_UNKNOWN_REG 0x300 #define SUN4I_HDMI_UNKNOWN_INPUT_SYNC BIT(27) -#define SUN4I_HDMI_DDC_CTRL_REG 0x500 -#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31) -#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30) -#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8) -#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE (1 << 8) -#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) -#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) - -#define SUN4I_HDMI_DDC_ADDR_REG 0x504 -#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) -#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) -#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) -#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & GENMASK(7, 0)) - -#define SUN4I_HDMI_DDC_INT_STATUS_REG 0x50c -#define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7) -#define SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW BIT(6) -#define SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW BIT(5) -#define SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST BIT(4) -#define SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR BIT(3) -#define SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR BIT(2) -#define SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR BIT(1) -#define SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE BIT(0) - -#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 -#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) -#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4) -#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) \ - (((n) << 4) & SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK) -#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1) -#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0) -#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) \ - ((n) & SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK) -#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1) - -#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 - -#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c -#define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1) - -#define SUN4I_HDMI_DDC_CMD_REG 0x520 -#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 0x6 -#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 0x5 -#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 0x3 - -#define SUN4I_HDMI_DDC_CLK_REG 0x528 -#define SUN4I_HDMI_DDC_CLK_M_OFFSET 3 -#define SUN4I_HDMI_DDC_CLK_M_MASK GENMASK(7, 4) -#define SUN4I_HDMI_DDC_CLK_N_MASK GENMASK(3, 0) -#define SUN4I_HDMI_DDC_CLK_M(m) \ - (((m) << SUN4I_HDMI_DDC_CLK_M_OFFSET) & SUN4I_HDMI_DDC_CLK_M_MASK) -#define SUN4I_HDMI_DDC_CLK_N(n) \ - ((n) & SUN4I_HDMI_DDC_CLK_N_MASK) -#define SUN4I_HDMI_DDC_CLK_M_GET(reg) \ - (((reg) & SUN4I_HDMI_DDC_CLK_M_MASK) >> SUN4I_HDMI_DDC_CLK_M_OFFSET) -#define SUN4I_HDMI_DDC_CLK_N_GET(reg) \ - ((reg) & SUN4I_HDMI_DDC_CLK_N_MASK) - -#define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540 -#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9) -#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8) - -#define SUN4I_HDMI_DDC_FIFO_SIZE 16 - -/* A31 specific */ -#define SUN6I_HDMI_DDC_CTRL_REG 0x500 -#define SUN6I_HDMI_DDC_CTRL_RESET BIT(31) -#define SUN6I_HDMI_DDC_CTRL_START_CMD BIT(27) -#define SUN6I_HDMI_DDC_CTRL_SDA_ENABLE BIT(6) -#define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4) -#define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0) - -#define SUN6I_HDMI_DDC_CMD_REG 0x508 -#define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16) -/* command types in lower 3 bits are the same as sun4i */ - -#define SUN6I_HDMI_DDC_ADDR_REG 0x50c -#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) -#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) -#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) -#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) << 1) & GENMASK(7, 1)) - -#define SUN6I_HDMI_DDC_INT_STATUS_REG 0x514 -#define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8) -/* lower 8 bits are the same as sun4i */ - -#define SUN6I_HDMI_DDC_FIFO_CTRL_REG 0x518 -#define SUN6I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(15) -/* lower 9 bits are the same as sun4i */ - -#define SUN6I_HDMI_DDC_CLK_REG 0x520 -/* DDC CLK bit fields are the same, but the formula is not */ - -#define SUN6I_HDMI_DDC_FIFO_DATA_REG 0x580 - enum sun4i_hdmi_pkt_type { SUN4I_HDMI_PKT_AVI = 2, SUN4I_HDMI_PKT_END = 15, diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c index d057d7c0f60462..68d04e86b89b2e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c @@ -14,6 +14,7 @@ #include #include "sun4i_hdmi.h" +#include "sun4i_hdmi_i2c.h" struct sun4i_ddc { struct clk_hw hw; diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index f4c57a025742ac..2f3039da16966f 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -30,6 +30,7 @@ #include "sun4i_crtc.h" #include "sun4i_drv.h" #include "sun4i_hdmi.h" +#include "sun4i_hdmi_i2c.h" static inline struct sun4i_hdmi * drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) @@ -308,24 +309,6 @@ static const struct sun4i_hdmi_variant sun4i_variant = { SUN4I_HDMI_PLL_CTRL_BWS | SUN4I_HDMI_PLL_CTRL_PLL_EN, - .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), - .ddc_clk_pre_divider = 2, - .ddc_clk_m_offset = 1, - - .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), - .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), - .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), - .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), - .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), - .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), - .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), - .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), - .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), - .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), - .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), - .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), - .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), - .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, .ddc_fifo_has_dir = true, }; @@ -358,27 +341,6 @@ static const struct sun4i_hdmi_variant sun5i_variant = { SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS | SUN4I_HDMI_PLL_CTRL_PLL_EN, - - .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), - .ddc_clk_pre_divider = 2, - .ddc_clk_m_offset = 1, - - .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), - .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), - .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), - .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), - .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), - .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), - .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), - .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), - .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), - .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), - .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), - .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), - .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), - - .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, - .ddc_fifo_has_dir = true, }; static const struct sun4i_hdmi_variant sun6i_variant = { @@ -414,28 +376,7 @@ static const struct sun4i_hdmi_variant sun6i_variant = { SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_PLL_EN, - .ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6), - .ddc_clk_pre_divider = 1, - .ddc_clk_m_offset = 2, - .tmds_clk_div_offset = 1, - - .field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0), - .field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27), - .field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31), - .field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31), - .field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7), - .field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8), - .field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18), - .field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), - .field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), - .field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25), - .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2), - .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6), - .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4), - - .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG, - .ddc_fifo_thres_incl = true, }; static const struct regmap_config sun4i_hdmi_regmap_config = { diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c index 58e9d37e8c17c0..3c7c020fc99da9 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -13,20 +13,126 @@ #include #include "sun4i_hdmi.h" - -#define SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK ( \ - SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION | \ - SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW | \ - SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW | \ - SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR | \ - SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR | \ - SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR \ -) +#include "sun4i_hdmi_i2c.h" /* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */ #define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX -static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) +struct sun4i_hdmi_i2c_variant { + struct reg_field ddc_clk_reg; + u8 ddc_clk_pre_divider; + u8 ddc_clk_m_offset; + + /* Register fields for I2C adapter */ + struct reg_field field_ddc_en; + struct reg_field field_ddc_start; + struct reg_field field_ddc_reset; + struct reg_field field_ddc_addr_reg; + struct reg_field field_ddc_slave_addr; + struct reg_field field_ddc_int_mask; + struct reg_field field_ddc_int_status; + struct reg_field field_ddc_fifo_clear; + struct reg_field field_ddc_fifo_rx_thres; + struct reg_field field_ddc_fifo_tx_thres; + struct reg_field field_ddc_byte_count; + struct reg_field field_ddc_cmd; + struct reg_field field_ddc_sda_en; + struct reg_field field_ddc_sck_en; + + /* DDC FIFO register offset */ + u32 ddc_fifo_reg; + + /* + * DDC FIFO threshold boundary conditions + * + * This is used to cope with the threshold boundary condition + * being slightly different on sun5i and sun6i. + * + * On sun5i the threshold is exclusive, i.e. does not include, + * the value of the threshold. ( > for RX; < for TX ) + * On sun6i the threshold is inclusive, i.e. includes, the + * value of the threshold. ( >= for RX; <= for TX ) + */ + bool ddc_fifo_thres_incl; + + bool ddc_fifo_has_dir; +}; + +struct sun4i_hdmi_i2c_drv { + struct device *dev; + + void __iomem *base; + struct regmap *regmap; + + struct clk *ddc_clk; + + struct i2c_adapter adap; + + struct regmap_field *field_ddc_en; + struct regmap_field *field_ddc_start; + struct regmap_field *field_ddc_reset; + struct regmap_field *field_ddc_addr_reg; + struct regmap_field *field_ddc_slave_addr; + struct regmap_field *field_ddc_int_mask; + struct regmap_field *field_ddc_int_status; + struct regmap_field *field_ddc_fifo_clear; + struct regmap_field *field_ddc_fifo_rx_thres; + struct regmap_field *field_ddc_fifo_tx_thres; + struct regmap_field *field_ddc_byte_count; + struct regmap_field *field_ddc_cmd; + struct regmap_field *field_ddc_sda_en; + struct regmap_field *field_ddc_sck_en; + + const struct sun4i_hdmi_i2c_variant *variant; +}; + +static const struct sun4i_hdmi_i2c_variant sun4i_variant = { + .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 2, + .ddc_clk_m_offset = 1, + + .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), + .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), + .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), + .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), + .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), + .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), + + .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_has_dir = true, +}; + +static const struct sun4i_hdmi_i2c_variant sun6i_variant = { + .ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 1, + .ddc_clk_m_offset = 2, + + .field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27), + .field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31), + .field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7), + .field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25), + .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6), + .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4), + + .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_thres_incl = true, +}; + +static int fifo_transfer(struct sun4i_hdmi_i2c_drv *drv, u8 *buf, int len, bool read) { /* * 1 byte takes 9 clock cycles (8 bits + 1 ACK) = 90 us for 100 kHz @@ -42,7 +148,7 @@ static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1. */ int read_len = RX_THRESHOLD + - (hdmi->variant->ddc_fifo_thres_incl ? 0 : 1); + (drv->variant->ddc_fifo_thres_incl ? 0 : 1); /* * Limit transfer length by FIFO threshold or FIFO size. @@ -51,7 +157,7 @@ static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE); /* Wait until error, FIFO request bit set or transfer complete */ - if (regmap_field_read_poll_timeout(hdmi->field_ddc_int_status, reg, + if (regmap_field_read_poll_timeout(drv->field_ddc_int_status, reg, reg & mask, len * byte_time_ns, 100000)) return -ETIMEDOUT; @@ -60,37 +166,37 @@ static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read) return -EIO; if (read) - readsb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len); + readsb(drv->base + drv->variant->ddc_fifo_reg, buf, len); else - writesb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len); + writesb(drv->base + drv->variant->ddc_fifo_reg, buf, len); /* Clear FIFO request bit by forcing a write to that bit */ - regmap_field_force_write(hdmi->field_ddc_int_status, + regmap_field_force_write(drv->field_ddc_int_status, SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST); return len; } -static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) +static int xfer_msg(struct sun4i_hdmi_i2c_drv *drv, struct i2c_msg *msg) { int i, len; u32 reg; /* Set FIFO direction */ - if (hdmi->variant->ddc_fifo_has_dir) { - reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + if (drv->variant->ddc_fifo_has_dir) { + reg = readl(drv->base + SUN4I_HDMI_DDC_CTRL_REG); reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; reg |= (msg->flags & I2C_M_RD) ? SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; - writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG); + writel(reg, drv->base + SUN4I_HDMI_DDC_CTRL_REG); } /* Clear address register (not cleared by soft reset) */ - regmap_field_write(hdmi->field_ddc_addr_reg, 0); + regmap_field_write(drv->field_ddc_addr_reg, 0); /* Set I2C address */ - regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr); + regmap_field_write(drv->field_ddc_slave_addr, msg->addr); /* * Set FIFO RX/TX thresholds and clear FIFO @@ -98,47 +204,47 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) * If threshold is inclusive, we can set the TX threshold to * 0 instead of 1. */ - regmap_field_write(hdmi->field_ddc_fifo_tx_thres, - hdmi->variant->ddc_fifo_thres_incl ? 0 : 1); - regmap_field_write(hdmi->field_ddc_fifo_rx_thres, RX_THRESHOLD); - regmap_field_write(hdmi->field_ddc_fifo_clear, 1); - if (regmap_field_read_poll_timeout(hdmi->field_ddc_fifo_clear, + regmap_field_write(drv->field_ddc_fifo_tx_thres, + drv->variant->ddc_fifo_thres_incl ? 0 : 1); + regmap_field_write(drv->field_ddc_fifo_rx_thres, RX_THRESHOLD); + regmap_field_write(drv->field_ddc_fifo_clear, 1); + if (regmap_field_read_poll_timeout(drv->field_ddc_fifo_clear, reg, !reg, 100, 2000)) return -EIO; /* Set transfer length */ - regmap_field_write(hdmi->field_ddc_byte_count, msg->len); + regmap_field_write(drv->field_ddc_byte_count, msg->len); /* Set command */ - regmap_field_write(hdmi->field_ddc_cmd, + regmap_field_write(drv->field_ddc_cmd, msg->flags & I2C_M_RD ? SUN4I_HDMI_DDC_CMD_IMPLICIT_READ : SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE); /* Clear interrupt status bits by forcing a write */ - regmap_field_force_write(hdmi->field_ddc_int_status, + regmap_field_force_write(drv->field_ddc_int_status, SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE); /* Start command */ - regmap_field_write(hdmi->field_ddc_start, 1); + regmap_field_write(drv->field_ddc_start, 1); /* Transfer bytes */ for (i = 0; i < msg->len; i += len) { - len = fifo_transfer(hdmi, msg->buf + i, msg->len - i, + len = fifo_transfer(drv, msg->buf + i, msg->len - i, msg->flags & I2C_M_RD); if (len <= 0) return len; } /* Wait for command to finish */ - if (regmap_field_read_poll_timeout(hdmi->field_ddc_start, + if (regmap_field_read_poll_timeout(drv->field_ddc_start, reg, !reg, 100, 100000)) return -EIO; /* Check for errors */ - regmap_field_read(hdmi->field_ddc_int_status, ®); + regmap_field_read(drv->field_ddc_int_status, ®); if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) || !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) { return -EIO; @@ -150,7 +256,7 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg) static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { - struct sun4i_hdmi *hdmi = i2c_get_adapdata(adap); + struct sun4i_hdmi_i2c_drv *drv = i2c_get_adapdata(adap); u32 reg; int err, i, ret = num; @@ -162,30 +268,30 @@ static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, } /* DDC clock needs to be enabled for the module to work */ - clk_prepare_enable(hdmi->ddc_clk); - clk_set_rate(hdmi->ddc_clk, 100000); + clk_prepare_enable(drv->ddc_clk); + clk_set_rate(drv->ddc_clk, 100000); /* Reset I2C controller */ - regmap_field_write(hdmi->field_ddc_en, 1); - regmap_field_write(hdmi->field_ddc_reset, 1); - if (regmap_field_read_poll_timeout(hdmi->field_ddc_reset, + regmap_field_write(drv->field_ddc_en, 1); + regmap_field_write(drv->field_ddc_reset, 1); + if (regmap_field_read_poll_timeout(drv->field_ddc_reset, reg, !reg, 100, 2000)) { - clk_disable_unprepare(hdmi->ddc_clk); + clk_disable_unprepare(drv->ddc_clk); return -EIO; } - regmap_field_write(hdmi->field_ddc_sck_en, 1); - regmap_field_write(hdmi->field_ddc_sda_en, 1); + regmap_field_write(drv->field_ddc_sck_en, 1); + regmap_field_write(drv->field_ddc_sda_en, 1); for (i = 0; i < num; i++) { - err = xfer_msg(hdmi, &msgs[i]); + err = xfer_msg(drv, &msgs[i]); if (err) { ret = err; break; } } - clk_disable_unprepare(hdmi->ddc_clk); + clk_disable_unprepare(drv->ddc_clk); return ret; } @@ -199,123 +305,141 @@ static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = { .functionality = sun4i_hdmi_i2c_func, }; -static int sun4i_hdmi_init_regmap_fields(struct sun4i_hdmi *hdmi) +static int sun4i_hdmi_i2c_init_regmap_fields(struct sun4i_hdmi_i2c_drv *drv) { - hdmi->field_ddc_en = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_en); - if (IS_ERR(hdmi->field_ddc_en)) - return PTR_ERR(hdmi->field_ddc_en); - - hdmi->field_ddc_start = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_start); - if (IS_ERR(hdmi->field_ddc_start)) - return PTR_ERR(hdmi->field_ddc_start); - - hdmi->field_ddc_reset = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_reset); - if (IS_ERR(hdmi->field_ddc_reset)) - return PTR_ERR(hdmi->field_ddc_reset); - - hdmi->field_ddc_addr_reg = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_addr_reg); - if (IS_ERR(hdmi->field_ddc_addr_reg)) - return PTR_ERR(hdmi->field_ddc_addr_reg); - - hdmi->field_ddc_slave_addr = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_slave_addr); - if (IS_ERR(hdmi->field_ddc_slave_addr)) - return PTR_ERR(hdmi->field_ddc_slave_addr); - - hdmi->field_ddc_int_mask = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_int_mask); - if (IS_ERR(hdmi->field_ddc_int_mask)) - return PTR_ERR(hdmi->field_ddc_int_mask); - - hdmi->field_ddc_int_status = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_int_status); - if (IS_ERR(hdmi->field_ddc_int_status)) - return PTR_ERR(hdmi->field_ddc_int_status); - - hdmi->field_ddc_fifo_clear = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_fifo_clear); - if (IS_ERR(hdmi->field_ddc_fifo_clear)) - return PTR_ERR(hdmi->field_ddc_fifo_clear); - - hdmi->field_ddc_fifo_rx_thres = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_fifo_rx_thres); - if (IS_ERR(hdmi->field_ddc_fifo_rx_thres)) - return PTR_ERR(hdmi->field_ddc_fifo_rx_thres); - - hdmi->field_ddc_fifo_tx_thres = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_fifo_tx_thres); - if (IS_ERR(hdmi->field_ddc_fifo_tx_thres)) - return PTR_ERR(hdmi->field_ddc_fifo_tx_thres); - - hdmi->field_ddc_byte_count = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_byte_count); - if (IS_ERR(hdmi->field_ddc_byte_count)) - return PTR_ERR(hdmi->field_ddc_byte_count); - - hdmi->field_ddc_cmd = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_cmd); - if (IS_ERR(hdmi->field_ddc_cmd)) - return PTR_ERR(hdmi->field_ddc_cmd); - - hdmi->field_ddc_sda_en = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_sda_en); - if (IS_ERR(hdmi->field_ddc_sda_en)) - return PTR_ERR(hdmi->field_ddc_sda_en); - - hdmi->field_ddc_sck_en = - devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->field_ddc_sck_en); - if (IS_ERR(hdmi->field_ddc_sck_en)) - return PTR_ERR(hdmi->field_ddc_sck_en); + drv->field_ddc_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_en); + if (IS_ERR(drv->field_ddc_en)) + return PTR_ERR(drv->field_ddc_en); + + drv->field_ddc_start = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_start); + if (IS_ERR(drv->field_ddc_start)) + return PTR_ERR(drv->field_ddc_start); + + drv->field_ddc_reset = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_reset); + if (IS_ERR(drv->field_ddc_reset)) + return PTR_ERR(drv->field_ddc_reset); + + drv->field_ddc_addr_reg = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_addr_reg); + if (IS_ERR(drv->field_ddc_addr_reg)) + return PTR_ERR(drv->field_ddc_addr_reg); + + drv->field_ddc_slave_addr = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_slave_addr); + if (IS_ERR(drv->field_ddc_slave_addr)) + return PTR_ERR(drv->field_ddc_slave_addr); + + drv->field_ddc_int_mask = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_int_mask); + if (IS_ERR(drv->field_ddc_int_mask)) + return PTR_ERR(drv->field_ddc_int_mask); + + drv->field_ddc_int_status = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_int_status); + if (IS_ERR(drv->field_ddc_int_status)) + return PTR_ERR(drv->field_ddc_int_status); + + drv->field_ddc_fifo_clear = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_fifo_clear); + if (IS_ERR(drv->field_ddc_fifo_clear)) + return PTR_ERR(drv->field_ddc_fifo_clear); + + drv->field_ddc_fifo_rx_thres = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_fifo_rx_thres); + if (IS_ERR(drv->field_ddc_fifo_rx_thres)) + return PTR_ERR(drv->field_ddc_fifo_rx_thres); + + drv->field_ddc_fifo_tx_thres = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_fifo_tx_thres); + if (IS_ERR(drv->field_ddc_fifo_tx_thres)) + return PTR_ERR(drv->field_ddc_fifo_tx_thres); + + drv->field_ddc_byte_count = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_byte_count); + if (IS_ERR(drv->field_ddc_byte_count)) + return PTR_ERR(drv->field_ddc_byte_count); + + drv->field_ddc_cmd = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_cmd); + if (IS_ERR(drv->field_ddc_cmd)) + return PTR_ERR(drv->field_ddc_cmd); + + drv->field_ddc_sda_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_en); + if (IS_ERR(drv->field_ddc_sda_en)) + return PTR_ERR(drv->field_ddc_sda_en); + + drv->field_ddc_sck_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_en); + if (IS_ERR(drv->field_ddc_sck_en)) + return PTR_ERR(drv->field_ddc_sck_en); return 0; } int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi) { - struct i2c_adapter *adap; + struct sun4i_hdmi_i2c_drv *drv; int ret = 0; ret = sun4i_ddc_create(hdmi, hdmi->ddc_parent_clk); if (ret) return ret; - ret = sun4i_hdmi_init_regmap_fields(hdmi); + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + drv->dev = dev; + + drv->base = hdmi->base; + drv->regmap = hdmi->regmap; + drv->ddc_clk = hdmi->ddc_clk; + + if (of_device_is_compatible(dev->of_node, "allwinner,sun4i-a10-hdmi")) + drv->variant = &sun4i_variant; + if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a10s-hdmi")) + drv->variant = &sun4i_variant; + if (of_device_is_compatible(dev->of_node, "allwinner,sun6i-a31-hdmi")) + drv->variant = &sun6i_variant; + if (!drv->variant) { + dev_err(dev, "unsupported platform variant"); + return -EINVAL; + } + + ret = sun4i_hdmi_i2c_init_regmap_fields(drv); if (ret) return ret; - adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); - if (!adap) - return -ENOMEM; - adap->owner = THIS_MODULE; - adap->class = I2C_CLASS_DDC; - adap->algo = &sun4i_hdmi_i2c_algorithm; - strlcpy(adap->name, "sun4i_hdmi_i2c adapter", sizeof(adap->name)); - i2c_set_adapdata(adap, hdmi); + i2c_set_adapdata(&drv->adap, drv); + drv->adap.owner = THIS_MODULE; + drv->adap.class = I2C_CLASS_DDC; + drv->adap.algo = &sun4i_hdmi_i2c_algorithm; + strlcpy(drv->adap.name, "sun4i_hdmi_i2c adapter", sizeof(drv->adap.name)); - ret = i2c_add_adapter(adap); + ret = i2c_add_adapter(&drv->adap); if (ret) return ret; - hdmi->i2c = adap; + hdmi->i2c = &drv->adap; return ret; } diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h new file mode 100644 index 00000000000000..6296c822da2a2e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Copyright (C) 2017 Chen-Yu Tsai + * Copyirght (C) 2017 Jonathan Liu + * Copyright (C) 2017 Olliver Schinagl + * + * Chen-Yu Tsai + * Maxime Ripard + * Jonathan Liu + * Olliver Schinagl + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _SUN4I_HDMI_I2C_H_ +#define _SUN4I_HDMI_I2C_H_ + +#define SUN4I_HDMI_DDC_CTRL_REG 0x500 +#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31) +#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE (1 << 8) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) +#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) + +#define SUN4I_HDMI_DDC_ADDR_REG 0x504 +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & GENMASK(7, 0)) + +#define SUN4I_HDMI_DDC_INT_STATUS_REG 0x50c +#define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW BIT(6) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW BIT(5) +#define SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST BIT(4) +#define SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR BIT(3) +#define SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR BIT(2) +#define SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR BIT(1) +#define SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE BIT(0) + +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510 +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) \ + (((n) << 4) & SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) \ + ((n) & SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1) + +#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518 + +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c +#define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1) + +#define SUN4I_HDMI_DDC_CMD_REG 0x520 +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 0x6 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 0x5 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 0x3 + +#define SUN4I_HDMI_DDC_CLK_REG 0x528 +#define SUN4I_HDMI_DDC_CLK_M_OFFSET 3 +#define SUN4I_HDMI_DDC_CLK_M_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_CLK_N_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_CLK_M(m) \ + (((m) << SUN4I_HDMI_DDC_CLK_M_OFFSET) & SUN4I_HDMI_DDC_CLK_M_MASK) +#define SUN4I_HDMI_DDC_CLK_N(n) \ + ((n) & SUN4I_HDMI_DDC_CLK_N_MASK) +#define SUN4I_HDMI_DDC_CLK_M_GET(reg) \ + (((reg) & SUN4I_HDMI_DDC_CLK_M_MASK) >> SUN4I_HDMI_DDC_CLK_M_OFFSET) +#define SUN4I_HDMI_DDC_CLK_N_GET(reg) \ + ((reg) & SUN4I_HDMI_DDC_CLK_N_MASK) + +#define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540 +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9) +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8) + +#define SUN4I_HDMI_DDC_FIFO_SIZE 16 + +/* A31 specific */ +#define SUN6I_HDMI_DDC_CTRL_REG 0x500 +#define SUN6I_HDMI_DDC_CTRL_RESET BIT(31) +#define SUN6I_HDMI_DDC_CTRL_START_CMD BIT(27) +#define SUN6I_HDMI_DDC_CTRL_SDA_ENABLE BIT(6) +#define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4) +#define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0) + +#define SUN6I_HDMI_DDC_CMD_REG 0x508 +#define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16) +/* command types in lower 3 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_ADDR_REG 0x50c +#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) +#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) +#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) +#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) << 1) & GENMASK(7, 1)) + +#define SUN6I_HDMI_DDC_INT_STATUS_REG 0x514 +#define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8) +/* lower 8 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_FIFO_CTRL_REG 0x518 +#define SUN6I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(15) +/* lower 9 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_CLK_REG 0x520 +/* DDC CLK bit fields are the same, but the formula is not */ + +#define SUN6I_HDMI_DDC_FIFO_DATA_REG 0x580 + +#endif From 75bc7e4f13f3c8df8758e3d26f9658183994e56e Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 1 Mar 2018 08:38:31 +0100 Subject: [PATCH 76/93] drm/sun4i: make fifo_transfer more verbose in case of error The fifo_transfer call currently makes an assumption on the clockrate of the hdmi-i2c bus. However this assumption is based on the fact that the clockrate is exactly what we request, e.g. if we set the rate to 100 kHz the clock runs on exactly 100 kHz. This unfortunately is not exactly true, as the actual rate depends on the parents and we may end up with a slower clock. Further more if the clock changes from the 100 kHz now this assumption may also not hold. The remedy is easily fixed by asking the clk framework the actual clockrate, with adding 8 clocks (1 byte) as a safety margin. Further more, in case of error, it really helps to know that this timeout has occurred so add a print statement for that. Finally, the variable was renamed to more accurately tell us what it is, a byte time in micro seconds, not nano seconds. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c index 3c7c020fc99da9..a92a36a633097e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -134,15 +134,12 @@ static const struct sun4i_hdmi_i2c_variant sun6i_variant = { static int fifo_transfer(struct sun4i_hdmi_i2c_drv *drv, u8 *buf, int len, bool read) { - /* - * 1 byte takes 9 clock cycles (8 bits + 1 ACK) = 90 us for 100 kHz - * clock. As clock rate is fixed, just round it up to 100 us. - */ - const unsigned long byte_time_ns = 100; const u32 mask = SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; u32 reg; + unsigned long byte_time_us; + /* * If threshold is inclusive, then the FIFO may only have * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1. @@ -156,11 +153,19 @@ static int fifo_transfer(struct sun4i_hdmi_i2c_drv *drv, u8 *buf, int len, bool */ len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE); + /* + * 1 byte takes 9 clock cycles (8 bits + 1 ACK) times the number of + * bytes to be transmitted. One additional 'round-up' byte is added + * as a margin. + */ + byte_time_us = (len + 1) * 9 * clk_get_rate(drv->ddc_clk) / 10000; + /* Wait until error, FIFO request bit set or transfer complete */ if (regmap_field_read_poll_timeout(drv->field_ddc_int_status, reg, - reg & mask, len * byte_time_ns, - 100000)) + reg & mask, byte_time_us, 100000)) { + dev_err(drv->dev, "DDC bus timeout after %lu us\n", byte_time_us); return -ETIMEDOUT; + } if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) return -EIO; From 46fc76bdbc20bce0c5944860bc6b19eac18729e7 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Thu, 1 Mar 2018 08:19:44 +0100 Subject: [PATCH 77/93] drm: sun4i: check for i2c busy status The HDMI-I2C block has a bus busy indicator, which indicates whether the bus is busy or not. Make use of this to check if the bus is busy before attempting to transmit. This prevents any unnoticed errors when unable being to transmit due to a busy bus. Signed-off-by: Olliver Schinagl --- drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 77 ++++++++++++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h | 11 ++++ 2 files changed, 88 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c index a92a36a633097e..f1d038febb2edb 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -38,6 +38,13 @@ struct sun4i_hdmi_i2c_variant { struct reg_field field_ddc_cmd; struct reg_field field_ddc_sda_en; struct reg_field field_ddc_sck_en; + struct reg_field field_ddc_bus_busy; + struct reg_field field_ddc_sda_state; + struct reg_field field_ddc_sck_state; + struct reg_field field_ddc_sda_line_ctrl_en; + struct reg_field field_ddc_sck_line_ctrl_en; + struct reg_field field_ddc_sda_line_ctrl; + struct reg_field field_ddc_sck_line_ctrl; /* DDC FIFO register offset */ u32 ddc_fifo_reg; @@ -82,6 +89,13 @@ struct sun4i_hdmi_i2c_drv { struct regmap_field *field_ddc_cmd; struct regmap_field *field_ddc_sda_en; struct regmap_field *field_ddc_sck_en; + struct regmap_field *field_ddc_bus_busy; + struct regmap_field *field_ddc_sda_state; + struct regmap_field *field_ddc_sck_state; + struct regmap_field *field_ddc_sda_line_ctrl; + struct regmap_field *field_ddc_sck_line_ctrl; + struct regmap_field *field_ddc_sda_line_ctrl_en; + struct regmap_field *field_ddc_sck_line_ctrl_en; const struct sun4i_hdmi_i2c_variant *variant; }; @@ -104,6 +118,13 @@ static const struct sun4i_hdmi_i2c_variant sun4i_variant = { .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), + .field_ddc_bus_busy = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG, 10, 10), + .field_ddc_sda_state = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG, 9, 9), + .field_ddc_sck_state = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG, 8, 8), + .field_ddc_sck_line_ctrl = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG, 3, 3), + .field_ddc_sck_line_ctrl_en = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG, 2, 2), + .field_ddc_sda_line_ctrl = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG, 1, 1), + .field_ddc_sda_line_ctrl_en = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG, 0, 0), .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, .ddc_fifo_has_dir = true, @@ -127,6 +148,13 @@ static const struct sun4i_hdmi_i2c_variant sun6i_variant = { .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2), .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6), .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4), + .field_ddc_bus_busy = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG, 10, 10), + .field_ddc_sda_state = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG, 9, 9), + .field_ddc_sck_state = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG, 8, 8), + .field_ddc_sck_line_ctrl = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG, 3, 3), + .field_ddc_sck_line_ctrl_en = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG, 2, 2), + .field_ddc_sda_line_ctrl = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG, 1, 1), + .field_ddc_sda_line_ctrl_en = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG, 0, 0), .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG, .ddc_fifo_thres_incl = true, @@ -184,9 +212,16 @@ static int fifo_transfer(struct sun4i_hdmi_i2c_drv *drv, u8 *buf, int len, bool static int xfer_msg(struct sun4i_hdmi_i2c_drv *drv, struct i2c_msg *msg) { + unsigned int bus_busy = 0; int i, len; u32 reg; + regmap_field_read(drv->field_ddc_bus_busy, &bus_busy); + if (bus_busy) { + dev_err(drv->dev, "failed to transfer data, bus busy\n"); + return -EAGAIN; + } + /* Set FIFO direction */ if (drv->variant->ddc_fifo_has_dir) { reg = readl(drv->base + SUN4I_HDMI_DDC_CTRL_REG); @@ -396,6 +431,48 @@ static int sun4i_hdmi_i2c_init_regmap_fields(struct sun4i_hdmi_i2c_drv *drv) if (IS_ERR(drv->field_ddc_sck_en)) return PTR_ERR(drv->field_ddc_sck_en); + drv->field_ddc_bus_busy = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_bus_busy); + if (IS_ERR(drv->field_ddc_bus_busy)) + return PTR_ERR(drv->field_ddc_bus_busy); + + drv->field_ddc_sda_state = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_state); + if (IS_ERR(drv->field_ddc_sda_state)) + return PTR_ERR(drv->field_ddc_sda_state); + + drv->field_ddc_sck_state = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_state); + if (IS_ERR(drv->field_ddc_sck_state)) + return PTR_ERR(drv->field_ddc_sck_state); + + drv->field_ddc_sda_line_ctrl = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_line_ctrl); + if (IS_ERR(drv->field_ddc_sda_line_ctrl)) + return PTR_ERR(drv->field_ddc_sda_line_ctrl); + + drv->field_ddc_sck_line_ctrl = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_line_ctrl); + if (IS_ERR(drv->field_ddc_sck_line_ctrl)) + return PTR_ERR(drv->field_ddc_sck_line_ctrl); + + drv->field_ddc_sda_line_ctrl_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_line_ctrl_en); + if (IS_ERR(drv->field_ddc_sda_line_ctrl_en)) + return PTR_ERR(drv->field_ddc_sda_line_ctrl_en); + + drv->field_ddc_sck_line_ctrl_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_line_ctrl_en); + if (IS_ERR(drv->field_ddc_sck_line_ctrl_en)) + return PTR_ERR(drv->field_ddc_sck_line_ctrl_en); + return 0; } diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h index 6296c822da2a2e..a747998157203d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.h @@ -60,6 +60,15 @@ #define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 0x5 #define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 0x3 +#define SUN4I_HDMI_DDC_EXT_REG 0x524 +#define SUN4I_HDMI_DDC_EXT_BUS_BUSY BIT(10) +#define SUN4I_HDMI_DDC_EXT_SDA_STATE BIT(9) +#define SUN4I_HDMI_DDC_EXT_SCK_STATE BIT(8) +#define SUN4I_HDMI_DDC_EXT_SCL_LINE_CTRL BIT(3) +#define SUN4I_HDMI_DDC_EXT_SCL_LINE_CTRL_EN BIT(2) +#define SUN4I_HDMI_DDC_EXT_SDA_LINE_CTRL BIT(1) +#define SUN4I_HDMI_DDC_EXT_SDA_LINE_CTRL_EN BIT(0) + #define SUN4I_HDMI_DDC_CLK_REG 0x528 #define SUN4I_HDMI_DDC_CLK_M_OFFSET 3 #define SUN4I_HDMI_DDC_CLK_M_MASK GENMASK(7, 4) @@ -87,6 +96,8 @@ #define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4) #define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0) +#define SUN6I_HDMI_DDC_EXT_REG 0x504 + #define SUN6I_HDMI_DDC_CMD_REG 0x508 #define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16) /* command types in lower 3 bits are the same as sun4i */ From 0c15f4543517b1eed606bc01d0230b9d0bc298e0 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 20 Oct 2017 15:54:38 +0200 Subject: [PATCH 78/93] [WIP-do-not-merge] drm/sunxi: Split off hdmi-i2c as a separated driver With this patch, the hdmi-i2c peripheral is split off into its own identity. By doing so, this gives board designers and users various new uses. It is now possible to use the ddc-i2c-bus property on the display node to actually choose an I2C controller. E.g. if a board designer chooses to use a different I2C controller for the EDID eeprom, they are free to do so. The HDMI-I2C controller node can now have various frequency settings. In other words if due to layout/routing issues the default clock of 100 kHz is to high for stable operation, the clock could be lowered to a supported frequency for that board. Supporting peripherals on the other end of the bus. Some monitors support various sensors or controls over the I2C bus. Think monitor orientation, back light, brightness, touch etc control. By having a separated node it becomes possible to configure these devices. Note, that this is a bad example in the sense that while a monitor (HDMI) supports hot plugging, I2C device detection does not. This is a potentially future feature, e.g. a hotplug event re-checks the bus and re-probes I2C drivers on the bus. Not working properly: Legacy clocking. For some reason when using an old devicetree the clocks are not setup properly. A few future improvements are left TODO: * Re-probe i2c bus after hot plugging of the HDMI connector. * I2C clock recovery Signed-off-by: Olliver Schinagl --- .../bindings/display/sunxi/sun4i-drm.txt | 52 +- arch/arm/boot/dts/sun7i-a20.dtsi | 23 +- drivers/gpu/drm/sun4i/Kconfig | 8 + drivers/gpu/drm/sun4i/Makefile | 8 +- drivers/gpu/drm/sun4i/sun4i_hdmi.h | 12 +- drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 39 +- drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.h | 23 + drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 259 ++++---- drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 499 ++------------- drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.c | 598 ++++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.h | 238 +++++++ drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 20 +- 12 files changed, 1188 insertions(+), 591 deletions(-) create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.h diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index 769b512d1b3cf1..26346f9d299148 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -50,9 +50,8 @@ Required properties: * pll-0: the first video PLL * pll-1: the second video PLL - clock-names: the clock names mentioned above + - clock-output-names: the name of the output clock by the HDMI/TMDS encoder - dmas: phandles to the DMA channels used by the HDMI encoder - * ddc-tx: The channel for DDC transmission - * ddc-rx: The channel for DDC reception * audio-tx: The channel used for audio transmission - dma-names: the channel names mentioned above @@ -61,6 +60,32 @@ Required properties: first port should be the input endpoint. The second should be the output, usually to an HDMI connector. +Optional properties: + - ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing + +HDMI-I2C bus driver +------------------- + +The HDMI-I2C bus controller is implemented using a reasonably common I2C block +and can be used as a regular I2C device in a board layout. The only caveat +however is that the bus timing is dependent on the TMDS clock and thus the +exact I2C bus frequency may not be achieved. + +Required properties: + - compatible: value must be one of: + * allwinner,sun4i-a10-hdmi-i2c + * allwinner,sun6i-a31-hdmi-i2c + - reg: base address and size of the memory-mapped region + - clocks: phandle to the clock feeding the HDMI-I2C bus controller + * hdmi: the HDMI/TMDS peripheral is the input clock, common on sun4i + * ddc: a dedicated DDC clock is the input clock, common on sun6i + - dmas: phandles to the DMA channels used by the HDMI-I2C bus controller + * ddc-tx: The channel for DDC transmission + * ddc-rx: The channel for DDC reception + +Optional properties: + - clock-frequency: see the common i2c binding document + TV Encoder ---------- @@ -277,16 +302,16 @@ connector { hdmi: hdmi@01c16000 { compatible = "allwinner,sun5i-a10s-hdmi"; - reg = <0x01c16000 0x1000>; + reg = <0x01c16000 0x500>; interrupts = <58>; clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>, <&ccu CLK_PLL_VIDEO0_2X>, <&ccu CLK_PLL_VIDEO1_2X>; clock-names = "ahb", "mod", "pll-0", "pll-1"; - dmas = <&dma SUN4I_DMA_NORMAL 16>, - <&dma SUN4I_DMA_NORMAL 16>, - <&dma SUN4I_DMA_DEDICATED 24>; - dma-names = "ddc-tx", "ddc-rx", "audio-tx"; + clock-output-names = "hdmi-tmds"; + ddc-i2c-bus = <&hdmi_i2c>; + dmas = <&dma SUN4I_DMA_DEDICATED 24>; + dma-names = "audio-tx"; ports { #address-cells = <1>; @@ -314,6 +339,19 @@ hdmi: hdmi@01c16000 { }; }; +hdmi_i2c: i2c@16500 { + compatible = "allwinner,sun7i-a20-hdmi-i2c", + "allwinner,sun4i-a10-hdmi-i2c"; + reg = <0x01c16500 0x40>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&hdmi>; + dmas = <&dma SUN4I_DMA_NORMAL 16>, + <&dma SUN4I_DMA_NORMAL 16>, + dma-names = "ddc-tx", "ddc-rx"; + status = "disabled"; +}; + tve0: tv-encoder@01c0a000 { compatible = "allwinner,sun4i-a10-tv-encoder"; reg = <0x01c0a000 0x1000>; diff --git a/arch/arm/boot/dts/sun7i-a20.dtsi b/arch/arm/boot/dts/sun7i-a20.dtsi index a917276aac9290..d27dca82cdad7b 100644 --- a/arch/arm/boot/dts/sun7i-a20.dtsi +++ b/arch/arm/boot/dts/sun7i-a20.dtsi @@ -575,16 +575,16 @@ hdmi: hdmi@1c16000 { compatible = "allwinner,sun7i-a20-hdmi", "allwinner,sun5i-a10s-hdmi"; - reg = <0x01c16000 0x1000>; + reg = <0x01c16000 0x500>; interrupts = ; clocks = <&ccu CLK_AHB_HDMI0>, <&ccu CLK_HDMI>, <&ccu CLK_PLL_VIDEO0_2X>, <&ccu CLK_PLL_VIDEO1_2X>; clock-names = "ahb", "mod", "pll-0", "pll-1"; - dmas = <&dma SUN4I_DMA_NORMAL 16>, - <&dma SUN4I_DMA_NORMAL 16>, - <&dma SUN4I_DMA_DEDICATED 24>; - dma-names = "ddc-tx", "ddc-rx", "audio-tx"; + #clock-cells = <0>; + clock-output-names = "hdmi-tmds"; + dmas = <&dma SUN4I_DMA_DEDICATED 24>; + dma-names = "audio-tx"; status = "disabled"; ports { @@ -615,6 +615,19 @@ }; }; + hdmi_i2c: i2c@16500 { + compatible = "allwinner,sun7i-a20-hdmi-i2c", + "allwinner,sun4i-a10-hdmi-i2c"; + reg = <0x01c16500 0x40>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&hdmi>; + dmas = <&dma SUN4I_DMA_NORMAL 16>, + <&dma SUN4I_DMA_NORMAL 16>; + dma-names = "ddc-tx", "ddc-rx"; + status = "disabled"; + }; + spi2: spi@1c17000 { compatible = "allwinner,sun4i-a10-spi"; reg = <0x01c17000 0x1000>; diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 7b6d6930530837..3eff346b593c79 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -22,6 +22,14 @@ config DRM_SUN4I_HDMI Choose this option if you have an Allwinner SoC with an HDMI controller. +config I2C_SUN4I_HDMI + tristate "Allwinner A10 HDMI I2C Support" + depends on DRM_SUN4I_HDMI + select I2C + help + Choose this option if you have an Allwinner SoC with an I2C + enabled HDMI controller and want to enable the I2C channel. + config DRM_SUN4I_HDMI_CEC bool "Allwinner A10 HDMI CEC Support" depends on DRM_SUN4I_HDMI diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 0c2f8c7facae70..34745338824776 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -4,9 +4,11 @@ sun4i-backend-y += sun4i_backend.o sun4i_layer.o sun4i-drm-y += sun4i_drv.o sun4i-drm-y += sun4i_framebuffer.o -sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o +sun4i-hdmi-i2c-y += sun4i_hdmi_i2c_drv.o sun4i_hdmi_ddc_clk.o +sun4i-hdmi-i2c-y += sun4i_hdmi_i2c.o + +sun4i-drm-hdmi-y += sun4i_hdmi_i2c_drv.o sun4i_hdmi_ddc_clk.o sun4i-drm-hdmi-y += sun4i_hdmi_enc.o -sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o @@ -24,3 +26,5 @@ obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o + +obj-$(CONFIG_I2C_SUN4I_HDMI) += sun4i-hdmi-i2c.o diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index a63f6ad8103aa2..88fed25855eccd 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h @@ -126,10 +126,6 @@ struct sun4i_hdmi_variant { u32 pad_ctrl1_init_val; u32 pll_ctrl_init_val; - struct reg_field ddc_clk_reg; - u8 ddc_clk_pre_divider; - u8 ddc_clk_m_offset; - u8 tmds_clk_div_offset; /* Register fields for I2C adapter */ @@ -173,7 +169,6 @@ struct sun4i_hdmi { struct device *dev; void __iomem *base; - struct regmap *regmap; /* Reset control */ struct reset_control *reset; @@ -181,15 +176,16 @@ struct sun4i_hdmi { /* Parent clocks */ struct clk *bus_clk; struct clk *mod_clk; - struct clk *ddc_parent_clk; struct clk *pll0_clk; struct clk *pll1_clk; /* And the clocks we create */ - struct clk *ddc_clk; struct clk *tmds_clk; + const char *tmds_clk_name; struct i2c_adapter *i2c; + /* Legacy dt i2c device */ + struct sun4i_hdmi_i2c_drv *i2c_drv; /* Regmap fields for I2C adapter */ struct regmap_field *field_ddc_en; @@ -215,8 +211,6 @@ struct sun4i_hdmi { const struct sun4i_hdmi_variant *variant; }; -int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); int sun4i_tmds_create(struct sun4i_hdmi *hdmi); -int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi); #endif /* _SUN4I_HDMI_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c index 68d04e86b89b2e..0a902fcd58ebbd 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c @@ -10,16 +10,20 @@ * the License, or (at your option) any later version. */ +#include #include +#include +#include +#include #include +#include -#include "sun4i_hdmi.h" -#include "sun4i_hdmi_i2c.h" +#include "sun4i_hdmi_i2c_drv.h" struct sun4i_ddc { struct clk_hw hw; - struct sun4i_hdmi *hdmi; struct regmap_field *reg; + struct clk *parent_clk; u8 pre_div; u8 m_offset; }; @@ -110,7 +114,9 @@ static const struct clk_ops sun4i_ddc_ops = { .set_rate = sun4i_ddc_set_rate, }; -int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) +struct clk *sun4i_ddc_create(struct device *dev, struct regmap *regmap, + const struct sun4i_hdmi_i2c_variant *variant, + const struct clk *parent) { struct clk_init_data init; struct sun4i_ddc *ddc; @@ -118,30 +124,25 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent) parent_name = __clk_get_name(parent); if (!parent_name) - return -ENODEV; + return ERR_PTR(-ENODEV); - ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL); + ddc = devm_kzalloc(dev, sizeof(*ddc), GFP_KERNEL); if (!ddc) - return -ENOMEM; + return ERR_PTR(-ENOMEM); - ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap, - hdmi->variant->ddc_clk_reg); + ddc->reg = devm_regmap_field_alloc(dev, regmap, variant->ddc_clk_reg); if (IS_ERR(ddc->reg)) - return PTR_ERR(ddc->reg); + return ERR_CAST(ddc->reg); - init.name = "hdmi-ddc"; + init.name = "hdmi-i2c"; init.ops = &sun4i_ddc_ops; init.parent_names = &parent_name; init.num_parents = 1; + init.flags = CLK_RECALC_NEW_RATES; - ddc->hdmi = hdmi; ddc->hw.init = &init; - ddc->pre_div = hdmi->variant->ddc_clk_pre_divider; - ddc->m_offset = hdmi->variant->ddc_clk_m_offset; + ddc->pre_div = variant->ddc_clk_pre_divider; + ddc->m_offset = variant->ddc_clk_m_offset; - hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw); - if (IS_ERR(hdmi->ddc_clk)) - return PTR_ERR(hdmi->ddc_clk); - - return 0; + return devm_clk_register(dev, &ddc->hw); } diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.h b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.h new file mode 100644 index 00000000000000..12f86c378542b3 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018 Olliver Schinagl + * + * Olliver Schinagl + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#ifndef _SUN4I_HDMI_DDC_CLK_H_ +#define _SUN4I_HDMI_DDC_CLK_H_ + +#include +#include +#include + +#include "sun4i_hdmi_i2c_drv.h" + +struct clk *sun4i_ddc_create(struct device *dev, struct regmap *regmap, + const struct sun4i_hdmi_i2c_variant *variant, + const struct clk *parent); + +#endif diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 2f3039da16966f..eb5c85abaf35fe 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -18,19 +18,23 @@ #include #include +#include #include +#include +#include #include #include +#include #include +#include #include -#include #include #include "sun4i_backend.h" #include "sun4i_crtc.h" #include "sun4i_drv.h" #include "sun4i_hdmi.h" -#include "sun4i_hdmi_i2c.h" +#include "sun4i_hdmi_i2c_drv.h" static inline struct sun4i_hdmi * drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) @@ -281,6 +285,7 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { /* Only difference from sun5i is AMP is 4 instead of 6 */ static const struct sun4i_hdmi_variant sun4i_variant = { + .has_ddc_parent_clk = true, .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN | SUN4I_HDMI_PAD_CTRL0_PWENG | @@ -308,12 +313,10 @@ static const struct sun4i_hdmi_variant sun4i_variant = { SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS | SUN4I_HDMI_PLL_CTRL_PLL_EN, - - .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, - .ddc_fifo_has_dir = true, }; static const struct sun4i_hdmi_variant sun5i_variant = { + .has_ddc_parent_clk = true, .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN | SUN4I_HDMI_PAD_CTRL0_PWENG | @@ -344,7 +347,6 @@ static const struct sun4i_hdmi_variant sun5i_variant = { }; static const struct sun4i_hdmi_variant sun6i_variant = { - .has_ddc_parent_clk = true, .has_reset_control = true, .pad_ctrl0_init_val = 0xff | SUN4I_HDMI_PAD_CTRL0_TXEN | @@ -379,109 +381,28 @@ static const struct sun4i_hdmi_variant sun6i_variant = { .tmds_clk_div_offset = 1, }; -static const struct regmap_config sun4i_hdmi_regmap_config = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, - .max_register = 0x580, -}; - static int sun4i_hdmi_bind(struct device *dev, struct device *master, void *data) { - struct platform_device *pdev = to_platform_device(dev); + struct device_node *i2c_np; + struct device_node *node = dev_of_node(dev); struct drm_device *drm = data; struct sun4i_drv *drv = drm->dev_private; struct sun4i_hdmi *hdmi; - struct resource *res; u32 reg; int ret; - hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); - if (!hdmi) - return -ENOMEM; - dev_set_drvdata(dev, hdmi); - hdmi->dev = dev; - hdmi->drv = drv; - - hdmi->variant = of_device_get_match_data(dev); - if (!hdmi->variant) - return -EINVAL; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hdmi->base = devm_ioremap_resource(dev, res); - if (IS_ERR(hdmi->base)) { - dev_err(dev, "Couldn't map the HDMI encoder registers\n"); - return PTR_ERR(hdmi->base); + hdmi = dev_get_drvdata(dev); + if (!hdmi) { + dev_err(dev, "HDMI not initialized\n"); + return -ENODEV; } - if (hdmi->variant->has_reset_control) { - hdmi->reset = devm_reset_control_get(dev, NULL); - if (IS_ERR(hdmi->reset)) { - dev_err(dev, "Couldn't get the HDMI reset control\n"); - return PTR_ERR(hdmi->reset); - } - - ret = reset_control_deassert(hdmi->reset); - if (ret) { - dev_err(dev, "Couldn't deassert HDMI reset\n"); - return ret; - } - } + hdmi->drv = drv; - hdmi->bus_clk = devm_clk_get(dev, "ahb"); - if (IS_ERR(hdmi->bus_clk)) { - dev_err(dev, "Couldn't get the HDMI bus clock\n"); - ret = PTR_ERR(hdmi->bus_clk); - goto err_assert_reset; - } clk_prepare_enable(hdmi->bus_clk); - - hdmi->mod_clk = devm_clk_get(dev, "mod"); - if (IS_ERR(hdmi->mod_clk)) { - dev_err(dev, "Couldn't get the HDMI mod clock\n"); - ret = PTR_ERR(hdmi->mod_clk); - goto err_disable_bus_clk; - } clk_prepare_enable(hdmi->mod_clk); - hdmi->pll0_clk = devm_clk_get(dev, "pll-0"); - if (IS_ERR(hdmi->pll0_clk)) { - dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n"); - ret = PTR_ERR(hdmi->pll0_clk); - goto err_disable_mod_clk; - } - - hdmi->pll1_clk = devm_clk_get(dev, "pll-1"); - if (IS_ERR(hdmi->pll1_clk)) { - dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n"); - ret = PTR_ERR(hdmi->pll1_clk); - goto err_disable_mod_clk; - } - - hdmi->regmap = devm_regmap_init_mmio(dev, hdmi->base, - &sun4i_hdmi_regmap_config); - if (IS_ERR(hdmi->regmap)) { - dev_err(dev, "Couldn't create HDMI encoder regmap\n"); - return PTR_ERR(hdmi->regmap); - } - - ret = sun4i_tmds_create(hdmi); - if (ret) { - dev_err(dev, "Couldn't create the TMDS clock\n"); - goto err_disable_mod_clk; - } - - if (hdmi->variant->has_ddc_parent_clk) { - hdmi->ddc_parent_clk = devm_clk_get(dev, "ddc"); - if (IS_ERR(hdmi->ddc_parent_clk)) { - dev_err(dev, "Couldn't get the HDMI DDC clock\n"); - return PTR_ERR(hdmi->ddc_parent_clk); - } - } else { - hdmi->ddc_parent_clk = hdmi->tmds_clk; - } - writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG); writel(hdmi->variant->pad_ctrl0_init_val, @@ -492,10 +413,30 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, reg |= hdmi->variant->pll_ctrl_init_val; writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG); - ret = sun4i_hdmi_i2c_create(dev, hdmi); - if (ret) { + i2c_np = of_parse_phandle(node, "ddc-i2c-bus", 0); + if (!i2c_np) { + dev_warn(dev, "Missing ddc-i2c-bus node in %pOF\n", node); + + /* legacy devicetree's do not have the hdmi-i2c node */ + if (hdmi->variant->has_ddc_parent_clk) + hdmi->i2c_drv = sun4i_hdmi_i2c_setup(dev, hdmi->base, + hdmi->variant->has_ddc_parent_clk ? + hdmi->tmds_clk : NULL); + if (IS_ERR(hdmi->i2c_drv)) { + if (PTR_ERR(hdmi->i2c_drv) != -EPROBE_DEFER) + dev_err(dev, "Couldn't setup HDMI I2C driver\n"); + ret = PTR_ERR(hdmi->i2c_drv); + goto err_disable_clks; + } + + hdmi->i2c = &hdmi->i2c_drv->adap; + } else { + hdmi->i2c = of_find_i2c_adapter_by_node(i2c_np); + } + if (!hdmi->i2c) { dev_err(dev, "Couldn't create the HDMI I2C adapter\n"); - goto err_disable_mod_clk; + ret = -ENODEV; + goto err_disable_clks; } drm_encoder_helper_add(&hdmi->encoder, @@ -507,14 +448,12 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, NULL); if (ret) { dev_err(dev, "Couldn't initialise the HDMI encoder\n"); - goto err_del_i2c_adapter; + return -ENODEV; } - hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, - dev->of_node); + hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm, node); if (!hdmi->encoder.possible_crtcs) { - ret = -EPROBE_DEFER; - goto err_del_i2c_adapter; + return -EPROBE_DEFER; } #ifdef CONFIG_DRM_SUN4I_HDMI_CEC @@ -553,14 +492,9 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, err_cleanup_connector: cec_delete_adapter(hdmi->cec_adap); drm_encoder_cleanup(&hdmi->encoder); -err_del_i2c_adapter: - i2c_del_adapter(hdmi->i2c); -err_disable_mod_clk: +err_disable_clks: clk_disable_unprepare(hdmi->mod_clk); -err_disable_bus_clk: clk_disable_unprepare(hdmi->bus_clk); -err_assert_reset: - reset_control_assert(hdmi->reset); return ret; } @@ -569,10 +503,11 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master, { struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); + if (!hdmi->i2c_drv) + put_device(&hdmi->i2c->dev); cec_unregister_adapter(hdmi->cec_adap); drm_connector_cleanup(&hdmi->connector); drm_encoder_cleanup(&hdmi->encoder); - i2c_del_adapter(hdmi->i2c); clk_disable_unprepare(hdmi->mod_clk); clk_disable_unprepare(hdmi->bus_clk); } @@ -584,12 +519,118 @@ static const struct component_ops sun4i_hdmi_ops = { static int sun4i_hdmi_probe(struct platform_device *pdev) { + struct device_node *node = dev_of_node(&pdev->dev); + struct sun4i_hdmi *hdmi; + struct resource *res; + struct device *dev = &pdev->dev; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + hdmi->dev = dev; + + hdmi->variant = of_device_get_match_data(dev); + if (!hdmi->variant) { + dev_err(dev, "hdmi_tmds_clk: couldn't find matching device\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdmi->base)) { + dev_err(dev, "couldn't map the HDMI encoder registers\n"); + return PTR_ERR(hdmi->base); + } + + if (hdmi->variant->has_reset_control) { + hdmi->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(hdmi->reset)) { + dev_err(dev, "couldn't get the HDMI reset control\n"); + return PTR_ERR(hdmi->reset); + } + + ret = reset_control_deassert(hdmi->reset); + if (ret) { + dev_err(dev, "couldn't deassert HDMI reset\n"); + goto err_assert_reset; + } + } + + hdmi->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(hdmi->bus_clk)) { + dev_err(dev, "couldn't get the HDMI bus clock\n"); + ret = PTR_ERR(hdmi->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(hdmi->bus_clk); + + hdmi->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(hdmi->mod_clk)) { + dev_err(dev, "couldn't get the HDMI mod clock\n"); + ret = PTR_ERR(hdmi->mod_clk); + goto err_disable_bus_clk; + } + clk_prepare_enable(hdmi->mod_clk); + + hdmi->pll0_clk = devm_clk_get(dev, "pll-0"); + if (IS_ERR(hdmi->pll0_clk)) { + dev_err(dev, "couldn't get the HDMI PLL 0 clock\n"); + ret = PTR_ERR(hdmi->pll0_clk); + goto err_disable_mod_clk; + } + + hdmi->pll1_clk = devm_clk_get(dev, "pll-1"); + if (IS_ERR(hdmi->pll1_clk)) { + dev_err(dev, "couldn't get the HDMI PLL 1 clock\n"); + ret = PTR_ERR(hdmi->pll1_clk); + goto err_disable_mod_clk; + } + + ret = of_property_read_string(node, "clock-output-names", + &hdmi->tmds_clk_name); + if (ret) { + /* Deal with old/incomplete DTs */ + hdmi->tmds_clk_name = "hdmi-tmds"; + dev_warn(dev, "no 'clock-output-names', falling back to: %s\n", + hdmi->tmds_clk_name); + } + + ret = sun4i_tmds_create(hdmi); + if (ret) { + dev_err(dev, "couldn't create the TMDS clock\n"); + goto err_disable_mod_clk; + } + ret = of_clk_add_provider(node, of_clk_src_simple_get, hdmi->tmds_clk); + // TODO devm_of_clk_add_provider() + if (ret) { + dev_err(dev, "couldn't register the TMDS clock\n"); + goto err_disable_mod_clk; + } + + dev_set_drvdata(dev, hdmi); + return component_add(&pdev->dev, &sun4i_hdmi_ops); + +err_disable_mod_clk: + clk_disable_unprepare(hdmi->mod_clk); +err_disable_bus_clk: + clk_disable_unprepare(hdmi->bus_clk); +err_assert_reset: + reset_control_assert(hdmi->reset); + + return ret; } static int sun4i_hdmi_remove(struct platform_device *pdev) { + struct device_node *node = dev_of_node(&pdev->dev); + struct sun4i_hdmi *hdmi = dev_get_drvdata(&pdev->dev); + component_del(&pdev->dev, &sun4i_hdmi_ops); + sun4i_hdmi_i2c_fini(hdmi->i2c_drv); + of_clk_del_provider(node); return 0; } diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c index f1d038febb2edb..cb7e1d0d12ff24 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c @@ -1,104 +1,23 @@ /* - * Copyright (C) 2016 Maxime Ripard - * Copyright (C) 2017 Jonathan Liu + * Copyright (C) 2018 Olliver Schinagl + * + * Olliver Schinagl + * + * SPDX-License-Identifier: GPL-2.0+ * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. */ -#include -#include -#include - -#include "sun4i_hdmi.h" -#include "sun4i_hdmi_i2c.h" - -/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */ -#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX - -struct sun4i_hdmi_i2c_variant { - struct reg_field ddc_clk_reg; - u8 ddc_clk_pre_divider; - u8 ddc_clk_m_offset; - - /* Register fields for I2C adapter */ - struct reg_field field_ddc_en; - struct reg_field field_ddc_start; - struct reg_field field_ddc_reset; - struct reg_field field_ddc_addr_reg; - struct reg_field field_ddc_slave_addr; - struct reg_field field_ddc_int_mask; - struct reg_field field_ddc_int_status; - struct reg_field field_ddc_fifo_clear; - struct reg_field field_ddc_fifo_rx_thres; - struct reg_field field_ddc_fifo_tx_thres; - struct reg_field field_ddc_byte_count; - struct reg_field field_ddc_cmd; - struct reg_field field_ddc_sda_en; - struct reg_field field_ddc_sck_en; - struct reg_field field_ddc_bus_busy; - struct reg_field field_ddc_sda_state; - struct reg_field field_ddc_sck_state; - struct reg_field field_ddc_sda_line_ctrl_en; - struct reg_field field_ddc_sck_line_ctrl_en; - struct reg_field field_ddc_sda_line_ctrl; - struct reg_field field_ddc_sck_line_ctrl; - - /* DDC FIFO register offset */ - u32 ddc_fifo_reg; - - /* - * DDC FIFO threshold boundary conditions - * - * This is used to cope with the threshold boundary condition - * being slightly different on sun5i and sun6i. - * - * On sun5i the threshold is exclusive, i.e. does not include, - * the value of the threshold. ( > for RX; < for TX ) - * On sun6i the threshold is inclusive, i.e. includes, the - * value of the threshold. ( >= for RX; <= for TX ) - */ - bool ddc_fifo_thres_incl; - - bool ddc_fifo_has_dir; -}; - -struct sun4i_hdmi_i2c_drv { - struct device *dev; +#include +#include +#include +#include +#include +#include +#include - void __iomem *base; - struct regmap *regmap; +#include "sun4i_hdmi_i2c_drv.h" - struct clk *ddc_clk; - - struct i2c_adapter adap; - - struct regmap_field *field_ddc_en; - struct regmap_field *field_ddc_start; - struct regmap_field *field_ddc_reset; - struct regmap_field *field_ddc_addr_reg; - struct regmap_field *field_ddc_slave_addr; - struct regmap_field *field_ddc_int_mask; - struct regmap_field *field_ddc_int_status; - struct regmap_field *field_ddc_fifo_clear; - struct regmap_field *field_ddc_fifo_rx_thres; - struct regmap_field *field_ddc_fifo_tx_thres; - struct regmap_field *field_ddc_byte_count; - struct regmap_field *field_ddc_cmd; - struct regmap_field *field_ddc_sda_en; - struct regmap_field *field_ddc_sck_en; - struct regmap_field *field_ddc_bus_busy; - struct regmap_field *field_ddc_sda_state; - struct regmap_field *field_ddc_sck_state; - struct regmap_field *field_ddc_sda_line_ctrl; - struct regmap_field *field_ddc_sck_line_ctrl; - struct regmap_field *field_ddc_sda_line_ctrl_en; - struct regmap_field *field_ddc_sck_line_ctrl_en; - - const struct sun4i_hdmi_i2c_variant *variant; -}; +#define SUN4I_HDMI_I2C_DRIVER_NAME "sun4i-hdmi-i2c" static const struct sun4i_hdmi_i2c_variant sun4i_variant = { .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), @@ -160,368 +79,70 @@ static const struct sun4i_hdmi_i2c_variant sun6i_variant = { .ddc_fifo_thres_incl = true, }; -static int fifo_transfer(struct sun4i_hdmi_i2c_drv *drv, u8 *buf, int len, bool read) -{ - const u32 mask = SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | - SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | - SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; - u32 reg; - unsigned long byte_time_us; - - /* - * If threshold is inclusive, then the FIFO may only have - * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1. - */ - int read_len = RX_THRESHOLD + - (drv->variant->ddc_fifo_thres_incl ? 0 : 1); - - /* - * Limit transfer length by FIFO threshold or FIFO size. - * For TX the threshold is for an empty FIFO. - */ - len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE); - - /* - * 1 byte takes 9 clock cycles (8 bits + 1 ACK) times the number of - * bytes to be transmitted. One additional 'round-up' byte is added - * as a margin. - */ - byte_time_us = (len + 1) * 9 * clk_get_rate(drv->ddc_clk) / 10000; - - /* Wait until error, FIFO request bit set or transfer complete */ - if (regmap_field_read_poll_timeout(drv->field_ddc_int_status, reg, - reg & mask, byte_time_us, 100000)) { - dev_err(drv->dev, "DDC bus timeout after %lu us\n", byte_time_us); - return -ETIMEDOUT; - } - - if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) - return -EIO; - - if (read) - readsb(drv->base + drv->variant->ddc_fifo_reg, buf, len); - else - writesb(drv->base + drv->variant->ddc_fifo_reg, buf, len); - - /* Clear FIFO request bit by forcing a write to that bit */ - regmap_field_force_write(drv->field_ddc_int_status, - SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST); +static const struct regmap_config sun4i_hdmi_i2c_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x80, +}; - return len; -} +static const struct of_device_id sun4i_hdmi_i2c_of_table[] = { + { .compatible = "allwinner,sun4i-a10-hdmi-i2c", .data = &sun4i_variant }, + { .compatible = "allwinner,sun6i-a31-hdmi-i2c", .data = &sun6i_variant }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sun4i_hdmi_i2c_of_table); -static int xfer_msg(struct sun4i_hdmi_i2c_drv *drv, struct i2c_msg *msg) +static int sun4i_hdmi_i2c_probe(struct platform_device *pdev) { - unsigned int bus_busy = 0; - int i, len; - u32 reg; + struct sun4i_hdmi_i2c_drv *drv; + struct resource *res; + void __iomem *base; - regmap_field_read(drv->field_ddc_bus_busy, &bus_busy); - if (bus_busy) { - dev_err(drv->dev, "failed to transfer data, bus busy\n"); - return -EAGAIN; - } + if (!pdev) + return -ENODEV; - /* Set FIFO direction */ - if (drv->variant->ddc_fifo_has_dir) { - reg = readl(drv->base + SUN4I_HDMI_DDC_CTRL_REG); - reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; - reg |= (msg->flags & I2C_M_RD) ? - SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : - SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; - writel(reg, drv->base + SUN4I_HDMI_DDC_CTRL_REG); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "couldn't map the HDMI-I2C registers\n"); + return PTR_ERR(base); } - /* Clear address register (not cleared by soft reset) */ - regmap_field_write(drv->field_ddc_addr_reg, 0); - - /* Set I2C address */ - regmap_field_write(drv->field_ddc_slave_addr, msg->addr); - - /* - * Set FIFO RX/TX thresholds and clear FIFO - * - * If threshold is inclusive, we can set the TX threshold to - * 0 instead of 1. - */ - regmap_field_write(drv->field_ddc_fifo_tx_thres, - drv->variant->ddc_fifo_thres_incl ? 0 : 1); - regmap_field_write(drv->field_ddc_fifo_rx_thres, RX_THRESHOLD); - regmap_field_write(drv->field_ddc_fifo_clear, 1); - if (regmap_field_read_poll_timeout(drv->field_ddc_fifo_clear, - reg, !reg, 100, 2000)) - return -EIO; - - /* Set transfer length */ - regmap_field_write(drv->field_ddc_byte_count, msg->len); - - /* Set command */ - regmap_field_write(drv->field_ddc_cmd, - msg->flags & I2C_M_RD ? - SUN4I_HDMI_DDC_CMD_IMPLICIT_READ : - SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE); - - /* Clear interrupt status bits by forcing a write */ - regmap_field_force_write(drv->field_ddc_int_status, - SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | - SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | - SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE); - - /* Start command */ - regmap_field_write(drv->field_ddc_start, 1); - - /* Transfer bytes */ - for (i = 0; i < msg->len; i += len) { - len = fifo_transfer(drv, msg->buf + i, msg->len - i, - msg->flags & I2C_M_RD); - if (len <= 0) - return len; + drv = sun4i_hdmi_i2c_init(&pdev->dev, base, sun4i_hdmi_i2c_of_table, + &sun4i_hdmi_i2c_regmap_config, NULL); + if (IS_ERR(drv)) { + if (PTR_ERR(drv) != -EPROBE_DEFER) + dev_err(&pdev->dev, "couldn't setup HDMI-I2C driver\n"); + return PTR_ERR(drv); } - /* Wait for command to finish */ - if (regmap_field_read_poll_timeout(drv->field_ddc_start, - reg, !reg, 100, 100000)) - return -EIO; - - /* Check for errors */ - regmap_field_read(drv->field_ddc_int_status, ®); - if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) || - !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) { - return -EIO; - } + platform_set_drvdata(pdev, drv); return 0; } -static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, - struct i2c_msg *msgs, int num) -{ - struct sun4i_hdmi_i2c_drv *drv = i2c_get_adapdata(adap); - u32 reg; - int err, i, ret = num; - - for (i = 0; i < num; i++) { - if (!msgs[i].len) - return -EINVAL; - if (msgs[i].len > SUN4I_HDMI_DDC_BYTE_COUNT_MAX) - return -EINVAL; - } - - /* DDC clock needs to be enabled for the module to work */ - clk_prepare_enable(drv->ddc_clk); - clk_set_rate(drv->ddc_clk, 100000); - - /* Reset I2C controller */ - regmap_field_write(drv->field_ddc_en, 1); - regmap_field_write(drv->field_ddc_reset, 1); - if (regmap_field_read_poll_timeout(drv->field_ddc_reset, - reg, !reg, 100, 2000)) { - clk_disable_unprepare(drv->ddc_clk); - return -EIO; - } - - regmap_field_write(drv->field_ddc_sck_en, 1); - regmap_field_write(drv->field_ddc_sda_en, 1); - - for (i = 0; i < num; i++) { - err = xfer_msg(drv, &msgs[i]); - if (err) { - ret = err; - break; - } - } - - clk_disable_unprepare(drv->ddc_clk); - return ret; -} - -static u32 sun4i_hdmi_i2c_func(struct i2c_adapter *adap) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; -} -static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = { - .master_xfer = sun4i_hdmi_i2c_xfer, - .functionality = sun4i_hdmi_i2c_func, -}; - -static int sun4i_hdmi_i2c_init_regmap_fields(struct sun4i_hdmi_i2c_drv *drv) +static int sun4i_hdmi_i2c_remove(struct platform_device *pdev) { - drv->field_ddc_en = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_en); - if (IS_ERR(drv->field_ddc_en)) - return PTR_ERR(drv->field_ddc_en); - - drv->field_ddc_start = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_start); - if (IS_ERR(drv->field_ddc_start)) - return PTR_ERR(drv->field_ddc_start); - - drv->field_ddc_reset = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_reset); - if (IS_ERR(drv->field_ddc_reset)) - return PTR_ERR(drv->field_ddc_reset); - - drv->field_ddc_addr_reg = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_addr_reg); - if (IS_ERR(drv->field_ddc_addr_reg)) - return PTR_ERR(drv->field_ddc_addr_reg); - - drv->field_ddc_slave_addr = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_slave_addr); - if (IS_ERR(drv->field_ddc_slave_addr)) - return PTR_ERR(drv->field_ddc_slave_addr); - - drv->field_ddc_int_mask = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_int_mask); - if (IS_ERR(drv->field_ddc_int_mask)) - return PTR_ERR(drv->field_ddc_int_mask); - - drv->field_ddc_int_status = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_int_status); - if (IS_ERR(drv->field_ddc_int_status)) - return PTR_ERR(drv->field_ddc_int_status); - - drv->field_ddc_fifo_clear = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_fifo_clear); - if (IS_ERR(drv->field_ddc_fifo_clear)) - return PTR_ERR(drv->field_ddc_fifo_clear); - - drv->field_ddc_fifo_rx_thres = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_fifo_rx_thres); - if (IS_ERR(drv->field_ddc_fifo_rx_thres)) - return PTR_ERR(drv->field_ddc_fifo_rx_thres); - - drv->field_ddc_fifo_tx_thres = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_fifo_tx_thres); - if (IS_ERR(drv->field_ddc_fifo_tx_thres)) - return PTR_ERR(drv->field_ddc_fifo_tx_thres); - - drv->field_ddc_byte_count = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_byte_count); - if (IS_ERR(drv->field_ddc_byte_count)) - return PTR_ERR(drv->field_ddc_byte_count); - - drv->field_ddc_cmd = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_cmd); - if (IS_ERR(drv->field_ddc_cmd)) - return PTR_ERR(drv->field_ddc_cmd); - - drv->field_ddc_sda_en = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sda_en); - if (IS_ERR(drv->field_ddc_sda_en)) - return PTR_ERR(drv->field_ddc_sda_en); + struct sun4i_hdmi_i2c_drv *drv = platform_get_drvdata(pdev); - drv->field_ddc_sck_en = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sck_en); - if (IS_ERR(drv->field_ddc_sck_en)) - return PTR_ERR(drv->field_ddc_sck_en); - - drv->field_ddc_bus_busy = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_bus_busy); - if (IS_ERR(drv->field_ddc_bus_busy)) - return PTR_ERR(drv->field_ddc_bus_busy); - - drv->field_ddc_sda_state = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sda_state); - if (IS_ERR(drv->field_ddc_sda_state)) - return PTR_ERR(drv->field_ddc_sda_state); - - drv->field_ddc_sck_state = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sck_state); - if (IS_ERR(drv->field_ddc_sck_state)) - return PTR_ERR(drv->field_ddc_sck_state); - - drv->field_ddc_sda_line_ctrl = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sda_line_ctrl); - if (IS_ERR(drv->field_ddc_sda_line_ctrl)) - return PTR_ERR(drv->field_ddc_sda_line_ctrl); - - drv->field_ddc_sck_line_ctrl = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sck_line_ctrl); - if (IS_ERR(drv->field_ddc_sck_line_ctrl)) - return PTR_ERR(drv->field_ddc_sck_line_ctrl); - - drv->field_ddc_sda_line_ctrl_en = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sda_line_ctrl_en); - if (IS_ERR(drv->field_ddc_sda_line_ctrl_en)) - return PTR_ERR(drv->field_ddc_sda_line_ctrl_en); - - drv->field_ddc_sck_line_ctrl_en = - devm_regmap_field_alloc(drv->dev, drv->regmap, - drv->variant->field_ddc_sck_line_ctrl_en); - if (IS_ERR(drv->field_ddc_sck_line_ctrl_en)) - return PTR_ERR(drv->field_ddc_sck_line_ctrl_en); + sun4i_hdmi_i2c_fini(drv); return 0; } -int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi) -{ - struct sun4i_hdmi_i2c_drv *drv; - int ret = 0; - - ret = sun4i_ddc_create(hdmi, hdmi->ddc_parent_clk); - if (ret) - return ret; - - drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); - if (!drv) - return -ENOMEM; - - drv->dev = dev; - - drv->base = hdmi->base; - drv->regmap = hdmi->regmap; - drv->ddc_clk = hdmi->ddc_clk; - - if (of_device_is_compatible(dev->of_node, "allwinner,sun4i-a10-hdmi")) - drv->variant = &sun4i_variant; - if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a10s-hdmi")) - drv->variant = &sun4i_variant; - if (of_device_is_compatible(dev->of_node, "allwinner,sun6i-a31-hdmi")) - drv->variant = &sun6i_variant; - if (!drv->variant) { - dev_err(dev, "unsupported platform variant"); - return -EINVAL; - } - - ret = sun4i_hdmi_i2c_init_regmap_fields(drv); - if (ret) - return ret; - - - i2c_set_adapdata(&drv->adap, drv); - drv->adap.owner = THIS_MODULE; - drv->adap.class = I2C_CLASS_DDC; - drv->adap.algo = &sun4i_hdmi_i2c_algorithm; - strlcpy(drv->adap.name, "sun4i_hdmi_i2c adapter", sizeof(drv->adap.name)); - - ret = i2c_add_adapter(&drv->adap); - if (ret) - return ret; - - hdmi->i2c = &drv->adap; +static struct platform_driver sun4i_hdmi_i2c_driver = { + .probe = sun4i_hdmi_i2c_probe, + .remove = sun4i_hdmi_i2c_remove, + .driver = { + .name = SUN4I_HDMI_I2C_DRIVER_NAME, + .of_match_table = sun4i_hdmi_i2c_of_table, + }, +}; +module_platform_driver(sun4i_hdmi_i2c_driver); - return ret; -} +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Olliver Schinagl "); +MODULE_DESCRIPTION("I2C adapter driver for Allwinner sunxi HDMI I2C bus"); +MODULE_ALIAS("platform:" SUN4I_HDMI_I2C_DRIVER_NAME); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.c new file mode 100644 index 00000000000000..1c3f7f53560238 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.c @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Copyright (C) 2017 Chen-Yu Tsai + * Copyright (C) 2017 Jonathan Liu + * Copyright (C) 2017 Olliver Schinagl + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun4i_hdmi_ddc_clk.h" +#include "sun4i_hdmi_i2c_drv.h" + +#define SUN4I_HDMI_I2C_SPEED_MAX 25000000 +#define SUN4I_HDMI_I2C_SPEED_DEFAULT 100000 + +/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */ +#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX + +static const struct sun4i_hdmi_i2c_variant sun4i_legacy_variant = { + .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 6), + .ddc_clk_pre_divider = 4, + .ddc_clk_m_offset = 1, + + .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 31, 31), + .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 30, 30), + .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 0), + .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 31), + .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 6), + .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 31, 31), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 9), + .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 9, 9), + .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 8, 8), + .field_ddc_bus_busy = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 10, 10), + .field_ddc_sda_state = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 9, 9), + .field_ddc_sck_state = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 8, 8), + .field_ddc_sck_line_ctrl = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 3, 3), + .field_ddc_sck_line_ctrl_en = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 2, 2), + .field_ddc_sda_line_ctrl = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 1, 1), + .field_ddc_sda_line_ctrl_en = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 0), + + .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG + + SUN4I_HDMI_DDC_OFFSET, + .ddc_fifo_has_dir = true, +}; + +static const struct sun4i_hdmi_i2c_variant sun6i_legacy_variant = { + .parent_clk_name = "ddc", + .ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 6), + .ddc_clk_pre_divider = 1, + .ddc_clk_m_offset = 2, + + .field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 0), + .field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 27, 27), + .field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 31, 31), + .field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG + + SUN4I_HDMI_DDC_OFFSET, 1, 31), + .field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG + + SUN4I_HDMI_DDC_OFFSET, 1, 7), + .field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 18, 18), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG + + SUN4I_HDMI_DDC_OFFSET, 16, 25), + .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 6, 6), + .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG + + SUN4I_HDMI_DDC_OFFSET, 4, 4), + .field_ddc_bus_busy = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 10, 10), + .field_ddc_sda_state = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 9, 9), + .field_ddc_sck_state = REG_FIELD(SUN4I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 8, 8), + .field_ddc_sck_line_ctrl = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 3, 3), + .field_ddc_sck_line_ctrl_en = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 2, 2), + .field_ddc_sda_line_ctrl = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 1, 1), + .field_ddc_sda_line_ctrl_en = REG_FIELD(SUN6I_HDMI_DDC_EXT_REG + + SUN4I_HDMI_DDC_OFFSET, 0, 0), + + .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG + + SUN4I_HDMI_DDC_OFFSET, + .ddc_fifo_thres_incl = true, +}; + +static const struct regmap_config sun4i_hdmi_i2c_legacy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x580, +}; + +static int fifo_transfer(struct sun4i_hdmi_i2c_drv *drv, u8 *buf, int len, bool read) +{ + const u32 mask = SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | + SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; + u32 reg; + unsigned long byte_time_us; + + /* + * If threshold is inclusive, then the FIFO may only have + * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1. + */ + int read_len = RX_THRESHOLD + + (drv->variant->ddc_fifo_thres_incl ? 0 : 1); + + /* + * Limit transfer length by FIFO threshold or FIFO size. + * For TX the threshold is for an empty FIFO. + */ + len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE); + + /* + * 1 byte takes 9 clock cycles (8 bits + 1 ACK) times the number of + * bytes to be transmitted. One additional 'round-up' byte is added + * as a margin. + */ + byte_time_us = (len + 1) * 9 * clk_get_rate(drv->ddc_clk) / 10000; + + /* Wait until error, FIFO request bit set or transfer complete */ + if (regmap_field_read_poll_timeout(drv->field_ddc_int_status, reg, + reg & mask, byte_time_us, 100000)) { + dev_err(drv->dev, "DDC bus timeout after %lu us\n", byte_time_us); + return -ETIMEDOUT; + } + + if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) + return -EIO; + + if (read) + readsb(drv->base + drv->variant->ddc_fifo_reg, buf, len); + else + writesb(drv->base + drv->variant->ddc_fifo_reg, buf, len); + + /* Clear FIFO request bit by forcing a write to that bit */ + regmap_field_force_write(drv->field_ddc_int_status, + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST); + + return len; +} + +static int xfer_msg(struct sun4i_hdmi_i2c_drv *drv, struct i2c_msg *msg) +{ + unsigned int bus_busy; + int i, len; + int err; + u32 reg; + + err = regmap_field_read(drv->field_ddc_bus_busy, &bus_busy); + if (err) + return err; + + if (bus_busy) { + dev_err(drv->dev, "failed to transfer data, bus busy\n"); + return -EAGAIN; + } + + /* Set FIFO direction */ + if (drv->variant->ddc_fifo_has_dir) { + reg = readl(drv->base + SUN4I_HDMI_DDC_CTRL_REG); + reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK; + reg |= (msg->flags & I2C_M_RD) ? + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ : + SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE; + writel(reg, drv->base + SUN4I_HDMI_DDC_CTRL_REG); + } + + /* Clear address register (not cleared by soft reset) */ + regmap_field_write(drv->field_ddc_addr_reg, 0); + + /* Set I2C address */ + regmap_field_write(drv->field_ddc_slave_addr, msg->addr); + + /* + * Set FIFO RX/TX thresholds and clear FIFO + * + * If threshold is inclusive, we can set the TX threshold to + * 0 instead of 1. + */ + regmap_field_write(drv->field_ddc_fifo_tx_thres, + drv->variant->ddc_fifo_thres_incl ? 0 : 1); + regmap_field_write(drv->field_ddc_fifo_rx_thres, RX_THRESHOLD); + regmap_field_write(drv->field_ddc_fifo_clear, 1); + if (regmap_field_read_poll_timeout(drv->field_ddc_fifo_clear, + reg, !reg, 100, 2000)) + return -EIO; + + /* Set transfer length */ + regmap_field_write(drv->field_ddc_byte_count, msg->len); + + /* Set command */ + regmap_field_write(drv->field_ddc_cmd, + msg->flags & I2C_M_RD ? + SUN4I_HDMI_DDC_CMD_IMPLICIT_READ : + SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE); + + /* Clear interrupt status bits by forcing a write */ + regmap_field_force_write(drv->field_ddc_int_status, + SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK | + SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST | + SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE); + + /* Start command */ + regmap_field_write(drv->field_ddc_start, 1); + + /* Transfer bytes */ + for (i = 0; i < msg->len; i += len) { + len = fifo_transfer(drv, msg->buf + i, msg->len - i, + msg->flags & I2C_M_RD); + if (len <= 0) + return len; + } + + /* Wait for command to finish */ + if (regmap_field_read_poll_timeout(drv->field_ddc_start, + reg, !reg, 100, 100000)) + return -EIO; + + /* Check for errors */ + regmap_field_read(drv->field_ddc_int_status, ®); + if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) || + !(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) { + return -EIO; + } + + return 0; +} + +static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct sun4i_hdmi_i2c_drv *drv = i2c_get_adapdata(adap); + u32 reg; + int err, i, ret = num; + + for (i = 0; i < num; i++) { + if (!msgs[i].len) + return -EINVAL; + if (msgs[i].len > SUN4I_HDMI_DDC_BYTE_COUNT_MAX) + return -EINVAL; + } + + /* DDC clock needs to be enabled for the module to work */ + clk_prepare_enable(drv->ddc_clk); + err = clk_set_rate(drv->ddc_clk, drv->clock_freq); + if (err) { + dev_err(drv->dev, "unable to set HDMI-I2C clock rate\n"); + ret = err; + goto exit; + } + + /* Reset I2C controller */ + regmap_field_write(drv->field_ddc_en, 1); + regmap_field_write(drv->field_ddc_reset, 1); + if (regmap_field_read_poll_timeout(drv->field_ddc_reset, + reg, !reg, 100, 2000)) { + ret = -EIO; + goto exit; + } + + regmap_field_write(drv->field_ddc_sck_en, 1); + regmap_field_write(drv->field_ddc_sda_en, 1); + + for (i = 0; i < num; i++) { + err = xfer_msg(drv, &msgs[i]); + if (err) { + ret = err; + break; + } + } + +exit: + clk_disable_unprepare(drv->ddc_clk); + return ret; +} + +static u32 sun4i_hdmi_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = { + .master_xfer = sun4i_hdmi_i2c_xfer, + .functionality = sun4i_hdmi_i2c_func, +}; + +static int sun4i_hdmi_i2c_init_regmap_fields(struct sun4i_hdmi_i2c_drv *drv) +{ + drv->field_ddc_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_en); + if (IS_ERR(drv->field_ddc_en)) + return PTR_ERR(drv->field_ddc_en); + + drv->field_ddc_start = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_start); + if (IS_ERR(drv->field_ddc_start)) + return PTR_ERR(drv->field_ddc_start); + + drv->field_ddc_reset = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_reset); + if (IS_ERR(drv->field_ddc_reset)) + return PTR_ERR(drv->field_ddc_reset); + + drv->field_ddc_addr_reg = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_addr_reg); + if (IS_ERR(drv->field_ddc_addr_reg)) + return PTR_ERR(drv->field_ddc_addr_reg); + + drv->field_ddc_slave_addr = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_slave_addr); + if (IS_ERR(drv->field_ddc_slave_addr)) + return PTR_ERR(drv->field_ddc_slave_addr); + + drv->field_ddc_int_mask = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_int_mask); + if (IS_ERR(drv->field_ddc_int_mask)) + return PTR_ERR(drv->field_ddc_int_mask); + + drv->field_ddc_int_status = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_int_status); + if (IS_ERR(drv->field_ddc_int_status)) + return PTR_ERR(drv->field_ddc_int_status); + + drv->field_ddc_fifo_clear = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_fifo_clear); + if (IS_ERR(drv->field_ddc_fifo_clear)) + return PTR_ERR(drv->field_ddc_fifo_clear); + + drv->field_ddc_fifo_rx_thres = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_fifo_rx_thres); + if (IS_ERR(drv->field_ddc_fifo_rx_thres)) + return PTR_ERR(drv->field_ddc_fifo_rx_thres); + + drv->field_ddc_fifo_tx_thres = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_fifo_tx_thres); + if (IS_ERR(drv->field_ddc_fifo_tx_thres)) + return PTR_ERR(drv->field_ddc_fifo_tx_thres); + + drv->field_ddc_byte_count = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_byte_count); + if (IS_ERR(drv->field_ddc_byte_count)) + return PTR_ERR(drv->field_ddc_byte_count); + + drv->field_ddc_cmd = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_cmd); + if (IS_ERR(drv->field_ddc_cmd)) + return PTR_ERR(drv->field_ddc_cmd); + + drv->field_ddc_sda_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_en); + if (IS_ERR(drv->field_ddc_sda_en)) + return PTR_ERR(drv->field_ddc_sda_en); + + drv->field_ddc_sck_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_en); + if (IS_ERR(drv->field_ddc_sck_en)) + return PTR_ERR(drv->field_ddc_sck_en); + + drv->field_ddc_bus_busy = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_bus_busy); + if (IS_ERR(drv->field_ddc_bus_busy)) + return PTR_ERR(drv->field_ddc_bus_busy); + + drv->field_ddc_sda_state = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_state); + if (IS_ERR(drv->field_ddc_sda_state)) + return PTR_ERR(drv->field_ddc_sda_state); + + drv->field_ddc_sck_state = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_state); + if (IS_ERR(drv->field_ddc_sck_state)) + return PTR_ERR(drv->field_ddc_sck_state); + + drv->field_ddc_sda_line_ctrl = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_line_ctrl); + if (IS_ERR(drv->field_ddc_sda_line_ctrl)) + return PTR_ERR(drv->field_ddc_sda_line_ctrl); + + drv->field_ddc_sck_line_ctrl = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_line_ctrl); + if (IS_ERR(drv->field_ddc_sck_line_ctrl)) + return PTR_ERR(drv->field_ddc_sck_line_ctrl); + + drv->field_ddc_sda_line_ctrl_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sda_line_ctrl_en); + if (IS_ERR(drv->field_ddc_sda_line_ctrl_en)) + return PTR_ERR(drv->field_ddc_sda_line_ctrl_en); + + drv->field_ddc_sck_line_ctrl_en = + devm_regmap_field_alloc(drv->dev, drv->regmap, + drv->variant->field_ddc_sck_line_ctrl_en); + if (IS_ERR(drv->field_ddc_sck_line_ctrl_en)) + return PTR_ERR(drv->field_ddc_sck_line_ctrl_en); + + return 0; +} + +struct sun4i_hdmi_i2c_drv +*sun4i_hdmi_i2c_init(struct device *dev, void __iomem *base, + const struct of_device_id *of_id_table, + const struct regmap_config *regmap_config, + struct clk *parent_clk) +{ + struct sun4i_hdmi_i2c_drv *drv; + const struct of_device_id *of_id; + struct device_node *node = dev_of_node(dev); + int ret; + + if ((!dev) || (!base) || (!regmap_config) || (!of_id_table)) + return ERR_PTR(-ENODEV); + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return ERR_PTR(-ENOMEM); + + drv->dev = dev; + drv->base = base; + + of_id = of_match_device(of_id_table, drv->dev); + if (!of_id) { + dev_err(drv->dev, "missing platform data\n"); + return ERR_PTR(-ENODEV); + } + drv->variant = of_id->data; + // TODO: of_dev_get_data + + if (parent_clk) { + drv->parent_clk = parent_clk; + } else { + drv->parent_clk = devm_clk_get(drv->dev, + drv->variant->parent_clk_name); + } + if (IS_ERR(drv->parent_clk)) { + if (PTR_ERR(drv->parent_clk) != -EPROBE_DEFER) + dev_err(drv->dev, "couldn't get the HDMI-I2C clock\n"); + return ERR_CAST(drv->parent_clk); + } + + ret = of_property_read_u32(node, "clock-frequency", + &drv->clock_freq); + if (ret || (drv->clock_freq > SUN4I_HDMI_I2C_SPEED_MAX)) + drv->clock_freq = SUN4I_HDMI_I2C_SPEED_DEFAULT; + + drv->regmap = devm_regmap_init_mmio(drv->dev, drv->base, regmap_config); + if (IS_ERR(drv->regmap)) { + dev_err(drv->dev, "couldn't create HDMI-I2C regmap\n"); + return ERR_CAST(drv->regmap); + } + + ret = sun4i_hdmi_i2c_init_regmap_fields(drv); + if (ret) { + dev_err(drv->dev, "couldn't init HDMI-I2C regmap fields\n"); + return ERR_PTR(ret); + } + + drv->ddc_clk = sun4i_ddc_create(drv->dev, drv->regmap, drv->variant, + drv->parent_clk); + if (IS_ERR(drv->ddc_clk)) { + dev_err(drv->dev, "couldn't create the HDMI-I2C clock\n"); + return ERR_CAST(drv->ddc_clk); + } + + // TODO devm_of_clk_add_provider() + ret = of_clk_add_provider(node, of_clk_src_simple_get, drv->ddc_clk); + if (ret) { + dev_err(drv->dev, "couldn't register the HDMI-I2C clock\n"); + return ERR_PTR(ret); + } + + ret = clk_prepare_enable(drv->ddc_clk); + if (ret) { + dev_err(drv->dev, "unable to enable HDMI-I2C clock\n"); + return ERR_PTR(ret); + } + + i2c_set_adapdata(&drv->adap, drv); + drv->adap.dev.parent = drv->dev; + drv->adap.owner = THIS_MODULE; + drv->adap.class = I2C_CLASS_DDC; + drv->adap.algo = &sun4i_hdmi_i2c_algorithm; + drv->adap.dev.of_node = node; + strlcpy(drv->adap.name, "sun4i_hdmi_i2c adapter", sizeof(drv->adap.name)); + + clk_disable_unprepare(drv->ddc_clk); + + ret = i2c_add_adapter(&drv->adap); + if (ret) { + dev_err(drv->dev, "unable to create HDMI-I2C adapter\n"); + goto ddc_clk_err; + } + + return drv; + +ddc_clk_err: + clk_disable_unprepare(drv->ddc_clk); + + return ERR_PTR(ret); +} + +static const struct of_device_id sun4i_hdmi_i2c_legacy_of_table[] = { + { .compatible = "allwinner,sun4i-a10-hdmi", .data = &sun4i_legacy_variant }, + { .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_legacy_variant }, + { /* sentinel */ } +}; + +struct sun4i_hdmi_i2c_drv *sun4i_hdmi_i2c_setup(struct device *dev, + void __iomem *base, + struct clk *clk) +{ + return sun4i_hdmi_i2c_init(dev, base, sun4i_hdmi_i2c_legacy_of_table, + &sun4i_hdmi_i2c_legacy_regmap_config, clk); +} + +void sun4i_hdmi_i2c_fini(struct sun4i_hdmi_i2c_drv *drv) +{ + struct device_node *node = dev_of_node(drv->dev); + + clk_prepare_enable(drv->ddc_clk); + i2c_del_adapter(&drv->adap); + clk_disable_unprepare(drv->ddc_clk); + of_clk_del_provider(node); +} diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.h b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.h new file mode 100644 index 00000000000000..08ed3658459063 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c_drv.h @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Copyright (C) 2017 Chen-Yu Tsai + * Copyright (C) 2017 Jonathan Liu + * Copyright (C) 2017 Olliver Schinagl + * + * SPDX-License-Identifier: GPL-2.0+ + * + */ + +#ifndef _SUN4I_HDMI_I2C_DRV_H_ +#define _SUN4I_HDMI_I2C_DRV_H_ + +#include +#include +#include +#include +#include +#include + +#define SUN4I_HDMI_DDC_OFFSET 0x500 + +#define SUN4I_HDMI_DDC_CTRL_REG 0x00 +#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31) +#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE (1 << 8) +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8) +#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0) + +#define SUN4I_HDMI_DDC_ADDR_REG 0x04 +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & GENMASK(7, 0)) + +#define SUN4I_HDMI_DDC_INT_STATUS_REG 0x0c +#define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW BIT(6) +#define SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW BIT(5) +#define SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST BIT(4) +#define SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR BIT(3) +#define SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR BIT(2) +#define SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR BIT(1) +#define SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE BIT(0) + +#define SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK ( \ + SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION | \ + SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW | \ + SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW | \ + SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR | \ + SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR | \ + SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR \ +) + +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x10 +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) \ + (((n) << 4) & SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK) +#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) \ + ((n) & SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK) +#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1) + +#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x18 + +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x1c +#define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1) + +#define SUN4I_HDMI_DDC_CMD_REG 0x20 +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 0x6 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 0x5 +#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 0x3 + +#define SUN4I_HDMI_DDC_EXT_REG 0x24 +#define SUN4I_HDMI_DDC_EXT_BUS_BUSY BIT(10) +#define SUN4I_HDMI_DDC_EXT_SDA_STATE BIT(9) +#define SUN4I_HDMI_DDC_EXT_SCK_STATE BIT(8) +#define SUN4I_HDMI_DDC_EXT_SCL_LINE_CTRL BIT(3) +#define SUN4I_HDMI_DDC_EXT_SCL_LINE_CTRL_EN BIT(2) +#define SUN4I_HDMI_DDC_EXT_SDA_LINE_CTRL BIT(1) +#define SUN4I_HDMI_DDC_EXT_SDA_LINE_CTRL_EN BIT(0) + +#define SUN4I_HDMI_DDC_CLK_REG 0x28 +#define SUN4I_HDMI_DDC_CLK_M_OFFSET 3 +#define SUN4I_HDMI_DDC_CLK_M_MASK GENMASK(7, 4) +#define SUN4I_HDMI_DDC_CLK_N_MASK GENMASK(3, 0) +#define SUN4I_HDMI_DDC_CLK_M(m) \ + (((m) << SUN4I_HDMI_DDC_CLK_M_OFFSET) & SUN4I_HDMI_DDC_CLK_M_MASK) +#define SUN4I_HDMI_DDC_CLK_N(n) \ + ((n) & SUN4I_HDMI_DDC_CLK_N_MASK) +#define SUN4I_HDMI_DDC_CLK_M_GET(reg) \ + (((reg) & SUN4I_HDMI_DDC_CLK_M_MASK) >> SUN4I_HDMI_DDC_CLK_M_OFFSET) +#define SUN4I_HDMI_DDC_CLK_N_GET(reg) \ + ((reg) & SUN4I_HDMI_DDC_CLK_N_MASK) + +#define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x40 +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9) +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8) + +#define SUN4I_HDMI_DDC_FIFO_SIZE 16 + +/* A31 specific */ +#define SUN6I_HDMI_DDC_CTRL_REG 0x00 +#define SUN6I_HDMI_DDC_CTRL_RESET BIT(31) +#define SUN6I_HDMI_DDC_CTRL_START_CMD BIT(27) +#define SUN6I_HDMI_DDC_CTRL_SDA_ENABLE BIT(6) +#define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4) +#define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0) + +#define SUN6I_HDMI_DDC_EXT_REG 0x04 + +#define SUN6I_HDMI_DDC_CMD_REG 0x08 +#define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16) +/* command types in lower 3 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_ADDR_REG 0x0c +#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) << 24) & GENMASK(31, 24)) +#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) << 16) & GENMASK(23, 16)) +#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) << 8) & GENMASK(15, 8)) +#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) << 1) & GENMASK(7, 1)) + +#define SUN6I_HDMI_DDC_INT_STATUS_REG 0x14 +#define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8) +/* lower 8 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_FIFO_CTRL_REG 0x18 +#define SUN6I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(15) +/* lower 9 bits are the same as sun4i */ + +#define SUN6I_HDMI_DDC_CLK_REG 0x20 +/* DDC CLK bit fields are the same, but the formula is not */ + +#define SUN6I_HDMI_DDC_FIFO_DATA_REG 0x80 + +struct sun4i_hdmi_i2c_variant { + char *parent_clk_name; + + struct reg_field ddc_clk_reg; + u8 ddc_clk_pre_divider; + u8 ddc_clk_m_offset; + + /* Register fields for I2C adapter */ + struct reg_field field_ddc_en; + struct reg_field field_ddc_start; + struct reg_field field_ddc_reset; + struct reg_field field_ddc_addr_reg; + struct reg_field field_ddc_slave_addr; + struct reg_field field_ddc_int_mask; + struct reg_field field_ddc_int_status; + struct reg_field field_ddc_fifo_clear; + struct reg_field field_ddc_fifo_rx_thres; + struct reg_field field_ddc_fifo_tx_thres; + struct reg_field field_ddc_byte_count; + struct reg_field field_ddc_cmd; + struct reg_field field_ddc_sda_en; + struct reg_field field_ddc_sck_en; + struct reg_field field_ddc_bus_busy; + struct reg_field field_ddc_sda_state; + struct reg_field field_ddc_sck_state; + struct reg_field field_ddc_sda_line_ctrl_en; + struct reg_field field_ddc_sck_line_ctrl_en; + struct reg_field field_ddc_sda_line_ctrl; + struct reg_field field_ddc_sck_line_ctrl; + + + /* DDC FIFO register offset */ + u32 ddc_fifo_reg; + + /* + * DDC FIFO threshold boundary conditions + * + * This is used to cope with the threshold boundary condition + * being slightly different on sun5i and sun6i. + * + * On sun5i the threshold is exclusive, i.e. does not include, + * the value of the threshold. ( > for RX; < for TX ) + * On sun6i the threshold is inclusive, i.e. includes, the + * value of the threshold. ( >= for RX; <= for TX ) + */ + bool ddc_fifo_thres_incl; + + bool ddc_fifo_has_dir; +}; + +struct sun4i_hdmi_i2c_drv { + struct device *dev; + + void __iomem *base; + struct regmap *regmap; + + struct clk *parent_clk; + struct clk *ddc_clk; + uint32_t clock_freq; + + struct i2c_adapter adap; + + struct regmap_field *field_ddc_en; + struct regmap_field *field_ddc_start; + struct regmap_field *field_ddc_reset; + struct regmap_field *field_ddc_addr_reg; + struct regmap_field *field_ddc_slave_addr; + struct regmap_field *field_ddc_int_mask; + struct regmap_field *field_ddc_int_status; + struct regmap_field *field_ddc_fifo_clear; + struct regmap_field *field_ddc_fifo_rx_thres; + struct regmap_field *field_ddc_fifo_tx_thres; + struct regmap_field *field_ddc_byte_count; + struct regmap_field *field_ddc_cmd; + struct regmap_field *field_ddc_sda_en; + struct regmap_field *field_ddc_sck_en; + struct regmap_field *field_ddc_bus_busy; + struct regmap_field *field_ddc_sda_state; + struct regmap_field *field_ddc_sck_state; + struct regmap_field *field_ddc_sda_line_ctrl; + struct regmap_field *field_ddc_sck_line_ctrl; + struct regmap_field *field_ddc_sda_line_ctrl_en; + struct regmap_field *field_ddc_sck_line_ctrl_en; + + + const struct sun4i_hdmi_i2c_variant *variant; +}; + +struct sun4i_hdmi_i2c_drv +*sun4i_hdmi_i2c_init(struct device *dev, void __iomem *base, + const struct of_device_id *of_id_table, + const struct regmap_config *regmap_config, + struct clk *parent_clk); + +void sun4i_hdmi_i2c_fini(struct sun4i_hdmi_i2c_drv *drv); + +struct sun4i_hdmi_i2c_drv *sun4i_hdmi_i2c_setup(struct device *dev, + void __iomem *base, + struct clk *clk); + +#endif diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c index 6e3293beff0d57..5785ffb03f972a 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c @@ -10,7 +10,13 @@ * the License, or (at your option) any later version. */ +#include #include +#include +#include +#include +#include +#include #include "sun4i_hdmi.h" @@ -21,6 +27,15 @@ struct sun4i_tmds { u8 div_offset; }; +struct sun4i_tmds_ddc { + struct device *dev; + void __iomem *base; + + struct clk_hw hw; + + spinlock_t lock; +}; + static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw) { return container_of(hw, struct sun4i_tmds, hw); @@ -205,6 +220,9 @@ int sun4i_tmds_create(struct sun4i_hdmi *hdmi) struct sun4i_tmds *tmds; const char *parents[2]; + if (!hdmi->tmds_clk_name) + return -ENODEV; + parents[0] = __clk_get_name(hdmi->pll0_clk); if (!parents[0]) return -ENODEV; @@ -217,7 +235,7 @@ int sun4i_tmds_create(struct sun4i_hdmi *hdmi) if (!tmds) return -ENOMEM; - init.name = "hdmi-tmds"; + init.name = hdmi->tmds_clk_name; init.ops = &sun4i_tmds_ops; init.parent_names = parents; init.num_parents = 2; From 6e4973b49d3c6993b72d2f820f58e96ccfcac1cb Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 30 Mar 2018 12:06:34 +0200 Subject: [PATCH 79/93] arm/dts: Use hdmi_i2c node on the OLinuXino Lime2 As the Lime2 uses the internal HDMI I2C bus for its EDID, explicitly configure it as so in the devicetree. Signed-off-by: Olliver Schinagl --- arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts index ba250189d07f66..bf8d919794cff8 100644 --- a/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts +++ b/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2.dts @@ -121,6 +121,12 @@ }; &hdmi { + ddc-i2c-bus = <&hdmi_i2c>; + status = "okay"; +}; + +&hdmi_i2c { + clock-frequency = <100000>; status = "okay"; }; From 78c434d655e6a1f833ae058af3335933769e6003 Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 20 Apr 2018 14:23:50 +0200 Subject: [PATCH 80/93] net/rfkill/rfkill-gpio: Alphabetize headers This patch does no functional change but to sort the headers. Signed-off-by: Olliver Schinagl --- net/rfkill/rfkill-gpio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/rfkill/rfkill-gpio.c b/net/rfkill/rfkill-gpio.c index 41bd496531d45e..e77ebefd894f88 100644 --- a/net/rfkill/rfkill-gpio.c +++ b/net/rfkill/rfkill-gpio.c @@ -16,16 +16,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include +#include #include +#include #include #include #include -#include #include -#include +#include #include -#include -#include struct rfkill_gpio_data { const char *name; From cec74d4e43a7ec1b587890c2c29cec7282f7588c Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Fri, 20 Apr 2018 14:44:09 +0200 Subject: [PATCH 81/93] net/rfkill/rfkill-gpio: Add support for devicetree bindings Some devices may want to bind their rfkill properties via devicetree bindings. Enable this for rfkill-gpio. Signed-off-by: Olliver Schinagl --- .../devicetree/bindings/net/rfkill-gpio.txt | 33 +++++++++++++++++++ net/rfkill/rfkill-gpio.c | 12 +++++++ 2 files changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/rfkill-gpio.txt diff --git a/Documentation/devicetree/bindings/net/rfkill-gpio.txt b/Documentation/devicetree/bindings/net/rfkill-gpio.txt new file mode 100644 index 00000000000000..391a126bb01add --- /dev/null +++ b/Documentation/devicetree/bindings/net/rfkill-gpio.txt @@ -0,0 +1,33 @@ +Linux rfkill-gpio Bindings +-------------------------- + +This binding provides support the rfkill via an gpio interface. + +Note: Only one of the gpios definitions is required. If both are set, both +will be toggled uppon rfkill invocation. + +Required properties: +- compatible Should be one of: + "linux,rfkill-gpio" +- type Shall be one of: + "wlan" + "bluetooth" + "ultrawideband" + "wimax" + "wwan" + "gps" + "fm" + "nfc" + If left undefined, the 'RFKILL_TYPE_ALL' type + shall be used. + +- shutdown-gpios Which GPIO to use for shutdown +or +- reset-gpios Which GPIO to use for reset + +Example: + phy0-kill { + compatible = "linux,rfkill-gpio"; + type = "wlan"; + shutdown-gpios = <&pio 2 17 GPIO_ACTIVE_HIGH>; + }; diff --git a/net/rfkill/rfkill-gpio.c b/net/rfkill/rfkill-gpio.c index e77ebefd894f88..96f9b0c6e6288d 100644 --- a/net/rfkill/rfkill-gpio.c +++ b/net/rfkill/rfkill-gpio.c @@ -21,8 +21,11 @@ #include #include #include +#include #include +#include #include +#include #include #include #include @@ -165,12 +168,21 @@ static const struct acpi_device_id rfkill_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, rfkill_acpi_match); #endif +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id rfkill_of_match[] = { + { .compatible = "linux,rfkill-gpio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rfkill_of_match); +#endif + static struct platform_driver rfkill_gpio_driver = { .probe = rfkill_gpio_probe, .remove = rfkill_gpio_remove, .driver = { .name = "rfkill_gpio", .acpi_match_table = ACPI_PTR(rfkill_acpi_match), + .of_match_table = of_match_ptr(rfkill_of_match), }, }; From e28c2cbfe4482560040d56a3c47ac76cb54821bf Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:11:51 +0100 Subject: [PATCH 82/93] Revert "fbdev: ssd1307fb: Do not always clear the display on boot" This reverts commit 788b0c111bc075922bf1e227fdc02f9a08eca3b8. --- Documentation/devicetree/bindings/display/fbdev.txt | 2 +- drivers/video/fbdev/core/fb_of.c | 3 ++- drivers/video/fbdev/ssd1307fb.c | 3 +-- include/linux/fb.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/display/fbdev.txt b/Documentation/devicetree/bindings/display/fbdev.txt index 5c572a123e6a61..d3ed6ef846f614 100644 --- a/Documentation/devicetree/bindings/display/fbdev.txt +++ b/Documentation/devicetree/bindings/display/fbdev.txt @@ -1,6 +1,6 @@ General framebuffer properties: Optional properties for framebuffers: - - no-clear-on-probe : Do not clear the framebuffer during probe (bool) + - clear-on-probe : clear the framebuffer during probe (bool) - rotate : rotate a framebuffer over 0, 1, 2 and 3 (or 0, 90, 180 and 270) degree's (integer) diff --git a/drivers/video/fbdev/core/fb_of.c b/drivers/video/fbdev/core/fb_of.c index cb2de340d614c9..57bc3dc30bf3d5 100644 --- a/drivers/video/fbdev/core/fb_of.c +++ b/drivers/video/fbdev/core/fb_of.c @@ -17,9 +17,10 @@ void fb_parse_properties(struct device *dev, struct fb_of_properties *prop) { + bool clear; u16 rotate; - prop->no_clear_on_probe = device_property_read_bool(dev, "no-clear-on-probe"); + clear = device_property_read_bool(dev, "clear-on-probe"); if (device_property_read_u16(dev, "rotate", &rotate)) rotate = FB_ROTATE_UR; diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index d7b6cc384492ac..a36a8b0788cbc4 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -534,8 +534,7 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) return ret; /* Clear the screen */ - if (!par->no_clear_on_probe) - ssd1307fb_update_display(par); + ssd1307fb_update_display(par); return 0; } diff --git a/include/linux/fb.h b/include/linux/fb.h index b62113926c5703..08b6825b00152e 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -121,7 +121,7 @@ struct fb_cursor_user { }; struct fb_of_properties { - bool no_clear_on_probe; + bool clear_on_probe; __u8 rotate; }; From b9597bd96d273f40deddf2358f9c41da413a6882 Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:12:17 +0100 Subject: [PATCH 83/93] Revert "fbdev: ssd1307fb: Limit to support 0 - 180 rotation only" This reverts commit 5f00ea8326071b3f7694ffa388b93f215a699c20. --- drivers/video/fbdev/ssd1307fb.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index a36a8b0788cbc4..199e5979e638a0 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -379,10 +379,6 @@ static int ssd1307fb_check_var(struct fb_var_screeninfo *var, struct fb_info *in { struct ssd1307fb_par *par = info->par; - /* Only up right and upside down rotations are supported */ - if ((var->rotate != FB_ROTATE_UR) && (var->rotate != FB_ROTATE_UD)) - return -EINVAL; - if (par->rotate != var->rotate) { if (var->rotate > FB_ROTATE_CCW) { var->rotate = par->rotate; From d0c61cd679d929c8b29608cca2d6c4db2cace46b Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:12:33 +0100 Subject: [PATCH 84/93] Revert "fbdev: ssd1307fb: Add hardware accelerated rotation support" This reverts commit 478d8bce62608a68c44b540d3dfc71149241aae7. --- drivers/video/fbdev/ssd1307fb.c | 147 +++++++------------------------- 1 file changed, 30 insertions(+), 117 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 199e5979e638a0..1ed2bfd516452c 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -20,7 +20,6 @@ #include #include #include -#include #define SSD1307FB_DATA 0x40 #define SSD1307FB_COMMAND 0x80 @@ -33,22 +32,15 @@ #define SSD1307FB_SET_PAGE_RANGE 0x22 #define SSD1307FB_CONTRAST 0x81 #define SSD1307FB_CHARGE_PUMP 0x8d +#define SSD1307FB_SEG_REMAP_ON 0xa1 #define _SSD1307FB_CHARGE_PUMP_SET 0x10 #define SSD1307FB_CHARGE_PUMP_SET(pump) \ ((_SSD1307FB_CHARGE_PUMP_SET) | \ ((pump) ? BIT(2) : 0)) -#define _SSD1307FB_SEG_REMAP 0xa0 -#define SSD1307FB_SEG_REMAP(seg_remap) \ - (_SSD1307FB_SEG_REMAP | \ - ((seg_remap) ? BIT(0) : 0x0)) #define SSD1307FB_DISPLAY_OFF 0xae #define SSD1307FB_SET_MULTIPLEX_RATIO 0xa8 #define SSD1307FB_DISPLAY_ON 0xaf #define SSD1307FB_START_PAGE_ADDRESS 0xb0 -#define _SSD1307FB_COM_INVDIR 0xc0 -#define SSD1307FB_COM_INVDIR(com_invdir)\ - (_SSD1307FB_COM_INVDIR | \ - ((com_invdir) ? BIT(3) : 0x0)) #define SSD1307FB_SET_DISPLAY_OFFSET 0xd3 #define SSD1307FB_SET_CLOCK_FREQ 0xd5 #define SSD1307FB_CLOCK_FREQ(freq, div) \ @@ -82,12 +74,6 @@ struct ssd1307fb_deviceinfo { int need_chargepump; }; -struct ssd1307fb_rot_lut { - u8 seg_remap; - u8 com_invdir; - u8 addr_mode; -}; - struct ssd1307fb_par { u32 com_invdir; u32 com_lrremap; @@ -106,9 +92,6 @@ struct ssd1307fb_par { struct pwm_device *pwm; u32 pwm_period; struct gpio_desc *reset; - bool no_clear_on_probe; - u8 rotate; - struct ssd1307fb_rot_lut rot_lut[FB_ROTATE_CCW + 1]; struct regulator *vbat_reg; u32 seg_remap; u32 vcomh; @@ -281,53 +264,31 @@ static int ssd1307fb_blank(int blank_mode, struct fb_info *info) return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); } -static int ssd1307fb_rotate(struct fb_info *info) +static int ssd1307fb_set_par(struct fb_info *info) { struct ssd1307fb_par *par = info->par; - u32 rot = info->var.rotate; int ret; - if (par->rotate == rot) - return 0; - - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, par->rot_lut[rot].addr_mode); - if (ret < 0) - return ret; + /* Set segment re-map */ + if (par->seg_remap) { + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SEG_REMAP_ON); + if (ret < 0) + return ret; + }; - ret = ssd1307fb_write_cmd(par->client, par->rot_lut[rot].seg_remap); + /* Set COM direction */ + com_invdir = 0xc0 | (par->com_invdir & 0x1) << 3; + ret = ssd1307fb_write_cmd(par->client, com_invdir); if (ret < 0) return ret; - ret = ssd1307fb_write_cmd(par->client, par->rot_lut[rot].com_invdir); + /* Switch to horizontal addressing mode */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); if (ret < 0) return ret; - if ((rot == FB_ROTATE_CW) || (rot == FB_ROTATE_CCW)) { - info->var.xres = par->height; - info->var.yres = par->width; - } else { - info->var.xres = par->width; - info->var.yres = par->height; - } - info->var.xres_virtual = info->var.xres; - info->var.yres_virtual = info->var.yres; - info->fix.line_length = info->var.xres_virtual / 8; - - par->rotate = rot; - - return 0; -} - -static int ssd1307fb_set_par(struct fb_info *info) -{ - struct ssd1307fb_par *par = info->par; - int ret; - - ret = ssd1307fb_rotate(info); + ret = ssd1307fb_write_cmd(par->client, + SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); if (ret < 0) return ret; @@ -375,20 +336,6 @@ static void ssd1307fb_copyarea(struct fb_info *info, const struct fb_copyarea *a ssd1307fb_update_display(par); } -static int ssd1307fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) -{ - struct ssd1307fb_par *par = info->par; - - if (par->rotate != var->rotate) { - if (var->rotate > FB_ROTATE_CCW) { - var->rotate = par->rotate; - return -EINVAL; - } - } - - return 0; -} - static void ssd1307fb_imageblit(struct fb_info *info, const struct fb_image *image) { struct ssd1307fb_par *par = info->par; @@ -404,7 +351,6 @@ static struct fb_ops ssd1307fb_ops = { .fb_set_par = ssd1307fb_set_par, .fb_fillrect = ssd1307fb_fillrect, .fb_copyarea = ssd1307fb_copyarea, - .fb_check_var = ssd1307fb_check_var, .fb_imageblit = ssd1307fb_imageblit, }; @@ -462,6 +408,19 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; + /* Set segment re-map */ + 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; + /* Set multiplex ratio value */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_MULTIPLEX_RATIO); if (ret < 0) @@ -629,8 +588,6 @@ static int ssd1307fb_probe(struct i2c_client *client, struct fb_deferred_io *ssd1307fb_defio; u32 vmem_size; struct ssd1307fb_par *par; - struct fb_of_properties prop; - bool seg_remap, com_invdir; u8 *vmem; int ret; @@ -672,10 +629,6 @@ static int ssd1307fb_probe(struct i2c_client *client, } } - fb_parse_properties(&client->dev, &prop); - par->rotate = prop.rotate; - par->no_clear_on_probe = prop.no_clear_on_probe; - if (of_property_read_u32(node, "solomon,width", &par->width)) par->width = 96; @@ -694,10 +647,10 @@ static int ssd1307fb_probe(struct i2c_client *client, if (of_property_read_u32(node, "solomon,prechargep2", &par->prechargep2)) par->prechargep2 = 2; - seg_remap = !of_property_read_bool(node, "solomon,segment-no-remap"); + 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"); - com_invdir = of_property_read_bool(node, "solomon,com-invdir"); + par->com_invdir = of_property_read_bool(node, "solomon,com-invdir"); par->contrast = 127; par->vcomh = par->device_info->default_vcomh; @@ -727,47 +680,13 @@ static int ssd1307fb_probe(struct i2c_client *client, ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io; info->fbops = &ssd1307fb_ops; - info->flags = FBINFO_HWACCEL_ROTATE; info->fbdefio = ssd1307fb_defio; info->var = ssd1307fb_var; - - /* - * We really only support flipping of the display and so we use flip - * an both x and y flip to rotate from 0 -> 180 degree's. - */ - par->rot_lut[FB_ROTATE_UR].addr_mode = SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL; - par->rot_lut[FB_ROTATE_UR].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? true : false); - par->rot_lut[FB_ROTATE_UR].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? true : false); - par->rot_lut[FB_ROTATE_UD].addr_mode = SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL; - par->rot_lut[FB_ROTATE_UD].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? false : true); - par->rot_lut[FB_ROTATE_UD].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? false : true); - - /* - * The same trick is used to flip from 90 -> 270 degree's. - * To rotate 0 -> 90 degree's we tell the controller to use vertical - * scan-out rather then horizontal. - */ - par->rot_lut[FB_ROTATE_CW].addr_mode = SSD1307FB_SET_ADDRESS_MODE_VERTICAL; - par->rot_lut[FB_ROTATE_CW].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? true : false); - par->rot_lut[FB_ROTATE_CW].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? true : false); - par->rot_lut[FB_ROTATE_CCW].addr_mode = SSD1307FB_SET_ADDRESS_MODE_VERTICAL; - par->rot_lut[FB_ROTATE_CCW].seg_remap = SSD1307FB_SEG_REMAP(seg_remap ? false : true); - par->rot_lut[FB_ROTATE_CCW].com_invdir = SSD1307FB_COM_INVDIR(com_invdir ? false : true); - - if ((par->rotate == FB_ROTATE_CW) || (par->rotate == FB_ROTATE_CCW)) { - info->var.xres = par->height; - info->var.yres = par->width; - } else { - info->var.xres = par->width; - info->var.yres = par->height; - } - info->var.xres = par->width; info->var.xres_virtual = info->var.xres; info->var.yres = par->height; info->var.yres_virtual = info->var.yres; - info->var.rotate = par->rotate; info->var.red.length = 1; info->var.red.offset = 0; @@ -814,12 +733,6 @@ static int ssd1307fb_probe(struct i2c_client *client, if (ret) goto regulator_enable_error; - ret = ssd1307fb_check_var(&info->var, info); - if (ret) { - dev_err(&client->dev, "unable to check parameters\n"); - goto bl_init_error; - } - ret = ssd1307fb_set_par(info); if (ret) { dev_err(&client->dev, "unable to setup parameters\n"); From 6ab4bc25d0854b0b20c87d64420a5f265cdc3981 Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:17:11 +0100 Subject: [PATCH 85/93] Revert "fbdev: ssd1307fb: Ensure display is toggled properly" This reverts commit 963d544791ec662f317dd00c809a397b872b9b95. --- drivers/video/fbdev/ssd1307fb.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 1ed2bfd516452c..ccba70c6039de7 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -491,6 +491,11 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) /* Clear the screen */ ssd1307fb_update_display(par); + /* Turn on the display */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); + if (ret < 0) + return ret; + return 0; } @@ -713,13 +718,6 @@ static int ssd1307fb_probe(struct i2c_client *client, udelay(4); } - /* Ensure display is turned off while initializing */ - ret = ssd1307fb_blank(FB_BLANK_NORMAL, info); - if (ret) { - dev_err(&client->dev, "unable to blank screen\n"); - goto bl_init_error; - } - if (par->vbat_reg) { ret = regulator_enable(par->vbat_reg); if (ret) { @@ -759,13 +757,6 @@ static int ssd1307fb_probe(struct i2c_client *client, bl->props.max_brightness = MAX_CONTRAST; info->bl_dev = bl; - /* Turn on the display */ - ret = ssd1307fb_blank(FB_BLANK_UNBLANK, info); - if (ret) { - dev_err(&client->dev, "unable to unblank screen\n"); - goto bl_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; From fdd729f4657f6dd43ec341e8e657932c51fc1817 Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:17:23 +0100 Subject: [PATCH 86/93] Revert "fbdev: ssd1307fb: Ensure set_par can do bring-up" This reverts commit 0697c6c280b270c8cbd565e0c93c944486324eb1. --- drivers/video/fbdev/ssd1307fb.c | 102 ++++++++++++-------------------- 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index ccba70c6039de7..00f81bb5c81cc6 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -264,64 +264,6 @@ static int ssd1307fb_blank(int blank_mode, struct fb_info *info) return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); } -static int ssd1307fb_set_par(struct fb_info *info) -{ - struct ssd1307fb_par *par = info->par; - int ret; - - /* Set segment re-map */ - 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; - - /* Switch to horizontal addressing mode */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, - SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); - if (ret < 0) - return ret; - - /* Set column range */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, 0x0); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, par->width - 1); - if (ret < 0) - return ret; - - /* Set page range */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, 0x0); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, - par->page_offset + (par->height / 8) - 1); - if (ret < 0) - return ret; - - return 0; -} - static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { struct ssd1307fb_par *par = info->par; @@ -348,7 +290,6 @@ static struct fb_ops ssd1307fb_ops = { .fb_read = fb_sys_read, .fb_write = ssd1307fb_write, .fb_blank = ssd1307fb_blank, - .fb_set_par = ssd1307fb_set_par, .fb_fillrect = ssd1307fb_fillrect, .fb_copyarea = ssd1307fb_copyarea, .fb_imageblit = ssd1307fb_imageblit, @@ -488,6 +429,43 @@ static int ssd1307fb_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); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, + SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); + if (ret < 0) + return ret; + + /* Set column range */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, 0x0); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, par->width - 1); + if (ret < 0) + return ret; + + /* Set page range */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, 0x0); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, + par->page_offset + (par->height / 8) - 1); + if (ret < 0) + return ret; + /* Clear the screen */ ssd1307fb_update_display(par); @@ -731,12 +709,6 @@ static int ssd1307fb_probe(struct i2c_client *client, if (ret) goto regulator_enable_error; - ret = ssd1307fb_set_par(info); - if (ret) { - dev_err(&client->dev, "unable to setup parameters\n"); - goto bl_init_error; - } - ret = register_framebuffer(info); if (ret) { dev_err(&client->dev, "Couldn't register the framebuffer\n"); From b76a6bae89322999b00869a32d3d3efcfef364f2 Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:22:43 +0100 Subject: [PATCH 87/93] Revert "fbdev: ssd1307fb: Remove unused forward declaration" This reverts commit 927589c4d96dec40226048828d51a12c03aae922. --- drivers/video/fbdev/ssd1307fb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 00f81bb5c81cc6..0ba04c8227c810 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -66,6 +66,8 @@ static u_int refreshrate = REFRESHRATE; module_param(refreshrate, uint, 0); +struct ssd1307fb_par; + struct ssd1307fb_deviceinfo { u32 default_vcomh; u32 default_dclk_div; From 77f3eff88ab41283b7a611fcc0dd29b11636d26b Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:23:02 +0100 Subject: [PATCH 88/93] Revert "fbdev: ssd1307fb: Reduce magic values with defines" This reverts commit b1a1ce2192fccdd4d8071a186cd93115872f752c. --- drivers/video/fbdev/ssd1307fb.c | 36 +++++++++------------------------ 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 0ba04c8227c810..f8f0677b2d4307 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -6,7 +6,6 @@ * Licensed under the GPLv2 or later. */ -#include #include #include #include @@ -33,30 +32,14 @@ #define SSD1307FB_CONTRAST 0x81 #define SSD1307FB_CHARGE_PUMP 0x8d #define SSD1307FB_SEG_REMAP_ON 0xa1 -#define _SSD1307FB_CHARGE_PUMP_SET 0x10 -#define SSD1307FB_CHARGE_PUMP_SET(pump) \ - ((_SSD1307FB_CHARGE_PUMP_SET) | \ - ((pump) ? BIT(2) : 0)) #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_CLOCK_FREQ(freq, div) \ - ((((freq) << 4) & GENMASK(7, 4)) | \ - (((((div) - 1) << 0) & GENMASK(3, 0)))) #define SSD1307FB_SET_PRECHARGE_PERIOD 0xd9 -#define SSD1307FB_PRECHARGE_PERIOD(period1, period2) \ - ((((period2 << 4) & GENMASK(7, 4) )) | \ - (((period1) << 0) & GENMASK(3, 0))) #define SSD1307FB_SET_COM_PINS_CONFIG 0xda -#define _SSD1307FB_COM_PINS_CONFIG 0x02 -#define SSD1307FB_COM_PINS_CONFIG(com_seq, com_lrremap) \ - ((_SSD1307FB_COM_PINS_CONFIG) | \ - ((com_lrremap) ? BIT(5) : 0) | \ - ((com_seq) ? 0 : BIT(4))) - #define SSD1307FB_SET_VCOMH 0xdb #define MAX_CONTRAST 255 @@ -306,7 +289,7 @@ static void ssd1307fb_deferred_io(struct fb_info *info, static int ssd1307fb_init(struct ssd1307fb_par *par) { int ret; - u8 cmd; + u32 precharge, dclk, com_invdir, compins; struct pwm_args pargs; char status; struct device *dev = &par->client->dev; @@ -387,8 +370,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - cmd = SSD1307FB_CLOCK_FREQ(par->dclk_frq, par->dclk_div); - ret = ssd1307fb_write_cmd(par->client, cmd); + dclk = ((par->dclk_div - 1) & 0xf) | (par->dclk_frq & 0xf) << 4; + ret = ssd1307fb_write_cmd(par->client, dclk); if (ret < 0) return ret; @@ -397,8 +380,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - cmd = SSD1307FB_PRECHARGE_PERIOD(par->prechargep1, par->prechargep2); - ret = ssd1307fb_write_cmd(par->client, cmd); + precharge = (par->prechargep1 & 0xf) | (par->prechargep2 & 0xf) << 4; + ret = ssd1307fb_write_cmd(par->client, precharge); if (ret < 0) return ret; @@ -407,8 +390,9 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - cmd = SSD1307FB_COM_PINS_CONFIG(par->com_seq, par->com_lrremap); - ret = ssd1307fb_write_cmd(par->client, cmd); + compins = 0x02 | !(par->com_seq & 0x1) << 4 + | (par->com_lrremap & 0x1) << 5; + ret = ssd1307fb_write_cmd(par->client, compins); if (ret < 0) return ret; @@ -426,8 +410,8 @@ static int ssd1307fb_init(struct ssd1307fb_par *par) if (ret < 0) return ret; - cmd = SSD1307FB_CHARGE_PUMP_SET(par->device_info->need_chargepump); - ret = ssd1307fb_write_cmd(par->client, cmd); + ret = ssd1307fb_write_cmd(par->client, + BIT(4) | (par->device_info->need_chargepump ? BIT(2) : 0)); if (ret < 0) return ret; From 6017cbd45e7a185f7a6cf230a1d0b531800cc93b Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:23:21 +0100 Subject: [PATCH 89/93] Revert "fbdev: ssd1307fb: Use resolution instead of dimensions" This reverts commit eaa8663e5c63b0eb8c0b569af5f1ee12e0ea4ee1. --- drivers/video/fbdev/ssd1307fb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index f8f0677b2d4307..d403adac910d23 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -653,9 +653,9 @@ static int ssd1307fb_probe(struct i2c_client *client, info->var = ssd1307fb_var; info->var.xres = par->width; - info->var.xres_virtual = info->var.xres; + info->var.xres_virtual = par->width; info->var.yres = par->height; - info->var.yres_virtual = info->var.yres; + info->var.yres_virtual = par->height; info->var.red.length = 1; info->var.red.offset = 0; @@ -668,7 +668,7 @@ static int ssd1307fb_probe(struct i2c_client *client, info->fix = ssd1307fb_fix; info->fix.smem_start = __pa(vmem); info->fix.smem_len = vmem_size; - info->fix.line_length = info->var.xres / 8; + info->fix.line_length = par->width / 8; fb_deferred_io_init(info); From d0f6f9fe064dc0f5304c2ec26c715a5e5ab016e8 Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Tue, 26 Mar 2019 12:23:36 +0100 Subject: [PATCH 90/93] Revert "fbdev: ssd1307fb: cleanup: Group fb_info.fix" This reverts commit 7c6105c03901425304b09de9c86c1b617cd25e44. --- drivers/video/fbdev/ssd1307fb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index d403adac910d23..7fa613f55cdc58 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -649,6 +649,8 @@ static int ssd1307fb_probe(struct i2c_client *client, ssd1307fb_defio->deferred_io = ssd1307fb_deferred_io; info->fbops = &ssd1307fb_ops; + info->fix = ssd1307fb_fix; + info->fix.line_length = par->width / 8; info->fbdefio = ssd1307fb_defio; info->var = ssd1307fb_var; @@ -665,10 +667,8 @@ static int ssd1307fb_probe(struct i2c_client *client, info->var.blue.offset = 0; info->screen_base = (u8 __force __iomem *)vmem; - info->fix = ssd1307fb_fix; info->fix.smem_start = __pa(vmem); info->fix.smem_len = vmem_size; - info->fix.line_length = par->width / 8; fb_deferred_io_init(info); From 9d3942c431e2b30739a7235099078492d048b603 Mon Sep 17 00:00:00 2001 From: Raymond Siudak Date: Wed, 27 Mar 2019 13:40:38 +0100 Subject: [PATCH 91/93] "fbdev: ssd1307fb: Do not always clear the display on boot"" Fixes EMP-373 Signed-off-by: Raymond Siudak --- .../devicetree/bindings/display/fbdev.txt | 2 +- drivers/video/fbdev/core/fb_of.c | 3 +- drivers/video/fbdev/ssd1307fb.c | 113 ++++++++++++------ include/linux/fb.h | 2 +- 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/Documentation/devicetree/bindings/display/fbdev.txt b/Documentation/devicetree/bindings/display/fbdev.txt index d3ed6ef846f614..5c572a123e6a61 100644 --- a/Documentation/devicetree/bindings/display/fbdev.txt +++ b/Documentation/devicetree/bindings/display/fbdev.txt @@ -1,6 +1,6 @@ General framebuffer properties: Optional properties for framebuffers: - - clear-on-probe : clear the framebuffer during probe (bool) + - no-clear-on-probe : Do not clear the framebuffer during probe (bool) - rotate : rotate a framebuffer over 0, 1, 2 and 3 (or 0, 90, 180 and 270) degree's (integer) diff --git a/drivers/video/fbdev/core/fb_of.c b/drivers/video/fbdev/core/fb_of.c index 57bc3dc30bf3d5..cb2de340d614c9 100644 --- a/drivers/video/fbdev/core/fb_of.c +++ b/drivers/video/fbdev/core/fb_of.c @@ -17,10 +17,9 @@ void fb_parse_properties(struct device *dev, struct fb_of_properties *prop) { - bool clear; u16 rotate; - clear = device_property_read_bool(dev, "clear-on-probe"); + prop->no_clear_on_probe = device_property_read_bool(dev, "no-clear-on-probe"); if (device_property_read_u16(dev, "rotate", &rotate)) rotate = FB_ROTATE_UR; diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 7fa613f55cdc58..84ce3f397b1c63 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -77,6 +77,7 @@ struct ssd1307fb_par { struct pwm_device *pwm; u32 pwm_period; struct gpio_desc *reset; + bool no_clear_on_probe; struct regulator *vbat_reg; u32 seg_remap; u32 vcomh; @@ -249,6 +250,65 @@ static int ssd1307fb_blank(int blank_mode, struct fb_info *info) return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); } +static int ssd1307fb_set_par(struct fb_info *info) +{ + struct ssd1307fb_par *par = info->par; + int ret; + u32 com_invdir; + + /* Set segment re-map */ + 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; + + /* Switch to horizontal addressing mode */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_ADDRESS_MODE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, + SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); + if (ret < 0) + return ret; + + /* Set column range */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, 0x0); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, par->width - 1); + if (ret < 0) + return ret; + + /* Set page range */ + ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, 0x0); + if (ret < 0) + return ret; + + ret = ssd1307fb_write_cmd(par->client, + par->page_offset + (par->height / 8) - 1); + if (ret < 0) + return ret; + + return 0; +} + static void ssd1307fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { struct ssd1307fb_par *par = info->par; @@ -275,6 +335,7 @@ static struct fb_ops ssd1307fb_ops = { .fb_read = fb_sys_read, .fb_write = ssd1307fb_write, .fb_blank = ssd1307fb_blank, + .fb_set_par = ssd1307fb_set_par, .fb_fillrect = ssd1307fb_fillrect, .fb_copyarea = ssd1307fb_copyarea, .fb_imageblit = ssd1307fb_imageblit, @@ -415,45 +476,9 @@ static int ssd1307fb_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); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, - SSD1307FB_SET_ADDRESS_MODE_HORIZONTAL); - if (ret < 0) - return ret; - - /* Set column range */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_COL_RANGE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, 0x0); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, par->width - 1); - if (ret < 0) - return ret; - - /* Set page range */ - ret = ssd1307fb_write_cmd(par->client, SSD1307FB_SET_PAGE_RANGE); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, 0x0); - if (ret < 0) - return ret; - - ret = ssd1307fb_write_cmd(par->client, - par->page_offset + (par->height / 8) - 1); - if (ret < 0) - return ret; - /* Clear the screen */ - ssd1307fb_update_display(par); + if (!par->no_clear_on_probe) + ssd1307fb_update_display(par); /* Turn on the display */ ret = ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); @@ -557,6 +582,7 @@ static int ssd1307fb_probe(struct i2c_client *client, struct fb_deferred_io *ssd1307fb_defio; u32 vmem_size; struct ssd1307fb_par *par; + struct fb_of_properties prop; u8 *vmem; int ret; @@ -598,7 +624,10 @@ static int ssd1307fb_probe(struct i2c_client *client, } } - if (of_property_read_u32(node, "solomon,width", &par->width)) + fb_parse_properties(&client->dev, &prop); + par->no_clear_on_probe = prop.no_clear_on_probe; + + if (of_property_read_u32(node, "solomon,width", &par->width)) par->width = 96; if (of_property_read_u32(node, "solomon,height", &par->height)) @@ -695,6 +724,12 @@ static int ssd1307fb_probe(struct i2c_client *client, if (ret) goto regulator_enable_error; + ret = ssd1307fb_set_par(info); + if (ret) { + dev_err(&client->dev, "unable to setup parameters\n"); + goto bl_init_error; + } + ret = register_framebuffer(info); if (ret) { dev_err(&client->dev, "Couldn't register the framebuffer\n"); diff --git a/include/linux/fb.h b/include/linux/fb.h index 08b6825b00152e..b62113926c5703 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -121,7 +121,7 @@ struct fb_cursor_user { }; struct fb_of_properties { - bool clear_on_probe; + bool no_clear_on_probe; __u8 rotate; }; From eaace6d4aede0e0cb6286bdd5929a1cba4fb4dc9 Mon Sep 17 00:00:00 2001 From: Robert Delien Date: Fri, 29 Mar 2019 15:30:02 +0100 Subject: [PATCH 92/93] PWM beeper forcing PWM to inactive statr When disabling, some PWM hardware IP leaves the pin in the state it happens to be in at that very moment. To make sure it is in the inactive state, the duty cycle is now set to 0 first, before PWM is being disabled. Solves: EMP-628 --- drivers/input/misc/pwm-beeper.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/input/misc/pwm-beeper.c b/drivers/input/misc/pwm-beeper.c index edca0d737750bd..7a53e008f29d4f 100644 --- a/drivers/input/misc/pwm-beeper.c +++ b/drivers/input/misc/pwm-beeper.c @@ -67,11 +67,22 @@ static int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period) static void pwm_beeper_off(struct pwm_beeper *beeper) { + struct pwm_state state; + if (beeper->amplifier_on) { regulator_disable(beeper->amplifier); beeper->amplifier_on = false; } + /* + * When disabling, some PWM hardware IP leaves the pin in the state it + * happens to be in. To make sure it is in the inactive state, set the + * duty cycle to 0 first. + */ + pwm_get_state(beeper->pwm, &state); + state.duty_cycle = 0; + pwm_apply_state(beeper->pwm, &state); + pwm_disable(beeper->pwm); } From 6c9945fe1528df97274384e45733e49a4b04db95 Mon Sep 17 00:00:00 2001 From: ckielstra Date: Wed, 14 Jul 2021 10:20:03 +0200 Subject: [PATCH 93/93] fbdev: ssd1307fb: Change default display refresh frequency from 1 to 15 Hz EM-3628 There are better ways for modifying a module's parameters than changing the source code, but... modifying the refreshrate by means of a parameter to modprobe didn't work. There are two old commits for this that were removed when the UM3 support was removed, but restoring those old commits didn't fix the slow display. See: - jedi-build commit 1832dc6831fd48316caac04dfd6d4ce54e859d5d - um-kernel commit 5f1b51f7f26a36deafce1f1d3e880bec63bd2765 --- drivers/video/fbdev/ssd1307fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 84ce3f397b1c63..b599faeb7d1f3a 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -44,7 +44,7 @@ #define MAX_CONTRAST 255 -#define REFRESHRATE 1 +#define REFRESHRATE 15 static u_int refreshrate = REFRESHRATE; module_param(refreshrate, uint, 0);