Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 2/2] ARM: imx: Add speed grading check for imx6ul
From: Shawn Guo @ 2016-12-30 11:57 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1480475098-2037-2-git-send-email-ping.bai@nxp.com>

On Wed, Nov 30, 2016 at 11:04:58AM +0800, Bai Ping wrote:
> On i.MX6UL, it has two type of part, the difference
> is the max ARM frequency that can run at(528MHz/700MHz).
> The part can be identified by part marking for speed grading
> fuse. This patch add speed grading check to disable the unsupported
> OPP dynamically.
> 
> Signed-off-by: Bai Ping <ping.bai@nxp.com>

We chose to handle such speed grading thing in IMX platform code with
the assumption that this is a special case for i.MX6Q only.  No, I'm not
going to take such code for any other i.MX6 SoCs.  Please patch cpufreq
driver to handle the speed grading check.

Shawn

^ permalink raw reply

* [PATCH] drivers: remoteproc: constify rproc_ops structures
From: Bjorn Andersson @ 2016-12-30 12:06 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1481974176-19912-1-git-send-email-bhumirks@gmail.com>

On Sat 17 Dec 03:29 PST 2016, Bhumika Goyal wrote:

> Declare rproc_ops structures as const as they are only passed as an
> argument to the function rproc_alloc. This argument is of type const, so
> rproc_ops structures having this property can be declared const too.
> Done using Coccinelle:
> 
> @r1 disable optional_qualifier @
> identifier i;
> position p;
> @@
> static struct rproc_ops i at p = {...};
> 
> @ok1@
> identifier r1.i;
> position p;
> @@
> rproc_alloc(...,&i at p,...)
> 
> @bad@
> position p!={r1.p,ok1.p};
> identifier r1.i;
> @@
> i at p
> 
> @depends on !bad disable optional_qualifier@
> identifier r1.i;
> @@
> +const
> struct rproc_ops i;
> 
> File sizes before:
>    text	   data	    bss	    dec	    hex	filename
>    1258	    416	      0	   1674	    68a remoteproc/omap_remoteproc.o
>    2402	    240	      0	   2642	    a52 remoteproc/st_remoteproc.o
>    2064	    272	      0	   2336	    920 remoteproc/st_slim_rproc.o
>    2160	    240	      0	   2400	    960 remoteproc/wkup_m3_rproc.o
> 
> File sizes after:
>    text	   data	    bss	    dec	    hex	filename
>    1297	    368	      0	   1665	    681 remoteproc/omap_remoteproc.o
>    2434	    192	      0	   2626	    a42 remoteproc/st_remoteproc.o
>    2112	    240	      0	   2352	    930 remoteproc/st_slim_rproc.o
>    2200	    192	      0	   2392	    958 remoteproc/wkup_m3_rproc.o
> 
> Signed-off-by: Bhumika Goyal <bhumirks@gmail.com>

Thanks Bhumika, this looks good.

But as Suman already asked. Is there any reason why da8xx_remoteproc.c
did not get updated? It looks like the same change would apply there.


Unless I'm missing something, please update the patch and I'll be happy
to apply it.

Regards,
Bjorn

^ permalink raw reply

* [BUG] ARM64: amlogic: gxbb: unhandled level 2 translation fault (11)
From: Heinrich Schuchardt @ 2016-12-30 12:18 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <c91b83af-0eb8-c519-3428-12f34b04d725@baylibre.com>

On 12/30/2016 10:44 AM, Neil Armstrong wrote:
> On 12/30/2016 09:51 AM, Neil Armstrong wrote:
>> On 12/29/2016 10:18 PM, Heinrich Schuchardt wrote:
>>> On 12/29/2016 10:07 AM, Neil Armstrong wrote:
>>>> On 12/24/2016 03:00 PM, Heinrich Schuchardt wrote:
>>>>> When trying to run sddm on an Hardkernel Odroid C2 I invariably run into the
>>>>> translation fault below.
>>>>>
>>>>> The following mail thread relates this kind of problem to TLB (translation
>>>>> lookaside buffer) broadcasting.
>>>>>
>>>>> https://lkml.org/lkml/2014/4/15/207
>>>>>
>>>>> [ 3163.014263] sddm[1851]: unhandled level 2 translation fault (11) at 0x00000160, esr 0x82000006
>>>>> [ 3163.017287] pgd = ffff80007bf86000
>>>>> [ 3163.020589] [00000160] *pgd=000000007a8a3003
>>>>> [ 3163.024733] , *pud=000000007be9c003
>>>>> [ 3163.028095] , *pmd=0000000000000000
>>>>>
>>>>>
>>>>> [ 3163.033026] CPU: 1 PID: 1851 Comm: sddm Not tainted 4.9.0-next-20161212-r022-arm64 #1
>>>>> [ 3163.040831] Hardware name: Hardkernel ODROID-C2 (DT)
>>>>> [ 3163.045698] task: ffff80007bc6d780 task.stack: ffff80007c524000
>>>>> [ 3163.051563] PC is at 0x160
>>>>> [ 3163.054231] LR is at 0xffff9a9fbc98
>>>>> [ 3163.057686] pc : [<0000000000000160>] lr : [<0000ffff9a9fbc98>] pstate: 40000000
>>>>> [ 3163.065022] sp : 0000ffffd7180130
>>>>> [ 3163.068281] x29: 0000ffffd7180130 x28: 0000ffffd7180288 
>>>>> [ 3163.073538] x27: 0000ffff9aa94000 x26: 0000000000000001 
>>>>> [ 3163.078798] x25: 0000000000000000 x24: 0000ffffd7180410 
>>>>> [ 3163.084060] x23: 000000000e0c2190 x22: 000000000e0ca5c0 
>>>>> [ 3163.089322] x21: 0000ffff9ac35000 x20: 0000000000454fa9 
>>>>> [ 3163.094583] x19: 0000000000454fa8 x18: 000000000e0b5938 
>>>>> [ 3163.099843] x17: 0000ffff9a3f2988 x16: 0000ffff9ac36aa0 
>>>>> [ 3163.105105] x15: 0000000000000000 x14: 0000000000000000 
>>>>> [ 3163.110367] x13: 6d00640064007300 x12: 0800000005000000 
>>>>> [ 3163.115627] x11: 0000040000000000 x10: 0000a00000000000 
>>>>> [ 3163.120889] x9 : 00003fffffffffff x8 : 0000000000000000 
>>>>> [ 3163.126150] x7 : 000000000e0cb520 x6 : 0000000000454fc0 
>>>>> [ 3163.131412] x5 : 0000ffffd717ffd8 x4 : 000000000e0cb510 
>>>>> [ 3163.136680] x3 : 0000000000000004 x2 : f2f9022b551b3900 
>>>>> [ 3163.141935] x1 : 0000000000000160 x0 : 000000000e0ca5c0 
>>>>>
>>>>> Best regards
>>>>>
>>>>> Heinrich Schuchardt
>>>>
>>>> Hi Heinrich,
>>>>
>>>> I personally never had this issue even while loading huge applications loke LibreOffice and Gnome environment.
>>>>
>>>> I will have a look and try to reproduce this issue, can you provide us your configuration and user-space complete use case ?
>>>>
>>>> Neil
>>>>
>>>
>>> Hello Neil,
>>>
>>> the kernel is build with
>>> https://github.com/xypron/kernel-odroid-c2/tree/f8d565ff755e92fd585f5ae10123ce20abe03968
>>>
>>> Especially look at the patch directory and config/config-next-20161212.
>>>
>>> The userland is Debian Stretch with this package:
>>> https://packages.debian.org/stretch/sddm
>>>
>>> The link
>>> https://www.spinics.net/lists/arm-kernel/msg550204.html
>>> that you mention in a separate mail just links to this very thread due
>>> to linux-arm-kernel at lists.infradead.org being in copy.
>>>
>>> Best regards
>>>
>>> Heinrich Schuchardt
>>>
>>
>> Hi,
>>
>> Thanks for the details, but why do you use the next-20161212 tag ? does it work with 4.10-rc1 or previous next tags ?
>>
>> Neil
>>
> 
> Hi Heinrich,
> 
> I'm able to reproduce the bug using SDMM from Ubuntu running a 4.10-rc1 patched with memory zones :
> [   17.988446] sddm-greeter[2366]: unhandled level 2 translation fault (11) at 0x00000000, esr 0x92000006
> [   17.988451] pgd = ffff80003c3ee000
> [   17.988472] [00000000] *pgd=00000000398bd003
> [   17.988474] , *pud=00000000398bf003
> [   17.990477] , *pmd=0000000000000000
> 
> 
> [   17.990485] CPU: 3 PID: 2366 Comm: sddm-greeter Not tainted 4.10.0-rc1-00004-gd3f812382-dirty #488
> [   17.990487] Hardware name: Amlogic Meson GXBB P200 Development Board (DT)
> [   17.990489] task: ffff80003c160000 task.stack: ffff80003c314000
> [   17.990493] PC is at 0xffffb2999994
> [   17.990495] LR is at 0xffffb299f774
> [   17.990497] pc : [<0000ffffb2999994>] lr : [<0000ffffb299f774>] pstate: 20000000
> [   17.990503] sp : 0000ffffd2d1d5b0
> [   17.990504] x29: 0000ffffd2d1d5b0 x28: 0000ffffad10e010
> [   17.990508] x27: 0000000009c376d0 x26: 0000000000000001
> [   17.990511] x25: 0000ffffb2b67000 x24: 0000ffffad10e010
> [   17.990514] x23: 0000000000000000 x22: 0000000000000000
> [   17.990517] x21: 0000000009c376d0 x20: 0000ffffb2fcdcb8
> [   17.990520] x19: 0000ffffb2b2b000 x18: 0000000009c78450
> [   17.990523] x17: 0000ffffb2b68068 x16: 0000ffffb2022158
> [   17.990526] x15: 0000ffffb2fcd000 x14: 0000000000000000
> [   17.990529] x13: aaaaaaaaaaaaaaab x12: 0000000000010000
> [   17.990532] x11: 0000000000000008 x10: 0000000009c3b980
> [   17.990535] x9 : 0000000009c38c40 x8 : 0000ffffd2d1d4e0
> [   17.990538] x7 : 0000000000000000 x6 : 0000000009c3b998
> [   17.990541] x5 : 0000000009c3b980 x4 : 0000800000000000
> [   17.990544] x3 : 00000000fffffff0 x2 : 0000ffffad10e010
> [   17.990547] x1 : 0000000009c379d8 x0 : 0000000000000000
> 
> Looking about other occurrence of such error, it seems it may be a issue from sdmm instead.
> 
> I'll continue searching,
> Neil
> 
> 
Hello Neil,

I am seeing a simular error when I try to use x2go

to have a remote desktop on the Odroid C2.

[47912.997112] fuse init (API version 7.26)
[47913.405815] x2goagent[28395]: unhandled level 0 translation fault
(11) at 0x3042415a74614c, esr 0x82000004
[47913.409849] pgd = ffff800079a11000
[47913.413704] [3042415a74614c] *pgd=0000000000000000


[47913.420673] CPU: 1 PID: 28395 Comm: x2goagent Not tainted
4.9.0-next-20161212-r024-arm64 #1
[47913.428992] Hardware name: Hardkernel ODROID-C2 (DT)
[47913.433906] task: ffff80007c670000 task.stack: ffff800079e6c000
[47913.439747] PC is at 0x3042415a74614c
[47913.443400] LR is at 0x323042415a74614c
[47913.447194] pc : [<003042415a74614c>] lr : [<323042415a74614c>]
pstate: 80000000
[47913.454528] sp : 0000ffffd7240140
[47913.457789] x29: 313042414c74614c x28: 000000000000007f
[47913.463016] x27: 000000000000007f x26: 0000000000000001
[47913.468278] x25: 0000000000000000 x24: 0000aaaaee52a7e0
[47913.473540] x23: 0000ffffd7240310 x22: 363042414274614c
[47913.478800] x21: 353042415674614c x20: 343042414374614c
[47913.484061] x19: 333042415874614c x18: 0000000000000000
[47913.489324] x17: 0000ffffb1ce2da0 x16: 0000000000000000
[47913.494589] x15: 0000000000000434 x14: 000000000000001f
[47913.499852] x13: 0000aaaa00703f00 x12: 0000003ff8081fff
[47913.505114] x11: 0000000000000000 x10: 0000000000000002
[47913.510373] x9 : 0000000000000010 x8 : 0000000000000042
[47913.515633] x7 : 011cff0800001fff x6 : 0000022300090101
[47913.520904] x5 : 000000000000088c x4 : 0000000000000007
[47913.526158] x3 : 0000aaaae52c1db0 x2 : 0000aaaae573a5a0
[47913.531417] x1 : 0000000000000000 x0 : 0000000000000000

Best regards

Heinrich Schuchardt

^ permalink raw reply

* Unhandled level 2 translation fault (11) at 0x000000b8, esr 0x92000046, rpi3 (aarch64)
From: Bas van Tiel @ 2016-12-30 12:21 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161230151323.583b7ac5@xhacker>

>> Hi,
>>
>> when using a signal handler as a way to context switch between
>> different usercontexts a reproducible exception occurs on my rpi3 in
>> 64-bit mode. (https://gist.github.com/DanGe42/7148946)
>>
>> Running the context_demo program as a 32-bit ARM executable on a
>> 64-bit kernel is OK, running as a 32 || 64 bit executable on an x86
>> kernel is OK.
>>
>> In the first exception the PC doesn?t look correct, and the *pmd is 0.
>> The 2nd exception happens after running the program again, the PC is 0x0.
>>
>> A successful function trace was not possible -> complete kernel hangup
>> when enabling.
>>
>> Is there another way to gather more information about what is happening?
>
> I can reproduce Segmentation fault with your program on Marvell berlin SoCs
> my kernel version is 4.1, I didn't tested 4.9, 4.10-rc1 etc..
>
> Then I increased the STACKSIZE from 4096 to 8192 in context_demo.c,
> everything works fine now. Maybe arm64 need a bit larger signalstack?
>

yes, increased STACKSIZE to 8192 helps on 4.9/4,10-rc1 but after a
while the exception still occurs, although the message is different.
The *pmd is not 0 in this case.

to trigger this scenario:
- INTERVAL set to 500 [ns]
- kernel with maxcpus=0
- start a 'find /' command in the shell in parallel of the program
- stdout, stderr > redirected to file.

[  850.581983] a.out[173]: unhandled level 3 permission fault (11) at
0x004391f0, esr 0x8200000f
[  850.591833] pgd = ffffffc039311000
[  850.596725] [004391f0] *pgd=0000000039340003
[  850.602145] , *pud=0000000039340003
[  850.608352] , *pmd=000000003922c003
[  850.611963] , *pte=00e80000359c0f53
[  850.618111]
[  850.621102]
[  850.624032] CPU: 0 PID: 173 Comm: a.out Not tainted 4.9.0-v8+ #5
[  850.631314] Hardware name: Raspberry Pi 3 Model B (DT)
[  850.637925] task: ffffffc039a13100 task.stack: ffffffc039a14000
[  850.645314] PC is at 0x4391f0
[  850.649783] LR is at 0x4391f0
[  850.654035] pc : [<00000000004391f0>] lr : [<00000000004391f0>]
pstate: 60000000
[  850.662920] sp : 0000000000420da0
[  850.667516] x29: 00000000004391f0 x28: 0000000000000000
[  850.677145] x27: 0000000000000000 x26: 0000000000000000

When I taskset the context_demo program to other cores that are
completely isolated (CONFIG_NO_HZ_FULL, isolcpus=1,2,3) it will run
continuously with the modified STACKSIZE.

regards
Bas

^ permalink raw reply

* [PATCH 0/2] Use macros to describe gpios on rockchip platform
From: Heiko Stuebner @ 2016-12-30 12:31 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1477140706-6886-1-git-send-email-andy.yan@rock-chips.com>

Am Samstag, 22. Oktober 2016, 20:51:46 CET schrieb Andy Yan:
> As patch 150696e2e3a4("Add GPIO pin index definition for rockchip pinctrl")
> has been applied, now we can use these macros to describe the corresponding
> gpio ranther than hard code numbers, this will make the dts easier to read
> and write.
> 
> Some ideas from Krzysztof's patch on EXYNOS[0].
> 
> [0]https://lkml.org/lkml/2016/9/4/71
> 
> 
> 
> Andy Yan (2):
>   ARM: dts: rockchip: use pin constants to describe gpios
>   ARM64: dts: rockchip: use pin constants to describe gpios

applied to dts branches for 4.11
I've also adapted the gpio interrupts accordingly inside the patches.


Thanks
Heiko

^ permalink raw reply

* [linux-sunxi] [PATCH 1/2] drivers: pinctrl: add driver for Allwinner H5 SoC
From: Linus Walleij @ 2016-12-30 12:55 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1280f095-ab03-93f8-14d2-99d13ba1ce55@arm.com>

On Mon, Dec 26, 2016 at 3:33 PM, Andr? Przywara <andre.przywara@arm.com> wrote:

> So while this patch technically looks correct, I was wondering if we
> should really explore the possibility of making the whole of sunxi
> pinctrl DT controlled.
> I brought this up a while ago, but people weren't overly enthusiastic
> about it, though their argument weren't really convincing to me[1].
>
> So:
> As this "driver" here is basically a table linking GPIO bit settings
> (the actual mux value) to names and every pin we care about needs to be
> enumerated in the DT anyway, why not just add something like:
> allwinner,pinmux = <4>;
> to each pin(group) in the DT and get rid of this "driver" file here
> entirely?

I'm open to that if you can use pinctrl-single which is in the kernel
for this purpose only, and is used with both OMAPs and HiSilicon.

It recently was improved and will be improved more in this cycle,
see for example:
commit 42124bc598f64f84b3335d5a058304207695b84f
pinctrl: Introduce generic #pinctrl-cells and pinctrl_parse_index_with_args

> (...) Also I guess the common sunxi
> pinctrl driver code needs some significant rework.

I would guess is just needs replacing with pinctrl-single in that case.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH 2/3] ARM: dts: imx6ul: Add OCOTP node
From: Shawn Guo @ 2016-12-30 13:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1480689949-17957-2-git-send-email-d.schultz@phytec.de>

