* [PATCH 2/3] clk: bcm: Update and add tingray clock entries
From: Ray Jui @ 2018-05-31 0:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <152772366322.144038.12289577935853704907@swboyd.mtv.corp.google.com>
Hi Stephen,
On 5/30/2018 4:41 PM, Stephen Boyd wrote:
> Subject is missing an 's' on stringray?
>
Yes, will fix this.
> Quoting Ray Jui (2018-05-25 09:45:16)
>> Update and add Stingray clock definitions and tables so they match the
>> binding document and the latest ASIC datasheet
>>
>> Signed-off-by: Pramod Kumar <pramod.kumar@broadcom.com>
>> Signed-off-by: Ray Jui <ray.jui@broadcom.com>
>
> Otherwise it looks ok, but maybe Pramod should be the author?
>
Will fix this for all commits in this series.
I'll fix the above and send out v2.
Thanks,
Ray
^ permalink raw reply
* [PATCH v6 1/2] dt-bindings: pinctrl: qcom: add gpio-ranges, gpio-reserved-ranges
From: Rob Herring @ 2018-05-31 0:14 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <6dbaadee4bb22638a2c2e6433e8d1740884ecfd9.1527505307.git.chunkeey@gmail.com>
On Mon, May 28, 2018 at 01:06:01PM +0200, Christian Lamparter wrote:
> This patch adds the gpio-ranges and gpio-reserved-ranges property
> definitions to the binding text files supported by the pinctrl-msm
> driver framework.
>
> gpio-ranges:
> For DT-based platforms the pinctrl-msm framework currently relies
> on the deprecated-for-DT gpiochip_add_pin_range() function to add
> the range of GPIOs to be handled by the pin controller. Due to
> interactions within gpiolib code, this causes the pinctrl-msm
> driver to bail out (-517) during boot when a gpio-hog is declared.
> This can be fatal and cause the system to not boot or reset
> (for a detailed explanation and call-trace, refer to patch:
> "pinctrl: msm: fix gpio-hog related boot issues" in this series).
>
> gpio-reserved-ranges:
> The binding has been added as a precaution since the TrustZone
> firmware (aka QSEE), which is running as the hypervisor, might
> have reserved certain, but undisclosed pins. Hence reading or
> writing to the registers for those pins will cause an
> XPU violation and this subsequently crashes the kernel.
>
> Signed-off-by: Christian Lamparter <chunkeey@gmail.com>
> ---
> .../devicetree/bindings/pinctrl/qcom,apq8064-pinctrl.txt | 6 ++++++
> .../devicetree/bindings/pinctrl/qcom,apq8084-pinctrl.txt | 9 +++++++++
> .../devicetree/bindings/pinctrl/qcom,ipq4019-pinctrl.txt | 6 ++++++
> .../devicetree/bindings/pinctrl/qcom,ipq8064-pinctrl.txt | 6 ++++++
> .../devicetree/bindings/pinctrl/qcom,ipq8074-pinctrl.txt | 9 +++++++++
> .../devicetree/bindings/pinctrl/qcom,mdm9615-pinctrl.txt | 9 +++++++++
> .../devicetree/bindings/pinctrl/qcom,msm8660-pinctrl.txt | 6 ++++++
> .../devicetree/bindings/pinctrl/qcom,msm8916-pinctrl.txt | 9 +++++++++
> .../devicetree/bindings/pinctrl/qcom,msm8960-pinctrl.txt | 9 +++++++++
> .../devicetree/bindings/pinctrl/qcom,msm8974-pinctrl.txt | 6 ++++++
> .../devicetree/bindings/pinctrl/qcom,msm8994-pinctrl.txt | 9 +++++++++
> .../devicetree/bindings/pinctrl/qcom,msm8996-pinctrl.txt | 9 +++++++++
> 12 files changed, 93 insertions(+)
Reviewed-by: Rob Herring <robh@kernel.org>
^ permalink raw reply
* [PATCH v4 0/2] regulator: add QCOM RPMh regulator driver
From: David Collins @ 2018-05-31 0:11 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180530163343.GV6920@sirena.org.uk>
Hello Mark,
On 05/30/2018 09:33 AM, Mark Brown wrote:
> On Tue, May 22, 2018 at 07:43:16PM -0700, David Collins wrote:
>> This patch series adds a driver and device tree binding documentation for
>> PMIC regulator control via Resource Power Manager-hardened (RPMh) on some
>> Qualcomm Technologies, Inc. SoCs such as SDM845. RPMh is a hardware block
>
> So, this is a very big driver and obviously it being RPM based it
> doesn't look like other regulators which is causing problems, especially
> when coupled with the desire to implement a bunch of more exotic
> features like the mode setting. I think this review is going to go a
> lot more smoothly if you split this up into a base driver with just
> normal, standard stuff that doesn't add too many custom properties or
> unusual ways of working and then a series of patches on top of that
> adding things like the mode adjustment and interaction with other RPM
> clients.
>
> We've got other RPM based regulators in tree already so the baseline bit
> shouldn't be too hard, that'll make the rest of the patches much smaller
> and easier to review and mean that the bits that are simpler and easier
> to cope with don't need to be reposted.
I'll split up the patches so that reviewing is easier. For the base
patch, would you prefer that I remove *all* mode support (handled by
generic regulator framework DT properties) or only remove the special
purpose drms mode handling support (i.e. qcom,regulator-drms-modes and
qcom,drms-mode-max-microamps)?
Thanks,
David
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
^ permalink raw reply
* [PATCH v10 2/5] dt-bindings: scsi: ufs: add document for hisi-ufs
From: Rob Herring @ 2018-05-31 0:10 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180525091712.37227-3-liwei213@huawei.com>
On Fri, May 25, 2018 at 05:17:09PM +0800, Li Wei wrote:
> add ufs node document for Hisilicon.
>
> Signed-off-by: Li Wei <liwei213@huawei.com>
> ---
> Documentation/devicetree/bindings/ufs/ufs-hisi.txt | 41 ++++++++++++++++++++++
> .../devicetree/bindings/ufs/ufshcd-pltfrm.txt | 10 ++++--
> 2 files changed, 48 insertions(+), 3 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/ufs/ufs-hisi.txt
Reviewed-by: Rob Herring <robh@kernel.org>
^ permalink raw reply
* [PATCH] kbuild: add machine size to CHEKCFLAGS
From: Randy Dunlap @ 2018-05-31 0:06 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAMHZB6GXVPvr1uwbemuxqPPtNzYT7jeVokR6q9tz2mS_=TG6vA@mail.gmail.com>
On 05/30/2018 04:06 PM, Luc Van Oostenryck wrote:
> On Thu, May 31, 2018 at 12:00 AM, Andreas F?rber <afaerber@suse.de> wrote:
>> Hi Luc,
>>
>> The typo in the subject made me curious...
>>
>> Am 30.05.2018 um 22:48 schrieb Luc Van Oostenryck:
>>> By default, sparse assumes a 64bit machine when compiled on x86-64
>>> and 32bit when compiled on anything else.
>>>
>>> This can of course create all sort of problems for the other archs, like
>>> issuing false warnings ('shift too big (32) for type unsigned long'), or
>>> worse, failing to emit legitimate warnings.
>>>
>>> Fix this by adding the -m32/-m64 flag, depending on CONFIG_64BIT,
>>> to CHECKFLAGS in the main Makefile (and so for all archs).
>>> Also, remove the now unneeded -m32/-m64 in arch specific Makefiles.
>>>
>>> Signed-off-by: Luc Van Oostenryck <luc.vanoostenryck@gmail.com>
>>> ---
>>> Makefile | 3 +++
>>> arch/alpha/Makefile | 2 +-
>>> arch/arm/Makefile | 2 +-
>>> arch/arm64/Makefile | 2 +-
>>> arch/ia64/Makefile | 2 +-
>>> arch/mips/Makefile | 3 ---
>>> arch/parisc/Makefile | 2 +-
>>> arch/sparc/Makefile | 2 +-
>>> arch/x86/Makefile | 2 +-
>>> 9 files changed, 10 insertions(+), 10 deletions(-)
>>
>> What about the architectures not touched by your patch that previously
>> had no -m32/-m64? (arc, c6x, h8300, hexagon, m68k, microblaze, nds32,
>> nios2, openrisc, powerpc, riscv, s390, sh, unicore32, xtensa)
>
> As explained in the patch, by default sparse uses -m64 if compiled on x86-64
> and 32bit on everything else (well, more recent versions use -m64 if
> compiled on any 64 bit machine). I think that most ppc devs use a ppc
> machine and so ppc was most probably fine (at least ppc64) but I suspect
> that most of these others archs either had never sparse used on them
> or had a lot of wrong warnings. IOW, it was maybe OK but most probably
> incorrect for them and now it is OK.
>
>> You forgot to CC them on this patch.
>
> I didn't thought/knew it was needed and the CC list is already
> quite long but, if needed, no problem for me.
Ideally, adding linux-arch at vger.kernel.org would be sufficient, but
sadly I have doubts about that.
>> Have you really checked that all their toolchains support the -m32/-m64
>> flags you newly introduce for them? Apart from non-biarch architectures,
>> I'm thinking of 31-bit s390 as a corner case where !64 != 32.
>
> Hmm, there is no change to anything I call 'toolchain related', like
> compiler and linker. The only change is sparse (or any other checker)
> receiving now a correct and explicit -m32 or -m64.
>
> For s390, as far as I know:
> 1) it has CONFIG_64BIT unconditionally definee (because the old 31bit
> is no more supported, now everything is s390x only).
> 2) even if the *address space* was only 31 bit, I'm very sure
> that sizeof(long) and sizeof(void*) was 4 on these machine
> hence -m32 would have been correct.
--
~Randy
^ permalink raw reply
* [PATCH v4 2/2] regulator: add QCOM RPMh regulator driver
From: David Collins @ 2018-05-30 23:58 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAD=FV=WF+onhAAWrbmwcHiWyCjuZ=6vvnhodUsia=Ps-da6_4A@mail.gmail.com>
Hello Doug,
On 05/29/2018 10:32 PM, Doug Anderson wrote:
> On Tue, May 22, 2018 at 7:43 PM, David Collins <collinsd@codeaurora.org> wrote:
>> + * @ever_enabled: Boolean indicating that the regulator has been
>> + * explicitly enabled at least once. Voltage
>> + * requests should be cached when this flag is not
>> + * set.
>
> Do you really need this extra boolean? Can't you just check if
> "enabled" is still "-EINVAL"? If it is then you don't pass the
> voltage along.
>
> ...this would mean that you'd also need to send the voltage vote when
> the regulator core tries to disable unused regulators at the end of
> bootup, but that should be OK right? If we never touched a regulator
> anywhere at probe time and we're about to vote to disable it, we know
> there's nobody requiring it to still be on. We can vote for the
> voltage now without fear of messing up a vote that the BIOS left in
> place.
>
> In theory this should also allow you to assert your vote about the
> voltage of a regulator that has never been enabled, which (if I
> understand correctly) you consider to be a feature.
Removing 'ever_enabled' and caching the voltage when 'enabled == -EINVAL'
seems workable. I'm a little concerned about this resulting in voltage =
regulator-min-microvolt requests being sent for all regulators that are
not explicitly enabled by Linux consumers before late_initcall_sync().
Theoretically all of the boot loader hand-off cases should be taken care
of by this point so it should be safe.
I'll make this change.
Take care,
David
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
^ permalink raw reply
* [PATCH 2/3] clk: bcm: Update and add tingray clock entries
From: Stephen Boyd @ 2018-05-30 23:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527266717-8406-3-git-send-email-ray.jui@broadcom.com>
Subject is missing an 's' on stringray?
Quoting Ray Jui (2018-05-25 09:45:16)
> Update and add Stingray clock definitions and tables so they match the
> binding document and the latest ASIC datasheet
>
> Signed-off-by: Pramod Kumar <pramod.kumar@broadcom.com>
> Signed-off-by: Ray Jui <ray.jui@broadcom.com>
Otherwise it looks ok, but maybe Pramod should be the author?
^ permalink raw reply
* [PATCH v4 1/2] regulator: dt-bindings: add QCOM RPMh regulator bindings
From: David Collins @ 2018-05-30 23:39 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAD=FV=WL9enzYCoSx0fT_ny40ciLJU-hhS9joJ6nySXvWPqAxQ@mail.gmail.com>
Hello Mark and Doug,
On 05/30/2018 09:24 AM, Doug Anderson wrote:
> On Wed, May 30, 2018 at 9:20 AM, Mark Brown <broonie@kernel.org> wrote:
>> On Wed, May 30, 2018 at 09:12:25AM -0700, Doug Anderson wrote:
>>> On Wed, May 30, 2018 at 8:50 AM, Mark Brown <broonie@kernel.org> wrote:
>>
>>>> No, I'm saying that I don't know why that property exists at all. This
>>>> sounds like it's intended to be the amount of current the regulator can
>>>> deliver in each mode which is normally a design property of the silicon.
>>
>>> Ah, got it. So the whole point here is to be able to implement either
>>> the function "set_load" or the function "get_optimum_mode". We need
>>> some sort of table to convert from current to mode. That's what this
>>> table does.
>>
>> We do need that table, my expectation would be that this table would be
>> in the driver as it's not something I'd expect to vary between different
>> systems but rather be a property of the silicon design. No sense in
>> every single board having to copy it in.
>
> Ah, got it! I'd be OK with it being hardcoded in the driver.
>
> At one point I think David was making the argument that some boards
> have different noise needs for the rails and thus you might want to
> change modes at different currents. I don't know if this is realistic
> but I believe it was part of his original argument for why this needed
> to be specified. If we can hardcode this in the driver I'm fine with
> it... That would actually solve many of my objections too...
The DRMS modes to use and max allowed current per mode need to be
specified at the board level in device tree instead of hard-coded per
regulator type in the driver. There are at least two use cases driving
this need: LDOs shared between RPMh client processors and SMPSes requiring
PWM mode in certain circumstances.
For LDOs the maximum low power mode (LPM) current is typically 10 mA or 30
mA (depending upon subtype) per hardware documentation. Unfortunately,
sharing control of regulators with other processors adds some subtlety to
the LPM current limit that should actually be applied at runtime.
Consider the case of a regulator with physical 10 mA LPM max current. Say
that modem and application processors each have a load on the regulator
that draws 9 mA. If they each respect the 10 mA limit, then they'd each
vote for LPM. The VRM block in RPMh hardware will aggregate these requests
together using a max function which will result in the regulator being set
to LPM, even though the total load is 18 mA (which would require high
power mode (HPM)). To get around this corner case, a LPM max current of 1
uA can be used for all LDO supplies that have non-application processor
consumers. Thus, any non-zero regulator_set_load() current request will
result in setting the regulator to HPM (which is always safe).
The second situation that needs board-level DRMS mode and current limit
specification is SMPS regulator AUTO mode to PWM (HPM) mode switching.
SMPS regulators should theoretically always be able to operate in AUTO
mode as it switches automatically between PWM mode (which can provide the
maximum current) and PFM mode (which supports lower current but has higher
efficiency). However, there may be board/system issues that require
switching to PWM mode for certain use cases as it has better load
regulation (i.e. no PFM ripple for lower loads) and supports more
aggressive load current steps (i.e. greater A/ns).
If a Linux consumer requires the ability to force a given SMPS regulator
from AUTO mode into PWM mode and that SMPS is shared by other Linux
consumers (which may be the case, but at least must be guaranteed to work
architecturally), then regulator_set_load() is the only option since it
provides aggregation, where as regulator_set_mode() does not.
regulator_set_load() can be utilized in this case by specifying AUTO mode
and PWM mode as drms modes and specifying some particular AUTO mode
maximum current (that is known by the consumer) in device tree. The
consumer can then call regulator_set_load() with the imposed AUTO mode
limit + delta when PWM mode is required and a lower value when AUTO mode
is sufficient.
Note that I previously mentioned the need for board-level drms mode and
current limit specification in [1].
Take care,
David
[1]: https://lkml.org/lkml/2018/3/22/802
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
^ permalink raw reply
* [PATCH] kbuild: add machine size to CHEKCFLAGS
From: Luc Van Oostenryck @ 2018-05-30 23:06 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <d47b72cc-9209-a190-38b3-969870e1bf26@suse.de>
On Thu, May 31, 2018 at 12:00 AM, Andreas F?rber <afaerber@suse.de> wrote:
> Hi Luc,
>
> The typo in the subject made me curious...
>
> Am 30.05.2018 um 22:48 schrieb Luc Van Oostenryck:
>> By default, sparse assumes a 64bit machine when compiled on x86-64
>> and 32bit when compiled on anything else.
>>
>> This can of course create all sort of problems for the other archs, like
>> issuing false warnings ('shift too big (32) for type unsigned long'), or
>> worse, failing to emit legitimate warnings.
>>
>> Fix this by adding the -m32/-m64 flag, depending on CONFIG_64BIT,
>> to CHECKFLAGS in the main Makefile (and so for all archs).
>> Also, remove the now unneeded -m32/-m64 in arch specific Makefiles.
>>
>> Signed-off-by: Luc Van Oostenryck <luc.vanoostenryck@gmail.com>
>> ---
>> Makefile | 3 +++
>> arch/alpha/Makefile | 2 +-
>> arch/arm/Makefile | 2 +-
>> arch/arm64/Makefile | 2 +-
>> arch/ia64/Makefile | 2 +-
>> arch/mips/Makefile | 3 ---
>> arch/parisc/Makefile | 2 +-
>> arch/sparc/Makefile | 2 +-
>> arch/x86/Makefile | 2 +-
>> 9 files changed, 10 insertions(+), 10 deletions(-)
>
> What about the architectures not touched by your patch that previously
> had no -m32/-m64? (arc, c6x, h8300, hexagon, m68k, microblaze, nds32,
> nios2, openrisc, powerpc, riscv, s390, sh, unicore32, xtensa)
As explained in the patch, by default sparse uses -m64 if compiled on x86-64
and 32bit on everything else (well, more recent versions use -m64 if
compiled on any 64 bit machine). I think that most ppc devs use a ppc
machine and so ppc was most probably fine (at least ppc64) but I suspect
that most of these others archs either had never sparse used on them
or had a lot of wrong warnings. IOW, it was maybe OK but most probably
incorrect for them and now it is OK.
> You forgot to CC them on this patch.
I didn't thought/knew it was needed and the CC list is already
quite long but, if needed, no problem for me.
> Have you really checked that all their toolchains support the -m32/-m64
> flags you newly introduce for them? Apart from non-biarch architectures,
> I'm thinking of 31-bit s390 as a corner case where !64 != 32.
Hmm, there is no change to anything I call 'toolchain related', like
compiler and linker. The only change is sparse (or any other checker)
receiving now a correct and explicit -m32 or -m64.
For s390, as far as I know:
1) it has CONFIG_64BIT unconditionally definee (because the old 31bit
is no more supported, now everything is s390x only).
2) even if the *address space* was only 31 bit, I'm very sure
that sizeof(long) and sizeof(void*) was 4 on these machine
hence -m32 would have been correct.
Best regards,
-- Luc
^ permalink raw reply
* [PATCH] kbuild: add machine size to CHEKCFLAGS
From: Rob Landley @ 2018-05-30 22:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <d47b72cc-9209-a190-38b3-969870e1bf26@suse.de>
On 05/30/2018 05:00 PM, Andreas F?rber wrote:
> What about the architectures not touched by your patch that previously
> had no -m32/-m64? (arc, c6x, h8300, hexagon, m68k, microblaze, nds32,
> nios2, openrisc, powerpc, riscv, s390, sh, unicore32, xtensa)
>
> You forgot to CC them on this patch.
A) He cc'd arch/sh on the previous patch earlier today, to which I replied:
https://marc.info/?l=linux-sh&m=152769132515226&w=2
B) Every change to common infrastructure should cc: every arch? Really? So like
filesystem changes and stuff to?
> Have you really checked that all their toolchains support the -m32/-m64
> flags you newly introduce for them? Apart from non-biarch architectures,
> I'm thinking of 31-bit s390 as a corner case where !64 != 32.
1) Last I heard Linux implements lp64:
http://www.unix.org/whitepapers/64bit.html
2) it's unlikely to be worse than it was before the patch,
3) last I checked https://github.com/landley/mkroot boots to an s390 shell
prompt under qemu, although I haven't tried building with this patch. (And you
may still need to add HOST_EXTRA='lex yacc bison flex' to the command line
unless they've re-added the _shipped versions like the old kconfig had...) Point
is, shouldn't be too hard to test it. Presumably that's why we have an -rc1 and
then 6 more -rc versions each release...
Rob
^ permalink raw reply
* [PATCH 00/12] introduce support for early platform drivers
From: Rob Herring @ 2018-05-30 22:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180530194032.982.41562@harbor.lan>
On Wed, May 30, 2018 at 2:40 PM, Michael Turquette
<mturquette@baylibre.com> wrote:
> Hi Rob,
>
> Quoting Rob Herring (2018-05-14 06:20:57)
>> On Mon, May 14, 2018 at 6:38 AM, Bartosz Golaszewski <brgl@bgdev.pl> wrote:
>> > 2018-05-11 22:13 GMT+02:00 Rob Herring <robh+dt@kernel.org>:
>> >> On Fri, May 11, 2018 at 11:20 AM, Bartosz Golaszewski <brgl@bgdev.pl> wrote:
>> >>> This series is a follow-up to the RFC[1] posted a couple days ago.
>> >>>
>> >>> NOTE: this series applies on top of my recent patches[2] that move the previous
>> >>> implementation of early platform devices to arch/sh.
>> >>>
>> >>> Problem:
>> >>>
>> >>> Certain class of devices, such as timers, certain clock drivers and irq chip
>> >>> drivers need to be probed early in the boot sequence. The currently preferred
>> >>> approach is using one of the OF_DECLARE() macros. This however does not create
>> >>> a platform device which has many drawbacks - such as not being able to use
>> >>> devres routines, dev_ log functions or no way of deferring the init OF function
>> >>> if some other resources are missing.
>> >>
>> >> I skimmed though this and it doesn't look horrible (how's that for
>> >> positive feedback? ;) ). But before going into the details, I think
>> >> first there needs to be agreement this is the right direction.
>> >>
>> >> The question does remain though as to whether this class of devices
>> >> should be platform drivers. They can't be modules. They can't be
>> >> hotplugged. Can they be runtime-pm enabled? So the advantage is ...
>> >>
>> >
>> > The main (but not the only) advantage for drivers that can both be
>> > platform drivers and OF_DECLARE drivers is that we get a single entry
>> > point and can reuse code without resorting to checking if (!dev). It
>> > results in more consistent code base. Another big advantage is
>> > consolidation of device tree and machine code for SoC drivers used in
>> > different boards of which some are still using board files and others
>> > are defined in DT (see: DaVinci).
>> >
>> >> I assume that the clock maintainers had some reason to move clocks to
>> >> be platform drivers. It's just not clear to me what that was.
>> >>
>> >>> For drivers that use both platform drivers and OF_DECLARE the situation is even
>> >>> more complicated as the code needs to take into account that there can possibly
>> >>> be no struct device present. For a specific use case that we're having problems
>> >>> with, please refer to the recent DaVinci common-clock conversion patches and
>> >>> the nasty workaround that this problem implies[3].
>> >>
>> >> So devm_kzalloc will work with this solution? Why did we need
>> >> devm_kzalloc in the first place? The clocks can never be removed and
>> >> cleaning up on error paths is kind of pointless. The system would be
>> >> hosed, right?
>> >>
>> >
>> > It depends - not all clocks are necessary for system to boot.
>>
>> That doesn't matter. You have a single driver for all/most of the
>> clocks, so the driver can't be removed.
>
> -ECANOFWORMS
>
> A couple of quick rebuttals, but I imagine we're going to disagree on
> the platform_driver thing as a matter of taste no matter what...
It's really more should the clocksource, clockevents and primary
interrupt controller be drivers. Let's get agreement on that first. If
yes, then it probably does make sense that their dependencies are
drivers too. If not, then making only the dependencies drivers doesn't
seem right to me.
> 1) There should be multiple clk drivers in a properly modeled system.
> Some folks still incorrectly put all clocks in a system into a single
> driver because That's How We Used To Do It, and some systems (simpler
> ones) really only have a single clock generator IP block.
>
> Excepting those two reasons above, we really should have separate
> drivers for clocks controlled by the PMIC, for the (one or more) clock
> generator blocks inside of the AP/SoC, and then even more for the
> drivers that map to IP blocks that have their own clock gens.
I agree those should be separate entities at least. But for a given
h/w block, if you already have to use OF_DECLARE, why would you try to
split that into OF_DECLARE and a driver? what advantage does putting
non-boot essential clocks in a driver or transitioning to a driver get
you?
And I've seen PMIC clocks could be inputs to the SoC's clock
controller(s), so the dependencies get more complicated. Then does the
PMIC driver and its dependencies need to be early drivers too?
> Good examples of the latter are display controllers that generate their
> own PLL and pixel clock. Or MMC controllers that have a
> runtime-programmable clock divider. Examples of these are merged into
> mainline.
But those are drivers of types other than a clock controller that
happen to register some clocks as well. I wasn't saying these cases
can't or shouldn't be part of the driver model. Look at irqchips. We
have some that use the driver model (e.g. every GPIO driver) and some
that don't because there's no need (e.g. GIC).
> 2) Stephen and I wanted clock drivers to actually be represented in the
> driver model. There were these gigantic clock drivers that exclusively
> used CLK_OF_DECLARE and they just sort of floated out there in the
> ether... no representation in sysfs, no struct device to map onto a
> clock controller struct, etc.
>
> I'd be happy to hear why you think that platform_driver is a bad fit for
> a device driver that generally manages memory-mapped system resources
> that are part of the system glue and not really tied to a specific bus.
> Sounds like a good fit to me.
>
> If platform_driver doesn't handle the early boot thing well, that tells
> me that we have a problem to solve in platform_driver, not in the clk
> subsystem or drivers.
Doing things earlier is not the only way to solve the problems.
Perhaps we need to figure out how to start things later. Maybe it's
not feasible here, I don't know.
> 3) Lots of clock controllers should be loadable modules. E.g. i2c clock
> expanders, potentially external PMIC-related drivers, external audio
> codecs, etc.
>
> Again, repeating my point #1 above, just because many platforms have a
> monolithic clock driver does not mean that this is the Right Way to do
> it.
And in those cases, I completely agree they should be part of a driver.
Rob
^ permalink raw reply
* [PATCH] ASoC: mediatek: export mtk-afe symbols as needed
From: Arnd Bergmann @ 2018-05-30 22:00 UTC (permalink / raw)
To: linux-arm-kernel
The new mt6797-afe driver uses some functions in a common file, which
works for a built-in driver but fails for a loadable module:
ERROR: "mtk_afe_pcm_free" [sound/soc/mediatek/mt6797/snd-soc-mt6797-afe.ko] undefined!
ERROR: "mtk_afe_add_sub_dai_control" [sound/soc/mediatek/mt6797/snd-soc-mt6797-afe.ko] undefined!
ERROR: "mtk_afe_pcm_new" [sound/soc/mediatek/mt6797/snd-soc-mt6797-afe.ko] undefined!
ERROR: "mtk_afe_combine_sub_dai" [sound/soc/mediatek/mt6797/snd-soc-mt6797-afe.ko] undefined!
ERROR: "mtk_afe_pcm_ops" [sound/soc/mediatek/mt6797/snd-soc-mt6797-afe.ko] undefined!
This exports the five symbols above for modules.
Fixes: b3c702f56bf5 ("ASoC: mt6797: combine DAI to register component")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
sound/soc/mediatek/common/mtk-afe-platform-driver.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.c b/sound/soc/mediatek/common/mtk-afe-platform-driver.c
index 00618587ef1e..51ec4ff6ed95 100644
--- a/sound/soc/mediatek/common/mtk-afe-platform-driver.c
+++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.c
@@ -57,6 +57,7 @@ int mtk_afe_combine_sub_dai(struct mtk_base_afe *afe)
return 0;
}
+EXPORT_SYMBOL_GPL(mtk_afe_combine_sub_dai);
int mtk_afe_add_sub_dai_control(struct snd_soc_component *component)
{
@@ -90,6 +91,7 @@ int mtk_afe_add_sub_dai_control(struct snd_soc_component *component)
return 0;
}
+EXPORT_SYMBOL_GPL(mtk_afe_add_sub_dai_control);
static snd_pcm_uframes_t mtk_afe_pcm_pointer
(struct snd_pcm_substream *substream)
@@ -130,6 +132,7 @@ const struct snd_pcm_ops mtk_afe_pcm_ops = {
.ioctl = snd_pcm_lib_ioctl,
.pointer = mtk_afe_pcm_pointer,
};
+EXPORT_SYMBOL_GPL(mtk_afe_pcm_ops);
int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
@@ -143,11 +146,13 @@ int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd)
afe->dev,
size, size);
}
+EXPORT_SYMBOL_GPL(mtk_afe_pcm_new);
void mtk_afe_pcm_free(struct snd_pcm *pcm)
{
snd_pcm_lib_preallocate_free_for_all(pcm);
}
+EXPORT_SYMBOL_GPL(mtk_afe_pcm_free);
const struct snd_soc_component_driver mtk_afe_pcm_platform = {
.name = AFE_PCM_NAME,
--
2.9.0
^ permalink raw reply related
* [PATCH] kbuild: add machine size to CHEKCFLAGS
From: Andreas Färber @ 2018-05-30 22:00 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180530204838.22079-1-luc.vanoostenryck@gmail.com>
Hi Luc,
The typo in the subject made me curious...
Am 30.05.2018 um 22:48 schrieb Luc Van Oostenryck:
> By default, sparse assumes a 64bit machine when compiled on x86-64
> and 32bit when compiled on anything else.
>
> This can of course create all sort of problems for the other archs, like
> issuing false warnings ('shift too big (32) for type unsigned long'), or
> worse, failing to emit legitimate warnings.
>
> Fix this by adding the -m32/-m64 flag, depending on CONFIG_64BIT,
> to CHECKFLAGS in the main Makefile (and so for all archs).
> Also, remove the now unneeded -m32/-m64 in arch specific Makefiles.
>
> Signed-off-by: Luc Van Oostenryck <luc.vanoostenryck@gmail.com>
> ---
> Makefile | 3 +++
> arch/alpha/Makefile | 2 +-
> arch/arm/Makefile | 2 +-
> arch/arm64/Makefile | 2 +-
> arch/ia64/Makefile | 2 +-
> arch/mips/Makefile | 3 ---
> arch/parisc/Makefile | 2 +-
> arch/sparc/Makefile | 2 +-
> arch/x86/Makefile | 2 +-
> 9 files changed, 10 insertions(+), 10 deletions(-)
What about the architectures not touched by your patch that previously
had no -m32/-m64? (arc, c6x, h8300, hexagon, m68k, microblaze, nds32,
nios2, openrisc, powerpc, riscv, s390, sh, unicore32, xtensa)
You forgot to CC them on this patch.
Have you really checked that all their toolchains support the -m32/-m64
flags you newly introduce for them? Apart from non-biarch architectures,
I'm thinking of 31-bit s390 as a corner case where !64 != 32.
Regards,
Andreas
--
SUSE Linux GmbH, Maxfeldstr. 5, 90409 N?rnberg, Germany
GF: Felix Imend?rffer, Jane Smithard, Graham Norton
HRB 21284 (AG N?rnberg)
^ permalink raw reply
* [PATCH v2] spi: bcm2835aux: ensure interrupts are enabled for shared handler
From: Rob Herring @ 2018-05-30 21:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <87sh69nelw.fsf@anholt.net>
On Wed, May 30, 2018 at 2:11 PM, Eric Anholt <eric@anholt.net> wrote:
> Rob Herring <robh@kernel.org> writes:
>
>> The BCM2835 AUX SPI has a shared interrupt line (with AUX UART).
>> Downstream fixes this with an AUX irqchip to demux the IRQ sources and a
>> DT change which breaks compatibility with older kernels. The AUX irqchip
>> was already rejected for upstream[1] and the DT change would break
>> working systems if the DTB is updated to a newer one. The latter issue
>> was brought to my attention by Alex Graf.
>>
>> The root cause however is a bug in the shared handler. A shared handler
>> must correctly identify it actually handled an interrupt. The handler
>> here was processing data whether interrupts were enabled or not.
>> It would return IRQ_HANDLED if there was any data and not only when
>> there was an actual interrupt pending. The result is that another
>> device's IRQ could cause the SPI's IRQ handler to run and process data
>> when the the SPI driver working in polled mode. Fix this by adding a
>> check in the IRQ handler that the TXEMPTY or IDLE interrupts are enabled
>> and always return IRQ_NONE when they are not.
>
> FWIW, I see v1 already applied in -next.
Sigh, indeed. I thought I had checked that. Though Mark had comments
on the commit msg, so I assumed he wanted changes. The automated
applied emails just get lost in my inbox. It would be nice if they
kept the subject instead of adding 'Applied "...' so gmail would group
them. Of course, it would be nice if gmail could honor threading too.
Rob
^ permalink raw reply
* [PATCH] ARM: pxa, regulator: fix building ezx e680
From: Arnd Bergmann @ 2018-05-30 21:24 UTC (permalink / raw)
To: linux-arm-kernel
The reference to camera_supply_gpiod_table was added in the wrong function,
as observed from this randconfig build failure:
arch/arm/mach-pxa/ezx.c: In function 'e680_init':
arch/arm/mach-pxa/ezx.c:905:26: error: 'camera_supply_gpiod_table' undeclared (first use in this function)
gpiod_add_lookup_table(&camera_supply_gpiod_table);
Fixes: 6059577cb28d ("regulator: fixed: Convert to use GPIO descriptor only")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
arch/arm/mach-pxa/ezx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm/mach-pxa/ezx.c b/arch/arm/mach-pxa/ezx.c
index 2b4bd6d94855..4f33eea73f9a 100644
--- a/arch/arm/mach-pxa/ezx.c
+++ b/arch/arm/mach-pxa/ezx.c
@@ -902,7 +902,6 @@ static void __init e680_init(void)
pxa_set_keypad_info(&e680_keypad_platform_data);
- gpiod_add_lookup_table(&camera_supply_gpiod_table);
pwm_add_table(ezx_pwm_lookup, ARRAY_SIZE(ezx_pwm_lookup));
platform_add_devices(ARRAY_AND_SIZE(ezx_devices));
platform_add_devices(ARRAY_AND_SIZE(e680_devices));
@@ -1132,6 +1131,7 @@ static void __init a910_init(void)
if (a910_camera_init() == 0)
pxa_set_camera_info(&a910_pxacamera_platform_data);
+ gpiod_add_lookup_table(&camera_supply_gpiod_table);
pwm_add_table(ezx_pwm_lookup, ARRAY_SIZE(ezx_pwm_lookup));
platform_add_devices(ARRAY_AND_SIZE(ezx_devices));
platform_add_devices(ARRAY_AND_SIZE(a910_devices));
--
2.9.0
^ permalink raw reply related
* [PATCH v7 10/10] drivers: clk: Add ZynqMP clock driver
From: Randy Dunlap @ 2018-05-30 21:17 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-11-git-send-email-jollys@xilinx.com>
On 05/30/2018 01:55 PM, Jolly Shah wrote:
> From: Jolly Shah <jolly.shah@xilinx.com>
> diff --git a/drivers/clk/zynqmp/Kconfig b/drivers/clk/zynqmp/Kconfig
> new file mode 100644
> index 0000000..fe815f7
> --- /dev/null
> +++ b/drivers/clk/zynqmp/Kconfig
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +config COMMON_CLK_ZYNQMP
> + bool "Support for Xilinx ZynqMP Ultrascale+ clock controllers"
> + depends on OF
> + depends on ARCH_ZYNQMP || COMPILE_TEST
> + depends on ZYNQMP_FIRMWARE
> + help
> + Support for the Zynqmp Ultrascale clock controller.
> + It has a dependency on the PMU firmware.
> + Say Y if you want to support clock support
Say Y if you want to include clock support.
--
~Randy
^ permalink raw reply
* [PATCH v7 10/10] drivers: clk: Add ZynqMP clock driver
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Jolly Shah <jolly.shah@xilinx.com>
This patch adds CCF compliant clock driver for ZynqMP.
Clock driver queries supported clock information from
firmware and regiters pll and output clocks with CCF.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Tejas Patel <tejasp@xilinx.com>
Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/zynqmp/Kconfig | 11 +
drivers/clk/zynqmp/Makefile | 4 +
drivers/clk/zynqmp/clk-gate-zynqmp.c | 146 +++++++
drivers/clk/zynqmp/clk-mux-zynqmp.c | 150 +++++++
drivers/clk/zynqmp/clk-zynqmp.h | 53 +++
drivers/clk/zynqmp/clkc.c | 737 +++++++++++++++++++++++++++++++++++
drivers/clk/zynqmp/divider.c | 219 +++++++++++
drivers/clk/zynqmp/pll.c | 345 ++++++++++++++++
10 files changed, 1667 insertions(+)
create mode 100644 drivers/clk/zynqmp/Kconfig
create mode 100644 drivers/clk/zynqmp/Makefile
create mode 100644 drivers/clk/zynqmp/clk-gate-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-mux-zynqmp.c
create mode 100644 drivers/clk/zynqmp/clk-zynqmp.h
create mode 100644 drivers/clk/zynqmp/clkc.c
create mode 100644 drivers/clk/zynqmp/divider.c
create mode 100644 drivers/clk/zynqmp/pll.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 98ce9fc..a2ebcf7 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -252,6 +252,7 @@ source "drivers/clk/sprd/Kconfig"
source "drivers/clk/sunxi-ng/Kconfig"
source "drivers/clk/tegra/Kconfig"
source "drivers/clk/ti/Kconfig"
+source "drivers/clk/zynqmp/Kconfig"
source "drivers/clk/uniphier/Kconfig"
endmenu
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 71ec41e..b6ac0d2 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -100,3 +100,4 @@ obj-$(CONFIG_X86) += x86/
endif
obj-$(CONFIG_ARCH_ZX) += zte/
obj-$(CONFIG_ARCH_ZYNQ) += zynq/
+obj-$(CONFIG_COMMON_CLK_ZYNQMP) += zynqmp/
diff --git a/drivers/clk/zynqmp/Kconfig b/drivers/clk/zynqmp/Kconfig
new file mode 100644
index 0000000..fe815f7
--- /dev/null
+++ b/drivers/clk/zynqmp/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config COMMON_CLK_ZYNQMP
+ bool "Support for Xilinx ZynqMP Ultrascale+ clock controllers"
+ depends on OF
+ depends on ARCH_ZYNQMP || COMPILE_TEST
+ depends on ZYNQMP_FIRMWARE
+ help
+ Support for the Zynqmp Ultrascale clock controller.
+ It has a dependency on the PMU firmware.
+ Say Y if you want to support clock support
diff --git a/drivers/clk/zynqmp/Makefile b/drivers/clk/zynqmp/Makefile
new file mode 100644
index 0000000..0ec24bf
--- /dev/null
+++ b/drivers/clk/zynqmp/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+# Zynq Ultrascale+ MPSoC clock specific Makefile
+
+obj-$(CONFIG_ARCH_ZYNQMP) += pll.o clk-gate-zynqmp.o divider.o clk-mux-zynqmp.o clkc.o
diff --git a/drivers/clk/zynqmp/clk-gate-zynqmp.c b/drivers/clk/zynqmp/clk-gate-zynqmp.c
new file mode 100644
index 0000000..b927eb1
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-gate-zynqmp.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC clock controller
+ *
+ * Copyright (C) 2016-2018 Xilinx
+ *
+ * Gated clock implementation
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include "clk-zynqmp.h"
+
+/**
+ * struct clk_gate - gating clock
+ * @hw: handle between common and hardware-specific interfaces
+ * @flags: hardware-specific flags
+ * @clk_id: Id of clock
+ */
+struct zynqmp_clk_gate {
+ struct clk_hw hw;
+ u8 flags;
+ u32 clk_id;
+};
+
+#define to_zynqmp_clk_gate(_hw) container_of(_hw, struct zynqmp_clk_gate, hw)
+
+/**
+ * zynqmp_clk_gate_enable() - Enable clock
+ * @hw: handle between common and hardware-specific interfaces
+ *
+ * Return: 0 on success else error code
+ */
+static int zynqmp_clk_gate_enable(struct clk_hw *hw)
+{
+ struct zynqmp_clk_gate *gate = to_zynqmp_clk_gate(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = gate->clk_id;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_enable(clk_id);
+
+ if (ret)
+ pr_warn_once("%s() clock enabled failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ return ret;
+}
+
+/*
+ * zynqmp_clk_gate_disable() - Disable clock
+ * @hw: handle between common and hardware-specific interfaces
+ */
+static void zynqmp_clk_gate_disable(struct clk_hw *hw)
+{
+ struct zynqmp_clk_gate *gate = to_zynqmp_clk_gate(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = gate->clk_id;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_disable(clk_id);
+
+ if (ret)
+ pr_warn_once("%s() clock disable failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+}
+
+/**
+ * zynqmp_clk_gate_is_enable() - Check clock state
+ * @hw: handle between common and hardware-specific interfaces
+ *
+ * Return: 1 if enabled, 0 if disabled else error code
+ */
+static int zynqmp_clk_gate_is_enabled(struct clk_hw *hw)
+{
+ struct zynqmp_clk_gate *gate = to_zynqmp_clk_gate(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = gate->clk_id;
+ int state, ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_getstate(clk_id, &state);
+ if (ret) {
+ pr_warn_once("%s() clock get state failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+ return -EIO;
+ }
+
+ return state ? 1 : 0;
+}
+
+const struct clk_ops zynqmp_clk_gate_ops = {
+ .enable = zynqmp_clk_gate_enable,
+ .disable = zynqmp_clk_gate_disable,
+ .is_enabled = zynqmp_clk_gate_is_enabled,
+};
+EXPORT_SYMBOL_GPL(zynqmp_clk_gate_ops);
+
+/**
+ * zynqmp_clk_register_gate() - register a gate clock with the clock framework
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @clk_id: Id of this clock
+ * @parent: name of this clock's parent
+ * @flags: framework-specific flags for this clock
+ * @clk_gate_flags: gate-specific flags for this clock
+ *
+ * Return: clock hardware of the registered clock gate
+ */
+struct clk_hw *zynqmp_clk_register_gate(struct device *dev, const char *name,
+ u32 clk_id, const char *parent,
+ unsigned long flags,
+ u8 clk_gate_flags)
+{
+ struct zynqmp_clk_gate *gate;
+ struct clk_hw *hw;
+ int ret;
+ struct clk_init_data init;
+
+ /* allocate the gate */
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &zynqmp_clk_gate_ops;
+ init.flags = flags;
+ init.parent_names = &parent;
+ init.num_parents = 1;
+
+ /* struct clk_gate assignments */
+ gate->flags = clk_gate_flags;
+ gate->hw.init = &init;
+ gate->clk_id = clk_id;
+
+ hw = &gate->hw;
+ ret = clk_hw_register(dev, hw);
+ if (ret) {
+ kfree(gate);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/zynqmp/clk-mux-zynqmp.c b/drivers/clk/zynqmp/clk-mux-zynqmp.c
new file mode 100644
index 0000000..a0b452d
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-mux-zynqmp.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC mux
+ *
+ * Copyright (C) 2016-2018 Xilinx
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include "clk-zynqmp.h"
+
+/*
+ * DOC: basic adjustable multiplexer clock that cannot gate
+ *
+ * Traits of this clock:
+ * prepare - clk_prepare only ensures that parents are prepared
+ * enable - clk_enable only ensures that parents are enabled
+ * rate - rate is only affected by parent switching. No clk_set_rate support
+ * parent - parent is adjustable through clk_set_parent
+ */
+
+/**
+ * struct zynqmp_clk_mux - multiplexer clock
+ *
+ * @hw: handle between common and hardware-specific interfaces
+ * @flags: hardware-specific flags
+ * @clk_id: Id of clock
+ */
+struct zynqmp_clk_mux {
+ struct clk_hw hw;
+ u8 flags;
+ u32 clk_id;
+};
+
+#define to_zynqmp_clk_mux(_hw) container_of(_hw, struct zynqmp_clk_mux, hw)
+
+/**
+ * zynqmp_clk_mux_get_parent() - Get parent of clock
+ * @hw: handle between common and hardware-specific interfaces
+ *
+ * Return: Parent index
+ */
+static u8 zynqmp_clk_mux_get_parent(struct clk_hw *hw)
+{
+ struct zynqmp_clk_mux *mux = to_zynqmp_clk_mux(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = mux->clk_id;
+ u32 val;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_getparent(clk_id, &val);
+
+ if (ret)
+ pr_warn_once("%s() getparent failed for clock: %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ return val;
+}
+
+/**
+ * zynqmp_clk_mux_set_parent() - Set parent of clock
+ * @hw: handle between common and hardware-specific interfaces
+ * @index: Parent index
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_clk_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct zynqmp_clk_mux *mux = to_zynqmp_clk_mux(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = mux->clk_id;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_setparent(clk_id, index);
+
+ if (ret)
+ pr_warn_once("%s() set parent failed for clock: %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ return ret;
+}
+
+const struct clk_ops zynqmp_clk_mux_ops = {
+ .get_parent = zynqmp_clk_mux_get_parent,
+ .set_parent = zynqmp_clk_mux_set_parent,
+ .determine_rate = __clk_mux_determine_rate,
+};
+EXPORT_SYMBOL_GPL(zynqmp_clk_mux_ops);
+
+const struct clk_ops zynqmp_clk_mux_ro_ops = {
+ .get_parent = zynqmp_clk_mux_get_parent,
+};
+EXPORT_SYMBOL_GPL(zynqmp_clk_mux_ro_ops);
+
+/**
+ * zynqmp_clk_register_mux() - register a mux table with the clock
+ * framework
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @clk_id: Id of this clock
+ * @parents: name of this clock's parents
+ * @num_parents: number of parents
+ * @flags: framework-specific flags for this clock
+ * @clk_mux_flags: mux-specific flags for this clock
+ *
+ * Return: clock hardware of the registered clock mux
+ */
+struct clk_hw *zynqmp_clk_register_mux(struct device *dev, const char *name,
+ u32 clk_id,
+ const char * const *parents,
+ u8 num_parents,
+ unsigned long flags,
+ u8 clk_mux_flags)
+{
+ struct zynqmp_clk_mux *mux;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ /* allocate the mux */
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ if (clk_mux_flags & CLK_MUX_READ_ONLY)
+ init.ops = &zynqmp_clk_mux_ro_ops;
+ else
+ init.ops = &zynqmp_clk_mux_ops;
+ init.flags = flags;
+ init.parent_names = parents;
+ init.num_parents = num_parents;
+
+ /* struct clk_mux assignments */
+ mux->flags = clk_mux_flags;
+ mux->hw.init = &init;
+ mux->clk_id = clk_id;
+
+ hw = &mux->hw;
+ ret = clk_hw_register(dev, hw);
+ if (ret) {
+ kfree(hw);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+EXPORT_SYMBOL_GPL(zynqmp_clk_register_mux);
diff --git a/drivers/clk/zynqmp/clk-zynqmp.h b/drivers/clk/zynqmp/clk-zynqmp.h
new file mode 100644
index 0000000..57e81d45
--- /dev/null
+++ b/drivers/clk/zynqmp/clk-zynqmp.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2016-2018 Xilinx
+ */
+
+#ifndef __LINUX_CLK_ZYNQMP_H_
+#define __LINUX_CLK_ZYNQMP_H_
+
+#include <linux/spinlock.h>
+
+#include <linux/firmware/xlnx-zynqmp.h>
+
+/* Clock APIs payload parameters */
+#define CLK_GET_NAME_RESP_LEN 16
+#define CLK_GET_TOPOLOGY_RESP_WORDS 3
+#define CLK_GET_PARENTS_RESP_WORDS 3
+#define CLK_GET_ATTR_RESP_WORDS 1
+
+enum topology_type {
+ TYPE_INVALID,
+ TYPE_MUX,
+ TYPE_PLL,
+ TYPE_FIXEDFACTOR,
+ TYPE_DIV1,
+ TYPE_DIV2,
+ TYPE_GATE,
+};
+
+struct clk_hw *zynqmp_clk_register_pll(struct device *dev, const char *name,
+ u32 clk_id,
+ const char *parent,
+ unsigned long flag);
+
+struct clk_hw *zynqmp_clk_register_gate(struct device *dev, const char *name,
+ u32 clk_id,
+ const char *parent,
+ unsigned long flags,
+ u8 clk_gate_flags);
+
+struct clk_hw *zynqmp_clk_register_divider(struct device *dev,
+ const char *name,
+ u32 clk_id, u32 div_type,
+ const char *parent,
+ unsigned long flags,
+ u8 clk_divider_flags);
+
+struct clk_hw *zynqmp_clk_register_mux(struct device *dev, const char *name,
+ u32 clk_id,
+ const char * const *parents,
+ u8 num_parents,
+ unsigned long flags,
+ u8 clk_mux_flags);
+#endif
diff --git a/drivers/clk/zynqmp/clkc.c b/drivers/clk/zynqmp/clkc.c
new file mode 100644
index 0000000..a315fc2
--- /dev/null
+++ b/drivers/clk/zynqmp/clkc.c
@@ -0,0 +1,737 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC clock controller
+ *
+ * Copyright (C) 2016-2018 Xilinx
+ *
+ * Based on drivers/clk/zynq/clkc.c
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "clk-zynqmp.h"
+
+#define MAX_PARENT 100
+#define MAX_NODES 6
+#define MAX_NAME_LEN 50
+#define MAX_CLOCK 300
+
+#define CLK_INIT_ENABLE_SHIFT 1
+#define CLK_TYPE_SHIFT 2
+
+#define PM_API_PAYLOAD_LEN 3
+
+#define NA_PARENT 0xFFFFFFFF
+#define DUMMY_PARENT 0xFFFFFFFE
+
+#define CLK_TYPE_FIELD_LEN 4
+#define CLK_TOPOLOGY_NODE_OFFSET 16
+#define NODES_PER_RESP 3
+
+#define CLK_TYPE_FIELD_MASK 0xF
+#define CLK_FLAG_FIELD_MASK GENMASK(21, 8)
+#define CLK_TYPE_FLAG_FIELD_MASK GENMASK(31, 24)
+
+#define CLK_PARENTS_ID_LEN 16
+#define CLK_PARENTS_ID_MASK 0xFFFF
+
+/* Flags for parents */
+#define PARENT_CLK_SELF 0
+#define PARENT_CLK_NODE1 1
+#define PARENT_CLK_NODE2 2
+#define PARENT_CLK_NODE3 3
+#define PARENT_CLK_NODE4 4
+#define PARENT_CLK_EXTERNAL 5
+
+#define END_OF_CLK_NAME "END_OF_CLK"
+#define RESERVED_CLK_NAME ""
+
+#define CLK_VALID_MASK 0x1
+#define CLK_INIT_ENABLE_MASK (0x1 << CLK_INIT_ENABLE_SHIFT)
+
+enum clk_type {
+ CLK_TYPE_OUTPUT,
+ CLK_TYPE_EXTERNAL,
+};
+
+/**
+ * struct clock_parent - Structure for parent of clock
+ * @name: Parent name
+ * @id: Parent clock ID
+ * @flag: Parent flags
+ */
+struct clock_parent {
+ char name[MAX_NAME_LEN];
+ int id;
+ u32 flag;
+};
+
+/**
+ * struct clock_topology - Structure for topology of clock
+ * @type: Type of topology
+ * @flag: Topology flags
+ * @type_flag: Topology type specific flag
+ */
+struct clock_topology {
+ u32 type;
+ u32 flag;
+ u32 type_flag;
+};
+
+/**
+ * struct zynqmp_clock - Structure for clock
+ * @clk_name: Clock name
+ * @valid: Validity flag of clock
+ * @init_enable: init_enable flag of clock
+ * @type: Clock type (Output/External)
+ * @node: Clock tolology nodes
+ * @num_nodes: Number of nodes present in topology
+ * @parent: structure of parent of clock
+ * @num_parents: Number of parents of clock
+ */
+struct zynqmp_clock {
+ char clk_name[MAX_NAME_LEN];
+ u32 valid;
+ u32 init_enable;
+ enum clk_type type;
+ struct clock_topology node[MAX_NODES];
+ u32 num_nodes;
+ struct clock_parent parent[MAX_PARENT];
+ u32 num_parents;
+};
+
+static const char clk_type_postfix[][10] = {
+ [TYPE_INVALID] = "",
+ [TYPE_MUX] = "_mux",
+ [TYPE_GATE] = "",
+ [TYPE_DIV1] = "_div1",
+ [TYPE_DIV2] = "_div2",
+ [TYPE_FIXEDFACTOR] = "_ff",
+ [TYPE_PLL] = ""
+};
+
+static struct zynqmp_clock clock[MAX_CLOCK];
+static struct clk_hw_onecell_data *zynqmp_data;
+static unsigned int clock_max_idx;
+static const struct zynqmp_eemi_ops *eemi_ops;
+
+/**
+ * zynqmp_is_valid_clock() - Check whether clock is valid or not
+ * @clk_id: Clock index
+ * @valid: 1: if clock is valid
+ * 0: invalid clock
+ *
+ * Return: 0 on success else error code
+ */
+static int zynqmp_is_valid_clock(u32 clk_id, u32 *valid)
+{
+ if (clk_id > clock_max_idx)
+ return -ENODEV;
+
+ *valid = clock[clk_id].valid;
+
+ return *valid ? 0 : -EINVAL;
+}
+
+/**
+ * zynqmp_get_clock_name() - Get name of clock from Clock index
+ * @clk_id: Clock index
+ * @clk_name: Name of clock
+ *
+ * Return: 0 on success else error code
+ */
+static int zynqmp_get_clock_name(u32 clk_id, char *clk_name)
+{
+ int ret;
+ u32 valid;
+
+ ret = zynqmp_is_valid_clock(clk_id, &valid);
+ if (!ret && valid) {
+ strncpy(clk_name, clock[clk_id].clk_name, MAX_NAME_LEN);
+ return ret;
+ } else {
+ return ret;
+ }
+}
+
+/**
+ * zynqmp_get_clock_type() - Get type of clock
+ * @clk_id: Clock index
+ * @type: Clock type: CLK_TYPE_OUTPUT or CLK_TYPE_EXTERNAL
+ *
+ * Return: 0 on success else error code
+ */
+static int zynqmp_get_clock_type(u32 clk_id, u32 *type)
+{
+ int ret;
+ u32 valid;
+
+ ret = zynqmp_is_valid_clock(clk_id, &valid);
+ if (!ret && valid) {
+ *type = clock[clk_id].type;
+ return ret;
+ } else {
+ return ret;
+ }
+}
+
+/**
+ * zynqmp_pm_clock_get_name() - Get the name of clock for given id
+ * @clock_id: ID of the clock to be queried
+ * @name: Name of given clock
+ *
+ * This function is used to get name of clock specified by given
+ * clock ID.
+ *
+ * Return: Returns 0, in case of error name would be 0
+ */
+static int zynqmp_pm_clock_get_name(u32 clock_id, char *name)
+{
+ struct zynqmp_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+
+ qdata.qid = PM_QID_CLOCK_GET_NAME;
+ qdata.arg1 = clock_id;
+
+ eemi_ops->query_data(qdata, ret_payload);
+ memcpy(name, ret_payload, CLK_GET_NAME_RESP_LEN);
+
+ return 0;
+}
+
+/**
+ * zynqmp_pm_clock_get_topology() - Get the topology of clock for given id
+ * @clock_id: ID of the clock to be queried
+ * @index: Node index of clock topology
+ * @topology: Buffer to store nodes in topology and flags
+ *
+ * This function is used to get topology information for the clock
+ * specified by given clock ID.
+ *
+ * This API will return 3 node of topology with a single response. To get
+ * other nodes, master should call same API in loop with new
+ * index till error is returned. E.g First call should have
+ * index 0 which will return nodes 0,1 and 2. Next call, index
+ * should be 3 which will return nodes 3,4 and 5 and so on.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_get_topology(u32 clock_id, u32 index, u32 *topology)
+{
+ struct zynqmp_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_TOPOLOGY;
+ qdata.arg1 = clock_id;
+ qdata.arg2 = index;
+
+ ret = eemi_ops->query_data(qdata, ret_payload);
+ memcpy(topology, &ret_payload[1], CLK_GET_TOPOLOGY_RESP_WORDS * 4);
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_clock_get_fixedfactor_params() - Get clock's fixed factor params
+ * @clock_id: Clock ID
+ * @mul: Multiplication value
+ * @div: Divisor value
+ *
+ * This function is used to get fixed factor parameters for the fixed
+ * clock. This API is applicable only for the fixed clock.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_get_fixedfactor_params(u32 clock_id,
+ u32 *mul,
+ u32 *div)
+{
+ struct zynqmp_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS;
+ qdata.arg1 = clock_id;
+
+ ret = eemi_ops->query_data(qdata, ret_payload);
+ *mul = ret_payload[1];
+ *div = ret_payload[2];
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_clock_get_parents() - Get the first 3 parents of clock for given id
+ * @clock_id: Clock ID
+ * @index: Parent index
+ * @parents: 3 parents of the given clock
+ *
+ * This function is used to get 3 parents for the clock specified by
+ * given clock ID.
+ *
+ * This API will return 3 parents with a single response. To get
+ * other parents, master should call same API in loop with new
+ * parent index till error is returned. E.g First call should have
+ * index 0 which will return parents 0,1 and 2. Next call, index
+ * should be 3 which will return parent 3,4 and 5 and so on.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_get_parents(u32 clock_id, u32 index, u32 *parents)
+{
+ struct zynqmp_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_PARENTS;
+ qdata.arg1 = clock_id;
+ qdata.arg2 = index;
+
+ ret = eemi_ops->query_data(qdata, ret_payload);
+ memcpy(parents, &ret_payload[1], CLK_GET_PARENTS_RESP_WORDS * 4);
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_clock_get_attributes() - Get the attributes of clock for given id
+ * @clock_id: Clock ID
+ * @attr: Clock attributes
+ *
+ * This function is used to get clock's attributes(e.g. valid, clock type, etc).
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_get_attributes(u32 clock_id, u32 *attr)
+{
+ struct zynqmp_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_CLOCK_GET_ATTRIBUTES;
+ qdata.arg1 = clock_id;
+
+ ret = eemi_ops->query_data(qdata, ret_payload);
+ memcpy(attr, &ret_payload[1], CLK_GET_ATTR_RESP_WORDS * 4);
+
+ return ret;
+}
+
+/**
+ * zynqmp_clock_get_topology() - Get topology of clock from firmware using
+ * PM_API
+ * @clk_id: Clock index
+ * @clk_topology: Structure of clock topology
+ * @num_nodes: number of nodes
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_clock_get_topology(u32 clk_id,
+ struct clock_topology *clk_topology,
+ u32 *num_nodes)
+{
+ int j, k = 0, ret;
+ u32 pm_resp[PM_API_PAYLOAD_LEN] = {0};
+
+ *num_nodes = 0;
+ for (j = 0; j <= MAX_NODES; j += 3) {
+ ret = zynqmp_pm_clock_get_topology(clk_id, j, pm_resp);
+ if (ret)
+ return ret;
+ for (k = 0; k < PM_API_PAYLOAD_LEN; k++) {
+ if (!(pm_resp[k] & CLK_TYPE_FIELD_MASK))
+ return 0;
+ clk_topology[*num_nodes].type = pm_resp[k] &
+ CLK_TYPE_FIELD_MASK;
+ clk_topology[*num_nodes].flag =
+ FIELD_GET(CLK_FLAG_FIELD_MASK,
+ pm_resp[k]);
+ clk_topology[*num_nodes].type_flag =
+ FIELD_GET(CLK_TYPE_FLAG_FIELD_MASK,
+ pm_resp[k]);
+ (*num_nodes)++;
+ }
+ }
+ return 0;
+}
+
+/**
+ * zynqmp_clock_get_parents() - Get parents info from firmware using PM_API
+ * @clk_id: Clock index
+ * @parents: Structure of parent information
+ * @num_parents: Total number of parents
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_clock_get_parents(u32 clk_id, struct clock_parent *parents,
+ u32 *num_parents)
+{
+ int j = 0, k, ret, total_parents = 0;
+ u32 pm_resp[PM_API_PAYLOAD_LEN] = {0};
+ struct clock_parent *parent;
+
+ do {
+ /* Get parents from firmware */
+ ret = zynqmp_pm_clock_get_parents(clk_id, j, pm_resp);
+ if (ret)
+ return ret;
+
+ for (k = 0; k < PM_API_PAYLOAD_LEN; k++) {
+ if (pm_resp[k] == NA_PARENT) {
+ *num_parents = total_parents;
+ return 0;
+ }
+
+ parent = &parents[k + j];
+ parent->id = pm_resp[k] & CLK_PARENTS_ID_MASK;
+ if (pm_resp[k] == DUMMY_PARENT) {
+ strcpy(parent->name, "dummy_name");
+ parent->flag = 0;
+ } else {
+ parent->flag = pm_resp[k] >>
+ CLK_PARENTS_ID_LEN;
+ if (zynqmp_get_clock_name(parent->id,
+ parent->name))
+ continue;
+ }
+ total_parents++;
+ }
+ j += PM_API_PAYLOAD_LEN;
+ } while (total_parents <= MAX_PARENT);
+ return 0;
+}
+
+/**
+ * zynqmp_get_parent_list() - Create list of parents name
+ * @np: Device node
+ * @clk_id: Clock index
+ * @parent_list: List of parent's name
+ * @num_parents: Total number of parents
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_get_parent_list(struct device_node *np, u32 clk_id,
+ const char **parent_list, u32 *num_parents)
+{
+ int i = 0, ret;
+ u32 total_parents = clock[clk_id].num_parents;
+ struct clock_topology *clk_nodes;
+ struct clock_parent *parents;
+
+ clk_nodes = clock[clk_id].node;
+ parents = clock[clk_id].parent;
+
+ for (i = 0; i < total_parents; i++) {
+ if (!parents[i].flag) {
+ parent_list[i] = parents[i].name;
+ } else if (parents[i].flag == PARENT_CLK_EXTERNAL) {
+ ret = of_property_match_string(np, "clock-names",
+ parents[i].name);
+ if (ret < 0)
+ strcpy(parents[i].name, "dummy_name");
+ parent_list[i] = parents[i].name;
+ } else {
+ strcat(parents[i].name,
+ clk_type_postfix[clk_nodes[parents[i].flag - 1].
+ type]);
+ parent_list[i] = parents[i].name;
+ }
+ }
+
+ *num_parents = total_parents;
+ return 0;
+}
+
+/**
+ * zynqmp_register_clk_topology() - Register clock topology
+ * @clk_id: Clock index
+ * @clk_name: Clock Name
+ * @num_parents: Total number of parents
+ * @parent_names: List of parents name
+ *
+ * Return: Returns either clock hardware or error+reason
+ */
+static struct clk_hw *zynqmp_register_clk_topology(int clk_id, char *clk_name,
+ int num_parents,
+ const char **parent_names)
+{
+ int j, ret;
+ u32 num_nodes, mult, div;
+ char *clk_out = NULL;
+ struct clock_topology *nodes;
+ struct clk_hw *hw = NULL;
+
+ nodes = clock[clk_id].node;
+ num_nodes = clock[clk_id].num_nodes;
+
+ for (j = 0; j < num_nodes; j++) {
+ /*
+ * Clock name received from firmware is output clock name.
+ * Intermediate clock names are postfixed with type of clock.
+ */
+ if (j != (num_nodes - 1)) {
+ clk_out = kasprintf(GFP_KERNEL, "%s%s", clk_name,
+ clk_type_postfix[nodes[j].type]);
+ } else {
+ clk_out = kasprintf(GFP_KERNEL, "%s", clk_name);
+ }
+
+ switch (nodes[j].type) {
+ case TYPE_MUX:
+ hw = zynqmp_clk_register_mux(NULL, clk_out,
+ clk_id, parent_names,
+ num_parents,
+ nodes[j].flag,
+ nodes[j].type_flag);
+ break;
+ case TYPE_PLL:
+ hw = zynqmp_clk_register_pll(NULL, clk_out, clk_id,
+ parent_names[0],
+ nodes[j].flag);
+ break;
+ case TYPE_FIXEDFACTOR:
+ ret = zynqmp_pm_clock_get_fixedfactor_params(clk_id,
+ &mult,
+ &div);
+ hw = clk_hw_register_fixed_factor(NULL, clk_out,
+ parent_names[0],
+ nodes[j].flag, mult,
+ div);
+ break;
+ case TYPE_DIV1:
+ case TYPE_DIV2:
+ hw = zynqmp_clk_register_divider(NULL, clk_out, clk_id,
+ nodes[j].type,
+ parent_names[0],
+ nodes[j].flag,
+ nodes[j].type_flag);
+ break;
+ case TYPE_GATE:
+
+ hw = zynqmp_clk_register_gate(NULL, clk_out, clk_id,
+ parent_names[0],
+ nodes[j].flag,
+ nodes[j].type_flag);
+ break;
+ default:
+ pr_err("%s() Unknown topology for %s\n",
+ __func__, clk_out);
+ break;
+ }
+ if (IS_ERR(hw))
+ pr_warn_once("%s() %s register fail with %ld\n",
+ __func__, clk_name, PTR_ERR(hw));
+
+ parent_names[0] = clk_out;
+ }
+ kfree(clk_out);
+ return hw;
+}
+
+/**
+ * zynqmp_register_clocks() - Register clocks
+ * @np: Device node
+ *
+ * Return: 0 on success else error code
+ */
+static int zynqmp_register_clocks(struct device_node *np)
+{
+ int ret;
+ u32 i, total_parents = 0, type = 0;
+ const char *parent_names[MAX_PARENT];
+
+ for (i = 0; i < clock_max_idx; i++) {
+ char clk_name[MAX_NAME_LEN];
+
+ /* get clock name, continue to next clock if name not found */
+ if (zynqmp_get_clock_name(i, clk_name))
+ continue;
+
+ /* Check if clock is valid and output clock.
+ * Do not regiter invalid or external clock.
+ */
+ ret = zynqmp_get_clock_type(i, &type);
+ if (ret || type != CLK_TYPE_OUTPUT)
+ continue;
+
+ /* Get parents of clock*/
+ if (zynqmp_get_parent_list(np, i, parent_names,
+ &total_parents)) {
+ WARN_ONCE(1, "No parents found for %s\n",
+ clock[i].clk_name);
+ continue;
+ }
+
+ zynqmp_data->hws[i] =
+ zynqmp_register_clk_topology(i, clk_name,
+ total_parents,
+ parent_names);
+ }
+
+ for (i = 0; i < clock_max_idx; i++) {
+ if (IS_ERR(zynqmp_data->hws[i])) {
+ pr_err("Zynq Ultrascale+ MPSoC clk %s: register failed with %ld\n",
+ clock[i].clk_name, PTR_ERR(zynqmp_data->hws[i]));
+ WARN_ON(1);
+ }
+ }
+ return 0;
+}
+
+/**
+ * zynqmp_get_clock_info() - Get clock information from firmware using PM_API
+ */
+static void zynqmp_get_clock_info(void)
+{
+ int i, ret;
+ u32 attr, type = 0;
+
+ memset(clock, 0, sizeof(clock));
+ for (i = 0; i < MAX_CLOCK; i++) {
+ zynqmp_pm_clock_get_name(i, clock[i].clk_name);
+ if (!strcmp(clock[i].clk_name, END_OF_CLK_NAME)) {
+ clock_max_idx = i;
+ break;
+ } else if (!strcmp(clock[i].clk_name, RESERVED_CLK_NAME)) {
+ continue;
+ }
+
+ ret = zynqmp_pm_clock_get_attributes(i, &attr);
+ if (ret)
+ continue;
+
+ clock[i].valid = attr & CLK_VALID_MASK;
+ clock[i].init_enable = !!(attr & CLK_INIT_ENABLE_MASK);
+ clock[i].type = attr >> CLK_TYPE_SHIFT ? CLK_TYPE_EXTERNAL :
+ CLK_TYPE_OUTPUT;
+ }
+
+ /* Get topology of all clock */
+ for (i = 0; i < clock_max_idx; i++) {
+ ret = zynqmp_get_clock_type(i, &type);
+ if (ret || type != CLK_TYPE_OUTPUT)
+ continue;
+
+ ret = zynqmp_clock_get_topology(i, clock[i].node,
+ &clock[i].num_nodes);
+ if (ret)
+ continue;
+
+ ret = zynqmp_clock_get_parents(i, clock[i].parent,
+ &clock[i].num_parents);
+ if (ret)
+ continue;
+ }
+}
+
+/**
+ * zynqmp_validate_eemi_ops() - Validate eemi ops
+ *
+ * Return: 0 on success else error code
+ */
+static inline int zynqmp_validate_eemi_ops(void)
+{
+ eemi_ops = zynqmp_pm_get_eemi_ops();
+ if (!eemi_ops || !eemi_ops->query_data ||
+ !eemi_ops->clock_setdivider ||
+ !eemi_ops->clock_getdivider ||
+ !eemi_ops->clock_setparent ||
+ !eemi_ops->clock_getparent ||
+ !eemi_ops->clock_getstate ||
+ !eemi_ops->clock_disable ||
+ !eemi_ops->clock_enable ||
+ !eemi_ops->ioctl)
+ return -ENXIO;
+
+ return 0;
+}
+
+/**
+ * zynqmp_clk_setup() - Setup the clock framework and register clocks
+ * @np: Device node
+ *
+ * Return: 0 on success else error code
+ */
+static int __init zynqmp_clk_setup(struct device_node *np)
+{
+ int idx;
+
+ idx = of_property_match_string(np, "clock-names", "pss_ref_clk");
+ if (idx < 0) {
+ pr_err("pss_ref_clk not provided\n");
+ return -ENOENT;
+ }
+ idx = of_property_match_string(np, "clock-names", "video_clk");
+ if (idx < 0) {
+ pr_err("video_clk not provided\n");
+ return -ENOENT;
+ }
+ idx = of_property_match_string(np, "clock-names", "pss_alt_ref_clk");
+ if (idx < 0) {
+ pr_err("pss_alt_ref_clk not provided\n");
+ return -ENOENT;
+ }
+ idx = of_property_match_string(np, "clock-names", "aux_ref_clk");
+ if (idx < 0) {
+ pr_err("aux_ref_clk not provided\n");
+ return -ENOENT;
+ }
+ idx = of_property_match_string(np, "clock-names", "gt_crx_ref_clk");
+ if (idx < 0) {
+ pr_err("aux_ref_clk not provided\n");
+ return -ENOENT;
+ }
+
+ zynqmp_data = kzalloc(sizeof(*zynqmp_data) + sizeof(*zynqmp_data) *
+ MAX_CLOCK, GFP_KERNEL);
+ if (!zynqmp_data)
+ return -ENOMEM;
+
+ zynqmp_get_clock_info();
+ zynqmp_register_clocks(np);
+
+ zynqmp_data->num = clock_max_idx;
+ of_clk_add_hw_provider(np, of_clk_hw_onecell_get, zynqmp_data);
+
+ return 0;
+}
+
+/**
+ * zynqmp_clock_init() - Initialize zynqmp clocks
+ *
+ * Return: 0 on success else error code
+ */
+static int __init zynqmp_clock_init(void)
+{
+ int ret;
+ struct device_node *np;
+
+ np = of_find_compatible_node(NULL, NULL, "xlnx,zynqmp");
+ if (!np)
+ return -ENOENT;
+ of_node_put(np);
+
+ np = of_find_compatible_node(NULL, NULL, "xlnx,zynqmp-clk");
+ if (!np) {
+ pr_err("%s: clk node not found\n", __func__);
+ return -ENOENT;
+ }
+
+ ret = zynqmp_validate_eemi_ops();
+ if (ret) {
+ pr_err("%s: eemi ops validation fail\n", __func__);
+ of_node_put(np);
+ return ret;
+ }
+
+ ret = zynqmp_clk_setup(np);
+ of_node_put(np);
+
+ return ret;
+}
+arch_initcall(zynqmp_clock_init);
diff --git a/drivers/clk/zynqmp/divider.c b/drivers/clk/zynqmp/divider.c
new file mode 100644
index 0000000..ef3e2e9
--- /dev/null
+++ b/drivers/clk/zynqmp/divider.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC Divider support
+ *
+ * Copyright (C) 2016-2018 Xilinx
+ *
+ * Adjustable divider clock implementation
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include "clk-zynqmp.h"
+
+/*
+ * DOC: basic adjustable divider clock that cannot gate
+ *
+ * Traits of this clock:
+ * prepare - clk_prepare only ensures that parents are prepared
+ * enable - clk_enable only ensures that parents are enabled
+ * rate - rate is adjustable. clk->rate = ceiling(parent->rate / divisor)
+ * parent - fixed parent. No clk_set_parent support
+ */
+
+#define to_zynqmp_clk_divider(_hw) \
+ container_of(_hw, struct zynqmp_clk_divider, hw)
+
+#define CLK_FRAC BIT(13) /* has a fractional parent */
+
+/**
+ * struct zynqmp_clk_divider - adjustable divider clock
+ * @hw: handle between common and hardware-specific interfaces
+ * @flags: Hardware specific flags
+ * @clk_id: Id of clock
+ * @div_type: divisor type (TYPE_DIV1 or TYPE_DIV2)
+ */
+struct zynqmp_clk_divider {
+ struct clk_hw hw;
+ u8 flags;
+ u32 clk_id;
+ u32 div_type;
+};
+
+static int zynqmp_divider_get_val(unsigned long parent_rate, unsigned long rate)
+{
+ return DIV_ROUND_CLOSEST(parent_rate, rate);
+}
+
+/**
+ * zynqmp_clk_divider_recalc_rate() - Recalc rate of divider clock
+ * @hw: handle between common and hardware-specific interfaces
+ * @parent_rate: rate of parent clock
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static unsigned long zynqmp_clk_divider_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct zynqmp_clk_divider *divider = to_zynqmp_clk_divider(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = divider->clk_id;
+ u32 div_type = divider->div_type;
+ u32 div, value;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_getdivider(clk_id, &div);
+
+ if (ret)
+ pr_warn_once("%s() get divider failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ if (div_type == TYPE_DIV1)
+ value = div & 0xFFFF;
+ else
+ value = (div >> 16) & 0xFFFF;
+
+ return DIV_ROUND_UP_ULL(parent_rate, value);
+}
+
+/**
+ * zynqmp_clk_divider_round_rate() - Round rate of divider clock
+ * @hw: handle between common and hardware-specific interfaces
+ * @rate: rate of clock to be set
+ * @prate: rate of parent clock
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static long zynqmp_clk_divider_round_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long *prate)
+{
+ struct zynqmp_clk_divider *divider = to_zynqmp_clk_divider(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = divider->clk_id;
+ u32 div_type = divider->div_type;
+ u32 bestdiv;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ /* if read only, just return current value */
+ if (divider->flags & CLK_DIVIDER_READ_ONLY) {
+ ret = eemi_ops->clock_getdivider(clk_id, &bestdiv);
+
+ if (ret)
+ pr_warn_once("%s() get divider failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+ if (div_type == TYPE_DIV1)
+ bestdiv = bestdiv & 0xFFFF;
+ else
+ bestdiv = (bestdiv >> 16) & 0xFFFF;
+
+ return DIV_ROUND_UP_ULL((u64)*prate, bestdiv);
+ }
+
+ bestdiv = zynqmp_divider_get_val(*prate, rate);
+
+ if ((clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) &&
+ (clk_hw_get_flags(hw) & CLK_FRAC))
+ bestdiv = rate % *prate ? 1 : bestdiv;
+ *prate = rate * bestdiv;
+
+ return rate;
+}
+
+/**
+ * zynqmp_clk_divider_set_rate() - Set rate of divider clock
+ * @hw: handle between common and hardware-specific interfaces
+ * @rate: rate of clock to be set
+ * @parent_rate: rate of parent clock
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct zynqmp_clk_divider *divider = to_zynqmp_clk_divider(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = divider->clk_id;
+ u32 div_type = divider->div_type;
+ u32 value, div;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ value = zynqmp_divider_get_val(parent_rate, rate);
+ if (div_type == TYPE_DIV1) {
+ div = value & 0xFFFF;
+ div |= 0xffff << 16;
+ } else {
+ div = 0xffff;
+ div |= value << 16;
+ }
+
+ ret = eemi_ops->clock_setdivider(clk_id, div);
+
+ if (ret)
+ pr_warn_once("%s() set divider failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ return ret;
+}
+
+static const struct clk_ops zynqmp_clk_divider_ops = {
+ .recalc_rate = zynqmp_clk_divider_recalc_rate,
+ .round_rate = zynqmp_clk_divider_round_rate,
+ .set_rate = zynqmp_clk_divider_set_rate,
+};
+
+/**
+ * zynqmp_clk_register_divider() - register a divider clock
+ * @dev: device registering this clock
+ * @name: name of this clock
+ * @clk_id: Id of clock
+ * @div_type: Type of divisor
+ * @parent: name of clock's parent
+ * @flags: framework-specific flags
+ * @clk_divider_flags: divider-specific flags for this clock
+ *
+ * Return: clock hardware to registered clock divider
+ */
+struct clk_hw *zynqmp_clk_register_divider(struct device *dev,
+ const char *name,
+ u32 clk_id, u32 div_type,
+ const char *parent,
+ unsigned long flags,
+ u8 clk_divider_flags)
+{
+ struct zynqmp_clk_divider *div;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ /* allocate the divider */
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &zynqmp_clk_divider_ops;
+ init.flags = flags;
+ init.parent_names = &parent;
+ init.num_parents = 1;
+
+ /* struct clk_divider assignments */
+ div->flags = clk_divider_flags;
+ div->hw.init = &init;
+ div->clk_id = clk_id;
+ div->div_type = div_type;
+
+ hw = &div->hw;
+ ret = clk_hw_register(dev, hw);
+ if (ret) {
+ kfree(div);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+EXPORT_SYMBOL_GPL(zynqmp_clk_register_divider);
diff --git a/drivers/clk/zynqmp/pll.c b/drivers/clk/zynqmp/pll.c
new file mode 100644
index 0000000..1782829
--- /dev/null
+++ b/drivers/clk/zynqmp/pll.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zynq UltraScale+ MPSoC PLL driver
+ *
+ * Copyright (C) 2016-2018 Xilinx
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include "clk-zynqmp.h"
+
+/**
+ * struct zynqmp_pll - Structure for PLL clock
+ * @hw: Handle between common and hardware-specific interfaces
+ * @clk_id: PLL clock ID
+ */
+struct zynqmp_pll {
+ struct clk_hw hw;
+ u32 clk_id;
+};
+
+#define to_zynqmp_pll(_hw) container_of(_hw, struct zynqmp_pll, hw)
+
+#define PLL_FBDIV_MIN 25
+#define PLL_FBDIV_MAX 125
+
+#define PS_PLL_VCO_MIN 1500000000
+#define PS_PLL_VCO_MAX 3000000000UL
+
+enum pll_mode {
+ PLL_MODE_INT,
+ PLL_MODE_FRAC,
+};
+
+#define FRAC_OFFSET 0x8
+#define PLLFCFG_FRAC_EN BIT(31)
+#define FRAC_DIV BIT(16) /* 2^16 */
+
+/**
+ * zynqmp_pll_get_mode() - Get mode of PLL
+ * @hw: Handle between common and hardware-specific interfaces
+ *
+ * Return: Mode of PLL
+ */
+static inline enum pll_mode zynqmp_pll_get_mode(struct clk_hw *hw)
+{
+ struct zynqmp_pll *clk = to_zynqmp_pll(hw);
+ u32 clk_id = clk->clk_id;
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->ioctl(0, IOCTL_GET_PLL_FRAC_MODE, clk_id, 0,
+ ret_payload);
+ if (ret)
+ pr_warn_once("%s() PLL get frac mode failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ return ret_payload[1];
+}
+
+/**
+ * zynqmp_pll_set_mode() - Set the PLL mode
+ * @hw: Handle between common and hardware-specific interfaces
+ * @on: Flag to determine the mode
+ */
+static inline void zynqmp_pll_set_mode(struct clk_hw *hw, bool on)
+{
+ struct zynqmp_pll *clk = to_zynqmp_pll(hw);
+ u32 clk_id = clk->clk_id;
+ const char *clk_name = clk_hw_get_name(hw);
+ int ret;
+ u32 mode;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ if (on)
+ mode = PLL_MODE_FRAC;
+ else
+ mode = PLL_MODE_INT;
+
+ ret = eemi_ops->ioctl(0, IOCTL_SET_PLL_FRAC_MODE, clk_id, mode, NULL);
+ if (ret)
+ pr_warn_once("%s() PLL set frac mode failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+}
+
+/**
+ * zynqmp_pll_round_rate() - Round a clock frequency
+ * @hw: Handle between common and hardware-specific interfaces
+ * @rate: Desired clock frequency
+ * @prate: Clock frequency of parent clock
+ *
+ * Return: Frequency closest to @rate the hardware can generate
+ */
+static long zynqmp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ u32 fbdiv;
+ long rate_div, f;
+
+ /* Enable the fractional mode if needed */
+ rate_div = (rate * FRAC_DIV) / *prate;
+ f = rate_div % FRAC_DIV;
+ zynqmp_pll_set_mode(hw, !!f);
+
+ if (zynqmp_pll_get_mode(hw) == PLL_MODE_FRAC) {
+ if (rate > PS_PLL_VCO_MAX) {
+ fbdiv = rate / PS_PLL_VCO_MAX;
+ rate = rate / (fbdiv + 1);
+ }
+ if (rate < PS_PLL_VCO_MIN) {
+ fbdiv = DIV_ROUND_UP(PS_PLL_VCO_MIN, rate);
+ rate = rate * fbdiv;
+ }
+ return rate;
+ }
+
+ fbdiv = DIV_ROUND_CLOSEST(rate, *prate);
+ fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX);
+ return *prate * fbdiv;
+}
+
+/**
+ * zynqmp_pll_recalc_rate() - Recalculate clock frequency
+ * @hw: Handle between common and hardware-specific interfaces
+ * @parent_rate: Clock frequency of parent clock
+ *
+ * Return: Current clock frequency
+ */
+static unsigned long zynqmp_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct zynqmp_pll *clk = to_zynqmp_pll(hw);
+ u32 clk_id = clk->clk_id;
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 fbdiv, data;
+ unsigned long rate, frac;
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ /*
+ * makes probably sense to redundantly save fbdiv in the struct
+ * zynqmp_pll to save the IO access.
+ */
+ ret = eemi_ops->clock_getdivider(clk_id, &fbdiv);
+ if (ret)
+ pr_warn_once("%s() get divider failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ rate = parent_rate * fbdiv;
+ if (zynqmp_pll_get_mode(hw) == PLL_MODE_FRAC) {
+ eemi_ops->ioctl(0, IOCTL_GET_PLL_FRAC_DATA, clk_id, 0,
+ ret_payload);
+ data = ret_payload[1];
+ frac = (parent_rate * data) / FRAC_DIV;
+ rate = rate + frac;
+ }
+
+ return rate;
+}
+
+/**
+ * zynqmp_pll_set_rate() - Set rate of PLL
+ * @hw: Handle between common and hardware-specific interfaces
+ * @rate: Frequency of clock to be set
+ * @parent_rate: Clock frequency of parent clock
+ *
+ * Set PLL divider to set desired rate.
+ *
+ * Returns: rate which is set on success else error code
+ */
+static int zynqmp_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct zynqmp_pll *clk = to_zynqmp_pll(hw);
+ u32 clk_id = clk->clk_id;
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 fbdiv, data;
+ long rate_div, frac, m, f;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ if (zynqmp_pll_get_mode(hw) == PLL_MODE_FRAC) {
+ rate_div = ((rate * FRAC_DIV) / parent_rate);
+ m = rate_div / FRAC_DIV;
+ f = rate_div % FRAC_DIV;
+ m = clamp_t(u32, m, (PLL_FBDIV_MIN), (PLL_FBDIV_MAX));
+ rate = parent_rate * m;
+ frac = (parent_rate * f) / FRAC_DIV;
+
+ ret = eemi_ops->clock_setdivider(clk_id, m);
+ if (ret)
+ pr_warn_once("%s() set divider failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ data = (FRAC_DIV * f) / FRAC_DIV;
+ eemi_ops->ioctl(0, IOCTL_SET_PLL_FRAC_DATA, clk_id, data, NULL);
+
+ return rate + frac;
+ }
+
+ fbdiv = DIV_ROUND_CLOSEST(rate, parent_rate);
+ fbdiv = clamp_t(u32, fbdiv, PLL_FBDIV_MIN, PLL_FBDIV_MAX);
+ ret = eemi_ops->clock_setdivider(clk_id, fbdiv);
+ if (ret)
+ pr_warn_once("%s() set divider failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ return parent_rate * fbdiv;
+}
+
+/**
+ * zynqmp_pll_is_enabled() - Check if a clock is enabled
+ * @hw: Handle between common and hardware-specific interfaces
+ *
+ * Return: 1 if the clock is enabled, 0 otherwise
+ */
+static int zynqmp_pll_is_enabled(struct clk_hw *hw)
+{
+ struct zynqmp_pll *clk = to_zynqmp_pll(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = clk->clk_id;
+ unsigned int state;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ ret = eemi_ops->clock_getstate(clk_id, &state);
+ if (ret) {
+ pr_warn_once("%s() clock get state failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+ return -EIO;
+ }
+
+ return state ? 1 : 0;
+}
+
+/**
+ * zynqmp_pll_enable() - Enable clock
+ * @hw: Handle between common and hardware-specific interfaces
+ *
+ * Return: 0 on success else error code
+ */
+static int zynqmp_pll_enable(struct clk_hw *hw)
+{
+ struct zynqmp_pll *clk = to_zynqmp_pll(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = clk->clk_id;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ if (zynqmp_pll_is_enabled(hw))
+ return 0;
+
+ pr_info("PLL: enable\n");
+
+ ret = eemi_ops->clock_enable(clk_id);
+ if (ret)
+ pr_warn_once("%s() clock enable failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+
+ return ret;
+}
+
+/**
+ * zynqmp_pll_disable() - Disable clock
+ * @hw: Handle between common and hardware-specific interfaces
+ */
+static void zynqmp_pll_disable(struct clk_hw *hw)
+{
+ struct zynqmp_pll *clk = to_zynqmp_pll(hw);
+ const char *clk_name = clk_hw_get_name(hw);
+ u32 clk_id = clk->clk_id;
+ int ret;
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+
+ if (!zynqmp_pll_is_enabled(hw))
+ return;
+
+ pr_info("PLL: shutdown\n");
+
+ ret = eemi_ops->clock_disable(clk_id);
+ if (ret)
+ pr_warn_once("%s() clock disable failed for %s, ret = %d\n",
+ __func__, clk_name, ret);
+}
+
+static const struct clk_ops zynqmp_pll_ops = {
+ .enable = zynqmp_pll_enable,
+ .disable = zynqmp_pll_disable,
+ .is_enabled = zynqmp_pll_is_enabled,
+ .round_rate = zynqmp_pll_round_rate,
+ .recalc_rate = zynqmp_pll_recalc_rate,
+ .set_rate = zynqmp_pll_set_rate,
+};
+
+/**
+ * zynqmp_clk_register_pll() - Register PLL with the clock framework
+ * @dev: Device pointer
+ * @name: PLL name
+ * @clk_id: Clock ID
+ * @parent: Parent clock name
+ * @flag: PLL flgas
+ *
+ * Return: clock hardware to the registered clock
+ */
+struct clk_hw *zynqmp_clk_register_pll(struct device *dev, const char *name,
+ u32 clk_id,
+ const char *parent,
+ unsigned long flag)
+{
+ struct zynqmp_pll *pll;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ init.name = name;
+ init.ops = &zynqmp_pll_ops;
+ init.flags = flag;
+ init.parent_names = &parent;
+ init.num_parents = 1;
+
+ pll = kmalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ /* Populate the struct */
+ pll->hw.init = &init;
+ pll->clk_id = clk_id;
+
+ hw = &pll->hw;
+ ret = clk_hw_register(dev, hw);
+ if (ret) {
+ kfree(pll);
+ return ERR_PTR(ret);
+ }
+
+ clk_hw_set_rate_range(hw, PS_PLL_VCO_MIN, PS_PLL_VCO_MAX);
+ if (ret < 0)
+ pr_err("%s:ERROR clk_set_rate_range failed %d\n", name, ret);
+
+ return hw;
+}
--
2.7.4
^ permalink raw reply related
* [PATCH v7 09/10] dt-bindings: clock: Add bindings for ZynqMP clock driver
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
Add documentation to describe Xilinx ZynqMP clock driver
bindings.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
.../firmware/xilinx/xlnx,zynqmp-firmware.txt | 53 ++++++++++
include/dt-bindings/clock/xlnx,zynqmp-clk.h | 116 +++++++++++++++++++++
2 files changed, 169 insertions(+)
create mode 100644 include/dt-bindings/clock/xlnx,zynqmp-clk.h
diff --git a/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.txt b/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.txt
index 1b431d9..d215d15 100644
--- a/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.txt
+++ b/Documentation/devicetree/bindings/firmware/xilinx/xlnx,zynqmp-firmware.txt
@@ -17,6 +17,53 @@ Required properties:
- "smc" : SMC #0, following the SMCCC
- "hvc" : HVC #0, following the SMCCC
+--------------------------------------------------------------------------
+Device Tree Clock bindings for the Zynq Ultrascale+ MPSoC controlled using
+Zynq MPSoC firmware interface
+--------------------------------------------------------------------------
+The clock controller is a h/w block of Zynq Ultrascale+ MPSoC clock
+tree. It reads required input clock frequencies from the devicetree and acts
+as clock provider for all clock consumers of PS clocks.
+
+See clock_bindings.txt for more information on the generic clock bindings.
+
+Required properties:
+ - #clock-cells: Must be 1
+ - compatible: Must contain: "xlnx,zynqmp-clk"
+ - clocks: List of clock specifiers which are external input
+ clocks to the given clock controller. Please refer
+ the next section to find the input clocks for a
+ given controller.
+ - clock-names: List of clock names which are exteral input clocks
+ to the given clock controller. Please refer to the
+ clock bindings for more details.
+
+Input clocks for zynqmp Ultrascale+ clock controller:
+
+The Zynq UltraScale+ MPSoC has one primary and four alternative reference clock
+inputs. These required clock inputs are:
+ - pss_ref_clk (PS reference clock)
+ - video_clk (reference clock for video system )
+ - pss_alt_ref_clk (alternative PS reference clock)
+ - aux_ref_clk
+ - gt_crx_ref_clk (transceiver reference clock)
+
+The following strings are optional parameters to the 'clock-names' property in
+order to provide an optional (E)MIO clock source:
+ - swdt0_ext_clk
+ - swdt1_ext_clk
+ - gem0_emio_clk
+ - gem1_emio_clk
+ - gem2_emio_clk
+ - gem3_emio_clk
+ - mio_clk_XX # with XX = 00..77
+ - mio_clk_50_or_51 #for the mux clock to gem tsu from 50 or 51
+
+
+Output clocks are registered based on clock information received
+from firmware. Output clocks indexes are mentioned in
+include/dt-bindings/clock/xlnx,zynqmp-clk.h.
+
-------
Example
-------
@@ -25,5 +72,11 @@ firmware {
zynqmp_firmware: zynqmp-firmware {
compatible = "xlnx,zynqmp-firmware";
method = "smc";
+ zynqmp_clk : clock-controller {
+ #clock-cells = <1>;
+ compatible = "xlnx,zynqmp-clk";
+ clocks = <&pss_ref_clk>, <&video_clk>, <&pss_alt_ref_clk>, <&aux_ref_clk>, <>_crx_ref_clk>;
+ clock-names = "pss_ref_clk", "video_clk", "pss_alt_ref_clk","aux_ref_clk", "gt_crx_ref_clk";
+ };
};
};
diff --git a/include/dt-bindings/clock/xlnx,zynqmp-clk.h b/include/dt-bindings/clock/xlnx,zynqmp-clk.h
new file mode 100644
index 0000000..4aebe6e
--- /dev/null
+++ b/include/dt-bindings/clock/xlnx,zynqmp-clk.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx Zynq MPSoC Firmware layer
+ *
+ * Copyright (C) 2014-2018 Xilinx, Inc.
+ *
+ */
+
+#ifndef _DT_BINDINGS_CLK_ZYNQMP_H
+#define _DT_BINDINGS_CLK_ZYNQMP_H
+
+#define IOPLL 0
+#define RPLL 1
+#define APLL 2
+#define DPLL 3
+#define VPLL 4
+#define IOPLL_TO_FPD 5
+#define RPLL_TO_FPD 6
+#define APLL_TO_LPD 7
+#define DPLL_TO_LPD 8
+#define VPLL_TO_LPD 9
+#define ACPU 10
+#define ACPU_HALF 11
+#define DBF_FPD 12
+#define DBF_LPD 13
+#define DBG_TRACE 14
+#define DBG_TSTMP 15
+#define DP_VIDEO_REF 16
+#define DP_AUDIO_REF 17
+#define DP_STC_REF 18
+#define GDMA_REF 19
+#define DPDMA_REF 20
+#define DDR_REF 21
+#define SATA_REF 22
+#define PCIE_REF 23
+#define GPU_REF 24
+#define GPU_PP0_REF 25
+#define GPU_PP1_REF 26
+#define TOPSW_MAIN 27
+#define TOPSW_LSBUS 28
+#define GTGREF0_REF 29
+#define LPD_SWITCH 30
+#define LPD_LSBUS 31
+#define USB0_BUS_REF 32
+#define USB1_BUS_REF 33
+#define USB3_DUAL_REF 34
+#define USB0 35
+#define USB1 36
+#define CPU_R5 37
+#define CPU_R5_CORE 38
+#define CSU_SPB 39
+#define CSU_PLL 40
+#define PCAP 41
+#define IOU_SWITCH 42
+#define GEM_TSU_REF 43
+#define GEM_TSU 44
+#define GEM0_REF 45
+#define GEM1_REF 46
+#define GEM2_REF 47
+#define GEM3_REF 48
+#define GEM0_TX 49
+#define GEM1_TX 50
+#define GEM2_TX 51
+#define GEM3_TX 52
+#define QSPI_REF 53
+#define SDIO0_REF 54
+#define SDIO1_REF 55
+#define UART0_REF 56
+#define UART1_REF 57
+#define SPI0_REF 58
+#define SPI1_REF 59
+#define NAND_REF 60
+#define I2C0_REF 61
+#define I2C1_REF 62
+#define CAN0_REF 63
+#define CAN1_REF 64
+#define CAN0 65
+#define CAN1 66
+#define DLL_REF 67
+#define ADMA_REF 68
+#define TIMESTAMP_REF 69
+#define AMS_REF 70
+#define PL0_REF 71
+#define PL1_REF 72
+#define PL2_REF 73
+#define PL3_REF 74
+#define WDT 75
+#define IOPLL_INT 76
+#define IOPLL_PRE_SRC 77
+#define IOPLL_HALF 78
+#define IOPLL_INT_MUX 79
+#define IOPLL_POST_SRC 80
+#define RPLL_INT 81
+#define RPLL_PRE_SRC 82
+#define RPLL_HALF 83
+#define RPLL_INT_MUX 84
+#define RPLL_POST_SRC 85
+#define APLL_INT 86
+#define APLL_PRE_SRC 87
+#define APLL_HALF 88
+#define APLL_INT_MUX 89
+#define APLL_POST_SRC 90
+#define DPLL_INT 91
+#define DPLL_PRE_SRC 92
+#define DPLL_HALF 93
+#define DPLL_INT_MUX 94
+#define DPLL_POST_SRC 95
+#define VPLL_INT 96
+#define VPLL_PRE_SRC 97
+#define VPLL_HALF 98
+#define VPLL_INT_MUX 99
+#define VPLL_POST_SRC 100
+#define CAN0_MIO 101
+#define CAN1_MIO 102
+
+#endif
--
2.7.4
^ permalink raw reply related
* [PATCH v7 08/10] firmware: xilinx: Add debugfs for query data API
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
Add debugfs file to query platform specific data from firmware
using debugfs interface.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
drivers/firmware/xilinx/zynqmp-debug.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/drivers/firmware/xilinx/zynqmp-debug.c b/drivers/firmware/xilinx/zynqmp-debug.c
index 148dbff..1cb69f7 100644
--- a/drivers/firmware/xilinx/zynqmp-debug.c
+++ b/drivers/firmware/xilinx/zynqmp-debug.c
@@ -33,6 +33,7 @@ static char debugfs_buf[PAGE_SIZE];
static struct pm_api_info pm_api_list[] = {
PM_API(PM_GET_API_VERSION),
PM_API(PM_IOCTL),
+ PM_API(PM_QUERY_DATA),
};
/**
@@ -105,6 +106,32 @@ static int process_api_request(u32 pm_id, u64 *pm_api_arg, u32 *pm_api_ret)
sprintf(debugfs_buf, "IOCTL return value: %u\n",
pm_api_ret[1]);
break;
+ case PM_QUERY_DATA:
+ {
+ struct zynqmp_pm_query_data qdata = {0};
+
+ qdata.qid = pm_api_arg[0];
+ qdata.arg1 = pm_api_arg[1];
+ qdata.arg2 = pm_api_arg[2];
+ qdata.arg3 = pm_api_arg[3];
+
+ ret = eemi_ops->query_data(qdata, pm_api_ret);
+ if (ret)
+ break;
+
+ if (qdata.qid == PM_QID_CLOCK_GET_NAME)
+ sprintf(debugfs_buf, "Clock name = %s\n",
+ (char *)pm_api_ret);
+ else if (qdata.qid == PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS)
+ sprintf(debugfs_buf, "Multiplier = %d, Divider = %d\n",
+ pm_api_ret[1], pm_api_ret[2]);
+ else
+ sprintf(debugfs_buf,
+ "data[0] = 0x%08x\ndata[1] = 0x%08x\n data[2] = 0x%08x\ndata[3] = 0x%08x\n",
+ pm_api_ret[0], pm_api_ret[1],
+ pm_api_ret[2], pm_api_ret[3]);
+ break;
+ }
default:
sprintf(debugfs_buf, "Unsupported PM-API request\n");
ret = -EINVAL;
--
2.7.4
^ permalink raw reply related
* [PATCH v7 07/10] firmware: xilinx: Add debugfs for IOCTL API
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
Add debugfs file to set/get IOCTL using debugfs interface.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
drivers/firmware/xilinx/zynqmp-debug.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/firmware/xilinx/zynqmp-debug.c b/drivers/firmware/xilinx/zynqmp-debug.c
index 4800366..148dbff 100644
--- a/drivers/firmware/xilinx/zynqmp-debug.c
+++ b/drivers/firmware/xilinx/zynqmp-debug.c
@@ -32,6 +32,7 @@ static char debugfs_buf[PAGE_SIZE];
#define PM_API(id) {id, #id, strlen(#id)}
static struct pm_api_info pm_api_list[] = {
PM_API(PM_GET_API_VERSION),
+ PM_API(PM_IOCTL),
};
/**
@@ -95,6 +96,15 @@ static int process_api_request(u32 pm_id, u64 *pm_api_arg, u32 *pm_api_ret)
sprintf(debugfs_buf, "PM-API Version = %d.%d\n",
pm_api_version >> 16, pm_api_version & 0xffff);
break;
+ case PM_IOCTL:
+ ret = eemi_ops->ioctl(pm_api_arg[0], pm_api_arg[1],
+ pm_api_arg[2], pm_api_arg[3],
+ &pm_api_ret[0]);
+ if (!ret && (pm_api_arg[1] == IOCTL_GET_PLL_FRAC_MODE ||
+ pm_api_arg[1] == IOCTL_GET_PLL_FRAC_DATA))
+ sprintf(debugfs_buf, "IOCTL return value: %u\n",
+ pm_api_ret[1]);
+ break;
default:
sprintf(debugfs_buf, "Unsupported PM-API request\n");
ret = -EINVAL;
--
2.7.4
^ permalink raw reply related
* [PATCH v7 06/10] firmware: xilinx: Add debugfs interface
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
Firmware-debug provides debugfs interface to all APIs.
Debugfs can be used to call firmware APIs with required
parameters.
Usage:
* Calling firmware API through debugfs:
# echo "<api-name> <arg1> .. <argn>" > /sys/.../zynqmp-firmware/pm
* Read output of last called firmware API:
# cat /sys/.../zynqmp-firmware/pm
Refer ug1200 for more information on these APIs:
* https://www.xilinx.com/support/documentation/user_guides/ug1200-eemi-api.pdf
Add basic debugfs file to get API version.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
drivers/firmware/xilinx/Kconfig | 7 ++
drivers/firmware/xilinx/Makefile | 1 +
drivers/firmware/xilinx/zynqmp-debug.c | 212 +++++++++++++++++++++++++++++++++
drivers/firmware/xilinx/zynqmp-debug.h | 22 ++++
drivers/firmware/xilinx/zynqmp.c | 9 ++
5 files changed, 251 insertions(+)
create mode 100644 drivers/firmware/xilinx/zynqmp-debug.c
create mode 100644 drivers/firmware/xilinx/zynqmp-debug.h
diff --git a/drivers/firmware/xilinx/Kconfig b/drivers/firmware/xilinx/Kconfig
index cce4e4f..10faa10 100644
--- a/drivers/firmware/xilinx/Kconfig
+++ b/drivers/firmware/xilinx/Kconfig
@@ -13,4 +13,11 @@ config ZYNQMP_FIRMWARE
Say yes to enable ZynqMP firmware interface driver.
In doubt, say N
+config ZYNQMP_FIRMWARE_DEBUG
+ bool "Enable Xilinx Zynq MPSoC firmware debug APIs"
+ depends on ZYNQMP_FIRMWARE && DEBUG_FS
+ help
+ Say yes to enable ZynqMP firmware interface debug APIs.
+ In doubt, say N
+
endmenu
diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile
index 29f7bf2..875a537 100644
--- a/drivers/firmware/xilinx/Makefile
+++ b/drivers/firmware/xilinx/Makefile
@@ -2,3 +2,4 @@
# Makefile for Xilinx firmwares
obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o
+obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o
diff --git a/drivers/firmware/xilinx/zynqmp-debug.c b/drivers/firmware/xilinx/zynqmp-debug.c
new file mode 100644
index 0000000..4800366
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp-debug.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Zynq MPSoC Firmware layer for debugfs APIs
+ *
+ * Copyright (C) 2014-2018 Xilinx, Inc.
+ *
+ * Michal Simek <michal.simek@xilinx.com>
+ * Davorin Mista <davorin.mista@aggios.com>
+ * Jolly Shah <jollys@xilinx.com>
+ * Rajan Vaja <rajanv@xilinx.com>
+ */
+
+#include <linux/compiler.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+
+#include <linux/firmware/xlnx-zynqmp.h>
+#include "zynqmp-debug.h"
+
+#define PM_API_NAME_LEN 50
+
+struct pm_api_info {
+ u32 api_id;
+ char api_name[PM_API_NAME_LEN];
+ char api_name_len;
+};
+
+static char debugfs_buf[PAGE_SIZE];
+
+#define PM_API(id) {id, #id, strlen(#id)}
+static struct pm_api_info pm_api_list[] = {
+ PM_API(PM_GET_API_VERSION),
+};
+
+/**
+ * zynqmp_pm_argument_value() - Extract argument value from a PM-API request
+ * @arg: Entered PM-API argument in string format
+ *
+ * Return: Argument value in unsigned integer format on success
+ * 0 otherwise
+ */
+static u64 zynqmp_pm_argument_value(char *arg)
+{
+ u64 value;
+
+ if (!arg)
+ return 0;
+
+ if (!kstrtou64(arg, 0, &value))
+ return value;
+
+ return 0;
+}
+
+/**
+ * get_pm_api_id() - Extract API-ID from a PM-API request
+ * @pm_api_req: Entered PM-API argument in string format
+ * @pm_id: API-ID
+ *
+ * Return: 0 on success else error code
+ */
+static int get_pm_api_id(char *pm_api_req, u32 *pm_id)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pm_api_list) ; i++) {
+ if (!strncasecmp(pm_api_req, pm_api_list[i].api_name,
+ pm_api_list[i].api_name_len)) {
+ *pm_id = pm_api_list[i].api_id;
+ break;
+ }
+ }
+
+ /* If no name was entered look for PM-API ID instead */
+ if (i == ARRAY_SIZE(pm_api_list) && kstrtouint(pm_api_req, 10, pm_id))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int process_api_request(u32 pm_id, u64 *pm_api_arg, u32 *pm_api_ret)
+{
+ const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
+ u32 pm_api_version;
+ int ret;
+
+ if (!eemi_ops)
+ return -ENXIO;
+
+ switch (pm_id) {
+ case PM_GET_API_VERSION:
+ ret = eemi_ops->get_api_version(&pm_api_version);
+ sprintf(debugfs_buf, "PM-API Version = %d.%d\n",
+ pm_api_version >> 16, pm_api_version & 0xffff);
+ break;
+ default:
+ sprintf(debugfs_buf, "Unsupported PM-API request\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_debugfs_api_write() - debugfs write function
+ * @file: User file structure
+ * @ptr: User entered PM-API string
+ * @len: Length of the userspace buffer
+ * @off: Offset within the file
+ *
+ * Used for triggering pm api functions by writing
+ * echo <pm_api_id> > /sys/kernel/debug/zynqmp_pm/power or
+ * echo <pm_api_name> > /sys/kernel/debug/zynqmp_pm/power
+ *
+ * Return: Number of bytes copied if PM-API request succeeds,
+ * the corresponding error code otherwise
+ */
+static ssize_t zynqmp_pm_debugfs_api_write(struct file *file,
+ const char __user *ptr, size_t len,
+ loff_t *off)
+{
+ char *kern_buff, *tmp_buff;
+ char *pm_api_req;
+ u32 pm_id = 0;
+ u64 pm_api_arg[4] = {0, 0, 0, 0};
+ /* Return values from PM APIs calls */
+ u32 pm_api_ret[4] = {0, 0, 0, 0};
+
+ int ret;
+ int i = 0;
+
+ strcpy(debugfs_buf, "");
+
+ if (*off != 0 || len == 0)
+ return -EINVAL;
+
+ kern_buff = kzalloc(len, GFP_KERNEL);
+ if (!kern_buff)
+ return -ENOMEM;
+
+ tmp_buff = kern_buff;
+
+ ret = strncpy_from_user(kern_buff, ptr, len);
+ if (ret < 0) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ /* Read the API name from a user request */
+ pm_api_req = strsep(&kern_buff, " ");
+
+ ret = get_pm_api_id(pm_api_req, &pm_id);
+ if (ret < 0)
+ goto err;
+
+ /* Read node_id and arguments from the PM-API request */
+ pm_api_req = strsep(&kern_buff, " ");
+ while ((i < ARRAY_SIZE(pm_api_arg)) && pm_api_req) {
+ pm_api_arg[i++] = zynqmp_pm_argument_value(pm_api_req);
+ pm_api_req = strsep(&kern_buff, " ");
+ }
+
+ ret = process_api_request(pm_id, pm_api_arg, pm_api_ret);
+
+err:
+ kfree(tmp_buff);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+/**
+ * zynqmp_pm_debugfs_api_read() - debugfs read function
+ * @file: User file structure
+ * @ptr: Requested pm_api_version string
+ * @len: Length of the userspace buffer
+ * @off: Offset within the file
+ *
+ * Return: Length of the version string on success
+ * else error code
+ */
+static ssize_t zynqmp_pm_debugfs_api_read(struct file *file, char __user *ptr,
+ size_t len, loff_t *off)
+{
+ return simple_read_from_buffer(ptr, len, off, debugfs_buf,
+ strlen(debugfs_buf));
+}
+
+/* Setup debugfs fops */
+static const struct file_operations fops_zynqmp_pm_dbgfs = {
+ .owner = THIS_MODULE,
+ .write = zynqmp_pm_debugfs_api_write,
+ .read = zynqmp_pm_debugfs_api_read,
+};
+
+/**
+ * zynqmp_pm_api_debugfs_init - Initialize debugfs interface
+ *
+ * Return: None
+ */
+void zynqmp_pm_api_debugfs_init(void)
+{
+ struct dentry *root_dir;
+
+ /* Initialize debugfs interface */
+ root_dir = debugfs_create_dir("zynqmp-firmware", NULL);
+ debugfs_create_file("pm", 0660, root_dir, NULL,
+ &fops_zynqmp_pm_dbgfs);
+}
diff --git a/drivers/firmware/xilinx/zynqmp-debug.h b/drivers/firmware/xilinx/zynqmp-debug.h
new file mode 100644
index 0000000..3303b37
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp-debug.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx Zynq MPSoC Firmware layer
+ *
+ * Copyright (C) 2014-2018 Xilinx
+ *
+ * Michal Simek <michal.simek@xilinx.com>
+ * Davorin Mista <davorin.mista@aggios.com>
+ * Jolly Shah <jollys@xilinx.com>
+ * Rajan Vaja <rajanv@xilinx.com>
+ */
+
+#ifndef __FIRMWARE_ZYNQMP_DEBUG_H__
+#define __FIRMWARE_ZYNQMP_DEBUG_H__
+
+#if IS_REACHABLE(CONFIG_ZYNQMP_FIRMWARE_DEBUG)
+void zynqmp_pm_api_debugfs_init(void);
+#else
+static inline void zynqmp_pm_api_debugfs_init(void) { }
+#endif
+
+#endif /* __FIRMWARE_ZYNQMP_DEBUG_H__ */
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
index c764d6e..83d2699 100644
--- a/drivers/firmware/xilinx/zynqmp.c
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -21,6 +21,7 @@
#include <linux/uaccess.h>
#include <linux/firmware/xlnx-zynqmp.h>
+#include "zynqmp-debug.h"
/**
* zynqmp_pm_ret_code() - Convert PMU-FW error codes to Linux error codes
@@ -551,3 +552,11 @@ static int __init zynqmp_plat_init(void)
return ret;
}
early_initcall(zynqmp_plat_init);
+
+static int zynqmp_firmware_init(void)
+{
+ zynqmp_pm_api_debugfs_init();
+
+ return 0;
+}
+device_initcall(zynqmp_firmware_init);
--
2.7.4
^ permalink raw reply related
* [PATCH v7 05/10] firmware: xilinx: Add clock APIs
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
Add clock APIs to control clocks through firmware
interface.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
drivers/firmware/xilinx/zynqmp.c | 186 ++++++++++++++++++++++++++++++++++-
include/linux/firmware/xlnx-zynqmp.h | 30 ++++++
2 files changed, 214 insertions(+), 2 deletions(-)
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
index 86d9bb8..c764d6e 100644
--- a/drivers/firmware/xilinx/zynqmp.c
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -269,14 +269,196 @@ static int zynqmp_pm_ioctl(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2,
*/
static int zynqmp_pm_query_data(struct zynqmp_pm_query_data qdata, u32 *out)
{
- return zynqmp_pm_invoke_fn(PM_QUERY_DATA, qdata.qid, qdata.arg1,
- qdata.arg2, qdata.arg3, out);
+ int ret;
+
+ ret = zynqmp_pm_invoke_fn(PM_QUERY_DATA, qdata.qid, qdata.arg1,
+ qdata.arg2, qdata.arg3, out);
+
+ /*
+ * For clock name query, all bytes in SMC response are clock name
+ * characters and return code is always success. For invalid clocks,
+ * clock name bytes would be 0s.
+ */
+ return qdata.qid == PM_QID_CLOCK_GET_NAME ? 0 : ret;
+}
+
+/**
+ * zynqmp_pm_clock_enable() - Enable the clock for given id
+ * @clock_id: ID of the clock to be enabled
+ *
+ * This function is used by master to enable the clock
+ * including peripherals and PLL clocks.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_enable(u32 clock_id)
+{
+ return zynqmp_pm_invoke_fn(PM_CLOCK_ENABLE, clock_id, 0, 0, 0, NULL);
+}
+
+/**
+ * zynqmp_pm_clock_disable() - Disable the clock for given id
+ * @clock_id: ID of the clock to be disable
+ *
+ * This function is used by master to disable the clock
+ * including peripherals and PLL clocks.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_disable(u32 clock_id)
+{
+ return zynqmp_pm_invoke_fn(PM_CLOCK_DISABLE, clock_id, 0, 0, 0, NULL);
+}
+
+/**
+ * zynqmp_pm_clock_getstate() - Get the clock state for given id
+ * @clock_id: ID of the clock to be queried
+ * @state: 1/0 (Enabled/Disabled)
+ *
+ * This function is used by master to get the state of clock
+ * including peripherals and PLL clocks.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_getstate(u32 clock_id, u32 *state)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETSTATE, clock_id, 0,
+ 0, 0, ret_payload);
+ *state = ret_payload[1];
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_clock_setdivider() - Set the clock divider for given id
+ * @clock_id: ID of the clock
+ * @divider: divider value
+ *
+ * This function is used by master to set divider for any clock
+ * to achieve desired rate.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_setdivider(u32 clock_id, u32 divider)
+{
+ return zynqmp_pm_invoke_fn(PM_CLOCK_SETDIVIDER, clock_id, divider,
+ 0, 0, NULL);
+}
+
+/**
+ * zynqmp_pm_clock_getdivider() - Get the clock divider for given id
+ * @clock_id: ID of the clock
+ * @divider: divider value
+ *
+ * This function is used by master to get divider values
+ * for any clock.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_getdivider(u32 clock_id, u32 *divider)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETDIVIDER, clock_id, 0,
+ 0, 0, ret_payload);
+ *divider = ret_payload[1];
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_clock_setrate() - Set the clock rate for given id
+ * @clock_id: ID of the clock
+ * @rate: rate value in hz
+ *
+ * This function is used by master to set rate for any clock.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_setrate(u32 clock_id, u64 rate)
+{
+ return zynqmp_pm_invoke_fn(PM_CLOCK_SETRATE, clock_id,
+ rate & 0xFFFFFFFF,
+ (rate >> 32) & 0xFFFFFFFF,
+ 0, NULL);
+}
+
+/**
+ * zynqmp_pm_clock_getrate() - Get the clock rate for given id
+ * @clock_id: ID of the clock
+ * @rate: rate value in hz
+ *
+ * This function is used by master to get rate
+ * for any clock.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_getrate(u32 clock_id, u64 *rate)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETRATE, clock_id, 0,
+ 0, 0, ret_payload);
+ *rate = ((u64)ret_payload[2] << 32) | ret_payload[1];
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_clock_setparent() - Set the clock parent for given id
+ * @clock_id: ID of the clock
+ * @parent_id: parent id
+ *
+ * This function is used by master to set parent for any clock.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_setparent(u32 clock_id, u32 parent_id)
+{
+ return zynqmp_pm_invoke_fn(PM_CLOCK_SETPARENT, clock_id,
+ parent_id, 0, 0, NULL);
+}
+
+/**
+ * zynqmp_pm_clock_getparent() - Get the clock parent for given id
+ * @clock_id: ID of the clock
+ * @parent_id: parent id
+ *
+ * This function is used by master to get parent index
+ * for any clock.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_clock_getparent(u32 clock_id, u32 *parent_id)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETPARENT, clock_id, 0,
+ 0, 0, ret_payload);
+ *parent_id = ret_payload[1];
+
+ return ret;
}
static const struct zynqmp_eemi_ops eemi_ops = {
.get_api_version = zynqmp_pm_get_api_version,
.ioctl = zynqmp_pm_ioctl,
.query_data = zynqmp_pm_query_data,
+ .clock_enable = zynqmp_pm_clock_enable,
+ .clock_disable = zynqmp_pm_clock_disable,
+ .clock_getstate = zynqmp_pm_clock_getstate,
+ .clock_setdivider = zynqmp_pm_clock_setdivider,
+ .clock_getdivider = zynqmp_pm_clock_getdivider,
+ .clock_setrate = zynqmp_pm_clock_setrate,
+ .clock_getrate = zynqmp_pm_clock_getrate,
+ .clock_setparent = zynqmp_pm_clock_setparent,
+ .clock_getparent = zynqmp_pm_clock_getparent,
};
/**
diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
index 354385d..29fb352 100644
--- a/include/linux/firmware/xlnx-zynqmp.h
+++ b/include/linux/firmware/xlnx-zynqmp.h
@@ -36,6 +36,15 @@ enum pm_api_id {
PM_GET_API_VERSION = 1,
PM_IOCTL = 34,
PM_QUERY_DATA,
+ PM_CLOCK_ENABLE,
+ PM_CLOCK_DISABLE,
+ PM_CLOCK_GETSTATE,
+ PM_CLOCK_SETDIVIDER,
+ PM_CLOCK_GETDIVIDER,
+ PM_CLOCK_SETRATE,
+ PM_CLOCK_GETRATE,
+ PM_CLOCK_SETPARENT,
+ PM_CLOCK_GETPARENT,
};
/* PMU-FW return status codes */
@@ -49,8 +58,20 @@ enum pm_ret_status {
XST_PM_ABORT_SUSPEND,
};
+enum pm_ioctl_id {
+ IOCTL_SET_PLL_FRAC_MODE = 8,
+ IOCTL_GET_PLL_FRAC_MODE,
+ IOCTL_SET_PLL_FRAC_DATA,
+ IOCTL_GET_PLL_FRAC_DATA,
+};
+
enum pm_query_id {
PM_QID_INVALID,
+ PM_QID_CLOCK_GET_NAME,
+ PM_QID_CLOCK_GET_TOPOLOGY,
+ PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS,
+ PM_QID_CLOCK_GET_PARENTS,
+ PM_QID_CLOCK_GET_ATTRIBUTES,
};
/**
@@ -71,6 +92,15 @@ struct zynqmp_eemi_ops {
int (*get_api_version)(u32 *version);
int (*ioctl)(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2, u32 *out);
int (*query_data)(struct zynqmp_pm_query_data qdata, u32 *out);
+ int (*clock_enable)(u32 clock_id);
+ int (*clock_disable)(u32 clock_id);
+ int (*clock_getstate)(u32 clock_id, u32 *state);
+ int (*clock_setdivider)(u32 clock_id, u32 divider);
+ int (*clock_getdivider)(u32 clock_id, u32 *divider);
+ int (*clock_setrate)(u32 clock_id, u64 rate);
+ int (*clock_getrate)(u32 clock_id, u64 *rate);
+ int (*clock_setparent)(u32 clock_id, u32 parent_id);
+ int (*clock_getparent)(u32 clock_id, u32 *parent_id);
};
#if IS_REACHABLE(CONFIG_ARCH_ZYNQMP)
--
2.7.4
^ permalink raw reply related
* [PATCH v7 04/10] firmware: xilinx: Add query data API
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
Add ZynqMP firmware query data API to query platform
specific information(clocks, pins) from firmware.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
drivers/firmware/xilinx/zynqmp.c | 14 ++++++++++++++
include/linux/firmware/xlnx-zynqmp.h | 20 ++++++++++++++++++++
2 files changed, 34 insertions(+)
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
index 34c5ad5..86d9bb8 100644
--- a/drivers/firmware/xilinx/zynqmp.c
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -260,9 +260,23 @@ static int zynqmp_pm_ioctl(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2,
arg1, arg2, out);
}
+/**
+ * zynqmp_pm_query_data() - Get query data from firmware
+ * @qdata: Variable to the zynqmp_pm_query_data structure
+ * @out: Returned output value
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_query_data(struct zynqmp_pm_query_data qdata, u32 *out)
+{
+ return zynqmp_pm_invoke_fn(PM_QUERY_DATA, qdata.qid, qdata.arg1,
+ qdata.arg2, qdata.arg3, out);
+}
+
static const struct zynqmp_eemi_ops eemi_ops = {
.get_api_version = zynqmp_pm_get_api_version,
.ioctl = zynqmp_pm_ioctl,
+ .query_data = zynqmp_pm_query_data,
};
/**
diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
index 2eec6e7..354385d 100644
--- a/include/linux/firmware/xlnx-zynqmp.h
+++ b/include/linux/firmware/xlnx-zynqmp.h
@@ -35,6 +35,7 @@
enum pm_api_id {
PM_GET_API_VERSION = 1,
PM_IOCTL = 34,
+ PM_QUERY_DATA,
};
/* PMU-FW return status codes */
@@ -48,9 +49,28 @@ enum pm_ret_status {
XST_PM_ABORT_SUSPEND,
};
+enum pm_query_id {
+ PM_QID_INVALID,
+};
+
+/**
+ * struct zynqmp_pm_query_data - PM query data structure
+ * @qid: query ID
+ * @arg1: Argument 1 of query data
+ * @arg2: Argument 2 of query data
+ * @arg3: Argument 3 of query data
+ */
+struct zynqmp_pm_query_data {
+ u32 qid;
+ u32 arg1;
+ u32 arg2;
+ u32 arg3;
+};
+
struct zynqmp_eemi_ops {
int (*get_api_version)(u32 *version);
int (*ioctl)(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2, u32 *out);
+ int (*query_data)(struct zynqmp_pm_query_data qdata, u32 *out);
};
#if IS_REACHABLE(CONFIG_ARCH_ZYNQMP)
--
2.7.4
^ permalink raw reply related
* [PATCH v7 03/10] firmware: xilinx: Add zynqmp IOCTL API for device control
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
Add ZynqMP firmware IOCTL API to control and configure
devices like PLLs, SD, Gem, etc.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
drivers/firmware/xilinx/zynqmp.c | 20 ++++++++++++++++++++
include/linux/firmware/xlnx-zynqmp.h | 2 ++
2 files changed, 22 insertions(+)
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
index 70e335a..34c5ad5 100644
--- a/drivers/firmware/xilinx/zynqmp.c
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -241,8 +241,28 @@ static int get_set_conduit_method(struct device_node *np)
return 0;
}
+/**
+ * zynqmp_pm_ioctl() - PM IOCTL API for device control and configs
+ * @node_id: Node ID of the device
+ * @ioctl_id: ID of the requested IOCTL
+ * @arg1: Argument 1 to requested IOCTL call
+ * @arg2: Argument 2 to requested IOCTL call
+ * @out: Returned output value
+ *
+ * This function calls IOCTL to firmware for device control and configuration.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_ioctl(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2,
+ u32 *out)
+{
+ return zynqmp_pm_invoke_fn(PM_IOCTL, node_id, ioctl_id,
+ arg1, arg2, out);
+}
+
static const struct zynqmp_eemi_ops eemi_ops = {
.get_api_version = zynqmp_pm_get_api_version,
+ .ioctl = zynqmp_pm_ioctl,
};
/**
diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
index cb63bed..2eec6e7 100644
--- a/include/linux/firmware/xlnx-zynqmp.h
+++ b/include/linux/firmware/xlnx-zynqmp.h
@@ -34,6 +34,7 @@
enum pm_api_id {
PM_GET_API_VERSION = 1,
+ PM_IOCTL = 34,
};
/* PMU-FW return status codes */
@@ -49,6 +50,7 @@ enum pm_ret_status {
struct zynqmp_eemi_ops {
int (*get_api_version)(u32 *version);
+ int (*ioctl)(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2, u32 *out);
};
#if IS_REACHABLE(CONFIG_ARCH_ZYNQMP)
--
2.7.4
^ permalink raw reply related
* [PATCH v7 02/10] firmware: xilinx: Add Zynqmp firmware driver
From: Jolly Shah @ 2018-05-30 20:55 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1527713725-1086-1-git-send-email-jollys@xilinx.com>
From: Rajan Vaja <rajanv@xilinx.com>
This patch is adding communication layer with firmware.
Firmware driver provides an interface to firmware APIs.
Interface APIs can be used by any driver to communicate to
PMUFW(Platform Management Unit). All requests go through ATF.
Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
---
arch/arm64/Kconfig.platforms | 1 +
drivers/firmware/Kconfig | 1 +
drivers/firmware/Makefile | 1 +
drivers/firmware/xilinx/Kconfig | 16 ++
drivers/firmware/xilinx/Makefile | 4 +
drivers/firmware/xilinx/zynqmp.c | 337 +++++++++++++++++++++++++++++++++++
include/linux/firmware/xlnx-zynqmp.h | 63 +++++++
7 files changed, 423 insertions(+)
create mode 100644 drivers/firmware/xilinx/Kconfig
create mode 100644 drivers/firmware/xilinx/Makefile
create mode 100644 drivers/firmware/xilinx/zynqmp.c
create mode 100644 include/linux/firmware/xlnx-zynqmp.h
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index fbedbd8..6454458 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -274,6 +274,7 @@ config ARCH_ZX
config ARCH_ZYNQMP
bool "Xilinx ZynqMP Family"
+ select ZYNQMP_FIRMWARE
help
This enables support for Xilinx ZynqMP Family
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index b7c7482..f41eb0d 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -257,5 +257,6 @@ source "drivers/firmware/google/Kconfig"
source "drivers/firmware/efi/Kconfig"
source "drivers/firmware/meson/Kconfig"
source "drivers/firmware/tegra/Kconfig"
+source "drivers/firmware/xilinx/Kconfig"
endmenu
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index b248238..f90363e 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -31,3 +31,4 @@ obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
obj-$(CONFIG_EFI) += efi/
obj-$(CONFIG_UEFI_CPER) += efi/
obj-y += tegra/
+obj-y += xilinx/
diff --git a/drivers/firmware/xilinx/Kconfig b/drivers/firmware/xilinx/Kconfig
new file mode 100644
index 0000000..cce4e4f
--- /dev/null
+++ b/drivers/firmware/xilinx/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+# Kconfig for Xilinx firmwares
+
+menu "Zynq MPSoC Firmware Drivers"
+ depends on ARCH_ZYNQMP
+
+config ZYNQMP_FIRMWARE
+ bool "Enable Xilinx Zynq MPSoC firmware interface"
+ help
+ Firmware interface driver is used by different to
+ communicate with the firmware for various platform
+ management services.
+ Say yes to enable ZynqMP firmware interface driver.
+ In doubt, say N
+
+endmenu
diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile
new file mode 100644
index 0000000..29f7bf2
--- /dev/null
+++ b/drivers/firmware/xilinx/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for Xilinx firmwares
+
+obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
new file mode 100644
index 0000000..70e335a
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Zynq MPSoC Firmware layer
+ *
+ * Copyright (C) 2014-2018 Xilinx, Inc.
+ *
+ * Michal Simek <michal.simek@xilinx.com>
+ * Davorin Mista <davorin.mista@aggios.com>
+ * Jolly Shah <jollys@xilinx.com>
+ * Rajan Vaja <rajanv@xilinx.com>
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/compiler.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/firmware/xlnx-zynqmp.h>
+
+/**
+ * zynqmp_pm_ret_code() - Convert PMU-FW error codes to Linux error codes
+ * @ret_status: PMUFW return code
+ *
+ * Return: corresponding Linux error code
+ */
+static int zynqmp_pm_ret_code(u32 ret_status)
+{
+ switch (ret_status) {
+ case XST_PM_SUCCESS:
+ case XST_PM_DOUBLE_REQ:
+ return 0;
+ case XST_PM_NO_ACCESS:
+ return -EACCES;
+ case XST_PM_ABORT_SUSPEND:
+ return -ECANCELED;
+ case XST_PM_INTERNAL:
+ case XST_PM_CONFLICT:
+ case XST_PM_INVALID_NODE:
+ default:
+ return -EINVAL;
+ }
+}
+
+static noinline int do_fw_call_fail(u64 arg0, u64 arg1, u64 arg2,
+ u32 *ret_payload)
+{
+ return -ENODEV;
+}
+
+/*
+ * PM function call wrapper
+ * Invoke do_fw_call_smc or do_fw_call_hvc, depending on the configuration
+ */
+static int (*do_fw_call)(u64, u64, u64, u32 *ret_payload) = do_fw_call_fail;
+
+/**
+ * do_fw_call_smc() - Call system-level platform management layer (SMC)
+ * @arg0: Argument 0 to SMC call
+ * @arg1: Argument 1 to SMC call
+ * @arg2: Argument 2 to SMC call
+ * @ret_payload: Returned value array
+ *
+ * Invoke platform management function via SMC call (no hypervisor present).
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static noinline int do_fw_call_smc(u64 arg0, u64 arg1, u64 arg2,
+ u32 *ret_payload)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(arg0, arg1, arg2, 0, 0, 0, 0, 0, &res);
+
+ if (ret_payload) {
+ ret_payload[0] = lower_32_bits(res.a0);
+ ret_payload[1] = upper_32_bits(res.a0);
+ ret_payload[2] = lower_32_bits(res.a1);
+ ret_payload[3] = upper_32_bits(res.a1);
+ }
+
+ return zynqmp_pm_ret_code((enum pm_ret_status)res.a0);
+}
+
+/**
+ * do_fw_call_hvc() - Call system-level platform management layer (HVC)
+ * @arg0: Argument 0 to HVC call
+ * @arg1: Argument 1 to HVC call
+ * @arg2: Argument 2 to HVC call
+ * @ret_payload: Returned value array
+ *
+ * Invoke platform management function via HVC
+ * HVC-based for communication through hypervisor
+ * (no direct communication with ATF).
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static noinline int do_fw_call_hvc(u64 arg0, u64 arg1, u64 arg2,
+ u32 *ret_payload)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_hvc(arg0, arg1, arg2, 0, 0, 0, 0, 0, &res);
+
+ if (ret_payload) {
+ ret_payload[0] = lower_32_bits(res.a0);
+ ret_payload[1] = upper_32_bits(res.a0);
+ ret_payload[2] = lower_32_bits(res.a1);
+ ret_payload[3] = upper_32_bits(res.a1);
+ }
+
+ return zynqmp_pm_ret_code((enum pm_ret_status)res.a0);
+}
+
+/**
+ * zynqmp_pm_invoke_fn() - Invoke the system-level platform management layer
+ * caller function depending on the configuration
+ * @pm_api_id: Requested PM-API call
+ * @arg0: Argument 0 to requested PM-API call
+ * @arg1: Argument 1 to requested PM-API call
+ * @arg2: Argument 2 to requested PM-API call
+ * @arg3: Argument 3 to requested PM-API call
+ * @ret_payload: Returned value array
+ *
+ * Invoke platform management function for SMC or HVC call, depending on
+ * configuration.
+ * Following SMC Calling Convention (SMCCC) for SMC64:
+ * Pm Function Identifier,
+ * PM_SIP_SVC + PM_API_ID =
+ * ((SMC_TYPE_FAST << FUNCID_TYPE_SHIFT)
+ * ((SMC_64) << FUNCID_CC_SHIFT)
+ * ((SIP_START) << FUNCID_OEN_SHIFT)
+ * ((PM_API_ID) & FUNCID_NUM_MASK))
+ *
+ * PM_SIP_SVC - Registered ZynqMP SIP Service Call.
+ * PM_API_ID - Platform Management API ID.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 arg0, u32 arg1,
+ u32 arg2, u32 arg3, u32 *ret_payload)
+{
+ /*
+ * Added SIP service call Function Identifier
+ * Make sure to stay in x0 register
+ */
+ u64 smc_arg[4];
+
+ smc_arg[0] = PM_SIP_SVC | pm_api_id;
+ smc_arg[1] = ((u64)arg1 << 32) | arg0;
+ smc_arg[2] = ((u64)arg3 << 32) | arg2;
+
+ return do_fw_call(smc_arg[0], smc_arg[1], smc_arg[2], ret_payload);
+}
+
+static u32 pm_api_version;
+static u32 pm_tz_version;
+
+/**
+ * zynqmp_pm_get_api_version() - Get version number of PMU PM firmware
+ * @version: Returned version value
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_get_api_version(u32 *version)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ if (!version)
+ return -EINVAL;
+
+ /* Check is PM API version already verified */
+ if (pm_api_version > 0) {
+ *version = pm_api_version;
+ return 0;
+ }
+ ret = zynqmp_pm_invoke_fn(PM_GET_API_VERSION, 0, 0, 0, 0, ret_payload);
+ *version = ret_payload[1];
+
+ return ret;
+}
+
+/**
+ * zynqmp_pm_get_trustzone_version() - Get secure trustzone firmware version
+ * @version: Returned version value
+ *
+ * Return: Returns status, either success or error+reason
+ */
+static int zynqmp_pm_get_trustzone_version(u32 *version)
+{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ if (!version)
+ return -EINVAL;
+
+ /* Check is PM trustzone version already verified */
+ if (pm_tz_version > 0) {
+ *version = pm_tz_version;
+ return 0;
+ }
+ ret = zynqmp_pm_invoke_fn(PM_GET_TRUSTZONE_VERSION, 0, 0,
+ 0, 0, ret_payload);
+ *version = ret_payload[1];
+
+ return ret;
+}
+
+/**
+ * get_set_conduit_method() - Choose SMC or HVC based communication
+ * @np: Pointer to the device_node structure
+ *
+ * Use SMC or HVC-based functions to communicate with EL2/EL3.
+ *
+ * Return: Returns 0 on success or error code
+ */
+static int get_set_conduit_method(struct device_node *np)
+{
+ const char *method;
+
+ if (of_property_read_string(np, "method", &method)) {
+ pr_warn("%s missing \"method\" property\n", __func__);
+ return -ENXIO;
+ }
+
+ if (!strcmp("hvc", method)) {
+ do_fw_call = do_fw_call_hvc;
+ } else if (!strcmp("smc", method)) {
+ do_fw_call = do_fw_call_smc;
+ } else {
+ pr_warn("%s Invalid \"method\" property: %s\n",
+ __func__, method);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct zynqmp_eemi_ops eemi_ops = {
+ .get_api_version = zynqmp_pm_get_api_version,
+};
+
+/**
+ * zynqmp_pm_get_eemi_ops - Get eemi ops functions
+ *
+ * Return: pointer of eemi_ops structure
+ */
+const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void)
+{
+ return &eemi_ops;
+}
+EXPORT_SYMBOL_GPL(zynqmp_pm_get_eemi_ops);
+
+static int zynqmp_firmware_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ return of_platform_populate(dev->of_node, NULL, NULL, dev);
+}
+
+static const struct of_device_id zynqmp_firmware_of_match[] = {
+ {.compatible = "xlnx,zynqmp-firmware"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, zynqmp_firmware_of_match);
+
+static struct platform_driver zynqmp_firmware_driver = {
+ .driver = {
+ .name = "zynqmp_firmware",
+ .of_match_table = zynqmp_firmware_of_match,
+ },
+ .probe = zynqmp_firmware_probe,
+};
+module_platform_driver(zynqmp_firmware_driver);
+
+static int __init zynqmp_plat_init(void)
+{
+ int ret;
+ struct device_node *np;
+
+ np = of_find_compatible_node(NULL, NULL, "xlnx,zynqmp");
+ if (!np)
+ return 0;
+ of_node_put(np);
+
+ /*
+ * We're running on a ZynqMP machine,
+ * the zynqmp-firmware node is mandatory.
+ */
+ np = of_find_compatible_node(NULL, NULL, "xlnx,zynqmp-firmware");
+ if (!np) {
+ pr_warn("%s: zynqmp-firmware node not found\n", __func__);
+ return -ENXIO;
+ }
+
+ ret = get_set_conduit_method(np);
+ if (ret) {
+ of_node_put(np);
+ return ret;
+ }
+
+ /* Check PM API version number */
+ zynqmp_pm_get_api_version(&pm_api_version);
+ if (pm_api_version < ZYNQMP_PM_VERSION) {
+ panic("%s Platform Management API version error. Expected: v%d.%d - Found: v%d.%d\n",
+ __func__,
+ ZYNQMP_PM_VERSION_MAJOR, ZYNQMP_PM_VERSION_MINOR,
+ pm_api_version >> 16, pm_api_version & 0xFFFF);
+ }
+
+ pr_info("%s Platform Management API v%d.%d\n", __func__,
+ pm_api_version >> 16, pm_api_version & 0xFFFF);
+
+ /* Check trustzone version number */
+ ret = zynqmp_pm_get_trustzone_version(&pm_tz_version);
+ if (ret)
+ panic("Legacy trustzone found without version support\n");
+
+ if (pm_tz_version < ZYNQMP_TZ_VERSION)
+ panic("%s Trustzone version error. Expected: v%d.%d - Found: v%d.%d\n",
+ __func__,
+ ZYNQMP_TZ_VERSION_MAJOR, ZYNQMP_TZ_VERSION_MINOR,
+ pm_tz_version >> 16, pm_tz_version & 0xFFFF);
+
+ pr_info("%s Trustzone version v%d.%d\n", __func__,
+ pm_tz_version >> 16, pm_tz_version & 0xFFFF);
+
+ of_node_put(np);
+
+ return ret;
+}
+early_initcall(zynqmp_plat_init);
diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
new file mode 100644
index 0000000..cb63bed
--- /dev/null
+++ b/include/linux/firmware/xlnx-zynqmp.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx Zynq MPSoC Firmware layer
+ *
+ * Copyright (C) 2014-2018 Xilinx
+ *
+ * Michal Simek <michal.simek@xilinx.com>
+ * Davorin Mista <davorin.mista@aggios.com>
+ * Jolly Shah <jollys@xilinx.com>
+ * Rajan Vaja <rajanv@xilinx.com>
+ */
+
+#ifndef __FIRMWARE_ZYNQMP_H__
+#define __FIRMWARE_ZYNQMP_H__
+
+#define ZYNQMP_PM_VERSION_MAJOR 1
+#define ZYNQMP_PM_VERSION_MINOR 0
+
+#define ZYNQMP_PM_VERSION ((ZYNQMP_PM_VERSION_MAJOR << 16) | \
+ ZYNQMP_PM_VERSION_MINOR)
+
+#define ZYNQMP_TZ_VERSION_MAJOR 1
+#define ZYNQMP_TZ_VERSION_MINOR 0
+
+#define ZYNQMP_TZ_VERSION ((ZYNQMP_TZ_VERSION_MAJOR << 16) | \
+ ZYNQMP_TZ_VERSION_MINOR)
+
+/* SMC SIP service Call Function Identifier Prefix */
+#define PM_SIP_SVC 0xC2000000
+#define PM_GET_TRUSTZONE_VERSION 0xa03
+
+/* Number of 32bits values in payload */
+#define PAYLOAD_ARG_CNT 4U
+
+enum pm_api_id {
+ PM_GET_API_VERSION = 1,
+};
+
+/* PMU-FW return status codes */
+enum pm_ret_status {
+ XST_PM_SUCCESS = 0,
+ XST_PM_INTERNAL = 2000,
+ XST_PM_CONFLICT,
+ XST_PM_NO_ACCESS,
+ XST_PM_INVALID_NODE,
+ XST_PM_DOUBLE_REQ,
+ XST_PM_ABORT_SUSPEND,
+};
+
+struct zynqmp_eemi_ops {
+ int (*get_api_version)(u32 *version);
+};
+
+#if IS_REACHABLE(CONFIG_ARCH_ZYNQMP)
+const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void);
+#else
+static inline struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void)
+{
+ return NULL;
+}
+#endif
+
+#endif /* __FIRMWARE_ZYNQMP_H__ */
--
2.7.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox