* [GIT PULL 2/5] memory: tegra: Changes for v4.18-rc1
From: Thierry Reding @ 2018-05-18 14:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180518142245.20242-1-thierry.reding@gmail.com>
Hi ARM SoC maintainers,
The following changes since commit 60cc43fc888428bb2f18f08997432d426a243338:
Linux 4.17-rc1 (2018-04-15 18:24:20 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git tags/tegra-for-4.18-memory
for you to fetch changes up to bef89a8d81ca97aca864778746b110cf52847868:
memory: tegra: Remove Tegra114 SATA and AFI reset definitions (2018-05-18 12:33:02 +0200)
Thanks,
Thierry
----------------------------------------------------------------
memory: tegra: Changes for v4.18-rc1
This contains some cleanup of the memory controller driver as well as
unification work to share more code between Tegra20 and later SoC
generations. Also included are an implementation for the hot resets
functionality by the memory controller which is required to properly
reset busy hardware.
----------------------------------------------------------------
Dmitry Osipenko (14):
dt-bindings: memory: tegra: Add hot resets definitions
memory: tegra: Do not handle spurious interrupts
memory: tegra: Setup interrupts mask before requesting IRQ
memory: tegra: Apply interrupts mask per SoC
memory: tegra: Remove unused headers inclusions
memory: tegra: Squash tegra20-mc into common tegra-mc driver
memory: tegra: Introduce memory client hot reset
memory: tegra: Add Tegra20 memory controller hot resets
memory: tegra: Add Tegra30 memory controller hot resets
memory: tegra: Add Tegra114 memory controller hot resets
memory: tegra: Add Tegra124 memory controller hot resets
memory: tegra: Register SMMU after MC driver became ready
dt-bindings: memory: tegra: Remove Tegra114 SATA and AFI reset definitions
memory: tegra: Remove Tegra114 SATA and AFI reset definitions
Thierry Reding (1):
memory: tegra: Add Tegra210 memory controller hot resets
drivers/memory/Kconfig | 10 -
drivers/memory/Makefile | 1 -
drivers/memory/tegra/Makefile | 1 +
drivers/memory/tegra/mc.c | 362 +++++++++++++++++++++++++++----
drivers/memory/tegra/mc.h | 22 ++
drivers/memory/tegra/tegra114.c | 33 +++
drivers/memory/tegra/tegra124.c | 48 ++++
drivers/memory/tegra/tegra20.c | 296 +++++++++++++++++++++++++
drivers/memory/tegra/tegra210.c | 53 ++++-
drivers/memory/tegra/tegra30.c | 35 +++
drivers/memory/tegra20-mc.c | 254 ----------------------
include/dt-bindings/memory/tegra114-mc.h | 17 ++
include/dt-bindings/memory/tegra124-mc.h | 25 +++
include/dt-bindings/memory/tegra20-mc.h | 21 ++
include/dt-bindings/memory/tegra210-mc.h | 31 +++
include/dt-bindings/memory/tegra30-mc.h | 19 ++
include/soc/tegra/mc.h | 37 +++-
17 files changed, 956 insertions(+), 309 deletions(-)
create mode 100644 drivers/memory/tegra/tegra20.c
delete mode 100644 drivers/memory/tegra20-mc.c
create mode 100644 include/dt-bindings/memory/tegra20-mc.h
^ permalink raw reply
* [GIT PULL 1/5] dt-bindings: tegra: Changes for v4.18-rc1
From: Thierry Reding @ 2018-05-18 14:22 UTC (permalink / raw)
To: linux-arm-kernel
Hi ARM SoC maintainers,
The following changes since commit 60cc43fc888428bb2f18f08997432d426a243338:
Linux 4.17-rc1 (2018-04-15 18:24:20 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git tags/tegra-for-4.18-dt-bindings
for you to fetch changes up to eb8f53b6d3125894e3d825976eb7e03150496362:
dt-bindings: Relocate Tegra20 memory controller bindings (2018-04-27 11:15:51 +0200)
Thanks,
Thierry
----------------------------------------------------------------
dt-bindings: tegra: Changes for v4.18-rc1
This contains the device tree bindings updates for the memory controller
hot resets that are implemented by driver patches in a different branch.
----------------------------------------------------------------
Dmitry Osipenko (3):
dt-bindings: arm: tegra: Remove duplicated Tegra30+ MC binding
dt-bindings: memory: tegra: Document #reset-cells property of the Tegra30 MC
dt-bindings: arm: tegra: Document #reset-cells property of the Tegra20 MC
Thierry Reding (1):
dt-bindings: Relocate Tegra20 memory controller bindings
.../bindings/arm/tegra/nvidia,tegra30-mc.txt | 18 ------------------
.../tegra => memory-controllers}/nvidia,tegra20-mc.txt | 12 +++++++++++-
.../bindings/memory-controllers/nvidia,tegra30-mc.txt | 5 +++++
3 files changed, 16 insertions(+), 19 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/arm/tegra/nvidia,tegra30-mc.txt
rename Documentation/devicetree/bindings/{arm/tegra => memory-controllers}/nvidia,tegra20-mc.txt (57%)
^ permalink raw reply
* [PATCH v5 4/9] dt-bindings: input: touchscreen: resistive-adc-touch: create bindings
From: Rob Herring @ 2018-05-18 14:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1525949114-29263-5-git-send-email-eugen.hristev@microchip.com>
On Thu, May 10, 2018 at 01:45:09PM +0300, Eugen Hristev wrote:
> Added bindings for generic resistive touchscreen ADC.
>
> Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>
> ---
> Changes in v5:
> - changed property name touchscreen-threshold-pressure to
> touchscreen-min-pressure
>
> Changes in v3:
> - renamed file and compatible to exclude "generic" keyword
> - removed the pressure threshold property, added it as a common
> touchscreen property in the touchscreen common bindings in a separate
> commit.
>
> Changes in v2:
> - modified bindings to have a generic resistive touchscreen adc driver
> instead of specific architecture one.
>
>
> .../input/touchscreen/resistive-adc-touch.txt | 30 ++++++++++++++++++++++
> 1 file changed, 30 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/touchscreen/resistive-adc-touch.txt
Reviewed-by: Rob Herring <robh@kernel.org>
^ permalink raw reply
* [PATCH] efi/fb: Convert PCI bus address to resource if translated by the bridge
From: Sinan Kaya @ 2018-05-18 14:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <36cf3f12-bfe8-1c38-4d2c-b785fc64a3f6@arm.com>
On 5/18/2018 6:42 AM, Robin Murphy wrote:
>
> FWIW, now that I've actually gone and looked, it appears you could simplify the whole function quite a bit by getting rid of these and just using the new local resource directly, especially since the only actual use of size is an open-coded resource_contains().
Posted V2 a minute ago. I did the simplification on a prior patch in order not to
mix the issues.
--
Sinan Kaya
Qualcomm Datacenter Technologies, Inc. as an affiliate of Qualcomm Technologies, Inc.
Qualcomm Technologies, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project.
^ permalink raw reply
* [PATCH V2 2/2] efi/fb: Convert PCI bus address to resource if translated by the bridge
From: Sinan Kaya @ 2018-05-18 14:17 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526653072-7153-1-git-send-email-okaya@codeaurora.org>
A host bridge is allowed to remap BAR addresses using _TRA attribute in
_CRS windows.
pci_bus 0000:00: root bus resource [mem 0x80100100000-0x8011fffffff window] (bus address [0x00100000-0x1fffffff])
pci 0000:02:00.0: reg 0x10: [mem 0x8011e000000-0x8011effffff]
When a VGA device is behind such a host bridge and the resource is
translated efifb driver is trying to do ioremap against bus address
rather than the resource address and is failing to probe.
efifb: probing for efifb
efifb: cannot reserve video memory at 0x1e000000
efifb: framebuffer at 0x1e000000, using 1920k, total 1875k
efifb: mode is 800x600x32, linelength=3200, pages=1
efifb: scrolling: redraw
efifb: Truecolor: size=8:8:8:8, shift=24:16:8:0
Use the host bridge offset information to convert bus address to
resource address in the fixup.
Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
---
drivers/video/fbdev/efifb.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/video/fbdev/efifb.c b/drivers/video/fbdev/efifb.c
index 6daac8d..429cc85 100644
--- a/drivers/video/fbdev/efifb.c
+++ b/drivers/video/fbdev/efifb.c
@@ -431,6 +431,7 @@ static void efifb_fixup_resources(struct pci_dev *dev)
.end = screen_info.lfb_base + screen_info.lfb_size - 1,
.flags = IORESOURCE_MEM,
};
+ struct pci_bus_region region;
int i;
if (efifb_pci_dev || screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
@@ -442,6 +443,10 @@ static void efifb_fixup_resources(struct pci_dev *dev)
if (!screen_res.start)
return;
+ region.start = screen_res.start;
+ region.end = screen_res.end;
+ pcibios_bus_to_resource(dev->bus, &screen_res, ®ion);
+
for (i = 0; i <= PCI_STD_RESOURCE_END; i++) {
struct resource *res = &dev->resource[i];
--
2.7.4
^ permalink raw reply related
* [PATCH V2 1/2] efi/fb: Simplify fixup code to prefer struct resource
From: Sinan Kaya @ 2018-05-18 14:17 UTC (permalink / raw)
To: linux-arm-kernel
Get rid of base and size variables in favor of a struct resource.
The conditional for checking window can be replaced with
resource_contains().
Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
---
drivers/video/fbdev/efifb.c | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/drivers/video/fbdev/efifb.c b/drivers/video/fbdev/efifb.c
index 46a4484..6daac8d 100644
--- a/drivers/video/fbdev/efifb.c
+++ b/drivers/video/fbdev/efifb.c
@@ -426,17 +426,20 @@ static void record_efifb_bar_resource(struct pci_dev *dev, int idx, u64 offset)
static void efifb_fixup_resources(struct pci_dev *dev)
{
- u64 base = screen_info.lfb_base;
- u64 size = screen_info.lfb_size;
+ struct resource screen_res = {
+ .start = screen_info.lfb_base,
+ .end = screen_info.lfb_base + screen_info.lfb_size - 1,
+ .flags = IORESOURCE_MEM,
+ };
int i;
if (efifb_pci_dev || screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
return;
if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)
- base |= (u64)screen_info.ext_lfb_base << 32;
+ screen_res.start |= (u64)screen_info.ext_lfb_base << 32;
- if (!base)
+ if (!screen_res.start)
return;
for (i = 0; i <= PCI_STD_RESOURCE_END; i++) {
@@ -445,8 +448,10 @@ static void efifb_fixup_resources(struct pci_dev *dev)
if (!(res->flags & IORESOURCE_MEM))
continue;
- if (res->start <= base && res->end >= base + size - 1) {
- record_efifb_bar_resource(dev, i, base - res->start);
+ if (resource_contains(res, &screen_res)) {
+ u64 win_offset = screen_res.start - res->start;
+
+ record_efifb_bar_resource(dev, i, win_offset);
break;
}
}
--
2.7.4
^ permalink raw reply related
* [PATCH 02/20] dma-mapping: provide a generic dma-noncoherent implementation
From: Alexey Brodkin @ 2018-05-18 14:13 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180518132731.GA31125@lst.de>
Hi Christoph,
On Fri, 2018-05-18 at 15:27 +0200, hch at lst.de wrote:
> On Fri, May 18, 2018 at 01:03:46PM +0000, Alexey Brodkin wrote:
> > Note mmc_get_dma_dir() is just "data->flags & MMC_DATA_WRITE ? DMA_TO_DEVICE : DMA_FROM_DEVICE".
> > I.e. if we're preparing for sending data dma_noncoherent_map_sg() will have DMA_TO_DEVICE which
> > is quite OK for passing to dma_noncoherent_sync_sg_for_device() but in case of reading we'll have
> > DMA_FROM_DEVICE which we'll pass to dma_noncoherent_sync_sg_for_device() in dma_noncoherent_map_sg().
> >
> > I'd say this is not entirely correct because IMHO arch_sync_dma_for_cpu() is supposed to only be used
> > in case of DMA_FROM_DEVICE and arch_sync_dma_for_device() only in case of DMA_TO_DEVICE.
>
> arc overrides the dir paramter of the dma_sync_single_for_device/
> dma_sync_single_for_cpu calls. My patches dropped that, and I have
> restored that, and audit for the other architectures is pending.
Well at least for me that's a confusion what is a reason to pass direction
to function which purpose is already known.
I'd say that XXX_sync_for_device() doesn't need _variable_ direction as an argument,
otherwise what does that mean if we pass DMA_FROM_DEVICE to that function?
> That being said the existing arc code still looks rather odd as it
> didn't do the same thing for the scatterlist versions of the calls.
That might easily be the case so good we caught that now and it will be fixed :)
> I've thrown in a few patches into my new tree to make the sg versions
> make the normal calls, and to clean up the area a bit.
I'll try your newer series now, thanks!
-Alexey
^ permalink raw reply
* [PATCH 1/2] ARM: OMAP2+: prm44xx: Introduce context save/restore for am43 PRCM IO
From: Tony Lindgren @ 2018-05-18 14:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526632822-26049-1-git-send-email-j-keerthy@ti.com>
* Keerthy <j-keerthy@ti.com> [180518 08:42]:
> From: Dave Gerlach <d-gerlach@ti.com>
>
> There are two registers on am43x needed for IO daisy chain wake to work
> properly, however currently after an RTC+DDR cycle they are lost. We
> must take care to save and restore these before and after entering RTC
> mode otherwise IO daisy chain wake will stop working from DeepSleep
> after resuming.
OK applying these two also into omap-for-v4.18/soc.
Regards,
Tony
^ permalink raw reply
* [PATCH 4/4] omap2: powerdomain: Inroduce cpu_pm notifiers for context save/restore
From: Tony Lindgren @ 2018-05-18 13:54 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <67b3758f-8616-22ab-786e-206f7e5de020@ti.com>
* Tero Kristo <t-kristo@ti.com> [180518 07:02]:
> On 18/05/18 07:32, Keerthy wrote:
> >
> >
> > On Friday 18 May 2018 03:15 AM, Tony Lindgren wrote:
> > > * Keerthy <j-keerthy@ti.com> [180516 15:21]:
> > > > + /* Only AM43XX can lose pwrdm context during rtc-ddr suspend */
> > > > + if (soc_is_am43xx()) {
> > > > + nb.notifier_call = cpu_notifier;
> > > > + cpu_pm_register_notifier(&nb);
> > > > + }
> > > > +
> > >
> > > Hmm in patch 3/4 you add omap4_pwrdm_save_context(), I think
> > > that we'd want to run with the notifier for cpuidle on omap4?
> >
> > Okay i believe that is not needed for cpuidle on omap4. PRCM on wakeup
> > domain so save/restore not needed for powerdomain on omap4.
> >
> > Tero can confirm the same.
>
> Yea I don't believe this is needed. Only certain portions of the OMAP4 PRCM
> lose context during device off mode (which is currently not supported by
> linux), and whatever portions do lose, they should use the SAR_RAM approach
> for context save/restore, as that is supported by HW/ROM code.
OK
> > Should i rename omap4_pwrdm_save_context to am43xx_pwrdm_save_context to
> > avoid confusion?
No need to, it follows the naming prm44xx.c.
Regards,
Tony
^ permalink raw reply
* [PATCH 2/4] omap2: clockdomain: Inroduce cpu_pm notifiers for context save/restore
From: Tony Lindgren @ 2018-05-18 13:54 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <14b0d567-f0cb-72bb-2efc-b21788d9993d@ti.com>
* Tero Kristo <t-kristo@ti.com> [180518 08:41]:
> On 18/05/18 08:57, Keerthy wrote:
> >
> >
> > On Friday 18 May 2018 03:13 AM, Tony Lindgren wrote:
> > > * Keerthy <j-keerthy@ti.com> [180516 15:21]:
> > > > Inroduce cpu_pm notifiers for context save/restore. This will be
> > > > needed for am43xx family in case of rtc only mode with ddr in
> > > > self-refresh.
> > > ...
> > > > +static int cpu_notifier(struct notifier_block *nb, unsigned long cmd, void *v)
> > > > +{
> > > > + switch (cmd) {
> > > > + case CPU_CLUSTER_PM_ENTER:
> > > > + if (enable_off_mode)
> > > > + clkdm_save_context();
> > > > + break;
> > > > + case CPU_CLUSTER_PM_EXIT:
> > > > + if (enable_off_mode)
> > > > + clkdm_restore_context();
> > > > + break;
> > > > + }
> > > > +
> > > > + return NOTIFY_OK;
> > > > +}
> > >
> > > Can you do this too only on suspend instead of the cpu notifier?
> > > If you need to call this eventually from cpuidle also then yeah
> > > the notifier makes sense.
> >
> > Clockdomain under omap2 does not have suspend/resume calls as its still
> > not a driver. The initial version of this patch had save/restore calls
> > directly from pm33xx-core during suspend/resume platform ops are called.
> >
> > Are you suggesting that?
>
> I think using cpu notifiers would help in eventual transition of this under
> drivers also, as this doesn't need any custom interfaces to be exported
> around. And, as it seems now, this is only needed for AM43xx at the moment,
> no other SoCs need this for any purpose, even if device off would be
> implemented. Only exception would be if we want to implement RTC+DDR sort of
> functionality on any other SoC.
OK thanks makes sense. Applying all these into omap-for-v4.18/soc.
Regards,
Tony
^ permalink raw reply
* [PATCH 1/5] dt-bindings: pinctrl: document the STMFX pinctrl bindings
From: Lee Jones @ 2018-05-18 13:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <28c374e6-b440-f785-b371-03fe15c8bc4b@st.com>
On Fri, 18 May 2018, Amelie DELAUNAY wrote:
> On 05/17/2018 08:36 AM, Lee Jones wrote:
> > On Wed, 16 May 2018, Amelie DELAUNAY wrote:
> >
> >>
> >>
> >> On 05/16/2018 04:20 PM, Linus Walleij wrote:
> >>> On Wed, May 9, 2018 at 9:56 AM, Amelie DELAUNAY <amelie.delaunay@st.com> wrote:
> >>>
> >>>> Indeed, stmfx has other functions than GPIO. But, after comments done
> >>>> here: [1] and there: [2], it has been decided to move MFD parent/GPIO
> >>>> child drivers into a single PINCTRL/GPIO driver because of the following
> >>>> reasons:
> >>>> - Other stmfx functions (IDD measurement and TouchScreen controller) are
> >>>> not used on any of the boards using an stmfx and supported by Linux, so
> >>>> no way to test these functions, and no need to maintain them while they
> >>>> are not being used.
> >>>> - But, in the case a new board will use more than GPIO function on
> >>>> stmfx, the actual implementation allow to easily extract common init
> >>>> part of stmfx and put it in an MFD driver.
> >>>>
> >>>> So I could remove gpio sub-node and put its contents in stmfx node and
> >>>> keep single PINCTRL/GPIO driver for the time being.
> >>>> Please advise,
> >>>
> >>> I would normally advice to use the right modeling from the start, create
> >>> the MFD driver and spawn the devices from there. It is confusing
> >>> if the layout of the driver(s) doesn't really match the layout of the
> >>> hardware.
> >>>
> >>> I understand that it is a pain to write new MFD drivers to get your
> >>> things going and it would be "nice to get this working really quick
> >>> now" but in my experience it is better to do it right from the start.
> >>>
> >>
> >> Hi Linus,
> >>
> >> Thanks for your advice. I understand the point.
> >> So, the right modeling would be to:
> >> - create an MFD driver with the common init part of stmfx
> >> - remove all common init part of stmfx-pinctrl driver and keep only all
> >> gpio/pinctrl functions.
> >>
> >> I will not develop the other stmfx functions (IDD measurement driver and
> >> TouchScreen controller driver) because, as explained ealier, they are
> >> not used on any of the boards using an stmfx and supported by Linux, so
> >> no way to test these functions, and no need to maintain them while they
> >> are not being used.
> >>
> >> Lee, are you OK with that ?
> >
> > I missed a lot of this conversation I think, but from what I've read,
> > it sounds fine.
> >
>
> I summarize the situation:
> - I still don't have an official datasheet for STMFX device which could
> justify the use of an MFD driver;
> - the MFD driver will contain the STMFX chip initialization stuff such
> as regmap initialization (regmap structure will be shared with the
> child), chip initialization, global interrupt management;
> - there will be only one child (GPIO/PINCTRL node) for the time being.
>
> So, is "MFD driver + GPIO/PINCTRL driver" the right modeling, and does
> it still sound fine after this summary ? :)
It is starting to sound like there will only ever be one child device,
which starts to cross the line into "this is not an MFD" (M = Multi)
territory.
--
Lee Jones [???]
Linaro Services Technical Lead
Linaro.org ? Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* [PATCH 6/6] arm64: perf: Add support for chaining counters
From: Robin Murphy @ 2018-05-18 13:49 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526638943-2110-7-git-send-email-suzuki.poulose@arm.com>
On 18/05/18 11:22, Suzuki K Poulose wrote:
> Add support for chained event counters. PMUv3 allows chaining
> a pair of adjacent PMU counters (with the lower counter number
> being always "even"). The low counter is programmed to count
> the event of interest and the high counter(odd numbered) is
> programmed with a special event code (0x1e - Chain). Thus
> we need special allocation schemes to make the full use of
> available counters. So, we allocate the counters from either
> ends. i.e, chained counters are allocated from the lower
> end in pairs of two and the normal counters are allocated
> from the higher number. Also makes necessary changes to
> handle the chained events as a single event with 2 counters.
>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Will Deacon <will.deacon@arm.com>
> Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
> ---
> arch/arm64/kernel/perf_event.c | 226 ++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 202 insertions(+), 24 deletions(-)
>
> diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c
> index ea8e060..5f81cd0 100644
> --- a/arch/arm64/kernel/perf_event.c
> +++ b/arch/arm64/kernel/perf_event.c
> @@ -446,9 +446,11 @@ static struct attribute_group armv8_pmuv3_events_attr_group = {
> };
>
> PMU_FORMAT_ATTR(event, "config:0-15");
> +PMU_FORMAT_ATTR(chain, "config1:0");
>
> static struct attribute *armv8_pmuv3_format_attrs[] = {
> &format_attr_event.attr,
> + &format_attr_chain.attr,
> NULL,
> };
>
> @@ -457,6 +459,12 @@ static struct attribute_group armv8_pmuv3_format_attr_group = {
> .attrs = armv8_pmuv3_format_attrs,
> };
>
> +static bool armv8pmu_event_is_chained(struct perf_event *event)
> +{
> + return event->attr.config1 & 0x1;
> +}
> +
> +
> /*
> * Perf Events' indices
> */
> @@ -512,6 +520,36 @@ static inline int armv8pmu_select_counter(int idx)
> return idx;
> }
>
> +static inline u32 armv8pmu_read_evcntr(int idx)
> +{
> + return (armv8pmu_select_counter(idx) == idx) ?
> + read_sysreg(pmxevcntr_el0) : 0;
> +}
> +
> +static inline u64 armv8pmu_read_chain_counter(int idx)
> +{
> + u64 prev_hi, hi, lo;
> +
> + do {
> + prev_hi = armv8pmu_read_evcntr(idx);
> + isb();
> + lo = armv8pmu_read_evcntr(idx - 1);
> + isb();
> + hi = armv8pmu_read_evcntr(idx);
> + isb();
> + } while (prev_hi != hi);
Is it worth trying to elide that last isb() in the highly likely case
that we don't need it?
> +
> + return (hi << 32) | lo;
> +}
> +
> +static inline u64 armv8pmu_read_hw_counter(struct perf_event *event)
> +{
> + int idx = event->hw.idx;
> +
> + return armv8pmu_event_is_chained(event) ?
> + armv8pmu_read_chain_counter(idx) : armv8pmu_read_evcntr(idx);
> +}
> +
> static inline u64 armv8pmu_read_counter(struct perf_event *event)
> {
> struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
> @@ -524,12 +562,37 @@ static inline u64 armv8pmu_read_counter(struct perf_event *event)
> smp_processor_id(), idx);
> else if (idx == ARMV8_IDX_CYCLE_COUNTER)
> value = read_sysreg(pmccntr_el0);
> - else if (armv8pmu_select_counter(idx) == idx)
> - value = read_sysreg(pmxevcntr_el0);
> + else
> + value = armv8pmu_read_hw_counter(event);
>
> return value;
> }
>
> +static inline void armv8pmu_write_evcntr(int idx, u32 value)
> +{
> + if (armv8pmu_select_counter(idx) == idx)
> + write_sysreg(value, pmxevcntr_el0);
> +}
> +
> +static inline void armv8pmu_write_chain_counter(int idx, u64 value)
> +{
> + armv8pmu_write_evcntr(idx, value >> 32);
> + isb();
> + armv8pmu_write_evcntr(idx - 1, value);
> + isb();
Either that isb() is unnecessary, or we are (and have been) missing one
after a non-chained write.
> +}
> +
> +static inline void armv8pmu_write_hw_counter(struct perf_event *event,
> + u64 value)
> +{
> + int idx = event->hw.idx;
> +
> + if (armv8pmu_event_is_chained(event))
> + armv8pmu_write_chain_counter(idx, value);
> + else
> + armv8pmu_write_evcntr(idx, value);
> +}
> +
> static inline void armv8pmu_write_counter(struct perf_event *event, u64 value)
> {
> struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
> @@ -541,8 +604,8 @@ static inline void armv8pmu_write_counter(struct perf_event *event, u64 value)
> smp_processor_id(), idx);
> else if (idx == ARMV8_IDX_CYCLE_COUNTER)
> write_sysreg(value, pmccntr_el0);
> - else if (armv8pmu_select_counter(idx) == idx)
> - write_sysreg(value, pmxevcntr_el0);
> + else
> + armv8pmu_write_hw_counter(event, value);
> }
>
> static inline void armv8pmu_write_evtype(int idx, u32 val)
> @@ -553,6 +616,28 @@ static inline void armv8pmu_write_evtype(int idx, u32 val)
> }
> }
>
> +static inline void armv8pmu_write_event_type(struct perf_event *event)
> +{
> + struct hw_perf_event *hwc = &event->hw;
> + int idx = hwc->idx;
> +
> + /*
> + * For chained events, write the high counter event type
> + * followed by the low counter.
> + */
> + if (armv8pmu_event_is_chained(event)) {
> + u32 chain_evt = ARMV8_PMUV3_PERFCTR_CHAIN;
> +
> + /* Set the filters as that of the main event for chain */
> + chain_evt |= hwc->config_base & ~ARMV8_PMU_EVTYPE_EVENT;
> + armv8pmu_write_evtype(idx, chain_evt);
> + isb();
> + idx--;
> + }
> +
> + armv8pmu_write_evtype(idx, hwc->config_base);
> +}
> +
> static inline int armv8pmu_enable_counter(int idx)
> {
> u32 counter = ARMV8_IDX_TO_COUNTER(idx);
> @@ -560,6 +645,23 @@ static inline int armv8pmu_enable_counter(int idx)
> return idx;
> }
>
> +static inline void armv8pmu_enable_event_counter(struct perf_event *event)
> +{
> + int idx = event->hw.idx;
> +
> + /*
> + * For chained events, we enable the high counter followed by
> + * the low counter.
> + */
> + armv8pmu_enable_counter(idx);
> +
> + if (armv8pmu_event_is_chained(event)) {
> + isb();
> + armv8pmu_enable_counter(idx - 1);
> + }
> +
> +}
> +
> static inline int armv8pmu_disable_counter(int idx)
> {
> u32 counter = ARMV8_IDX_TO_COUNTER(idx);
> @@ -567,6 +669,24 @@ static inline int armv8pmu_disable_counter(int idx)
> return idx;
> }
>
> +static inline void armv8pmu_disable_event_counter(struct perf_event *event)
> +{
> + struct hw_perf_event *hwc = &event->hw;
Nit: might as well drop this and be consistent with the enable case.
> + int idx = hwc->idx;
> +
> + /*
> + * Disable the low counter followed by the high counter
> + * for chained events.
> + */
> + if (armv8pmu_event_is_chained(event)) {
> + armv8pmu_disable_counter(idx - 1);
> + isb();
> + }
> +
> + armv8pmu_disable_counter(idx);
> +}
> +
> +
> static inline int armv8pmu_enable_intens(int idx)
> {
> u32 counter = ARMV8_IDX_TO_COUNTER(idx);
> @@ -574,6 +694,12 @@ static inline int armv8pmu_enable_intens(int idx)
> return idx;
> }
>
> +static inline int armv8pmu_enable_event_irq(struct perf_event *event)
> +{
> + /* For chained events, enable the interrupt for only the high counter */
> + return armv8pmu_enable_intens(event->hw.idx);
> +}
> +
> static inline int armv8pmu_disable_intens(int idx)
> {
> u32 counter = ARMV8_IDX_TO_COUNTER(idx);
> @@ -586,6 +712,11 @@ static inline int armv8pmu_disable_intens(int idx)
> return idx;
> }
>
> +static inline int armv8pmu_disable_event_irq(struct perf_event *event)
> +{
> + return armv8pmu_disable_intens(event->hw.idx);
> +}
> +
> static inline u32 armv8pmu_getreset_flags(void)
> {
> u32 value;
> @@ -603,10 +734,8 @@ static inline u32 armv8pmu_getreset_flags(void)
> static void armv8pmu_enable_event(struct perf_event *event)
> {
> unsigned long flags;
> - struct hw_perf_event *hwc = &event->hw;
> struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
> struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events);
> - int idx = hwc->idx;
>
> /*
> * Enable counter and interrupt, and set the counter to count
> @@ -617,22 +746,22 @@ static void armv8pmu_enable_event(struct perf_event *event)
> /*
> * Disable counter
> */
> - armv8pmu_disable_counter(idx);
> + armv8pmu_disable_event_counter(event);
>
> /*
> * Set event (if destined for PMNx counters).
> */
> - armv8pmu_write_evtype(idx, hwc->config_base);
> + armv8pmu_write_event_type(event);
>
> /*
> * Enable interrupt for this counter
> */
> - armv8pmu_enable_intens(idx);
> + armv8pmu_enable_event_irq(event);
>
> /*
> * Enable counter
> */
> - armv8pmu_enable_counter(idx);
> + armv8pmu_enable_event_counter(event);
>
> raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
> }
> @@ -640,10 +769,8 @@ static void armv8pmu_enable_event(struct perf_event *event)
> static void armv8pmu_disable_event(struct perf_event *event)
> {
> unsigned long flags;
> - struct hw_perf_event *hwc = &event->hw;
> struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
> struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events);
> - int idx = hwc->idx;
>
> /*
> * Disable counter and interrupt
> @@ -651,14 +778,14 @@ static void armv8pmu_disable_event(struct perf_event *event)
> raw_spin_lock_irqsave(&events->pmu_lock, flags);
>
> /*
> - * Disable counter
> + * Disable counter for this event
> */
> - armv8pmu_disable_counter(idx);
> + armv8pmu_disable_event_counter(event);
>
> /*
> - * Disable interrupt for this counter
> + * Disable interrupt for this event counter
> */
> - armv8pmu_disable_intens(idx);
> + armv8pmu_disable_event_irq(event);
>
> raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
> }
> @@ -747,6 +874,39 @@ static void armv8pmu_stop(struct arm_pmu *cpu_pmu)
> raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
> }
>
> +static int armv8pmu_get_single_idx(struct pmu_hw_events *cpuc,
> + struct arm_pmu *cpu_pmu)
> +{
> + int idx;
> +
> + for (idx = cpu_pmu->num_events - 1; idx >= ARMV8_IDX_COUNTER0; ++idx)
> + if (!test_and_set_bit(idx, cpuc->used_mask))
> + return idx;
> + return -EAGAIN;
> +}
> +
> +static int armv8pmu_get_chain_idx(struct pmu_hw_events *cpuc,
> + struct arm_pmu *cpu_pmu)
> +{
> + int idx;
> +
> + /*
> + * Chaining requires two consecutive event counters, where
> + * the lower idx must be even. We allocate chain events
> + * from the lower index (i.e, counter0) and the single events
> + * from the higher end to maximise the utilisation.
> + */
> + for (idx = ARMV8_IDX_COUNTER0 + 1; idx < cpu_pmu->num_events; idx += 2)
> + if (!test_and_set_bit(idx, cpuc->used_mask)) {
> + /* Check if the preceding even counter is available */
> + if (!test_and_set_bit(idx - 1, cpuc->used_mask))
> + return idx;
> + /* Release the Odd counter */
> + clear_bit(idx, cpuc->used_mask);
> + }
> + return -EAGAIN;
> +}
> +
> static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc,
> struct perf_event *event)
> {
> @@ -755,7 +915,10 @@ static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc,
> struct hw_perf_event *hwc = &event->hw;
> unsigned long evtype = hwc->config_base & ARMV8_PMU_EVTYPE_EVENT;
>
> - /* Always prefer to place a cycle counter into the cycle counter. */
> + /*
> + * Always prefer to place a cycle counter into the cycle counter
> + * irrespective of whether we are counting 32bit/64bit
I don't think that comment change adds much :/
> + */
> if (evtype == ARMV8_PMUV3_PERFCTR_CPU_CYCLES) {
> if (!test_and_set_bit(ARMV8_IDX_CYCLE_COUNTER, cpuc->used_mask))
> return ARMV8_IDX_CYCLE_COUNTER;
> @@ -764,13 +927,21 @@ static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc,
> /*
> * Otherwise use events counters
> */
> - for (idx = ARMV8_IDX_COUNTER0; idx < cpu_pmu->num_events; ++idx) {
> - if (!test_and_set_bit(idx, cpuc->used_mask))
> - return idx;
> - }
> + idx = armv8pmu_event_is_chained(event) ?
> + armv8pmu_get_chain_idx(cpuc, cpu_pmu) :
> + armv8pmu_get_single_idx(cpuc, cpu_pmu);
>
> - /* The counters are all in use. */
> - return -EAGAIN;
> + return idx;
> +}
> +
> +static void armv8pmu_clear_event_idx(struct pmu_hw_events *cpuc,
> + struct perf_event *event)
> +{
> + struct hw_perf_event *hwc = &event->hw;
> +
> + clear_bit(hwc->idx, cpuc->used_mask);
> + if (armv8pmu_event_is_chained(event))
> + clear_bit(hwc->idx - 1, cpuc->used_mask);
> }
>
> /*
> @@ -845,8 +1016,14 @@ static int __armv8_pmuv3_map_event(struct perf_event *event,
> &armv8_pmuv3_perf_cache_map,
> ARMV8_PMU_EVTYPE_EVENT);
>
> - if (hw_event_id == ARMV8_PMUV3_PERFCTR_CPU_CYCLES)
> + if (hw_event_id == ARMV8_PMUV3_PERFCTR_CPU_CYCLES) {
> + /* Prevent chaining for cycle counter */
Why? Sure, we want to avoid executing the chaining logic if we're
scheduling a cycles event in the dedicated counter (which is perhaps
what the comment above wanted to say), but if one ends up allocated into
a regular counter (e.g. if the user asks for multiple cycle counts with
different filters), then I don't see any reason to forbid that being
chained.
Robin.
> + if (armv8pmu_event_is_chained(event))
> + return -EINVAL;
> event->hw.flags |= ARMPMU_EVT_LONG;
> + } else if (armv8pmu_event_is_chained(event)) {
> + event->hw.flags |= ARMPMU_EVT_LONG;
> + }
>
> /* Onl expose micro/arch events supported by this PMU */
> if ((hw_event_id > 0) && (hw_event_id < ARMV8_PMUV3_MAX_COMMON_EVENTS)
> @@ -954,6 +1131,7 @@ static int armv8_pmu_init(struct arm_pmu *cpu_pmu)
> cpu_pmu->read_counter = armv8pmu_read_counter,
> cpu_pmu->write_counter = armv8pmu_write_counter,
> cpu_pmu->get_event_idx = armv8pmu_get_event_idx,
> + cpu_pmu->clear_event_idx = armv8pmu_clear_event_idx,
> cpu_pmu->start = armv8pmu_start,
> cpu_pmu->stop = armv8pmu_stop,
> cpu_pmu->reset = armv8pmu_reset,
>
^ permalink raw reply
* [PATCH 02/20] dma-mapping: provide a generic dma-noncoherent implementation
From: hch at lst.de @ 2018-05-18 13:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <bad125dff49f6e49c895e818c9d1abb346a46e8e.camel@synopsys.com>
On Fri, May 18, 2018 at 01:03:46PM +0000, Alexey Brodkin wrote:
> Note mmc_get_dma_dir() is just "data->flags & MMC_DATA_WRITE ? DMA_TO_DEVICE : DMA_FROM_DEVICE".
> I.e. if we're preparing for sending data dma_noncoherent_map_sg() will have DMA_TO_DEVICE which
> is quite OK for passing to dma_noncoherent_sync_sg_for_device() but in case of reading we'll have
> DMA_FROM_DEVICE which we'll pass to dma_noncoherent_sync_sg_for_device() in dma_noncoherent_map_sg().
>
> I'd say this is not entirely correct because IMHO arch_sync_dma_for_cpu() is supposed to only be used
> in case of DMA_FROM_DEVICE and arch_sync_dma_for_device() only in case of DMA_TO_DEVICE.
arc overrides the dir paramter of the dma_sync_single_for_device/
dma_sync_single_for_cpu calls. My patches dropped that, and I have
restored that, and audit for the other architectures is pending.
That being said the existing arc code still looks rather odd as it
didn't do the same thing for the scatterlist versions of the calls.
I've thrown in a few patches into my new tree to make the sg versions
make the normal calls, and to clean up the area a bit.
> You seem to lost an offset in the page so if we happen to have a buffer not aligned to
> a page boundary then we were obviously corrupting data outside our data :)
Oops! Thank you for all the debugging!
^ permalink raw reply
* [PATCH REPOST] mfd: syscon: atmel-smc: include string.h
From: Lee Jones @ 2018-05-18 13:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180504103446.20104-1-bigeasy@linutronix.de>
On Fri, 04 May 2018, Sebastian Andrzej Siewior wrote:
> The string.h header file is needed for the memset() definition. The RT
> build fails because it is not pulled in via other header files.
>
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Nicolas Ferre <nicolas.ferre@microchip.com>
> Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
> Cc: linux-arm-kernel at lists.infradead.org
> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> ---
> drivers/mfd/atmel-smc.c | 1 +
> 1 file changed, 1 insertion(+)
Applied, thanks.
--
Lee Jones [???]
Linaro Services Technical Lead
Linaro.org ? Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* [PATCH 10/17] irqchip/irq-mvebu-sei: add new driver for Marvell SEI
From: Miquel Raynal @ 2018-05-18 13:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180502111744.5391afff@windsurf.home>
Hi Thomas,
On Wed, 2 May 2018 11:17:44 +0200, Thomas Petazzoni
<thomas.petazzoni@bootlin.com> wrote:
> Hello,
>
> On Sat, 21 Apr 2018 15:55:30 +0200, Miquel Raynal wrote:
>
> > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > index 5ed465ab1c76..6b5b75cb4694 100644
> > --- a/drivers/irqchip/Makefile
> > +++ b/drivers/irqchip/Makefile
> > @@ -73,6 +73,7 @@ obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
> > obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
> > obj-$(CONFIG_MSCC_OCELOT_IRQ) += irq-mscc-ocelot.o
> > obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o
> > +obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o
> > obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o
> > obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
> > obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
>
> Alphabetic ordering would put SEI after PIC I guess :)
Sure.
>
> > diff --git a/drivers/irqchip/irq-mvebu-sei.c b/drivers/irqchip/irq-mvebu-sei.c
> > new file mode 100644
> > index 000000000000..5c12c74e3f09
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-mvebu-sei.c
> > @@ -0,0 +1,449 @@
> > +// SPDX-License-Identifier: GPL-2.0 OR X11
>
> License for code is GPL-2.0 only. We use GPL-2.0 OR X11 for Device
> Trees.
I did not know, thanks for the explanation.
>
> > +#define pr_fmt(fmt) "mvebu-sei: " fmt
> > +
> > +#include <linux/irq.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/irqchip/chained_irq.h>
> > +#include <linux/irqdomain.h>
> > +#include <linux/kernel.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/msi.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/irqchip.h>
> > +
> > +#include <dt-bindings/interrupt-controller/arm-gic.h>
> > +
> > +#define GICP_SET_SEI_OFFSET 0x30
> > +#define GICP_CLR_SEI_OFFSET GICP_SET_SEI_OFFSET
>
> Why do you have this concept of set/clr if there is only one register ?
> I assume that if there is only a "set" register, it means that SEI
> interrupts can only be edge triggered, contrary to NSR interrupts,
> which have separate set/clr to support level-triggered interrupts.
>
> Having only a "set" offset means that we can rely on the existing
> "struct msi_msg" to convey both the MSI doorbell address and payload,
> and we don't need the mvebu_gicp_get_doorbells() hack.
I misunderstood the specification at first (when copying code from the
gicp driver), and then continued in my mistake. I removed the whole
doorbell thing.
>
> > +/* Cause register */
> > +#define GICP_SECR(idx) (0x0 + (idx * 0x4))
> > +/* Mask register */
> > +#define GICP_SEMR(idx) (0x20 + (idx * 0x4))
>
> Minor nit: order register definitions by order of increasing offset,
> i.e the GICP_SET_SEI_OFFSET should be defined here.
Ok.
>
> > +#define SEI_IRQ_NB_PER_REG 32
> > +#define SEI_IRQ_REG_NB 2
>
> s/NB/COUNT/
Changed.
>
> > +#define SEI_IRQ_NB (SEI_IRQ_NB_PER_REG * SEI_IRQ_REG_NB)
>
> Ditto.
>
> > +#define SEI_IRQ_REG_IDX(irq_id) (irq_id / SEI_IRQ_NB_PER_REG)
> > +#define SEI_IRQ_REG_BIT(irq_id) (irq_id % SEI_IRQ_NB_PER_REG)
> > +
> > +struct mvebu_sei_interrupt_range {
> > + u32 first;
> > + u32 number;
> > +};
> > +
> > +struct mvebu_sei {
> > + struct device *dev;
> > + void __iomem *base;
> > + struct resource *res;
> > + struct irq_domain *ap_domain;
> > + struct irq_domain *cp_domain;
> > + struct mvebu_sei_interrupt_range ap_interrupts;
> > + struct mvebu_sei_interrupt_range cp_interrupts;
> > + /* Lock on MSI allocations/releases */
> > + spinlock_t cp_msi_lock;
> > + DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_NB);
> > +};
> > +
> > +static int mvebu_sei_domain_to_sei_irq(struct mvebu_sei *sei,
> > + struct irq_domain *domain,
> > + irq_hw_number_t hwirq)
> > +{
> > + if (domain == sei->ap_domain)
> > + return sei->ap_interrupts.first + hwirq;
> > + else
> > + return sei->cp_interrupts.first + hwirq;
>
> I am not entirely clear whether we need subnodes or not in this
> binding, but I guess we do because we have one subset of the interrupts
> that are wired interrupts, and another part that are MSI triggered.
>
> Perhaps this is one aspect on which Marc Zyngier can comment ?
>
> > +static void mvebu_sei_reset(struct mvebu_sei *sei)
> > +{
> > + u32 reg_idx;
> > +
> > + for (reg_idx = 0; reg_idx < SEI_IRQ_REG_NB; reg_idx++) {
> > + /* Clear all cause bits */
> > + writel(0xFFFFFFFF, sei->base + GICP_SECR(reg_idx));
> > + /* Enable all interrupts */
> > + writel(0, sei->base + GICP_SEMR(reg_idx));
>
> Enabling interrupts by default ? This looks weird. They should only be
> enabled... when enabled.
That's right, no need to enable them here.
>
> > +int mvebu_sei_get_doorbells(struct device_node *dn, phys_addr_t *set,
> > + phys_addr_t *clr)
> > +{
> > + struct platform_device *pdev;
> > + struct mvebu_sei *sei;
> > +
> > + pdev = of_find_device_by_node(dn->parent);
> > + if (!pdev)
> > + return -ENODEV;
> > +
> > + sei = platform_get_drvdata(pdev);
> > + if (!sei)
> > + return -ENODEV;
> > +
> > + *set = (phys_addr_t)(sei->res->start + GICP_SET_SEI_OFFSET);
> > + *clr = (phys_addr_t)(sei->res->start + GICP_CLR_SEI_OFFSET);
> > +
> > + return 0;
> > +}
>
> As I said above, I believe this hack is not needed, because SEIs are
> edge-triggered, and we have a single SET_SEI_OFFSET MSI doorbell
> address to convey, which makes "struct msi_msg" as it is today
> sufficient.
Removed.
>
> > +static void mvebu_sei_mask_irq(struct irq_data *d)
> > +{
> > + struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
> > + u32 reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
>
> This doesn't look right. The d->hwirq is relative to the beginning of
> the domain, while you should use sei_irq here. For example, the SEI
> n?32 (first interrupt in the second register) will have d->hwirq = 11,
> because it is the 11th SEI interrupt for the CP. So here, you will
> conclude that reg_idx = 0, while it should be reg_idx = 1.
This is true. I did not saw it because... well... all interrupts were
enabled by default.
>
> > + u32 sei_irq = mvebu_sei_domain_to_sei_irq(sei, d->domain, d->hwirq);
> > + u32 irq_mask = BIT(SEI_IRQ_REG_BIT(sei_irq));
> > + u32 reg;
> > +
> > + /* 1 disables the interrupt */
> > + reg = readl(sei->base + GICP_SEMR(reg_idx));
> > + writel(reg | irq_mask, sei->base + GICP_SEMR(reg_idx));
>
> Personal taste here, but I prefer:
>
> reg = readl(sei->base + GICP_SEMR(reg_idx));
> reg |= BIT(SEI_IRQ_REG_BIT(sei_irq));
> writel(reg, sei->base + GICP_SEMR(reg_idx));
No problem.
>
> > +static void mvebu_sei_unmask_irq(struct irq_data *d)
> > +{
> > + struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
> > + u32 reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
>
> Same mistake as above I believe.
>
> > + u32 sei_irq = mvebu_sei_domain_to_sei_irq(sei, d->domain, d->hwirq);
> > + u32 irq_mask = BIT(SEI_IRQ_REG_BIT(sei_irq));
> > + u32 reg;
> > +
> > + /* 0 enables the interrupt */
> > + reg = readl(sei->base + GICP_SEMR(reg_idx));
> > + writel(reg & ~irq_mask, sei->base + GICP_SEMR(reg_idx));
>
> And same nitpick comment :-)
>
> > +static int mvebu_sei_irq_domain_alloc(struct irq_domain *domain,
> > + unsigned int virq, unsigned int nr_irqs,
> > + void *args)
>
> I think the coding style says that arguments should be aligned, no ?
>
> > +{
> > + struct mvebu_sei *sei = domain->host_data;
> > + struct irq_fwspec *fwspec = args;
> > + struct irq_chip *irq_chip;
> > + int sei_hwirq, hwirq;
> > + int ret;
> > +
> > + /* Software only supports single allocations for now */
> > + if (nr_irqs != 1)
> > + return -ENOTSUPP;
> > +
> > + if (domain == sei->ap_domain) {
> > + irq_chip = &mvebu_sei_ap_wired_irq_chip;
> > + hwirq = fwspec->param[0];
> > + } else {
> > + irq_chip = &mvebu_sei_cp_msi_irq_chip;
> > + spin_lock(&sei->cp_msi_lock);
> > + hwirq = bitmap_find_free_region(sei->cp_msi_bitmap, SEI_IRQ_NB,
> > + 0);
> > + spin_unlock(&sei->cp_msi_lock);
> > + if (hwirq < 0)
> > + return -ENOSPC;
> > + }
> > +
> > + sei_hwirq = mvebu_sei_domain_to_sei_irq(sei, domain, hwirq);
> > +
> > + fwspec->fwnode = domain->parent->fwnode;
> > + fwspec->param_count = 3;
> > + fwspec->param[0] = GIC_SPI;
> > + fwspec->param[1] = sei_hwirq;
> > + fwspec->param[2] = IRQ_TYPE_EDGE_RISING;
>
> Maybe it's me being confused, but I thought all SEI interrupts were
> muxed together to a single SPI for the parent GIC. But here, you
> allocate different SPIs at the GIC level. Intuitively, this doesn't
> look good. Haven't you copy/pasted too much from the gicp driver, where
> we have a 1:1 mapping between interrupts coming into the GICP and
> interrupts signaled by the GICP to the GIC, while here we have a N:1
> mapping, with N interrupts coming into the GICP_SEI, and only one
> interrupt leaving the GICP_SEI to the GIC ?
I am a bit in troubles understanding what fwspec->param[1] exactly
means here. I suppose I should s/sei_hwirq/0/ as there is only one SPI
interrupt to refer to?
Maybe Marc can comment on this too?
>
> > +static const struct irq_domain_ops mvebu_sei_ap_domain_ops = {
> > + .xlate = irq_domain_xlate_onecell,
> > + .alloc = mvebu_sei_irq_domain_alloc,
> > + .free = mvebu_sei_irq_domain_free,
> > +};
> > +
> > +static const struct irq_domain_ops mvebu_sei_cp_domain_ops = {
> > + .xlate = irq_domain_xlate_twocell,
> > + .alloc = mvebu_sei_irq_domain_alloc,
> > + .free = mvebu_sei_irq_domain_free,
> > +};
>
> Why do you need two cells for the interrupts coming from the CP and
> only one cell for the interrupts coming from the AP ?
>
> For thermal in the AP, you do:
>
> + interrupt-parent = <&sei_wired_controller>;
> + interrupts = <18>;
>
> i.e, you don't specify an interrupt type. For thermal in the CP, you do:
>
> + interrupts-extended =
> + <&CP110_LABEL(icu_sei) 116 IRQ_TYPE_LEVEL_HIGH>;
>
> here you specify an interrupt type. I'm not sure why you have this
> difference. Even more so because I think a SEI level interrupt is not
> possible, since you only have a "SET" register and no "CLR" register.
and then you wrote:
<quote>
> OK, my comment is not very correct here, I'm comparing apple to
> oranges. The former its an interrupt directly pointing to the GICP_SEI,
> while the latter is an interrupt of the ICU, which itself will notify
> the GICP_SEI through an MSI.
> However, I'm still confused as to why you have .xlate =
> irq_domain_xlate_twocell for the mvebu_sei_cp_domain_ops. I think there
> is no need for ->xlate() call back here because it's going to be a MSI
> domain.
</quote>
For thermal in the AP I should probably add an IRQ_TYPE_LEVEL_HIGH in
order to describe properly the interrupt (wired).
I also removed the .xlate entry for the CP domain, I was not sure it
was useless for MSI but it looks it is.
>
> Some guidance from Marc here might be useful perhaps.
>
>
> > +static int mvebu_sei_probe(struct platform_device *pdev)
> > +{
> > + struct device_node *node = pdev->dev.of_node, *parent, *child;
> > + struct irq_domain *parent_domain, *plat_domain;
> > + struct mvebu_sei *sei;
> > + const __be32 *property;
> > + u32 top_level_spi, size;
> > + int ret;
> > +
> > + sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);
> > + if (!sei)
> > + return -ENOMEM;
> > +
> > + sei->dev = &pdev->dev;
> > +
> > + spin_lock_init(&sei->cp_msi_lock);
> > +
> > + sei->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (!sei->res) {
> > + dev_err(sei->dev, "Failed to retrieve SEI resource\n");
> > + return -ENODEV;
> > + }
> > +
> > + sei->base = devm_ioremap(sei->dev, sei->res->start,
> > + resource_size(sei->res));
> > + if (!sei->base) {
> > + dev_err(sei->dev, "Failed to remap SEI resource\n");
> > + return -ENODEV;
> > + }
>
> Use devm_ioremap_resource() here, and remove the error handling of
> platform_get_resource(), because it's already taken care of by
> devm_ioremap_resource().
Good tip, I'll remember.
>
> > + /*
> > + * Reserve the single (top-level) parent SPI IRQ from which all the
> > + * interrupts handled by this driver will be signaled.
> > + */
> > + top_level_spi = irq_of_parse_and_map(node, 0);
> > + if (top_level_spi <= 0) {
> > + dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");
> > + return -ENODEV;
> > + }
>
> Rather than top_level_spi, something like parent_irq would make more
> sense to me.
Renamed.
>
> > + irq_set_chained_handler(top_level_spi, mvebu_sei_handle_cascade_irq);
> > + irq_set_handler_data(top_level_spi, sei);
> > +
> > + /*
> > + * SEIs in the range [ 0; 20] are wired and come from the AP.
> > + * SEIs in the range [21; 63] are CP SEI and are triggered through MSIs.
> > + *
> > + * Each SEI 'domain' is represented as a subnode.
> > + */
> > +
> > + /* Get a reference to the parent domain to create a hierarchy */
> > + parent = of_irq_find_parent(node);
> > + if (!parent) {
> > + dev_err(sei->dev, "Failed to find parent IRQ node\n");
> > + ret = -ENODEV;
> > + goto dispose_irq;
> > + }
> > +
> > + parent_domain = irq_find_host(parent);
> > + if (!parent_domain) {
> > + dev_err(sei->dev, "Failed to find parent IRQ domain\n");
> > + ret = -ENODEV;
> > + goto dispose_irq;
> > + }
> > +
> > + /* Create the 'wired' hierarchy */
> > + child = of_find_node_by_name(node, "sei-wired-controller");
> > + if (!child) {
> > + dev_err(sei->dev, "Missing 'sei-wired-controller' subnode\n");
> > + ret = -ENODEV;
> > + goto dispose_irq;
> > + }
>
> Don't forget to of_node_put(child) once you're done using this DT node
> reference.
Ok.
>
> > +
> > + property = of_get_property(child, "reg", &size);
> > + if (!property || size != (2 * sizeof(u32))) {
> > + dev_err(sei->dev, "Missing subnode 'reg' property\n");
> > + ret = -ENODEV;
> > + goto dispose_irq;
> > + }
>
> As Rob said, I don't think the "reg" property is appropriate for this
> usage.
I will change.
>
> > + sei->ap_interrupts.first = be32_to_cpu(property[0]);
> > + sei->ap_interrupts.number = be32_to_cpu(property[1]);
> > + sei->ap_domain = irq_domain_create_hierarchy(parent_domain, 0,
> > + sei->ap_interrupts.number,
> > + of_node_to_fwnode(child),
> > + &mvebu_sei_ap_domain_ops,
> > + sei);
> > + if (!sei->ap_domain) {
> > + dev_err(sei->dev, "Failed to create AP IRQ domain\n");
> > + ret = -ENOMEM;
> > + goto dispose_irq;
> > + }
> > +
> > + /* Create the 'MSI' hierarchy */
> > + child = of_find_node_by_name(node, "sei-msi-controller");
> > + if (!child) {
> > + dev_err(sei->dev, "Missing 'sei-msi-controller' subnode\n");
> > + ret = -ENODEV;
> > + goto remove_ap_domain;
> > + }
>
> Ditto: missing of_node_put(child) somewhere below to balance
> of_find_node_by_name().
Ok.
>
> > + property = of_get_property(child, "reg", &size);
> > + if (!property || size != (2 * sizeof(u32))) {
> > + dev_err(sei->dev, "Missing subnode 'reg' property\n");
> > + ret = -ENODEV;
> > + goto remove_ap_domain;
> > + }
> > +
> > + sei->cp_interrupts.first = be32_to_cpu(property[0]);
> > + sei->cp_interrupts.number = be32_to_cpu(property[1]);
> > + sei->cp_domain = irq_domain_create_hierarchy(parent_domain, 0,
> > + sei->cp_interrupts.number,
> > + of_node_to_fwnode(child),
> > + &mvebu_sei_cp_domain_ops,
> > + sei);
> > + if (!sei->cp_domain) {
> > + pr_err("Failed to create CPs IRQ domain\n");
> > + ret = -ENOMEM;
> > + goto remove_ap_domain;
> > + }
> > +
> > + plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(child),
> > + &mvebu_sei_msi_domain_info,
> > + sei->cp_domain);
> > + if (!plat_domain) {
> > + pr_err("Failed to create CPs MSI domain\n");
> > + ret = -ENOMEM;
> > + goto remove_cp_domain;
> > + }
>
>
>
> > +
> > + platform_set_drvdata(pdev, sei);
> > +
> > + mvebu_sei_reset(sei);
>
> Please do the reset *before* registering the IRQ domains, it's more
> logical to have the HW ready and then expose it to Linux rather than
> the opposite.
Sure.
>
> It would be nice to have the review from Marc on this driver,
> especially on whether the SEI is properly modeled in terms of IRQ
> domains;
>
> > diff --git a/drivers/irqchip/irq-mvebu-sei.h b/drivers/irqchip/irq-mvebu-sei.h
> > new file mode 100644
> > index 000000000000..f0c12a441923
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-mvebu-sei.h
> > @@ -0,0 +1,12 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +#ifndef __MVEBU_SEI_H__
> > +#define __MVEBU_SEI_H__
> > +
> > +#include <linux/types.h>
> > +
> > +struct device_node;
> > +
> > +int mvebu_sei_get_doorbells(struct device_node *dn, phys_addr_t *set,
> > + phys_addr_t *clr);
> > +
> > +#endif /* __MVEBU_SEI_H__ */
>
> This header file can be removed if you drop mvebu_sei_get_doorbells(),
> as suggested above.
Indeed.
>
> Best regards,
>
> Thomas
Thanks!
Miqu?l
^ permalink raw reply
* [PATCH 3/6] arm_pmu: Add support for long event counters
From: Robin Murphy @ 2018-05-18 13:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526638943-2110-4-git-send-email-suzuki.poulose@arm.com>
On 18/05/18 11:22, Suzuki K Poulose wrote:
> Each PMU has a set of fixed width event counters. But in some
> special cases, the events could be counted using a counter which
> effectively has twice the normal width of a coutner.
> e.g, Arm V8 PMUv3 has a 64 bit cycle counter which can count
> only the CPU cylces. Also, the PMU can chain the event counters
> to effectively count as a 64bit counter.
Nit: a few typos in that paragraph.
> Add support for tracking the events that uses double the normal
> counter size. This only affects the periods set for each counter.
>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Will Deacon <will.deacon@arm.com>
> Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
> ---
> drivers/perf/arm_pmu.c | 25 ++++++++++++++++++++++---
> include/linux/perf/arm_pmu.h | 6 ++++++
> 2 files changed, 28 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c
> index e23e1a1..1adabb5 100644
> --- a/drivers/perf/arm_pmu.c
> +++ b/drivers/perf/arm_pmu.c
> @@ -33,6 +33,21 @@ static inline u64 arm_pmu_max_period(struct arm_pmu *pmu)
> return (((u64)1) << (pmu->counter_width)) - 1;
> }
>
> +static inline u64 arm_pmu_get_event_max_period(struct arm_pmu *pmu,
The "get_" here seems a bit at odds with arm_pmu_max_period() - I'd be
inlined to go for slightly more consistent naming (with a slight
personal preference towards removing it here rather than adding it there)
> + struct perf_event *event)
> +{
> + u64 period = arm_pmu_max_period(pmu);
> +
> + /*
> + * To prevent shift-counter-overflow warning, create the
> + * mask, by shift + OR sequence.
> + */
> + if (event->hw.flags & ARMPMU_EVT_LONG)
> + period = (period << pmu->counter_width) | period;
> +
> + return period;
> +}
> +
> static int
> armpmu_map_cache_event(const unsigned (*cache_map)
> [PERF_COUNT_HW_CACHE_MAX]
> @@ -122,7 +137,7 @@ int armpmu_event_set_period(struct perf_event *event)
> u64 max_period;
> int ret = 0;
>
> - max_period = arm_pmu_max_period(armpmu);
> + max_period = arm_pmu_get_event_max_period(armpmu, event);
> if (unlikely(left <= -period)) {
> left = period;
> local64_set(&hwc->period_left, left);
> @@ -148,7 +163,7 @@ int armpmu_event_set_period(struct perf_event *event)
>
> local64_set(&hwc->prev_count, (u64)-left);
>
> - armpmu->write_counter(event, (u64)(-left) & 0xffffffff);
> + armpmu->write_counter(event, (u64)(-left) & max_period);
>
> perf_event_update_userpage(event);
>
> @@ -160,7 +175,7 @@ u64 armpmu_event_update(struct perf_event *event)
> struct arm_pmu *armpmu = to_arm_pmu(event->pmu);
> struct hw_perf_event *hwc = &event->hw;
> u64 delta, prev_raw_count, new_raw_count;
> - u64 max_period = arm_pmu_max_period(armpmu);
> + u64 max_period = arm_pmu_get_event_max_period(armpmu, event);
>
> again:
> prev_raw_count = local64_read(&hwc->prev_count);
> @@ -368,6 +383,7 @@ __hw_perf_event_init(struct perf_event *event)
> struct hw_perf_event *hwc = &event->hw;
> int mapping;
>
> + hwc->flags = 0;
> mapping = armpmu->map_event(event);
>
> if (mapping < 0) {
> @@ -670,6 +686,9 @@ static void cpu_pm_pmu_setup(struct arm_pmu *armpmu, unsigned long cmd)
> continue;
>
> event = hw_events->events[idx];
> + /* Chained events could use multiple counters */
> + if (!event)
> + continue;
This hunk looks a little out of place; does it perhaps belong to patch #6?
Robin.
>
> switch (cmd) {
> case CPU_PM_ENTER:
> diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
> index 705e8c3..ed7e3f7 100644
> --- a/include/linux/perf/arm_pmu.h
> +++ b/include/linux/perf/arm_pmu.h
> @@ -25,6 +25,12 @@
> */
> #define ARMPMU_MAX_HWEVENTS 32
>
> +/*
> + * ARM PMU hw_event flags
> + */
> +/* Event uses a counter with double the normal width */
> +#define ARMPMU_EVT_LONG 1
> +
> #define HW_OP_UNSUPPORTED 0xFFFF
> #define C(_x) PERF_COUNT_HW_CACHE_##_x
> #define CACHE_OP_UNSUPPORTED 0xFFFF
>
^ permalink raw reply
* [PATCH 1/6] arm_pmu: Refactor maximum period handling
From: Robin Murphy @ 2018-05-18 13:10 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526638943-2110-2-git-send-email-suzuki.poulose@arm.com>
Hi Suzuki,
On 18/05/18 11:22, Suzuki K Poulose wrote:
> Each PMU defines their max_period of the counter as the maximum
> value that can be counted. In order to support chaining of the
> counters, change this parameter to indicate the counter width
> to deduce the max_period. This will be useful to compute the
> max_period for chained counters.
>
> No functional changes.
>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Will Deacon <will.deacon@arm.com>
> Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
> ---
> arch/arm/kernel/perf_event_v6.c | 4 ++--
> arch/arm/kernel/perf_event_v7.c | 2 +-
> arch/arm/kernel/perf_event_xscale.c | 4 ++--
> arch/arm64/kernel/perf_event.c | 2 +-
> drivers/perf/arm_pmu.c | 16 ++++++++++++----
> include/linux/perf/arm_pmu.h | 2 +-
> 6 files changed, 19 insertions(+), 11 deletions(-)
>
> diff --git a/arch/arm/kernel/perf_event_v6.c b/arch/arm/kernel/perf_event_v6.c
> index 1d7061a..d52a3fa 100644
> --- a/arch/arm/kernel/perf_event_v6.c
> +++ b/arch/arm/kernel/perf_event_v6.c
> @@ -497,7 +497,7 @@ static void armv6pmu_init(struct arm_pmu *cpu_pmu)
> cpu_pmu->stop = armv6pmu_stop;
> cpu_pmu->map_event = armv6_map_event;
> cpu_pmu->num_events = 3;
> - cpu_pmu->max_period = (1LLU << 32) - 1;
> + cpu_pmu->counter_width = 32;
> }
>
> static int armv6_1136_pmu_init(struct arm_pmu *cpu_pmu)
> @@ -548,7 +548,7 @@ static int armv6mpcore_pmu_init(struct arm_pmu *cpu_pmu)
> cpu_pmu->stop = armv6pmu_stop;
> cpu_pmu->map_event = armv6mpcore_map_event;
> cpu_pmu->num_events = 3;
> - cpu_pmu->max_period = (1LLU << 32) - 1;
> + cpu_pmu->counter_width = 32;
>
> return 0;
> }
> diff --git a/arch/arm/kernel/perf_event_v7.c b/arch/arm/kernel/perf_event_v7.c
> index 870b66c..3d8ec6a 100644
> --- a/arch/arm/kernel/perf_event_v7.c
> +++ b/arch/arm/kernel/perf_event_v7.c
> @@ -1171,7 +1171,7 @@ static void armv7pmu_init(struct arm_pmu *cpu_pmu)
> cpu_pmu->start = armv7pmu_start;
> cpu_pmu->stop = armv7pmu_stop;
> cpu_pmu->reset = armv7pmu_reset;
> - cpu_pmu->max_period = (1LLU << 32) - 1;
> + cpu_pmu->counter_width = 32;
> };
>
> static void armv7_read_num_pmnc_events(void *info)
> diff --git a/arch/arm/kernel/perf_event_xscale.c b/arch/arm/kernel/perf_event_xscale.c
> index fcf218d..6eb0e21 100644
> --- a/arch/arm/kernel/perf_event_xscale.c
> +++ b/arch/arm/kernel/perf_event_xscale.c
> @@ -375,7 +375,7 @@ static int xscale1pmu_init(struct arm_pmu *cpu_pmu)
> cpu_pmu->stop = xscale1pmu_stop;
> cpu_pmu->map_event = xscale_map_event;
> cpu_pmu->num_events = 3;
> - cpu_pmu->max_period = (1LLU << 32) - 1;
> + cpu_pmu->counter_width = 32;
>
> return 0;
> }
> @@ -745,7 +745,7 @@ static int xscale2pmu_init(struct arm_pmu *cpu_pmu)
> cpu_pmu->stop = xscale2pmu_stop;
> cpu_pmu->map_event = xscale_map_event;
> cpu_pmu->num_events = 5;
> - cpu_pmu->max_period = (1LLU << 32) - 1;
> + cpu_pmu->counter_width = 32;
>
> return 0;
> }
> diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c
> index 85a251b..408f92c 100644
> --- a/arch/arm64/kernel/perf_event.c
> +++ b/arch/arm64/kernel/perf_event.c
> @@ -961,7 +961,7 @@ static int armv8_pmu_init(struct arm_pmu *cpu_pmu)
> cpu_pmu->start = armv8pmu_start,
> cpu_pmu->stop = armv8pmu_stop,
> cpu_pmu->reset = armv8pmu_reset,
> - cpu_pmu->max_period = (1LLU << 32) - 1,
> + cpu_pmu->counter_width = 32;
Given that none of the 6 instances above differ, this looks suspiciously
redundant. AFAICS max_period has been there from the very beginning with
no explicit justification, so I can only assume it was anticipating more
future variability than actually turned out. With 8 years of hindsight
now, I think it would be reasonable to assume that counters are 32-bit
except in certain special cases where they might be 64-bit; since that
can't be described by a single "counter size" value anyway, and by the
end of this series we have the means to handle it correctly via flags, I
propose that we just get rid of this and hard-code 32 in
arm_pmu_max_period().
> cpu_pmu->set_event_filter = armv8pmu_set_event_filter;
>
> return 0;
> diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c
> index 1a0d340..e23e1a1 100644
> --- a/drivers/perf/arm_pmu.c
> +++ b/drivers/perf/arm_pmu.c
> @@ -28,6 +28,11 @@
> static DEFINE_PER_CPU(struct arm_pmu *, cpu_armpmu);
> static DEFINE_PER_CPU(int, cpu_irq);
>
> +static inline u64 arm_pmu_max_period(struct arm_pmu *pmu)
> +{
> + return (((u64)1) << (pmu->counter_width)) - 1;
Nit: "1ULL << ..."
Otherwise, looks fine to me.
Robin.
> +}
> +
> static int
> armpmu_map_cache_event(const unsigned (*cache_map)
> [PERF_COUNT_HW_CACHE_MAX]
> @@ -114,8 +119,10 @@ int armpmu_event_set_period(struct perf_event *event)
> struct hw_perf_event *hwc = &event->hw;
> s64 left = local64_read(&hwc->period_left);
> s64 period = hwc->sample_period;
> + u64 max_period;
> int ret = 0;
>
> + max_period = arm_pmu_max_period(armpmu);
> if (unlikely(left <= -period)) {
> left = period;
> local64_set(&hwc->period_left, left);
> @@ -136,8 +143,8 @@ int armpmu_event_set_period(struct perf_event *event)
> * effect we are reducing max_period to account for
> * interrupt latency (and we are being very conservative).
> */
> - if (left > (armpmu->max_period >> 1))
> - left = armpmu->max_period >> 1;
> + if (left > (max_period >> 1))
> + left = (max_period >> 1);
>
> local64_set(&hwc->prev_count, (u64)-left);
>
> @@ -153,6 +160,7 @@ u64 armpmu_event_update(struct perf_event *event)
> struct arm_pmu *armpmu = to_arm_pmu(event->pmu);
> struct hw_perf_event *hwc = &event->hw;
> u64 delta, prev_raw_count, new_raw_count;
> + u64 max_period = arm_pmu_max_period(armpmu);
>
> again:
> prev_raw_count = local64_read(&hwc->prev_count);
> @@ -162,7 +170,7 @@ u64 armpmu_event_update(struct perf_event *event)
> new_raw_count) != prev_raw_count)
> goto again;
>
> - delta = (new_raw_count - prev_raw_count) & armpmu->max_period;
> + delta = (new_raw_count - prev_raw_count) & max_period;
>
> local64_add(delta, &event->count);
> local64_sub(delta, &hwc->period_left);
> @@ -402,7 +410,7 @@ __hw_perf_event_init(struct perf_event *event)
> * is far less likely to overtake the previous one unless
> * you have some serious IRQ latency issues.
> */
> - hwc->sample_period = armpmu->max_period >> 1;
> + hwc->sample_period = arm_pmu_max_period(armpmu) >> 1;
> hwc->last_period = hwc->sample_period;
> local64_set(&hwc->period_left, hwc->sample_period);
> }
> diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
> index 40036a5..c8c31cf 100644
> --- a/include/linux/perf/arm_pmu.h
> +++ b/include/linux/perf/arm_pmu.h
> @@ -94,7 +94,7 @@ struct arm_pmu {
> void (*reset)(void *);
> int (*map_event)(struct perf_event *event);
> int num_events;
> - u64 max_period;
> + u8 counter_width;
> bool secure_access; /* 32-bit ARM only */
> #define ARMV8_PMUV3_MAX_COMMON_EVENTS 0x40
> DECLARE_BITMAP(pmceid_bitmap, ARMV8_PMUV3_MAX_COMMON_EVENTS);
>
^ permalink raw reply
* [PATCH v2 2/2] KVM: arm/arm64: harden unmap_stage2_ptes in case end is not PAGE_SIZE aligned
From: Jia He @ 2018-05-18 13:04 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <2185a61e-c157-e177-9bad-83b6f27fd784@arm.com>
On 5/18/2018 5:48 PM, Marc Zyngier Wrote:
> On 18/05/18 10:27, Jia He wrote:
>> If it passes addr=0x202920000,size=0xfe00 to unmap_stage2_range->
>> ...->unmap_stage2_ptes, unmap_stage2_ptes will get addr=0x202920000,
>> end=0x20292fe00. After first while loop addr=0x202930000, end=0x20292fe00,
>> then addr!=end. Thus it will touch another pages by put_pages() in the
>> 2nd loop.
>>
>> This patch fixes it by hardening the break condition of while loop.
>>
>> Signed-off-by: jia.he at hxt-semitech.com
>> ---
>> v2: newly added
>>
>> virt/kvm/arm/mmu.c | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/virt/kvm/arm/mmu.c b/virt/kvm/arm/mmu.c
>> index 8dac311..45cd040 100644
>> --- a/virt/kvm/arm/mmu.c
>> +++ b/virt/kvm/arm/mmu.c
>> @@ -217,7 +217,7 @@ static void unmap_stage2_ptes(struct kvm *kvm, pmd_t *pmd,
>>
>> put_page(virt_to_page(pte));
>> }
>> - } while (pte++, addr += PAGE_SIZE, addr != end);
>> + } while (pte++, addr += PAGE_SIZE, addr < end);
>>
>> if (stage2_pte_table_empty(start_pte))
>> clear_stage2_pmd_entry(kvm, pmd, start_addr);
>>
>
> I don't think this change is the right thing to do. You get that failure
> because you're being passed a size that is not a multiple of PAGE_SIZE.
> That's the mistake.
>
> You should ensure that this never happens, rather than changing the page
> table walkers (which are consistent with the way this kind of code is
> written in other places of the kernel). As you mentioned in your first
> patch, the real issue is that KSM is broken, and this is what should be
> fixed.
>
Got it, thanks
Should I resend the patch 1/2 without any changes after droping patch 2/2?
--
Cheers,
Jia
^ permalink raw reply
* [PATCH V2 1/3] clk: imx7d: correct enet phy ref clock gates
From: Stefan Agner @ 2018-05-18 13:03 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526605266-18464-1-git-send-email-Anson.Huang@nxp.com>
On 18.05.2018 03:01, Anson Huang wrote:
> IMX7D_ENET_PHY_REF_ROOT_DIV supplies clock for PHY directly,
> there is no clock gate after it, rename it to
> IMX7D_ENET_PHY_REF_ROOT_CLK to avoid device tree change.
>
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
Reviewed-by: Stefan Agner <stefan@agner.ch>
--
Stefan
> ---
> drivers/clk/imx/clk-imx7d.c | 3 +--
> 1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/drivers/clk/imx/clk-imx7d.c b/drivers/clk/imx/clk-imx7d.c
> index 975a20d..23d5090a 100644
> --- a/drivers/clk/imx/clk-imx7d.c
> +++ b/drivers/clk/imx/clk-imx7d.c
> @@ -738,7 +738,7 @@ static void __init imx7d_clocks_init(struct
> device_node *ccm_node)
> clks[IMX7D_ENET1_TIME_ROOT_DIV] =
> imx_clk_divider2("enet1_time_post_div", "enet1_time_pre_div", base +
> 0xa780, 0, 6);
> clks[IMX7D_ENET2_REF_ROOT_DIV] =
> imx_clk_divider2("enet2_ref_post_div", "enet2_ref_pre_div", base +
> 0xa800, 0, 6);
> clks[IMX7D_ENET2_TIME_ROOT_DIV] =
> imx_clk_divider2("enet2_time_post_div", "enet2_time_pre_div", base +
> 0xa880, 0, 6);
> - clks[IMX7D_ENET_PHY_REF_ROOT_DIV] =
> imx_clk_divider2("enet_phy_ref_post_div", "enet_phy_ref_pre_div", base
> + 0xa900, 0, 6);
> + clks[IMX7D_ENET_PHY_REF_ROOT_CLK] =
> imx_clk_divider2("enet_phy_ref_root_clk", "enet_phy_ref_pre_div", base
> + 0xa900, 0, 6);
> clks[IMX7D_EIM_ROOT_DIV] = imx_clk_divider2("eim_post_div",
> "eim_pre_div", base + 0xa980, 0, 6);
> clks[IMX7D_NAND_ROOT_CLK] = imx_clk_divider2("nand_root_clk",
> "nand_pre_div", base + 0xaa00, 0, 6);
> clks[IMX7D_QSPI_ROOT_DIV] = imx_clk_divider2("qspi_post_div",
> "qspi_pre_div", base + 0xaa80, 0, 6);
> @@ -816,7 +816,6 @@ static void __init imx7d_clocks_init(struct
> device_node *ccm_node)
> clks[IMX7D_ENET1_TIME_ROOT_CLK] =
> imx_clk_gate4("enet1_time_root_clk", "enet1_time_post_div", base +
> 0x44f0, 0);
> clks[IMX7D_ENET2_REF_ROOT_CLK] = imx_clk_gate4("enet2_ref_root_clk",
> "enet2_ref_post_div", base + 0x4500, 0);
> clks[IMX7D_ENET2_TIME_ROOT_CLK] =
> imx_clk_gate4("enet2_time_root_clk", "enet2_time_post_div", base +
> 0x4510, 0);
> - clks[IMX7D_ENET_PHY_REF_ROOT_CLK] =
> imx_clk_gate4("enet_phy_ref_root_clk", "enet_phy_ref_post_div", base +
> 0x4520, 0);
> clks[IMX7D_EIM_ROOT_CLK] = imx_clk_gate4("eim_root_clk",
> "eim_post_div", base + 0x4160, 0);
> clks[IMX7D_NAND_RAWNAND_CLK] =
> imx_clk_gate2_shared2("nand_rawnand_clk", "nand_root_clk", base +
> 0x4140, 0, &share_count_nand);
> clks[IMX7D_NAND_USDHC_BUS_RAWNAND_CLK] =
> imx_clk_gate2_shared2("nand_usdhc_rawnand_clk", "nand_usdhc_root_clk",
> base + 0x4140, 0, &share_count_nand);
^ permalink raw reply
* [PATCH 02/20] dma-mapping: provide a generic dma-noncoherent implementation
From: Alexey Brodkin @ 2018-05-18 13:03 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180511075945.16548-3-hch@lst.de>
Hi Christoph,
On Fri, 2018-05-11 at 09:59 +0200, Christoph Hellwig wrote:
[snip]
There seems to be one subtle issue with map/unmap code.
While investigating problems on ARC I added instrumentation as below:
---------------------------------------->8------------------------------------
--- a/arch/arc/mm/dma.c
+++ b/arch/arc/mm/dma.c
@@ -152,14 +152,37 @@ static void _dma_cache_sync(struct device *dev, phys_addr_t paddr, size_t size,
}
}
+static const char *dir_to_str(enum dma_data_direction dir)
+{
+ switch (dir) {
+ case DMA_BIDIRECTIONAL: return "DMA_BIDIRECTIONAL";
+ case DMA_TO_DEVICE: return "DMA_TO_DEVICE";
+ case DMA_FROM_DEVICE: return "DMA_FROM_DEVICE";
+ case DMA_NONE: return "DMA_NONE";
+ default: return "WRONG_VALUE!";
+ }
+}
+
void arch_sync_dma_for_device(struct device *dev, phys_addr_t paddr,
size_t size, enum dma_data_direction dir)
{
+ if (dir != DMA_TO_DEVICE){
+ dump_stack();
+ printk(" *** %s@%d: DMA direction is %s instead of %s\n",
+ __func__, __LINE__, dir_to_str(dir), dir_to_str(DMA_TO_DEVICE));
+ }
+
return _dma_cache_sync(dev, paddr, size, dir);
}
void arch_sync_dma_for_cpu(struct device *dev, phys_addr_t paddr,
size_t size, enum dma_data_direction dir)
{
+ if (dir != DMA_FROM_DEVICE) {
+ dump_stack();
+ printk(" *** %s@%d: DMA direction is %s instead of %s\n",
+ __func__, __LINE__, dir_to_str(dir), dir_to_str(DMA_FROM_DEVICE));
+ }
+
return _dma_cache_sync(dev, paddr, size, dir);
}
---------------------------------------->8------------------------------------
And with that I noticed a bit unexpected output, see below:
---------------------------------------->8------------------------------------
Stack Trace:
arc_unwind_core.constprop.1+0xd4/0xf8
dump_stack+0x68/0x80
arch_sync_dma_for_device+0x34/0xc4
dma_noncoherent_map_sg+0x80/0x94
__dw_mci_start_request+0x1ee/0x868
dw_mci_request+0x17e/0x1c8
mmc_wait_for_req+0x106/0x1ac
mmc_app_sd_status+0x108/0x130
mmc_sd_setup_card+0xc6/0x2e8
mmc_attach_sd+0x1b6/0x394
mmc_rescan+0x2f4/0x3bc
process_one_work+0x194/0x348
worker_thread+0xf2/0x478
kthread+0x120/0x13c
ret_from_fork+0x18/0x1c
*** arch_sync_dma_for_device at 172: DMA direction is DMA_FROM_DEVICE instead of DMA_TO_DEVICE
...
Stack Trace:
arc_unwind_core.constprop.1+0xd4/0xf8
dump_stack+0x68/0x80
arch_sync_dma_for_device+0x34/0xc4
dma_noncoherent_map_page+0x86/0x8c
usb_hcd_map_urb_for_dma+0x49e/0x53c
usb_hcd_submit_urb+0x43c/0x8c4
usb_control_msg+0xbe/0x16c
hub_port_init+0x5e0/0xb0c
hub_event+0x4e6/0x1164
process_one_work+0x194/0x348
worker_thread+0xf2/0x478
kthread+0x120/0x13c
ret_from_fork+0x18/0x1c
mmcblk0: p1 p2
*** arch_sync_dma_for_device at 172: DMA direction is DMA_FROM_DEVICE instead of DMA_TO_DEVICE
...
and quite some more of the similar
...
---------------------------------------->8------------------------------------
In case of MMC/DW_MCI (AKA DesignWare MobileStorage controller) that's an execution flow:
1) __dw_mci_start_request()
2) dw_mci_pre_dma_transfer()
3) dma_map_sg(..., mmc_get_dma_dir(data))
Note mmc_get_dma_dir() is just "data->flags & MMC_DATA_WRITE ? DMA_TO_DEVICE : DMA_FROM_DEVICE".
I.e. if we're preparing for sending data dma_noncoherent_map_sg() will have DMA_TO_DEVICE which
is quite OK for passing to dma_noncoherent_sync_sg_for_device() but in case of reading we'll have
DMA_FROM_DEVICE which we'll pass to dma_noncoherent_sync_sg_for_device() in dma_noncoherent_map_sg().
I'd say this is not entirely correct because IMHO arch_sync_dma_for_cpu() is supposed to only be used
in case of DMA_FROM_DEVICE and arch_sync_dma_for_device() only in case of DMA_TO_DEVICE.
> +static dma_addr_t dma_noncoherent_map_page(struct device *dev, struct page *page,
> + unsigned long offset, size_t size, enum dma_data_direction dir,
> + unsigned long attrs)
> +{
> + dma_addr_t addr;
> +
> + addr = dma_direct_map_page(dev, page, offset, size, dir, attrs);
> + if (!dma_mapping_error(dev, addr) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
> + arch_sync_dma_for_device(dev, page_to_phys(page), size, dir);
> + return addr;
> +}
> +
> +static int dma_noncoherent_map_sg(struct device *dev, struct scatterlist *sgl,
> + int nents, enum dma_data_direction dir, unsigned long attrs)
> +{
> + nents = dma_direct_map_sg(dev, sgl, nents, dir, attrs);
> + if (nents > 0 && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
> + dma_noncoherent_sync_sg_for_device(dev, sgl, nents, dir);
> + return nents;
> +}
The same is for unmap functions.
My guess is we need to respect direction in map/unmap functions and use
either dma_noncoherent_sync_single_for_cpu(..., DMA_FROM_DEVICE) or
dma_noncoherent_sync_single_for_device(...,DMA_TO_DEVICE).
> +static void dma_noncoherent_unmap_page(struct device *dev, dma_addr_t addr,
> + size_t size, enum dma_data_direction dir, unsigned long attrs)
> +{
> + if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC))
> + dma_noncoherent_sync_single_for_cpu(dev, addr, size, dir);
> +}
> +
> +static void dma_noncoherent_unmap_sg(struct device *dev, struct scatterlist *sgl,
> + int nents, enum dma_data_direction dir, unsigned long attrs)
> +{
> + if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC))
> + dma_noncoherent_sync_sg_for_cpu(dev, sgl, nents, dir);
> +}
> +#endif
But the real fix of my problem is:
---------------------------------------->8------------------------------------
--- a/lib/dma-noncoherent.c
+++ b/lib/dma-noncoherent.c
@@ -35,7 +35,7 @@ static dma_addr_t dma_noncoherent_map_page(struct device *dev, struct page *page
addr = dma_direct_map_page(dev, page, offset, size, dir, attrs);
if (!dma_mapping_error(dev, addr) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
- arch_sync_dma_for_device(dev, page_to_phys(page), size, dir);
+ arch_sync_dma_for_device(dev, page_to_phys(page) + offset, size, dir);
return addr;
}
---------------------------------------->8------------------------------------
You seem to lost an offset in the page so if we happen to have a buffer not aligned to
a page boundary then we were obviously corrupting data outside our data :)
-Alexey
^ permalink raw reply
* [PATCH V2 2/3] clk: imx7d: correct enet clock CCGR registers
From: Stefan Agner @ 2018-05-18 13:02 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526605266-18464-2-git-send-email-Anson.Huang@nxp.com>
On 18.05.2018 03:01, Anson Huang wrote:
> Correct enet clock gates as below:
>
> CCGR6: IMX7D_ENET_AXI_ROOT_CLK (enet1 and enet2 bus clocks)
> CCGR112: IMX7D_ENET1_TIME_ROOT_CLK, IMX7D_ENET1_IPG_ROOT_CLK
> CCGR113: IMX7D_ENET2_TIME_ROOT_CLK, IMX7D_ENET2_IPG_ROOT_CLK
>
> Just rename unused IMX7D_ENETx_REF_ROOT_CLK for
> IMX7D_ENETx_IPG_ROOT_CLK instead of adding new clocks.
Are you sure that IMX7D_ENETx_REF_ROOT_CLK are not used?
I understand that the reference manual does not a gate at 0x44e0...
But in a earlier revision of our Colibri iMX7 we actually used clock
out, and referenced this clock to enable the reference clock (see also:
https://patchwork.kernel.org/patch/9211371/).
I guess if the gate really does not exist, then we should/would have to
set IMX7D_ENET1_REF_ROOT_DIV to use the SoC provided ref clock.
--
Stefan
>
> Based on Andy Duan's patch from the NXP kernel tree.
>
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
> drivers/clk/imx/clk-imx7d.c | 10 ++++++----
> include/dt-bindings/clock/imx7d-clock.h | 4 ++--
> 2 files changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/clk/imx/clk-imx7d.c b/drivers/clk/imx/clk-imx7d.c
> index 23d5090a..d4936b9 100644
> --- a/drivers/clk/imx/clk-imx7d.c
> +++ b/drivers/clk/imx/clk-imx7d.c
> @@ -26,6 +26,8 @@ static u32 share_count_sai1;
> static u32 share_count_sai2;
> static u32 share_count_sai3;
> static u32 share_count_nand;
> +static u32 share_count_enet1;
> +static u32 share_count_enet2;
>
> static const struct clk_div_table test_div_table[] = {
> { .val = 3, .div = 1, },
> @@ -805,6 +807,10 @@ static void __init imx7d_clocks_init(struct
> device_node *ccm_node)
> clks[IMX7D_MIPI_DSI_ROOT_CLK] = imx_clk_gate4("mipi_dsi_root_clk",
> "mipi_dsi_post_div", base + 0x4650, 0);
> clks[IMX7D_MIPI_CSI_ROOT_CLK] = imx_clk_gate4("mipi_csi_root_clk",
> "mipi_csi_post_div", base + 0x4640, 0);
> clks[IMX7D_MIPI_DPHY_ROOT_CLK] = imx_clk_gate4("mipi_dphy_root_clk",
> "mipi_dphy_post_div", base + 0x4660, 0);
> + clks[IMX7D_ENET1_IPG_ROOT_CLK] =
> imx_clk_gate2_shared2("enet1_ipg_root_clk", "enet_axi_post_div", base
> + 0x4700, 0, &share_count_enet1);
> + clks[IMX7D_ENET1_TIME_ROOT_CLK] =
> imx_clk_gate2_shared2("enet1_time_root_clk", "enet1_time_post_div",
> base + 0x4700, 0, &share_count_enet1);
> + clks[IMX7D_ENET2_IPG_ROOT_CLK] =
> imx_clk_gate2_shared2("enet2_ipg_root_clk", "enet_axi_post_div", base
> + 0x4710, 0, &share_count_enet2);
> + clks[IMX7D_ENET2_TIME_ROOT_CLK] =
> imx_clk_gate2_shared2("enet2_time_root_clk", "enet2_time_post_div",
> base + 0x4710, 0, &share_count_enet2);
> clks[IMX7D_SAI1_ROOT_CLK] = imx_clk_gate2_shared2("sai1_root_clk",
> "sai1_post_div", base + 0x48c0, 0, &share_count_sai1);
> clks[IMX7D_SAI1_IPG_CLK] = imx_clk_gate2_shared2("sai1_ipg_clk",
> "ipg_root_clk", base + 0x48c0, 0, &share_count_sai1);
> clks[IMX7D_SAI2_ROOT_CLK] = imx_clk_gate2_shared2("sai2_root_clk",
> "sai2_post_div", base + 0x48d0, 0, &share_count_sai2);
> @@ -812,10 +818,6 @@ static void __init imx7d_clocks_init(struct
> device_node *ccm_node)
> clks[IMX7D_SAI3_ROOT_CLK] = imx_clk_gate2_shared2("sai3_root_clk",
> "sai3_post_div", base + 0x48e0, 0, &share_count_sai3);
> clks[IMX7D_SAI3_IPG_CLK] = imx_clk_gate2_shared2("sai3_ipg_clk",
> "ipg_root_clk", base + 0x48e0, 0, &share_count_sai3);
> clks[IMX7D_SPDIF_ROOT_CLK] = imx_clk_gate4("spdif_root_clk",
> "spdif_post_div", base + 0x44d0, 0);
> - clks[IMX7D_ENET1_REF_ROOT_CLK] = imx_clk_gate4("enet1_ref_root_clk",
> "enet1_ref_post_div", base + 0x44e0, 0);
> - clks[IMX7D_ENET1_TIME_ROOT_CLK] =
> imx_clk_gate4("enet1_time_root_clk", "enet1_time_post_div", base +
> 0x44f0, 0);
> - clks[IMX7D_ENET2_REF_ROOT_CLK] = imx_clk_gate4("enet2_ref_root_clk",
> "enet2_ref_post_div", base + 0x4500, 0);
> - clks[IMX7D_ENET2_TIME_ROOT_CLK] =
> imx_clk_gate4("enet2_time_root_clk", "enet2_time_post_div", base +
> 0x4510, 0);
> clks[IMX7D_EIM_ROOT_CLK] = imx_clk_gate4("eim_root_clk",
> "eim_post_div", base + 0x4160, 0);
> clks[IMX7D_NAND_RAWNAND_CLK] =
> imx_clk_gate2_shared2("nand_rawnand_clk", "nand_root_clk", base +
> 0x4140, 0, &share_count_nand);
> clks[IMX7D_NAND_USDHC_BUS_RAWNAND_CLK] =
> imx_clk_gate2_shared2("nand_usdhc_rawnand_clk", "nand_usdhc_root_clk",
> base + 0x4140, 0, &share_count_nand);
> diff --git a/include/dt-bindings/clock/imx7d-clock.h
> b/include/dt-bindings/clock/imx7d-clock.h
> index b2325d3e2..0d67f53 100644
> --- a/include/dt-bindings/clock/imx7d-clock.h
> +++ b/include/dt-bindings/clock/imx7d-clock.h
> @@ -168,7 +168,7 @@
> #define IMX7D_SPDIF_ROOT_SRC 155
> #define IMX7D_SPDIF_ROOT_CG 156
> #define IMX7D_SPDIF_ROOT_DIV 157
> -#define IMX7D_ENET1_REF_ROOT_CLK 158
> +#define IMX7D_ENET1_IPG_ROOT_CLK 158
> #define IMX7D_ENET1_REF_ROOT_SRC 159
> #define IMX7D_ENET1_REF_ROOT_CG 160
> #define IMX7D_ENET1_REF_ROOT_DIV 161
> @@ -176,7 +176,7 @@
> #define IMX7D_ENET1_TIME_ROOT_SRC 163
> #define IMX7D_ENET1_TIME_ROOT_CG 164
> #define IMX7D_ENET1_TIME_ROOT_DIV 165
> -#define IMX7D_ENET2_REF_ROOT_CLK 166
> +#define IMX7D_ENET2_IPG_ROOT_CLK 166
> #define IMX7D_ENET2_REF_ROOT_SRC 167
> #define IMX7D_ENET2_REF_ROOT_CG 168
> #define IMX7D_ENET2_REF_ROOT_DIV 169
^ permalink raw reply
* [PATCH v9 15/15] ASoC: qcom: apq8096: Add db820c machine driver
From: Srinivas Kandagatla @ 2018-05-18 12:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to DB820c machine driver.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/Kconfig | 9 ++
sound/soc/qcom/Makefile | 2 +
sound/soc/qcom/apq8096.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 266 insertions(+)
create mode 100644 sound/soc/qcom/apq8096.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index 85bb7dd11fd9..87838fa27997 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -81,3 +81,12 @@ config SND_SOC_QDSP6
This will enable sound soc platform specific
audio drivers. This includes q6asm, q6adm,
q6afe interfaces to DSP using apr.
+
+config SND_SOC_MSM8996
+ tristate "SoC Machine driver for MSM8996 and APQ8096 boards"
+ depends on QCOM_APR
+ select SND_SOC_QDSP6
+ help
+ Support for Qualcomm Technologies LPASS audio block in
+ APQ8096 SoC-based systems.
+ Say Y if you want to use audio device on this SoCs
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
index 0276717917c0..206945bb9ba1 100644
--- a/sound/soc/qcom/Makefile
+++ b/sound/soc/qcom/Makefile
@@ -13,9 +13,11 @@ obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
# Machine
snd-soc-storm-objs := storm.o
snd-soc-apq8016-sbc-objs := apq8016_sbc.o
+snd-soc-apq8096-objs := apq8096.o
obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o
obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o
+obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o
#DSP lib
obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/
diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c
new file mode 100644
index 000000000000..561cd429e6f2
--- /dev/null
+++ b/sound/soc/qcom/apq8096.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/soc/qcom/apr.h>
+#include <linux/module.h>
+#include <linux/component.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+
+static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_interval *rate = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels = hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ rate->min = rate->max = 48000;
+ channels->min = channels->max = 2;
+
+ return 0;
+}
+
+static int apq8096_sbc_parse_of(struct snd_soc_card *card)
+{
+ struct device_node *np;
+ struct device_node *codec = NULL;
+ struct device_node *platform = NULL;
+ struct device_node *cpu = NULL;
+ struct device *dev = card->dev;
+ struct snd_soc_dai_link *link;
+ int ret, num_links;
+
+ ret = snd_soc_of_parse_card_name(card, "qcom,model");
+ if (ret) {
+ dev_err(dev, "Error parsing card name: %d\n", ret);
+ return ret;
+ }
+
+ /* DAPM routes */
+ if (of_property_read_bool(dev->of_node, "qcom,audio-routing")) {
+ ret = snd_soc_of_parse_audio_routing(card,
+ "qcom,audio-routing");
+ if (ret)
+ return ret;
+ }
+
+ /* Populate links */
+ num_links = of_get_child_count(dev->of_node);
+
+ /* Allocate the DAI link array */
+ card->dai_link = kcalloc(num_links, sizeof(*link), GFP_KERNEL);
+ if (!card->dai_link)
+ return -ENOMEM;
+
+ card->num_links = num_links;
+ link = card->dai_link;
+
+ for_each_child_of_node(dev->of_node, np) {
+ cpu = of_get_child_by_name(np, "cpu");
+ if (!cpu) {
+ dev_err(dev, "Can't find cpu DT node\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0);
+ if (!link->cpu_of_node) {
+ dev_err(card->dev, "error getting cpu phandle\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name);
+ if (ret) {
+ dev_err(card->dev, "error getting cpu dai name\n");
+ goto err;
+ }
+
+ platform = of_get_child_by_name(np, "platform");
+ codec = of_get_child_by_name(np, "codec");
+ if (codec && platform) {
+ link->platform_of_node = of_parse_phandle(platform,
+ "sound-dai",
+ 0);
+ if (!link->platform_of_node) {
+ dev_err(card->dev, "platform dai not found\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
+ if (ret < 0) {
+ dev_err(card->dev, "codec dai not found\n");
+ goto err;
+ }
+ link->no_pcm = 1;
+ link->ignore_pmdown_time = 1;
+ link->be_hw_params_fixup = apq8096_be_hw_params_fixup;
+ } else {
+ link->platform_of_node = link->cpu_of_node;
+ link->codec_dai_name = "snd-soc-dummy-dai";
+ link->codec_name = "snd-soc-dummy";
+ link->dynamic = 1;
+ }
+
+ link->ignore_suspend = 1;
+ ret = of_property_read_string(np, "link-name", &link->name);
+ if (ret) {
+ dev_err(card->dev, "error getting codec dai_link name\n");
+ goto err;
+ }
+
+ link->dpcm_playback = 1;
+ link->dpcm_capture = 1;
+ link->stream_name = link->name;
+ link++;
+ }
+
+ return 0;
+err:
+ of_node_put(cpu);
+ of_node_put(codec);
+ of_node_put(platform);
+ kfree(card->dai_link);
+ return ret;
+}
+
+static int apq8096_bind(struct device *dev)
+{
+ struct snd_soc_card *card;
+ int ret;
+
+ card = kzalloc(sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ component_bind_all(dev, card);
+ card->dev = dev;
+ ret = apq8096_sbc_parse_of(card);
+ if (ret) {
+ dev_err(dev, "Error parsing OF data\n");
+ goto err;
+ }
+
+ ret = snd_soc_register_card(card);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ component_unbind_all(dev, card);
+ kfree(card);
+ return ret;
+}
+
+static void apq8096_unbind(struct device *dev)
+{
+ struct snd_soc_card *card = dev_get_drvdata(dev);
+
+ snd_soc_unregister_card(card);
+ component_unbind_all(dev, card);
+ kfree(card->dai_link);
+ kfree(card);
+}
+
+static const struct component_master_ops apq8096_ops = {
+ .bind = apq8096_bind,
+ .unbind = apq8096_unbind,
+};
+
+static int apq8016_compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static void apq8016_release_of(struct device *dev, void *data)
+{
+ of_node_put(data);
+}
+
+static int add_audio_components(struct device *dev,
+ struct component_match **matchptr)
+{
+ struct device_node *np, *platform, *cpu, *node, *dai_node;
+
+ node = dev->of_node;
+
+ for_each_child_of_node(node, np) {
+ cpu = of_get_child_by_name(np, "cpu");
+ if (cpu) {
+ dai_node = of_parse_phandle(cpu, "sound-dai", 0);
+ of_node_get(dai_node);
+ component_match_add_release(dev, matchptr,
+ apq8016_release_of,
+ apq8016_compare_of,
+ dai_node);
+ }
+
+ platform = of_get_child_by_name(np, "platform");
+ if (platform) {
+ dai_node = of_parse_phandle(platform, "sound-dai", 0);
+ component_match_add_release(dev, matchptr,
+ apq8016_release_of,
+ apq8016_compare_of,
+ dai_node);
+ }
+ }
+
+ return 0;
+}
+
+static int apq8096_platform_probe(struct platform_device *pdev)
+{
+ struct component_match *match = NULL;
+ int ret;
+
+ ret = add_audio_components(&pdev->dev, &match);
+ if (ret)
+ return ret;
+
+ return component_master_add_with_match(&pdev->dev, &apq8096_ops, match);
+}
+
+static int apq8096_platform_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &apq8096_ops);
+
+ return 0;
+}
+
+static const struct of_device_id msm_snd_apq8096_dt_match[] = {
+ {.compatible = "qcom,apq8096-sndcard"},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, msm_snd_apq8096_dt_match);
+
+static struct platform_driver msm_snd_apq8096_driver = {
+ .probe = apq8096_platform_probe,
+ .remove = apq8096_platform_remove,
+ .driver = {
+ .name = "msm-snd-apq8096",
+ .owner = THIS_MODULE,
+ .of_match_table = msm_snd_apq8096_dt_match,
+ },
+};
+module_platform_driver(msm_snd_apq8096_driver);
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
+MODULE_DESCRIPTION("APQ8096 ASoC Machine Driver");
+MODULE_LICENSE("GPL v2");
--
2.16.2
^ permalink raw reply related
* [PATCH v9 14/15] ASoC: qdsp6: dt-bindings: Add apq8096 machine bindings
From: Srinivas Kandagatla @ 2018-05-18 12:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
Add devicetree bindings documentation file for Qualcomm apq8096 sound card.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-by: Rob Herring <robh@kernel.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
.../devicetree/bindings/sound/qcom,apq8096.txt | 109 +++++++++++++++++++++
1 file changed, 109 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,apq8096.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,apq8096.txt b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt
new file mode 100644
index 000000000000..aa54e49fc8a2
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt
@@ -0,0 +1,109 @@
+* Qualcomm Technologies APQ8096 ASoC sound card driver
+
+This binding describes the APQ8096 sound card, which uses qdsp for audio.
+
+- compatible:
+ Usage: required
+ Value type: <stringlist>
+ Definition: must be "qcom,apq8096-sndcard"
+
+- qcom,audio-routing:
+ Usage: Optional
+ Value type: <stringlist>
+ Definition: A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the
+ connection's sink, the second being the connection's
+ source. Valid names could be power supplies, MicBias
+ of codec and the jacks on the board:
+ Valid names include:
+
+ Board Connectors:
+ "Headphone Left"
+ "Headphone Right"
+ "Earphone"
+ "Line Out1"
+ "Line Out2"
+ "Line Out3"
+ "Line Out4"
+ "Analog Mic1"
+ "Analog Mic2"
+ "Analog Mic3"
+ "Analog Mic4"
+ "Analog Mic5"
+ "Analog Mic6"
+ "Digital Mic2"
+ "Digital Mic3"
+
+ Audio pins and MicBias on WCD9335 Codec:
+ "MIC_BIAS1
+ "MIC_BIAS2"
+ "MIC_BIAS3"
+ "MIC_BIAS4"
+ "AMIC1"
+ "AMIC2"
+ "AMIC3"
+ "AMIC4"
+ "AMIC5"
+ "AMIC6"
+ "AMIC6"
+ "DMIC1"
+ "DMIC2"
+ "DMIC3"
+= dailinks
+Each subnode of sndcard represents either a dailink, and subnodes of each
+dailinks would be cpu/codec/platform dais.
+
+- link-name:
+ Usage: required
+ Value type: <string>
+ Definition: User friendly name for dai link
+
+= CPU, PLATFORM, CODEC dais subnodes
+- cpu:
+ Usage: required
+ Value type: <subnode>
+ Definition: cpu dai sub-node
+
+- codec:
+ Usage: Optional
+ Value type: <subnode>
+ Definition: codec dai sub-node
+
+- platform:
+ Usage: Optional
+ Value type: <subnode>
+ Definition: platform dai sub-node
+
+- sound-dai:
+ Usage: required
+ Value type: <phandle with arguments>
+ Definition: dai phandle/s and port of CPU/CODEC/PLATFORM node.
+
+Example:
+
+audio {
+ compatible = "qcom,apq8096-sndcard";
+ qcom,model = "DB820c";
+
+ mm1-dai-link {
+ link-name = "MultiMedia1";
+ cpu {
+ sound-dai = <&q6asmdai MSM_FRONTEND_DAI_MULTIMEDIA1>;
+ };
+ };
+
+ hdmi-dai-link {
+ link-name = "HDMI Playback";
+ cpu {
+ sound-dai = <&q6afe HDMI_RX>;
+ };
+
+ platform {
+ sound-dai = <&q6adm>;
+ };
+
+ codec {
+ sound-dai = <&hdmi 0>;
+ };
+ };
+};
--
2.16.2
^ permalink raw reply related
* [PATCH v9 13/15] ASoC: qdsp6: q6asm: Add q6asm dai driver
From: Srinivas Kandagatla @ 2018-05-18 12:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to q6asm dai driver which configures Q6ASM streams
to pass pcm data.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/Kconfig | 4 +
sound/soc/qcom/qdsp6/Makefile | 1 +
sound/soc/qcom/qdsp6/q6asm-dai.c | 624 +++++++++++++++++++++++++++++++++++++++
3 files changed, 629 insertions(+)
create mode 100644 sound/soc/qcom/qdsp6/q6asm-dai.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index d3523a30d942..85bb7dd11fd9 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -62,6 +62,9 @@ config SND_SOC_QDSP6_ROUTING
config SND_SOC_QDSP6_ASM
tristate
+config SND_SOC_QDSP6_ASM_DAI
+ tristate
+
config SND_SOC_QDSP6
tristate "SoC ALSA audio driver for QDSP6"
depends on QCOM_APR && HAS_DMA
@@ -72,6 +75,7 @@ config SND_SOC_QDSP6
select SND_SOC_QDSP6_ADM
select SND_SOC_QDSP6_ROUTING
select SND_SOC_QDSP6_ASM
+ select SND_SOC_QDSP6_ASM_DAI
help
To add support for MSM QDSP6 Soc Audio.
This will enable sound soc platform specific
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
index bada1aa303c2..c33b3cacbea1 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o
obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o
obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o
obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o
+obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o
diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c
new file mode 100644
index 000000000000..349c6a883c63
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6asm-dai.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/component.h>
+#include <sound/soc.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <asm/dma.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_device.h>
+#include <sound/pcm_params.h>
+#include "q6asm.h"
+#include "q6routing.h"
+#include "q6dsp-errno.h"
+
+#define DRV_NAME "q6asm-fe-dai"
+
+#define PLAYBACK_MIN_NUM_PERIODS 2
+#define PLAYBACK_MAX_NUM_PERIODS 8
+#define PLAYBACK_MAX_PERIOD_SIZE 65536
+#define PLAYBACK_MIN_PERIOD_SIZE 128
+#define CAPTURE_MIN_NUM_PERIODS 2
+#define CAPTURE_MAX_NUM_PERIODS 8
+#define CAPTURE_MAX_PERIOD_SIZE 4096
+#define CAPTURE_MIN_PERIOD_SIZE 320
+#define SID_MASK_DEFAULT 0xF
+
+enum stream_state {
+ Q6ASM_STREAM_IDLE = 0,
+ Q6ASM_STREAM_STOPPED,
+ Q6ASM_STREAM_RUNNING,
+};
+
+struct q6asm_dai_rtd {
+ struct snd_pcm_substream *substream;
+ phys_addr_t phys;
+ unsigned int pcm_size;
+ unsigned int pcm_count;
+ unsigned int pcm_irq_pos; /* IRQ position */
+ unsigned int periods;
+ uint16_t bits_per_sample;
+ uint16_t source; /* Encoding source bit mask */
+ struct audio_client *audio_client;
+ uint16_t session_id;
+ enum stream_state state;
+};
+
+struct q6asm_dai_data {
+ long long int sid;
+};
+
+static struct snd_pcm_hardware q6asm_dai_hardware_capture = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE),
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 4,
+ .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS *
+ CAPTURE_MAX_PERIOD_SIZE,
+ .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
+ .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
+ .periods_min = CAPTURE_MIN_NUM_PERIODS,
+ .periods_max = CAPTURE_MAX_NUM_PERIODS,
+ .fifo_size = 0,
+};
+
+static struct snd_pcm_hardware q6asm_dai_hardware_playback = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE),
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 8,
+ .buffer_bytes_max = (PLAYBACK_MAX_NUM_PERIODS *
+ PLAYBACK_MAX_PERIOD_SIZE),
+ .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
+ .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
+ .periods_min = PLAYBACK_MIN_NUM_PERIODS,
+ .periods_max = PLAYBACK_MAX_NUM_PERIODS,
+ .fifo_size = 0,
+};
+
+#define Q6ASM_FEDAI_DRIVER(num) { \
+ .playback = { \
+ .stream_name = "MultiMedia"#num" Playback", \
+ .rates = (SNDRV_PCM_RATE_8000_192000| \
+ SNDRV_PCM_RATE_KNOT), \
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE), \
+ .channels_min = 1, \
+ .channels_max = 8, \
+ .rate_min = 8000, \
+ .rate_max = 192000, \
+ }, \
+ .capture = { \
+ .stream_name = "MultiMedia"#num" Capture", \
+ .rates = (SNDRV_PCM_RATE_8000_48000| \
+ SNDRV_PCM_RATE_KNOT), \
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE), \
+ .channels_min = 1, \
+ .channels_max = 4, \
+ .rate_min = 8000, \
+ .rate_max = 48000, \
+ }, \
+ .name = "MultiMedia"#num, \
+ .probe = fe_dai_probe, \
+ .id = MSM_FRONTEND_DAI_MULTIMEDIA##num, \
+ }
+
+/* Conventional and unconventional sample rate supported */
+static unsigned int supported_sample_rates[] = {
+ 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
+ 88200, 96000, 176400, 192000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_sample_rates = {
+ .count = ARRAY_SIZE(supported_sample_rates),
+ .list = supported_sample_rates,
+ .mask = 0,
+};
+
+static void event_handler(uint32_t opcode, uint32_t token,
+ uint32_t *payload, void *priv)
+{
+ struct q6asm_dai_rtd *prtd = priv;
+ struct snd_pcm_substream *substream = prtd->substream;
+
+ switch (opcode) {
+ case ASM_CLIENT_EVENT_CMD_RUN_DONE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ q6asm_write_async(prtd->audio_client,
+ prtd->pcm_count, 0, 0, NO_TIMESTAMP);
+ break;
+ case ASM_CLIENT_EVENT_CMD_EOS_DONE:
+ prtd->state = Q6ASM_STREAM_STOPPED;
+ break;
+ case ASM_CLIENT_EVENT_DATA_WRITE_DONE: {
+ prtd->pcm_irq_pos += prtd->pcm_count;
+ snd_pcm_period_elapsed(substream);
+ if (prtd->state == Q6ASM_STREAM_RUNNING)
+ q6asm_write_async(prtd->audio_client,
+ prtd->pcm_count, 0, 0, NO_TIMESTAMP);
+
+ break;
+ }
+ case ASM_CLIENT_EVENT_DATA_READ_DONE:
+ prtd->pcm_irq_pos += prtd->pcm_count;
+ snd_pcm_period_elapsed(substream);
+ if (prtd->state == Q6ASM_STREAM_RUNNING)
+ q6asm_read(prtd->audio_client);
+
+ break;
+ default:
+ break;
+ }
+}
+
+static int q6asm_dai_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+ struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME);
+ struct q6asm_dai_data *pdata;
+ int ret, i;
+
+ pdata = snd_soc_component_get_drvdata(c);
+ if (!pdata)
+ return -EINVAL;
+
+ if (!prtd || !prtd->audio_client) {
+ pr_err("%s: private data null or audio client freed\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
+ prtd->pcm_irq_pos = 0;
+ /* rate and channels are sent to audio driver */
+ if (prtd->state) {
+ /* clear the previous setup if any */
+ q6asm_cmd(prtd->audio_client, CMD_CLOSE);
+ q6asm_unmap_memory_regions(substream->stream,
+ prtd->audio_client);
+ q6routing_stream_close(soc_prtd->dai_link->id,
+ substream->stream);
+ }
+
+ ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client,
+ prtd->phys,
+ (prtd->pcm_size / prtd->periods),
+ prtd->periods);
+
+ if (ret < 0) {
+ pr_err("Audio Start: Buffer Allocation failed rc = %d\n",
+ ret);
+ return -ENOMEM;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM,
+ prtd->bits_per_sample);
+ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM,
+ prtd->bits_per_sample);
+ }
+
+ if (ret < 0) {
+ pr_err("%s: q6asm_open_write failed\n", __func__);
+ q6asm_audio_client_free(prtd->audio_client);
+ prtd->audio_client = NULL;
+ return -ENOMEM;
+ }
+
+ prtd->session_id = q6asm_get_session_id(prtd->audio_client);
+ ret = q6routing_stream_open(soc_prtd->dai_link->id, LEGACY_PCM_MODE,
+ prtd->session_id, substream->stream);
+ if (ret) {
+ pr_err("%s: stream reg failed ret:%d\n", __func__, ret);
+ return ret;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = q6asm_media_format_block_multi_ch_pcm(
+ prtd->audio_client, runtime->rate,
+ runtime->channels, NULL,
+ prtd->bits_per_sample);
+ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = q6asm_enc_cfg_blk_pcm_format_support(prtd->audio_client,
+ runtime->rate, runtime->channels,
+ prtd->bits_per_sample);
+
+ /* Queue the buffers */
+ for (i = 0; i < runtime->periods; i++)
+ q6asm_read(prtd->audio_client);
+
+ }
+ if (ret < 0)
+ pr_info("%s: CMD Format block failed\n", __func__);
+
+ prtd->state = Q6ASM_STREAM_RUNNING;
+
+ return 0;
+}
+
+static int q6asm_dai_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ prtd->state = Q6ASM_STREAM_STOPPED;
+ ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int q6asm_dai_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = soc_prtd->cpu_dai;
+ struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME);
+ struct q6asm_dai_rtd *prtd;
+ struct q6asm_dai_data *pdata;
+ struct device *dev = c->dev;
+ int ret = 0;
+ int stream_id;
+
+ stream_id = cpu_dai->driver->id;
+
+ pdata = snd_soc_component_get_drvdata(c);
+ if (!pdata) {
+ pr_err("Drv data not found ..\n");
+ return -EINVAL;
+ }
+
+ prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ prtd->substream = substream;
+ prtd->audio_client = q6asm_audio_client_alloc(dev,
+ (q6asm_cb)event_handler, prtd, stream_id,
+ LEGACY_PCM_MODE);
+ if (!prtd->audio_client) {
+ pr_info("%s: Could not allocate memory\n", __func__);
+ kfree(prtd);
+ return -ENOMEM;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ runtime->hw = q6asm_dai_hardware_playback;
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ runtime->hw = q6asm_dai_hardware_capture;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraints_sample_rates);
+ if (ret < 0)
+ pr_info("snd_pcm_hw_constraint_list failed\n");
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ pr_info("snd_pcm_hw_constraint_integer failed\n");
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ret = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE,
+ PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE);
+ if (ret < 0) {
+ pr_err("constraint for buffer bytes min max ret = %d\n",
+ ret);
+ }
+ }
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+ if (ret < 0) {
+ pr_err("constraint for period bytes step ret = %d\n",
+ ret);
+ }
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+ if (ret < 0) {
+ pr_err("constraint for buffer bytes step ret = %d\n",
+ ret);
+ }
+
+ runtime->private_data = prtd;
+
+ snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_playback);
+
+ runtime->dma_bytes = q6asm_dai_hardware_playback.buffer_bytes_max;
+
+
+ if (pdata->sid < 0)
+ prtd->phys = substream->dma_buffer.addr;
+ else
+ prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int q6asm_dai_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+
+ if (prtd->audio_client) {
+ q6asm_cmd(prtd->audio_client, CMD_CLOSE);
+ q6asm_unmap_memory_regions(substream->stream,
+ prtd->audio_client);
+ q6asm_audio_client_free(prtd->audio_client);
+ prtd->audio_client = NULL;
+ }
+ q6routing_stream_close(soc_prtd->dai_link->id,
+ substream->stream);
+ kfree(prtd);
+ return 0;
+}
+
+static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_pcm_substream *substream)
+{
+
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+
+ if (prtd->pcm_irq_pos >= prtd->pcm_size)
+ prtd->pcm_irq_pos = 0;
+
+ return bytes_to_frames(runtime, (prtd->pcm_irq_pos));
+}
+
+static int q6asm_dai_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
+ struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME);
+ struct device *dev = c->dev;
+
+ return dma_mmap_coherent(dev, vma,
+ runtime->dma_area, runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static int q6asm_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct q6asm_dai_rtd *prtd = runtime->private_data;
+
+ prtd->pcm_size = params_buffer_bytes(params);
+ prtd->periods = params_periods(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ prtd->bits_per_sample = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ prtd->bits_per_sample = 24;
+ break;
+ }
+
+ return 0;
+}
+
+static struct snd_pcm_ops q6asm_dai_ops = {
+ .open = q6asm_dai_open,
+ .hw_params = q6asm_dai_hw_params,
+ .close = q6asm_dai_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .prepare = q6asm_dai_prepare,
+ .trigger = q6asm_dai_trigger,
+ .pointer = q6asm_dai_pointer,
+ .mmap = q6asm_dai_mmap,
+};
+
+static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm_substream *psubstream, *csubstream;
+ struct snd_soc_component *c = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct snd_pcm *pcm = rtd->pcm;
+ struct device *dev;
+ int size, ret;
+
+ dev = c->dev;
+ size = q6asm_dai_hardware_playback.buffer_bytes_max;
+ psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (psubstream) {
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
+ &psubstream->dma_buffer);
+ if (ret) {
+ dev_err(dev, "Cannot allocate buffer(s)\n");
+ return ret;
+ }
+ }
+
+ csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ if (csubstream) {
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
+ &csubstream->dma_buffer);
+ if (ret) {
+ dev_err(dev, "Cannot allocate buffer(s)\n");
+ if (psubstream)
+ snd_dma_free_pages(&psubstream->dma_buffer);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static void q6asm_dai_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
+ substream = pcm->streams[i].substream;
+ if (substream) {
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+ }
+}
+
+static const struct snd_soc_dapm_route afe_pcm_routes[] = {
+ {"MM_DL1", NULL, "MultiMedia1 Playback" },
+ {"MM_DL2", NULL, "MultiMedia2 Playback" },
+ {"MM_DL3", NULL, "MultiMedia3 Playback" },
+ {"MM_DL4", NULL, "MultiMedia4 Playback" },
+ {"MM_DL5", NULL, "MultiMedia5 Playback" },
+ {"MM_DL6", NULL, "MultiMedia6 Playback" },
+ {"MM_DL7", NULL, "MultiMedia7 Playback" },
+ {"MM_DL7", NULL, "MultiMedia8 Playback" },
+ {"MultiMedia1 Capture", NULL, "MM_UL1"},
+ {"MultiMedia2 Capture", NULL, "MM_UL2"},
+ {"MultiMedia3 Capture", NULL, "MM_UL3"},
+ {"MultiMedia4 Capture", NULL, "MM_UL4"},
+ {"MultiMedia5 Capture", NULL, "MM_UL5"},
+ {"MultiMedia6 Capture", NULL, "MM_UL6"},
+ {"MultiMedia7 Capture", NULL, "MM_UL7"},
+ {"MultiMedia8 Capture", NULL, "MM_UL8"},
+
+};
+
+static int fe_dai_probe(struct snd_soc_dai *dai)
+{
+ struct snd_soc_dapm_context *dapm;
+
+ dapm = snd_soc_component_get_dapm(dai->component);
+ snd_soc_dapm_add_routes(dapm, afe_pcm_routes,
+ ARRAY_SIZE(afe_pcm_routes));
+
+ return 0;
+}
+
+
+static const struct snd_soc_component_driver q6asm_fe_dai_component = {
+ .name = DRV_NAME,
+ .ops = &q6asm_dai_ops,
+ .pcm_new = q6asm_dai_pcm_new,
+ .pcm_free = q6asm_dai_pcm_free,
+
+};
+
+static struct snd_soc_dai_driver q6asm_fe_dais[] = {
+ Q6ASM_FEDAI_DRIVER(1),
+ Q6ASM_FEDAI_DRIVER(2),
+ Q6ASM_FEDAI_DRIVER(3),
+ Q6ASM_FEDAI_DRIVER(4),
+ Q6ASM_FEDAI_DRIVER(5),
+ Q6ASM_FEDAI_DRIVER(6),
+ Q6ASM_FEDAI_DRIVER(7),
+ Q6ASM_FEDAI_DRIVER(8),
+};
+
+static int q6asm_dai_bind(struct device *dev, struct device *master, void *data)
+{
+ struct device_node *node = dev->of_node;
+ struct of_phandle_args args;
+ struct q6asm_dai_data *pdata;
+ int rc;
+
+ pdata = kzalloc(sizeof(struct q6asm_dai_data), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
+ if (rc < 0)
+ pdata->sid = -1;
+ else
+ pdata->sid = args.args[0] & SID_MASK_DEFAULT;
+
+ dev_set_drvdata(dev, pdata);
+
+ return snd_soc_register_component(dev, &q6asm_fe_dai_component,
+ q6asm_fe_dais,
+ ARRAY_SIZE(q6asm_fe_dais));
+}
+static void q6asm_dai_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct q6asm_dai_data *pdata = dev_get_drvdata(dev);
+
+ snd_soc_unregister_component(dev);
+
+ kfree(pdata);
+
+}
+
+static const struct component_ops q6asm_dai_comp_ops = {
+ .bind = q6asm_dai_bind,
+ .unbind = q6asm_dai_unbind,
+};
+
+static int q6asm_dai_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &q6asm_dai_comp_ops);
+}
+
+static int q6asm_dai_dev_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &q6asm_dai_comp_ops);
+ return 0;
+}
+
+static struct platform_driver q6asm_dai_platform_driver = {
+ .driver = {
+ .name = "q6asm-dai",
+ },
+ .probe = q6asm_dai_probe,
+ .remove = q6asm_dai_dev_remove,
+};
+module_platform_driver(q6asm_dai_platform_driver);
+
+MODULE_DESCRIPTION("Q6ASM dai driver");
+MODULE_LICENSE("GPL v2");
--
2.16.2
^ permalink raw reply related
* [PATCH v9 12/15] ASoC: qdsp6: q6afe: Add q6afe dai driver
From: Srinivas Kandagatla @ 2018-05-18 12:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180518125610.26200-1-srinivas.kandagatla@linaro.org>
This patch adds support to q6afe backend dais driver.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Reviewed-and-tested-by: Rohit kumar <rohitkr@codeaurora.org>
Reviewed-by: Banajit Goswami <bgoswami@codeaurora.org>
---
sound/soc/qcom/Kconfig | 4 +
sound/soc/qcom/qdsp6/Makefile | 1 +
sound/soc/qcom/qdsp6/q6afe-dai.c | 748 +++++++++++++++++++++++++++++++++++++++
3 files changed, 753 insertions(+)
create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index 43f9ed85efa8..d3523a30d942 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -50,6 +50,9 @@ config SND_SOC_QDSP6_CORE
config SND_SOC_QDSP6_AFE
tristate
+config SND_SOC_QDSP6_AFE_DAI
+ tristate
+
config SND_SOC_QDSP6_ADM
tristate
@@ -65,6 +68,7 @@ config SND_SOC_QDSP6
select SND_SOC_QDSP6_COMMON
select SND_SOC_QDSP6_CORE
select SND_SOC_QDSP6_AFE
+ select SND_SOC_QDSP6_AFE_DAI
select SND_SOC_QDSP6_ADM
select SND_SOC_QDSP6_ROUTING
select SND_SOC_QDSP6_ASM
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
index 0e8e2febb7ec..bada1aa303c2 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -1,6 +1,7 @@
obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o
obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o
obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o
+obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o
obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o
obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o
obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o
diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c
new file mode 100644
index 000000000000..4378e29a95c5
--- /dev/null
+++ b/sound/soc/qcom/qdsp6/q6afe-dai.c
@@ -0,0 +1,748 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include "q6afe.h"
+
+struct q6afe_dai_priv_data {
+ uint32_t sd_line_mask;
+};
+
+struct q6afe_dai_data {
+ struct q6afe_port *port[AFE_PORT_MAX];
+ struct q6afe_port_config port_config[AFE_PORT_MAX];
+ bool is_port_started[AFE_PORT_MAX];
+ struct q6afe_dai_priv_data priv[AFE_PORT_MAX];
+};
+
+static int q6slim_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_slim_cfg *slim = &dai_data->port_config[dai->id].slim;
+
+ slim->num_channels = params_channels(params);
+ slim->sample_rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_SPECIAL:
+ slim->bit_width = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ slim->bit_width = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ slim->bit_width = 32;
+ break;
+ default:
+ pr_err("%s: format %d\n",
+ __func__, params_format(params));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int channels = params_channels(params);
+ struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi;
+
+ hdmi->sample_rate = params_rate(params);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ hdmi->bit_width = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ hdmi->bit_width = 24;
+ break;
+ }
+
+ /* HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4 */
+ switch (channels) {
+ case 2:
+ hdmi->channel_allocation = 0;
+ break;
+ case 3:
+ hdmi->channel_allocation = 0x02;
+ break;
+ case 4:
+ hdmi->channel_allocation = 0x06;
+ break;
+ case 5:
+ hdmi->channel_allocation = 0x0A;
+ break;
+ case 6:
+ hdmi->channel_allocation = 0x0B;
+ break;
+ case 7:
+ hdmi->channel_allocation = 0x12;
+ break;
+ case 8:
+ hdmi->channel_allocation = 0x13;
+ break;
+ default:
+ dev_err(dai->dev, "invalid Channels = %u\n", channels);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int q6i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg;
+
+ i2s->sample_rate = params_rate(params);
+ i2s->bit_width = params_width(params);
+ i2s->num_channels = params_channels(params);
+ i2s->sd_line_mask = dai_data->priv[dai->id].sd_line_mask;
+
+ return 0;
+}
+
+static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg;
+
+ i2s->fmt = fmt;
+
+ return 0;
+}
+
+static void q6afe_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int rc;
+
+ rc = q6afe_port_stop(dai_data->port[dai->id]);
+ if (rc < 0)
+ dev_err(dai->dev, "fail to close AFE port (%d)\n", rc);
+
+ dai_data->is_port_started[dai->id] = false;
+
+}
+
+static int q6afe_mi2s_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int rc;
+
+ if (dai_data->is_port_started[dai->id]) {
+ /* stop the port and restart with new port config */
+ rc = q6afe_port_stop(dai_data->port[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to close AFE port (%d)\n", rc);
+ return rc;
+ }
+ }
+
+ rc = q6afe_i2s_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].i2s_cfg);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to prepare AFE port %x\n", dai->id);
+ return rc;
+ }
+
+ rc = q6afe_port_start(dai_data->port[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to start AFE port %x\n", dai->id);
+ return rc;
+ }
+ dai_data->is_port_started[dai->id] = true;
+
+ return 0;
+}
+
+static int q6afe_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ int rc;
+
+ if (dai_data->is_port_started[dai->id]) {
+ /* stop the port and restart with new port config */
+ rc = q6afe_port_stop(dai_data->port[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to close AFE port (%d)\n", rc);
+ return rc;
+ }
+ }
+
+ if (dai->id == HDMI_RX)
+ q6afe_hdmi_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].hdmi);
+ else if (dai->id >= SLIMBUS_0_RX && dai->id <= SLIMBUS_6_TX)
+ q6afe_slim_port_prepare(dai_data->port[dai->id],
+ &dai_data->port_config[dai->id].slim);
+
+ rc = q6afe_port_start(dai_data->port[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "fail to start AFE port %x\n", dai->id);
+ return rc;
+ }
+ dai_data->is_port_started[dai->id] = true;
+
+ return 0;
+}
+
+static int q6slim_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num, unsigned int *tx_slot,
+ unsigned int rx_num, unsigned int *rx_slot)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_port_config *pcfg = &dai_data->port_config[dai->id];
+ int i;
+
+ if (!rx_slot) {
+ pr_err("%s: rx slot not found\n", __func__);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < rx_num; i++) {
+ pcfg->slim.ch_mapping[i] = rx_slot[i];
+ pr_debug("%s: find number of channels[%d] ch[%d]\n",
+ __func__, i, rx_slot[i]);
+ }
+
+ pcfg->slim.num_channels = rx_num;
+
+ pr_debug("%s: SLIMBUS_%d_RX cnt[%d] ch[%d %d]\n", __func__,
+ (dai->id - SLIMBUS_0_RX) / 2, rx_num,
+ pcfg->slim.ch_mapping[0],
+ pcfg->slim.ch_mapping[1]);
+
+ return 0;
+}
+
+static int q6afe_mi2s_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_port *port = dai_data->port[dai->id];
+
+ switch (clk_id) {
+ case LPAIF_DIG_CLK:
+ return q6afe_port_set_sysclk(port, clk_id, 0, 5, freq, dir);
+ case LPAIF_BIT_CLK:
+ case LPAIF_OSR_CLK:
+ return q6afe_port_set_sysclk(port, clk_id,
+ Q6AFE_LPASS_CLK_SRC_INTERNAL,
+ Q6AFE_LPASS_CLK_ROOT_DEFAULT,
+ freq, dir);
+ case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1:
+ return q6afe_port_set_sysclk(port, clk_id,
+ Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO,
+ Q6AFE_LPASS_CLK_ROOT_DEFAULT,
+ freq, dir);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_route q6afe_dapm_routes[] = {
+ {"HDMI Playback", NULL, "HDMI_RX"},
+ {"Slimbus1 Playback", NULL, "SLIMBUS_1_RX"},
+ {"Slimbus2 Playback", NULL, "SLIMBUS_2_RX"},
+ {"Slimbus3 Playback", NULL, "SLIMBUS_3_RX"},
+ {"Slimbus4 Playback", NULL, "SLIMBUS_4_RX"},
+ {"Slimbus5 Playback", NULL, "SLIMBUS_5_RX"},
+ {"Slimbus6 Playback", NULL, "SLIMBUS_6_RX"},
+
+ {"Primary MI2S Playback", NULL, "PRI_MI2S_RX"},
+ {"Secondary MI2S Playback", NULL, "SEC_MI2S_RX"},
+ {"Tertiary MI2S Playback", NULL, "TERT_MI2S_RX"},
+ {"Quaternary MI2S Playback", NULL, "QUAT_MI2S_RX"},
+
+ {"TERT_MI2S_TX", NULL, "Tertiary MI2S Capture"},
+ {"PRI_MI2S_TX", NULL, "Primary MI2S Capture"},
+ {"SEC_MI2S_TX", NULL, "Secondary MI2S Capture"},
+ {"QUAT_MI2S_TX", NULL, "Quaternary MI2S Capture"},
+};
+
+static struct snd_soc_dai_ops q6hdmi_ops = {
+ .prepare = q6afe_dai_prepare,
+ .hw_params = q6hdmi_hw_params,
+ .shutdown = q6afe_dai_shutdown,
+};
+
+static struct snd_soc_dai_ops q6i2s_ops = {
+ .prepare = q6afe_mi2s_prepare,
+ .hw_params = q6i2s_hw_params,
+ .set_fmt = q6i2s_set_fmt,
+ .shutdown = q6afe_dai_shutdown,
+ .set_sysclk = q6afe_mi2s_set_sysclk,
+};
+
+static struct snd_soc_dai_ops q6slim_ops = {
+ .prepare = q6afe_dai_prepare,
+ .hw_params = q6slim_hw_params,
+ .shutdown = q6afe_dai_shutdown,
+ .set_channel_map = q6slim_set_channel_map,
+};
+
+static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+ struct q6afe_port *port;
+
+ port = q6afe_port_get_from_id(dai->dev, dai->id);
+ if (IS_ERR(port)) {
+ dev_err(dai->dev, "Unable to get afe port\n");
+ return -EINVAL;
+ }
+ dai_data->port[dai->id] = port;
+
+ return 0;
+}
+
+static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev);
+
+ q6afe_port_put(dai_data->port[dai->id]);
+ dai_data->port[dai->id] = NULL;
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver q6afe_dais[] = {
+ {
+ .playback = {
+ .stream_name = "HDMI Playback",
+ .rates = SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_max = 192000,
+ .rate_min = 48000,
+ },
+ .ops = &q6hdmi_ops,
+ .id = HDMI_RX,
+ .name = "HDMI",
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .name = "SLIMBUS_0_RX",
+ .ops = &q6slim_ops,
+ .id = SLIMBUS_0_RX,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ .playback = {
+ .stream_name = "Slimbus Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ }, {
+ .playback = {
+ .stream_name = "Slimbus1 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_1_RX",
+ .ops = &q6slim_ops,
+ .id = SLIMBUS_1_RX,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Slimbus2 Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_2_RX",
+ .ops = &q6slim_ops,
+ .id = SLIMBUS_2_RX,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Slimbus3 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_3_RX",
+ .ops = &q6slim_ops,
+ .id = SLIMBUS_3_RX,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Slimbus4 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_4_RX",
+ .ops = &q6slim_ops,
+ .id = SLIMBUS_4_RX,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Slimbus5 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .name = "SLIMBUS_5_RX",
+ .ops = &q6slim_ops,
+ .id = SLIMBUS_5_RX,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Slimbus6 Playback",
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ },
+ .ops = &q6slim_ops,
+ .name = "SLIMBUS_6_RX",
+ .id = SLIMBUS_6_RX,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Primary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = PRIMARY_MI2S_RX,
+ .name = "PRI_MI2S_RX",
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .capture = {
+ .stream_name = "Primary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = PRIMARY_MI2S_TX,
+ .name = "PRI_MI2S_TX",
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Secondary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .name = "SEC_MI2S_RX",
+ .id = SECONDARY_MI2S_RX,
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .capture = {
+ .stream_name = "Secondary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = SECONDARY_MI2S_TX,
+ .name = "SEC_MI2S_TX",
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Tertiary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .name = "TERT_MI2S_RX",
+ .id = TERTIARY_MI2S_RX,
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .capture = {
+ .stream_name = "Tertiary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = TERTIARY_MI2S_TX,
+ .name = "TERT_MI2S_TX",
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .playback = {
+ .stream_name = "Quaternary MI2S Playback",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .name = "QUAT_MI2S_RX",
+ .id = QUATERNARY_MI2S_RX,
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ }, {
+ .capture = {
+ .stream_name = "Quaternary MI2S Capture",
+ .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ .id = QUATERNARY_MI2S_TX,
+ .name = "QUAT_MI2S_TX",
+ .ops = &q6i2s_ops,
+ .probe = msm_dai_q6_dai_probe,
+ .remove = msm_dai_q6_dai_remove,
+ },
+};
+
+static int q6afe_of_xlate_dai_name(struct snd_soc_component *component,
+ struct of_phandle_args *args,
+ const char **dai_name)
+{
+ int id = args->args[0];
+ int ret = -EINVAL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) {
+ if (q6afe_dais[i].id == id) {
+ *dai_name = q6afe_dais[i].name;
+ ret = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = {
+ SND_SOC_DAPM_AIF_OUT("HDMI_RX", "HDMI Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_RX", "Slimbus Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_RX", "Slimbus1 Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_2_RX", "Slimbus2 Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_3_RX", "Slimbus3 Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_RX", "Slimbus4 Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_5_RX", "Slimbus5 Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SLIMBUS_6_RX", "Slimbus6 Playback", 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("QUAT_MI2S_RX", "Quaternary MI2S Playback",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_IN("QUAT_MI2S_TX", "Quaternary MI2S Capture",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TERT_MI2S_RX", "Tertiary MI2S Playback",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_IN("TERT_MI2S_TX", "Tertiary MI2S Capture",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_MI2S_RX", "Secondary MI2S Playback",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SEC_MI2S_TX", "Secondary MI2S Capture",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SEC_MI2S_RX_SD1",
+ "Secondary MI2S Playback SD1",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("PRI_MI2S_RX", "Primary MI2S Playback",
+ 0, 0, 0, 0),
+ SND_SOC_DAPM_AIF_IN("PRI_MI2S_TX", "Primary MI2S Capture",
+ 0, 0, 0, 0),
+};
+
+static const struct snd_soc_component_driver q6afe_dai_component = {
+ .name = "q6afe-dai-component",
+ .dapm_widgets = q6afe_dai_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets),
+ .dapm_routes = q6afe_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes),
+ .of_xlate_dai_name = q6afe_of_xlate_dai_name,
+
+};
+
+static void of_q6afe_parse_dai_data(struct device *dev,
+ struct q6afe_dai_data *data)
+{
+ struct device_node *node;
+ int ret;
+
+ for_each_child_of_node(dev->of_node, node) {
+ unsigned int lines[Q6AFE_MAX_MI2S_LINES];
+ struct q6afe_dai_priv_data *priv;
+ int id, i, num_lines;
+
+ ret = of_property_read_u32(node, "reg", &id);
+ if (ret || id > AFE_PORT_MAX) {
+ dev_err(dev, "valid dai id not found:%d\n", ret);
+ continue;
+ }
+
+ switch (id) {
+ /* MI2S specific properties */
+ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
+ priv = &data->priv[id];
+ ret = of_property_read_variable_u32_array(node,
+ "qcom,sd-lines",
+ lines, 0,
+ Q6AFE_MAX_MI2S_LINES);
+ if (ret < 0)
+ num_lines = 0;
+ else
+ num_lines = ret;
+
+ priv->sd_line_mask = 0;
+
+ for (i = 0; i < num_lines; i++)
+ priv->sd_line_mask |= BIT(lines[i]);
+
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static int q6afe_dai_bind(struct device *dev, struct device *master, void *data)
+{
+ struct q6afe_dai_data *dai_data;
+
+ dai_data = kzalloc(sizeof(*dai_data), GFP_KERNEL);
+ if (!dai_data)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, dai_data);
+
+ of_q6afe_parse_dai_data(dev, dai_data);
+
+ return snd_soc_register_component(dev, &q6afe_dai_component,
+ q6afe_dais, ARRAY_SIZE(q6afe_dais));
+}
+
+static void q6afe_dai_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct q6afe_dai_data *dai_data = dev_get_drvdata(dev);
+
+ snd_soc_unregister_component(dev);
+ kfree(dai_data);
+}
+
+static const struct component_ops q6afe_dai_comp_ops = {
+ .bind = q6afe_dai_bind,
+ .unbind = q6afe_dai_unbind,
+};
+
+static int q6afe_dai_dev_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &q6afe_dai_comp_ops);
+}
+
+static int q6afe_dai_dev_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &q6afe_dai_comp_ops);
+ return 0;
+}
+
+static struct platform_driver q6afe_dai_platform_driver = {
+ .driver = {
+ .name = "q6afe-dai",
+ },
+ .probe = q6afe_dai_dev_probe,
+ .remove = q6afe_dai_dev_remove,
+};
+module_platform_driver(q6afe_dai_platform_driver);
+
+MODULE_DESCRIPTION("Q6 Audio Fronend dai driver");
+MODULE_LICENSE("GPL v2");
--
2.16.2
^ 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