On Fri, Dec 02, 2016 at 03:45:48PM +0100, Daniel Schultz wrote:
> This device node adds OCOTP for the i.MX6UL SoC.
> 
> Signed-off-by: Daniel Schultz <d.schultz@phytec.de>

Bai Ping (Cc'ed here) from NXP is sending similar patches [1].  DTS
change looks good to me, but I will not apply it until the driver and
bindings get accepted.

Shawn

[1] https://www.spinics.net/lists/arm-kernel/msg540900.html

> ---
>  arch/arm/boot/dts/imx6ul.dtsi | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/imx6ul.dtsi b/arch/arm/boot/dts/imx6ul.dtsi
> index c5c05fd..ee53795 100644
> --- a/arch/arm/boot/dts/imx6ul.dtsi
> +++ b/arch/arm/boot/dts/imx6ul.dtsi
> @@ -849,6 +849,12 @@
>  				reg = <0x021b0000 0x4000>;
>  			};
>  
> +			ocotp: ocotp at 021bc000 {
> +				compatible = "fsl,imx6ul-ocotp";
> +				reg = <0x021bc000 0x4000>;
> +				clocks = <&clks IMX6UL_CLK_OCOTP>;
> +			};
> +
>  			lcdif: lcdif at 021c8000 {
>  				compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
>  				reg = <0x021c8000 0x4000>;
> -- 
> 1.9.1
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply

* [PATCH] pinctrl: single: fix spelling mistakes on "Ivalid"
From: Linus Walleij @ 2016-12-30 13:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161223004714.7491-1-colin.king@canonical.com>

On Fri, Dec 23, 2016 at 1:47 AM, Colin King <colin.king@canonical.com> wrote:

> From: Colin Ian King <colin.king@canonical.com>
>
> Trivial fixe to spelling mistake "Ivalid" to "Invalid" in
> dev_err  error message.
>
> Signed-off-by: Colin Ian King <colin.king@canonical.com>

Patch applied with Tony's ACK.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH V1] pinctrl:pxa:pinctrl-pxa2xx:- No need of devm functions
From: Linus Walleij @ 2016-12-30 13:02 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <87o9zvw69a.fsf@belgarion.home>

On Thu, Dec 29, 2016 at 8:20 AM, Robert Jarzmik <robert.jarzmik@free.fr> wrote:
> Linus Walleij <linus.walleij@linaro.org> writes:
>
>> On Thu, Dec 8, 2016 at 3:35 PM, Arvind Yadav <arvind.yadav.cs@gmail.com> wrote:
>>
>>> In functions pxa2xx_build_functions, the memory allocated for
>>> 'functions' is live within the function only. After the
>>> allocation it is immediately freed with devm_kfree. There is
>>> no need to allocate memory for 'functions' with devm function
>>> so replace devm_kcalloc  with kcalloc and devm_kfree with kfree.
>>>
>>> Signed-off-by: Arvind Yadav <arvind.yadav.cs@gmail.com>
>>
>> I want the maintainer Robert Jarzmik to review this before I do anything
>
> Hi Linus,
>
> I did review, on December the 10th. I wasn't very enthusiastic about the patch,
> if you check back my reply.

Sorry I missed it (mail overload as usual).

OK dropping this.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH 10/20] gpio: pca953x: Add optional reset gpio control
From: Linus Walleij @ 2016-12-30 13:17 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483050455-10683-11-git-send-email-steve_longerbeam@mentor.com>

On Thu, Dec 29, 2016 at 11:27 PM, Steve Longerbeam
<slongerbeam@gmail.com> wrote:

> Add optional reset-gpios pin control. If present, de-assert the
> specified reset gpio pin to bring the chip out of reset.
>
> Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Alexandre Courbot <gnurou@gmail.com>
> Cc: linux-gpio at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org

This seems like a separate patch from the other 19 patches so please send it
separately so I can handle logistics easier in the future.


> @@ -133,6 +134,7 @@ struct pca953x_chip {
>         const char *const *names;
>         unsigned long driver_data;
>         struct regulator *regulator;
> +       struct gpio_desc *reset_gpio;

Why do you even keep this around in the device state container?

As you only use it in the probe() function, use a local variable.

The descriptor will be free():ed by the devm infrastructure anyways.

> +               /* see if we need to de-assert a reset pin */
> +               chip->reset_gpio = devm_gpiod_get_optional(&client->dev,
> +                                                          "reset",
> +                                                          GPIOD_OUT_LOW);
> +               if (IS_ERR(chip->reset_gpio)) {
> +                       dev_err(&client->dev, "request for reset pin failed\n");
> +                       return PTR_ERR(chip->reset_gpio);
> +               }

Nice.

> +               if (chip->reset_gpio) {
> +                       /* bring chip out of reset */
> +                       dev_info(&client->dev, "releasing reset\n");
> +                       gpiod_set_value(chip->reset_gpio, 0);
> +               }

Is this really needed given that you set it low in the
devm_gpiod_get_optional()?

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH v2 1/4] pinctrl: samsung: Fix the width of PINCFG_TYPE_DRV bitfields for Exynos5433
From: Linus Walleij @ 2016-12-30 13:28 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161230041421.24448-2-andi.shyti@samsung.com>

On Fri, Dec 30, 2016 at 5:14 AM, Andi Shyti <andi.shyti@samsung.com> wrote:

> From: Chanwoo Choi <cw00.choi@samsung.com>
>
> This patch fixes the wrong width of PINCFG_TYPE_DRV bitfields for Exynos5433
> because PINCFG_TYPE_DRV of Exynos5433 has 4bit fields in the *_DRV
> registers. Usually, other Exynos have 2bit field for PINCFG_TYPE_DRV.
>
> Fixes: 3c5ecc9ed353 ("pinctrl: exynos: Add support for Exynos5433")
> Cc: stable at vger.kernel.org
> Cc: Tomasz Figa <tomasz.figa@gmail.com>
> Cc: Krzysztof Kozlowski <krzk@kernel.org>
> Cc: Sylwester Nawrocki <s.nawrocki@samsung.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Kukjin Kim <kgene@kernel.org>
> Cc: Javier Martinez Canillas <javier@osg.samsung.com>
> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>

Nominally I think you should sign this off too Andi, as you are in the delivery
path.

Patch applied for fixes.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH v2 2/4] pinctrl: dt-bindings: samsung: add drive strength macros for Exynos5433
From: Linus Walleij @ 2016-12-30 13:30 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161230041421.24448-3-andi.shyti@samsung.com>

On Fri, Dec 30, 2016 at 5:14 AM, Andi Shyti <andi.shyti@samsung.com> wrote:

> Commit 5db7e3bb87df ("pinctrl: dt-bindings: samsung: Add header with
> values used for configuration") has added a header file for defining the
> pinctrl values in order to avoid hardcoded settings in the Exynos
> DTS related files.
>
> Extend samsung.h to the Exynos5433 for drive strength values
> which are strictly related to the particular SoC and may defer
> from others.
>
> Signed-off-by: Andi Shyti <andi.shyti@samsung.com>

Patch applied with Chanwoo's review tag.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH v2 3/4] ARM64: dts: exynos5433: use macros for pinctrl configuration on Exynos5433
From: Linus Walleij @ 2016-12-30 13:32 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161230041421.24448-4-andi.shyti@samsung.com>

On Fri, Dec 30, 2016 at 5:14 AM, Andi Shyti <andi.shyti@samsung.com> wrote:

> Use the macros defined in include/dt-bindings/pinctrl/samsung.h
> instead of hardcoded values.
>
> Signed-off-by: Andi Shyti <andi.shyti@samsung.com>

These look fine, but that this and the other DTS patch through ARM SoC.

If you also need the headerfile patch (patch 2) to go through ARM SoC
that is fine,
I can take it out of pinctrl if you want.

Yours,
Linus Walleij

^ permalink raw reply

* [PATCH v4 0/4] Add runtime PM support for clocks (on Exynos SoC example)
From: Marek Szyprowski @ 2016-12-30 13:33 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <CGME20161230133344eucas1p21d107e1cf3096916aad1944f7e002bf9@eucas1p2.samsung.com>

Dear All,

This patchset adds runtime PM support to common clock framework. This is an
attempt to implement support for clock controllers, which belongs to a power
domain. This approach works surprisingly well on Exynos 5433 SoC, what allowed
us to solve various freeze/crash issues related to power management.

The main idea behind this patchset is to keep clock's controller power domain
enabled every time when at least one of its clock is enabled or access to its
registers is being made. Clock controller driver (clock provider) can
supply a struct device pointer, which is the used by clock core for tracking and
managing clock's controller runtime pm state. Each clk_prepare() operation will
first call pm_runtime_get_sync() on the supplied device, while clk_unprepare()
will do pm_runtime_put() at the end.

This runtime PM feature has been tested with Exynos4412 (not included in this
patchset) and Exynos5433 clocks drivers. Both have some clocks, which belongs to
respective power domains and need special handling during power on/off
procedures. Till now it wasn't handled at all, what caused various problems.

Patches for clocks drivers change the way the clock provider is initialized.
Instead of CLK_OF_DECLARE based initialization, a complete platform device driver
infrastructure is being used. This is needed to let driver to use runtime PM
feature and integrate with generic power domains. The side-effect of this change
is a delay in clock provider registeration during system boot, so early
initialized drivers might get EPROBEDEFER error when requesting their clocks.
This is an issue for IOMMU drivers, so this patchset will be fully functional
once the deferred probe for IOMMU will be merged.

Patches are based on vanilla v4.10-rc1 kernel.

Best regards
Marek Szyprowski
Samsung R&D Institute Poland

Changelog:

v4:
- Removed patch for Exynos4412 clocks from the patchset. DT bindings for power
  domain for ISP sub-controller needs more discussion. It will be handled
  separately when the runtime PM for clocks feature gets merged.
- Added patch with runtime PM support for Exynos AudioSS clock controller driver
  (needed to enable audio power domain on Exynos5 series).
- Fixes in Exynos5433 driver:
	1. added missing clock for Audio CMU
	2. added support for FSYS CMU
	3. improved support for DISP CMU (thanks to Andrzej Hajda for
	   investigating that).
- Rebased onto v4.10-rc1
- Waiting for a review...

v3: http://www.spinics.net/lists/arm-kernel/msg538122.html
- Removed CLK_RUNTIME_PM flag, core now simply checks if runtime pm is enabled
  for the provided device during clock registration as suggested by Ulf
- Simplified code for exynos4412 isp clock driver registration
- Resolved some other minor issues pointed by Ulf clk core code
- Rebased onto v4.9-rc1 and new version of IOMMU deferred probe patchset

v2: https://www.spinics.net/lists/arm-kernel/msg532798.html
- Simplified clk_pm_runtime_get/put functions, removed workaround for devices
  with disabled runtime pm. Such workaround is no longer needed since commit
  4d23a5e84806b202d9231929c9507ef7cf7a0185 ("PM / Domains: Allow runtime PM
  during system PM phases").
- Added CLK_RUNTIME_PM flag to indicate clocks, for which clock core should
  call runtime pm functions. This solves problem with clocks, for which struct
  device is already registered, but no runtime pm is enabled.
- Extended commit messages according to Ulf suggestions.
- Fixed some style issues pointed by Barlomiej.

v1: http://www.spinics.net/lists/arm-kernel/msg528128.html
- initial version


Marek Szyprowski (4):
  clk: Add support for runtime PM
  clk: samsung: Add support for runtime PM
  clk: samsung: exynos5433: Add runtime PM support
  clk: samsung: exynos-audss: Use runtime PM

 .../devicetree/bindings/clock/clk-exynos-audss.txt |   6 +
 .../devicetree/bindings/clock/exynos5433-clock.txt |  16 +
 drivers/clk/clk.c                                  | 111 +++++-
 drivers/clk/samsung/clk-exynos-audss.c             |  62 +--
 drivers/clk/samsung/clk-exynos5433.c               | 415 ++++++++++++++++-----
 drivers/clk/samsung/clk-pll.c                      |   2 +-
 drivers/clk/samsung/clk.c                          |  12 +-
 drivers/clk/samsung/clk.h                          |   7 +
 8 files changed, 504 insertions(+), 127 deletions(-)

-- 
1.9.1

^ permalink raw reply

* [PATCH v4 1/4] clk: Add support for runtime PM
From: Marek Szyprowski @ 2016-12-30 13:33 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483104816-20885-1-git-send-email-m.szyprowski@samsung.com>

Registers for some clocks might be located in the SOC area, which are under the
power domain. To enable access to those registers respective domain has to be
turned on. Additionally, registers for such clocks will usually loose its
contents when power domain is turned off, so additional saving and restoring of
them might be needed in the clock controller driver.

This patch adds basic infrastructure in the clocks core to allow implementing
driver for such clocks under power domains. Clock provider can supply a
struct device pointer, which is the used by clock core for tracking and managing
clock's controller runtime pm state. Each clk_prepare() operation
will first call pm_runtime_get_sync() on the supplied device, while
clk_unprepare() will do pm_runtime_put() at the end.

Additional calls to pm_runtime_get/put functions are required to ensure that any
register access (like calculating/changing clock rates and unpreparing/disabling
unused clocks on boot) will be done with clock controller in runtime resumend
state.

When one wants to register clock controller, which make use of this feature, he
has to:
1. Provide a struct device to the core when registering the provider.
2. Ensure to enable runtime PM for that device before registering clocks.
3. Make sure that the runtime PM status of the controller device reflects
   the HW state.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
---
 drivers/clk/clk.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 101 insertions(+), 10 deletions(-)

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 0fb39fe217d1..3cf202ccf5a9 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -21,6 +21,7 @@
 #include <linux/of.h>
 #include <linux/device.h>
 #include <linux/init.h>
+#include <linux/pm_runtime.h>
 #include <linux/sched.h>
 #include <linux/clkdev.h>
 
@@ -46,6 +47,7 @@ struct clk_core {
 	const struct clk_ops	*ops;
 	struct clk_hw		*hw;
 	struct module		*owner;
+	struct device		*dev;
 	struct clk_core		*parent;
 	const char		**parent_names;
 	struct clk_core		**parents;
@@ -87,6 +89,26 @@ struct clk {
 	struct hlist_node clks_node;
 };
 
+/***           runtime pm          ***/
+static int clk_pm_runtime_get(struct clk_core *core)
+{
+	int ret = 0;
+
+	if (!core->dev)
+		return 0;
+
+	ret = pm_runtime_get_sync(core->dev);
+	return ret < 0 ? ret : 0;
+}
+
+static void clk_pm_runtime_put(struct clk_core *core)
+{
+	if (!core->dev)
+		return;
+
+	pm_runtime_put(core->dev);
+}
+
 /***           locking             ***/
 static void clk_prepare_lock(void)
 {
@@ -150,6 +172,8 @@ static void clk_enable_unlock(unsigned long flags)
 
 static bool clk_core_is_prepared(struct clk_core *core)
 {
+	bool status;
+
 	/*
 	 * .is_prepared is optional for clocks that can prepare
 	 * fall back to software usage counter if it is missing
@@ -157,11 +181,20 @@ static bool clk_core_is_prepared(struct clk_core *core)
 	if (!core->ops->is_prepared)
 		return core->prepare_count;
 
-	return core->ops->is_prepared(core->hw);
+	if (clk_pm_runtime_get(core) == 0) {
+		status = core->ops->is_prepared(core->hw);
+		clk_pm_runtime_put(core);
+	} else {
+		status = false;
+	}
+
+	return status;
 }
 
 static bool clk_core_is_enabled(struct clk_core *core)
 {
+	bool status;
+
 	/*
 	 * .is_enabled is only mandatory for clocks that gate
 	 * fall back to software usage counter if .is_enabled is missing
@@ -169,7 +202,30 @@ static bool clk_core_is_enabled(struct clk_core *core)
 	if (!core->ops->is_enabled)
 		return core->enable_count;
 
-	return core->ops->is_enabled(core->hw);
+	/*
+	 * Check if clock controller's device is runtime active before
+	 * calling .is_enabled callback. If not, assume that clock is
+	 * disabled, because we might be called from atomic context, from
+	 * which pm_runtime_get() is not allowed.
+	 * This function is called mainly from clk_disable_unused_subtree,
+	 * which ensures proper runtime pm activation of controller before
+	 * taking enable spinlock, but the below check is needed if one tries
+	 * to call it from other places.
+	 */
+	if (core->dev) {
+		pm_runtime_get_noresume(core->dev);
+		if (!pm_runtime_active(core->dev)) {
+			status = false;
+			goto done;
+		}
+	}
+
+	status = core->ops->is_enabled(core->hw);
+done:
+	if (core->dev)
+		pm_runtime_put(core->dev);
+
+	return status;
 }
 
 /***    helper functions   ***/
@@ -489,6 +545,8 @@ static void clk_core_unprepare(struct clk_core *core)
 	if (core->ops->unprepare)
 		core->ops->unprepare(core->hw);
 
+	clk_pm_runtime_put(core);
+
 	trace_clk_unprepare_complete(core);
 	clk_core_unprepare(core->parent);
 }
@@ -530,10 +588,14 @@ static int clk_core_prepare(struct clk_core *core)
 		return 0;
 
 	if (core->prepare_count == 0) {
-		ret = clk_core_prepare(core->parent);
+		ret = clk_pm_runtime_get(core);
 		if (ret)
 			return ret;
 
+		ret = clk_core_prepare(core->parent);
+		if (ret)
+			goto runtime_put;
+
 		trace_clk_prepare(core);
 
 		if (core->ops->prepare)
@@ -541,15 +603,18 @@ static int clk_core_prepare(struct clk_core *core)
 
 		trace_clk_prepare_complete(core);
 
-		if (ret) {
-			clk_core_unprepare(core->parent);
-			return ret;
-		}
+		if (ret)
+			goto unprepare;
 	}
 
 	core->prepare_count++;
 
 	return 0;
+unprepare:
+	clk_core_unprepare(core->parent);
+runtime_put:
+	clk_pm_runtime_put(core);
+	return ret;
 }
 
 static int clk_core_prepare_lock(struct clk_core *core)
@@ -745,6 +810,9 @@ static void clk_unprepare_unused_subtree(struct clk_core *core)
 	if (core->flags & CLK_IGNORE_UNUSED)
 		return;
 
+	if (clk_pm_runtime_get(core))
+		return;
+
 	if (clk_core_is_prepared(core)) {
 		trace_clk_unprepare(core);
 		if (core->ops->unprepare_unused)
@@ -753,6 +821,8 @@ static void clk_unprepare_unused_subtree(struct clk_core *core)
 			core->ops->unprepare(core->hw);
 		trace_clk_unprepare_complete(core);
 	}
+
+	clk_pm_runtime_put(core);
 }
 
 static void clk_disable_unused_subtree(struct clk_core *core)
@@ -768,6 +838,9 @@ static void clk_disable_unused_subtree(struct clk_core *core)
 	if (core->flags & CLK_OPS_PARENT_ENABLE)
 		clk_core_prepare_enable(core->parent);
 
+	if (clk_pm_runtime_get(core))
+		goto unprepare_out;
+
 	flags = clk_enable_lock();
 
 	if (core->enable_count)
@@ -792,6 +865,8 @@ static void clk_disable_unused_subtree(struct clk_core *core)
 
 unlock_out:
 	clk_enable_unlock(flags);
+	clk_pm_runtime_put(core);
+unprepare_out:
 	if (core->flags & CLK_OPS_PARENT_ENABLE)
 		clk_core_disable_unprepare(core->parent);
 }
