* Re: [PATCH] drm: sun8i-ui/vi: Fix layer zpos change/atomic modesetting
From: Ondřej Jirman @ 2019-09-19 13:12 UTC (permalink / raw)
To: Maxime Ripard, David Airlie, Daniel Vetter, Chen-Yu Tsai,
dri-devel, linux-arm-kernel, linux-kernel
In-Reply-To: <20190919122058.fhpuafogdq7oir2d@core.my.home>
On Thu, Sep 19, 2019 at 02:20:58PM +0200, megous hlavni wrote:
> On Wed, Sep 18, 2019 at 10:16:17PM +0200, Maxime Ripard wrote:
> > On Wed, Sep 18, 2019 at 05:23:09PM +0200, Ondřej Jirman wrote:
> > > Hi,
> > >
> > > On Wed, Sep 18, 2019 at 04:17:34PM +0200, Maxime Ripard wrote:
> > > > Hi,
> > > >
> > > > On Sun, Sep 15, 2019 at 12:03:37AM +0200, megous@megous.com wrote:
> > > > > From: Ondrej Jirman <megous@megous.com>
> > > > >
> > > > > There are various issues that this re-work of sun8i_[uv]i_layer_enable
> > > > > function fixes:
> > > > >
> > > > > - Make sure that we re-initialize zpos on reset
> > > > > - Minimize register updates by doing them only when state changes
> > > > > - Fix issue where DE pipe might get disabled even if it is no longer
> > > > > used by the layer that's currently calling sun8i_ui_layer_enable
> > > > > - .atomic_disable callback is not really needed because .atomic_update
> > > > > can do the disable too, so drop the duplicate code
> > > > >
> > > > > Signed-off-by: Ondrej Jirman <megous@megous.com>
> > > >
> > > > It looks like these fixes should be in separate patches. Is there any
> > > > reason it's not the case?
> > >
> > > Bullet points just describe the resulting effect/benefits of the change to fix
> > > the pipe control register update issue (see the referenced e-mail).
> >
> > It's definitely ok to have multiple patches needed to address a single
> > perceived issue.
>
> Yes, but I can't simply split the patch. In order for each change to work on its
> own, they'd have to be done differently than the final result.
>
> I wouldn't mind at all if it was just a simple splitting, but you're asking
> for too much work, this time, for no benefit that I can see.
>
> > A commit is not about what you're fixing but what you're changing. And
> > the fact that you have tha bullet list in the first place proves that
> > you have multiple logical changes in your patch.
> >
> > And even then, your commit log mentions that you're fixing multiple
> > issues (without explaining them).
>
> I can reword the commit message if that helps, and skip the bullet list if it
> is confusing. There's a single core issue and that is that the driver doesn't
> update the pipe/channel configuration correctly leading to disabling of
> arbitrary layers (not even those being updated - update of UI layer may disable
> VI layer as a side effect for example) at wrong times. And only changes
> necessary to debug/fix this are included.
How about this:
A problem was found where identical configuration of planes leads
to different register settings at the HW layer when using a X server
with modesetting driver and one plane marked as a cursor.
On first run of the X server, only the black screen and the layer
containing the cursor is visible. Switching to console and back
corrects the situation.
I have dumped registers, and found out this:
(In both cases there are two enabled planes, plane 1 with zpos 0 and
plane 3 with zpos 1).
1) First Xorg run:
0x01101000 : 00000201
0x01101080 : 00000030
BLD_FILL_COLOR_CTL: (aka SUN8I_MIXER_BLEND_PIPE_CTL)
P1_EN
BLD_CH_RTCTL: (aka SUN8I_MIXER_BLEND_ROUTE)
P0_RTCTL channel0
P1_RTCTL channel3
2) After switch to console and back to Xorg:
0x01101000 : 00000301
0x01101080 : 00000031
BLD_FILL_COLOR_CTL:
P1_EN and P0_EN
BLD_CH_RTCTL:
P0_RTCTL channel1
P1_RTCTL channel3
What happens is that sun8i_ui_layer_enable() function may disable
blending pipes even if it is no longer assigned to its layer, because
of incorrect state/zpos tracking in the driver.
In particular, layer 1 is configured to zpos 0 and thus uses pipe 0.
When layer 3 is enabled by X server, sun8i_ui_layer_enable() will get
called with old_zpos=0 zpos=1, which will lead to disabling of pipe 0.
In general this issue can happen to any layer during enable or zpos
changes on multiple layers at once.
To correct this we now pass previous enabled/disabled state of the
layer, and pass real previous zpos of the layer to
sun8i_ui_layer_enable() and rework the sun8i_ui_layer_enable() function
to react to the state changes correctly. In order to not complicate
the atomic_disable callback with all of the above changes, we simply
remove it and implement all the chanes as part of atomic_update, which
also reduces the code duplication.
To make this all work, initial zpos positions of all layers need to be
restored to initial values on reset.
> I may try generating a nicer patch with a different diff options, if it makes it
> more readable for review.
>
> > > I can maybe split off the first bullet point into a separate patch. But
> > > I can't guarantee it will not make the original issue worse, because it might
> > > have been hiding the other issue with register updates.
> > >
> > > The rest is just a result of the single logical change. It doesn't work
> > > individually, it all has the goal of fixing the issue as a whole.
> > >
> > > If I were to split it I would have to actually re-implement .atomic_disable
> > > callback only to remove it in the next patch. I don't see the benefit.
> >
> > Your commit log says that you remove atomic_disable. Why would you
> > remove it, to add it back, to remove it again?
>
> Because if I remove it I need to re-implement the functionality in the update
> callback. The core will change what is called based on presence of callbacks.
> It's not a simple removal.
>
> If I first implement the new sun8i_[uv]i_layer_enable and update callback,
> keeping a disable callback would not work, because the new update callback
> will only work if disable callback is not defined (because it it is, then
> the drm core will not call the update callback in all cases that I need).
>
> regards,
> o.
>
> > Maxime
>
>
>
> > _______________________________________________
> > linux-arm-kernel mailing list
> > linux-arm-kernel@lists.infradead.org
> > http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: arm64 iommu groups issue
From: Robin Murphy @ 2019-09-19 13:25 UTC (permalink / raw)
To: John Garry, Marc Zyngier, Will Deacon, Lorenzo Pieralisi,
Sudeep Holla, Guohanjun (Hanjun Guo)
Cc: linux-kernel@vger.kernel.org, Alex Williamson, Shameer Kolothum,
Linuxarm, iommu, Bjorn Helgaas,
linux-arm-kernel@lists.infradead.org
In-Reply-To: <9625faf4-48ef-2dd3-d82f-931d9cf26976@huawei.com>
Hi John,
On 19/09/2019 09:43, John Garry wrote:
> Hi all,
>
> We have noticed a special behaviour on our arm64 D05 board when the SMMU
> is enabled with regards PCI device iommu groups.
>
> This platform does not support ACS, yet we find that all functions for a
> PCI device are not grouped together:
>
> root@ubuntu:/sys# dmesg | grep "Adding to iommu group"
> [ 7.307539] hisi_sas_v2_hw HISI0162:01: Adding to iommu group 0
> [ 12.590533] hns_dsaf HISI00B2:00: Adding to iommu group 1
> [ 13.688527] mlx5_core 000a:11:00.0: Adding to iommu group 2
> [ 14.324606] mlx5_core 000a:11:00.1: Adding to iommu group 3
> [ 14.937090] ehci-platform PNP0D20:00: Adding to iommu group 4
> [ 15.276637] pcieport 0002:f8:00.0: Adding to iommu group 5
> [ 15.340845] pcieport 0004:88:00.0: Adding to iommu group 6
> [ 15.392098] pcieport 0005:78:00.0: Adding to iommu group 7
> [ 15.443356] pcieport 000a:10:00.0: Adding to iommu group 8
> [ 15.484975] pcieport 000c:20:00.0: Adding to iommu group 9
> [ 15.543647] pcieport 000d:30:00.0: Adding to iommu group 10
> [ 15.599771] serial 0002:f9:00.0: Adding to iommu group 5
> [ 15.690807] serial 0002:f9:00.1: Adding to iommu group 5
> [ 84.322097] mlx5_core 000a:11:00.2: Adding to iommu group 8
> [ 84.856408] mlx5_core 000a:11:00.3: Adding to iommu group 8
>
> root@ubuntu:/sys# lspci -tv
> lspci -tvv
> -+-[000d:30]---00.0-[31]--
> +-[000c:20]---00.0-[21]----00.0 Huawei Technologies Co., Ltd.
> +-[000a:10]---00.0-[11-12]--+-00.0 Mellanox [ConnectX-5]
> | +-00.1 Mellanox [ConnectX-5]
> | +-00.2 Mellanox [ConnectX-5 VF]
> | \-00.3 Mellanox [ConnectX-5 VF]
> +-[0007:90]---00.0-[91]----00.0 Huawei Technologies Co., ...
> +-[0006:c0]---00.0-[c1]--
> +-[0005:78]---00.0-[79]--
> +-[0004:88]---00.0-[89]--
> +-[0002:f8]---00.0-[f9]--+-00.0 MosChip Semiconductor Technology ...
> | +-00.1 MosChip Semiconductor Technology ...
> | \-00.2 MosChip Semiconductor Technology ...
> \-[0000:00]-
>
> For the PCI devices in question - on port 000a:10:00.0 - you will notice
> that the port and VFs (000a:11:00.2, 3) are in one group, yet the 2 PFs
> (000a:11:00.0, 000a:11:00.1) are in separate groups.
>
> I also notice the same ordering nature on our D06 platform - the
> pcieport is added to an iommu group after PF for that port. However this
> platform supports ACS, so not such a problem.
>
> After some checking, I find that when the pcieport driver probes, the
> associated SMMU device had not registered yet with the IOMMU framework,
> so we defer the probe for this device - in iort.c:iort_iommu_xlate(),
> when no iommu ops are available, we defer.
>
> Yet, when the mlx5 PF devices probe, the iommu ops are available at this
> stage. So the probe continues and we get an iommu group for the device -
> but not the same group as the parent port, as it has not yet been added
> to a group. When the port eventually probes it gets a new, separate group.
>
> This all seems to be as the built-in module init ordering is as follows:
> pcieport drv, smmu drv, mlx5 drv
>
> I notice that if I build the mlx5 drv as a ko and insert after boot, all
> functions + pcieport are in the same group:
>
> [ 11.530046] hisi_sas_v2_hw HISI0162:01: Adding to iommu group 0
> [ 17.301093] hns_dsaf HISI00B2:00: Adding to iommu group 1
> [ 18.743600] ehci-platform PNP0D20:00: Adding to iommu group 2
> [ 20.212284] pcieport 0002:f8:00.0: Adding to iommu group 3
> [ 20.356303] pcieport 0004:88:00.0: Adding to iommu group 4
> [ 20.493337] pcieport 0005:78:00.0: Adding to iommu group 5
> [ 20.702999] pcieport 000a:10:00.0: Adding to iommu group 6
> [ 20.859183] pcieport 000c:20:00.0: Adding to iommu group 7
> [ 20.996140] pcieport 000d:30:00.0: Adding to iommu group 8
> [ 21.152637] serial 0002:f9:00.0: Adding to iommu group 3
> [ 21.346991] serial 0002:f9:00.1: Adding to iommu group 3
> [ 100.754306] mlx5_core 000a:11:00.0: Adding to iommu group 6
> [ 101.420156] mlx5_core 000a:11:00.1: Adding to iommu group 6
> [ 292.481714] mlx5_core 000a:11:00.2: Adding to iommu group 6
> [ 293.281061] mlx5_core 000a:11:00.3: Adding to iommu group 6
>
> This does seem like a problem for arm64 platforms which don't support
> ACS, yet enable an SMMU. Maybe also a problem even if they do support ACS.
>
> Opinion?
Yeah, this is less than ideal. One way to bodge it might be to make
pci_device_group() also walk downwards to see if any non-ACS-isolated
children already have a group, rather than assuming that groups get
allocated in hierarchical order, but that's far from ideal.
The underlying issue is that, for historical reasons, OF/IORT-based
IOMMU drivers have ended up with group allocation being tied to endpoint
driver probing via the dma_configure() mechanism (long story short,
driver probe is the only thing which can be delayed in order to wait for
a specific IOMMU instance to be ready). However, in the meantime, the
IOMMU API internals have evolved sufficiently that I think there's a way
to really put things right - I have the spark of an idea which I'll try
to sketch out ASAP...
Robin.
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [alsa-devel] [PATCH v1 8/9] ASoC: samsung: arndale: Add missing OF node dereferencing
From: Sylwester Nawrocki @ 2019-09-19 13:31 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: devicetree, alsa-devel, linux-samsung-soc@vger.kernel.org,
ckeepax, Bartłomiej Żołnierkiewicz, sbkim73,
patches, broonie, lgirdwood, robh+dt, linux-arm-kernel,
Marek Szyprowski
In-Reply-To: <CAJKOXPfwAx7vWSWS0M1JuGJvn6tKTp9yaT2qEOwmAWTUdvoptQ@mail.gmail.com>
On 9/19/19 14:58, Krzysztof Kozlowski wrote:
> On Thu, 19 Sep 2019 at 14:49, Sylwester Nawrocki <s.nawrocki@samsung.com> wrote:
>> On 9/19/19 10:22, Krzysztof Kozlowski wrote:
>>> Wasn't this issue introduced in 5/9? It looks weird to fix it here...
>> It has not been introduced by 5/9, the issue was there already before
>> my patch, there was even no remove() callback where OF node references
>> could be dropped.
>
> I see. Then please put it as first patch. You should not mix bugfixes
> with features. If mixing, be sure they can be applied before the
> features.
I will see if it is worth the effort to rebase this patch. I didn't bother
with that because this sound card driver is not used in mainline (there is
no related dts changes) and the patch is a fix for minor bug which I found
just before posting first version of the patch series.
--
Thanks,
Sylwester
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v2] of: restore old handling of cells_name=NULL in of_*_phandle_with_args()
From: Rob Herring @ 2019-09-19 13:32 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: devicetree@vger.kernel.org, Geert Uytterhoeven, Will Deacon,
Joerg Roedel, linux-kernel@vger.kernel.org,
linux-mediatek@lists.infradead.org, kernel@pengutronix.de,
Matthias Brugger, Geert Uytterhoeven, Marek Szyprowski,
Frank Rowand, Peter Rosin, linux-arm-kernel@lists.infradead.org,
Robin Murphy
In-Reply-To: <20190918084748.hnjkiq7wc5b35wjh@pengutronix.de>
On Wed, 18 Sep 2019 10:47:48 +0200, Uwe =?iso-8859-1?Q?Kleine-K=F6nig?= wrote:
> Before commit e42ee61017f5 ("of: Let of_for_each_phandle fallback to
> non-negative cell_count") the iterator functions calling
> of_for_each_phandle assumed a cell count of 0 if cells_name was NULL.
> This corner case was missed when implementing the fallback logic in
> e42ee61017f5 and resulted in an endless loop.
>
> Restore the old behaviour of of_count_phandle_with_args() and
> of_parse_phandle_with_args() and add a check to
> of_phandle_iterator_init() to prevent a similar failure as a safety
> precaution. of_parse_phandle_with_args_map() doesn't need a similar fix
> as cells_name isn't NULL there.
>
> Affected drivers are:
> - drivers/base/power/domain.c
> - drivers/base/power/domain.c
> - drivers/clk/ti/clk-dra7-atl.c
> - drivers/hwmon/ibmpowernv.c
> - drivers/i2c/muxes/i2c-demux-pinctrl.c
> - drivers/iommu/mtk_iommu.c
> - drivers/net/ethernet/freescale/fman/mac.c
> - drivers/opp/of.c
> - drivers/perf/arm_dsu_pmu.c
> - drivers/regulator/of_regulator.c
> - drivers/remoteproc/imx_rproc.c
> - drivers/soc/rockchip/pm_domains.c
> - sound/soc/fsl/imx-audmix.c
> - sound/soc/fsl/imx-audmix.c
> - sound/soc/meson/axg-card.c
> - sound/soc/samsung/tm2_wm5110.c
> - sound/soc/samsung/tm2_wm5110.c
>
> Thanks to Geert Uytterhoeven for reporting the issue, Peter Rosin for
> helping pinpoint the actual problem and the testers for confirming this
> fix.
>
> Fixes: e42ee61017f5 ("of: Let of_for_each_phandle fallback to non-negative cell_count")
> Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> ---
>
> On Wed, Sep 18, 2019 at 08:01:05AM +0000, Peter Rosin wrote:
> > On 2019-09-18 08:38, Uwe Kleine-König wrote:
> > > EXPORT_SYMBOL(of_parse_phandle_with_args);
> > >
> > > @@ -1765,6 +1779,18 @@ int of_count_phandle_with_args(const struct device_node *np, const char *list_na
> > > struct of_phandle_iterator it;
> > > int rc, cur_index = 0;
> > >
> > > + /* If cells_name is NULL we assume a cell count of 0 */
> > > + if (cells_name == NULL) {
> >
> > A couple of nits.
> >
> > I don't know if there are other considerations, but in the previous two
> > hunks you use !cells_name instead of comparing explicitly with NULL.
> > Personally, I find the shorter form more readable, and in the name of
> > consistency bla bla...
>
> Ack, changed to !cells_name here, too.
>
> >
> > Also, the comment explaining this NULL-check didn't really make sense
> > to me until I realized that knowing the cell count to be zero makes
> > counting trivial. Something along those lines should perhaps be in the
> > comment?
>
> You're right, I extended the comment a bit.
>
> > But as I said, these are nits. Feel free to ignore.
>
> I considered resending already anyhow as I fatfingerd my email address.
> this is fixed now, too. Additionally I fixed a typo in one of the
> comments.
>
> Thanks for your feedback.
>
> Best regards
> Uwe
>
> drivers/of/base.c | 35 +++++++++++++++++++++++++++++++++--
> 1 file changed, 33 insertions(+), 2 deletions(-)
>
Applied, thanks.
Rob
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH RFC 11/14] arm64: Move the ASID allocator code in a separate file
From: Guo Ren @ 2019-09-19 13:36 UTC (permalink / raw)
To: Anup Patel
Cc: julien.thierry@arm.com, catalin.marinas@arm.com, Palmer Dabbelt,
Will Deacon, christoffer.dall@arm.com, Atish Patra,
julien.grall@arm.com, gary@garyguo.net,
linux-riscv@lists.infradead.org, Will Deacon,
kvmarm@lists.cs.columbia.edu, rppt@linux.ibm.com,
Christoph Hellwig, aou@eecs.berkeley.edu, Arnd Bergmann,
suzuki.poulose@arm.com, marc.zyngier@arm.com, Paul Walmsley,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, iommu@lists.linux-foundation.org,
james.morse@arm.com
In-Reply-To: <MN2PR04MB60612846CD50ED157DE5AB548D8F0@MN2PR04MB6061.namprd04.prod.outlook.com>
Hi,
On Tue, Sep 17, 2019 at 11:42 AM Anup Patel <Anup.Patel@wdc.com> wrote:
> >
> > With a reply stating that the patch "absolutely does not work" ;)
>
> This patch was tested on existing HW (which does not have ASID implementation)
> and tested on QEMU (which has very simplistic Implementation of ASID).
>
> When I asked Gary Guo about way to get access to their HW (in same patch
> email thread), I did not get any reply. After so many months passed, I now
> doubt the his comment "absolutely does not work".
> >
> > What exactly do you want people to do with that? It's an awful lot of effort to
> > review this sort of stuff and given that Guo Ren is talking about sharing page
> > tables between the CPU and an accelerator, maybe you're better off
> > stabilising Linux for the platforms that you can actually test rather than
> > getting so far ahead of yourselves that you end up with a bunch of wasted
> > work on patches that probably won't get merged any time soon.
>
> The intention of the ASID patch was to encourage RISC-V implementations
> having ASID in HW and also ensure that things don't break on existing HW.
>
> I don't see our efforts being wasted in trying to make Linux RISC-V feature
> complete and encouraging more feature rich RISC-V CPUs.
>
> Delays in merging patches are fine as long as people have something to try
> on their RISC-V CPU implementations.
>
I'm the supporter of that patch:
http://archive.lwn.net:8080/linux-kernel/20190329045111.14040-1-anup.patel@wdc.com/T/#u
Because it implicit hw broadcast tlb invalidation optimization.
Honestly it's not suitable for remote tlb flush with software IPI, but
it's still much better than current RISC-V's.
I'll try it on our hardware: 910. wait a moment :)
--
Best Regards
Guo Ren
ML: https://lore.kernel.org/linux-csky/
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH v7 0/4] Add HDMI jack support on RK3288
From: Cheng-Yi Chiang @ 2019-09-19 13:54 UTC (permalink / raw)
To: linux-kernel
Cc: alsa-devel, Heiko Stuebner, Neil Armstrong, David Airlie,
dri-devel, Liam Girdwood, Hans Verkuil, Andrzej Hajda,
Laurent Pinchart, Jerome Brunet, Takashi Iwai, linux-rockchip,
dgreid, Cheng-Yi Chiang, tzungbi, Jonas Karlman, Russell King,
Mark Brown, Jaroslav Kysela, linux-arm-kernel, Jernej Skrabec,
dianders, Daniel Vetter
This patch series supports HDMI jack reporting on RK3288, which uses
DRM dw-hdmi driver and hdmi-codec codec driver.
The previous discussion about reporting jack status using hdmi-notifier
and drm_audio_component is at
https://lore.kernel.org/patchwork/patch/1083027/
The new approach is to use a callback mechanism that is
specific to hdmi-codec.
The dependent change on hdmi-codec.c
https://patchwork.kernel.org/patch/11047447
has been picked up by Mark Brown in ASoC tree for-5.4 branch.
Changes from v6 to v7:
1. rockchip_max98090: Use phandle of HDMI from DTS to find
codec_dai. With this we don't need to set fixed id for the
created hdmi-audio-codec device.
Cheng-Yi Chiang (4):
drm: bridge: dw-hdmi: Report connector status using callback
ASoC: rockchip-max98090: Add description for rockchip,hdmi-codec
ASoC: rockchip_max98090: Add dai_link for HDMI
ASoC: rockchip_max98090: Add HDMI jack support
.../bindings/sound/rockchip-max98090.txt | 2 +
.../boot/dts/rk3288-veyron-analog-audio.dtsi | 1 +
.../drm/bridge/synopsys/dw-hdmi-i2s-audio.c | 11 ++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 41 ++++-
include/drm/bridge/dw_hdmi.h | 4 +
sound/soc/rockchip/Kconfig | 3 +-
sound/soc/rockchip/rockchip_max98090.c | 149 ++++++++++++++----
7 files changed, 182 insertions(+), 29 deletions(-)
--
2.23.0.237.gc6a4ce50a0-goog
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH v7 1/4] drm: bridge: dw-hdmi: Report connector status using callback
From: Cheng-Yi Chiang @ 2019-09-19 13:54 UTC (permalink / raw)
To: linux-kernel
Cc: alsa-devel, Heiko Stuebner, Neil Armstrong, David Airlie,
dri-devel, Liam Girdwood, Hans Verkuil, Andrzej Hajda,
Laurent Pinchart, Jerome Brunet, Takashi Iwai, linux-rockchip,
dgreid, Cheng-Yi Chiang, tzungbi, Jonas Karlman, Russell King,
Mark Brown, Jaroslav Kysela, linux-arm-kernel, Jernej Skrabec,
dianders, Daniel Vetter
In-Reply-To: <20190919135450.62309-1-cychiang@chromium.org>
Allow codec driver register callback function for plug event.
The callback registration flow:
dw-hdmi <--- hw-hdmi-i2s-audio <--- hdmi-codec
dw-hdmi-i2s-audio implements hook_plugged_cb op
so codec driver can register the callback.
dw-hdmi exports a function dw_hdmi_set_plugged_cb so platform device
can register the callback.
When connector plug/unplug event happens, report this event using the
callback.
Make sure that audio and drm are using the single source of truth for
connector status.
Signed-off-by: Cheng-Yi Chiang <cychiang@chromium.org>
---
.../drm/bridge/synopsys/dw-hdmi-i2s-audio.c | 11 +++++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 41 ++++++++++++++++++-
include/drm/bridge/dw_hdmi.h | 4 ++
3 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
index 20f4f92dd866..d7e65c869415 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
@@ -160,12 +160,23 @@ static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
return -EINVAL;
}
+static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ struct dw_hdmi_i2s_audio_data *audio = data;
+ struct dw_hdmi *hdmi = audio->hdmi;
+
+ return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev);
+}
+
static struct hdmi_codec_ops dw_hdmi_i2s_ops = {
.hw_params = dw_hdmi_i2s_hw_params,
.audio_startup = dw_hdmi_i2s_audio_startup,
.audio_shutdown = dw_hdmi_i2s_audio_shutdown,
.get_eld = dw_hdmi_i2s_get_eld,
.get_dai_id = dw_hdmi_i2s_get_dai_id,
+ .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb,
};
static int snd_dw_hdmi_probe(struct platform_device *pdev)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index aa7efd4da1c8..7ffe8ed675ff 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -193,6 +193,10 @@ struct dw_hdmi {
struct mutex cec_notifier_mutex;
struct cec_notifier *cec_notifier;
+
+ hdmi_codec_plugged_cb plugged_cb;
+ struct device *codec_dev;
+ enum drm_connector_status last_connector_result;
};
#define HDMI_IH_PHY_STAT0_RX_SENSE \
@@ -217,6 +221,28 @@ static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset)
return val;
}
+static void handle_plugged_change(struct dw_hdmi *hdmi, bool plugged)
+{
+ if (hdmi->plugged_cb && hdmi->codec_dev)
+ hdmi->plugged_cb(hdmi->codec_dev, plugged);
+}
+
+int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ bool plugged;
+
+ mutex_lock(&hdmi->mutex);
+ hdmi->plugged_cb = fn;
+ hdmi->codec_dev = codec_dev;
+ plugged = hdmi->last_connector_result == connector_status_connected;
+ handle_plugged_change(hdmi, plugged);
+ mutex_unlock(&hdmi->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_plugged_cb);
+
static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg)
{
regmap_update_bits(hdmi->regm, reg << hdmi->reg_shift, mask, data);
@@ -2183,6 +2209,7 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
+ enum drm_connector_status result;
mutex_lock(&hdmi->mutex);
hdmi->force = DRM_FORCE_UNSPECIFIED;
@@ -2190,7 +2217,18 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
dw_hdmi_update_phy_mask(hdmi);
mutex_unlock(&hdmi->mutex);
- return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
+ result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
+
+ mutex_lock(&hdmi->mutex);
+ if (result != hdmi->last_connector_result) {
+ dev_dbg(hdmi->dev, "read_hpd result: %d", result);
+ handle_plugged_change(hdmi,
+ result == connector_status_connected);
+ hdmi->last_connector_result = result;
+ }
+ mutex_unlock(&hdmi->mutex);
+
+ return result;
}
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
@@ -2641,6 +2679,7 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->rxsense = true;
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
hdmi->mc_clkdis = 0x7f;
+ hdmi->last_connector_result = connector_status_disconnected;
mutex_init(&hdmi->mutex);
mutex_init(&hdmi->audio_mutex);
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index 4b3e863c4f8a..45a05e97e78a 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -6,6 +6,8 @@
#ifndef __DW_HDMI__
#define __DW_HDMI__
+#include <sound/hdmi-codec.h>
+
struct drm_connector;
struct drm_display_mode;
struct drm_encoder;
@@ -154,6 +156,8 @@ void dw_hdmi_resume(struct dw_hdmi *hdmi);
void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
+int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
+ struct device *codec_dev);
void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate);
void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt);
void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi, u8 *channel_status);
--
2.23.0.237.gc6a4ce50a0-goog
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v7 2/4] ASoC: rockchip-max98090: Add description for rockchip, hdmi-codec
From: Cheng-Yi Chiang @ 2019-09-19 13:54 UTC (permalink / raw)
To: linux-kernel
Cc: alsa-devel, Heiko Stuebner, Neil Armstrong, David Airlie,
dri-devel, Liam Girdwood, Hans Verkuil, Andrzej Hajda,
Laurent Pinchart, Jerome Brunet, Takashi Iwai, linux-rockchip,
dgreid, Cheng-Yi Chiang, tzungbi, Jonas Karlman, Russell King,
Mark Brown, Jaroslav Kysela, linux-arm-kernel, Jernej Skrabec,
dianders, Daniel Vetter
In-Reply-To: <20190919135450.62309-1-cychiang@chromium.org>
Add support for HDMI codec in rockchip-max98090.
Let user specify HDMI device node in DTS so machine driver can find
hdmi-codec device node for codec DAI.
Signed-off-by: Cheng-Yi Chiang <cychiang@chromium.org>
---
Documentation/devicetree/bindings/sound/rockchip-max98090.txt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/rockchip-max98090.txt b/Documentation/devicetree/bindings/sound/rockchip-max98090.txt
index a805aa99ad75..97fc838458f7 100644
--- a/Documentation/devicetree/bindings/sound/rockchip-max98090.txt
+++ b/Documentation/devicetree/bindings/sound/rockchip-max98090.txt
@@ -7,6 +7,7 @@ Required properties:
connected to the CODEC
- rockchip,audio-codec: The phandle of the MAX98090 audio codec
- rockchip,headset-codec: The phandle of Ext chip for jack detection
+- rockchip,hdmi-codec: The phandle of HDMI device for HDMI codec.
Example:
@@ -16,4 +17,5 @@ sound {
rockchip,i2s-controller = <&i2s>;
rockchip,audio-codec = <&max98090>;
rockchip,headset-codec = <&headsetcodec>;
+ rockchip,hdmi-codec = <&hdmi>;
};
--
2.23.0.237.gc6a4ce50a0-goog
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v7 3/4] ASoC: rockchip_max98090: Add dai_link for HDMI
From: Cheng-Yi Chiang @ 2019-09-19 13:54 UTC (permalink / raw)
To: linux-kernel
Cc: alsa-devel, Heiko Stuebner, Neil Armstrong, David Airlie,
dri-devel, Liam Girdwood, Hans Verkuil, Andrzej Hajda,
Laurent Pinchart, Jerome Brunet, Takashi Iwai, linux-rockchip,
dgreid, Cheng-Yi Chiang, tzungbi, Jonas Karlman, Russell King,
Mark Brown, Jaroslav Kysela, linux-arm-kernel, Jernej Skrabec,
dianders, Daniel Vetter
In-Reply-To: <20190919135450.62309-1-cychiang@chromium.org>
Use two dai_links. One for HDMI and one for max98090.
With this setup, audio can play to speaker and HDMI selectively.
Signed-off-by: Cheng-Yi Chiang <cychiang@chromium.org>
---
.../boot/dts/rk3288-veyron-analog-audio.dtsi | 1 +
sound/soc/rockchip/rockchip_max98090.c | 129 ++++++++++++++----
2 files changed, 103 insertions(+), 27 deletions(-)
diff --git a/arch/arm/boot/dts/rk3288-veyron-analog-audio.dtsi b/arch/arm/boot/dts/rk3288-veyron-analog-audio.dtsi
index 445270aa136e..51208d161d65 100644
--- a/arch/arm/boot/dts/rk3288-veyron-analog-audio.dtsi
+++ b/arch/arm/boot/dts/rk3288-veyron-analog-audio.dtsi
@@ -17,6 +17,7 @@
rockchip,hp-det-gpios = <&gpio6 RK_PA5 GPIO_ACTIVE_HIGH>;
rockchip,mic-det-gpios = <&gpio6 RK_PB3 GPIO_ACTIVE_LOW>;
rockchip,headset-codec = <&headsetcodec>;
+ rockchip,hdmi-codec = <&hdmi>;
};
};
diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c
index c5fc24675a33..6c217492bb30 100644
--- a/sound/soc/rockchip/rockchip_max98090.c
+++ b/sound/soc/rockchip/rockchip_max98090.c
@@ -11,6 +11,7 @@
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <sound/core.h>
+#include <sound/hdmi-codec.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -41,6 +42,7 @@ static const struct snd_soc_dapm_widget rk_dapm_widgets[] = {
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("Int Mic", NULL),
SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_LINE("HDMI", NULL),
};
static const struct snd_soc_dapm_route rk_audio_map[] = {
@@ -52,6 +54,7 @@ static const struct snd_soc_dapm_route rk_audio_map[] = {
{"Headphone", NULL, "HPR"},
{"Speaker", NULL, "SPKL"},
{"Speaker", NULL, "SPKR"},
+ {"HDMI", NULL, "TX"},
};
static const struct snd_kcontrol_new rk_mc_controls[] = {
@@ -59,6 +62,7 @@ static const struct snd_kcontrol_new rk_mc_controls[] = {
SOC_DAPM_PIN_SWITCH("Headset Mic"),
SOC_DAPM_PIN_SWITCH("Int Mic"),
SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("HDMI"),
};
static int rk_aif1_hw_params(struct snd_pcm_substream *substream,
@@ -92,38 +96,63 @@ static int rk_aif1_hw_params(struct snd_pcm_substream *substream,
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
SND_SOC_CLOCK_OUT);
- if (ret < 0) {
- dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret);
+ if (ret) {
+ dev_err(cpu_dai->dev, "Can't set cpu dai clock %d\n", ret);
return ret;
}
+ /* HDMI codec dai does not need to set sysclk. */
+ if (!strcmp(rtd->dai_link->name, "HDMI"))
+ return 0;
+
ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
SND_SOC_CLOCK_IN);
- if (ret < 0) {
- dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret);
+ if (ret) {
+ dev_err(codec_dai->dev, "Can't set codec dai clock %d\n", ret);
return ret;
}
- return ret;
+ return 0;
}
static const struct snd_soc_ops rk_aif1_ops = {
.hw_params = rk_aif1_hw_params,
};
-SND_SOC_DAILINK_DEFS(hifi,
- DAILINK_COMP_ARRAY(COMP_EMPTY()),
- DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")),
- DAILINK_COMP_ARRAY(COMP_EMPTY()));
-
-static struct snd_soc_dai_link rk_dailink = {
- .name = "max98090",
- .stream_name = "Audio",
- .ops = &rk_aif1_ops,
- /* set max98090 as slave */
- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBS_CFS,
- SND_SOC_DAILINK_REG(hifi),
+SND_SOC_DAILINK_DEFS(analog,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(hdmi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "i2s-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+enum {
+ DAILINK_MAX98090,
+ DAILINK_HDMI,
+};
+
+/* max98090 and HDMI codec dai_link */
+static struct snd_soc_dai_link rk_dailinks[] = {
+ [DAILINK_MAX98090] = {
+ .name = "max98090",
+ .stream_name = "Analog",
+ .ops = &rk_aif1_ops,
+ /* set max98090 as slave */
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(analog),
+ },
+ [DAILINK_HDMI] = {
+ .name = "HDMI",
+ .stream_name = "HDMI",
+ .ops = &rk_aif1_ops,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(hdmi),
+ }
};
static int rk_98090_headset_init(struct snd_soc_component *component);
@@ -136,8 +165,8 @@ static struct snd_soc_aux_dev rk_98090_headset_dev = {
static struct snd_soc_card snd_soc_card_rk = {
.name = "ROCKCHIP-I2S",
.owner = THIS_MODULE,
- .dai_link = &rk_dailink,
- .num_links = 1,
+ .dai_link = rk_dailinks,
+ .num_links = ARRAY_SIZE(rk_dailinks),
.aux_dev = &rk_98090_headset_dev,
.num_aux_devs = 1,
.dapm_widgets = rk_dapm_widgets,
@@ -173,27 +202,73 @@ static int snd_rk_mc_probe(struct platform_device *pdev)
int ret = 0;
struct snd_soc_card *card = &snd_soc_card_rk;
struct device_node *np = pdev->dev.of_node;
+ struct device_node *np_analog;
+ struct device_node *np_cpu;
+ struct device_node *np_hdmi_codec;
+ struct of_phandle_args args;
/* register the soc card */
card->dev = &pdev->dev;
- rk_dailink.codecs->of_node = of_parse_phandle(np,
- "rockchip,audio-codec", 0);
- if (!rk_dailink.codecs->of_node) {
+ np_analog = of_parse_phandle(np, "rockchip,audio-codec", 0);
+ if (!np_analog) {
dev_err(&pdev->dev,
"Property 'rockchip,audio-codec' missing or invalid\n");
return -EINVAL;
}
+ rk_dailinks[DAILINK_MAX98090].codecs->of_node = np_analog;
+
+ ret = of_parse_phandle_with_fixed_args(np, "rockchip,audio-codec",
+ 0, 0, &args);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Unable to parse property 'rockchip,audio-codec'\n");
+ return ret;
+ }
+
+ ret = snd_soc_get_dai_name(
+ &args, &rk_dailinks[DAILINK_MAX98090].codecs->dai_name);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to get codec dai_name\n");
+ return ret;
+ }
+
+ np_cpu = of_parse_phandle(np, "rockchip,i2s-controller", 0);
- rk_dailink.cpus->of_node = of_parse_phandle(np,
- "rockchip,i2s-controller", 0);
- if (!rk_dailink.cpus->of_node) {
+ if (!np_cpu) {
dev_err(&pdev->dev,
"Property 'rockchip,i2s-controller' missing or invalid\n");
return -EINVAL;
}
- rk_dailink.platforms->of_node = rk_dailink.cpus->of_node;
+ np_hdmi_codec = of_parse_phandle(np, "rockchip,hdmi-codec", 0);
+ if (!np_hdmi_codec) {
+ dev_err(&pdev->dev,
+ "Property 'rockchip,hdmi-codec' missing or invalid\n");
+ return -EINVAL;
+ }
+
+ rk_dailinks[DAILINK_HDMI].codecs->of_node = np_hdmi_codec;
+
+ ret = of_parse_phandle_with_fixed_args(np, "rockchip,hdmi-codec",
+ 0, 0, &args);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Unable to parse property 'rockchip,hdmi-codec'\n");
+ return ret;
+ }
+
+ ret = snd_soc_get_dai_name(
+ &args, &rk_dailinks[DAILINK_HDMI].codecs->dai_name);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to get hdmi codec dai_name\n");
+ return ret;
+ }
+
+ rk_dailinks[DAILINK_MAX98090].cpus->of_node = np_cpu;
+ rk_dailinks[DAILINK_MAX98090].platforms->of_node = np_cpu;
+ rk_dailinks[DAILINK_HDMI].cpus->of_node = np_cpu;
+ rk_dailinks[DAILINK_HDMI].platforms->of_node = np_cpu;
rk_98090_headset_dev.codec_of_node = of_parse_phandle(np,
"rockchip,headset-codec", 0);
--
2.23.0.237.gc6a4ce50a0-goog
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [PATCH v7 4/4] ASoC: rockchip_max98090: Add HDMI jack support
From: Cheng-Yi Chiang @ 2019-09-19 13:54 UTC (permalink / raw)
To: linux-kernel
Cc: alsa-devel, Heiko Stuebner, Neil Armstrong, David Airlie,
dri-devel, Liam Girdwood, Hans Verkuil, Andrzej Hajda,
Laurent Pinchart, Jerome Brunet, Takashi Iwai, linux-rockchip,
dgreid, Cheng-Yi Chiang, tzungbi, Jonas Karlman, Russell King,
Mark Brown, Jaroslav Kysela, linux-arm-kernel, Jernej Skrabec,
dianders, Daniel Vetter
In-Reply-To: <20190919135450.62309-1-cychiang@chromium.org>
In machine driver, create a jack and let hdmi-codec report jack status.
Signed-off-by: Cheng-Yi Chiang <cychiang@chromium.org>
---
sound/soc/rockchip/Kconfig | 3 ++-
sound/soc/rockchip/rockchip_max98090.c | 20 ++++++++++++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig
index b43657e6e655..d610b553ea3b 100644
--- a/sound/soc/rockchip/Kconfig
+++ b/sound/soc/rockchip/Kconfig
@@ -40,9 +40,10 @@ config SND_SOC_ROCKCHIP_MAX98090
select SND_SOC_ROCKCHIP_I2S
select SND_SOC_MAX98090
select SND_SOC_TS3A227E
+ select SND_SOC_HDMI_CODEC
help
Say Y or M here if you want to add support for SoC audio on Rockchip
- boards using the MAX98090 codec, such as Veyron.
+ boards using the MAX98090 codec and HDMI codec, such as Veyron.
config SND_SOC_ROCKCHIP_RT5645
tristate "ASoC support for Rockchip boards using a RT5645/RT5650 codec"
diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c
index 6c217492bb30..11cf252e8391 100644
--- a/sound/soc/rockchip/rockchip_max98090.c
+++ b/sound/soc/rockchip/rockchip_max98090.c
@@ -134,6 +134,25 @@ enum {
DAILINK_HDMI,
};
+static struct snd_soc_jack rk_hdmi_jack;
+
+static int rk_hdmi_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_card *card = runtime->card;
+ struct snd_soc_component *component = runtime->codec_dai->component;
+ int ret;
+
+ /* enable jack detection */
+ ret = snd_soc_card_jack_new(card, "HDMI Jack", SND_JACK_LINEOUT,
+ &rk_hdmi_jack, NULL, 0);
+ if (ret) {
+ dev_err(card->dev, "Can't new HDMI Jack %d\n", ret);
+ return ret;
+ }
+
+ return hdmi_codec_set_jack_detect(component, &rk_hdmi_jack);
+}
+
/* max98090 and HDMI codec dai_link */
static struct snd_soc_dai_link rk_dailinks[] = {
[DAILINK_MAX98090] = {
@@ -151,6 +170,7 @@ static struct snd_soc_dai_link rk_dailinks[] = {
.ops = &rk_aif1_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
+ .init = rk_hdmi_init,
SND_SOC_DAILINK_REG(hdmi),
}
};
--
2.23.0.237.gc6a4ce50a0-goog
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* Re: [PATCH v5 2/2] drm/bridge: Add NWL MIPI DSI host controller support
From: Andrzej Hajda @ 2019-09-19 13:56 UTC (permalink / raw)
To: Guido Günther
Cc: Mark Rutland, devicetree, Jernej Skrabec, Pengutronix Kernel Team,
Sam Ravnborg, Neil Armstrong, David Airlie, Fabio Estevam,
Sascha Hauer, Jonas Karlman, linux-kernel, dri-devel, Rob Herring,
Arnd Bergmann, NXP Linux Team, Daniel Vetter, Robert Chiras,
Lee Jones, Shawn Guo, linux-arm-kernel, Laurent Pinchart
In-Reply-To: <20190914161155.GA2933@bogon.m.sigxcpu.org>
On 14.09.2019 18:11, Guido Günther wrote:
> Hi Andrzej,
> thanks for having a look!
>
> On Fri, Sep 13, 2019 at 11:31:43AM +0200, Andrzej Hajda wrote:
>> On 09.09.2019 04:25, Guido Günther wrote:
>>> This adds initial support for the NWL MIPI DSI Host controller found on
>>> i.MX8 SoCs.
>>>
>>> It adds support for the i.MX8MQ but the same IP can be found on
>>> e.g. the i.MX8QXP.
>>>
>>> It has been tested on the Librem 5 devkit using mxsfb.
>>>
>>> Signed-off-by: Guido Günther <agx@sigxcpu.org>
>>> Co-developed-by: Robert Chiras <robert.chiras@nxp.com>
>>> Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
>>> Tested-by: Robert Chiras <robert.chiras@nxp.com>
>>> ---
>>> drivers/gpu/drm/bridge/Kconfig | 2 +
>>> drivers/gpu/drm/bridge/Makefile | 1 +
>>> drivers/gpu/drm/bridge/nwl-dsi/Kconfig | 16 +
>>> drivers/gpu/drm/bridge/nwl-dsi/Makefile | 4 +
>>> drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c | 499 ++++++++++++++++
>>> drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h | 65 +++
>>> drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c | 696 +++++++++++++++++++++++
>>> drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h | 112 ++++
>>
>> Why do you need separate files nwl-drv.[ch] and nwl-dsi.[ch] ? I guess
>> you can merge all into one file, maybe with separate file for NWL
>> register definitions.
> Idea is to have driver setup, soc specific hooks and revision specific
> quirks in one file and the dsi specific parts in another. If that
> doesn't fly I can merge into one if that's a requirement.
One file looks saner to me :), but more importantly it follows current
practice.
>
>>> 8 files changed, 1395 insertions(+)
>>> create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Kconfig
>>> create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/Makefile
>>> create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
>>> create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
>>> create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
>>> create mode 100644 drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
>>>
>>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
>>> index 1cc9f502c1f2..7980b5c2156f 100644
>>> --- a/drivers/gpu/drm/bridge/Kconfig
>>> +++ b/drivers/gpu/drm/bridge/Kconfig
>>> @@ -154,6 +154,8 @@ source "drivers/gpu/drm/bridge/analogix/Kconfig"
>>>
>>> source "drivers/gpu/drm/bridge/adv7511/Kconfig"
>>>
>>> +source "drivers/gpu/drm/bridge/nwl-dsi/Kconfig"
>>> +
>>> source "drivers/gpu/drm/bridge/synopsys/Kconfig"
>>>
>>> endmenu
>>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
>>> index 4934fcf5a6f8..d9f6c0f77592 100644
>>> --- a/drivers/gpu/drm/bridge/Makefile
>>> +++ b/drivers/gpu/drm/bridge/Makefile
>>> @@ -16,4 +16,5 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
>>> obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
>>> obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
>>> obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
>>> +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi/
>>> obj-y += synopsys/
>>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Kconfig b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
>>> new file mode 100644
>>> index 000000000000..7fa678e3b5e2
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/Kconfig
>>> @@ -0,0 +1,16 @@
>>> +config DRM_NWL_MIPI_DSI
>>> + tristate "Northwest Logic MIPI DSI Host controller"
>>> + depends on DRM
>>> + depends on COMMON_CLK
>>> + depends on OF && HAS_IOMEM
>>> + select DRM_KMS_HELPER
>>> + select DRM_MIPI_DSI
>>> + select DRM_PANEL_BRIDGE
>>> + select GENERIC_PHY_MIPI_DPHY
>>> + select MFD_SYSCON
>>> + select MULTIPLEXER
>>> + select REGMAP_MMIO
>>> + help
>>> + This enables the Northwest Logic MIPI DSI Host controller as
>>> + for example found on NXP's i.MX8 Processors.
>>> +
>>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/Makefile b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
>>> new file mode 100644
>>> index 000000000000..804baf2f1916
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/Makefile
>>> @@ -0,0 +1,4 @@
>>> +# SPDX-License-Identifier: GPL-2.0
>>> +nwl-mipi-dsi-y := nwl-drv.o nwl-dsi.o
>>> +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-mipi-dsi.o
>>> +header-test-y += nwl-drv.h nwl-dsi.h
>>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
>>> new file mode 100644
>>> index 000000000000..9ff43d2de127
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.c
>>> @@ -0,0 +1,499 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/*
>>> + * i.MX8 NWL MIPI DSI host driver
>>> + *
>>> + * Copyright (C) 2017 NXP
>>> + * Copyright (C) 2019 Purism SPC
>>> + */
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/irq.h>
>>> +#include <linux/mfd/syscon.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mux/consumer.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_platform.h>
>>> +#include <linux/phy/phy.h>
>>> +#include <linux/reset.h>
>>> +#include <linux/regmap.h>
>>
>> Alphabetic order
> Fixed for v6.
>
>>> +#include <linux/sys_soc.h>
>>> +
>>> +#include <drm/drm_atomic_helper.h>
>>> +#include <drm/drm_of.h>
>>> +#include <drm/drm_print.h>
>>> +#include <drm/drm_probe_helper.h>
>>> +
>>> +#include "nwl-drv.h"
>>> +#include "nwl-dsi.h"
>>> +
>>> +#define DRV_NAME "nwl-dsi"
>>> +
>>> +/* Possible platform specific clocks */
>>> +#define NWL_DSI_CLK_CORE "core"
>>> +
>>> +static const struct regmap_config nwl_dsi_regmap_config = {
>>> + .reg_bits = 16,
>>> + .val_bits = 32,
>>> + .reg_stride = 4,
>>> + .max_register = NWL_DSI_IRQ_MASK2,
>>> + .name = DRV_NAME,
>>> +};
>>
>> What is the point in using regmap here, why not simple writel/readl.
> For me
>
> cat /sys/kernel/debug/regmap/30a00000.mipi_dsi-imx-nwl-dsi/registers
>
> justifies it's use to help debugging problems when e.g. having it
> connected to panels I don't own, so I think it's worth keeping if
> possible.
It still sounds for me like a sledgehammer to crack a nut, but it seems
for many developers it is OK, so up to you.
>
>>> +
>>> +struct nwl_dsi_platform_data {
>>> + int (*poweron)(struct nwl_dsi *dsi);
>>> + int (*poweroff)(struct nwl_dsi *dsi);
>>> + int (*select_input)(struct nwl_dsi *dsi);
>>> + int (*deselect_input)(struct nwl_dsi *dsi);
>>> + struct nwl_dsi_plat_clk_config clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
>>> +};
>>
>> Another construct which do not have justification, at least for now.
>> Please simplify the driver, remove callbacks/intermediate
>> structs/quirks
>>
>> - for now they are useless.
>>
>> Unless there is a serious reason - in such case please describe it in
>> comments.
> They're needed for i.mx 8QM SoC support (the current driver only
> supports the i.mx 8MQ). It will be relatively easy to add with
> these so I expect these to show up quickly. I'll add a comment.
That does not work well, it is impossible to review such code without
looking at it's usage.
It would be better either to add 8QM driver in the same patchset showing
the value of these callbacks, either remove them and add later together
with 8QM driver.
Maybe these callbacks are not needed at all, but without 8QM code it is
hard to decide.
Btw. naming different SoCs 8QM an 8MQ suggests i.mx engineers are quite
nasty :)
>
> The quirks on the other hand only apply to some i.mx8MQ mask revisions
> so they need to be conditionalized. (or maybe I misunderstood you).
I guess at the moment the driver is tested only on one platform - you
have not tested platforms with different set of quirks, am I correct?
If so, the quirk 'infrastructure' is not really tested, driver just
anticipates other 8MQ SoCs/platforms. Practice shows that it does not
work well - adding support for different revision usually require either
more changes either no changes at all, so this pre-mature
"diversification" serves nothing except unnecessary noise.
>
>>> +
>>> +static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge *bridge)
>>> +{
>>> + return container_of(bridge, struct nwl_dsi, bridge);
>>> +}
>>> +
>>> +static int nwl_dsi_set_platform_clocks(struct nwl_dsi *dsi, bool enable)
>>> +{
>>> + struct device *dev = dsi->dev;
>>> + const char *id;
>>> + struct clk *clk;
>>> + size_t i;
>>> + unsigned long rate;
>>> + int ret, result = 0;
>>> +
>>> + DRM_DEV_DEBUG_DRIVER(dev, "%s platform clocks\n",
>>> + enable ? "enabling" : "disabling");
>>> + for (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
>>> + if (!dsi->clk_config[i].present)
>>> + continue;
>>> + id = dsi->clk_config[i].id;
>>> + clk = dsi->clk_config[i].clk;
>>> +
>>> + if (enable) {
>>> + ret = clk_prepare_enable(clk);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dev,
>>> + "Failed to enable %s clk: %d\n",
>>> + id, ret);
>>> + result = result ?: ret;
>>> + }
>>> + rate = clk_get_rate(clk);
>>> + DRM_DEV_DEBUG_DRIVER(dev, "Enabled %s clk @%lu Hz\n",
>>> + id, rate);
>>> + } else {
>>> + clk_disable_unprepare(clk);
>>> + DRM_DEV_DEBUG_DRIVER(dev, "Disabled %s clk\n", id);
>>> + }
>>> + }
>>> +
>>> + return result;
>>> +}
>>> +
>>> +static int nwl_dsi_plat_enable(struct nwl_dsi *dsi)
>>> +{
>>> + struct device *dev = dsi->dev;
>>> + int ret;
>>> +
>>> + if (dsi->pdata->select_input)
>>> + dsi->pdata->select_input(dsi);
>>> +
>>> + ret = nwl_dsi_set_platform_clocks(dsi, true);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + ret = dsi->pdata->poweron(dsi);
>>> + if (ret < 0)
>>> + DRM_DEV_ERROR(dev, "Failed to power on DSI: %d\n", ret);
>>> + return ret;
>>> +}
>>> +
>>> +static void nwl_dsi_plat_disable(struct nwl_dsi *dsi)
>>> +{
>>> + dsi->pdata->poweroff(dsi);
>>> + nwl_dsi_set_platform_clocks(dsi, false);
>>> + if (dsi->pdata->deselect_input)
>>> + dsi->pdata->deselect_input(dsi);
>>> +}
>>> +
>>> +static void nwl_dsi_bridge_disable(struct drm_bridge *bridge)
>>> +{
>>> + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
>>> +
>>> + nwl_dsi_disable(dsi);
>>> + nwl_dsi_plat_disable(dsi);
>>> + pm_runtime_put(dsi->dev);
>>> +}
>>> +
>>> +static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi,
>>> + const struct drm_display_mode *mode,
>>> + union phy_configure_opts *phy_opts)
>>> +{
>>> + unsigned long rate;
>>> + int ret;
>>> +
>>> + if (dsi->lanes < 1 || dsi->lanes > 4)
>>> + return -EINVAL;
>>> +
>>> + /*
>>> + * So far the DPHY spec minimal timings work for both mixel
>>> + * dphy and nwl dsi host
>>> + */
>>> + ret = phy_mipi_dphy_get_default_config(
>>> + mode->crtc_clock * 1000,
>>> + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes,
>>> + &phy_opts->mipi_dphy);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + rate = clk_get_rate(dsi->tx_esc_clk);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "LP clk is @%lu Hz\n", rate);
>>> + phy_opts->mipi_dphy.lp_clk_rate = rate;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
>>> + const struct drm_display_mode *mode,
>>> + struct drm_display_mode *adjusted_mode)
>>> +{
>>> + /* At least LCDIF + NWL needs active high sync */
>>> + adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
>>> + adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
>>> +
>>> + return true;
>>> +}
>>> +
>>> +static enum drm_mode_status
>>> +nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
>>> + const struct drm_display_mode *mode)
>>> +{
>>> + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
>>> + int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
>>> +
>>> + if (mode->clock * bpp > 15000000 * dsi->lanes)
>>> + return MODE_CLOCK_HIGH;
>>> +
>>> + if (mode->clock * bpp < 80000 * dsi->lanes)
>>> + return MODE_CLOCK_LOW;
>>> +
>>> + return MODE_OK;
>>> +}
>>> +
>>> +static void
>>> +nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
>>> + const struct drm_display_mode *mode,
>>> + const struct drm_display_mode *adjusted_mode)
>>> +{
>>> + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
>>> + struct device *dev = dsi->dev;
>>> + union phy_configure_opts new_cfg;
>>> + unsigned long phy_ref_rate;
>>> + int ret;
>>> +
>>> + ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg);
>>> + if (ret < 0)
>>> + return;
>>> +
>>> + /*
>>> + * If hs clock is unchanged, we're all good - all parameters are
>>> + * derived from it atm.
>>> + */
>>> + if (new_cfg.mipi_dphy.hs_clk_rate == dsi->phy_cfg.mipi_dphy.hs_clk_rate)
>>> + return;
>>> +
>>> + phy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
>>> + DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate);
>>> + /* Save the new desired phy config */
>>> + memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
>>> +
>>> + memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode));
>>> + drm_mode_debug_printmodeline(adjusted_mode);
>>> +}
>>> +
>>> +static void nwl_dsi_bridge_pre_enable(struct drm_bridge *bridge)
>>> +{
>>> + struct nwl_dsi *dsi = bridge_to_dsi(bridge);
>>> +
>>> + pm_runtime_get_sync(dsi->dev);
>>> + nwl_dsi_plat_enable(dsi);
>>> + nwl_dsi_enable(dsi);
>>> +}
>>> +
>>> +static int nwl_dsi_bridge_attach(struct drm_bridge *bridge)
>>> +{
>>> + struct nwl_dsi *dsi = bridge->driver_private;
>>> +
>>> + return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
>>> +}
>>> +
>>> +static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
>>> + .pre_enable = nwl_dsi_bridge_pre_enable,
>>> + .disable = nwl_dsi_bridge_disable,
>>> + .mode_fixup = nwl_dsi_bridge_mode_fixup,
>>> + .mode_set = nwl_dsi_bridge_mode_set,
>>> + .mode_valid = nwl_dsi_bridge_mode_valid,
>>> + .attach = nwl_dsi_bridge_attach,
>>> +};
>>> +
>>> +static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
>>> +{
>>> + struct platform_device *pdev = to_platform_device(dsi->dev);
>>> + struct clk *clk;
>>> + const char *clk_id;
>>> + void __iomem *base;
>>> + int i, ret;
>>> +
>>> + dsi->phy = devm_phy_get(dsi->dev, "dphy");
>>> + if (IS_ERR(dsi->phy)) {
>>> + ret = PTR_ERR(dsi->phy);
>>> + if (ret != -EPROBE_DEFER)
>>> + DRM_DEV_ERROR(dsi->dev, "Could not get PHY: %d\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + /* Platform dependent clocks */
>>> + memcpy(dsi->clk_config, dsi->pdata->clk_config,
>>> + sizeof(dsi->pdata->clk_config));
>>> +
>>> + for (i = 0; i < ARRAY_SIZE(dsi->pdata->clk_config); i++) {
>>> + if (!dsi->clk_config[i].present)
>>> + continue;
>>> +
>>> + clk_id = dsi->clk_config[i].id;
>>> + clk = devm_clk_get(dsi->dev, clk_id);
>>> + if (IS_ERR(clk)) {
>>> + ret = PTR_ERR(clk);
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to get %s clock: %d\n",
>>> + clk_id, ret);
>>> + return ret;
>>> + }
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Setup clk %s (rate: %lu)\n",
>>> + clk_id, clk_get_rate(clk));
>>> + dsi->clk_config[i].clk = clk;
>>> + }
>>> +
>>> + /* DSI clocks */
>>> + clk = devm_clk_get(dsi->dev, "phy_ref");
>>> + if (IS_ERR(clk)) {
>>> + ret = PTR_ERR(clk);
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to get phy_ref clock: %d\n",
>>> + ret);
>>> + return ret;
>>> + }
>>> + dsi->phy_ref_clk = clk;
>>> +
>>> + clk = devm_clk_get(dsi->dev, "rx_esc");
>>> + if (IS_ERR(clk)) {
>>> + ret = PTR_ERR(clk);
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to get rx_esc clock: %d\n",
>>> + ret);
>>> + return ret;
>>> + }
>>> + dsi->rx_esc_clk = clk;
>>> +
>>> + clk = devm_clk_get(dsi->dev, "tx_esc");
>>> + if (IS_ERR(clk)) {
>>> + ret = PTR_ERR(clk);
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to get tx_esc clock: %d\n",
>>> + ret);
>>> + return ret;
>>> + }
>>> + dsi->tx_esc_clk = clk;
>>> +
>>> + dsi->mux = devm_mux_control_get(dsi->dev, NULL);
>>> + if (IS_ERR(dsi->mux)) {
>>> + ret = PTR_ERR(dsi->mux);
>>> + if (ret != -EPROBE_DEFER)
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to get mux: %d\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + base = devm_platform_ioremap_resource(pdev, 0);
>>> + if (IS_ERR(base))
>>> + return PTR_ERR(base);
>>> +
>>> + dsi->regmap =
>>> + devm_regmap_init_mmio(dsi->dev, base, &nwl_dsi_regmap_config);
>>> + if (IS_ERR(dsi->regmap)) {
>>> + ret = PTR_ERR(dsi->regmap);
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to create NWL DSI regmap: %d\n",
>>> + ret);
>>> + return ret;
>>> + }
>>> +
>>> + dsi->irq = platform_get_irq(pdev, 0);
>>> + if (dsi->irq < 0) {
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ: %d\n",
>>> + dsi->irq);
>>> + return dsi->irq;
>>> + }
>>> +
>>> + dsi->rstc = devm_reset_control_array_get(dsi->dev, false, true);
>>> + if (IS_ERR(dsi->rstc)) {
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to get resets: %ld\n",
>>> + PTR_ERR(dsi->rstc));
>>> + return PTR_ERR(dsi->rstc);
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int imx8mq_dsi_select_input(struct nwl_dsi *dsi)
>>> +{
>>> + struct device_node *remote;
>>> + u32 use_dcss = 1;
>>> + int ret;
>>> +
>>> + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, 0);
>>> + if (strcmp(remote->name, "lcdif") == 0)
>>> + use_dcss = 0;
>>
>> Relying on node name seems to me wrong. I am not sure if whole logic for
>> input select should be here.
>>
>> My 1st impression is that selecting should be done rather in DCSS or
>> LCDIF driver, why do you want to put it here?
> Doing it in here keeps it at a single location where on the other hand
> it would need to be done in mxsfb (which handles other SoCs as well) and
> upcoming dcss. Also we can have in the dsi enable path which e.g. mxsfb
> doesn't even know about at this point.
But as I understand mux is not a part of this IP. It is always
problematic what to do with such small pieces of hw.
Let's keep it here until someone find better solution.
Btw do you have public tree for testing platforms with this driver, I am
curious about this mux device.
Regards
Andrzej
>
> Cheers,
> -- Guido
>
>> Regards
>>
>> Andrzej
>>
>>
>>> +
>>> + DRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
>>> + (use_dcss) ? "DCSS" : "LCDIF");
>>> +
>>> + ret = mux_control_try_select(dsi->mux, use_dcss);
>>> + if (ret < 0)
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to select input: %d\n", ret);
>>> +
>>> + of_node_put(remote);
>>> + return ret;
>>> +}
>>> +
>>> +
>>> +static int imx8mq_dsi_deselect_input(struct nwl_dsi *dsi)
>>> +{
>>> + int ret;
>>> +
>>> + ret = mux_control_deselect(dsi->mux);
>>> + if (ret < 0)
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to deselect input: %d\n", ret);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +
>>> +static int imx8mq_dsi_poweron(struct nwl_dsi *dsi)
>>> +{
>>> + int ret = 0;
>>> +
>>> + /* otherwise the display stays blank */
>>> + usleep_range(200, 300);
>>> +
>>> + if (dsi->rstc)
>>> + ret = reset_control_deassert(dsi->rstc);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int imx8mq_dsi_poweroff(struct nwl_dsi *dsi)
>>> +{
>>> + int ret = 0;
>>> +
>>> + if (dsi->quirks & SRC_RESET_QUIRK)
>>> + return 0;
>>> +
>>> + if (dsi->rstc)
>>> + ret = reset_control_assert(dsi->rstc);
>>> + return ret;
>>> +}
>>> +
>>> +static const struct drm_bridge_timings nwl_dsi_timings = {
>>> + .input_bus_flags = DRM_BUS_FLAG_DE_LOW,
>>> +};
>>> +
>>> +static const struct nwl_dsi_platform_data imx8mq_dev = {
>>> + .poweron = &imx8mq_dsi_poweron,
>>> + .poweroff = &imx8mq_dsi_poweroff,
>>> + .select_input = &imx8mq_dsi_select_input,
>>> + .deselect_input = &imx8mq_dsi_deselect_input,
>>> + .clk_config = {
>>> + { .id = NWL_DSI_CLK_CORE, .present = true },
>>> + },
>>> +};
>>> +
>>> +static const struct of_device_id nwl_dsi_dt_ids[] = {
>>> + { .compatible = "fsl,imx8mq-nwl-dsi", .data = &imx8mq_dev, },
>>> + { /* sentinel */ }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids);
>>> +
>>> +static const struct soc_device_attribute nwl_dsi_quirks_match[] = {
>>> + { .soc_id = "i.MX8MQ", .revision = "2.0",
>>> + .data = (void *)(E11418_HS_MODE_QUIRK | SRC_RESET_QUIRK) },
>>> + { /* sentinel. */ },
>>> +};
>>> +
>>> +static int nwl_dsi_probe(struct platform_device *pdev)
>>> +{
>>> + struct device *dev = &pdev->dev;
>>> + const struct of_device_id *of_id = of_match_device(nwl_dsi_dt_ids, dev);
>>> + const struct nwl_dsi_platform_data *pdata = of_id->data;
>>> + const struct soc_device_attribute *attr;
>>> + struct nwl_dsi *dsi;
>>> + int ret;
>>> +
>>> + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
>>> + if (!dsi)
>>> + return -ENOMEM;
>>> +
>>> + dsi->dev = dev;
>>> + dsi->pdata = pdata;
>>> +
>>> + ret = nwl_dsi_parse_dt(dsi);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = devm_request_irq(dev, dsi->irq, nwl_dsi_irq_handler, 0,
>>> + dev_name(dev), dsi);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dev, "Failed to request IRQ %d: %d\n", dsi->irq,
>>> + ret);
>>> + return ret;
>>> + }
>>> +
>>> + dsi->dsi_host.ops = &nwl_dsi_host_ops;
>>> + dsi->dsi_host.dev = dev;
>>> + ret = mipi_dsi_host_register(&dsi->dsi_host);
>>> + if (ret) {
>>> + DRM_DEV_ERROR(dev, "Failed to register MIPI host: %d\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + attr = soc_device_match(nwl_dsi_quirks_match);
>>> + if (attr)
>>> + dsi->quirks = (uintptr_t)attr->data;
>>> +
>>> + dsi->bridge.driver_private = dsi;
>>> + dsi->bridge.funcs = &nwl_dsi_bridge_funcs;
>>> + dsi->bridge.of_node = dev->of_node;
>>> + dsi->bridge.timings = &nwl_dsi_timings;
>>> +
>>> + dev_set_drvdata(dev, dsi);
>>> + pm_runtime_enable(dev);
>>> + return 0;
>>> +}
>>> +
>>> +static int nwl_dsi_remove(struct platform_device *pdev)
>>> +{
>>> + struct nwl_dsi *dsi = platform_get_drvdata(pdev);
>>> +
>>> + mipi_dsi_host_unregister(&dsi->dsi_host);
>>> + pm_runtime_disable(&pdev->dev);
>>> + return 0;
>>> +}
>>> +
>>> +static struct platform_driver nwl_dsi_driver = {
>>> + .probe = nwl_dsi_probe,
>>> + .remove = nwl_dsi_remove,
>>> + .driver = {
>>> + .of_match_table = nwl_dsi_dt_ids,
>>> + .name = DRV_NAME,
>>> + },
>>> +};
>>> +
>>> +module_platform_driver(nwl_dsi_driver);
>>> +
>>> +MODULE_AUTHOR("NXP Semiconductor");
>>> +MODULE_AUTHOR("Purism SPC");
>>> +MODULE_DESCRIPTION("Northwest Logic MIPI-DSI driver");
>>> +MODULE_LICENSE("GPL"); /* GPLv2 or later */
>>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
>>> new file mode 100644
>>> index 000000000000..1e72a9221401
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-drv.h
>>> @@ -0,0 +1,65 @@
>>> +/* SPDX-License-Identifier: GPL-2.0+ */
>>> +/*
>>> + * NWL MIPI DSI host driver
>>> + *
>>> + * Copyright (C) 2017 NXP
>>> + * Copyright (C) 2019 Purism SPC
>>> + */
>>> +
>>> +#ifndef __NWL_DRV_H__
>>> +#define __NWL_DRV_H__
>>> +
>>> +#include <linux/mux/consumer.h>
>>> +#include <linux/phy/phy.h>
>>> +
>>> +#include <drm/drm_bridge.h>
>>> +#include <drm/drm_mipi_dsi.h>
>>> +
>>> +struct nwl_dsi_platform_data;
>>> +
>>> +/* i.MX8 NWL quirks */
>>> +/* i.MX8MQ errata E11418 */
>>> +#define E11418_HS_MODE_QUIRK BIT(0)
>>> +/* Skip DSI bits in SRC on disable to avoid blank display on enable */
>>> +#define SRC_RESET_QUIRK BIT(1)
>>> +
>>> +#define NWL_DSI_MAX_PLATFORM_CLOCKS 1
>>> +struct nwl_dsi_plat_clk_config {
>>> + const char *id;
>>> + struct clk *clk;
>>> + bool present;
>>> +};
>>> +
>>> +struct nwl_dsi {
>>> + struct drm_bridge bridge;
>>> + struct mipi_dsi_host dsi_host;
>>> + struct drm_bridge *panel_bridge;
>>> + struct device *dev;
>>> + struct phy *phy;
>>> + union phy_configure_opts phy_cfg;
>>> + unsigned int quirks;
>>> +
>>> + struct regmap *regmap;
>>> + int irq;
>>> + struct reset_control *rstc;
>>> + struct mux_control *mux;
>>> +
>>> + /* DSI clocks */
>>> + struct clk *phy_ref_clk;
>>> + struct clk *rx_esc_clk;
>>> + struct clk *tx_esc_clk;
>>> + /* Platform dependent clocks */
>>> + struct nwl_dsi_plat_clk_config clk_config[NWL_DSI_MAX_PLATFORM_CLOCKS];
>>> +
>>> + /* dsi lanes */
>>> + u32 lanes;
>>> + enum mipi_dsi_pixel_format format;
>>> + struct drm_display_mode mode;
>>> + unsigned long dsi_mode_flags;
>>> +
>>> + struct nwl_dsi_transfer *xfer;
>>> +
>>> + const struct nwl_dsi_platform_data *pdata;
>>> +};
>>> +
>>> +#endif /* __NWL_DRV_H__ */
>>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
>>> new file mode 100644
>>> index 000000000000..e6038cb4e849
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.c
>>> @@ -0,0 +1,696 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/*
>>> + * NWL MIPI DSI host driver
>>> + *
>>> + * Copyright (C) 2017 NXP
>>> + * Copyright (C) 2019 Purism SPC
>>> + */
>>> +
>>> +#include <linux/bitfield.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/irq.h>
>>> +#include <linux/math64.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/time64.h>
>>> +
>>> +#include <video/mipi_display.h>
>>> +#include <video/videomode.h>
>>> +
>>> +#include <drm/drm_atomic_helper.h>
>>> +#include <drm/drm_crtc_helper.h>
>>> +#include <drm/drm_of.h>
>>> +#include <drm/drm_panel.h>
>>> +#include <drm/drm_print.h>
>>> +
>>> +#include "nwl-drv.h"
>>> +#include "nwl-dsi.h"
>>> +
>>> +#define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
>>> +
>>> +/*
>>> + * PKT_CONTROL format:
>>> + * [15: 0] - word count
>>> + * [17:16] - virtual channel
>>> + * [23:18] - data type
>>> + * [24] - LP or HS select (0 - LP, 1 - HS)
>>> + * [25] - perform BTA after packet is sent
>>> + * [26] - perform BTA only, no packet tx
>>> + */
>>> +#define NWL_DSI_WC(x) FIELD_PREP(GENMASK(15, 0), (x))
>>> +#define NWL_DSI_TX_VC(x) FIELD_PREP(GENMASK(17, 16), (x))
>>> +#define NWL_DSI_TX_DT(x) FIELD_PREP(GENMASK(23, 18), (x))
>>> +#define NWL_DSI_HS_SEL(x) FIELD_PREP(GENMASK(24, 24), (x))
>>> +#define NWL_DSI_BTA_TX(x) FIELD_PREP(GENMASK(25, 25), (x))
>>> +#define NWL_DSI_BTA_NO_TX(x) FIELD_PREP(GENMASK(26, 26), (x))
>>> +
>>> +/*
>>> + * RX_PKT_HEADER format:
>>> + * [15: 0] - word count
>>> + * [21:16] - data type
>>> + * [23:22] - virtual channel
>>> + */
>>> +#define NWL_DSI_RX_DT(x) FIELD_GET(GENMASK(21, 16), (x))
>>> +#define NWL_DSI_RX_VC(x) FIELD_GET(GENMASK(23, 22), (x))
>>> +
>>> +/* DSI Video mode */
>>> +#define NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES 0
>>> +#define NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS BIT(0)
>>> +#define NWL_DSI_VM_BURST_MODE BIT(1)
>>> +
>>> +/* * DPI color coding */
>>> +#define NWL_DSI_DPI_16_BIT_565_PACKED 0
>>> +#define NWL_DSI_DPI_16_BIT_565_ALIGNED 1
>>> +#define NWL_DSI_DPI_16_BIT_565_SHIFTED 2
>>> +#define NWL_DSI_DPI_18_BIT_PACKED 3
>>> +#define NWL_DSI_DPI_18_BIT_ALIGNED 4
>>> +#define NWL_DSI_DPI_24_BIT 5
>>> +
>>> +/* * DPI Pixel format */
>>> +#define NWL_DSI_PIXEL_FORMAT_16 0
>>> +#define NWL_DSI_PIXEL_FORMAT_18 BIT(0)
>>> +#define NWL_DSI_PIXEL_FORMAT_18L BIT(1)
>>> +#define NWL_DSI_PIXEL_FORMAT_24 (BIT(0) | BIT(1))
>>> +
>>> +enum transfer_direction {
>>> + DSI_PACKET_SEND,
>>> + DSI_PACKET_RECEIVE,
>>> +};
>>> +
>>> +struct nwl_dsi_transfer {
>>> + const struct mipi_dsi_msg *msg;
>>> + struct mipi_dsi_packet packet;
>>> + struct completion completed;
>>> +
>>> + int status; /* status of transmission */
>>> + enum transfer_direction direction;
>>> + bool need_bta;
>>> + u8 cmd;
>>> + u16 rx_word_count;
>>> + size_t tx_len; /* in bytes */
>>> + size_t rx_len; /* in bytes */
>>> +};
>>> +
>>> +static int nwl_dsi_write(struct nwl_dsi *dsi, unsigned int reg, u32 val)
>>> +{
>>> + int ret;
>>> +
>>> + ret = regmap_write(dsi->regmap, reg, val);
>>> + if (ret < 0)
>>> + DRM_DEV_ERROR(dsi->dev,
>>> + "Failed to write NWL DSI reg 0x%x: %d\n", reg,
>>> + ret);
>>> + return ret;
>>> +}
>>> +
>>> +static u32 nwl_dsi_read(struct nwl_dsi *dsi, u32 reg)
>>> +{
>>> + unsigned int val;
>>> + int ret;
>>> +
>>> + ret = regmap_read(dsi->regmap, reg, &val);
>>> + if (ret < 0)
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to read NWL DSI reg 0x%x: %d\n",
>>> + reg, ret);
>>> +
>>> + return val;
>>> +}
>>> +
>>> +static u32 nwl_dsi_get_dpi_pixel_format(enum mipi_dsi_pixel_format format)
>>> +{
>>> + switch (format) {
>>> + case MIPI_DSI_FMT_RGB565:
>>> + return NWL_DSI_PIXEL_FORMAT_16;
>>> + case MIPI_DSI_FMT_RGB666:
>>> + return NWL_DSI_PIXEL_FORMAT_18L;
>>> + case MIPI_DSI_FMT_RGB666_PACKED:
>>> + return NWL_DSI_PIXEL_FORMAT_18;
>>> + case MIPI_DSI_FMT_RGB888:
>>> + return NWL_DSI_PIXEL_FORMAT_24;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +}
>>> +
>>> +/*
>>> + * ps2bc - Picoseconds to byte clock cycles
>>> + */
>>> +static u32 ps2bc(struct nwl_dsi *dsi, unsigned long long ps)
>>> +{
>>> + u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
>>> +
>>> + return DIV64_U64_ROUND_UP(ps * dsi->mode.clock * bpp,
>>> + dsi->lanes * 8 * NSEC_PER_SEC);
>>> +}
>>> +
>>> +/*
>>> + * ui2bc - UI time periods to byte clock cycles
>>> + */
>>> +static u32 ui2bc(struct nwl_dsi *dsi, unsigned long long ui)
>>> +{
>>> + u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
>>> +
>>> + return DIV64_U64_ROUND_UP(ui * dsi->lanes,
>>> + dsi->mode.clock * 1000 * bpp);
>>> +}
>>> +
>>> +/*
>>> + * us2bc - micro seconds to lp clock cycles
>>> + */
>>> +static u32 us2lp(u32 lp_clk_rate, unsigned long us)
>>> +{
>>> + return DIV_ROUND_UP(us * lp_clk_rate, USEC_PER_SEC);
>>> +}
>>> +
>>> +static int nwl_dsi_config_host(struct nwl_dsi *dsi)
>>> +{
>>> + u32 cycles;
>>> + struct phy_configure_opts_mipi_dphy *cfg = &dsi->phy_cfg.mipi_dphy;
>>> +
>>> + if (dsi->lanes < 1 || dsi->lanes > 4)
>>> + return -EINVAL;
>>> +
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1);
>>> +
>>> + if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x01);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01);
>>> + } else {
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x00);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x00);
>>> + }
>>> +
>>> + /* values in byte clock cycles */
>>> + cycles = ui2bc(dsi, cfg->clk_pre);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_pre: 0x%x\n", cycles);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_T_PRE, cycles);
>>> + cycles = ps2bc(dsi, cfg->lpx + cfg->clk_prepare + cfg->clk_zero);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap (pre): 0x%x\n", cycles);
>>> + cycles += ui2bc(dsi, cfg->clk_pre);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_post: 0x%x\n", cycles);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_T_POST, cycles);
>>> + cycles = ps2bc(dsi, cfg->hs_exit);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_TX_GAP, cycles);
>>> +
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP, 0x01);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_HTX_TO_COUNT, 0x00);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_LRX_H_TO_COUNT, 0x00);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_BTA_H_TO_COUNT, 0x00);
>>> + /* In LP clock cycles */
>>> + cycles = us2lp(cfg->lp_clk_rate, cfg->wakeup);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_twakeup: 0x%x\n", cycles);
>>> + nwl_dsi_write(dsi, NWL_DSI_CFG_TWAKEUP, cycles);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
>>> +{
>>> + u32 color_format, mode;
>>> + bool burst_mode;
>>> + int hfront_porch, hback_porch, vfront_porch, vback_porch;
>>> + int hsync_len, vsync_len;
>>> +
>>> + hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay;
>>> + hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start;
>>> + hback_porch = dsi->mode.htotal - dsi->mode.hsync_end;
>>> +
>>> + vfront_porch = dsi->mode.vsync_start - dsi->mode.vdisplay;
>>> + vsync_len = dsi->mode.vsync_end - dsi->mode.vsync_start;
>>> + vback_porch = dsi->mode.vtotal - dsi->mode.vsync_end;
>>> +
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hfront_porch = %d\n", hfront_porch);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hback_porch = %d\n", hback_porch);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hsync_len = %d\n", hsync_len);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hdisplay = %d\n", dsi->mode.hdisplay);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vfront_porch = %d\n", vfront_porch);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vback_porch = %d\n", vback_porch);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vsync_len = %d\n", vsync_len);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vactive = %d\n", dsi->mode.vdisplay);
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "clock = %d kHz\n", dsi->mode.clock);
>>> +
>>> + color_format = nwl_dsi_get_dpi_pixel_format(dsi->format);
>>> + if (color_format < 0) {
>>> + DRM_DEV_ERROR(dsi->dev, "Invalid color format 0x%x\n",
>>> + dsi->format);
>>> + return color_format;
>>> + }
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "pixel fmt = %d\n", dsi->format);
>>> +
>>> + nwl_dsi_write(dsi, NWL_DSI_INTERFACE_COLOR_CODING, NWL_DSI_DPI_24_BIT);
>>> + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FORMAT, color_format);
>>> + /*
>>> + * Adjusting input polarity based on the video mode results in
>>> + * a black screen so always pick active low:
>>> + */
>>> + nwl_dsi_write(dsi, NWL_DSI_VSYNC_POLARITY,
>>> + NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW);
>>> + nwl_dsi_write(dsi, NWL_DSI_HSYNC_POLARITY,
>>> + NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW);
>>> +
>>> + burst_mode = (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_BURST) &&
>>> + !(dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE);
>>> +
>>> + if (burst_mode) {
>>> + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, NWL_DSI_VM_BURST_MODE);
>>> + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL, 256);
>>> + } else {
>>> + mode = ((dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) ?
>>> + NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES :
>>> + NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS);
>>> + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, mode);
>>> + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL,
>>> + dsi->mode.hdisplay);
>>> + }
>>> +
>>> + nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch);
>>> + nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch);
>>> + nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len);
>>> +
>>> + nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
>>> + nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1);
>>> + nwl_dsi_write(dsi, NWL_DSI_USE_NULL_PKT_BLLP, 0x0);
>>> + nwl_dsi_write(dsi, NWL_DSI_VC, 0x0);
>>> +
>>> + nwl_dsi_write(dsi, NWL_DSI_PIXEL_PAYLOAD_SIZE, dsi->mode.hdisplay);
>>> + nwl_dsi_write(dsi, NWL_DSI_VACTIVE, dsi->mode.vdisplay - 1);
>>> + nwl_dsi_write(dsi, NWL_DSI_VBP, vback_porch);
>>> + nwl_dsi_write(dsi, NWL_DSI_VFP, vfront_porch);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void nwl_dsi_init_interrupts(struct nwl_dsi *dsi)
>>> +{
>>> + u32 irq_enable;
>>> +
>>> + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, 0xffffffff);
>>> + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7);
>>> +
>>> + irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK |
>>> + NWL_DSI_RX_PKT_HDR_RCVD_MASK |
>>> + NWL_DSI_TX_FIFO_OVFLW_MASK |
>>> + NWL_DSI_HS_TX_TIMEOUT_MASK);
>>> +
>>> + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable);
>>> +}
>>> +
>>> +static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host,
>>> + struct mipi_dsi_device *device)
>>> +{
>>> + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host);
>>> + struct device *dev = dsi->dev;
>>> + struct drm_bridge *bridge;
>>> + struct drm_panel *panel;
>>> + int ret;
>>> +
>>> + DRM_DEV_INFO(dev, "lanes=%u, format=0x%x flags=0x%lx\n", device->lanes,
>>> + device->format, device->mode_flags);
>>> +
>>> + if (device->lanes < 1 || device->lanes > 4)
>>> + return -EINVAL;
>>> +
>>> + dsi->lanes = device->lanes;
>>> + dsi->format = device->format;
>>> + dsi->dsi_mode_flags = device->mode_flags;
>>> +
>>> + ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel,
>>> + &bridge);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + if (panel) {
>>> + bridge = drm_panel_bridge_add(panel, DRM_MODE_CONNECTOR_DSI);
>>> + if (IS_ERR(bridge))
>>> + return PTR_ERR(bridge);
>>> + }
>>> +
>>> + dsi->panel_bridge = bridge;
>>> + drm_bridge_add(&dsi->bridge);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int nwl_dsi_host_detach(struct mipi_dsi_host *dsi_host,
>>> + struct mipi_dsi_device *device)
>>> +{
>>> + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host);
>>> +
>>> + drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0);
>>> + drm_bridge_remove(&dsi->bridge);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status)
>>> +{
>>> + struct device *dev = dsi->dev;
>>> + struct nwl_dsi_transfer *xfer = dsi->xfer;
>>> + u8 *payload = xfer->msg->rx_buf;
>>> + u32 val;
>>> + u16 word_count;
>>> + u8 channel;
>>> + u8 data_type;
>>> +
>>> + xfer->status = 0;
>>> +
>>> + if (xfer->rx_word_count == 0) {
>>> + if (!(status & NWL_DSI_RX_PKT_HDR_RCVD))
>>> + return false;
>>> + /* Get the RX header and parse it */
>>> + val = nwl_dsi_read(dsi, NWL_DSI_RX_PKT_HEADER);
>>> + word_count = NWL_DSI_WC(val);
>>> + channel = NWL_DSI_RX_VC(val);
>>> + data_type = NWL_DSI_RX_DT(val);
>>> +
>>> + if (channel != xfer->msg->channel) {
>>> + DRM_DEV_ERROR(dev,
>>> + "[%02X] Channel mismatch (%u != %u)\n",
>>> + xfer->cmd, channel, xfer->msg->channel);
>>> + return true;
>>> + }
>>> +
>>> + switch (data_type) {
>>> + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
>>> + /* Fall through */
>>> + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
>>> + if (xfer->msg->rx_len > 1) {
>>> + /* read second byte */
>>> + payload[1] = word_count >> 8;
>>> + ++xfer->rx_len;
>>> + }
>>> + /* Fall through */
>>> + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
>>> + /* Fall through */
>>> + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
>>> + if (xfer->msg->rx_len > 0) {
>>> + /* read first byte */
>>> + payload[0] = word_count & 0xff;
>>> + ++xfer->rx_len;
>>> + }
>>> + xfer->status = xfer->rx_len;
>>> + return true;
>>> + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
>>> + word_count &= 0xff;
>>> + DRM_DEV_ERROR(dev, "[%02X] DSI error report: 0x%02x\n",
>>> + xfer->cmd, word_count);
>>> + xfer->status = -EPROTO;
>>> + return true;
>>> + }
>>> +
>>> + if (word_count > xfer->msg->rx_len) {
>>> + DRM_DEV_ERROR(
>>> + dev,
>>> + "[%02X] Receive buffer too small: %zu (< %u)\n",
>>> + xfer->cmd, xfer->msg->rx_len, word_count);
>>> + return true;
>>> + }
>>> +
>>> + xfer->rx_word_count = word_count;
>>> + } else {
>>> + /* Set word_count from previous header read */
>>> + word_count = xfer->rx_word_count;
>>> + }
>>> +
>>> + /* If RX payload is not yet received, wait for it */
>>> + if (!(status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD))
>>> + return false;
>>> +
>>> + /* Read the RX payload */
>>> + while (word_count >= 4) {
>>> + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
>>> + payload[0] = (val >> 0) & 0xff;
>>> + payload[1] = (val >> 8) & 0xff;
>>> + payload[2] = (val >> 16) & 0xff;
>>> + payload[3] = (val >> 24) & 0xff;
>>> + payload += 4;
>>> + xfer->rx_len += 4;
>>> + word_count -= 4;
>>> + }
>>> +
>>> + if (word_count > 0) {
>>> + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD);
>>> + switch (word_count) {
>>> + case 3:
>>> + payload[2] = (val >> 16) & 0xff;
>>> + ++xfer->rx_len;
>>> + /* Fall through */
>>> + case 2:
>>> + payload[1] = (val >> 8) & 0xff;
>>> + ++xfer->rx_len;
>>> + /* Fall through */
>>> + case 1:
>>> + payload[0] = (val >> 0) & 0xff;
>>> + ++xfer->rx_len;
>>> + break;
>>> + }
>>> + }
>>> +
>>> + xfer->status = xfer->rx_len;
>>> +
>>> + return true;
>>> +}
>>> +
>>> +static void nwl_dsi_finish_transmission(struct nwl_dsi *dsi, u32 status)
>>> +{
>>> + struct nwl_dsi_transfer *xfer = dsi->xfer;
>>> + bool end_packet = false;
>>> +
>>> + if (!xfer)
>>> + return;
>>> +
>>> + if (xfer->direction == DSI_PACKET_SEND &&
>>> + status & NWL_DSI_TX_PKT_DONE) {
>>> + xfer->status = xfer->tx_len;
>>> + end_packet = true;
>>> + } else if (status & NWL_DSI_DPHY_DIRECTION &&
>>> + ((status & (NWL_DSI_RX_PKT_HDR_RCVD |
>>> + NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)))) {
>>> + end_packet = nwl_dsi_read_packet(dsi, status);
>>> + }
>>> +
>>> + if (end_packet)
>>> + complete(&xfer->completed);
>>> +}
>>> +
>>> +static void nwl_dsi_begin_transmission(struct nwl_dsi *dsi)
>>> +{
>>> + struct nwl_dsi_transfer *xfer = dsi->xfer;
>>> + struct mipi_dsi_packet *pkt = &xfer->packet;
>>> + const u8 *payload;
>>> + size_t length;
>>> + u16 word_count;
>>> + u8 hs_mode;
>>> + u32 val;
>>> + u32 hs_workaround = 0;
>>> +
>>> + /* Send the payload, if any */
>>> + length = pkt->payload_length;
>>> + payload = pkt->payload;
>>> +
>>> + while (length >= 4) {
>>> + val = *(u32 *)payload;
>>> + hs_workaround |= !(val & 0xFFFF00);
>>> + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
>>> + payload += 4;
>>> + length -= 4;
>>> + }
>>> + /* Send the rest of the payload */
>>> + val = 0;
>>> + switch (length) {
>>> + case 3:
>>> + val |= payload[2] << 16;
>>> + /* Fall through */
>>> + case 2:
>>> + val |= payload[1] << 8;
>>> + hs_workaround |= !(val & 0xFFFF00);
>>> + /* Fall through */
>>> + case 1:
>>> + val |= payload[0];
>>> + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val);
>>> + break;
>>> + }
>>> + xfer->tx_len = pkt->payload_length;
>>> +
>>> + /*
>>> + * Send the header
>>> + * header[0] = Virtual Channel + Data Type
>>> + * header[1] = Word Count LSB (LP) or first param (SP)
>>> + * header[2] = Word Count MSB (LP) or second param (SP)
>>> + */
>>> + word_count = pkt->header[1] | (pkt->header[2] << 8);
>>> + if (hs_workaround && (dsi->quirks & E11418_HS_MODE_QUIRK)) {
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev,
>>> + "Using hs mode workaround for cmd 0x%x\n",
>>> + xfer->cmd);
>>> + hs_mode = 1;
>>> + } else {
>>> + hs_mode = (xfer->msg->flags & MIPI_DSI_MSG_USE_LPM) ? 0 : 1;
>>> + }
>>> + val = NWL_DSI_WC(word_count) | NWL_DSI_TX_VC(xfer->msg->channel) |
>>> + NWL_DSI_TX_DT(xfer->msg->type) | NWL_DSI_HS_SEL(hs_mode) |
>>> + NWL_DSI_BTA_TX(xfer->need_bta);
>>> + nwl_dsi_write(dsi, NWL_DSI_PKT_CONTROL, val);
>>> +
>>> + /* Send packet command */
>>> + nwl_dsi_write(dsi, NWL_DSI_SEND_PACKET, 0x1);
>>> +}
>>> +
>>> +static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host,
>>> + const struct mipi_dsi_msg *msg)
>>> +{
>>> + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host);
>>> + struct nwl_dsi_transfer xfer;
>>> + ssize_t ret = 0;
>>> +
>>> + /* Create packet to be sent */
>>> + dsi->xfer = &xfer;
>>> + ret = mipi_dsi_create_packet(&xfer.packet, msg);
>>> + if (ret < 0) {
>>> + dsi->xfer = NULL;
>>> + return ret;
>>> + }
>>> +
>>> + if ((msg->type & MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM ||
>>> + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM ||
>>> + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM ||
>>> + msg->type & MIPI_DSI_DCS_READ) &&
>>> + msg->rx_len > 0 && msg->rx_buf != NULL)
>>> + xfer.direction = DSI_PACKET_RECEIVE;
>>> + else
>>> + xfer.direction = DSI_PACKET_SEND;
>>> +
>>> + xfer.need_bta = (xfer.direction == DSI_PACKET_RECEIVE);
>>> + xfer.need_bta |= (msg->flags & MIPI_DSI_MSG_REQ_ACK) ? 1 : 0;
>>> + xfer.msg = msg;
>>> + xfer.status = -ETIMEDOUT;
>>> + xfer.rx_word_count = 0;
>>> + xfer.rx_len = 0;
>>> + xfer.cmd = 0x00;
>>> + if (msg->tx_len > 0)
>>> + xfer.cmd = ((u8 *)(msg->tx_buf))[0];
>>> + init_completion(&xfer.completed);
>>> +
>>> + ret = clk_prepare_enable(dsi->rx_esc_clk);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to enable rx_esc clk: %zd\n",
>>> + ret);
>>> + return ret;
>>> + }
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled rx_esc clk @%lu Hz\n",
>>> + clk_get_rate(dsi->rx_esc_clk));
>>> +
>>> + /* Initiate the DSI packet transmision */
>>> + nwl_dsi_begin_transmission(dsi);
>>> +
>>> + if (!wait_for_completion_timeout(&xfer.completed,
>>> + NWL_DSI_MIPI_FIFO_TIMEOUT)) {
>>> + DRM_DEV_ERROR(dsi_host->dev, "[%02X] DSI transfer timed out\n",
>>> + xfer.cmd);
>>> + ret = -ETIMEDOUT;
>>> + } else {
>>> + ret = xfer.status;
>>> + }
>>> +
>>> + clk_disable_unprepare(dsi->rx_esc_clk);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +const struct mipi_dsi_host_ops nwl_dsi_host_ops = {
>>> + .attach = nwl_dsi_host_attach,
>>> + .detach = nwl_dsi_host_detach,
>>> + .transfer = nwl_dsi_host_transfer,
>>> +};
>>> +
>>> +irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
>>> +{
>>> + u32 irq_status;
>>> + struct nwl_dsi *dsi = data;
>>> +
>>> + irq_status = nwl_dsi_read(dsi, NWL_DSI_IRQ_STATUS);
>>> +
>>> + if (irq_status & NWL_DSI_TX_FIFO_OVFLW)
>>> + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo overflow\n");
>>> +
>>> + if (irq_status & NWL_DSI_HS_TX_TIMEOUT)
>>> + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx timeout\n");
>>> +
>>> + if (irq_status & NWL_DSI_TX_PKT_DONE ||
>>> + irq_status & NWL_DSI_RX_PKT_HDR_RCVD ||
>>> + irq_status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)
>>> + nwl_dsi_finish_transmission(dsi, irq_status);
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +int nwl_dsi_enable(struct nwl_dsi *dsi)
>>> +{
>>> + struct device *dev = dsi->dev;
>>> + union phy_configure_opts *phy_cfg = &dsi->phy_cfg;
>>> + int ret;
>>> +
>>> + if (!dsi->lanes) {
>>> + DRM_DEV_ERROR(dev, "Need DSI lanes: %d\n", dsi->lanes);
>>> + return -EINVAL;
>>> + }
>>> +
>>> + ret = phy_init(dsi->phy);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dev, "Failed to init DSI phy: %d\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + ret = phy_configure(dsi->phy, phy_cfg);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dev, "Failed to configure DSI phy: %d\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + ret = clk_prepare_enable(dsi->tx_esc_clk);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dsi->dev, "Failed to enable tx_esc clk: %d\n",
>>> + ret);
>>> + return ret;
>>> + }
>>> + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled tx_esc clk @%lu Hz\n",
>>> + clk_get_rate(dsi->tx_esc_clk));
>>> +
>>> + ret = nwl_dsi_config_host(dsi);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dev, "Failed to set up DSI: %d", ret);
>>> + return ret;
>>> + }
>>> +
>>> + ret = nwl_dsi_config_dpi(dsi);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dev, "Failed to set up DPI: %d", ret);
>>> + return ret;
>>> + }
>>> +
>>> + ret = phy_power_on(dsi->phy);
>>> + if (ret < 0) {
>>> + DRM_DEV_ERROR(dev, "Failed to power on DPHY (%d)\n", ret);
>>> + return ret;
>>> + }
>>> +
>>> + nwl_dsi_init_interrupts(dsi);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +int nwl_dsi_disable(struct nwl_dsi *dsi)
>>> +{
>>> + struct device *dev = dsi->dev;
>>> +
>>> + DRM_DEV_DEBUG_DRIVER(dev, "Disabling clocks and phy\n");
>>> +
>>> + phy_power_off(dsi->phy);
>>> + phy_exit(dsi->phy);
>>> +
>>> + /* Disabling the clock before the phy breaks enabling dsi again */
>>> + clk_disable_unprepare(dsi->tx_esc_clk);
>>> +
>>> + return 0;
>>> +}
>>> diff --git a/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
>>> new file mode 100644
>>> index 000000000000..579b366de652
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/bridge/nwl-dsi/nwl-dsi.h
>>> @@ -0,0 +1,112 @@
>>> +/* SPDX-License-Identifier: GPL-2.0+ */
>>> +/*
>>> + * NWL MIPI DSI host driver
>>> + *
>>> + * Copyright (C) 2017 NXP
>>> + * Copyright (C) 2019 Purism SPC
>>> + */
>>> +#ifndef __NWL_DSI_H__
>>> +#define __NWL_DSI_H__
>>> +
>>> +#include <linux/irqreturn.h>
>>> +
>>> +#include <drm/drm_mipi_dsi.h>
>>> +
>>> +#include "nwl-drv.h"
>>> +
>>> +/* DSI HOST registers */
>>> +#define NWL_DSI_CFG_NUM_LANES 0x0
>>> +#define NWL_DSI_CFG_NONCONTINUOUS_CLK 0x4
>>> +#define NWL_DSI_CFG_T_PRE 0x8
>>> +#define NWL_DSI_CFG_T_POST 0xc
>>> +#define NWL_DSI_CFG_TX_GAP 0x10
>>> +#define NWL_DSI_CFG_AUTOINSERT_EOTP 0x14
>>> +#define NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP 0x18
>>> +#define NWL_DSI_CFG_HTX_TO_COUNT 0x1c
>>> +#define NWL_DSI_CFG_LRX_H_TO_COUNT 0x20
>>> +#define NWL_DSI_CFG_BTA_H_TO_COUNT 0x24
>>> +#define NWL_DSI_CFG_TWAKEUP 0x28
>>> +#define NWL_DSI_CFG_STATUS_OUT 0x2c
>>> +#define NWL_DSI_RX_ERROR_STATUS 0x30
>>> +
>>> +/* DSI DPI registers */
>>> +#define NWL_DSI_PIXEL_PAYLOAD_SIZE 0x200
>>> +#define NWL_DSI_PIXEL_FIFO_SEND_LEVEL 0x204
>>> +#define NWL_DSI_INTERFACE_COLOR_CODING 0x208
>>> +#define NWL_DSI_PIXEL_FORMAT 0x20c
>>> +#define NWL_DSI_VSYNC_POLARITY 0x210
>>> +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW 0
>>> +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_HIGH BIT(1)
>>> +
>>> +#define NWL_DSI_HSYNC_POLARITY 0x214
>>> +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW 0
>>> +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_HIGH BIT(1)
>>> +
>>> +#define NWL_DSI_VIDEO_MODE 0x218
>>> +#define NWL_DSI_HFP 0x21c
>>> +#define NWL_DSI_HBP 0x220
>>> +#define NWL_DSI_HSA 0x224
>>> +#define NWL_DSI_ENABLE_MULT_PKTS 0x228
>>> +#define NWL_DSI_VBP 0x22c
>>> +#define NWL_DSI_VFP 0x230
>>> +#define NWL_DSI_BLLP_MODE 0x234
>>> +#define NWL_DSI_USE_NULL_PKT_BLLP 0x238
>>> +#define NWL_DSI_VACTIVE 0x23c
>>> +#define NWL_DSI_VC 0x240
>>> +
>>> +/* DSI APB PKT control */
>>> +#define NWL_DSI_TX_PAYLOAD 0x280
>>> +#define NWL_DSI_PKT_CONTROL 0x284
>>> +#define NWL_DSI_SEND_PACKET 0x288
>>> +#define NWL_DSI_PKT_STATUS 0x28c
>>> +#define NWL_DSI_PKT_FIFO_WR_LEVEL 0x290
>>> +#define NWL_DSI_PKT_FIFO_RD_LEVEL 0x294
>>> +#define NWL_DSI_RX_PAYLOAD 0x298
>>> +#define NWL_DSI_RX_PKT_HEADER 0x29c
>>> +
>>> +/* DSI IRQ handling */
>>> +#define NWL_DSI_IRQ_STATUS 0x2a0
>>> +#define NWL_DSI_SM_NOT_IDLE BIT(0)
>>> +#define NWL_DSI_TX_PKT_DONE BIT(1)
>>> +#define NWL_DSI_DPHY_DIRECTION BIT(2)
>>> +#define NWL_DSI_TX_FIFO_OVFLW BIT(3)
>>> +#define NWL_DSI_TX_FIFO_UDFLW BIT(4)
>>> +#define NWL_DSI_RX_FIFO_OVFLW BIT(5)
>>> +#define NWL_DSI_RX_FIFO_UDFLW BIT(6)
>>> +#define NWL_DSI_RX_PKT_HDR_RCVD BIT(7)
>>> +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD BIT(8)
>>> +#define NWL_DSI_BTA_TIMEOUT BIT(29)
>>> +#define NWL_DSI_LP_RX_TIMEOUT BIT(30)
>>> +#define NWL_DSI_HS_TX_TIMEOUT BIT(31)
>>> +
>>> +#define NWL_DSI_IRQ_STATUS2 0x2a4
>>> +#define NWL_DSI_SINGLE_BIT_ECC_ERR BIT(0)
>>> +#define NWL_DSI_MULTI_BIT_ECC_ERR BIT(1)
>>> +#define NWL_DSI_CRC_ERR BIT(2)
>>> +
>>> +#define NWL_DSI_IRQ_MASK 0x2a8
>>> +#define NWL_DSI_SM_NOT_IDLE_MASK BIT(0)
>>> +#define NWL_DSI_TX_PKT_DONE_MASK BIT(1)
>>> +#define NWL_DSI_DPHY_DIRECTION_MASK BIT(2)
>>> +#define NWL_DSI_TX_FIFO_OVFLW_MASK BIT(3)
>>> +#define NWL_DSI_TX_FIFO_UDFLW_MASK BIT(4)
>>> +#define NWL_DSI_RX_FIFO_OVFLW_MASK BIT(5)
>>> +#define NWL_DSI_RX_FIFO_UDFLW_MASK BIT(6)
>>> +#define NWL_DSI_RX_PKT_HDR_RCVD_MASK BIT(7)
>>> +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASK BIT(8)
>>> +#define NWL_DSI_BTA_TIMEOUT_MASK BIT(29)
>>> +#define NWL_DSI_LP_RX_TIMEOUT_MASK BIT(30)
>>> +#define NWL_DSI_HS_TX_TIMEOUT_MASK BIT(31)
>>> +
>>> +#define NWL_DSI_IRQ_MASK2 0x2ac
>>> +#define NWL_DSI_SINGLE_BIT_ECC_ERR_MASK BIT(0)
>>> +#define NWL_DSI_MULTI_BIT_ECC_ERR_MASK BIT(1)
>>> +#define NWL_DSI_CRC_ERR_MASK BIT(2)
>>> +
>>> +extern const struct mipi_dsi_host_ops nwl_dsi_host_ops;
>>> +
>>> +irqreturn_t nwl_dsi_irq_handler(int irq, void *data);
>>> +int nwl_dsi_enable(struct nwl_dsi *dsi);
>>> +int nwl_dsi_disable(struct nwl_dsi *dsi);
>>> +
>>> +#endif /* __NWL_DSI_H__ */
>>
>
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH] usb: gadget: udc: lpc32xx: Use devm_platform_ioremap_resource() in lpc32xx_udc_probe()
From: Markus Elfring @ 2019-09-19 13:56 UTC (permalink / raw)
To: linux-usb, linux-arm-kernel, Alexandre Belloni, Arnd Bergmann,
Felipe Balbi, Greg Kroah-Hartman, Gustavo A. R. Silva,
Stephen Boyd, Sylvain Lemieux, Vladimir Zapolskiy, Wei Yongjun
Cc: Bartosz Golaszewski, kernel-janitors, LKML, Himanshu Jha
From: Markus Elfring <elfring@users.sourceforge.net>
Date: Thu, 19 Sep 2019 15:47:24 +0200
Simplify this function implementation by using a known wrapper function.
This issue was detected by using the Coccinelle software.
Signed-off-by: Markus Elfring <elfring@users.sourceforge.net>
---
drivers/usb/gadget/udc/lpc32xx_udc.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c
index b3e073fb88c6..0ed3fd8c6610 100644
--- a/drivers/usb/gadget/udc/lpc32xx_udc.c
+++ b/drivers/usb/gadget/udc/lpc32xx_udc.c
@@ -3000,7 +3000,6 @@ static int lpc32xx_udc_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct lpc32xx_udc *udc;
int retval, i;
- struct resource *res;
dma_addr_t dma_handle;
struct device_node *isp1301_node;
@@ -3048,9 +3047,6 @@ static int lpc32xx_udc_probe(struct platform_device *pdev)
* IORESOURCE_IRQ, USB device interrupt number
* IORESOURCE_IRQ, USB transceiver interrupt number
*/
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res)
- return -ENXIO;
spin_lock_init(&udc->lock);
@@ -3061,7 +3057,7 @@ static int lpc32xx_udc_probe(struct platform_device *pdev)
return udc->udp_irq[i];
}
- udc->udp_baseaddr = devm_ioremap_resource(dev, res);
+ udc->udp_baseaddr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(udc->udp_baseaddr)) {
dev_err(udc->dev, "IO map failure\n");
return PTR_ERR(udc->udp_baseaddr);
--
2.23.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* Re: [REGRESSION] sdhci no longer detects SD cards on LX2160A
From: Robin Murphy @ 2019-09-19 14:02 UTC (permalink / raw)
To: Russell King - ARM Linux admin, Y.b. Lu
Cc: dann frazier, linux-mmc, Adrian Hunter, Will Deacon, Nicolin Chen,
Christoph Hellwig, Linux ARM
In-Reply-To: <20190919091601.GH25745@shell.armlinux.org.uk>
On 19/09/2019 10:16, Russell King - ARM Linux admin wrote:
> On Tue, Sep 17, 2019 at 03:03:29PM +0100, Robin Murphy wrote:
>> On 17/09/2019 14:49, Russell King - ARM Linux admin wrote:
>>> As already replied, v4 mode is not documented as being available on
>>> the LX2160A - the bit in the control register is marked as "reserved".
>>> This is as expected as it is documented that it is using a v3.00 of
>>> the SDHCI standard, rather than v4.00.
>>>
>>> So, sorry, enabling "v4 mode" isn't a workaround in this scenario.
>>>
>>> Given that v4 mode is not mandatory, this shouldn't be a work-around.
>>>
>>> Given that it _does_ work some of the time with the table >4GB, then
>>> this is not an addressing limitation.
>>
>> Yes, that's what "something totally different" usually means.
>>
>>>> However, the other difference between getting a single page directly from
>>>> the page allocator vs. the CMA area is that accesses to the linear mapping
>>>> of the CMA area are probably pretty rare, whereas for the single-page case
>>>> it's much more likely that kernel tasks using adjacent pages could lead to
>>>> prefetching of the descriptor page's cacheable alias. That could certainly
>>>> explain how reverting that commit manages to hide an apparent coherency
>>>> issue.
>>>
>>> Right, so how do we fix this?
>>
>> By describing the hardware correctly in the DT.
>
> It would appear that it _is_ correctly described given the default
> hardware configuration, but the driver sets a bit in a control
> register that enables cache snooping.
Oh, fun. FWIW, the more general form of that statement would be "by
ensuring that the device behaviour and the DT description are
consistent", it's just rare to have both degrees of freedom.
Even in these cases, though, it tends to be ultimately necessary to
defer to what the DT says, because there can be situations where the IP
believes itself capable of enabling snoops, but the integration failed
to wire things up correctly for them to actually work. I know we have to
deal with that in arm-smmu, for one example.
> Adding "dma-coherent" to the DT description does not seem to be the
> correct solution, as we are reliant on the DT description and driver
> implementation both agreeing, which is fragile.
>
> From what I can see, there isn't a way for a driver to say "I've made
> this device is coherent now" and I suspect making the driver set the
> DMA snoop bit depending on whether "dma-coherent" is present in DT or
> not will cause data-corrupting regressions for other people.
>
> So, we're back to where we started - what is the right solution to
> this problem?
>
> The only thing I can think is that the driver needs to do something
> like:
>
> WARN_ON(!dev_is_dma_coherent(dev));
>
> in esdhc_of_enable_dma() as a first step, and ensuring that the snoop
> bit matches the state of dev_is_dma_coherent(dev)? Is it permitted to
> use dev_is_dma_coherent() in drivers - it doesn't seem to be part of
> the normal DMA API?
The safest option would be to query the firmware property layer via
device_get_dma_attr() - or potentially short-cut to of_dma_is_coherent()
for a pure DT driver. Even disregarding API purity, I don't think the
DMA API internals are really generic enough yet to reliably poke at
(although FWIW, *certain* cases like dma_direct_ops would now actually
work as expected if one did the unspeakable and flipped
dev->dma_coherent from a driver, but that would definitely not win any
friends).
Robin.
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v2 2/3] drm: Measure Self Refresh Entry/Exit times to avoid thrashing
From: Sean Paul @ 2019-09-19 14:14 UTC (permalink / raw)
To: dri-devel
Cc: heiko, David Airlie, Daniel Vetter, Maarten Lankhorst,
Sandy Huang, linux-kernel, linux-rockchip, Sean Paul, daniel,
jekarl, Sean Paul, linux-arm-kernel
In-Reply-To: <20190918200734.149876-2-sean@poorly.run>
On Wed, Sep 18, 2019 at 04:07:29PM -0400, Sean Paul wrote:
> From: Sean Paul <seanpaul@chromium.org>
>
> Currently the self refresh idle timer is a const set by the crtc. This
> is fine if the self refresh entry/exit times are well-known for all
> panels used on that crtc. However panels and workloads can vary quite a
> bit, and a timeout which works well for one doesn't work well for
> another.
>
> In the extreme, if the timeout is too short we could get in a situation
> where the self refresh exits are taking so long we queue up a self refresh
> entry before the exit commit is even finished.
>
> This patch changes the idle timeout to a moving average of the entry
> times + a moving average of exit times + the crtc constant.
>
> This patch was tested on rockchip, with a kevin CrOS panel the idle
> delay averages out to about ~235ms (35 entry + 100 exit + 100 const). On
> the same board, the bob panel idle delay lands around ~340ms (90 entry
> + 150 exit + 100 const).
>
> WRT the dedicated mutex in self_refresh_data, it would be nice if we
> could rely on drm_crtc.mutex to protect the average times, but there are
> a few reasons why a separate lock is a better choice:
> - We can't rely on drm_crtc.mutex being held if we're doing a nonblocking
> commit
> - We can't grab drm_crtc.mutex since drm_modeset_lock() doesn't tell us
> whether the lock was already held in the acquire context (it eats
> -EALREADY), so we can't tell if we should drop it or not
> - We don't need such a heavy-handed lock for what we're trying to do,
> commit ordering doesn't matter, so a point-of-use lock will be less
> contentious
>
> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Pushed the first 2 to drm-misc-next-fixes to fix the gru-bob regression. I'll
fix up the 3rd patch separately.
Thank you for the reviews!
Sean
> Signed-off-by: Sean Paul <seanpaul@chromium.org>
> Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190917200443.64481-2-sean@poorly.run
>
> Changes in v2:
> - Migrate locking explanation from comment to commit msg (Daniel)
> - Turf constant entry delay and multiply the avg times by 2 (Daniel)
> ---
> drivers/gpu/drm/drm_atomic_helper.c | 20 ++++++
> drivers/gpu/drm/drm_self_refresh_helper.c | 72 +++++++++++++++++++--
> drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 5 +-
> include/drm/drm_self_refresh_helper.h | 6 +-
> 4 files changed, 90 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 9d7e4da6c292..3f13fa9a9e24 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -26,6 +26,7 @@
> */
>
> #include <linux/dma-fence.h>
> +#include <linux/ktime.h>
>
> #include <drm/drm_atomic.h>
> #include <drm/drm_atomic_helper.h>
> @@ -1570,9 +1571,23 @@ static void commit_tail(struct drm_atomic_state *old_state)
> {
> struct drm_device *dev = old_state->dev;
> const struct drm_mode_config_helper_funcs *funcs;
> + ktime_t start;
> + s64 commit_time_ms;
>
> funcs = dev->mode_config.helper_private;
>
> + /*
> + * We're measuring the _entire_ commit, so the time will vary depending
> + * on how many fences and objects are involved. For the purposes of self
> + * refresh, this is desirable since it'll give us an idea of how
> + * congested things are. This will inform our decision on how often we
> + * should enter self refresh after idle.
> + *
> + * These times will be averaged out in the self refresh helpers to avoid
> + * overreacting over one outlier frame
> + */
> + start = ktime_get();
> +
> drm_atomic_helper_wait_for_fences(dev, old_state, false);
>
> drm_atomic_helper_wait_for_dependencies(old_state);
> @@ -1582,6 +1597,11 @@ static void commit_tail(struct drm_atomic_state *old_state)
> else
> drm_atomic_helper_commit_tail(old_state);
>
> + commit_time_ms = ktime_ms_delta(ktime_get(), start);
> + if (commit_time_ms > 0)
> + drm_self_refresh_helper_update_avg_times(old_state,
> + (unsigned long)commit_time_ms);
> +
> drm_atomic_helper_commit_cleanup_done(old_state);
>
> drm_atomic_state_put(old_state);
> diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
> index 9095cebf2147..68f4765a5896 100644
> --- a/drivers/gpu/drm/drm_self_refresh_helper.c
> +++ b/drivers/gpu/drm/drm_self_refresh_helper.c
> @@ -5,6 +5,7 @@
> * Authors:
> * Sean Paul <seanpaul@chromium.org>
> */
> +#include <linux/average.h>
> #include <linux/bitops.h>
> #include <linux/slab.h>
> #include <linux/workqueue.h>
> @@ -50,10 +51,17 @@
> * atomic_check when &drm_crtc_state.self_refresh_active is true.
> */
>
> +#define SELF_REFRESH_AVG_SEED_MS 200
> +
> +DECLARE_EWMA(psr_time, 4, 4)
> +
> struct drm_self_refresh_data {
> struct drm_crtc *crtc;
> struct delayed_work entry_work;
> - unsigned int entry_delay_ms;
> +
> + struct mutex avg_mutex;
> + struct ewma_psr_time entry_avg_ms;
> + struct ewma_psr_time exit_avg_ms;
> };
>
> static void drm_self_refresh_helper_entry_work(struct work_struct *work)
> @@ -121,6 +129,44 @@ static void drm_self_refresh_helper_entry_work(struct work_struct *work)
> drm_modeset_acquire_fini(&ctx);
> }
>
> +/**
> + * drm_self_refresh_helper_update_avg_times - Updates a crtc's SR time averages
> + * @state: the state which has just been applied to hardware
> + * @commit_time_ms: the amount of time in ms that this commit took to complete
> + *
> + * Called after &drm_mode_config_funcs.atomic_commit_tail, this function will
> + * update the average entry/exit self refresh times on self refresh transitions.
> + * These averages will be used when calculating how long to delay before
> + * entering self refresh mode after activity.
> + */
> +void drm_self_refresh_helper_update_avg_times(struct drm_atomic_state *state,
> + unsigned int commit_time_ms)
> +{
> + struct drm_crtc *crtc;
> + struct drm_crtc_state *old_crtc_state, *new_crtc_state;
> + int i;
> +
> + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
> + new_crtc_state, i) {
> + struct drm_self_refresh_data *sr_data = crtc->self_refresh_data;
> + struct ewma_psr_time *time;
> +
> + if (old_crtc_state->self_refresh_active ==
> + new_crtc_state->self_refresh_active)
> + continue;
> +
> + if (new_crtc_state->self_refresh_active)
> + time = &sr_data->entry_avg_ms;
> + else
> + time = &sr_data->exit_avg_ms;
> +
> + mutex_lock(&sr_data->avg_mutex);
> + ewma_psr_time_add(time, commit_time_ms);
> + mutex_unlock(&sr_data->avg_mutex);
> + }
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_update_avg_times);
> +
> /**
> * drm_self_refresh_helper_alter_state - Alters the atomic state for SR exit
> * @state: the state currently being checked
> @@ -152,6 +198,7 @@ void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state)
>
> for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> struct drm_self_refresh_data *sr_data;
> + unsigned int delay;
>
> /* Don't trigger the entry timer when we're already in SR */
> if (crtc_state->self_refresh_active)
> @@ -161,8 +208,13 @@ void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state)
> if (!sr_data)
> continue;
>
> + mutex_lock(&sr_data->avg_mutex);
> + delay = (ewma_psr_time_read(&sr_data->entry_avg_ms) +
> + ewma_psr_time_read(&sr_data->exit_avg_ms)) * 2;
> + mutex_unlock(&sr_data->avg_mutex);
> +
> mod_delayed_work(system_wq, &sr_data->entry_work,
> - msecs_to_jiffies(sr_data->entry_delay_ms));
> + msecs_to_jiffies(delay));
> }
> }
> EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> @@ -170,12 +222,10 @@ EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> /**
> * drm_self_refresh_helper_init - Initializes self refresh helpers for a crtc
> * @crtc: the crtc which supports self refresh supported displays
> - * @entry_delay_ms: amount of inactivity to wait before entering self refresh
> *
> * Returns zero if successful or -errno on failure
> */
> -int drm_self_refresh_helper_init(struct drm_crtc *crtc,
> - unsigned int entry_delay_ms)
> +int drm_self_refresh_helper_init(struct drm_crtc *crtc)
> {
> struct drm_self_refresh_data *sr_data = crtc->self_refresh_data;
>
> @@ -189,8 +239,18 @@ int drm_self_refresh_helper_init(struct drm_crtc *crtc,
>
> INIT_DELAYED_WORK(&sr_data->entry_work,
> drm_self_refresh_helper_entry_work);
> - sr_data->entry_delay_ms = entry_delay_ms;
> sr_data->crtc = crtc;
> + mutex_init(&sr_data->avg_mutex);
> + ewma_psr_time_init(&sr_data->entry_avg_ms);
> + ewma_psr_time_init(&sr_data->exit_avg_ms);
> +
> + /*
> + * Seed the averages so they're non-zero (and sufficiently large
> + * for even poorly performing panels). As time goes on, this will be
> + * averaged out and the values will trend to their true value.
> + */
> + ewma_psr_time_add(&sr_data->entry_avg_ms, SELF_REFRESH_AVG_SEED_MS);
> + ewma_psr_time_add(&sr_data->exit_avg_ms, SELF_REFRESH_AVG_SEED_MS);
>
> crtc->self_refresh_data = sr_data;
> return 0;
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> index 2f821c58007c..613404f86668 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> @@ -39,8 +39,6 @@
> #include "rockchip_drm_vop.h"
> #include "rockchip_rgb.h"
>
> -#define VOP_SELF_REFRESH_ENTRY_DELAY_MS 100
> -
> #define VOP_WIN_SET(vop, win, name, v) \
> vop_reg_set(vop, &win->phy->name, win->base, ~0, v, #name)
> #define VOP_SCL_SET(vop, win, name, v) \
> @@ -1563,8 +1561,7 @@ static int vop_create_crtc(struct vop *vop)
> init_completion(&vop->line_flag_completion);
> crtc->port = port;
>
> - ret = drm_self_refresh_helper_init(crtc,
> - VOP_SELF_REFRESH_ENTRY_DELAY_MS);
> + ret = drm_self_refresh_helper_init(crtc);
> if (ret)
> DRM_DEV_DEBUG_KMS(vop->dev,
> "Failed to init %s with SR helpers %d, ignoring\n",
> diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> index 397a583ccca7..5b79d253fb46 100644
> --- a/include/drm/drm_self_refresh_helper.h
> +++ b/include/drm/drm_self_refresh_helper.h
> @@ -12,9 +12,9 @@ struct drm_atomic_state;
> struct drm_crtc;
>
> void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> +void drm_self_refresh_helper_update_avg_times(struct drm_atomic_state *state,
> + unsigned int commit_time_ms);
>
> -int drm_self_refresh_helper_init(struct drm_crtc *crtc,
> - unsigned int entry_delay_ms);
> -
> +int drm_self_refresh_helper_init(struct drm_crtc *crtc);
> void drm_self_refresh_helper_cleanup(struct drm_crtc *crtc);
> #endif
> --
> Sean Paul, Software Engineer, Google / Chromium OS
>
--
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH 1/1] sched/rt: avoid contend with CFS task
From: Qais Yousef @ 2019-09-19 14:23 UTC (permalink / raw)
To: Vincent Guittot
Cc: wsd_upstream, Peter Zijlstra, linux-kernel, Jing-Ting Wu,
linux-mediatek, Matthias Brugger, Valentin Schneider, LAK
In-Reply-To: <CAKfTPtCuWrpW_o6r5cmGhLf_84PFHJhBk0pJ3fcbU_YgcBnTkQ@mail.gmail.com>
On 09/19/19 14:27, Vincent Guittot wrote:
> > > > But for requirement of performance, I think it is better to differentiate between idle CPU and CPU has CFS task.
> > > >
> > > > For example, we use rt-app to evaluate runnable time on non-patched environment.
> > > > There are (NR_CPUS-1) heavy CFS tasks and 1 RT Task. When a CFS task is running, the RT task wakes up and choose the same CPU.
> > > > The CFS task will be preempted and keep runnable until it is migrated to another cpu by load balance.
> > > > But load balance is not triggered immediately, it will be triggered until timer tick hits with some condition satisfied(ex. rq->next_balance).
> > >
> > > Yes you will have to wait for the next tick that will trigger an idle
> > > load balance because you have an idle cpu and 2 runnable tack (1 RT +
> > > 1CFS) on the same CPU. But you should not wait for more than 1 tick
> > >
> > > The current load_balance doesn't handle correctly the situation of 1
> > > CFS and 1 RT task on same CPU while 1 CPU is idle. There is a rework
> > > of the load_balance that is under review on the mailing list that
> > > fixes this problem and your CFS task should migrate to the idle CPU
> > > faster than now
> > >
> >
> > Period load balance should be triggered when current jiffies is behind
> > rq->next_balance, but rq->next_balance is not often exactly the same
> > with next tick.
> > If cpu_busy, interval = sd->balance_interval * sd->busy_factor, and
>
> But if there is an idle CPU on the system, the next idle load balance
> should apply shortly because the busy_factor is not used for this CPU
> which is not busy.
> In this case, the next_balance interval is sd_weight which is probably
> 4ms at cluster level and 8ms at system level in your case. This means
> between 1 and 2 ticks
But if the CFS task we're preempting was latency sensitive - this 1 or 2 tick
is too late of a recovery.
So while it's good we recover, but a preventative approach would be useful too.
Just saying :-) I'm still not sure if this is the best longer term approach.
--
Qais Yousef
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [RFC PATCH v2 01/11] devfreq: exynos-bus: Extract exynos_bus_profile_init()
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch adds a new static function, exynos_bus_profile_init(), extracted
from exynos_bus_probe().
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
---
drivers/devfreq/exynos-bus.c | 92 +++++++++++++++++++++---------------
1 file changed, 53 insertions(+), 39 deletions(-)
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c
index 29f422469960..78f38b7fb596 100644
--- a/drivers/devfreq/exynos-bus.c
+++ b/drivers/devfreq/exynos-bus.c
@@ -287,12 +287,62 @@ static int exynos_bus_parse_of(struct device_node *np,
return ret;
}
+static int exynos_bus_profile_init(struct exynos_bus *bus,
+ struct devfreq_dev_profile *profile)
+{
+ struct device *dev = bus->dev;
+ struct devfreq_simple_ondemand_data *ondemand_data;
+ int ret;
+
+ /* Initialize the struct profile and governor data for parent device */
+ profile->polling_ms = 50;
+ profile->target = exynos_bus_target;
+ profile->get_dev_status = exynos_bus_get_dev_status;
+ profile->exit = exynos_bus_exit;
+
+ ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
+ if (!ondemand_data) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ ondemand_data->upthreshold = 40;
+ ondemand_data->downdifferential = 5;
+
+ /* Add devfreq device to monitor and handle the exynos bus */
+ bus->devfreq = devm_devfreq_add_device(dev, profile,
+ DEVFREQ_GOV_SIMPLE_ONDEMAND,
+ ondemand_data);
+ if (IS_ERR(bus->devfreq)) {
+ dev_err(dev, "failed to add devfreq device\n");
+ ret = PTR_ERR(bus->devfreq);
+ goto err;
+ }
+
+ /*
+ * Enable devfreq-event to get raw data which is used to determine
+ * current bus load.
+ */
+ ret = exynos_bus_enable_edev(bus);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable devfreq-event devices\n");
+ goto err;
+ }
+
+ ret = exynos_bus_set_event(bus);
+ if (ret < 0) {
+ dev_err(dev, "failed to set event to devfreq-event devices\n");
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
static int exynos_bus_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node, *node;
struct devfreq_dev_profile *profile;
- struct devfreq_simple_ondemand_data *ondemand_data;
struct devfreq_passive_data *passive_data;
struct devfreq *parent_devfreq;
struct exynos_bus *bus;
@@ -334,45 +384,9 @@ static int exynos_bus_probe(struct platform_device *pdev)
if (passive)
goto passive;
- /* Initialize the struct profile and governor data for parent device */
- profile->polling_ms = 50;
- profile->target = exynos_bus_target;
- profile->get_dev_status = exynos_bus_get_dev_status;
- profile->exit = exynos_bus_exit;
-
- ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
- if (!ondemand_data) {
- ret = -ENOMEM;
+ ret = exynos_bus_profile_init(bus, profile);
+ if (ret < 0)
goto err;
- }
- ondemand_data->upthreshold = 40;
- ondemand_data->downdifferential = 5;
-
- /* Add devfreq device to monitor and handle the exynos bus */
- bus->devfreq = devm_devfreq_add_device(dev, profile,
- DEVFREQ_GOV_SIMPLE_ONDEMAND,
- ondemand_data);
- if (IS_ERR(bus->devfreq)) {
- dev_err(dev, "failed to add devfreq device\n");
- ret = PTR_ERR(bus->devfreq);
- goto err;
- }
-
- /*
- * Enable devfreq-event to get raw data which is used to determine
- * current bus load.
- */
- ret = exynos_bus_enable_edev(bus);
- if (ret < 0) {
- dev_err(dev, "failed to enable devfreq-event devices\n");
- goto err;
- }
-
- ret = exynos_bus_set_event(bus);
- if (ret < 0) {
- dev_err(dev, "failed to set event to devfreq-event devices\n");
- goto err;
- }
goto out;
passive:
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 00/11] Simple QoS for exynos-bus driver using interconnect
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: b.zolnierkie, sw0312.kim, Artur Świgoń, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <CGME20190919142321eucas1p164c2591ad402427cb71fd00c348a29ec@eucas1p1.samsung.com>
The following patchset adds interconnect[1][2] framework support to the
exynos-bus devfreq driver. Extending the devfreq driver with interconnect
capabilities started as a response to the issue referenced in [3]. The
patches can be subdivided into four logical groups:
(a) Refactoring the existing devfreq driver in order to improve readability
and accommodate for adding new code (patches 01--04/11).
(b) Tweaking the interconnect framework to support the exynos-bus use case
(patches 05--07/11). Exporting of_icc_get_from_provider() allows us to
avoid hardcoding every single graph edge in the DT or driver source, and
relaxing the requirement contained in that function removes the need to
provide dummy node IDs in the DT. Adjusting the logic in
apply_constraints() (drivers/interconnect/core.c) accounts for the fact
that every bus is a separate entity and therefore a separate interconnect
provider, albeit constituting a part of a larger hierarchy.
(c) Implementing interconnect providers in the exynos-bus devfreq driver
and adding required DT properties for one selected platform, namely
Exynos4412 (patches 08--09/11). Due to the fact that this aims to be a
generic driver for various Exynos SoCs, node IDs are generated dynamically
rather than hardcoded. This has been determined to be a simpler approach,
but depends on changes described in (b).
(d) Implementing a sample interconnect consumer for exynos-mixer targeted
at the issue referenced in [3], again with DT info only for Exynos4412
(patches 10--11/11).
Integration of devfreq and interconnect functionalities is achieved by
using dev_pm_qos_*() API[5]. All new code works equally well when
CONFIG_INTERCONNECT is 'n' (as in exynos_defconfig) in which case all
interconnect API functions are no-ops.
This patchset depends on [5].
--- Changes since v1 [6]:
* Rebase on [4] (coupled regulators).
* Rebase on [5] (dev_pm_qos for devfreq).
* Use dev_pm_qos_*() API[5] instead of overriding frequency in
exynos_bus_target().
* Use IDR for node ID allocation.
* Avoid goto in functions extracted in patches 01 & 02 (cf. patch 04).
* Reverse order of multiplication and division in
mixer_set_memory_bandwidth() (patch 11) to avoid integer overflow.
---
Artur Świgoń
Samsung R&D Institute Poland
Samsung Electronics
---
References:
[1] Documentation/interconnect/interconnect.rst
[2] Documentation/devicetree/bindings/interconnect/interconnect.txt
[3] https://patchwork.kernel.org/patch/10861757/ (original issue)
[4] https://patchwork.kernel.org/cover/11083663/ (coupled regulators; merged)
[5] https://patchwork.kernel.org/cover/11149497/ (dev_pm_qos for devfreq)
[6] https://patchwork.kernel.org/cover/11054417/ (v1 of this RFC)
Artur Świgoń (10):
devfreq: exynos-bus: Extract exynos_bus_profile_init()
devfreq: exynos-bus: Extract exynos_bus_profile_init_passive()
devfreq: exynos-bus: Change goto-based logic to if-else logic
devfreq: exynos-bus: Clean up code
interconnect: Export of_icc_get_from_provider()
interconnect: Relax requirement in of_icc_get_from_provider()
interconnect: Relax condition in apply_constraints()
arm: dts: exynos: Add parents and #interconnect-cells to Exynos4412
devfreq: exynos-bus: Add interconnect functionality to exynos-bus
arm: dts: exynos: Add interconnects to Exynos4412 mixer
Marek Szyprowski (1):
drm: exynos: mixer: Add interconnect support
.../boot/dts/exynos4412-odroid-common.dtsi | 1 +
arch/arm/boot/dts/exynos4412.dtsi | 10 +
drivers/devfreq/exynos-bus.c | 319 +++++++++++++-----
drivers/gpu/drm/exynos/exynos_mixer.c | 71 +++-
drivers/interconnect/core.c | 12 +-
include/linux/interconnect-provider.h | 6 +
6 files changed, 327 insertions(+), 92 deletions(-)
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [RFC PATCH v2 03/11] devfreq: exynos-bus: Change goto-based logic to if-else logic
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch improves code readability by changing the following construct:
> if (cond)
> goto passive;
> foo();
> goto out;
>passive:
> bar();
>out:
into this:
> if (cond)
> bar();
> else
> foo();
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
---
drivers/devfreq/exynos-bus.c | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c
index f85bed241631..60ad4319fd80 100644
--- a/drivers/devfreq/exynos-bus.c
+++ b/drivers/devfreq/exynos-bus.c
@@ -420,19 +420,13 @@ static int exynos_bus_probe(struct platform_device *pdev)
goto err_reg;
if (passive)
- goto passive;
+ ret = exynos_bus_profile_init_passive(bus, profile);
+ else
+ ret = exynos_bus_profile_init(bus, profile);
- ret = exynos_bus_profile_init(bus, profile);
if (ret < 0)
goto err;
- goto out;
-passive:
- ret = exynos_bus_profile_init_passive(bus, profile);
- if (ret < 0)
- goto err;
-
-out:
max_state = bus->devfreq->profile->max_state;
min_freq = (bus->devfreq->profile->freq_table[0] / 1000);
max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000);
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 02/11] devfreq: exynos-bus: Extract exynos_bus_profile_init_passive()
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch adds a new static function, exynos_bus_profile_init_passive(),
extracted from exynos_bus_probe().
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
---
drivers/devfreq/exynos-bus.c | 70 +++++++++++++++++++++---------------
1 file changed, 42 insertions(+), 28 deletions(-)
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c
index 78f38b7fb596..f85bed241631 100644
--- a/drivers/devfreq/exynos-bus.c
+++ b/drivers/devfreq/exynos-bus.c
@@ -338,13 +338,51 @@ static int exynos_bus_profile_init(struct exynos_bus *bus,
return ret;
}
+static int exynos_bus_profile_init_passive(struct exynos_bus *bus,
+ struct devfreq_dev_profile *profile)
+{
+ struct device *dev = bus->dev;
+ struct devfreq_passive_data *passive_data;
+ struct devfreq *parent_devfreq;
+ int ret = 0;
+
+ /* Initialize the struct profile and governor data for passive device */
+ profile->target = exynos_bus_target;
+ profile->exit = exynos_bus_passive_exit;
+
+ /* Get the instance of parent devfreq device */
+ parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
+ if (IS_ERR(parent_devfreq)) {
+ ret = -EPROBE_DEFER;
+ goto err;
+ }
+
+ passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL);
+ if (!passive_data) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ passive_data->parent = parent_devfreq;
+
+ /* Add devfreq device for exynos bus with passive governor */
+ bus->devfreq = devm_devfreq_add_device(dev, profile, DEVFREQ_GOV_PASSIVE,
+ passive_data);
+ if (IS_ERR(bus->devfreq)) {
+ dev_err(dev,
+ "failed to add devfreq dev with passive governor\n");
+ ret = PTR_ERR(bus->devfreq);
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
static int exynos_bus_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node, *node;
struct devfreq_dev_profile *profile;
- struct devfreq_passive_data *passive_data;
- struct devfreq *parent_devfreq;
struct exynos_bus *bus;
int ret, max_state;
unsigned long min_freq, max_freq;
@@ -390,33 +428,9 @@ static int exynos_bus_probe(struct platform_device *pdev)
goto out;
passive:
- /* Initialize the struct profile and governor data for passive device */
- profile->target = exynos_bus_target;
- profile->exit = exynos_bus_passive_exit;
-
- /* Get the instance of parent devfreq device */
- parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
- if (IS_ERR(parent_devfreq)) {
- ret = -EPROBE_DEFER;
+ ret = exynos_bus_profile_init_passive(bus, profile);
+ if (ret < 0)
goto err;
- }
-
- passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL);
- if (!passive_data) {
- ret = -ENOMEM;
- goto err;
- }
- passive_data->parent = parent_devfreq;
-
- /* Add devfreq device for exynos bus with passive governor */
- bus->devfreq = devm_devfreq_add_device(dev, profile, DEVFREQ_GOV_PASSIVE,
- passive_data);
- if (IS_ERR(bus->devfreq)) {
- dev_err(dev,
- "failed to add devfreq dev with passive governor\n");
- ret = PTR_ERR(bus->devfreq);
- goto err;
- }
out:
max_state = bus->devfreq->profile->max_state;
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 04/11] devfreq: exynos-bus: Clean up code
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch adds minor improvements to the exynos-bus driver.
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>
---
drivers/devfreq/exynos-bus.c | 66 ++++++++++++++----------------------
1 file changed, 25 insertions(+), 41 deletions(-)
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c
index 60ad4319fd80..8d44810cac69 100644
--- a/drivers/devfreq/exynos-bus.c
+++ b/drivers/devfreq/exynos-bus.c
@@ -15,11 +15,10 @@
#include <linux/device.h>
#include <linux/export.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/pm_opp.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
-#include <linux/slab.h>
#define DEFAULT_SATURATION_RATIO 40
@@ -178,7 +177,7 @@ static int exynos_bus_parent_parse_of(struct device_node *np,
struct device *dev = bus->dev;
struct opp_table *opp_table;
const char *vdd = "vdd";
- int i, ret, count, size;
+ int i, ret, count;
opp_table = dev_pm_opp_set_regulators(dev, &vdd, 1);
if (IS_ERR(opp_table)) {
@@ -201,8 +200,7 @@ static int exynos_bus_parent_parse_of(struct device_node *np,
}
bus->edev_count = count;
- size = sizeof(*bus->edev) * count;
- bus->edev = devm_kzalloc(dev, size, GFP_KERNEL);
+ bus->edev = devm_kcalloc(dev, count, sizeof(*bus->edev), GFP_KERNEL);
if (!bus->edev) {
ret = -ENOMEM;
goto err_regulator;
@@ -301,10 +299,9 @@ static int exynos_bus_profile_init(struct exynos_bus *bus,
profile->exit = exynos_bus_exit;
ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
- if (!ondemand_data) {
- ret = -ENOMEM;
- goto err;
- }
+ if (!ondemand_data)
+ return -ENOMEM;
+
ondemand_data->upthreshold = 40;
ondemand_data->downdifferential = 5;
@@ -314,8 +311,7 @@ static int exynos_bus_profile_init(struct exynos_bus *bus,
ondemand_data);
if (IS_ERR(bus->devfreq)) {
dev_err(dev, "failed to add devfreq device\n");
- ret = PTR_ERR(bus->devfreq);
- goto err;
+ return PTR_ERR(bus->devfreq);
}
/*
@@ -325,16 +321,13 @@ static int exynos_bus_profile_init(struct exynos_bus *bus,
ret = exynos_bus_enable_edev(bus);
if (ret < 0) {
dev_err(dev, "failed to enable devfreq-event devices\n");
- goto err;
+ return ret;
}
ret = exynos_bus_set_event(bus);
- if (ret < 0) {
+ if (ret < 0)
dev_err(dev, "failed to set event to devfreq-event devices\n");
- goto err;
- }
-err:
return ret;
}
@@ -344,7 +337,6 @@ static int exynos_bus_profile_init_passive(struct exynos_bus *bus,
struct device *dev = bus->dev;
struct devfreq_passive_data *passive_data;
struct devfreq *parent_devfreq;
- int ret = 0;
/* Initialize the struct profile and governor data for passive device */
profile->target = exynos_bus_target;
@@ -352,30 +344,26 @@ static int exynos_bus_profile_init_passive(struct exynos_bus *bus,
/* Get the instance of parent devfreq device */
parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
- if (IS_ERR(parent_devfreq)) {
- ret = -EPROBE_DEFER;
- goto err;
- }
+ if (IS_ERR(parent_devfreq))
+ return -EPROBE_DEFER;
passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL);
- if (!passive_data) {
- ret = -ENOMEM;
- goto err;
- }
+ if (!passive_data)
+ return -ENOMEM;
+
passive_data->parent = parent_devfreq;
/* Add devfreq device for exynos bus with passive governor */
- bus->devfreq = devm_devfreq_add_device(dev, profile, DEVFREQ_GOV_PASSIVE,
+ bus->devfreq = devm_devfreq_add_device(dev, profile,
+ DEVFREQ_GOV_PASSIVE,
passive_data);
if (IS_ERR(bus->devfreq)) {
dev_err(dev,
"failed to add devfreq dev with passive governor\n");
- ret = PTR_ERR(bus->devfreq);
- goto err;
+ return PTR_ERR(bus->devfreq);
}
-err:
- return ret;
+ return 0;
}
static int exynos_bus_probe(struct platform_device *pdev)
@@ -393,18 +381,18 @@ static int exynos_bus_probe(struct platform_device *pdev)
return -EINVAL;
}
- bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+ bus = devm_kzalloc(dev, sizeof(*bus), GFP_KERNEL);
if (!bus)
return -ENOMEM;
mutex_init(&bus->lock);
- bus->dev = &pdev->dev;
+ bus->dev = dev;
platform_set_drvdata(pdev, bus);
profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL);
if (!profile)
return -ENOMEM;
- node = of_parse_phandle(dev->of_node, "devfreq", 0);
+ node = of_parse_phandle(np, "devfreq", 0);
if (node) {
of_node_put(node);
passive = true;
@@ -461,12 +449,10 @@ static int exynos_bus_resume(struct device *dev)
int ret;
ret = exynos_bus_enable_edev(bus);
- if (ret < 0) {
+ if (ret < 0)
dev_err(dev, "failed to enable the devfreq-event devices\n");
- return ret;
- }
- return 0;
+ return ret;
}
static int exynos_bus_suspend(struct device *dev)
@@ -475,12 +461,10 @@ static int exynos_bus_suspend(struct device *dev)
int ret;
ret = exynos_bus_disable_edev(bus);
- if (ret < 0) {
+ if (ret < 0)
dev_err(dev, "failed to disable the devfreq-event devices\n");
- return ret;
- }
- return 0;
+ return ret;
}
#endif
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 05/11] interconnect: Export of_icc_get_from_provider()
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch makes the above function public (for use in exynos-bus devfreq
driver).
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>
---
drivers/interconnect/core.c | 3 ++-
include/linux/interconnect-provider.h | 6 ++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index 7b971228df38..95850700e367 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -279,7 +279,7 @@ EXPORT_SYMBOL_GPL(of_icc_xlate_onecell);
* Returns a valid pointer to struct icc_node on success or ERR_PTR()
* on failure.
*/
-static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
+struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
{
struct icc_node *node = ERR_PTR(-EPROBE_DEFER);
struct icc_provider *provider;
@@ -298,6 +298,7 @@ static struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
return node;
}
+EXPORT_SYMBOL_GPL(of_icc_get_from_provider);
/**
* of_icc_get() - get a path handle from a DT node based on name
diff --git a/include/linux/interconnect-provider.h b/include/linux/interconnect-provider.h
index b16f9effa555..070e411564e1 100644
--- a/include/linux/interconnect-provider.h
+++ b/include/linux/interconnect-provider.h
@@ -100,6 +100,7 @@ void icc_node_add(struct icc_node *node, struct icc_provider *provider);
void icc_node_del(struct icc_node *node);
int icc_provider_add(struct icc_provider *provider);
int icc_provider_del(struct icc_provider *provider);
+struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec);
#else
@@ -140,6 +141,11 @@ static inline int icc_provider_del(struct icc_provider *provider)
return -ENOTSUPP;
}
+struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
+{
+ return ERR_PTR(-ENOTSUPP);
+}
+
#endif /* CONFIG_INTERCONNECT */
#endif /* __LINUX_INTERCONNECT_PROVIDER_H */
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 06/11] interconnect: Relax requirement in of_icc_get_from_provider()
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch relaxes the condition in of_icc_get_from_provider() so that it
is no longer required to set #interconnect-cells = <1> in the DT. In case
of the devfreq driver for exynos-bus, #interconnect-cells is always zero.
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
Acked-by: Krzysztof Kozlowski <krzk@kernel.org>
---
drivers/interconnect/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index 95850700e367..f357c3a78858 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -284,7 +284,7 @@ struct icc_node *of_icc_get_from_provider(struct of_phandle_args *spec)
struct icc_node *node = ERR_PTR(-EPROBE_DEFER);
struct icc_provider *provider;
- if (!spec || spec->args_count != 1)
+ if (!spec)
return ERR_PTR(-EINVAL);
mutex_lock(&icc_lock);
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 07/11] interconnect: Relax condition in apply_constraints()
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
The exynos-bus devfreq driver is extended with interconnect functionality
by a subsequent patch. This patch removes a check from the interconnect
framework that prevents interconnect from working on exynos-bus, in which
every bus is a separate interconnect provider.
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
---
drivers/interconnect/core.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
index f357c3a78858..e8243665d5ba 100644
--- a/drivers/interconnect/core.c
+++ b/drivers/interconnect/core.c
@@ -224,11 +224,8 @@ static int apply_constraints(struct icc_path *path)
for (i = 0; i < path->num_nodes; i++) {
next = path->reqs[i].node;
- /*
- * Both endpoints should be valid master-slave pairs of the
- * same interconnect provider that will be configured.
- */
- if (!prev || next->provider != prev->provider) {
+ /* both endpoints should be valid master-slave pairs */
+ if (!prev) {
prev = next;
continue;
}
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 08/11] arm: dts: exynos: Add parents and #interconnect-cells to Exynos4412
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch adds two fields to the Exynos4412 DTS:
- parent: to declare connections between nodes that are not in a
parent-child relation in devfreq;
- #interconnect-cells: required by the interconnect framework.
Please note that #interconnect-cells is always zero and node IDs are not
hardcoded anywhere. The above-mentioned parent-child relation in devfreq
means that there is a shared power line ('devfreq' property). The 'parent'
property only signifies an interconnect connection.
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
---
arch/arm/boot/dts/exynos4412-odroid-common.dtsi | 1 +
arch/arm/boot/dts/exynos4412.dtsi | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/arch/arm/boot/dts/exynos4412-odroid-common.dtsi b/arch/arm/boot/dts/exynos4412-odroid-common.dtsi
index ea55f377d17c..bdd61ae86103 100644
--- a/arch/arm/boot/dts/exynos4412-odroid-common.dtsi
+++ b/arch/arm/boot/dts/exynos4412-odroid-common.dtsi
@@ -106,6 +106,7 @@
&bus_leftbus {
devfreq-events = <&ppmu_leftbus_3>, <&ppmu_rightbus_3>;
vdd-supply = <&buck3_reg>;
+ parent = <&bus_dmc>;
status = "okay";
};
diff --git a/arch/arm/boot/dts/exynos4412.dtsi b/arch/arm/boot/dts/exynos4412.dtsi
index d20db2dfe8e2..a70a671acacd 100644
--- a/arch/arm/boot/dts/exynos4412.dtsi
+++ b/arch/arm/boot/dts/exynos4412.dtsi
@@ -390,6 +390,7 @@
clocks = <&clock CLK_DIV_DMC>;
clock-names = "bus";
operating-points-v2 = <&bus_dmc_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -398,6 +399,7 @@
clocks = <&clock CLK_DIV_ACP>;
clock-names = "bus";
operating-points-v2 = <&bus_acp_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -406,6 +408,7 @@
clocks = <&clock CLK_DIV_C2C>;
clock-names = "bus";
operating-points-v2 = <&bus_dmc_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -459,6 +462,7 @@
clocks = <&clock CLK_DIV_GDL>;
clock-names = "bus";
operating-points-v2 = <&bus_leftbus_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -467,6 +471,7 @@
clocks = <&clock CLK_DIV_GDR>;
clock-names = "bus";
operating-points-v2 = <&bus_leftbus_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -475,6 +480,7 @@
clocks = <&clock CLK_ACLK160>;
clock-names = "bus";
operating-points-v2 = <&bus_display_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -483,6 +489,7 @@
clocks = <&clock CLK_ACLK133>;
clock-names = "bus";
operating-points-v2 = <&bus_fsys_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -491,6 +498,7 @@
clocks = <&clock CLK_ACLK100>;
clock-names = "bus";
operating-points-v2 = <&bus_peri_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
@@ -499,6 +507,7 @@
clocks = <&clock CLK_SCLK_MFC>;
clock-names = "bus";
operating-points-v2 = <&bus_leftbus_opp_table>;
+ #interconnect-cells = <0>;
status = "disabled";
};
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply related
* [RFC PATCH v2 09/11] devfreq: exynos-bus: Add interconnect functionality to exynos-bus
From: Artur Świgoń @ 2019-09-19 14:22 UTC (permalink / raw)
To: devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
linux-pm, dri-devel
Cc: Artur Świgoń, b.zolnierkie, sw0312.kim, krzk, inki.dae,
cw00.choi, myungjoo.ham, leonard.crestez, georgi.djakov,
m.szyprowski
In-Reply-To: <20190919142236.4071-1-a.swigon@samsung.com>
From: Artur Świgoń <a.swigon@partner.samsung.com>
This patch adds interconnect functionality to the exynos-bus devfreq
driver.
The SoC topology is a graph (or, more specifically, a tree) and most of
its edges are taken from the devfreq parent-child hierarchy (cf.
Documentation/devicetree/bindings/devfreq/exynos-bus.txt). Due to
unspecified relative probing order, -EPROBE_DEFER may be propagated to
guarantee that a child is probed before its parent.
Each bus is now an interconnect provider and an interconnect node as well
(cf. Documentation/interconnect/interconnect.rst), i.e. every bus registers
itself as a node. Node IDs are not hardcoded but rather assigned at
runtime, in probing order (subject to the above-mentioned exception
regarding relative order). This approach allows for using this driver with
various Exynos SoCs.
Frequencies requested via the interconnect API for a given node are
propagated to devfreq using dev_pm_qos_update_request(). Please note that
it is not an error when CONFIG_INTERCONNECT is 'n', in which case all
interconnect API functions are no-op.
Signed-off-by: Artur Świgoń <a.swigon@partner.samsung.com>
---
drivers/devfreq/exynos-bus.c | 153 +++++++++++++++++++++++++++++++++++
1 file changed, 153 insertions(+)
diff --git a/drivers/devfreq/exynos-bus.c b/drivers/devfreq/exynos-bus.c
index 8d44810cac69..e0232202720d 100644
--- a/drivers/devfreq/exynos-bus.c
+++ b/drivers/devfreq/exynos-bus.c
@@ -14,14 +14,19 @@
#include <linux/devfreq-event.h>
#include <linux/device.h>
#include <linux/export.h>
+#include <linux/idr.h>
+#include <linux/interconnect-provider.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#define DEFAULT_SATURATION_RATIO 40
+#define icc_units_to_khz(x) ((x) / 8)
+
struct exynos_bus {
struct device *dev;
@@ -35,6 +40,12 @@ struct exynos_bus {
struct opp_table *opp_table;
struct clk *clk;
unsigned int ratio;
+
+ /* One provider per bus, one node per provider */
+ struct icc_provider provider;
+ struct icc_node *node;
+
+ struct dev_pm_qos_request qos_req;
};
/*
@@ -59,6 +70,13 @@ exynos_bus_ops_edev(enable_edev);
exynos_bus_ops_edev(disable_edev);
exynos_bus_ops_edev(set_event);
+static int exynos_bus_next_id(void)
+{
+ static DEFINE_IDA(exynos_bus_icc_ida);
+
+ return ida_alloc(&exynos_bus_icc_ida, GFP_KERNEL);
+}
+
static int exynos_bus_get_event(struct exynos_bus *bus,
struct devfreq_event_data *edata)
{
@@ -171,6 +189,38 @@ static void exynos_bus_passive_exit(struct device *dev)
clk_disable_unprepare(bus->clk);
}
+static int exynos_bus_icc_set(struct icc_node *src, struct icc_node *dst)
+{
+ struct exynos_bus *src_bus = src->data, *dst_bus = dst->data;
+ s32 src_freq = icc_units_to_khz(src->avg_bw);
+ s32 dst_freq = icc_units_to_khz(dst->avg_bw);
+
+ dev_pm_qos_update_request(&src_bus->qos_req, src_freq);
+ dev_pm_qos_update_request(&dst_bus->qos_req, dst_freq);
+
+ return 0;
+}
+
+static int exynos_bus_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw,
+ u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
+{
+ *agg_avg += avg_bw;
+ *agg_peak = max(*agg_peak, peak_bw);
+
+ return 0;
+}
+
+static struct icc_node *exynos_bus_icc_xlate(struct of_phandle_args *spec,
+ void *data)
+{
+ struct exynos_bus *bus = data;
+
+ if (spec->np != bus->dev->of_node)
+ return ERR_PTR(-EINVAL);
+
+ return bus->node;
+}
+
static int exynos_bus_parent_parse_of(struct device_node *np,
struct exynos_bus *bus)
{
@@ -366,6 +416,101 @@ static int exynos_bus_profile_init_passive(struct exynos_bus *bus,
return 0;
}
+static int exynos_bus_icc_connect(struct exynos_bus *bus)
+{
+ struct device_node *np = bus->dev->of_node;
+ struct devfreq *parent_devfreq;
+ struct icc_node *parent_node = NULL;
+ struct of_phandle_args args;
+ int ret = 0;
+
+ parent_devfreq = devfreq_get_devfreq_by_phandle(bus->dev, 0);
+ if (!IS_ERR(parent_devfreq)) {
+ struct exynos_bus *parent_bus;
+
+ parent_bus = dev_get_drvdata(parent_devfreq->dev.parent);
+ parent_node = parent_bus->node;
+ } else {
+ /* Look for parent in DT */
+ int num = of_count_phandle_with_args(np, "parent",
+ "#interconnect-cells");
+ if (num != 1)
+ goto out; /* 'parent' is optional */
+
+ ret = of_parse_phandle_with_args(np, "parent",
+ "#interconnect-cells",
+ 0, &args);
+ if (ret < 0)
+ goto out;
+
+ of_node_put(args.np);
+
+ parent_node = of_icc_get_from_provider(&args);
+ if (IS_ERR(parent_node)) {
+ /* May be -EPROBE_DEFER */
+ ret = PTR_ERR(parent_node);
+ goto out;
+ }
+ }
+
+ ret = icc_link_create(bus->node, parent_node->id);
+
+out:
+ return ret;
+}
+
+static int exynos_bus_icc_init(struct exynos_bus *bus)
+{
+ struct device *dev = bus->dev;
+ struct icc_provider *provider = &bus->provider;
+ struct icc_node *node;
+ int id, ret;
+
+ /* Initialize the interconnect provider */
+ provider->set = exynos_bus_icc_set;
+ provider->aggregate = exynos_bus_icc_aggregate;
+ provider->xlate = exynos_bus_icc_xlate;
+ provider->dev = dev;
+ provider->data = bus;
+
+ ret = icc_provider_add(provider);
+ if (ret < 0)
+ goto out;
+
+ ret = id = exynos_bus_next_id();
+ if (ret < 0)
+ goto err_node;
+
+ node = icc_node_create(id);
+ if (IS_ERR(node)) {
+ ret = PTR_ERR(node);
+ goto err_node;
+ }
+
+ bus->node = node;
+ node->name = dev->of_node->name;
+ node->data = bus;
+ icc_node_add(node, provider);
+
+ ret = exynos_bus_icc_connect(bus);
+ if (ret < 0)
+ goto err_connect;
+
+ ret = dev_pm_qos_add_request(bus->devfreq->dev.parent, &bus->qos_req,
+ DEV_PM_QOS_MIN_FREQUENCY, 0);
+
+out:
+ return ret;
+
+err_connect:
+ icc_node_del(node);
+ icc_node_destroy(id);
+err_node:
+ icc_provider_del(provider);
+
+ return ret;
+}
+
static int exynos_bus_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -415,6 +560,14 @@ static int exynos_bus_probe(struct platform_device *pdev)
if (ret < 0)
goto err;
+ /*
+ * Initialize interconnect provider. A return value of -ENOTSUPP means
+ * that CONFIG_INTERCONNECT is disabled.
+ */
+ ret = exynos_bus_icc_init(bus);
+ if (ret < 0 && ret != -ENOTSUPP)
+ goto err;
+
max_state = bus->devfreq->profile->max_state;
min_freq = (bus->devfreq->profile->freq_table[0] / 1000);
max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000);
--
2.17.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ 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