@@ -1563,6 +1638,7 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
 {
 	struct clk_core *top, *fail_clk;
 	unsigned long rate = req_rate;
+	int ret = 0;
 
 	if (!core)
 		return 0;
@@ -1579,21 +1655,28 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
 	if (!top)
 		return -EINVAL;
 
+	ret = clk_pm_runtime_get(core);
+	if (ret)
+		return ret;
+
 	/* notify that we are about to change rates */
 	fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
 	if (fail_clk) {
 		pr_debug("%s: failed to set %s rate\n", __func__,
 				fail_clk->name);
 		clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
-		return -EBUSY;
+		ret = -EBUSY;
+		goto err;
 	}
 
 	/* change the rates */
 	clk_change_rate(top);
 
 	core->req_rate = req_rate;
+err:
+	clk_pm_runtime_put(core);
 
-	return 0;
+	return ret;
 }
 
 /**
@@ -1824,12 +1907,16 @@ static int clk_core_set_parent(struct clk_core *core, struct clk_core *parent)
 		p_rate = parent->rate;
 	}
 
+	ret = clk_pm_runtime_get(core);
+	if (ret)
+		goto out;
+
 	/* propagate PRE_RATE_CHANGE notifications */
 	ret = __clk_speculate_rates(core, p_rate);
 
 	/* abort if a driver objects */
 	if (ret & NOTIFY_STOP_MASK)
-		goto out;
+		goto runtime_put;
 
 	/* do the re-parent */
 	ret = __clk_set_parent(core, parent, p_index);
@@ -1842,6 +1929,8 @@ static int clk_core_set_parent(struct clk_core *core, struct clk_core *parent)
 		__clk_recalc_accuracies(core);
 	}
 
+runtime_put:
+	clk_pm_runtime_put(core);
 out:
 	clk_prepare_unlock();
 
@@ -2549,6 +2638,8 @@ struct clk *clk_register(struct device *dev, struct clk_hw *hw)
 		goto fail_name;
 	}
 	core->ops = hw->init->ops;
+	if (dev && pm_runtime_enabled(dev))
+		core->dev = dev;
 	if (dev && dev->driver)
 		core->owner = dev->driver->owner;
 	core->hw = hw;
-- 
1.9.1

^ permalink raw reply related

* [PATCH v4 2/4] clk: samsung: Add support for runtime PM
From: Marek Szyprowski @ 2016-12-30 13:33 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483104816-20885-1-git-send-email-m.szyprowski@samsung.com>

This patch adds struct device pointer to samsung_clk_provider and forwarding it
to clk_register_* functions, so drivers can register clocks, which use runtime
pm feature.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
---
 drivers/clk/samsung/clk-pll.c |  2 +-
 drivers/clk/samsung/clk.c     | 12 ++++++------
 drivers/clk/samsung/clk.h     |  1 +
 3 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/drivers/clk/samsung/clk-pll.c b/drivers/clk/samsung/clk-pll.c
index 9617825daabb..f5249a7e545d 100644
--- a/drivers/clk/samsung/clk-pll.c
+++ b/drivers/clk/samsung/clk-pll.c
@@ -1345,7 +1345,7 @@ static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx,
 	pll->lock_reg = base + pll_clk->lock_offset;
 	pll->con_reg = base + pll_clk->con_offset;
 
-	clk = clk_register(NULL, &pll->hw);
+	clk = clk_register(ctx->dev, &pll->hw);
 	if (IS_ERR(clk)) {
 		pr_err("%s: failed to register pll clock %s : %ld\n",
 			__func__, pll_clk->name, PTR_ERR(clk));
diff --git a/drivers/clk/samsung/clk.c b/drivers/clk/samsung/clk.c
index b7d87d6db9dc..e6923714f024 100644
--- a/drivers/clk/samsung/clk.c
+++ b/drivers/clk/samsung/clk.c
@@ -143,7 +143,7 @@ void __init samsung_clk_register_fixed_rate(struct samsung_clk_provider *ctx,
 	unsigned int idx, ret;
 
 	for (idx = 0; idx < nr_clk; idx++, list++) {
-		clk = clk_register_fixed_rate(NULL, list->name,
+		clk = clk_register_fixed_rate(ctx->dev, list->name,
 			list->parent_name, list->flags, list->fixed_rate);
 		if (IS_ERR(clk)) {
 			pr_err("%s: failed to register clock %s\n", __func__,
@@ -172,7 +172,7 @@ void __init samsung_clk_register_fixed_factor(struct samsung_clk_provider *ctx,
 	unsigned int idx;
 
 	for (idx = 0; idx < nr_clk; idx++, list++) {
-		clk = clk_register_fixed_factor(NULL, list->name,
+		clk = clk_register_fixed_factor(ctx->dev, list->name,
 			list->parent_name, list->flags, list->mult, list->div);
 		if (IS_ERR(clk)) {
 			pr_err("%s: failed to register clock %s\n", __func__,
@@ -193,7 +193,7 @@ void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx,
 	unsigned int idx, ret;
 
 	for (idx = 0; idx < nr_clk; idx++, list++) {
-		clk = clk_register_mux(NULL, list->name, list->parent_names,
+		clk = clk_register_mux(ctx->dev, list->name, list->parent_names,
 			list->num_parents, list->flags,
 			ctx->reg_base + list->offset,
 			list->shift, list->width, list->mux_flags, &ctx->lock);
@@ -226,13 +226,13 @@ void __init samsung_clk_register_div(struct samsung_clk_provider *ctx,
 
 	for (idx = 0; idx < nr_clk; idx++, list++) {
 		if (list->table)
-			clk = clk_register_divider_table(NULL, list->name,
+			clk = clk_register_divider_table(ctx->dev, list->name,
 				list->parent_name, list->flags,
 				ctx->reg_base + list->offset,
 				list->shift, list->width, list->div_flags,
 				list->table, &ctx->lock);
 		else
-			clk = clk_register_divider(NULL, list->name,
+			clk = clk_register_divider(ctx->dev, list->name,
 				list->parent_name, list->flags,
 				ctx->reg_base + list->offset, list->shift,
 				list->width, list->div_flags, &ctx->lock);
@@ -264,7 +264,7 @@ void __init samsung_clk_register_gate(struct samsung_clk_provider *ctx,
 	unsigned int idx, ret;
 
 	for (idx = 0; idx < nr_clk; idx++, list++) {
-		clk = clk_register_gate(NULL, list->name, list->parent_name,
+		clk = clk_register_gate(ctx->dev, list->name, list->parent_name,
 				list->flags, ctx->reg_base + list->offset,
 				list->bit_idx, list->gate_flags, &ctx->lock);
 		if (IS_ERR(clk)) {
diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h
index da3bdebabf1e..9263d8a27c6b 100644
--- a/drivers/clk/samsung/clk.h
+++ b/drivers/clk/samsung/clk.h
@@ -26,6 +26,7 @@
  */
 struct samsung_clk_provider {
 	void __iomem *reg_base;
+	struct device *dev;
 	struct clk_onecell_data clk_data;
 	spinlock_t lock;
 };
-- 
1.9.1

^ permalink raw reply related

* [PATCH v4 3/4] clk: samsung: exynos5433: Add runtime PM support
From: Marek Szyprowski @ 2016-12-30 13:33 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483104816-20885-1-git-send-email-m.szyprowski@samsung.com>

Add runtime pm support for all clock controller units (CMU), which belongs
to power domains and require special handling during on/off operations.
Typically special values has to be written to MUX registers to change
internal clocks parents to OSC clock before turning power off. During such
operation all clocks, which enters CMU has to be enabled to let MUX to
stabilize. Also for each CMU there is one special parent clock, which has
to be enabled all the time when any access to CMU registers is done.

This patch solves most of the mysterious external abort and freeze issues
caused by a lack of proper parent CMU clock enabled or incorrect turn off
procedure.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
---
 .../devicetree/bindings/clock/exynos5433-clock.txt |  16 +
 drivers/clk/samsung/clk-exynos5433.c               | 415 ++++++++++++++++-----
 drivers/clk/samsung/clk.h                          |   6 +
 3 files changed, 352 insertions(+), 85 deletions(-)

diff --git a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
index 1dc80f8811fe..5c7dd12e667a 100644
--- a/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
+++ b/Documentation/devicetree/bindings/clock/exynos5433-clock.txt
@@ -168,6 +168,11 @@ Required Properties:
 		- aclk_cam1_400
 		- aclk_cam1_552
 
+Optional properties:
+  - power-domains: a phandle to respective power domain node as described by
+	generic PM domain bindings (see power/power_domain.txt for more
+	information).
+
 Each clock is assigned an identifier and client nodes can use this identifier
 to specify the clock which they consume.
 
@@ -270,6 +275,7 @@ Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 		       <&cmu_top CLK_ACLK_G2D_266>,
 		       <&cmu_top CLK_ACLK_G2D_400>;
+		power-domains = <&pd_g2d>;
 	};
 
 	cmu_disp: clock-controller at 13b90000 {
@@ -295,6 +301,7 @@ Example 2: Examples of clock controller nodes are listed below.
 		       <&cmu_mif CLK_SCLK_DECON_ECLK_DISP>,
 		       <&cmu_mif CLK_SCLK_DECON_TV_VCLK_DISP>,
 		       <&cmu_mif CLK_ACLK_DISP_333>;
+		power-domains = <&pd_disp>;
 	};
 
 	cmu_aud: clock-controller at 114c0000 {
@@ -304,6 +311,7 @@ Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "fout_aud_pll";
 		clocks = <&xxti>, <&cmu_top CLK_FOUT_AUD_PLL>;
+		power-domains = <&pd_aud>;
 	};
 
 	cmu_bus0: clock-controller at 13600000 {
@@ -340,6 +348,7 @@ Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "aclk_g3d_400";
 		clocks = <&xxti>, <&cmu_top CLK_ACLK_G3D_400>;
+		power-domains = <&pd_g3d>;
 	};
 
 	cmu_gscl: clock-controller at 13cf0000 {
@@ -353,6 +362,7 @@ Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 			<&cmu_top CLK_ACLK_GSCL_111>,
 			<&cmu_top CLK_ACLK_GSCL_333>;
+		power-domains = <&pd_gscl>;
 	};
 
 	cmu_apollo: clock-controller at 11900000 {
@@ -384,6 +394,7 @@ Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 		       <&cmu_top CLK_SCLK_JPEG_MSCL>,
 		       <&cmu_top CLK_ACLK_MSCL_400>;
+		power-domains = <&pd_mscl>;
 	};
 
 	cmu_mfc: clock-controller at 15280000 {
@@ -393,6 +404,7 @@ Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "aclk_mfc_400";
 		clocks = <&xxti>, <&cmu_top CLK_ACLK_MFC_400>;
+		power-domains = <&pd_mfc>;
 	};
 
 	cmu_hevc: clock-controller at 14f80000 {
@@ -402,6 +414,7 @@ Example 2: Examples of clock controller nodes are listed below.
 
 		clock-names = "oscclk", "aclk_hevc_400";
 		clocks = <&xxti>, <&cmu_top CLK_ACLK_HEVC_400>;
+		power-domains = <&pd_hevc>;
 	};
 
 	cmu_isp: clock-controller at 146d0000 {
@@ -415,6 +428,7 @@ Example 2: Examples of clock controller nodes are listed below.
 		clocks = <&xxti>,
 		       <&cmu_top CLK_ACLK_ISP_DIS_400>,
 		       <&cmu_top CLK_ACLK_ISP_400>;
+		power-domains = <&pd_isp>;
 	};
 
 	cmu_cam0: clock-controller at 120d0000 {
@@ -430,6 +444,7 @@ Example 2: Examples of clock controller nodes are listed below.
 		       <&cmu_top CLK_ACLK_CAM0_333>,
 		       <&cmu_top CLK_ACLK_CAM0_400>,
 		       <&cmu_top CLK_ACLK_CAM0_552>;
+		power-domains = <&pd_cam0>;
 	};
 
 	cmu_cam1: clock-controller at 145d0000 {
@@ -451,6 +466,7 @@ Example 2: Examples of clock controller nodes are listed below.
 		       <&cmu_top CLK_ACLK_CAM1_333>,
 		       <&cmu_top CLK_ACLK_CAM1_400>,
 		       <&cmu_top CLK_ACLK_CAM1_552>;
+		power-domains = <&pd_cam1>;
 	};
 
 Example 3: UART controller node that consumes the clock generated by the clock
diff --git a/drivers/clk/samsung/clk-exynos5433.c b/drivers/clk/samsung/clk-exynos5433.c
index f096bd7df40c..9b12bc20c455 100644
--- a/drivers/clk/samsung/clk-exynos5433.c
+++ b/drivers/clk/samsung/clk-exynos5433.c
@@ -9,9 +9,14 @@
  * Common Clock Framework support for Exynos5443 SoC.
  */
 
+#include <linux/clk.h>
 #include <linux/clk-provider.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
 
 #include <dt-bindings/clock/exynos5433.h>
 
@@ -1989,6 +1994,14 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
 	ENABLE_IP_FSYS1,
 };
 
+static const struct samsung_clk_reg_dump fsys_suspend_regs[] = {
+	{ MUX_SEL_FSYS0, 0 },
+	{ MUX_SEL_FSYS1, 0 },
+	{ MUX_SEL_FSYS2, 0 },
+	{ MUX_SEL_FSYS3, 0 },
+	{ MUX_SEL_FSYS4, 0 },
+};
+
 static const struct samsung_fixed_rate_clock fsys_fixed_clks[] __initconst = {
 	/* PHY clocks from USBDRD30_PHY */
 	FRATE(CLK_PHYCLK_USBDRD30_UDRD30_PHYCLOCK_PHY,
@@ -2294,16 +2307,11 @@ static void __init exynos5433_cmu_peris_init(struct device_node *np)
 	.nr_clk_ids		= FSYS_NR_CLK,
 	.clk_regs		= fsys_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(fsys_clk_regs),
+	.suspend_regs		= fsys_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(fsys_suspend_regs),
+	.clk_name		= "aclk_fsys_200",
 };
 
-static void __init exynos5433_cmu_fsys_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &fsys_cmu_info);
-}
-
-CLK_OF_DECLARE(exynos5433_cmu_fsys, "samsung,exynos5433-cmu-fsys",
-		exynos5433_cmu_fsys_init);
-
 /*
  * Register offset definitions for CMU_G2D
  */
@@ -2333,6 +2341,10 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
 	DIV_ENABLE_IP_G2D_SECURE_SMMU_G2D,
 };
 
+static const struct samsung_clk_reg_dump g2d_suspend_regs[] = {
+	{ MUX_SEL_G2D0, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_aclk_g2d_266_user_p)		= { "oscclk", "aclk_g2d_266", };
 PNAME(mout_aclk_g2d_400_user_p)		= { "oscclk", "aclk_g2d_400", };
@@ -2418,16 +2430,11 @@ static void __init exynos5433_cmu_fsys_init(struct device_node *np)
 	.nr_clk_ids		= G2D_NR_CLK,
 	.clk_regs		= g2d_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(g2d_clk_regs),
+	.suspend_regs		= g2d_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(g2d_suspend_regs),
+	.clk_name		= "aclk_g2d_400",
 };
 
-static void __init exynos5433_cmu_g2d_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &g2d_cmu_info);
-}
-
-CLK_OF_DECLARE(exynos5433_cmu_g2d, "samsung,exynos5433-cmu-g2d",
-		exynos5433_cmu_g2d_init);
-
 /*
  * Register offset definitions for CMU_DISP
  */
@@ -2492,6 +2499,16 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
 	CLKOUT_CMU_DISP_DIV_STAT,
 };
 
+static const struct samsung_clk_reg_dump disp_suspend_regs[] = {
+	/* ignore status of external PHY muxes during suspend to avoid hangs */
+	{ MUX_IGNORE_DISP2, 0x00111111 },
+	{ MUX_SEL_DISP0, 0 },
+	{ MUX_SEL_DISP1, 0 },
+	{ MUX_SEL_DISP2, 0 },
+	{ MUX_SEL_DISP3, 0 },
+	{ MUX_SEL_DISP4, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_disp_pll_p)			= { "oscclk", "fout_disp_pll", };
 PNAME(mout_sclk_dsim1_user_p)		= { "oscclk", "sclk_dsim1_disp", };
@@ -2837,16 +2854,11 @@ static void __init exynos5433_cmu_g2d_init(struct device_node *np)
 	.nr_clk_ids		= DISP_NR_CLK,
 	.clk_regs		= disp_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(disp_clk_regs),
+	.suspend_regs		= disp_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(disp_suspend_regs),
+	.clk_name		= "aclk_disp_333",
 };
 
-static void __init exynos5433_cmu_disp_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &disp_cmu_info);
-}
-
-CLK_OF_DECLARE(exynos5433_cmu_disp, "samsung,exynos5433-cmu-disp",
-		exynos5433_cmu_disp_init);
-
 /*
  * Register offset definitions for CMU_AUD
  */
@@ -2881,6 +2893,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
 	ENABLE_IP_AUD1,
 };
 
+static const struct samsung_clk_reg_dump aud_suspend_regs[] = {
+	{ MUX_SEL_AUD0, 0 },
+	{ MUX_SEL_AUD1, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_aud_pll_user_aud_p)	= { "oscclk", "fout_aud_pll", };
 PNAME(mout_sclk_aud_pcm_p)	= { "mout_aud_pll_user", "ioclk_audiocdclk0",};
@@ -3007,16 +3024,11 @@ static void __init exynos5433_cmu_disp_init(struct device_node *np)
 	.nr_clk_ids		= AUD_NR_CLK,
 	.clk_regs		= aud_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(aud_clk_regs),
+	.suspend_regs		= aud_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(aud_suspend_regs),
+	.clk_name		= "fout_aud_pll",
 };
 
-static void __init exynos5433_cmu_aud_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &aud_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_aud, "samsung,exynos5433-cmu-aud",
-		exynos5433_cmu_aud_init);
-
-
 /*
  * Register offset definitions for CMU_BUS{0|1|2}
  */
@@ -3218,6 +3230,10 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
 	CLK_STOPCTRL,
 };
 
+static const struct samsung_clk_reg_dump g3d_suspend_regs[] = {
+	{ MUX_SEL_G3D, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_aclk_g3d_400_p)	= { "mout_g3d_pll", "aclk_g3d_400", };
 PNAME(mout_g3d_pll_p)		= { "oscclk", "fout_g3d_pll", };
@@ -3291,15 +3307,11 @@ static void __init exynos5433_cmu_aud_init(struct device_node *np)
 	.nr_clk_ids		= G3D_NR_CLK,
 	.clk_regs		= g3d_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(g3d_clk_regs),
+	.suspend_regs		= g3d_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(g3d_suspend_regs),
+	.clk_name		= "aclk_g3d_400",
 };
 
-static void __init exynos5433_cmu_g3d_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &g3d_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_g3d, "samsung,exynos5433-cmu-g3d",
-		exynos5433_cmu_g3d_init);
-
 /*
  * Register offset definitions for CMU_GSCL
  */
@@ -3338,6 +3350,12 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
 	ENABLE_IP_GSCL_SECURE_SMMU_GSCL2,
 };
 
+static const struct samsung_clk_reg_dump gscl_suspend_regs[] = {
+	{ MUX_SEL_GSCL, 0 },
+	{ ENABLE_ACLK_GSCL, 0xfff },
+	{ ENABLE_PCLK_GSCL, 0xff },
+};
+
 /* list of all parent clock list */
 PNAME(aclk_gscl_111_user_p)	= { "oscclk", "aclk_gscl_111", };
 PNAME(aclk_gscl_333_user_p)	= { "oscclk", "aclk_gscl_333", };
@@ -3432,15 +3450,11 @@ static void __init exynos5433_cmu_g3d_init(struct device_node *np)
 	.nr_clk_ids		= GSCL_NR_CLK,
 	.clk_regs		= gscl_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(gscl_clk_regs),
+	.suspend_regs		= gscl_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(gscl_suspend_regs),
+	.clk_name		= "aclk_gscl_111",
 };
 
-static void __init exynos5433_cmu_gscl_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &gscl_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_gscl, "samsung,exynos5433-cmu-gscl",
-		exynos5433_cmu_gscl_init);
-
 /*
  * Register offset definitions for CMU_APOLLO
  */
@@ -3966,6 +3980,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
 	ENABLE_IP_MSCL_SECURE_SMMU_JPEG,
 };
 
+static const struct samsung_clk_reg_dump mscl_suspend_regs[] = {
+	{ MUX_SEL_MSCL0, 0 },
+	{ MUX_SEL_MSCL1, 0 },
+};
+
 /* list of all parent clock list */
 PNAME(mout_sclk_jpeg_user_p)		= { "oscclk", "sclk_jpeg_mscl", };
 PNAME(mout_aclk_mscl_400_user_p)	= { "oscclk", "aclk_mscl_400", };
@@ -4078,15 +4097,11 @@ static void __init exynos5433_cmu_atlas_init(struct device_node *np)
 	.nr_clk_ids		= MSCL_NR_CLK,
 	.clk_regs		= mscl_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(mscl_clk_regs),
+	.suspend_regs		= mscl_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(mscl_suspend_regs),
+	.clk_name		= "aclk_mscl_400",
 };
 
-static void __init exynos5433_cmu_mscl_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &mscl_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_mscl, "samsung,exynos5433-cmu-mscl",
-		exynos5433_cmu_mscl_init);
-
 /*
  * Register offset definitions for CMU_MFC
  */
@@ -4116,6 +4131,10 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
 	ENABLE_IP_MFC_SECURE_SMMU_MFC,
 };
 
+static const struct samsung_clk_reg_dump mfc_suspend_regs[] = {
+	{ MUX_SEL_MFC, 0 },
+};
+
 PNAME(mout_aclk_mfc_400_user_p)		= { "oscclk", "aclk_mfc_400", };
 
 static const struct samsung_mux_clock mfc_mux_clks[] __initconst = {
@@ -4186,15 +4205,11 @@ static void __init exynos5433_cmu_mscl_init(struct device_node *np)
 	.nr_clk_ids		= MFC_NR_CLK,
 	.clk_regs		= mfc_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(mfc_clk_regs),
+	.suspend_regs		= mfc_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(mfc_suspend_regs),
+	.clk_name		= "aclk_mfc_400",
 };
 
-static void __init exynos5433_cmu_mfc_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &mfc_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_mfc, "samsung,exynos5433-cmu-mfc",
-		exynos5433_cmu_mfc_init);
-
 /*
  * Register offset definitions for CMU_HEVC
  */
@@ -4224,6 +4239,10 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
 	ENABLE_IP_HEVC_SECURE_SMMU_HEVC,
 };
 
+static const struct samsung_clk_reg_dump hevc_suspend_regs[] = {
+	{ MUX_SEL_HEVC, 0 },
+};
+
 PNAME(mout_aclk_hevc_400_user_p)	= { "oscclk", "aclk_hevc_400", };
 
 static const struct samsung_mux_clock hevc_mux_clks[] __initconst = {
@@ -4296,15 +4315,11 @@ static void __init exynos5433_cmu_mfc_init(struct device_node *np)
 	.nr_clk_ids		= HEVC_NR_CLK,
 	.clk_regs		= hevc_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(hevc_clk_regs),
+	.suspend_regs		= hevc_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(hevc_suspend_regs),
+	.clk_name		= "aclk_hevc_400",
 };
 
-static void __init exynos5433_cmu_hevc_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &hevc_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_hevc, "samsung,exynos5433-cmu-hevc",
-		exynos5433_cmu_hevc_init);
-
 /*
  * Register offset definitions for CMU_ISP
  */
@@ -4338,6 +4353,10 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
 	ENABLE_IP_ISP3,
 };
 
+static const struct samsung_clk_reg_dump isp_suspend_regs[] = {
+	{ MUX_SEL_ISP, 0 },
+};
+
 PNAME(mout_aclk_isp_dis_400_user_p)	= { "oscclk", "aclk_isp_dis_400", };
 PNAME(mout_aclk_isp_400_user_p)		= { "oscclk", "aclk_isp_400", };
 
@@ -4549,15 +4568,11 @@ static void __init exynos5433_cmu_hevc_init(struct device_node *np)
 	.nr_clk_ids		= ISP_NR_CLK,
 	.clk_regs		= isp_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(isp_clk_regs),
+	.suspend_regs		= isp_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(isp_suspend_regs),
+	.clk_name		= "aclk_isp_400",
 };
 
-static void __init exynos5433_cmu_isp_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &isp_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_isp, "samsung,exynos5433-cmu-isp",
-		exynos5433_cmu_isp_init);
-
 /*
  * Register offset definitions for CMU_CAM0
  */
@@ -4621,6 +4636,15 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
 	ENABLE_IP_CAM02,
 	ENABLE_IP_CAM03,
 };
+
+static const struct samsung_clk_reg_dump cam0_suspend_regs[] = {
+	{ MUX_SEL_CAM00, 0 },
+	{ MUX_SEL_CAM01, 0 },
+	{ MUX_SEL_CAM02, 0 },
+	{ MUX_SEL_CAM03, 0 },
+	{ MUX_SEL_CAM04, 0 },
+};
+
 PNAME(mout_aclk_cam0_333_user_p)	= { "oscclk", "aclk_cam0_333", };
 PNAME(mout_aclk_cam0_400_user_p)	= { "oscclk", "aclk_cam0_400", };
 PNAME(mout_aclk_cam0_552_user_p)	= { "oscclk", "aclk_cam0_552", };
@@ -5026,15 +5050,11 @@ static void __init exynos5433_cmu_isp_init(struct device_node *np)
 	.nr_clk_ids		= CAM0_NR_CLK,
 	.clk_regs		= cam0_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(cam0_clk_regs),
+	.suspend_regs		= cam0_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(cam0_suspend_regs),
+	.clk_name		= "aclk_cam0_400",
 };
 
-static void __init exynos5433_cmu_cam0_init(struct device_node *np)
-{
-	samsung_cmu_register_one(np, &cam0_cmu_info);
-}
-CLK_OF_DECLARE(exynos5433_cmu_cam0, "samsung,exynos5433-cmu-cam0",
-		exynos5433_cmu_cam0_init);
-
 /*
  * Register offset definitions for CMU_CAM1
  */
@@ -5081,6 +5101,12 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
 	ENABLE_IP_CAM12,
 };
 
+static const struct samsung_clk_reg_dump cam1_suspend_regs[] = {
+	{ MUX_SEL_CAM10, 0 },
+	{ MUX_SEL_CAM11, 0 },
+	{ MUX_SEL_CAM12, 0 },
+};
+
 PNAME(mout_sclk_isp_uart_user_p)	= { "oscclk", "sclk_isp_uart_cam1", };
 PNAME(mout_sclk_isp_spi1_user_p)	= { "oscclk", "sclk_isp_spi1_cam1", };
 PNAME(mout_sclk_isp_spi0_user_p)	= { "oscclk", "sclk_isp_spi0_cam1", };
@@ -5399,11 +5425,230 @@ static void __init exynos5433_cmu_cam0_init(struct device_node *np)
 	.nr_clk_ids		= CAM1_NR_CLK,
 	.clk_regs		= cam1_clk_regs,
 	.nr_clk_regs		= ARRAY_SIZE(cam1_clk_regs),
+	.suspend_regs		= cam1_suspend_regs,
+	.nr_suspend_regs	= ARRAY_SIZE(cam1_suspend_regs),
+	.clk_name		= "aclk_cam1_400",
+};
+
+
+struct exynos5433_cmu_data {
+	struct samsung_clk_provider ctx;
+
+	struct samsung_clk_reg_dump *clk_save;
+	unsigned int nr_clk_save;
+	const struct samsung_clk_reg_dump *clk_suspend;
+	unsigned int nr_clk_suspend;
+
+	struct clk *clk;
+	struct clk **pclks;
+	int nr_pclks;
+};
+
+static int exynos5433_cmu_suspend(struct device *dev)
+{
+	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
+	int i;
+
+	samsung_clk_save(data->ctx.reg_base, data->clk_save,
+			 data->nr_clk_save);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_enable(data->pclks[i]);
+
+	samsung_clk_restore(data->ctx.reg_base, data->clk_suspend,
+			    data->nr_clk_suspend);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_disable(data->pclks[i]);
+
+	clk_disable(data->clk);
+
+	return 0;
+}
+
+static int exynos5433_cmu_resume(struct device *dev)
+{
+	struct exynos5433_cmu_data *data = dev_get_drvdata(dev);
+	int i;
+
+	clk_enable(data->clk);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_enable(data->pclks[i]);
+
+	samsung_clk_restore(data->ctx.reg_base, data->clk_save,
+			    data->nr_clk_save);
+
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_disable(data->pclks[i]);
+
+	return 0;
+}
+
+static int __init exynos5433_cmu_probe(struct platform_device *pdev)
+{
+	const struct samsung_cmu_info *info;
+	struct exynos5433_cmu_data *data;
+	struct samsung_clk_provider *ctx;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	void __iomem *reg_base;
+	struct clk **clk_table;
+	int i;
+
+	info = of_device_get_match_data(dev);
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	ctx = &data->ctx;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	reg_base = devm_ioremap_resource(dev, res);
+	if (!reg_base) {
+		dev_err(dev, "failed to map registers\n");
+		return -ENOMEM;
+	}
+
+	clk_table = devm_kcalloc(dev, info->nr_clk_ids, sizeof(struct clk *),
+				 GFP_KERNEL);
+	if (!clk_table)
+		return -ENOMEM;
+
+	for (i = 0; i < info->nr_clk_ids; ++i)
+		clk_table[i] = ERR_PTR(-ENOENT);
+
+	ctx->clk_data.clks = clk_table;
+	ctx->clk_data.clk_num = info->nr_clk_ids;
+	ctx->reg_base = reg_base;
+	ctx->dev = dev;
+	spin_lock_init(&ctx->lock);
+
+	data->clk_save = samsung_clk_alloc_reg_dump(info->clk_regs,
+						    info->nr_clk_regs);
+	data->nr_clk_save = info->nr_clk_regs;
+	data->clk_suspend = info->suspend_regs;
+	data->nr_clk_suspend = info->nr_suspend_regs;
+	data->nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
+						    "#clock-cells");
+	if (data->nr_pclks > 0) {
+		data->pclks = devm_kcalloc(dev, sizeof(struct clk *),
+					   data->nr_pclks, GFP_KERNEL);
+
+		for (i = 0; i < data->nr_pclks; i++) {
+			struct clk *clk = of_clk_get(dev->of_node, i);
+
+			if (IS_ERR(clk))
+				return PTR_ERR(clk);
+			data->pclks[i] = clk;
+		}
+	}
+
+	/*
+	 * Prepare all parent clocks here to avoid potential deadlock caused
+	 * by global clock "prepare lock" grabbed by runtime pm callbacks
+	 * from pm workers.
+	 */
+	for (i = 0; i < data->nr_pclks; i++)
+		clk_prepare(data->pclks[i]);
+
+	if (info->clk_name)
+		data->clk = clk_get(dev, info->clk_name);
+	clk_prepare_enable(data->clk);
+
+	platform_set_drvdata(pdev, data);
+
+	/*
+	 * Enable runtime pm here, so clock core with use runtime pm for all
+	 * registered clocks.
+	 */
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	if (info->pll_clks)
+		samsung_clk_register_pll(ctx, info->pll_clks, info->nr_pll_clks,
+					 reg_base);
+	if (info->mux_clks)
+		samsung_clk_register_mux(ctx, info->mux_clks,
+					 info->nr_mux_clks);
+	if (info->div_clks)
+		samsung_clk_register_div(ctx, info->div_clks,
+					 info->nr_div_clks);
+	if (info->gate_clks)
+		samsung_clk_register_gate(ctx, info->gate_clks,
+					  info->nr_gate_clks);
+	if (info->fixed_clks)
+		samsung_clk_register_fixed_rate(ctx, info->fixed_clks,
+						info->nr_fixed_clks);
+	if (info->fixed_factor_clks)
+		samsung_clk_register_fixed_factor(ctx, info->fixed_factor_clks,
+						  info->nr_fixed_factor_clks);
+
+	samsung_clk_of_add_provider(dev->of_node, ctx);
+
+	return 0;
+}
+
+static const struct of_device_id exynos5433_cmu_of_match[] = {
+	{
+		.compatible = "samsung,exynos5433-cmu-aud",
+		.data = &aud_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-cam0",
+		.data = &cam0_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-cam1",
+		.data = &cam1_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-disp",
+		.data = &disp_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-g2d",
+		.data = &g2d_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-g3d",
+		.data = &g3d_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-fsys",
+		.data = &fsys_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-gscl",
+		.data = &gscl_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-mfc",
+		.data = &mfc_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-hevc",
+		.data = &hevc_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-isp",
+		.data = &isp_cmu_info,
+	}, {
+		.compatible = "samsung,exynos5433-cmu-mscl",
+		.data = &mscl_cmu_info,
+	}, {
+	},
+};
+
+static const struct dev_pm_ops exynos5433_cmu_pm_ops = {
+	SET_RUNTIME_PM_OPS(exynos5433_cmu_suspend, exynos5433_cmu_resume,
+			   NULL)
+	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				     pm_runtime_force_resume)
+};
+
+static struct platform_driver exynos5433_cmu_driver __refdata = {
+	.driver	= {
+		.name = "exynos5433-cmu",
+		.of_match_table = exynos5433_cmu_of_match,
+		.suppress_bind_attrs = true,
+		.pm = &exynos5433_cmu_pm_ops,
+	},
+	.probe = exynos5433_cmu_probe,
 };
 
-static void __init exynos5433_cmu_cam1_init(struct device_node *np)
+static int __init exynos5433_cmu_init(void)
 {
-	samsung_cmu_register_one(np, &cam1_cmu_info);
+	return platform_driver_register(&exynos5433_cmu_driver);
 }
-CLK_OF_DECLARE(exynos5433_cmu_cam1, "samsung,exynos5433-cmu-cam1",
-		exynos5433_cmu_cam1_init);
+core_initcall(exynos5433_cmu_init);
diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h
index 9263d8a27c6b..664020cb4794 100644
--- a/drivers/clk/samsung/clk.h
+++ b/drivers/clk/samsung/clk.h
@@ -354,6 +354,12 @@ struct samsung_cmu_info {
 	/* list and number of clocks registers */
 	const unsigned long *clk_regs;
 	unsigned int nr_clk_regs;
+
+	/* list and number of clocks registers to set before suspend */
+	const struct samsung_clk_reg_dump *suspend_regs;
+	unsigned int nr_suspend_regs;
+	/* name of the parent clock needed for CMU register access */
+	const char *clk_name;
 };
 
 extern struct samsung_clk_provider *__init samsung_clk_init(
-- 
1.9.1

^ permalink raw reply related

* [PATCH v4 4/4] clk: samsung: exynos-audss: Use runtime PM
From: Marek Szyprowski @ 2016-12-30 13:33 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1483104816-20885-1-git-send-email-m.szyprowski@samsung.com>

This patch adds support for runtime PM to Exynos Audio SubSystem driver to
enable full support for audio power domain on Exynos5 SoCs. The main change
is moving register saving and restoring code from system sleep PM ops to
runtime PM ops and implementing system sleep PM ops with generic
pm_runtime_force_suspend/resume helpers. Runtime PM of the Exynos AudSS
device is managed from clock core depending on the preparation status
of the provided clocks.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
---
This patch depends on "clk/samsung: exynos-audss: Replace syscore PM with
platform device PM" patch, which has been already queued for merging:
https://patchwork.kernel.org/patch/9447115/
---
 .../devicetree/bindings/clock/clk-exynos-audss.txt |  6 +++
 drivers/clk/samsung/clk-exynos-audss.c             | 62 +++++++++++++---------
 2 files changed, 43 insertions(+), 25 deletions(-)

diff --git a/Documentation/devicetree/bindings/clock/clk-exynos-audss.txt b/Documentation/devicetree/bindings/clock/clk-exynos-audss.txt
index 0c3d6015868d..f3635d5aeba4 100644
--- a/Documentation/devicetree/bindings/clock/clk-exynos-audss.txt
+++ b/Documentation/devicetree/bindings/clock/clk-exynos-audss.txt
@@ -33,6 +33,12 @@ Required Properties:
 - clock-names: Aliases for the above clocks. They should be "pll_ref",
   "pll_in", "cdclk", "sclk_audio", and "sclk_pcm_in" respectively.
 
+Optional Properties:
+
+  - power-domains: a phandle to respective power domain node as described by
+    generic PM domain bindings (see power/power_domain.txt for more
+    information).
+
 The following is the list of clocks generated by the controller. Each clock is
 assigned an identifier and client nodes use this identifier to specify the
 clock which they consume. Some of the clocks are available only on a particular
diff --git a/drivers/clk/samsung/clk-exynos-audss.c b/drivers/clk/samsung/clk-exynos-audss.c
index cb7df358a27d..25bb569bdd30 100644
--- a/drivers/clk/samsung/clk-exynos-audss.c
+++ b/drivers/clk/samsung/clk-exynos-audss.c
@@ -18,6 +18,7 @@
 #include <linux/syscore_ops.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 
 #include <dt-bindings/clock/exynos-audss-clk.h>
 
@@ -134,6 +135,7 @@ static int exynos_audss_clk_probe(struct platform_device *pdev)
 	struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in;
 	const struct exynos_audss_clk_drvdata *variant;
 	struct resource *res;
+	struct device *dev = &pdev->dev;
 	int i, ret = 0;
 
 	variant = of_device_get_match_data(&pdev->dev);
@@ -141,15 +143,15 @@ static int exynos_audss_clk_probe(struct platform_device *pdev)
 		return -EINVAL;
 
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	reg_base = devm_ioremap_resource(&pdev->dev, res);
+	reg_base = devm_ioremap_resource(dev, res);
 	if (IS_ERR(reg_base)) {
-		dev_err(&pdev->dev, "failed to map audss registers\n");
+		dev_err(dev, "failed to map audss registers\n");
 		return PTR_ERR(reg_base);
 	}
 
 	epll = ERR_PTR(-ENODEV);
 
-	clk_table = devm_kzalloc(&pdev->dev,
+	clk_table = devm_kzalloc(dev,
 				sizeof(struct clk *) * EXYNOS_AUDSS_MAX_CLKS,
 				GFP_KERNEL);
 	if (!clk_table)
@@ -158,8 +160,8 @@ static int exynos_audss_clk_probe(struct platform_device *pdev)
 	clk_data.clks = clk_table;
 	clk_data.clk_num = variant->num_clks;
 
-	pll_ref = devm_clk_get(&pdev->dev, "pll_ref");
-	pll_in = devm_clk_get(&pdev->dev, "pll_in");
+	pll_ref = devm_clk_get(dev, "pll_ref");
+	pll_in = devm_clk_get(dev, "pll_in");
 	if (!IS_ERR(pll_ref))
 		mout_audss_p[0] = __clk_get_name(pll_ref);
 	if (!IS_ERR(pll_in)) {
@@ -170,81 +172,89 @@ static int exynos_audss_clk_probe(struct platform_device *pdev)
 
 			ret = clk_prepare_enable(epll);
 			if (ret) {
-				dev_err(&pdev->dev,
+				dev_err(dev,
 					"failed to prepare the epll clock\n");
 				return ret;
 			}
 		}
 	}
-	clk_table[EXYNOS_MOUT_AUDSS] = clk_register_mux(NULL, "mout_audss",
+
+	/*
+	 * Enable runtime PM here, so clock core with use runtime PM for all
+	 * registered clocks.
+	 */
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	clk_table[EXYNOS_MOUT_AUDSS] = clk_register_mux(dev, "mout_audss",
 				mout_audss_p, ARRAY_SIZE(mout_audss_p),
 				CLK_SET_RATE_NO_REPARENT,
 				reg_base + ASS_CLK_SRC, 0, 1, 0, &lock);
 
-	cdclk = devm_clk_get(&pdev->dev, "cdclk");
-	sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio");
+	cdclk = devm_clk_get(dev, "cdclk");
+	sclk_audio = devm_clk_get(dev, "sclk_audio");
 	if (!IS_ERR(cdclk))
 		mout_i2s_p[1] = __clk_get_name(cdclk);
 	if (!IS_ERR(sclk_audio))
 		mout_i2s_p[2] = __clk_get_name(sclk_audio);
-	clk_table[EXYNOS_MOUT_I2S] = clk_register_mux(NULL, "mout_i2s",
+	clk_table[EXYNOS_MOUT_I2S] = clk_register_mux(dev, "mout_i2s",
 				mout_i2s_p, ARRAY_SIZE(mout_i2s_p),
 				CLK_SET_RATE_NO_REPARENT,
 				reg_base + ASS_CLK_SRC, 2, 2, 0, &lock);
 
-	clk_table[EXYNOS_DOUT_SRP] = clk_register_divider(NULL, "dout_srp",
+	clk_table[EXYNOS_DOUT_SRP] = clk_register_divider(dev, "dout_srp",
 				"mout_audss", 0, reg_base + ASS_CLK_DIV, 0, 4,
 				0, &lock);
 
-	clk_table[EXYNOS_DOUT_AUD_BUS] = clk_register_divider(NULL,
+	clk_table[EXYNOS_DOUT_AUD_BUS] = clk_register_divider(dev,
 				"dout_aud_bus", "dout_srp", 0,
 				reg_base + ASS_CLK_DIV, 4, 4, 0, &lock);
 
-	clk_table[EXYNOS_DOUT_I2S] = clk_register_divider(NULL, "dout_i2s",
+	clk_table[EXYNOS_DOUT_I2S] = clk_register_divider(dev, "dout_i2s",
 				"mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0,
 				&lock);
 
-	clk_table[EXYNOS_SRP_CLK] = clk_register_gate(NULL, "srp_clk",
+	clk_table[EXYNOS_SRP_CLK] = clk_register_gate(dev, "srp_clk",
 				"dout_srp", CLK_SET_RATE_PARENT,
 				reg_base + ASS_CLK_GATE, 0, 0, &lock);
 
-	clk_table[EXYNOS_I2S_BUS] = clk_register_gate(NULL, "i2s_bus",
+	clk_table[EXYNOS_I2S_BUS] = clk_register_gate(dev, "i2s_bus",
 				"dout_aud_bus", CLK_SET_RATE_PARENT,
 				reg_base + ASS_CLK_GATE, 2, 0, &lock);
 
-	clk_table[EXYNOS_SCLK_I2S] = clk_register_gate(NULL, "sclk_i2s",
+	clk_table[EXYNOS_SCLK_I2S] = clk_register_gate(dev, "sclk_i2s",
 				"dout_i2s", CLK_SET_RATE_PARENT,
 				reg_base + ASS_CLK_GATE, 3, 0, &lock);
 
-	clk_table[EXYNOS_PCM_BUS] = clk_register_gate(NULL, "pcm_bus",
+	clk_table[EXYNOS_PCM_BUS] = clk_register_gate(dev, "pcm_bus",
 				 "sclk_pcm", CLK_SET_RATE_PARENT,
 				reg_base + ASS_CLK_GATE, 4, 0, &lock);
 
-	sclk_pcm_in = devm_clk_get(&pdev->dev, "sclk_pcm_in");
+	sclk_pcm_in = devm_clk_get(dev, "sclk_pcm_in");
 	if (!IS_ERR(sclk_pcm_in))
 		sclk_pcm_p = __clk_get_name(sclk_pcm_in);
-	clk_table[EXYNOS_SCLK_PCM] = clk_register_gate(NULL, "sclk_pcm",
+	clk_table[EXYNOS_SCLK_PCM] = clk_register_gate(dev, "sclk_pcm",
 				sclk_pcm_p, CLK_SET_RATE_PARENT,
 				reg_base + ASS_CLK_GATE, 5, 0, &lock);
 
 	if (variant->has_adma_clk) {
-		clk_table[EXYNOS_ADMA] = clk_register_gate(NULL, "adma",
+		clk_table[EXYNOS_ADMA] = clk_register_gate(dev, "adma",
 				"dout_srp", CLK_SET_RATE_PARENT,
 				reg_base + ASS_CLK_GATE, 9, 0, &lock);
 	}
 
 	for (i = 0; i < clk_data.clk_num; i++) {
 		if (IS_ERR(clk_table[i])) {
-			dev_err(&pdev->dev, "failed to register clock %d\n", i);
+			dev_err(dev, "failed to register clock %d\n", i);
 			ret = PTR_ERR(clk_table[i]);
 			goto unregister;
 		}
 	}
 
-	ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get,
+	ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get,
 					&clk_data);
 	if (ret) {
-		dev_err(&pdev->dev, "failed to add clock provider\n");
+		dev_err(dev, "failed to add clock provider\n");
 		goto unregister;
 	}
 
@@ -272,8 +282,10 @@ static int exynos_audss_clk_remove(struct platform_device *pdev)
 }
 
 static const struct dev_pm_ops exynos_audss_clk_pm_ops = {
-	SET_LATE_SYSTEM_SLEEP_PM_OPS(exynos_audss_clk_suspend,
-				     exynos_audss_clk_resume)
+	SET_RUNTIME_PM_OPS(exynos_audss_clk_suspend, exynos_audss_clk_resume,
+			   NULL)
+	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				     pm_runtime_force_resume)
 };
 
 static struct platform_driver exynos_audss_clk_driver = {
-- 
1.9.1

^ permalink raw reply related

* [PATCH 0/5] ARM: dts: boundary: remove hardcoded LVDS bus format
From: Shawn Guo @ 2016-12-30 13:37 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161202090843.22613-1-gary.bisson@boundarydevices.com>

On Fri, Dec 02, 2016 at 10:08:38AM +0100, Gary Bisson wrote:
> Gary Bisson (5):
>   ARM: dts: imx6qdl-nit6xlite: remove hardcoded LVDS bus format
>   ARM: dts: imx6qdl-nitrogen6_max: remove hardcoded LVDS bus format
>   ARM: dts: imx6qdl-nitrogen6_som2: remove hardcoded LVDS bus format
>   ARM: dts: imx6qdl-nitrogen6x: remove hardcoded LVDS bus format
>   ARM: dts: imx6qdl-sabrelite: remove hardcoded LVDS bus format

I squashed them and applied, thanks.

Shawn

^ permalink raw reply

* [PATCH v2] ARM64: dts: meson-gx: Add reserved memory zone and usable memory range
From: Neil Armstrong @ 2016-12-30 13:40 UTC (permalink / raw)
  To: linux-arm-kernel

The Amlogic Meson GXBB/GXL/GXM secure monitor uses part of the memory space,
this patch adds this reserved zone and redefines the usable memory range.

Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
---
Changes since v1 at [1] :
 - Renamed reg into linux,usable-memory to ovveride u-boot memory
 - only kept secmon memory zone

 [1] http://lkml.kernel.org/r/20161212101801.28491-1-narmstrong at baylibre.com

 arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi       |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gx.dtsi                 | 12 ++++++++++++
 arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts    |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts       |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi          |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-meta.dts  |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-pro.dts   |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-telos.dts |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxl-nexbox-a95x.dts     |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts      |  2 +-
 arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts       |  2 +-
 11 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi
index 7a078be..ca3c7fa 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gx-p23x-q20x.dtsi
@@ -56,7 +56,7 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x80000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x7f000000>;
 	};
 
 	vddio_boot: regulator-vddio_boot {
diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
index eada0b5..7f244b5 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
@@ -55,6 +55,18 @@
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		/* global autoconfigured region for contiguous allocations */
+		secmon: secmon {
+			reg = <0x0 0x10000000 0x0 0x200000>;
+			no-map;
+		};
+	};
+
 	cpus {
 		#address-cells = <0x2>;
 		#size-cells = <0x0>;
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts
index 4cbd626..c7f008a 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-nexbox-a95x.dts
@@ -62,7 +62,7 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x40000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x3f000000>;
 	};
 
 	leds {
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts
index 238fbea..546cbe4 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts
@@ -61,7 +61,7 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x80000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x7f000000>;
 	};
 
 	usb_otg_pwr: regulator-usb-pwrs {
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi
index 4a96e0f..1fdf6da 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-p20x.dtsi
@@ -55,7 +55,7 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x40000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x3f000000>;
 	};
 
 	usb_pwr: regulator-usb-pwrs {
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-meta.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-meta.dts
index 62fb496..6ac5c89 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-meta.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-meta.dts
@@ -50,6 +50,6 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x80000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x7f000000>;
 	};
 };
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-pro.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-pro.dts
index 9a9663a..58be8b4 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-pro.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-pro.dts
@@ -50,6 +50,6 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x40000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x3f000000>;
 	};
 };
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-telos.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-telos.dts
index 2fe167b..010cb29 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-telos.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-vega-s95-telos.dts
@@ -50,6 +50,6 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x80000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x7f000000>;
 	};
 };
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-nexbox-a95x.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-nexbox-a95x.dts
index cea4a3e..fb4a89b 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxl-nexbox-a95x.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxl-nexbox-a95x.dts
@@ -60,7 +60,7 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x80000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x7f000000>;
 	};
 
 	vddio_card: gpio-regulator {
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts
index 9639f01..908894c 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxl-s905x-p212.dts
@@ -59,7 +59,7 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x80000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x7f000000>;
 	};
 };
 
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts b/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts
index 5a337d3..2077385 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts
+++ b/arch/arm64/boot/dts/amlogic/meson-gxm-nexbox-a1.dts
@@ -62,7 +62,7 @@
 
 	memory at 0 {
 		device_type = "memory";
-		reg = <0x0 0x0 0x0 0x80000000>;
+		linux,usable-memory = <0x0 0x1000000 0x0 0x7f000000>;
 	};
 
 	vddio_boot: regulator-vddio-boot {
-- 
1.9.1

^ permalink raw reply related

* [PATCH] pinctrl: add some comments to the hog/late init code
From: Linus Walleij @ 2016-12-30 13:45 UTC (permalink / raw)
  To: linux-arm-kernel

It confused me a bit so it may confuse others. Make it crystal
clear what is going on here for any future readers.

Cc: Tony Lindgren <tony@atomide.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/pinctrl/core.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c
index d02e8def9506..a9e3a75fb67d 100644
--- a/drivers/pinctrl/core.c
+++ b/drivers/pinctrl/core.c
@@ -1753,6 +1753,12 @@ static void pinctrl_late_init(struct work_struct *work)
 
 	pctldev = container_of(work, struct pinctrl_dev, late_init.work);
 
+	/*
+	 * If the pin controller does NOT have hogs, this will report an
+	 * error and we skip over this entire branch. This is why we can
+	 * call this function directly when we do not have hogs on the
+	 * device.
+	 */
 	pctldev->p = create_pinctrl(pctldev->dev, pctldev);
 	if (!IS_ERR(pctldev->p)) {
 		kref_get(&pctldev->p->users);
@@ -1847,6 +1853,12 @@ struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
 		goto out_err;
 	}
 
+	/*
+	 * If the device has hogs we want the probe() function of the driver
+	 * to complete before we go in and hog them and add the pin controller
+	 * to the list of controllers. If it has no hogs, we can just complete
+	 * the registration immediately.
+	 */
 	if (pinctrl_dt_has_hogs(pctldev))
 		schedule_delayed_work(&pctldev->late_init, 0);
 	else
-- 
2.9.3

^ permalink raw reply related

* [PATCH] pinctrl: stricten up generic group code
From: Linus Walleij @ 2016-12-30 14:07 UTC (permalink / raw)
  To: linux-arm-kernel

Rename the symbol PINCTRL_GENERIC to PINCTRL_GENERIC_GROUPS since
it all pertains to groups. Replace everywhere.

ifdef out the radix tree and the struct when not using the
generic groups.

Cc: Tony Lindgren <tony@atomide.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/pinctrl/Kconfig |  2 +-
 drivers/pinctrl/core.c  |  6 ++++--
 drivers/pinctrl/core.h  | 32 +++++++++++++++++---------------
 3 files changed, 22 insertions(+), 18 deletions(-)

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index b986998409d1..add257f80d76 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -8,7 +8,7 @@ config PINCTRL
 menu "Pin controllers"
 	depends on PINCTRL
 
-config GENERIC_PINCTRL
+config GENERIC_PINCTRL_GROUPS
 	bool
 
 config PINMUX
diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c
index 7b7d706f869c..736149d3a33d 100644
--- a/drivers/pinctrl/core.c
+++ b/drivers/pinctrl/core.c
@@ -540,7 +540,7 @@ void pinctrl_remove_gpio_range(struct pinctrl_dev *pctldev,
 }
 EXPORT_SYMBOL_GPL(pinctrl_remove_gpio_range);
 
-#ifdef CONFIG_GENERIC_PINCTRL
+#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
 
 /**
  * pinctrl_generic_get_group_count() - returns the number of pin groups
@@ -714,7 +714,7 @@ static void pinctrl_generic_free_groups(struct pinctrl_dev *pctldev)
 static inline void pinctrl_generic_free_groups(struct pinctrl_dev *pctldev)
 {
 }
-#endif /* CONFIG_GENERIC_PINCTRL */
+#endif /* CONFIG_GENERIC_PINCTRL_GROUPS */
 
 /**
  * pinctrl_get_group_selector() - returns the group selector for a group
@@ -1993,7 +1993,9 @@ struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
 	pctldev->desc = pctldesc;
 	pctldev->driver_data = driver_data;
 	INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL);
+#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
 	INIT_RADIX_TREE(&pctldev->pin_group_tree, GFP_KERNEL);
+#endif
 	INIT_LIST_HEAD(&pctldev->gpio_ranges);
 	INIT_DELAYED_WORK(&pctldev->late_init, pinctrl_late_init);
 	pctldev->dev = dev;
diff --git a/drivers/pinctrl/core.h b/drivers/pinctrl/core.h
index af98b6313902..b04c59bf9701 100644
--- a/drivers/pinctrl/core.h
+++ b/drivers/pinctrl/core.h
@@ -43,8 +43,10 @@ struct pinctrl_dev {
 	struct list_head node;
 	struct pinctrl_desc *desc;
 	struct radix_tree_root pin_desc_tree;
+#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
 	struct radix_tree_root pin_group_tree;
 	unsigned int num_groups;
+#endif
 	struct list_head gpio_ranges;
 	struct device *dev;
 	struct module *owner;
@@ -166,6 +168,20 @@ struct pin_desc {
 };
 
 /**
+ * struct pinctrl_maps - a list item containing part of the mapping table
+ * @node: mapping table list node
+ * @maps: array of mapping table entries
+ * @num_maps: the number of entries in @maps
+ */
+struct pinctrl_maps {
+	struct list_head node;
+	struct pinctrl_map const *maps;
+	unsigned num_maps;
+};
+
+#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
+
+/**
  * struct group_desc - generic pin group descriptor
  * @name: name of the pin group
  * @pins: array of pins that belong to the group
@@ -179,20 +195,6 @@ struct group_desc {
 	void *data;
 };
 
-/**
- * struct pinctrl_maps - a list item containing part of the mapping table
- * @node: mapping table list node
- * @maps: array of mapping table entries
- * @num_maps: the number of entries in @maps
- */
-struct pinctrl_maps {
-	struct list_head node;
-	struct pinctrl_map const *maps;
-	unsigned num_maps;
-};
-
-#ifdef CONFIG_GENERIC_PINCTRL
-
 int pinctrl_generic_get_group_count(struct pinctrl_dev *pctldev);
 
 const char *pinctrl_generic_get_group_name(struct pinctrl_dev *pctldev,
@@ -218,7 +220,7 @@ pinctrl_generic_remove_last_group(struct pinctrl_dev *pctldev)
 	return pinctrl_generic_remove_group(pctldev, pctldev->num_groups - 1);
 }
 
-#endif	/* CONFIG_GENERIC_PINCTRL */
+#endif	/* CONFIG_GENERIC_PINCTRL_GROUPS */
 
 struct pinctrl_dev *get_pinctrl_dev_from_devname(const char *dev_name);
 struct pinctrl_dev *get_pinctrl_dev_from_of_node(struct device_node *np);
-- 
2.9.3

^ permalink raw reply related

* [PATCH] ARM: dts: imx6q-utilite-pro: enable 2nd display pipeline
From: Shawn Guo @ 2016-12-30 14:27 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <ec9676f344eb4786a28a3c7b969f0e94@rwthex-s1-b.rwth-ad.de>

On Fri, Dec 02, 2016 at 03:37:22PM +0100, christopher.spinrath at rwth-aachen.de wrote:
> From: Christopher Spinrath <christopher.spinrath@rwth-aachen.de>
> 
> Apart from the already enabled Designware HDMI port, the Utilite Pro
> has a second display pipeline which has the following shape:
> 
>   IPU1 DI0 --> Parallel display --> tfp410 rgb24 to DVI encoder
>                                 --> HDMI connector.
> Enable support for it.
> 
> In addition, since this pipeline is hardwired to IPU1, sever the link
> between IPU1 and the SoC-internal Designware HDMI encoder forcing the
> latter to be connected to IPU2 instead of IPU1. Otherwise, it is not
> possible to drive both displays at high resolution due to the bandwidth
> limitations of a single IPU.
> 
> Signed-off-by: Christopher Spinrath <christopher.spinrath@rwth-aachen.de>

@Philipp, can you help review the changes?

> ---
> 
> Hi all,
> 
> the removal of the link between IPU1 and the Designware HDMI encoder is the
> result of a discussion I had with Philipp Zabel:
> 
>   https://lists.freedesktop.org/archives/dri-devel/2016-November/125399.html .
> 
> Altough it is not possible to connect anything else to IPU1 on the Utilite, this
> approach has at least one disadvantage: if the resolution is low enough such 
> that a single IPU can handle both displays then muxing both displays to IPU1
> would reduce the power consumption.
> 
> However, IMHO omitting the link IPU1 <--> DW HDMI is still the preferrable
> solution since I'm not aware of any OS/driver that is capable of switching IPUs
> or can handle the bandwidth limitation in a sane way. In particular, Linux is
> unusable when both displays are supposed to be driven at high resolution and
> both muxing options for the DW HDMI are available (this is not a userspace
> issue; the system becomes almost unresponsive as soon as the kernel sets the
> initial resolution).
> 
> Cheers,
> Christopher
> 
> P.S.: this patch depends on the tfp410 bridge driver which has recently been
> merged into drm-next.

v4.10-rc1 has the driver, so the dependency is gone now, I guess.

> 
>  arch/arm/boot/dts/imx6q-utilite-pro.dts | 115 ++++++++++++++++++++++++++++++++
>  1 file changed, 115 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/imx6q-utilite-pro.dts b/arch/arm/boot/dts/imx6q-utilite-pro.dts
> index 2200994..69bdd82 100644
> --- a/arch/arm/boot/dts/imx6q-utilite-pro.dts
> +++ b/arch/arm/boot/dts/imx6q-utilite-pro.dts
> @@ -59,6 +59,33 @@
>  		rtc1 = &snvs_rtc;
>  	};
>  
> +	encoder {
> +		compatible = "ti,tfp410";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		ports {
> +			#address-cells = <1>;
> +			#size-cells = <0>;
> +
> +			port at 0 {
> +				reg = <0>;
> +
> +				tfp410_in: endpoint {
> +					remote-endpoint = <&parallel_display_out>;
> +				};
> +			};
> +
> +			port at 1 {
> +				reg = <1>;
> +
> +				tfp410_out: endpoint {
> +					remote-endpoint = <&hdmi_connector_in>;
> +				};
> +			};
> +		};
> +	};
> +
>  	gpio-keys {
>  		compatible = "gpio-keys";
>  		pinctrl-names = "default";
> @@ -72,6 +99,19 @@
>  		};
>  	};
>  
> +	hdmi-connector {
> +		compatible = "hdmi-connector";
> +

The newline is unnecessary.

> +		type = "a";
> +		ddc-i2c-bus = <&i2c_dvi_ddc>;
> +
> +		port {
> +			hdmi_connector_in: endpoint {
> +				remote-endpoint = <&tfp410_out>;
> +			};
> +		};
> +	};
> +
>  	i2cmux {
>  		compatible = "i2c-mux-gpio";
>  		pinctrl-names = "default";
> @@ -105,8 +145,46 @@
>  			#size-cells = <0>;
>  		};
>  	};
> +
> +	parallel-display {
> +		compatible = "fsl,imx-parallel-display";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&pinctrl_ipu1>;
> +

Ditto

I can fix them up if I get a Reviewed-by tag from Philipp on this
version.

Shawn

> +		interface-pix-fmt = "rgb24";
> +
> +		port at 0 {
> +			reg = <0>;
> +
> +			parallel_display_in: endpoint {
> +				remote-endpoint = <&ipu1_di0_disp0>;
> +			};
> +		};
> +
> +		port at 1 {
> +			reg = <1>;
> +
> +			parallel_display_out: endpoint {
> +				remote-endpoint = <&tfp410_in>;
> +			};
> +		};
> +	};
>  };
>  
> +/*
> + * A single IPU is not able to drive both display interfaces available on the
> + * Utilite Pro at high resolution due to its bandwidth limitation. Since the
> + * tfp410 encoder is wired up to IPU1, sever the link between IPU1 and the
> + * SoC-internal Designware HDMI encoder forcing the latter to be connected to
> + * IPU2 instead of IPU1.
> + */
> +/delete-node/&ipu1_di0_hdmi;
> +/delete-node/&hdmi_mux_0;
> +/delete-node/&ipu1_di1_hdmi;
> +/delete-node/&hdmi_mux_1;
> +
>  &hdmi {
>  	ddc-i2c-bus = <&i2c2>;
>  	status = "okay";
> @@ -151,6 +229,39 @@
>  		>;
>  	};
>  
> +	pinctrl_ipu1: ipu1grp {
> +		fsl,pins = <
> +			MX6QDL_PAD_DI0_DISP_CLK__IPU1_DI0_DISP_CLK 0x38
> +			MX6QDL_PAD_DI0_PIN15__IPU1_DI0_PIN15       0x38
> +			MX6QDL_PAD_DI0_PIN2__IPU1_DI0_PIN02        0x38
> +			MX6QDL_PAD_DI0_PIN3__IPU1_DI0_PIN03        0x38
> +			MX6QDL_PAD_DISP0_DAT0__IPU1_DISP0_DATA00   0x38
> +			MX6QDL_PAD_DISP0_DAT1__IPU1_DISP0_DATA01   0x38
> +			MX6QDL_PAD_DISP0_DAT2__IPU1_DISP0_DATA02   0x38
> +			MX6QDL_PAD_DISP0_DAT3__IPU1_DISP0_DATA03   0x38
> +			MX6QDL_PAD_DISP0_DAT4__IPU1_DISP0_DATA04   0x38
> +			MX6QDL_PAD_DISP0_DAT5__IPU1_DISP0_DATA05   0x38
> +			MX6QDL_PAD_DISP0_DAT6__IPU1_DISP0_DATA06   0x38
> +			MX6QDL_PAD_DISP0_DAT7__IPU1_DISP0_DATA07   0x38
> +			MX6QDL_PAD_DISP0_DAT8__IPU1_DISP0_DATA08   0x38
> +			MX6QDL_PAD_DISP0_DAT9__IPU1_DISP0_DATA09   0x38
> +			MX6QDL_PAD_DISP0_DAT10__IPU1_DISP0_DATA10  0x38
> +			MX6QDL_PAD_DISP0_DAT11__IPU1_DISP0_DATA11  0x38
> +			MX6QDL_PAD_DISP0_DAT12__IPU1_DISP0_DATA12  0x38
> +			MX6QDL_PAD_DISP0_DAT13__IPU1_DISP0_DATA13  0x38
> +			MX6QDL_PAD_DISP0_DAT14__IPU1_DISP0_DATA14  0x38
> +			MX6QDL_PAD_DISP0_DAT15__IPU1_DISP0_DATA15  0x38
> +			MX6QDL_PAD_DISP0_DAT16__IPU1_DISP0_DATA16  0x38
> +			MX6QDL_PAD_DISP0_DAT17__IPU1_DISP0_DATA17  0x38
> +			MX6QDL_PAD_DISP0_DAT18__IPU1_DISP0_DATA18  0x38
> +			MX6QDL_PAD_DISP0_DAT19__IPU1_DISP0_DATA19  0x38
> +			MX6QDL_PAD_DISP0_DAT20__IPU1_DISP0_DATA20  0x38
> +			MX6QDL_PAD_DISP0_DAT21__IPU1_DISP0_DATA21  0x38
> +			MX6QDL_PAD_DISP0_DAT22__IPU1_DISP0_DATA22  0x38
> +			MX6QDL_PAD_DISP0_DAT23__IPU1_DISP0_DATA23  0x38
> +		>;
> +	};
> +
>  	pinctrl_uart2: uart2grp {
>  		fsl,pins = <
>  			MX6QDL_PAD_GPIO_7__UART2_TX_DATA 0x1b0b1
> @@ -194,6 +305,10 @@
>  	};
>  };
>  
> +&ipu1_di0_disp0 {
> +	remote-endpoint = <&parallel_display_in>;
> +};
> +
>  &pcie {
>  	pcie at 0,0 {
>  		reg = <0x000000 0 0 0 0>;
> -- 
> 2.10.2
> 

^ permalink raw reply

* [PATCH v7 3/5] ARM: dts: stm32: Add I2C1 support for STM32F429 SoC
From: M'boumba Cedric Madianga @ 2016-12-30 14:36 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <CACRpkdaNyL2iV+risLudW=O55w81-AuEZhMwRu9NLdXpnC2r1w@mail.gmail.com>

 Hi Linus,

2016-12-30 10:07 GMT+01:00 Linus Walleij <linus.walleij@linaro.org>:
> On Fri, Dec 23, 2016 at 2:09 PM, M'boumba Cedric Madianga
> <cedric.madianga@gmail.com> wrote:
>> 2016-12-22 20:11 GMT+01:00 Uwe Kleine-K?nig <u.kleine-koenig@pengutronix.de>:
>>> On Thu, Dec 22, 2016 at 02:35:02PM +0100, M'boumba Cedric Madianga wrote:
>>>> @@ -337,6 +350,16 @@
>>>>                                       slew-rate = <2>;
>>>>                               };
>>>>                       };
>>>> +
>>>> +                     i2c1_pins_b: i2c1 at 0 {
>>>> +                             pins1 {
>>>> +                                     pinmux = <STM32F429_PB9_FUNC_I2C1_SDA>;
>>>> +                                     drive-open-drain;
>>>> +                             };
>>>> +                             pins2 {
>>>> +                                     pinmux = <STM32F429_PB6_FUNC_I2C1_SCL>;
>>>> +                             };
>>>
>>> the second doesn't need the open-drain property? Why?
>>
>> I thought that open-drain was only needed for SDA line.
>> But after double-checking I2C specification, it seems that SDA and SCL
>> lines need open-drain or open-collector to perform the wired-AND
>> function.
>
> I think I2C SDA/SCL must be open drain by definition.
>
> It also requires pull-up resistors, I guess you have these mounted on the board
> so you do not need pull-up from the pin controller?
Yes, I have 1 pull-up resistor of 1,5K ohm for each line (SDA & SCL)
on the board

>
> Yours,
> Linus Walleij

^ permalink raw reply

* [PATCH v9 3/3] iio: adc: add support for Allwinner SoCs ADC
From: Jonathan Cameron @ 2016-12-30 14:40 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20161213143332.24988-4-quentin.schulz@free-electrons.com>

On 13/12/16 14:33, Quentin Schulz wrote:
> The Allwinner SoCs all have an ADC that can also act as a touchscreen
> controller and a thermal sensor. This patch adds the ADC driver which is
> based on the MFD for the same SoCs ADC.
> 
> This also registers the thermal adc channel in the iio map array so
> iio_hwmon could use it without modifying the Device Tree. This registers
> the driver in the thermal framework.
> 
> The thermal sensor requires the IP to be in touchscreen mode to return
> correct values. Therefore, if the user is continuously reading the ADC
> channel(s), the thermal framework in which the thermal sensor is
> registered will switch the IP in touchscreen mode to get a temperature
> value and requires a delay of 100ms (because of the mode switching),
> then the ADC will switch back to ADC mode and requires also a delay of
> 100ms. If the ADC readings are critical to user and the SoC temperature
> is not, this driver is capable of not registering the thermal sensor in
> the thermal framework and thus, "quicken" the ADC readings.
> 
> This driver probes on three different platform_device_id to take into
> account slight differences (registers bit and temperature computation)
> between Allwinner SoCs ADCs.
> 
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> Acked-by: Jonathan Cameron <jic23@kernel.org>
> Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
One comment inline but not a blocker.

I would ideally like an ack from the thermal side.  The relevant code
is small, but best to be sure and keep them in the loop as well.

It does feel a little convoluted to have both this directly providing
a thermal zone and being able to create one indirectly through hwmon as
well but this solution works for me I think...

Cc'd Zang and Eduardo.

Jonathan
> ---
> 
> v9:
>  - clarify comment on why we have to use the parent node as node for
>  registering in thermal framework, (backward compatibility)
>  - clarify comment on why we can disable CONFIG_THERMAL_OF,
>  - clarify Kconfig help to say that CONFIG_THERMAL_OF can be disabled
>  but should not in most cases,
>  - make return value of devm_thermal_zone_of_sensor_register a local
>  variable of the condition block,
>  - correct scale from _PLUS_MICRO to _PLUS_NANO for ADC raw readings
>  scale,
> 
> v8:
>  - remove Kconfig depends on !TOUCHSCREEN_SUN4I (moved to
> MFD_SUN4I_GPADC),
>  - fix return values of regmap_irq_get_virq and platform_get_irq_byname
> stored in an unsigned int and then check if negative,
>  - fix uninitialized ret value when an error occurs while registering
> the thermal sensor in the framework,
> 
> v7:
>  - add Kconfig depends on !TOUCHSCREEN_SUN4I,
>  - remove Kconfig selects THERMAL_OF,
>  - do not register thermal sensor if CONFIG_THERMAL_OF is disabled,
>  - disable irq in irq_handler rather than in read_raw,
>  - add delay when switching the IP's mode or channel (delay empirically found),
>  - quicken thermal sensor interrupt period,
>  - add masks for channel bits,
>  - fix deadlock in sun4i_gpadc_read if regmap_read/write fails,
>  - move some logic from sun4i_gpadc_read to sun4i_prepare_for_irq,
>  - mark last busy for runtime_pm only on success in sun4i_gpadc_read,
>  - remove cached values,
>  - increase wait_for_completion_timeout timeout to 1s to be sure to not miss the
>  thermal interrupt,
>  - add voltage scale,
>  - use devm_iio_device_register,
> 
> v6:
>  - remove "-mfd" from filenames and variables inside MFD driver,
>  - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
>  - cosmetic changes,
>  - use IDs and switch over ID to get cells specific to an architecture, instead
>  of using cells direclty, in of_device_id.data,
>  - compute size of mfd_cells array instead of hardcoded one,
> 
> v5:
>  - correct mail address,
> 
> v4:
>  - rename files and variables from sunxi* to sun4i*,
>  - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
>  - remove TP in defines name,
>  - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
>  - use devm functions for regmap_add_irq_chip and mfd_add_devices,
>  - remove remove functions (now empty thanks to devm functions),
> 
> v3:
>  - use defines in regmap_irq instead of hard coded BITs,
>  - use of_device_id data field to chose which MFD cells to add considering
>    the compatible responsible of the MFD probe,
>  - remove useless initializations,
>  - disable all interrupts before adding them to regmap_irqchip,
>  - add goto error label in probe,
>  - correct wrapping in header license,
>  - move defines from IIO driver to header,
>  - use GENMASK to limit the size of the variable passed to a macro,
>  - prefix register BIT defines with the name of the register,
>  - reorder defines,
> 
> v2:
>  - add license headers,
>  - reorder alphabetically includes,
>  - add SUNXI_GPADC_ prefixes for defines,
> 
>  drivers/iio/adc/Kconfig           |  17 ++
>  drivers/iio/adc/Makefile          |   1 +
>  drivers/iio/adc/sun4i-gpadc-iio.c | 613 ++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/sun4i-gpadc.h   |   2 +
>  4 files changed, 633 insertions(+)
>  create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 99c0514..6a6d369 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -434,6 +434,23 @@ config STX104
>  	  The base port addresses for the devices may be configured via the base
>  	  array module parameter.
>  
> +config SUN4I_GPADC
> +	tristate "Support for the Allwinner SoCs GPADC"
> +	depends on IIO
> +	depends on MFD_SUN4I_GPADC
> +	help
> +	  Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
> +	  GPADC. This ADC provides 4 channels which can be used as an ADC or as
> +	  a touchscreen input and one channel for thermal sensor.
> +
> +	  The thermal sensor slows down ADC readings and can be disabled by
> +	  disabling CONFIG_THERMAL_OF. However, the thermal sensor should be
> +	  enabled by default since the SoC temperature is usually more critical
> +	  than ADC readings.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called sun4i-gpadc-iio.
> +
>  config TI_ADC081C
>  	tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>  	depends on I2C
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 7a40c04..18ce8d6 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -41,6 +41,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>  obj-$(CONFIG_STX104) += stx104.o
> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>  obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>  obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>  obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
> new file mode 100644
> index 0000000..a8e134f
> --- /dev/null
> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
> @@ -0,0 +1,613 @@
> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
> + *
> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it under
> + * the terms of the GNU General Public License version 2 as published by the
> + * Free Software Foundation.
> + *
> + * The Allwinner SoCs all have an ADC that can also act as a touchscreen
> + * controller and a thermal sensor.
> + * The thermal sensor works only when the ADC acts as a touchscreen controller
> + * and is configured to throw an interrupt every fixed periods of time (let say
> + * every X seconds).
> + * One would be tempted to disable the IP on the hardware side rather than
> + * disabling interrupts to save some power but that resets the internal clock of
> + * the IP, resulting in having to wait X seconds every time we want to read the
> + * value of the thermal sensor.
> + * This is also the reason of using autosuspend in pm_runtime. If there was no
> + * autosuspend, the thermal sensor would need X seconds after every
> + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the
> + * thermal sensor to be requested again in a certain time span before it gets
> + * shutdown for not being used.
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/thermal.h>
> +#include <linux/delay.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/driver.h>
> +#include <linux/iio/machine.h>
> +#include <linux/mfd/sun4i-gpadc.h>
> +
> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
> +{
> +	return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
> +}
> +
> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
> +{
> +	return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
> +}
> +
> +struct gpadc_data {
> +	int		temp_offset;
> +	int		temp_scale;
> +	unsigned int	tp_mode_en;
> +	unsigned int	tp_adc_select;
> +	unsigned int	(*adc_chan_select)(unsigned int chan);
> +	unsigned int	adc_chan_mask;
> +};
> +
> +static const struct gpadc_data sun4i_gpadc_data = {
> +	.temp_offset = -1932,
> +	.temp_scale = 133,
> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun4i_gpadc_chan_select,
> +	.adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
> +};
> +
> +static const struct gpadc_data sun5i_gpadc_data = {
> +	.temp_offset = -1447,
> +	.temp_scale = 100,
> +	.tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun4i_gpadc_chan_select,
> +	.adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
> +};
> +
> +static const struct gpadc_data sun6i_gpadc_data = {
> +	.temp_offset = -1623,
> +	.temp_scale = 167,
> +	.tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
> +	.tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
> +	.adc_chan_select = &sun6i_gpadc_chan_select,
> +	.adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
> +};
> +
> +struct sun4i_gpadc_iio {
> +	struct iio_dev			*indio_dev;
> +	struct completion		completion;
> +	int				temp_data;
> +	u32				adc_data;
> +	struct regmap			*regmap;
> +	unsigned int			fifo_data_irq;
> +	atomic_t			ignore_fifo_data_irq;
> +	unsigned int			temp_data_irq;
> +	atomic_t			ignore_temp_data_irq;
> +	const struct gpadc_data		*data;
> +	/* prevents concurrent reads of temperature and ADC */
> +	struct mutex			mutex;
> +};
> +
> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) {		\
> +	.type = IIO_VOLTAGE,					\
> +	.indexed = 1,						\
> +	.channel = _channel,					\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\
> +	.datasheet_name = _name,				\
> +}
> +
> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
> +	{
> +		.adc_channel_label = "temp_adc",
> +		.consumer_dev_name = "iio_hwmon.0",
It's theoretically possible we have another one of these which will make
life interesting.  Oh well, no easy way around that at the mo...
> +	},
> +	{ /* sentinel */ },
> +};
> +
> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
> +	{
> +		.type = IIO_TEMP,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE) |
> +				      BIT(IIO_CHAN_INFO_OFFSET),
> +		.datasheet_name = "temp_adc",
> +	},
> +};
> +
> +static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] = {
> +	SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
> +	SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
> +	SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
> +	SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
> +};
> +
> +static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel,
> +				 unsigned int irq)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +	int ret;
> +	u32 reg;
> +
> +	pm_runtime_get_sync(indio_dev->dev.parent);
> +
> +	reinit_completion(&info->completion);
> +
> +	ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
> +			   SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, &reg);
> +	if (ret)
> +		return ret;
> +
> +	if (irq == info->fifo_data_irq) {
> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
> +				   info->data->tp_mode_en |
> +				   info->data->tp_adc_select |
> +				   info->data->adc_chan_select(channel));
> +		/*
> +		 * When the IP changes channel, it needs a bit of time to get
> +		 * correct values.
> +		 */
> +		if ((reg & info->data->adc_chan_mask) !=
> +			 info->data->adc_chan_select(channel))
> +			mdelay(10);
> +
> +	} else {
> +		/*
> +		 * The temperature sensor returns valid data only when the ADC
> +		 * operates in touchscreen mode.
> +		 */
> +		ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
> +				   info->data->tp_mode_en);
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * When the IP changes mode between ADC or touchscreen, it
> +	 * needs a bit of time to get correct values.
> +	 */
> +	if ((reg & info->data->tp_adc_select) != info->data->tp_adc_select)
> +		mdelay(100);
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
> +			    unsigned int irq)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +	int ret;
> +
> +	mutex_lock(&info->mutex);
> +
> +	ret = sun4i_prepare_for_irq(indio_dev, channel, irq);
> +	if (ret)
> +		goto err;
> +
> +	enable_irq(irq);
> +
> +	/*
> +	 * The temperature sensor throws an interruption periodically (currently
> +	 * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s delay
> +	 * makes sure an interruption occurs in normal conditions. If it doesn't
> +	 * occur, then there is a timeout.
> +	 */
> +	if (!wait_for_completion_timeout(&info->completion,
> +					 msecs_to_jiffies(1000))) {
> +		ret = -ETIMEDOUT;
> +		goto err;
> +	}
> +
> +	if (irq == info->fifo_data_irq)
> +		*val = info->adc_data;
> +	else
> +		*val = info->temp_data;
> +
> +	ret = 0;
> +	pm_runtime_mark_last_busy(indio_dev->dev.parent);
> +
> +err:
> +	pm_runtime_put_autosuspend(indio_dev->dev.parent);
> +	mutex_unlock(&info->mutex);
> +
> +	return ret;
> +}
> +
> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
> +				int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
> +}
> +
> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
> +}
> +
> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	*val = info->data->temp_offset;
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
> +
> +	*val = info->data->temp_scale;
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan, int *val,
> +				int *val2, long mask)
> +{
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_OFFSET:
> +		ret = sun4i_gpadc_temp_offset(indio_dev, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_RAW:
> +		if (chan->type == IIO_VOLTAGE)
> +			ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
> +						   val);
> +		else
> +			ret = sun4i_gpadc_temp_read(indio_dev, val);
> +
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		if (chan->type == IIO_VOLTAGE) {
> +			/* 3000mV / 4096 * raw */
> +			*val = 0;
> +			*val2 = 732421875;
> +			return IIO_VAL_INT_PLUS_NANO;
> +		}
> +
> +		ret = sun4i_gpadc_temp_scale(indio_dev, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static const struct iio_info sun4i_gpadc_iio_info = {
> +	.read_raw = sun4i_gpadc_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
> +{
> +	struct sun4i_gpadc_iio *info = dev_id;
> +
> +	if (atomic_read(&info->ignore_temp_data_irq))
> +		goto out;
> +
> +	if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
> +		complete(&info->completion);
> +
> +out:
> +	disable_irq_nosync(info->temp_data_irq);
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
> +{
> +	struct sun4i_gpadc_iio *info = dev_id;
> +
> +	if (atomic_read(&info->ignore_fifo_data_irq))
> +		goto out;
> +
> +	if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
> +		complete(&info->completion);
> +
> +out:
> +	disable_irq_nosync(info->fifo_data_irq);
> +	return IRQ_HANDLED;
> +}
> +
> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
> +
> +	/* Disable the ADC on IP */
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
> +	/* Disable temperature sensor on IP */
> +	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_runtime_resume(struct device *dev)
> +{
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
> +
> +	/* clkin = 6MHz */
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
> +		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
> +		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
> +		     SUN4I_GPADC_CTRL0_T_ACQ(63));
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
> +	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
> +		     SUN4I_GPADC_CTRL3_FILTER_EN |
> +		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
> +	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */
> +	regmap_write(info->regmap, SUN4I_GPADC_TPR,
> +		     SUN4I_GPADC_TPR_TEMP_ENABLE |
> +		     SUN4I_GPADC_TPR_TEMP_PERIOD(800));
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_get_temp(void *data, int *temp)
> +{
> +	struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
> +	int val, scale, offset;
> +
> +	if (sun4i_gpadc_temp_read(info->indio_dev, &val))
> +		return -ETIMEDOUT;
> +
> +	sun4i_gpadc_temp_scale(info->indio_dev, &scale);
> +	sun4i_gpadc_temp_offset(info->indio_dev, &offset);
> +
> +	*temp = (val + offset) * scale;
> +
> +	return 0;
> +}
> +
> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
> +	.get_temp = &sun4i_gpadc_get_temp,
> +};
> +
> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
> +	.runtime_suspend = &sun4i_gpadc_runtime_suspend,
> +	.runtime_resume = &sun4i_gpadc_runtime_resume,
> +};
> +
> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
> +			  irq_handler_t handler, const char *devname,
> +			  unsigned int *irq, atomic_t *atomic)
> +{
> +	int ret;
> +	struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
> +	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
> +
> +	/*
> +	 * Once the interrupt is activated, the IP continuously performs
> +	 * conversions thus throws interrupts. The interrupt is activated right
> +	 * after being requested but we want to control when these interrupts
> +	 * occur thus we disable it right after being requested. However, an
> +	 * interrupt might occur between these two instructions and we have to
> +	 * make sure that does not happen, by using atomic flags. We set the
> +	 * flag before requesting the interrupt and unset it right after
> +	 * disabling the interrupt. When an interrupt occurs between these two
> +	 * instructions, reading the atomic flag will tell us to ignore the
> +	 * interrupt.
> +	 */
> +	atomic_set(atomic, 1);
> +
> +	ret = platform_get_irq_byname(pdev, name);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "no %s interrupt registered\n", name);
> +		return ret;
> +	}
> +
> +	ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to get virq for irq %s\n", name);
> +		return ret;
> +	}
> +
> +	*irq = ret;
> +	ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 0,
> +					   devname, info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
> +			name, ret);
> +		return ret;
> +	}
> +
> +	disable_irq(*irq);
> +	atomic_set(atomic, 0);
> +
> +	return 0;
> +}
> +
> +static int sun4i_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct sun4i_gpadc_iio *info;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +	struct sun4i_gpadc_dev *sun4i_gpadc_dev;
> +
> +	sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	info = iio_priv(indio_dev);
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	mutex_init(&info->mutex);
> +	info->regmap = sun4i_gpadc_dev->regmap;
> +	info->indio_dev = indio_dev;
> +	init_completion(&info->completion);
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &sun4i_gpadc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
> +	indio_dev->channels = sun4i_gpadc_channels;
> +
> +	info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
> +
> +	/*
> +	 * Since the controller needs to be in touchscreen mode for its thermal
> +	 * sensor to operate properly, and that switching between the two modes
> +	 * needs a delay, always registering in the thermal framework will
> +	 * significantly slow down the conversion rate of the ADCs.
> +	 *
> +	 * Therefore, instead of depending on THERMAL_OF in Kconfig, we only
> +	 * register the sensor if that option is enabled, eventually leaving
> +	 * that choice to the user.
> +	 */
> +
> +	if (IS_ENABLED(CONFIG_THERMAL_OF)) {
> +		/*
> +		 * This driver is a child of an MFD which has a node in the DT
> +		 * but not its children, because of DT backward compatibility
> +		 * for A10, A13 and A31 SoCs. Therefore, the resulting devices
> +		 * of this driver do not have an of_node variable.
> +		 * However, its parent (the MFD driver) has an of_node variable
> +		 * and since devm_thermal_zone_of_sensor_register uses its first
> +		 * argument to match the phandle defined in the node of the
> +		 * thermal driver with the of_node of the device passed as first
> +		 * argument and the third argument to call ops from
> +		 * thermal_zone_of_device_ops, the solution is to use the parent
> +		 * device as first argument to match the phandle with its
> +		 * of_node, and the device from this driver as third argument to
> +		 * return the temperature.
> +		 */
> +		struct thermal_zone_device *tzd;
> +		tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0,
> +							   info,
> +							   &sun4i_ts_tz_ops);
> +		if (IS_ERR(tzd)) {
> +			dev_err(&pdev->dev,
> +				"could not register thermal sensor: %ld\n",
> +				PTR_ERR(tzd));
> +			ret = PTR_ERR(tzd);
> +			goto err;
> +		}
> +	} else {
> +		indio_dev->num_channels =
> +			ARRAY_SIZE(sun4i_gpadc_channels_no_temp);
> +		indio_dev->channels = sun4i_gpadc_channels_no_temp;
> +	}
> +
> +	pm_runtime_set_autosuspend_delay(&pdev->dev,
> +					 SUN4I_GPADC_AUTOSUSPEND_DELAY);
> +	pm_runtime_use_autosuspend(&pdev->dev);
> +	pm_runtime_set_suspended(&pdev->dev);
> +	pm_runtime_enable(&pdev->dev);
> +
> +	if (IS_ENABLED(CONFIG_THERMAL_OF)) {
> +		ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
> +				     sun4i_gpadc_temp_data_irq_handler,
> +				     "temp_data", &info->temp_data_irq,
> +				     &info->ignore_temp_data_irq);
> +		if (ret < 0)
> +			goto err;
> +	}
> +
> +	ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
> +			     sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
> +			     &info->fifo_data_irq, &info->ignore_fifo_data_irq);
> +	if (ret < 0)
> +		goto err;
> +
> +	if (IS_ENABLED(CONFIG_THERMAL_OF)) {
> +		ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps);
> +		if (ret < 0) {
> +			dev_err(&pdev->dev,
> +				"failed to register iio map array\n");
> +			goto err;
> +		}
> +	}
> +
> +	ret = devm_iio_device_register(&pdev->dev, indio_dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "could not register the device\n");
> +		goto err_map;
> +	}
> +
> +	return 0;
> +
> +err_map:
> +	if (IS_ENABLED(CONFIG_THERMAL_OF))
> +		iio_map_array_unregister(indio_dev);
> +
> +err:
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +
> +	return ret;
> +}
> +
> +static int sun4i_gpadc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +
> +	pm_runtime_put(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +	if (IS_ENABLED(CONFIG_THERMAL_OF))
> +		iio_map_array_unregister(indio_dev);
> +
> +	return 0;
> +}
> +
> +static const struct platform_device_id sun4i_gpadc_id[] = {
> +	{ "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
> +	{ "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
> +	{ "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
> +	{ /* sentinel */ },
> +};
> +
> +static struct platform_driver sun4i_gpadc_driver = {
> +	.driver = {
> +		.name = "sun4i-gpadc-iio",
> +		.pm = &sun4i_gpadc_pm_ops,
> +	},
> +	.id_table = sun4i_gpadc_id,
> +	.probe = sun4i_gpadc_probe,
> +	.remove = sun4i_gpadc_remove,
> +};
> +
> +module_platform_driver(sun4i_gpadc_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
> index d7a29f2..509e736 100644
> --- a/include/linux/mfd/sun4i-gpadc.h
> +++ b/include/linux/mfd/sun4i-gpadc.h
> @@ -28,6 +28,7 @@
>  #define SUN4I_GPADC_CTRL1_TP_MODE_EN			BIT(4)
>  #define SUN4I_GPADC_CTRL1_TP_ADC_SELECT			BIT(3)
>  #define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(2, 0) & (x))
> +#define SUN4I_GPADC_CTRL1_ADC_CHAN_MASK			GENMASK(2, 0)
>  
>  /* TP_CTRL1 bits for sun6i SOCs */
>  #define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN		BIT(7)
> @@ -35,6 +36,7 @@
>  #define SUN6I_GPADC_CTRL1_TP_MODE_EN			BIT(5)
>  #define SUN6I_GPADC_CTRL1_TP_ADC_SELECT			BIT(4)
>  #define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(3, 0) & BIT(x))
> +#define SUN6I_GPADC_CTRL1_ADC_CHAN_MASK			GENMASK(3, 0)
>  
>  #define SUN4I_GPADC_CTRL2				0x08
>  
> 

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox