From: Al Stone ahs3@redhat.com
This patch set is in the acpi-topic-seattle branch of acpi.git.
The basic idea is that these patches are an example of how to implement ACPI for the AMD Seattle platform. Many of them have been borrowed from the Fedora arm64 kernel tree which is at:
git://git.fedorahosted.org/git/kernel-arm64.git
Additional patches come from Linaro and LKMLA. With the exception of the PCI patches, or others mentioned below, the intent is that the original authors of these patches will submit them upstream. As always, they rely on the ACPI core patches already being in place.
Patches 0001-006 are some cleanup and nice-to-have patches that let ACPI, UEFI and the Seattle platform work better together.
Patch 0007 is Graeme's SBSA console patch, which ultimately needs to be replaced with a proper pl011 console driver (still being worked on by ARM, I believe).
Patch 0008 introduces ACPI support for the Seattle's AHCI code.
Patch 0009 fixes a compilation error in the PNP code.
Patch 0010 introduces some skeleton PCI functions to be replaced later.
Patch 0011 adds support for the secondary CPU parking protocol.
Patches 0011 and 0012 add general support for the AMD XGBE 10Gbe network interface.
Patches 0014-0025 are from the device properties previously posted on the ACPI mailing list; these may need updates if the final versions put into the Linux tree differ. NB: 0017-0025 are not strictly required for Seattle, but have been included so that the entire patch set posted on LKMLA is included.
Patch 0026 adds ACPI support to the AMD XGBE 10Gbe interface. Yes, just one patch.
Patch 0027 adds code to make it easy to set up DMA coherency on a device using ACPI.
Patch 0028 may or may not be useful for upstream; it corrects a problem with DMA operations when ACPI and PCI are being used but there may be better ways to handle it.
Patch 0029 removes at DT dependency in the arch timer that can cause a hang when using ACPI.
Patches 0030-0034 do basic PCI setup based on the contents of the MCFG table in ACPI (for PCIe); these are also a first generation and definitely need refinement and refactoring -- in some cases, there is code duplicated from x86 or ia64, for example.
Patches 0035-0037 fix compiler warnings that annoyed me.
Patch 0038 allows one to skip adding a console= parameter on the command line.
These patches were tested on an AMD Seattle platform, using the 71C version of their firmware (future versions will have needed fixes for PCI windows and some other odds and ends). The kernel command line was:
BOOT_IMAGE=/Image.acpi.git root=UUID=d5f87d27-cd7d-4ac5-9e16-dfa850ae32ad \ ro console=ttySBSA0,115200 earlycon=sbsauart,0xe1010000 vconsole.keymap=us \ crashkernel=auto vconsole.font=latarcyrheb-sun16 acpi=force \ acpi_force_table_verification uefi_debug
The console, SATA, and NIC all work as expected. PCIe works (PCI will need a new DSDT) with a SATA card I had laying around. It is known to work with a couple of other PCIe devices, but that's all I tried.
Things to do: -- Get PCI re-done; there are way too many x86-isms in the code, and some serious refactoring and cleanup is needed; the ia64 seems to have taken some good steps in this direction.
-- MSI(-X) support: a DT version has been posted, and some ACPI work is under way.
-- KVM support: ACPI support patches are available, but were not included here since I have not had time to test them.
-- Subject the code to the full FWTS; this is pending available LAVA machines and some patches to FWTS, but the CI loop is set up.
-- Plus whatever ACPI spec changes or additions arise....
Tested-by: Al Stone al.stone@linaro.org
Aaron Lu (2): input: gpio_keys_polled - Add support for GPIO descriptors input: gpio_keys_polled - Make use of device property API
Al Stone (5): Fix arm64 compilation error in PNP code arm64/pci/acpi: initial support for ACPI probing of PCI drivers/base: correct function prototype to remove compiler warning drivers/of: fix new device property function that could return a bad value AMD / XGBE : remove duplicate function definition
Graeme Gregory (2): acpi: add arm to the platforms that use ioremap tty: SBSA compatible UART
Hanjun Guo (1): ARM64 / ACPI: Introduce some PCI functions when PCI is enabled
Mark Salter (11): arm64: use EFI as last resort for reboot and poweroff acpi: fix acpi_os_ioremap for arm64 arm64: add parking protocol support acpi: add utility to test for device dma coherency arm64: [NOT FOR UPSTREAM] fix dma_ops for ACPI and PCI devices clocksource: arm_arch_timer: fix system hang arm64/pci: replace weak raw pci ops with settable ops arm64/acpi/pci: add support for parsing MCFG table arm64/acpi/pci: provide hook for MCFG fixups iommu/arm-smmu: fix NULL dereference with ACPI PCI devices arm64: avoid need for console= to enable serial console
Mika Westerberg (7): ACPI: Add support for device specific properties ACPI: Allow drivers to match using Device Tree compatible property misc: at25: Make use of device property API gpio / ACPI: Add support for _DSD device properties gpio: sch: Consolidate core and resume banks leds: leds-gpio: Add support for GPIO descriptors gpio: Support for unified device properties interface
Rafael J. Wysocki (3): Driver core: Unified device properties interface for platform firmware Driver core: Unified interface for firmware node properties leds: leds-gpio: Make use of device property API
Russell King (2): ARM: Blacklist GCC 4.8.0 to GCC 4.8.2 - PR58854 ARM: fix some printk formats
Suravee Suthikulpanit (1): ata: ahci_platform: Add ACPI support for AMD Seattle SATA controller
Tom Lendacky (3): drivers: net: AMD Seattle XGBE 10GbE support for A0 silicon drivers: net: AMD Seattle XGBE PHY support for A0 silicon amd-xgbe: AMD 10GbE driver APCI support for A0
Will Deacon (1): zap_pte_range: update addr when forcing flush after TLB batching faiure
Documentation/acpi/gpio-properties.txt | 52 ++ arch/arm/kernel/asm-offsets.c | 12 +- arch/arm/mm/init.c | 8 +- arch/arm64/Kconfig | 6 + arch/arm64/Makefile | 1 + arch/arm64/include/asm/acpi.h | 3 + arch/arm64/include/asm/pci.h | 57 +++ arch/arm64/include/asm/smp.h | 5 + arch/arm64/kernel/Makefile | 3 +- arch/arm64/kernel/acpi.c | 54 +- arch/arm64/kernel/cpu_ops.c | 4 + arch/arm64/kernel/efi.c | 11 + arch/arm64/kernel/pci.c | 108 ++-- arch/arm64/kernel/process.c | 6 + arch/arm64/kernel/setup.c | 22 + arch/arm64/kernel/smp_parking_protocol.c | 107 ++++ arch/arm64/mm/dma-mapping.c | 103 ++++ arch/arm64/pci/Makefile | 2 + arch/arm64/pci/mmconfig.c | 431 ++++++++++++++++ arch/arm64/pci/pci.c | 375 ++++++++++++++ drivers/acpi/Makefile | 1 + drivers/acpi/internal.h | 6 + drivers/acpi/osl.c | 6 +- drivers/acpi/property.c | 567 +++++++++++++++++++++ drivers/acpi/scan.c | 120 ++++- drivers/acpi/utils.c | 26 + drivers/ata/Kconfig | 2 +- drivers/ata/ahci_platform.c | 13 + drivers/base/Makefile | 2 +- drivers/base/dma-coherent.c | 6 +- drivers/base/property.c | 625 +++++++++++++++++++++++ drivers/clocksource/arm_arch_timer.c | 11 +- drivers/gpio/devres.c | 36 ++ drivers/gpio/gpio-sch.c | 293 +++++------ drivers/gpio/gpiolib-acpi.c | 78 ++- drivers/gpio/gpiolib.c | 86 +++- drivers/gpio/gpiolib.h | 7 +- drivers/input/keyboard/gpio_keys_polled.c | 112 ++--- drivers/iommu/arm-smmu.c | 8 +- drivers/irqchip/irq-gic-v3.c | 10 + drivers/irqchip/irq-gic.c | 10 + drivers/leds/leds-gpio.c | 186 +++---- drivers/misc/eeprom/at25.c | 34 +- drivers/net/ethernet/amd/Kconfig | 2 +- drivers/net/ethernet/amd/xgbe/xgbe-dev.c | 16 +- drivers/net/ethernet/amd/xgbe/xgbe-drv.c | 3 + drivers/net/ethernet/amd/xgbe/xgbe-main.c | 289 ++++++++--- drivers/net/ethernet/amd/xgbe/xgbe-mdio.c | 20 +- drivers/net/ethernet/amd/xgbe/xgbe-ptp.c | 4 +- drivers/net/ethernet/amd/xgbe/xgbe.h | 13 + drivers/net/phy/Kconfig | 2 +- drivers/net/phy/amd-xgbe-phy.c | 791 ++++++++++++++++-------------- drivers/of/base.c | 103 +++- drivers/pnp/resource.c | 2 + drivers/tty/Kconfig | 6 + drivers/tty/Makefile | 1 + drivers/tty/sbsauart.c | 355 ++++++++++++++ include/acpi/acpi_bus.h | 27 + include/acpi/acpi_io.h | 6 + include/asm-generic/vmlinux.lds.h | 7 + include/linux/acpi.h | 106 +++- include/linux/gpio/consumer.h | 8 + include/linux/gpio_keys.h | 3 + include/linux/irqchip/arm-gic.h | 2 + include/linux/leds.h | 1 + include/linux/of.h | 44 ++ include/linux/property.h | 107 ++++ mm/memory.c | 1 + 68 files changed, 4611 insertions(+), 923 deletions(-) create mode 100644 Documentation/acpi/gpio-properties.txt create mode 100644 arch/arm64/kernel/smp_parking_protocol.c create mode 100644 arch/arm64/pci/Makefile create mode 100644 arch/arm64/pci/mmconfig.c create mode 100644 arch/arm64/pci/pci.c create mode 100644 drivers/acpi/property.c create mode 100644 drivers/base/property.c create mode 100644 drivers/tty/sbsauart.c create mode 100644 include/linux/property.h
From: Russell King rmk+kernel@arm.linux.org.uk
These stock GCC versions miscompile the kernel by incorrectly optimising the function epilogue code - by first increasing the stack pointer, and then loading entries from below the stack. This means that an opportune interrupt or exception will corrupt the current function's saved state, which may result in the parent function seeing different register values.
As this bug has been known to result in corrupted filesystems, and these buggy compiler versions seem to be frequently used, we have little option but to blacklist these compiler versions.
Distributions may have fixed PR58854, but as their compilers are totally indistinguishable from the buggy versions, it is unfortunate that this also results in those also being blacklisted. Given the filesystem corruption potential of the original, this is the lesser evil. People who want to build with their fixed compiler versions will need to adjust the kernel source. (Distros need to think about the implications of fixing such a compiler bug, and consider how to ensure that their fixed compiler versions can be detected if they wish to avoid this.)
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk --- arch/arm/kernel/asm-offsets.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/arm/kernel/asm-offsets.c b/arch/arm/kernel/asm-offsets.c index 713e807..2d2d608 100644 --- a/arch/arm/kernel/asm-offsets.c +++ b/arch/arm/kernel/asm-offsets.c @@ -10,6 +10,7 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#include <linux/compiler.h> #include <linux/sched.h> #include <linux/mm.h> #include <linux/dma-mapping.h> @@ -39,10 +40,19 @@ * GCC 3.2.x: miscompiles NEW_AUX_ENT in fs/binfmt_elf.c * (http://gcc.gnu.org/PR8896) and incorrect structure * initialisation in fs/jffs2/erase.c + * GCC 4.8.0-4.8.2: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58854 + * miscompiles find_get_entry(), and can result in EXT3 and EXT4 + * filesystem corruption (possibly other FS too). */ +#ifdef __GNUC__ #if (__GNUC__ == 3 && __GNUC_MINOR__ < 3) #error Your compiler is too buggy; it is known to miscompile kernels. -#error Known good compilers: 3.3 +#error Known good compilers: 3.3, 4.x +#endif +#if GCC_VERSION >= 40800 && GCC_VERSION < 40803 +#error Your compiler is too buggy; it is known to miscompile kernels +#error and result in filesystem corruption and oopses. +#endif #endif
int main(void)
From: Russell King rmk+kernel@arm.linux.org.uk
GCC 4.9 complains if we take the difference of two pointers, and it's printed with "%d". Fix this by using the proper flag - "t" for ptrdiff_t.
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk --- arch/arm/mm/init.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c index 92bba32..9481f85 100644 --- a/arch/arm/mm/init.c +++ b/arch/arm/mm/init.c @@ -559,10 +559,10 @@ void __init mem_init(void) #ifdef CONFIG_MODULES " modules : 0x%08lx - 0x%08lx (%4ld MB)\n" #endif - " .text : 0x%p" " - 0x%p" " (%4d kB)\n" - " .init : 0x%p" " - 0x%p" " (%4d kB)\n" - " .data : 0x%p" " - 0x%p" " (%4d kB)\n" - " .bss : 0x%p" " - 0x%p" " (%4d kB)\n", + " .text : 0x%p" " - 0x%p" " (%4td kB)\n" + " .init : 0x%p" " - 0x%p" " (%4td kB)\n" + " .data : 0x%p" " - 0x%p" " (%4td kB)\n" + " .bss : 0x%p" " - 0x%p" " (%4td kB)\n",
MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) + (PAGE_SIZE)),
From: Will Deacon will.deacon@arm.com
When unmapping a range of pages in zap_pte_range, the page being unmapped is added to an mmu_gather_batch structure for asynchronous freeing. If we run out of space in the batch structure before the range has been completely unmapped, then we break out of the loop, force a TLB flush and free the pages that we have batched so far. If there are further pages to unmap, then we resume the loop where we left off.
Unfortunately, we forget to update addr when we break out of the loop, which causes us to truncate the range being invalidated as the end address is exclusive. When we re-enter the loop at the same address, the page has already been freed and the pte_present test will fail, meaning that we do not reconsider the address for invalidation.
This patch fixes the problem by incrementing addr by the PAGE_SIZE before breaking out of the loop on batch failure.
Signed-off-by: Will Deacon will.deacon@arm.com Cc: stable@vger.kernel.org Signed-off-by: Linus Torvalds torvalds@linux-foundation.org --- mm/memory.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/mm/memory.c b/mm/memory.c index 1cc6bfb..3e50383 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1147,6 +1147,7 @@ again: print_bad_pte(vma, addr, ptent, page); if (unlikely(!__tlb_remove_page(tlb, page))) { force_flush = 1; + addr += PAGE_SIZE; break; } continue;
From: Mark Salter msalter@redhat.com
Wire in support for EFI reboot and poweroff functions. We use these only if no other mechanism has been registered with arm_pm_reboot and/or pm_power_off respectively.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/kernel/efi.c | 11 +++++++++++ arch/arm64/kernel/process.c | 6 ++++++ 2 files changed, 17 insertions(+)
diff --git a/arch/arm64/kernel/efi.c b/arch/arm64/kernel/efi.c index 95c49eb..a73daae 100644 --- a/arch/arm64/kernel/efi.c +++ b/arch/arm64/kernel/efi.c @@ -471,3 +471,14 @@ err_unmap: return -1; } early_initcall(arm64_enter_virtual_mode); + +/* + * If nothing else is handling pm_power_off, use EFI + * + * This is called from a late_initcall after other mechanisms + * have had a chance to register a handler. + */ +bool efi_poweroff_required(void) +{ + return pm_power_off == NULL; +} diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index fde9923..466f8cd 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -43,6 +43,7 @@ #include <linux/hw_breakpoint.h> #include <linux/personality.h> #include <linux/notifier.h> +#include <linux/efi.h>
#include <asm/compat.h> #include <asm/cacheflush.h> @@ -157,6 +158,11 @@ void machine_restart(char *cmd) do_kernel_restart(cmd);
/* + * If all else fails, try EFI + */ + efi_reboot(reboot_mode, cmd); + + /* * Whoops - the architecture was unable to reboot. */ printk("Reboot failed -- System halted\n");
On Wednesday 05 November 2014 20:32:23 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Wire in support for EFI reboot and poweroff functions. We use these only if no other mechanism has been registered with arm_pm_reboot and/or pm_power_off respectively.
Signed-off-by: Mark Salter msalter@redhat.com
We recently added register_restart_handler infrastructure, which should be able to handle this more nicely. Can you have a look?
Arnd
From: Graeme Gregory graeme.gregory@linaro.org
Now with the base changes to the arm memory mapping it is safe to convert to using ioremap to map in the tables.
Signed-off-by: Al Stone al.stone@linaro.org Signed-off-by: Graeme Gregory graeme.gregory@linaro.org --- drivers/acpi/osl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 9964f70..5c480d5 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -336,11 +336,11 @@ acpi_map_lookup_virt(void __iomem *virt, acpi_size size) return NULL; }
-#ifndef CONFIG_IA64 -#define should_use_kmap(pfn) page_is_ram(pfn) -#else +#if defined(CONFIG_IA64) || defined(CONFIG_ARM) || defined(CONFIG_ARM64) /* ioremap will take care of cache attributes */ #define should_use_kmap(pfn) 0 +#else +#define should_use_kmap(pfn) page_is_ram(pfn) #endif
static void __iomem *acpi_map(acpi_physical_address pg_off, unsigned long pg_sz)
From: Mark Salter msalter@redhat.com
The acpi_os_ioremap() function may be used to map normal RAM or IO regions. The current implementation simply uses ioremap_cache(). This will work for some architectures, but arm64 ioremap_cache() cannot be used to map IO regions which don't support caching. So for arm64, use ioremap() for non-RAM regions.
Signed-off-by: Mark Salter msalter@redhat.com --- include/acpi/acpi_io.h | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/include/acpi/acpi_io.h b/include/acpi/acpi_io.h index 444671e..9d573db 100644 --- a/include/acpi/acpi_io.h +++ b/include/acpi/acpi_io.h @@ -1,11 +1,17 @@ #ifndef _ACPI_IO_H_ #define _ACPI_IO_H_
+#include <linux/mm.h> #include <linux/io.h>
static inline void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size) { +#ifdef CONFIG_ARM64 + if (!page_is_ram(phys >> PAGE_SHIFT)) + return ioremap(phys, size); +#endif + return ioremap_cache(phys, size); }
On Wednesday 05 November 2014 20:32:25 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
The acpi_os_ioremap() function may be used to map normal RAM or IO regions. The current implementation simply uses ioremap_cache(). This will work for some architectures, but arm64 ioremap_cache() cannot be used to map IO regions which don't support caching. So for arm64, use ioremap() for non-RAM regions.
Signed-off-by: Mark Salter msalter@redhat.com
I would probably turn this logic around: x86 is the odd one out here (using MTRR), arm64 is the common case in which you are not allowed to use ioremap_cache() for MMIO registers.
Arnd
From: Graeme Gregory graeme.gregory@linaro.org
This is a subset of pl011 UART which does not supprt DMA or baud rate changing. It does, however, provide earlycon support (i.e., using "earlycon=ttySBSA" on the kernel command line).
It is specified in the Server Base System Architecture document from ARM.
Signed-off-by: Graeme Gregory graeme.gregory@linaro.org --- drivers/tty/Kconfig | 6 + drivers/tty/Makefile | 1 + drivers/tty/sbsauart.c | 355 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 drivers/tty/sbsauart.c
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index b24aa01..50fe279 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -419,4 +419,10 @@ config DA_CONSOLE help This enables a console on a Dash channel.
+config SBSAUART_TTY + tristate "SBSA UART TTY Driver" + help + Console and system TTY driver for the SBSA UART which is defined + in the Server Base System Architecure document for ARM64 servers. + endif # TTY diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 58ad1c0..c3211c0 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK) += synclink.o obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_DA_TTY) += metag_da.o +obj-$(CONFIG_SBSAUART_TTY) += sbsauart.o
obj-y += ipwireless/ diff --git a/drivers/tty/sbsauart.c b/drivers/tty/sbsauart.c new file mode 100644 index 0000000..402f168 --- /dev/null +++ b/drivers/tty/sbsauart.c @@ -0,0 +1,355 @@ +/* + * SBSA (Server Base System Architecture) Compatible UART driver + * + * Copyright (C) 2014 Linaro Ltd + * + * Author: Graeme Gregory graeme.gregory@linaro.org + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/acpi.h> +#include <linux/amba/serial.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/serial_core.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> + +struct sbsa_tty { + struct tty_port port; + spinlock_t lock; + void __iomem *base; + u32 irq; + int opencount; + struct console console; +}; + +static struct tty_driver *sbsa_tty_driver; +static struct sbsa_tty *sbsa_tty; + +#define SBSAUART_CHAR_MASK 0xFF + +static void sbsa_raw_putc(struct uart_port *port, int c) +{ + while (readw(port->membase + UART01x_FR) & UART01x_FR_TXFF) + ; + writew(c & 0xFF, port->membase + UART01x_DR); +} + +static void sbsa_uart_early_write(struct console *con, const char *buf, + unsigned count) +{ + struct earlycon_device *dev = con->data; + + uart_console_write(&dev->port, buf, count, sbsa_raw_putc); +} + +static int __init sbsa_uart_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + if (!device->port.membase) + return -ENODEV; + + device->con->write = sbsa_uart_early_write; + return 0; +} +EARLYCON_DECLARE(sbsauart, sbsa_uart_early_console_setup); + +static void sbsa_tty_do_write(const char *buf, unsigned count) +{ + unsigned long irq_flags; + struct sbsa_tty *qtty = sbsa_tty; + void __iomem *base = qtty->base; + unsigned n; + + spin_lock_irqsave(&qtty->lock, irq_flags); + for (n = 0; n < count; n++) { + while (readw(base + UART01x_FR) & UART01x_FR_TXFF) + ; + writew(buf[n], base + UART01x_DR); + } + spin_unlock_irqrestore(&qtty->lock, irq_flags); +} + +static void sbsauart_fifo_to_tty(struct sbsa_tty *qtty) +{ + void __iomem *base = qtty->base; + unsigned int flag, max_count = 32; + u16 status, ch; + + while (max_count--) { + status = readw(base + UART01x_FR); + if (status & UART01x_FR_RXFE) + break; + + /* Take chars from the FIFO and update status */ + ch = readw(base + UART01x_DR); + flag = TTY_NORMAL; + + if (ch & UART011_DR_BE) + flag = TTY_BREAK; + else if (ch & UART011_DR_PE) + flag = TTY_PARITY; + else if (ch & UART011_DR_FE) + flag = TTY_FRAME; + else if (ch & UART011_DR_OE) + flag = TTY_OVERRUN; + + ch &= SBSAUART_CHAR_MASK; + + tty_insert_flip_char(&qtty->port, ch, flag); + } + + tty_schedule_flip(&qtty->port); + + /* Clear the RX IRQ */ + writew(UART011_RXIC | UART011_RXIC, base + UART011_ICR); +} + +static irqreturn_t sbsa_tty_interrupt(int irq, void *dev_id) +{ + struct sbsa_tty *qtty = sbsa_tty; + unsigned long irq_flags; + + spin_lock_irqsave(&qtty->lock, irq_flags); + sbsauart_fifo_to_tty(qtty); + spin_unlock_irqrestore(&qtty->lock, irq_flags); + + return IRQ_HANDLED; +} + +static int sbsa_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct sbsa_tty *qtty = sbsa_tty; + + return tty_port_open(&qtty->port, tty, filp); +} + +static void sbsa_tty_close(struct tty_struct *tty, struct file *filp) +{ + tty_port_close(tty->port, tty, filp); +} + +static void sbsa_tty_hangup(struct tty_struct *tty) +{ + tty_port_hangup(tty->port); +} + +static int sbsa_tty_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + sbsa_tty_do_write(buf, count); + return count; +} + +static int sbsa_tty_write_room(struct tty_struct *tty) +{ + return 32; +} + +static void sbsa_tty_console_write(struct console *co, const char *b, + unsigned count) +{ + sbsa_tty_do_write(b, count); + + if (b[count - 1] == '\n') + sbsa_tty_do_write("\r", 1); +} + +static struct tty_driver *sbsa_tty_console_device(struct console *c, + int *index) +{ + *index = c->index; + return sbsa_tty_driver; +} + +static int sbsa_tty_console_setup(struct console *co, char *options) +{ + if ((unsigned)co->index > 0) + return -ENODEV; + if (sbsa_tty->base == NULL) + return -ENODEV; + return 0; +} + +static struct tty_port_operations sbsa_port_ops = { +}; + +static const struct tty_operations sbsa_tty_ops = { + .open = sbsa_tty_open, + .close = sbsa_tty_close, + .hangup = sbsa_tty_hangup, + .write = sbsa_tty_write, + .write_room = sbsa_tty_write_room, +}; + +static int sbsa_tty_create_driver(void) +{ + int ret; + struct tty_driver *tty; + + sbsa_tty = kzalloc(sizeof(*sbsa_tty), GFP_KERNEL); + if (sbsa_tty == NULL) { + ret = -ENOMEM; + goto err_alloc_sbsa_tty_failed; + } + tty = alloc_tty_driver(1); + if (tty == NULL) { + ret = -ENOMEM; + goto err_alloc_tty_driver_failed; + } + tty->driver_name = "sbsauart"; + tty->name = "ttySBSA"; + tty->type = TTY_DRIVER_TYPE_SERIAL; + tty->subtype = SERIAL_TYPE_NORMAL; + tty->init_termios = tty_std_termios; + tty->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV; + tty_set_operations(tty, &sbsa_tty_ops); + ret = tty_register_driver(tty); + if (ret) + goto err_tty_register_driver_failed; + + sbsa_tty_driver = tty; + return 0; + +err_tty_register_driver_failed: + put_tty_driver(tty); +err_alloc_tty_driver_failed: + kfree(sbsa_tty); + sbsa_tty = NULL; +err_alloc_sbsa_tty_failed: + return ret; +} + +static void sbsa_tty_delete_driver(void) +{ + tty_unregister_driver(sbsa_tty_driver); + put_tty_driver(sbsa_tty_driver); + sbsa_tty_driver = NULL; + kfree(sbsa_tty); + sbsa_tty = NULL; +} + +static int sbsa_tty_probe(struct platform_device *pdev) +{ + struct sbsa_tty *qtty; + int ret = -EINVAL; + int i; + struct resource *r; + struct device *ttydev; + void __iomem *base; + u32 irq; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) + return -EINVAL; + + base = ioremap(r->start, r->end - r->start); + if (base == NULL) + pr_err("sbsa_tty: unable to remap base\n"); + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r == NULL) + goto err_unmap; + + irq = r->start; + + if (pdev->id > 0) + goto err_unmap; + + ret = sbsa_tty_create_driver(); + if (ret) + goto err_unmap; + + qtty = sbsa_tty; + spin_lock_init(&qtty->lock); + tty_port_init(&qtty->port); + qtty->port.ops = &sbsa_port_ops; + qtty->base = base; + qtty->irq = irq; + + /* Clear and Mask all IRQs */ + writew(0, base + UART011_IMSC); + writew(0xFFFF, base + UART011_ICR); + + ret = request_irq(irq, sbsa_tty_interrupt, IRQF_SHARED, + "sbsa_tty", pdev); + if (ret) + goto err_request_irq_failed; + + /* Unmask the RX IRQ */ + writew(UART011_RXIM | UART011_RTIM, base + UART011_IMSC); + + ttydev = tty_port_register_device(&qtty->port, sbsa_tty_driver, + 0, &pdev->dev); + if (IS_ERR(ttydev)) { + ret = PTR_ERR(ttydev); + goto err_tty_register_device_failed; + } + + strcpy(qtty->console.name, "ttySBSA"); + qtty->console.write = sbsa_tty_console_write; + qtty->console.device = sbsa_tty_console_device; + qtty->console.setup = sbsa_tty_console_setup; + qtty->console.flags = CON_PRINTBUFFER; + qtty->console.index = pdev->id; + register_console(&qtty->console); + + return 0; + + tty_unregister_device(sbsa_tty_driver, i); +err_tty_register_device_failed: + free_irq(irq, pdev); +err_request_irq_failed: + sbsa_tty_delete_driver(); +err_unmap: + iounmap(base); + return ret; +} + +static int sbsa_tty_remove(struct platform_device *pdev) +{ + struct sbsa_tty *qtty; + + qtty = sbsa_tty; + unregister_console(&qtty->console); + tty_unregister_device(sbsa_tty_driver, pdev->id); + iounmap(qtty->base); + qtty->base = 0; + free_irq(qtty->irq, pdev); + sbsa_tty_delete_driver(); + return 0; +} + +static const struct acpi_device_id sbsa_acpi_match[] = { + { "ARMH0011", 0 }, + { } +}; + +static struct platform_driver sbsa_tty_platform_driver = { + .probe = sbsa_tty_probe, + .remove = sbsa_tty_remove, + .driver = { + .name = "sbsa_tty", + .acpi_match_table = ACPI_PTR(sbsa_acpi_match), + } +}; + +module_platform_driver(sbsa_tty_platform_driver); + +MODULE_LICENSE("GPL v2");
On Wednesday 05 November 2014 20:32:26 al.stone@linaro.org wrote:
From: Graeme Gregory graeme.gregory@linaro.org
This is a subset of pl011 UART which does not supprt DMA or baud rate changing. It does, however, provide earlycon support (i.e., using "earlycon=ttySBSA" on the kernel command line).
It is specified in the Server Base System Architecture document from ARM.
Signed-off-by: Graeme Gregory graeme.gregory@linaro.org
This one has been going back and forth so many times, I no longer know what the latest result is. Do we expect to have a separate driver for this or use a modified pl011 driver now?
If we keep this driver, I would ask you to add a DT binding that describes the register and irq settings and a compatible string as well. The driver is written to be completely portable and we should be able to use the same code for both ACPI and DT on hardware that doesn't actually implement a full pl011 but supports this driver.
Arnd
On 6 November 2014 10:03, Arnd Bergmann arnd@arndb.de wrote:
On Wednesday 05 November 2014 20:32:26 al.stone@linaro.org wrote:
From: Graeme Gregory graeme.gregory@linaro.org
This is a subset of pl011 UART which does not supprt DMA or baud rate changing. It does, however, provide earlycon support (i.e., using "earlycon=ttySBSA" on the kernel command line).
It is specified in the Server Base System Architecture document from ARM.
Signed-off-by: Graeme Gregory graeme.gregory@linaro.org
This one has been going back and forth so many times, I no longer know what the latest result is. Do we expect to have a separate driver for this or use a modified pl011 driver now?
If we keep this driver, I would ask you to add a DT binding that describes the register and irq settings and a compatible string as well. The driver is written to be completely portable and we should be able to use the same code for both ACPI and DT on hardware that doesn't actually implement a full pl011 but supports this driver.
This driver was pretty soundly savaged by Alan Cox
https://lkml.org/lkml/2014/9/17/161
Which is not really surprising as I wrote it with a fever.
Motion on the ARM modifications of pl011 driver seem to have stopped dead!
Graeme
From: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
This patch adds ACPI support for non-PCI SATA contoller in ahci_platform driver. It adds ACPI matching table in ahci_platform to support AMD Seattle SATA controller with following ASL structure in DSDT:
Device (SATA0) { Name(_HID, "AMDI0600") // Seattle AHSATA Name (_CCA, 1) // Cache-coherent controller Name (_CRS, ResourceTemplate () { Memory32Fixed (ReadWrite, 0xE0300000, 0x00010000) Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,,,) { 387 } }) }
Since ATA driver should not require PCI support for ATA_ACPI, this patch also removes dependency in the driver/ata/Kconfig.
Signed-off-by: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com --- drivers/ata/Kconfig | 2 +- drivers/ata/ahci_platform.c | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index cd4cccb..edb00c6 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -48,7 +48,7 @@ config ATA_VERBOSE_ERROR
config ATA_ACPI bool "ATA ACPI Support" - depends on ACPI && PCI + depends on ACPI default y help This option adds support for ATA-related ACPI objects. diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c index 06f1d59..df2ea85 100644 --- a/drivers/ata/ahci_platform.c +++ b/drivers/ata/ahci_platform.c @@ -20,6 +20,9 @@ #include <linux/platform_device.h> #include <linux/libata.h> #include <linux/ahci_platform.h> +#ifdef CONFIG_ATA_ACPI +#include <linux/acpi.h> +#endif #include "ahci.h"
static const struct ata_port_info ahci_port_info = { @@ -71,6 +74,13 @@ static const struct of_device_id ahci_of_match[] = { }; MODULE_DEVICE_TABLE(of, ahci_of_match);
+#ifdef CONFIG_ATA_ACPI +static const struct acpi_device_id ahci_acpi_match[] = { + { "AMDI0600", 0 }, /* AMD Seattle AHCI */ + { }, +}; +#endif + static struct platform_driver ahci_driver = { .probe = ahci_probe, .remove = ata_platform_remove_one, @@ -78,6 +88,9 @@ static struct platform_driver ahci_driver = { .name = "ahci", .owner = THIS_MODULE, .of_match_table = ahci_of_match, +#ifdef CONFIG_ATA_ACPI + .acpi_match_table = ACPI_PTR(ahci_acpi_match), +#endif .pm = &ahci_pm_ops, }, };
On Wednesday 05 November 2014 20:32:27 al.stone@linaro.org wrote:
From: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
This patch adds ACPI support for non-PCI SATA contoller in ahci_platform driver. It adds ACPI matching table in ahci_platform to support AMD Seattle SATA controller with following ASL structure in DSDT:
Device (SATA0) { Name(_HID, "AMDI0600") // Seattle AHSATA Name (_CCA, 1) // Cache-coherent controller Name (_CRS, ResourceTemplate () { Memory32Fixed (ReadWrite, 0xE0300000, 0x00010000) Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,,,) { 387 } }) }
Since ATA driver should not require PCI support for ATA_ACPI, this patch also removes dependency in the driver/ata/Kconfig.
Signed-off-by: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
Question: is this DSDT structure specified somewhere? The AHCI specification requires a PCI bus, but I expect that other SoCs will do the same thing as Seattle, and it would be a shame if they came up with a different representation.
Arnd
On 11/6/14 04:06, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:27 al.stone@linaro.org wrote:
From: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
This patch adds ACPI support for non-PCI SATA contoller in ahci_platform driver. It adds ACPI matching table in ahci_platform to support AMD Seattle SATA controller with following ASL structure in DSDT:
Device (SATA0) { Name(_HID, "AMDI0600") // Seattle AHSATA Name (_CCA, 1) // Cache-coherent controller Name (_CRS, ResourceTemplate () { Memory32Fixed (ReadWrite, 0xE0300000, 0x00010000) Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,,,) { 387 } }) }
Since ATA driver should not require PCI support for ATA_ACPI, this patch also removes dependency in the driver/ata/Kconfig.
Signed-off-by: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
Question: is this DSDT structure specified somewhere? The AHCI specification requires a PCI bus, but I expect that other SoCs will do the same thing as Seattle, and it would be a shame if they came up with a different representation.
Arnd
Actually, this patch might be a bit old now. As suggested earlier on the ML (`https://lkml.org/lkml/2014/10/6/343), we have thought about reusing the PCI class code (_CLS) instead as following.
Device (SATA0) // AHCI { Name(_HID, "AMDI0600") // Seattle AHCI/SATA Name (_CCA, 1) // Cache-coherent controller Name (_CLS, Package (3) { 0x01, // Base Class: Mass Storage 0x06, // Sub-Class: Serial ATA 0x01, // Interface: AHCI }) .......
So, this patch should now be deprecated and not to be going upstream.
Thanks, Suravee
On Thursday 06 November 2014 06:52:59 Suravee Suthikulpanit wrote:
On 11/6/14 04:06, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:27 al.stone@linaro.org wrote:
From: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
This patch adds ACPI support for non-PCI SATA contoller in ahci_platform driver. It adds ACPI matching table in ahci_platform to support AMD Seattle SATA controller with following ASL structure in DSDT:
Device (SATA0) { Name(_HID, "AMDI0600") // Seattle AHSATA Name (_CCA, 1) // Cache-coherent controller Name (_CRS, ResourceTemplate () { Memory32Fixed (ReadWrite, 0xE0300000, 0x00010000) Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,,,) { 387 } }) }
Since ATA driver should not require PCI support for ATA_ACPI, this patch also removes dependency in the driver/ata/Kconfig.
Signed-off-by: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
Question: is this DSDT structure specified somewhere? The AHCI specification requires a PCI bus, but I expect that other SoCs will do the same thing as Seattle, and it would be a shame if they came up with a different representation.
Arnd
Actually, this patch might be a bit old now. As suggested earlier on the ML (`https://lkml.org/lkml/2014/10/6/343), we have thought about reusing the PCI class code (_CLS) instead as following.
Device (SATA0) // AHCI { Name(_HID, "AMDI0600") // Seattle AHCI/SATA Name (_CCA, 1) // Cache-coherent controller Name (_CLS, Package (3) { 0x01, // Base Class: Mass Storage 0x06, // Sub-Class: Serial ATA 0x01, // Interface: AHCI }) .......
So, this patch should now be deprecated and not to be going upstream.
Ok, but is the new way of doing this standardized anywhere, or is it just better than the original approach?
Arnd
On 11/6/14 07:28, Arnd Bergmann wrote:
On Thursday 06 November 2014 06:52:59 Suravee Suthikulpanit wrote:
On 11/6/14 04:06, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:27 al.stone@linaro.org wrote:
From: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
This patch adds ACPI support for non-PCI SATA contoller in ahci_platform driver. It adds ACPI matching table in ahci_platform to support AMD Seattle SATA controller with following ASL structure in DSDT:
Device (SATA0) { Name(_HID, "AMDI0600") // Seattle AHSATA Name (_CCA, 1) // Cache-coherent controller Name (_CRS, ResourceTemplate () { Memory32Fixed (ReadWrite, 0xE0300000, 0x00010000) Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,,,) { 387 } }) }
Since ATA driver should not require PCI support for ATA_ACPI, this patch also removes dependency in the driver/ata/Kconfig.
Signed-off-by: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
Question: is this DSDT structure specified somewhere? The AHCI specification requires a PCI bus, but I expect that other SoCs will do the same thing as Seattle, and it would be a shame if they came up with a different representation.
Arnd
Actually, this patch might be a bit old now. As suggested earlier on the ML (`https://lkml.org/lkml/2014/10/6/343), we have thought about reusing the PCI class code (_CLS) instead as following.
Device (SATA0) // AHCI { Name(_HID, "AMDI0600") // Seattle AHCI/SATA Name (_CCA, 1) // Cache-coherent controller Name (_CLS, Package (3) { 0x01, // Base Class: Mass Storage 0x06, // Sub-Class: Serial ATA 0x01, // Interface: AHCI }) .......
So, this patch should now be deprecated and not to be going upstream.
Ok, but is the new way of doing this standardized anywhere, or is it just better than the original approach?
Arnd
I believe it is being discussed in the ARM/Linaro ACPI meetings, and it's probably a better approach.
Leo could probably comment more on this as well.
Thanks,
Suravee
On Thu, Nov 06, 2014 at 07:48:29AM -0600, Suravee Suthikulpanit wrote:
On 11/6/14 07:28, Arnd Bergmann wrote:
On Thursday 06 November 2014 06:52:59 Suravee Suthikulpanit wrote:
On 11/6/14 04:06, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:27 al.stone@linaro.org wrote:
From: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
This patch adds ACPI support for non-PCI SATA contoller in ahci_platform driver. It adds ACPI matching table in ahci_platform to support AMD Seattle SATA controller with following ASL structure in DSDT:
Device (SATA0) { Name(_HID, "AMDI0600") // Seattle AHSATA Name (_CCA, 1) // Cache-coherent controller Name (_CRS, ResourceTemplate () { Memory32Fixed (ReadWrite, 0xE0300000, 0x00010000) Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,,,) { 387 } }) }
Since ATA driver should not require PCI support for ATA_ACPI, this patch also removes dependency in the driver/ata/Kconfig.
Signed-off-by: Suravee Suthikulpanit Suravee.Suthikulpanit@amd.com
Question: is this DSDT structure specified somewhere? The AHCI specification requires a PCI bus, but I expect that other SoCs will do the same thing as Seattle, and it would be a shame if they came up with a different representation.
Arnd
Actually, this patch might be a bit old now. As suggested earlier on the ML (`https://lkml.org/lkml/2014/10/6/343), we have thought about reusing the PCI class code (_CLS) instead as following.
Device (SATA0) // AHCI { Name(_HID, "AMDI0600") // Seattle AHCI/SATA Name (_CCA, 1) // Cache-coherent controller Name (_CLS, Package (3) { 0x01, // Base Class: Mass Storage 0x06, // Sub-Class: Serial ATA 0x01, // Interface: AHCI }) .......
So, this patch should now be deprecated and not to be going upstream.
Ok, but is the new way of doing this standardized anywhere, or is it just better than the original approach?
Arnd
I believe it is being discussed in the ARM/Linaro ACPI meetings, and it's probably a better approach.
Leo could probably comment more on this as well.
I beleive Windows can already instantiate devices from _CLS, linux just never had the ability. So its not new to ACPI just new to Linux. It was suggested as a better solution for generic devices that do not have a generic ACPI identifier but do have a generic _CLS.
Leo found some issues with the spec in this area and it is being improved.
Graeme
On Thursday 06 November 2014 15:36:06 Graeme Gregory wrote:
I beleive Windows can already instantiate devices from _CLS, linux just never had the ability. So its not new to ACPI just new to Linux. It was suggested as a better solution for generic devices that do not have a generic ACPI identifier but do have a generic _CLS.
Leo found some issues with the spec in this area and it is being improved.
Ok, very good.
Arnd
From: Al Stone ahs3@redhat.com
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/pnp/resource.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/drivers/pnp/resource.c b/drivers/pnp/resource.c index 782e822..d952462 100644 --- a/drivers/pnp/resource.c +++ b/drivers/pnp/resource.c @@ -313,6 +313,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, progif = class & 0xff; class >>= 8;
+#ifdef HAVE_ARCH_PCI_GET_LEGACY_IDE_IRQ if (class == PCI_CLASS_STORAGE_IDE) { /* * Unless both channels are native-PCI mode only, @@ -326,6 +327,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, return 1; } } +#endif /* HAVE_ARCH_PCI_GET_LEGACY_IDE_IRQ */
return 0; }
On Wednesday 05 November 2014 20:32:28 al.stone@linaro.org wrote:
diff --git a/drivers/pnp/resource.c b/drivers/pnp/resource.c index 782e822..d952462 100644 --- a/drivers/pnp/resource.c +++ b/drivers/pnp/resource.c @@ -313,6 +313,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, progif = class & 0xff; class >>= 8; +#ifdef HAVE_ARCH_PCI_GET_LEGACY_IDE_IRQ if (class == PCI_CLASS_STORAGE_IDE) { /* * Unless both channels are native-PCI mode only, @@ -326,6 +327,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, return 1; } } +#endif /* HAVE_ARCH_PCI_GET_LEGACY_IDE_IRQ */ return 0; }
This breaks x86 AFAICT.
Not sure what the right solution for this is, I guess we should return an invalid IRQ from pci_get_legacy_ide_irq(). The asm-generic implementation doesn't seem overly generic here, blame the asm-generic maintainer.
Arnd
From: Hanjun Guo hanjun.guo@linaro.org
Introduce some PCI functions to make ACPI can be compiled when CONFIG_PCI is enabled, these functions should be revisited when implemented on ARM64.
Signed-off-by: Hanjun Guo hanjun.guo@linaro.org [fixed up for 3.17-rc] Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/Makefile | 1 + arch/arm64/kernel/acpi.c | 20 ++++++++++++++++++++ arch/arm64/pci/Makefile | 1 + arch/arm64/pci/pci.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 arch/arm64/pci/Makefile create mode 100644 arch/arm64/pci/pci.c
diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile index 20901ff..983d72a 100644 --- a/arch/arm64/Makefile +++ b/arch/arm64/Makefile @@ -49,6 +49,7 @@ core-$(CONFIG_NET) += arch/arm64/net/ core-$(CONFIG_KVM) += arch/arm64/kvm/ core-$(CONFIG_XEN) += arch/arm64/xen/ core-$(CONFIG_CRYPTO) += arch/arm64/crypto/ +drivers-$(CONFIG_PCI) += arch/arm64/pci/ libs-y := arch/arm64/lib/ $(libs-y) libs-y += $(LIBGCC) libs-$(CONFIG_EFI_STUB) += drivers/firmware/efi/libstub/ diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 41312bd..6411600 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -357,3 +357,23 @@ static int __init parse_acpi(char *arg) return 0; } early_param("acpi", parse_acpi); + +int acpi_isa_irq_to_gsi(unsigned isa_irq, u32 *gsi) +{ + return -1; +} + +int acpi_register_ioapic(acpi_handle handle, u64 phys_addr, u32 gsi_base) +{ + /* TBD */ + return -EINVAL; +} +EXPORT_SYMBOL(acpi_register_ioapic); + +int acpi_unregister_ioapic(acpi_handle handle, u32 gsi_base) +{ + /* TBD */ + return -EINVAL; +} +EXPORT_SYMBOL(acpi_unregister_ioapic); + diff --git a/arch/arm64/pci/Makefile b/arch/arm64/pci/Makefile new file mode 100644 index 0000000..b8d5dbd --- /dev/null +++ b/arch/arm64/pci/Makefile @@ -0,0 +1 @@ +obj-y += pci.o diff --git a/arch/arm64/pci/pci.c b/arch/arm64/pci/pci.c new file mode 100644 index 0000000..b03b0eb --- /dev/null +++ b/arch/arm64/pci/pci.c @@ -0,0 +1,28 @@ +#include <linux/acpi.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/pci.h> + +/** + * raw_pci_read - Platform-specific PCI config space access. + * + * Default empty implementation. Replace with an architecture-specific setup + * routine, if necessary. + */ +int __weak raw_pci_read(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *val) +{ + return -EINVAL; +} + +int __weak raw_pci_write(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 val) +{ + return -EINVAL; +} + +/* Root bridge scanning */ +struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) +{ + return NULL; +}
On Wednesday 05 November 2014 20:32:29 al.stone@linaro.org wrote:
+/**
- raw_pci_read - Platform-specific PCI config space access.
- Default empty implementation. Replace with an architecture-specific setup
- routine, if necessary.
- */
+int __weak raw_pci_read(unsigned int domain, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *val)
+{
return -EINVAL;
+}
+int __weak raw_pci_write(unsigned int domain, unsigned int bus,
unsigned int devfn, int reg, int len, u32 val)
+{
return -EINVAL;
+}
+/* Root bridge scanning */ +struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) +{
return NULL;
+}
Having __weak implementations makes no sense here, there is no code that could override them. I guess these could go into drivers/pci/host/pci-host-generic.c once if you add the patch to your series that implements arm64 support for this driver.
Arnd
From: Mark Salter msalter@redhat.com
This is a first-cut effort at parking protocol support. It is very much a work in progress (as is the spec it is based on). This code deviates from the current spec in a number of ways to work around current firmware issues and issues with kernels using 64K page sizes.
caveat utilitor
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/Kconfig | 3 + arch/arm64/include/asm/acpi.h | 3 + arch/arm64/include/asm/smp.h | 5 ++ arch/arm64/kernel/Makefile | 3 +- arch/arm64/kernel/acpi.c | 34 ++++++++-- arch/arm64/kernel/cpu_ops.c | 4 ++ arch/arm64/kernel/smp_parking_protocol.c | 107 +++++++++++++++++++++++++++++++ drivers/irqchip/irq-gic-v3.c | 10 +++ drivers/irqchip/irq-gic.c | 10 +++ include/linux/irqchip/arm-gic.h | 2 + 10 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 arch/arm64/kernel/smp_parking_protocol.c
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index aee6a60..7789db7 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -269,6 +269,9 @@ config SMP
If you don't know what to do here, say N.
+config ARM_PARKING_PROTOCOL + def_bool y if SMP + config SCHED_MC bool "Multi-core scheduler support" depends on SMP diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 483ff45..6e692f4 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -89,11 +89,14 @@ static inline bool acpi_has_cpu_in_madt(void) static inline void arch_fix_phys_package_id(int num, u32 slot) { } void __init acpi_smp_init_cpus(void);
+extern int acpi_get_cpu_parked_address(int cpu, u64 *addr); + #else static inline void disable_acpi(void) { } static inline bool acpi_psci_present(void) { return false; } static inline bool acpi_psci_use_hvc(void) { return false; } static inline void acpi_smp_init_cpus(void) { } +static inline int acpi_get_cpu_parked_address(int cpu, u64 *addr) { return -EOPNOTSUPP; } #endif /* CONFIG_ACPI */
#endif /*_ASM_ACPI_H*/ diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h index bf22650..3411561 100644 --- a/arch/arm64/include/asm/smp.h +++ b/arch/arm64/include/asm/smp.h @@ -52,6 +52,11 @@ extern void set_smp_cross_call(void (*)(const struct cpumask *, unsigned int)); extern void (*__smp_cross_call)(const struct cpumask *, unsigned int);
/* + * Provide a function to signal a parked secondary CPU. + */ +extern void set_smp_boot_wakeup_call(void (*)(int cpu)); + +/* * Called from the secondary holding pen, this is the secondary CPU entry point. */ asmlinkage void secondary_start_kernel(void); diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index f48e3f7..f4ba4fe 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -21,7 +21,8 @@ arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \ sys_compat.o arm64-obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o entry-ftrace.o arm64-obj-$(CONFIG_MODULES) += arm64ksyms.o module.o -arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o topology.o +arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o topology.o \ + smp_parking_protocol.o arm64-obj-$(CONFIG_PERF_EVENTS) += perf_regs.o arm64-obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 6411600..7b3318b 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -37,6 +37,9 @@ EXPORT_SYMBOL(acpi_pci_disabled);
static int enabled_cpus; /* Processors (GICC) with enabled flag in MADT */
+static char *boot_method; +static u64 parked_address[NR_CPUS]; + /* * Since we're on ARM, the default interrupt routing model * clearly has to be GIC. @@ -71,7 +74,7 @@ void __init __acpi_unmap_table(char *map, unsigned long size) * * Returns the logical cpu number which maps to MPIDR */ -static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) +static int acpi_map_gic_cpu_interface(u64 mpidr, u64 parked_addr, u8 enabled) { int cpu;
@@ -125,9 +128,11 @@ static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) cpu = 0; }
+ parked_address[cpu] = parked_addr; + /* CPU 0 was already initialized */ if (cpu) { - cpu_ops[cpu] = cpu_get_ops(acpi_psci_present() ? "psci" : NULL); + cpu_ops[cpu] = cpu_get_ops(boot_method); if (!cpu_ops[cpu]) return -EINVAL;
@@ -140,7 +145,7 @@ static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) set_cpu_possible(cpu, true); } else { /* get cpu0's ops, no need to return if ops is null */ - cpu_ops[0] = cpu_get_ops(acpi_psci_present() ? "psci" : NULL); + cpu_ops[0] = cpu_get_ops(boot_method); }
enabled_cpus++; @@ -161,7 +166,7 @@ acpi_parse_gic_cpu_interface(struct acpi_subtable_header *header, acpi_table_print_madt_entry(header);
acpi_map_gic_cpu_interface(processor->arm_mpidr & MPIDR_HWID_BITMASK, - processor->flags & ACPI_MADT_ENABLED); + processor->parked_address, processor->flags & ACPI_MADT_ENABLED);
return 0; } @@ -278,9 +283,12 @@ static int __init acpi_parse_fadt(struct acpi_table_header *table) * the ACPI spec or the Parking protocol spec. */ if (acpi_psci_present()) - return 0; + boot_method = "psci"; + else if (IS_ENABLED(CONFIG_ARM_PARKING_PROTOCOL)) + boot_method = "parking-protocol";
- pr_warn("No PSCI support, will not bring up secondary CPUs\n"); + if (!boot_method) + pr_warn("No boot method, will not bring up secondary CPUs\n"); return -EOPNOTSUPP; }
@@ -341,6 +349,20 @@ void __init acpi_gic_init(void) early_acpi_os_unmap_memory((char *)table, tbl_size); }
+/* + * Parked Address in ACPI GIC structure will be used as the CPU + * release address + */ +int acpi_get_cpu_parked_address(int cpu, u64 *addr) +{ + if (!addr || !parked_address[cpu]) + return -EINVAL; + + *addr = parked_address[cpu]; + + return 0; +} + static int __init parse_acpi(char *arg) { if (!arg) diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c index 1a04deb..1d90f31 100644 --- a/arch/arm64/kernel/cpu_ops.c +++ b/arch/arm64/kernel/cpu_ops.c @@ -23,6 +23,7 @@ #include <linux/string.h>
extern const struct cpu_operations smp_spin_table_ops; +extern const struct cpu_operations smp_parking_protocol_ops; extern const struct cpu_operations cpu_psci_ops;
const struct cpu_operations *cpu_ops[NR_CPUS]; @@ -30,6 +31,9 @@ const struct cpu_operations *cpu_ops[NR_CPUS]; static const struct cpu_operations *supported_cpu_ops[] = { #ifdef CONFIG_SMP &smp_spin_table_ops, +#ifdef CONFIG_ARM_PARKING_PROTOCOL + &smp_parking_protocol_ops, +#endif #endif &cpu_psci_ops, NULL, diff --git a/arch/arm64/kernel/smp_parking_protocol.c b/arch/arm64/kernel/smp_parking_protocol.c new file mode 100644 index 0000000..e9c0c68 --- /dev/null +++ b/arch/arm64/kernel/smp_parking_protocol.c @@ -0,0 +1,107 @@ +/* + * Parking Protocol SMP initialisation + * + * Based largely on spin-table method. + * + * Copyright (C) 2013 ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/smp.h> +#include <linux/types.h> +#include <linux/acpi.h> + +#include <asm/cacheflush.h> +#include <asm/cpu_ops.h> +#include <asm/cputype.h> +#include <asm/smp_plat.h> + +static phys_addr_t cpu_mailbox_addr[NR_CPUS]; + +static void (*__smp_boot_wakeup)(int cpu); + +void set_smp_boot_wakeup_call(void (*fn)(int cpu)) +{ + __smp_boot_wakeup = fn; +} + +static int smp_parking_protocol_cpu_init(struct device_node *dn, + unsigned int cpu) +{ + /* + * Determine the mailbox address. + */ + if (!acpi_get_cpu_parked_address(cpu, &cpu_mailbox_addr[cpu])) { + pr_info("%s: ACPI parked addr=%llx\n", + __func__, cpu_mailbox_addr[cpu]); + return 0; + } + + pr_err("CPU %d: missing or invalid parking protocol mailbox\n", cpu); + + return -1; +} + +static int smp_parking_protocol_cpu_prepare(unsigned int cpu) +{ + return 0; +} + +struct parking_protocol_mailbox { + __le32 cpu_id; + __le32 reserved; + __le64 entry_point; +}; + +static int smp_parking_protocol_cpu_boot(unsigned int cpu) +{ + struct parking_protocol_mailbox __iomem *mailbox; + + if (!cpu_mailbox_addr[cpu] || !__smp_boot_wakeup) + return -ENODEV; + + /* + * The mailbox may or may not be inside the linear mapping. + * As ioremap_cache will either give us a new mapping or reuse the + * existing linear mapping, we can use it to cover both cases. In + * either case the memory will be MT_NORMAL. + */ + mailbox = ioremap_cache(cpu_mailbox_addr[cpu], sizeof(*mailbox)); + if (!mailbox) + return -ENOMEM; + + /* + * We write the entry point and cpu id as LE regardless of the + * native endianess of the kernel. Therefore, any boot-loaders + * that read this address need to convert this address to the + * Boot-Loader's endianess before jumping. + */ + writeq(__pa(secondary_entry), &mailbox->entry_point); + writel(cpu, &mailbox->cpu_id); + __flush_dcache_area(mailbox, sizeof(*mailbox)); + __smp_boot_wakeup(cpu); + + iounmap(mailbox); + + return 0; +} + +const struct cpu_operations smp_parking_protocol_ops = { + .name = "parking-protocol", + .cpu_init = smp_parking_protocol_cpu_init, + .cpu_prepare = smp_parking_protocol_cpu_prepare, + .cpu_boot = smp_parking_protocol_cpu_boot, +}; diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index aa17ae8..d330dab 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -506,9 +506,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) isb(); }
+#ifdef CONFIG_ARM_PARKING_PROTOCOL +static void gic_wakeup_parked_cpu(int cpu) +{ + gic_raise_softirq(cpumask_of(cpu), 0); +} +#endif + static void gic_smp_init(void) { set_smp_cross_call(gic_raise_softirq); +#ifdef CONFIG_ARM_PARKING_PROTOCOL + set_smp_boot_wakeup_call(gic_wakeup_parked_cpu); +#endif register_cpu_notifier(&gic_cpu_notifier); }
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index afdeaa1..26e6773 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -643,6 +643,13 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } + +#ifdef CONFIG_ARM_PARKING_PROTOCOL +static void gic_wakeup_parked_cpu(int cpu) +{ + gic_raise_softirq(cpumask_of(cpu), GIC_DIST_SOFTINT_NSATT); +} +#endif #endif
#ifdef CONFIG_BL_SWITCHER @@ -998,6 +1005,9 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); +#ifdef CONFIG_ARM_PARKING_PROTOCOL + set_smp_boot_wakeup_call(gic_wakeup_parked_cpu); +#endif #endif set_handle_irq(gic_handle_irq); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 13eed92..dc9cb5f 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -55,6 +55,8 @@ (GICD_INT_DEF_PRI << 8) |\ GICD_INT_DEF_PRI)
+#define GIC_DIST_SOFTINT_NSATT 0x8000 + #define GICH_HCR 0x0 #define GICH_VTR 0x4 #define GICH_VMCR 0x8
On Wednesday 05 November 2014 20:32:30 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
This is a first-cut effort at parking protocol support. It is very much a work in progress (as is the spec it is based on). This code deviates from the current spec in a number of ways to work around current firmware issues and issues with kernels using 64K page sizes.
caveat utilitor
Signed-off-by: Mark Salter msalter@redhat.com
Is this needed for Seattle? My impression was that X-Gene is the only user of this feature, so it should be in that branch instead.
Arnd
On 11/06/2014 03:14 AM, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:30 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
This is a first-cut effort at parking protocol support. It is very much a work in progress (as is the spec it is based on). This code deviates from the current spec in a number of ways to work around current firmware issues and issues with kernels using 64K page sizes.
caveat utilitor
Signed-off-by: Mark Salter msalter@redhat.com
Is this needed for Seattle? My impression was that X-Gene is the only user of this feature, so it should be in that branch instead.
Arnd
Yeah, this is a messy one. Seattle does not currently support PSCI but will in the future. It does currently support parking protocol, however, so this patch lets the same firmware work for both Microsoft and for Linux. At some point, it will not be needed for Seattle, but will be for APM, which currently requires firmware updates to move from the spin-table to parking protocol.
Note that the ACPI spec allows for either PSCI or parking protocol for use on ARM. The SBSA says only PSCI should be used.
On Thu, Nov 06, 2014 at 03:32:30AM +0000, al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
This is a first-cut effort at parking protocol support. It is very much a work in progress (as is the spec it is based on). This code deviates from the current spec in a number of ways to work around current firmware issues and issues with kernels using 64K page sizes.
caveat utilitor
It would be very nice for the patch to have comments as to what is a workaround. Some things I've commented on below might simply be bugs, but it's not always obvious.
Signed-off-by: Mark Salter msalter@redhat.com
arch/arm64/Kconfig | 3 + arch/arm64/include/asm/acpi.h | 3 + arch/arm64/include/asm/smp.h | 5 ++ arch/arm64/kernel/Makefile | 3 +- arch/arm64/kernel/acpi.c | 34 ++++++++-- arch/arm64/kernel/cpu_ops.c | 4 ++ arch/arm64/kernel/smp_parking_protocol.c | 107 +++++++++++++++++++++++++++++++ drivers/irqchip/irq-gic-v3.c | 10 +++ drivers/irqchip/irq-gic.c | 10 +++ include/linux/irqchip/arm-gic.h | 2 + 10 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 arch/arm64/kernel/smp_parking_protocol.c
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index aee6a60..7789db7 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -269,6 +269,9 @@ config SMP
If you don't know what to do here, say N.
+config ARM_PARKING_PROTOCOL
def_bool y if SMP
config SCHED_MC bool "Multi-core scheduler support" depends on SMP diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 483ff45..6e692f4 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -89,11 +89,14 @@ static inline bool acpi_has_cpu_in_madt(void) static inline void arch_fix_phys_package_id(int num, u32 slot) { } void __init acpi_smp_init_cpus(void);
+extern int acpi_get_cpu_parked_address(int cpu, u64 *addr);
#else static inline void disable_acpi(void) { } static inline bool acpi_psci_present(void) { return false; } static inline bool acpi_psci_use_hvc(void) { return false; } static inline void acpi_smp_init_cpus(void) { } +static inline int acpi_get_cpu_parked_address(int cpu, u64 *addr) { return -EOPNOTSUPP; } #endif /* CONFIG_ACPI */
#endif /*_ASM_ACPI_H*/ diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h index bf22650..3411561 100644 --- a/arch/arm64/include/asm/smp.h +++ b/arch/arm64/include/asm/smp.h @@ -52,6 +52,11 @@ extern void set_smp_cross_call(void (*)(const struct cpumask *, unsigned int)); extern void (*__smp_cross_call)(const struct cpumask *, unsigned int);
/*
- Provide a function to signal a parked secondary CPU.
- */
+extern void set_smp_boot_wakeup_call(void (*)(int cpu));
Can't you just call smp_cross_call directly?
Then you don't need anything in the interrupt controller driver (be it GICv2 or GICv3). I was under the impression that the parking protocol just required an IPI, not any particular one.
+/*
- Called from the secondary holding pen, this is the secondary CPU entry point.
*/ asmlinkage void secondary_start_kernel(void); diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index f48e3f7..f4ba4fe 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -21,7 +21,8 @@ arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \ sys_compat.o arm64-obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o entry-ftrace.o arm64-obj-$(CONFIG_MODULES) += arm64ksyms.o module.o -arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o topology.o +arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o topology.o \
smp_parking_protocol.o
arm64-obj-$(CONFIG_PERF_EVENTS) += perf_regs.o arm64-obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 6411600..7b3318b 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -37,6 +37,9 @@ EXPORT_SYMBOL(acpi_pci_disabled);
static int enabled_cpus; /* Processors (GICC) with enabled flag in MADT */
+static char *boot_method; +static u64 parked_address[NR_CPUS];
/*
- Since we're on ARM, the default interrupt routing model
- clearly has to be GIC.
@@ -71,7 +74,7 @@ void __init __acpi_unmap_table(char *map, unsigned long size)
- Returns the logical cpu number which maps to MPIDR
*/ -static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) +static int acpi_map_gic_cpu_interface(u64 mpidr, u64 parked_addr, u8 enabled) { int cpu;
@@ -125,9 +128,11 @@ static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) cpu = 0; }
parked_address[cpu] = parked_addr;
/* CPU 0 was already initialized */ if (cpu) {
cpu_ops[cpu] = cpu_get_ops(acpi_psci_present() ? "psci" : NULL);
cpu_ops[cpu] = cpu_get_ops(boot_method); if (!cpu_ops[cpu]) return -EINVAL;
@@ -140,7 +145,7 @@ static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) set_cpu_possible(cpu, true); } else { /* get cpu0's ops, no need to return if ops is null */
cpu_ops[0] = cpu_get_ops(acpi_psci_present() ? "psci" : NULL);
cpu_ops[0] = cpu_get_ops(boot_method); } enabled_cpus++;
@@ -161,7 +166,7 @@ acpi_parse_gic_cpu_interface(struct acpi_subtable_header *header, acpi_table_print_madt_entry(header);
acpi_map_gic_cpu_interface(processor->arm_mpidr & MPIDR_HWID_BITMASK,
processor->flags & ACPI_MADT_ENABLED);
processor->parked_address, processor->flags & ACPI_MADT_ENABLED); return 0;
} @@ -278,9 +283,12 @@ static int __init acpi_parse_fadt(struct acpi_table_header *table) * the ACPI spec or the Parking protocol spec. */ if (acpi_psci_present())
return 0;
boot_method = "psci";
else if (IS_ENABLED(CONFIG_ARM_PARKING_PROTOCOL))
boot_method = "parking-protocol";
pr_warn("No PSCI support, will not bring up secondary CPUs\n");
if (!boot_method)
pr_warn("No boot method, will not bring up secondary CPUs\n"); return -EOPNOTSUPP; }
@@ -341,6 +349,20 @@ void __init acpi_gic_init(void) early_acpi_os_unmap_memory((char *)table, tbl_size); }
+/*
- Parked Address in ACPI GIC structure will be used as the CPU
- release address
- */
+int acpi_get_cpu_parked_address(int cpu, u64 *addr) +{
if (!addr || !parked_address[cpu])
return -EINVAL;
I don't think addr can ever be NULL given that the only caller (smp_parking_protocol_cpu_init) generates addr by taking the address of an array element.
*addr = parked_address[cpu];
return 0;
+}
static int __init parse_acpi(char *arg) { if (!arg) diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c index 1a04deb..1d90f31 100644 --- a/arch/arm64/kernel/cpu_ops.c +++ b/arch/arm64/kernel/cpu_ops.c @@ -23,6 +23,7 @@ #include <linux/string.h>
extern const struct cpu_operations smp_spin_table_ops; +extern const struct cpu_operations smp_parking_protocol_ops; extern const struct cpu_operations cpu_psci_ops;
const struct cpu_operations *cpu_ops[NR_CPUS]; @@ -30,6 +31,9 @@ const struct cpu_operations *cpu_ops[NR_CPUS]; static const struct cpu_operations *supported_cpu_ops[] = { #ifdef CONFIG_SMP &smp_spin_table_ops, +#ifdef CONFIG_ARM_PARKING_PROTOCOL
&smp_parking_protocol_ops,
+#endif #endif
Won't this mean "parking-protocol" will be accepted in a dtb? That shouldn't be the case.
&cpu_psci_ops, NULL,
diff --git a/arch/arm64/kernel/smp_parking_protocol.c b/arch/arm64/kernel/smp_parking_protocol.c new file mode 100644 index 0000000..e9c0c68 --- /dev/null +++ b/arch/arm64/kernel/smp_parking_protocol.c @@ -0,0 +1,107 @@ +/*
- Parking Protocol SMP initialisation
- Based largely on spin-table method.
- Copyright (C) 2013 ARM Ltd.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 as
- published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see http://www.gnu.org/licenses/.
- */
+#include <linux/delay.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/smp.h> +#include <linux/types.h> +#include <linux/acpi.h>
+#include <asm/cacheflush.h> +#include <asm/cpu_ops.h> +#include <asm/cputype.h> +#include <asm/smp_plat.h>
+static phys_addr_t cpu_mailbox_addr[NR_CPUS];
+static void (*__smp_boot_wakeup)(int cpu);
+void set_smp_boot_wakeup_call(void (*fn)(int cpu)) +{
__smp_boot_wakeup = fn;
+}
+static int smp_parking_protocol_cpu_init(struct device_node *dn,
unsigned int cpu)
+{
/*
* Determine the mailbox address.
*/
if (!acpi_get_cpu_parked_address(cpu, &cpu_mailbox_addr[cpu])) {
pr_info("%s: ACPI parked addr=%llx\n",
__func__, cpu_mailbox_addr[cpu]);
return 0;
}
pr_err("CPU %d: missing or invalid parking protocol mailbox\n", cpu);
return -1;
+}
+static int smp_parking_protocol_cpu_prepare(unsigned int cpu) +{
return 0;
+}
+struct parking_protocol_mailbox {
__le32 cpu_id;
__le32 reserved;
__le64 entry_point;
+};
+static int smp_parking_protocol_cpu_boot(unsigned int cpu) +{
struct parking_protocol_mailbox __iomem *mailbox;
if (!cpu_mailbox_addr[cpu] || !__smp_boot_wakeup)
return -ENODEV;
We should probably complain about the lack of a mailbox in the prepare callback, so we don't set the CPU as present.
/*
* The mailbox may or may not be inside the linear mapping.
* As ioremap_cache will either give us a new mapping or reuse the
* existing linear mapping, we can use it to cover both cases. In
* either case the memory will be MT_NORMAL.
*/
mailbox = ioremap_cache(cpu_mailbox_addr[cpu], sizeof(*mailbox));
if (!mailbox)
return -ENOMEM;
The mailbox should not ever be in the linear mapping, and the memory map is broken if that is the case. The spec doesn't allow for cacheable mappings of the mailbox.
/*
* We write the entry point and cpu id as LE regardless of the
* native endianess of the kernel. Therefore, any boot-loaders
* that read this address need to convert this address to the
* Boot-Loader's endianess before jumping.
*/
writeq(__pa(secondary_entry), &mailbox->entry_point);
writel(cpu, &mailbox->cpu_id);
__flush_dcache_area(mailbox, sizeof(*mailbox));
Without a cacheable mapping, this flush shouldn't be necessary.
__smp_boot_wakeup(cpu);
This should be able to be an smp_cross_call.
iounmap(mailbox);
return 0;
+}
+const struct cpu_operations smp_parking_protocol_ops = {
.name = "parking-protocol",
.cpu_init = smp_parking_protocol_cpu_init,
.cpu_prepare = smp_parking_protocol_cpu_prepare,
.cpu_boot = smp_parking_protocol_cpu_boot,
+};
I believe all of the below GIC changes can go.
Thanks, Mark.
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index aa17ae8..d330dab 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -506,9 +506,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) isb(); }
+#ifdef CONFIG_ARM_PARKING_PROTOCOL +static void gic_wakeup_parked_cpu(int cpu) +{
gic_raise_softirq(cpumask_of(cpu), 0);
+} +#endif
static void gic_smp_init(void) { set_smp_cross_call(gic_raise_softirq); +#ifdef CONFIG_ARM_PARKING_PROTOCOL
set_smp_boot_wakeup_call(gic_wakeup_parked_cpu);
+#endif register_cpu_notifier(&gic_cpu_notifier); }
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index afdeaa1..26e6773 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -643,6 +643,13 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
raw_spin_unlock_irqrestore(&irq_controller_lock, flags);
}
+#ifdef CONFIG_ARM_PARKING_PROTOCOL +static void gic_wakeup_parked_cpu(int cpu) +{
gic_raise_softirq(cpumask_of(cpu), GIC_DIST_SOFTINT_NSATT);
+} +#endif #endif
#ifdef CONFIG_BL_SWITCHER @@ -998,6 +1005,9 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); +#ifdef CONFIG_ARM_PARKING_PROTOCOL
set_smp_boot_wakeup_call(gic_wakeup_parked_cpu);
+#endif #endif set_handle_irq(gic_handle_irq); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 13eed92..dc9cb5f 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -55,6 +55,8 @@ (GICD_INT_DEF_PRI << 8) |\ GICD_INT_DEF_PRI)
+#define GIC_DIST_SOFTINT_NSATT 0x8000
#define GICH_HCR 0x0 #define GICH_VTR 0x4
#define GICH_VMCR 0x8
1.9.3
Linaro-acpi mailing list Linaro-acpi@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-acpi
From: Tom Lendacky thomas.lendacky@amd.com
This patch modifies the upstream AMD 10GbE XGBE Ethernet driver to support A0 Seattle silicon in currently shipping systems. The upstream Linux driver is targetted for Seattle B0 silicon.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/net/ethernet/amd/xgbe/xgbe-dev.c | 12 ++++++++++++ drivers/net/ethernet/amd/xgbe/xgbe-drv.c | 3 +++ drivers/net/ethernet/amd/xgbe/xgbe-main.c | 1 + drivers/net/ethernet/amd/xgbe/xgbe.h | 3 +++ 4 files changed, 19 insertions(+)
diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c index 9da3a03..1c03abb 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c @@ -695,6 +695,18 @@ static int xgbe_read_mmd_regs(struct xgbe_prv_data *pdata, int prtad, else mmd_address = (pdata->mdio_mmd << 16) | (mmd_reg & 0xffff);
+ if (XGBE_SEATTLE_A0) { + /* The PCS implementation has reversed the devices in + * package registers so we need to change 05 to 06 and + * 06 to 05 if being read (these registers are readonly + * so no need to do this in the write function) + */ + if ((mmd_address & 0xffff) == 0x05) + mmd_address = (mmd_address & ~0xffff) | 0x06; + else if ((mmd_address & 0xffff) == 0x06) + mmd_address = (mmd_address & ~0xffff) | 0x05; + } + /* The PCS registers are accessed using mmio. The underlying APB3 * management interface uses indirect addressing to access the MMD * register sets. This requires accessing of the PCS register in two diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c index 2955499..423ae3d 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c @@ -425,6 +425,9 @@ void xgbe_get_all_hw_features(struct xgbe_prv_data *pdata) hw_feat->rx_ch_cnt++; hw_feat->tx_ch_cnt++;
+ /* A0 does not support NUMTC, hardcode it for now */ + hw_feat->tc_cnt = XGBE_TC_CNT; + DBGPR("<--xgbe_get_all_hw_features\n"); }
diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-main.c b/drivers/net/ethernet/amd/xgbe/xgbe-main.c index f5a8fa0..3809d1c 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-main.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-main.c @@ -532,6 +532,7 @@ static int xgbe_resume(struct device *dev) #endif /* CONFIG_PM */
static const struct of_device_id xgbe_of_match[] = { + { .compatible = "amd,xgbe-seattle-v0a", }, { .compatible = "amd,xgbe-seattle-v1a", }, {}, }; diff --git a/drivers/net/ethernet/amd/xgbe/xgbe.h b/drivers/net/ethernet/amd/xgbe/xgbe.h index 789957d..5636db4 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe.h +++ b/drivers/net/ethernet/amd/xgbe/xgbe.h @@ -186,8 +186,11 @@ #define XGBE_FIFO_SIZE_B(x) (x) #define XGBE_FIFO_SIZE_KB(x) (x * 1024)
+#define XGBE_TC_CNT 2 #define XGBE_TC_MIN_QUANTUM 10
+#define XGBE_SEATTLE_A0 ((read_cpuid_id() & 0x00f0000f) == 0) + /* Helper macro for descriptor handling * Always use XGBE_GET_DESC_DATA to access the descriptor data * since the index is free-running and needs to be and-ed
From: Tom Lendacky thomas.lendacky@amd.com
This patch modifies the upstream AMD XGBE PHY driver to support A0 Seattle silicon in currently shipping systems. The upstream Linux driver is targetted for Seattle B0 silicon.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/net/phy/amd-xgbe-phy.c | 564 +++++++++++++++++++---------------------- 1 file changed, 260 insertions(+), 304 deletions(-)
diff --git a/drivers/net/phy/amd-xgbe-phy.c b/drivers/net/phy/amd-xgbe-phy.c index c456559..90145d9 100644 --- a/drivers/net/phy/amd-xgbe-phy.c +++ b/drivers/net/phy/amd-xgbe-phy.c @@ -75,14 +75,17 @@ #include <linux/of_device.h> #include <linux/uaccess.h>
+ MODULE_AUTHOR("Tom Lendacky thomas.lendacky@amd.com"); MODULE_LICENSE("Dual BSD/GPL"); -MODULE_VERSION("1.0.0-a"); +MODULE_VERSION("0.0.0-a"); MODULE_DESCRIPTION("AMD 10GbE (amd-xgbe) PHY driver");
-#define XGBE_PHY_ID 0x000162d0 +#define XGBE_PHY_ID 0x7996ced0 #define XGBE_PHY_MASK 0xfffffff0
+#define XGBE_PHY_SERDES_RETRY 32 +#define XGBE_PHY_CHANNEL_PROPERTY "amd,serdes-channel" #define XGBE_PHY_SPEEDSET_PROPERTY "amd,speed-set"
#define XGBE_AN_INT_CMPLT 0x01 @@ -99,11 +102,9 @@ MODULE_DESCRIPTION("AMD 10GbE (amd-xgbe) PHY driver"); #ifndef MDIO_PMA_10GBR_PMD_CTRL #define MDIO_PMA_10GBR_PMD_CTRL 0x0096 #endif - #ifndef MDIO_PMA_10GBR_FEC_CTRL #define MDIO_PMA_10GBR_FEC_CTRL 0x00ab #endif - #ifndef MDIO_AN_XNP #define MDIO_AN_XNP 0x0016 #endif @@ -111,93 +112,14 @@ MODULE_DESCRIPTION("AMD 10GbE (amd-xgbe) PHY driver"); #ifndef MDIO_AN_INTMASK #define MDIO_AN_INTMASK 0x8001 #endif - #ifndef MDIO_AN_INT #define MDIO_AN_INT 0x8002 #endif
-#ifndef MDIO_AN_KR_CTRL -#define MDIO_AN_KR_CTRL 0x8003 -#endif - #ifndef MDIO_CTRL1_SPEED1G #define MDIO_CTRL1_SPEED1G (MDIO_CTRL1_SPEED10G & ~BMCR_SPEED100) #endif
-#ifndef MDIO_KR_CTRL_PDETECT -#define MDIO_KR_CTRL_PDETECT 0x01 -#endif - -/* SerDes integration register offsets */ -#define SIR0_KR_RT_1 0x002c -#define SIR0_STATUS 0x0040 -#define SIR1_SPEED 0x0000 - -/* SerDes integration register entry bit positions and sizes */ -#define SIR0_KR_RT_1_RESET_INDEX 11 -#define SIR0_KR_RT_1_RESET_WIDTH 1 -#define SIR0_STATUS_RX_READY_INDEX 0 -#define SIR0_STATUS_RX_READY_WIDTH 1 -#define SIR0_STATUS_TX_READY_INDEX 8 -#define SIR0_STATUS_TX_READY_WIDTH 1 -#define SIR1_SPEED_DATARATE_INDEX 4 -#define SIR1_SPEED_DATARATE_WIDTH 2 -#define SIR1_SPEED_PI_SPD_SEL_INDEX 12 -#define SIR1_SPEED_PI_SPD_SEL_WIDTH 4 -#define SIR1_SPEED_PLLSEL_INDEX 3 -#define SIR1_SPEED_PLLSEL_WIDTH 1 -#define SIR1_SPEED_RATECHANGE_INDEX 6 -#define SIR1_SPEED_RATECHANGE_WIDTH 1 -#define SIR1_SPEED_TXAMP_INDEX 8 -#define SIR1_SPEED_TXAMP_WIDTH 4 -#define SIR1_SPEED_WORDMODE_INDEX 0 -#define SIR1_SPEED_WORDMODE_WIDTH 3 - -#define SPEED_10000_CDR 0x7 -#define SPEED_10000_PLL 0x1 -#define SPEED_10000_RATE 0x0 -#define SPEED_10000_TXAMP 0xa -#define SPEED_10000_WORD 0x7 - -#define SPEED_2500_CDR 0x2 -#define SPEED_2500_PLL 0x0 -#define SPEED_2500_RATE 0x1 -#define SPEED_2500_TXAMP 0xf -#define SPEED_2500_WORD 0x1 - -#define SPEED_1000_CDR 0x2 -#define SPEED_1000_PLL 0x0 -#define SPEED_1000_RATE 0x3 -#define SPEED_1000_TXAMP 0xf -#define SPEED_1000_WORD 0x1 - -/* SerDes RxTx register offsets */ -#define RXTX_REG20 0x0050 -#define RXTX_REG114 0x01c8 - -/* SerDes RxTx register entry bit positions and sizes */ -#define RXTX_REG20_BLWC_ENA_INDEX 2 -#define RXTX_REG20_BLWC_ENA_WIDTH 1 -#define RXTX_REG114_PQ_REG_INDEX 9 -#define RXTX_REG114_PQ_REG_WIDTH 7 - -#define RXTX_10000_BLWC 0 -#define RXTX_10000_PQ 0x1e - -#define RXTX_2500_BLWC 1 -#define RXTX_2500_PQ 0xa - -#define RXTX_1000_BLWC 1 -#define RXTX_1000_PQ 0xa - -/* Bit setting and getting macros - * The get macro will extract the current bit field value from within - * the variable - * - * The set macro will clear the current bit field value within the - * variable and then set the bit field of the variable to the - * specified value - */ #define GET_BITS(_var, _index, _width) \ (((_var) >> (_index)) & ((0x1 << (_width)) - 1))
@@ -207,70 +129,12 @@ do { \ (_var) |= (((_val) & ((0x1 << (_width)) - 1)) << (_index)); \ } while (0)
-#define XSIR_GET_BITS(_var, _prefix, _field) \ - GET_BITS((_var), \ - _prefix##_##_field##_INDEX, \ - _prefix##_##_field##_WIDTH) - -#define XSIR_SET_BITS(_var, _prefix, _field, _val) \ - SET_BITS((_var), \ - _prefix##_##_field##_INDEX, \ - _prefix##_##_field##_WIDTH, (_val)) - -/* Macros for reading or writing SerDes integration registers - * The ioread macros will get bit fields or full values using the - * register definitions formed using the input names - * - * The iowrite macros will set bit fields or full values using the - * register definitions formed using the input names - */ -#define XSIR0_IOREAD(_priv, _reg) \ - ioread16((_priv)->sir0_regs + _reg) - -#define XSIR0_IOREAD_BITS(_priv, _reg, _field) \ - GET_BITS(XSIR0_IOREAD((_priv), _reg), \ - _reg##_##_field##_INDEX, \ - _reg##_##_field##_WIDTH) - -#define XSIR0_IOWRITE(_priv, _reg, _val) \ - iowrite16((_val), (_priv)->sir0_regs + _reg) - -#define XSIR0_IOWRITE_BITS(_priv, _reg, _field, _val) \ -do { \ - u16 reg_val = XSIR0_IOREAD((_priv), _reg); \ - SET_BITS(reg_val, \ - _reg##_##_field##_INDEX, \ - _reg##_##_field##_WIDTH, (_val)); \ - XSIR0_IOWRITE((_priv), _reg, reg_val); \ -} while (0) - -#define XSIR1_IOREAD(_priv, _reg) \ - ioread16((_priv)->sir1_regs + _reg) - -#define XSIR1_IOREAD_BITS(_priv, _reg, _field) \ - GET_BITS(XSIR1_IOREAD((_priv), _reg), \ - _reg##_##_field##_INDEX, \ - _reg##_##_field##_WIDTH) +#define XCMU_IOREAD(_priv, _reg) \ + ioread16((_priv)->cmu_regs + _reg)
-#define XSIR1_IOWRITE(_priv, _reg, _val) \ - iowrite16((_val), (_priv)->sir1_regs + _reg) +#define XCMU_IOWRITE(_priv, _reg, _val) \ + iowrite16((_val), (_priv)->cmu_regs + _reg)
-#define XSIR1_IOWRITE_BITS(_priv, _reg, _field, _val) \ -do { \ - u16 reg_val = XSIR1_IOREAD((_priv), _reg); \ - SET_BITS(reg_val, \ - _reg##_##_field##_INDEX, \ - _reg##_##_field##_WIDTH, (_val)); \ - XSIR1_IOWRITE((_priv), _reg, reg_val); \ -} while (0) - -/* Macros for reading or writing SerDes RxTx registers - * The ioread macros will get bit fields or full values using the - * register definitions formed using the input names - * - * The iowrite macros will set bit fields or full values using the - * register definitions formed using the input names - */ #define XRXTX_IOREAD(_priv, _reg) \ ioread16((_priv)->rxtx_regs + _reg)
@@ -291,6 +155,78 @@ do { \ XRXTX_IOWRITE((_priv), _reg, reg_val); \ } while (0)
+/* SerDes CMU register offsets */ +#define CMU_REG15 0x003c +#define CMU_REG16 0x0040 + +/* SerDes CMU register entry bit positions and sizes */ +#define CMU_REG16_TX_RATE_CHANGE_BASE 15 +#define CMU_REG16_RX_RATE_CHANGE_BASE 14 +#define CMU_REG16_RATE_CHANGE_DECR 2 + + +/* SerDes RxTx register offsets */ +#define RXTX_REG2 0x0008 +#define RXTX_REG3 0x000c +#define RXTX_REG5 0x0014 +#define RXTX_REG6 0x0018 +#define RXTX_REG20 0x0050 +#define RXTX_REG53 0x00d4 +#define RXTX_REG114 0x01c8 +#define RXTX_REG115 0x01cc +#define RXTX_REG142 0x0238 + +/* SerDes RxTx register entry bit positions and sizes */ +#define RXTX_REG2_RESETB_INDEX 15 +#define RXTX_REG2_RESETB_WIDTH 1 +#define RXTX_REG3_TX_DATA_RATE_INDEX 14 +#define RXTX_REG3_TX_DATA_RATE_WIDTH 2 +#define RXTX_REG3_TX_WORD_MODE_INDEX 11 +#define RXTX_REG3_TX_WORD_MODE_WIDTH 3 +#define RXTX_REG5_TXAMP_CNTL_INDEX 7 +#define RXTX_REG5_TXAMP_CNTL_WIDTH 4 +#define RXTX_REG6_RX_DATA_RATE_INDEX 9 +#define RXTX_REG6_RX_DATA_RATE_WIDTH 2 +#define RXTX_REG6_RX_WORD_MODE_INDEX 11 +#define RXTX_REG6_RX_WORD_MODE_WIDTH 3 +#define RXTX_REG20_BLWC_ENA_INDEX 2 +#define RXTX_REG20_BLWC_ENA_WIDTH 1 +#define RXTX_REG53_RX_PLLSELECT_INDEX 15 +#define RXTX_REG53_RX_PLLSELECT_WIDTH 1 +#define RXTX_REG53_TX_PLLSELECT_INDEX 14 +#define RXTX_REG53_TX_PLLSELECT_WIDTH 1 +#define RXTX_REG53_PI_SPD_SEL_CDR_INDEX 10 +#define RXTX_REG53_PI_SPD_SEL_CDR_WIDTH 4 +#define RXTX_REG114_PQ_REG_INDEX 9 +#define RXTX_REG114_PQ_REG_WIDTH 7 +#define RXTX_REG115_FORCE_LAT_CAL_START_INDEX 2 +#define RXTX_REG115_FORCE_LAT_CAL_START_WIDTH 1 +#define RXTX_REG115_FORCE_SUM_CAL_START_INDEX 1 +#define RXTX_REG115_FORCE_SUM_CAL_START_WIDTH 1 +#define RXTX_REG142_SUM_CALIB_DONE_INDEX 15 +#define RXTX_REG142_SUM_CALIB_DONE_WIDTH 1 +#define RXTX_REG142_SUM_CALIB_ERR_INDEX 14 +#define RXTX_REG142_SUM_CALIB_ERR_WIDTH 1 +#define RXTX_REG142_LAT_CALIB_DONE_INDEX 11 +#define RXTX_REG142_LAT_CALIB_DONE_WIDTH 1 + +#define RXTX_FULL_RATE 0x0 +#define RXTX_HALF_RATE 0x1 +#define RXTX_FIFTH_RATE 0x3 +#define RXTX_66BIT_WORD 0x7 +#define RXTX_10BIT_WORD 0x1 +#define RXTX_10G_TX_AMP 0xa +#define RXTX_1G_TX_AMP 0xf +#define RXTX_10G_CDR 0x7 +#define RXTX_1G_CDR 0x2 +#define RXTX_10G_PLL 0x1 +#define RXTX_1G_PLL 0x0 +#define RXTX_10G_PQ 0x1e +#define RXTX_1G_PQ 0xa + + +DEFINE_SPINLOCK(cmu_lock); + enum amd_xgbe_phy_an { AMD_XGBE_AN_READY = 0, AMD_XGBE_AN_START, @@ -328,17 +264,18 @@ struct amd_xgbe_phy_priv {
/* SerDes related mmio resources */ struct resource *rxtx_res; - struct resource *sir0_res; - struct resource *sir1_res; + struct resource *cmu_res;
/* SerDes related mmio registers */ void __iomem *rxtx_regs; /* SerDes Rx/Tx CSRs */ - void __iomem *sir0_regs; /* SerDes integration registers (1/2) */ - void __iomem *sir1_regs; /* SerDes integration registers (2/2) */ + void __iomem *cmu_regs; /* SerDes CMU CSRs */ + + unsigned int serdes_channel; + unsigned int speed_set;
/* Maintain link status for re-starting auto-negotiation */ unsigned int link; - unsigned int speed_set; + enum amd_xgbe_phy_mode mode;
/* Auto-negotiation state machine support */ struct mutex an_mutex; @@ -348,7 +285,6 @@ struct amd_xgbe_phy_priv { enum amd_xgbe_phy_rx kx_state; struct work_struct an_work; struct workqueue_struct *an_workqueue; - unsigned int parallel_detect; };
static int amd_xgbe_an_enable_kr_training(struct phy_device *phydev) @@ -401,33 +337,51 @@ static int amd_xgbe_phy_pcs_power_cycle(struct phy_device *phydev) static void amd_xgbe_phy_serdes_start_ratechange(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; + u16 val, mask; + + /* Assert Rx and Tx ratechange in CMU_reg16 */ + val = XCMU_IOREAD(priv, CMU_REG16);
- /* Assert Rx and Tx ratechange */ - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, RATECHANGE, 1); + mask = (1 << (CMU_REG16_TX_RATE_CHANGE_BASE - + (priv->serdes_channel * CMU_REG16_RATE_CHANGE_DECR))) | + (1 << (CMU_REG16_RX_RATE_CHANGE_BASE - + (priv->serdes_channel * CMU_REG16_RATE_CHANGE_DECR))); + val |= mask; + + XCMU_IOWRITE(priv, CMU_REG16, val); }
static void amd_xgbe_phy_serdes_complete_ratechange(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; + u16 val, mask; unsigned int wait; - u16 status;
- /* Release Rx and Tx ratechange */ - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, RATECHANGE, 0); + /* Release Rx and Tx ratechange for proper channel in CMU_reg16 */ + val = XCMU_IOREAD(priv, CMU_REG16); + + mask = (1 << (CMU_REG16_TX_RATE_CHANGE_BASE - + (priv->serdes_channel * CMU_REG16_RATE_CHANGE_DECR))) | + (1 << (CMU_REG16_RX_RATE_CHANGE_BASE - + (priv->serdes_channel * CMU_REG16_RATE_CHANGE_DECR))); + val &= ~mask;
- /* Wait for Rx and Tx ready */ + XCMU_IOWRITE(priv, CMU_REG16, val); + + /* Wait for Rx and Tx ready in CMU_reg15 */ + mask = (1 << priv->serdes_channel) | + (1 << (priv->serdes_channel + 8)); wait = XGBE_PHY_RATECHANGE_COUNT; while (wait--) { - usleep_range(50, 75); + udelay(50);
- status = XSIR0_IOREAD(priv, SIR0_STATUS); - if (XSIR_GET_BITS(status, SIR0_STATUS, RX_READY) && - XSIR_GET_BITS(status, SIR0_STATUS, TX_READY)) + val = XCMU_IOREAD(priv, CMU_REG15); + if ((val & mask) == mask) return; }
netdev_dbg(phydev->attached_dev, "SerDes rx/tx not ready (%#hx)\n", - status); + val); }
static int amd_xgbe_phy_xgmii_mode(struct phy_device *phydev) @@ -435,8 +389,8 @@ static int amd_xgbe_phy_xgmii_mode(struct phy_device *phydev) struct amd_xgbe_phy_priv *priv = phydev->priv; int ret;
- /* Enable KR training */ - ret = amd_xgbe_an_enable_kr_training(phydev); + /* Disable KR training */ + ret = amd_xgbe_an_disable_kr_training(phydev); if (ret < 0) return ret;
@@ -462,19 +416,32 @@ static int amd_xgbe_phy_xgmii_mode(struct phy_device *phydev) return ret;
/* Set SerDes to 10G speed */ + spin_lock(&cmu_lock); + amd_xgbe_phy_serdes_start_ratechange(phydev);
- XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, DATARATE, SPEED_10000_RATE); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, WORDMODE, SPEED_10000_WORD); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, TXAMP, SPEED_10000_TXAMP); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PLLSEL, SPEED_10000_PLL); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PI_SPD_SEL, SPEED_10000_CDR); + XRXTX_IOWRITE_BITS(priv, RXTX_REG3, TX_DATA_RATE, RXTX_FULL_RATE); + XRXTX_IOWRITE_BITS(priv, RXTX_REG3, TX_WORD_MODE, RXTX_66BIT_WORD);
- XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, RXTX_10000_BLWC); - XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_10000_PQ); + XRXTX_IOWRITE_BITS(priv, RXTX_REG5, TXAMP_CNTL, RXTX_10G_TX_AMP); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG6, RX_DATA_RATE, RXTX_FULL_RATE); + XRXTX_IOWRITE_BITS(priv, RXTX_REG6, RX_WORD_MODE, RXTX_66BIT_WORD); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, 0); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, RX_PLLSELECT, RXTX_10G_PLL); + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, TX_PLLSELECT, RXTX_10G_PLL); + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, PI_SPD_SEL_CDR, RXTX_10G_CDR); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_10G_PQ);
amd_xgbe_phy_serdes_complete_ratechange(phydev);
+ spin_unlock(&cmu_lock); + + priv->mode = AMD_XGBE_MODE_KR; + return 0; }
@@ -510,19 +477,32 @@ static int amd_xgbe_phy_gmii_2500_mode(struct phy_device *phydev) return ret;
/* Set SerDes to 2.5G speed */ + spin_lock(&cmu_lock); + amd_xgbe_phy_serdes_start_ratechange(phydev);
- XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, DATARATE, SPEED_2500_RATE); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, WORDMODE, SPEED_2500_WORD); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, TXAMP, SPEED_2500_TXAMP); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PLLSEL, SPEED_2500_PLL); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PI_SPD_SEL, SPEED_2500_CDR); + XRXTX_IOWRITE_BITS(priv, RXTX_REG3, TX_DATA_RATE, RXTX_HALF_RATE); + XRXTX_IOWRITE_BITS(priv, RXTX_REG3, TX_WORD_MODE, RXTX_10BIT_WORD); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG5, TXAMP_CNTL, RXTX_1G_TX_AMP); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG6, RX_DATA_RATE, RXTX_HALF_RATE); + XRXTX_IOWRITE_BITS(priv, RXTX_REG6, RX_WORD_MODE, RXTX_10BIT_WORD);
- XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, RXTX_2500_BLWC); - XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_2500_PQ); + XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, 1); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, RX_PLLSELECT, RXTX_1G_PLL); + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, TX_PLLSELECT, RXTX_1G_PLL); + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, PI_SPD_SEL_CDR, RXTX_1G_CDR); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_1G_PQ);
amd_xgbe_phy_serdes_complete_ratechange(phydev);
+ spin_unlock(&cmu_lock); + + priv->mode = AMD_XGBE_MODE_KX; + return 0; }
@@ -558,47 +538,33 @@ static int amd_xgbe_phy_gmii_mode(struct phy_device *phydev) return ret;
/* Set SerDes to 1G speed */ + spin_lock(&cmu_lock); + amd_xgbe_phy_serdes_start_ratechange(phydev);
- XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, DATARATE, SPEED_1000_RATE); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, WORDMODE, SPEED_1000_WORD); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, TXAMP, SPEED_1000_TXAMP); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PLLSEL, SPEED_1000_PLL); - XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PI_SPD_SEL, SPEED_1000_CDR); + XRXTX_IOWRITE_BITS(priv, RXTX_REG3, TX_DATA_RATE, RXTX_FIFTH_RATE); + XRXTX_IOWRITE_BITS(priv, RXTX_REG3, TX_WORD_MODE, RXTX_10BIT_WORD);
- XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, RXTX_1000_BLWC); - XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_1000_PQ); + XRXTX_IOWRITE_BITS(priv, RXTX_REG5, TXAMP_CNTL, RXTX_1G_TX_AMP);
- amd_xgbe_phy_serdes_complete_ratechange(phydev); + XRXTX_IOWRITE_BITS(priv, RXTX_REG6, RX_DATA_RATE, RXTX_FIFTH_RATE); + XRXTX_IOWRITE_BITS(priv, RXTX_REG6, RX_WORD_MODE, RXTX_10BIT_WORD);
- return 0; -} + XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, 1);
-static int amd_xgbe_phy_cur_mode(struct phy_device *phydev, - enum amd_xgbe_phy_mode *mode) -{ - int ret; + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, RX_PLLSELECT, RXTX_1G_PLL); + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, TX_PLLSELECT, RXTX_1G_PLL); + XRXTX_IOWRITE_BITS(priv, RXTX_REG53, PI_SPD_SEL_CDR, RXTX_1G_CDR);
- ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); - if (ret < 0) - return ret; + XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_1G_PQ);
- if ((ret & MDIO_PCS_CTRL2_TYPE) == MDIO_PCS_CTRL2_10GBR) - *mode = AMD_XGBE_MODE_KR; - else - *mode = AMD_XGBE_MODE_KX; - - return 0; -} + amd_xgbe_phy_serdes_complete_ratechange(phydev);
-static bool amd_xgbe_phy_in_kr_mode(struct phy_device *phydev) -{ - enum amd_xgbe_phy_mode mode; + spin_unlock(&cmu_lock);
- if (amd_xgbe_phy_cur_mode(phydev, &mode)) - return false; + priv->mode = AMD_XGBE_MODE_KX;
- return (mode == AMD_XGBE_MODE_KR); + return 0; }
static int amd_xgbe_phy_switch_mode(struct phy_device *phydev) @@ -607,7 +573,7 @@ static int amd_xgbe_phy_switch_mode(struct phy_device *phydev) int ret;
/* If we are in KR switch to KX, and vice-versa */ - if (amd_xgbe_phy_in_kr_mode(phydev)) { + if (priv->mode == AMD_XGBE_MODE_KR) { if (priv->speed_set == AMD_XGBE_PHY_SPEEDSET_1000_10000) ret = amd_xgbe_phy_gmii_mode(phydev); else @@ -619,20 +585,15 @@ static int amd_xgbe_phy_switch_mode(struct phy_device *phydev) return ret; }
-static int amd_xgbe_phy_set_mode(struct phy_device *phydev, - enum amd_xgbe_phy_mode mode) +static enum amd_xgbe_phy_an amd_xgbe_an_switch_mode(struct phy_device *phydev) { - enum amd_xgbe_phy_mode cur_mode; int ret;
- ret = amd_xgbe_phy_cur_mode(phydev, &cur_mode); - if (ret) - return ret; - - if (mode != cur_mode) - ret = amd_xgbe_phy_switch_mode(phydev); + ret = amd_xgbe_phy_switch_mode(phydev); + if (ret < 0) + return AMD_XGBE_AN_ERROR;
- return ret; + return AMD_XGBE_AN_START; }
static enum amd_xgbe_phy_an amd_xgbe_an_tx_training(struct phy_device *phydev, @@ -643,8 +604,8 @@ static enum amd_xgbe_phy_an amd_xgbe_an_tx_training(struct phy_device *phydev,
*state = AMD_XGBE_RX_COMPLETE;
- /* If we're not in KR mode then we're done */ - if (!amd_xgbe_phy_in_kr_mode(phydev)) + /* If we're in KX mode then we're done */ + if (priv->mode == AMD_XGBE_MODE_KX) return AMD_XGBE_AN_EVENT;
/* Enable/Disable FEC */ @@ -672,13 +633,9 @@ static enum amd_xgbe_phy_an amd_xgbe_an_tx_training(struct phy_device *phydev, if (ret < 0) return AMD_XGBE_AN_ERROR;
- XSIR0_IOWRITE_BITS(priv, SIR0_KR_RT_1, RESET, 1); - ret |= 0x01; phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL, ret);
- XSIR0_IOWRITE_BITS(priv, SIR0_KR_RT_1, RESET, 0); - return AMD_XGBE_AN_EVENT; }
@@ -702,6 +659,7 @@ static enum amd_xgbe_phy_an amd_xgbe_an_tx_xnp(struct phy_device *phydev, static enum amd_xgbe_phy_an amd_xgbe_an_rx_bpa(struct phy_device *phydev, enum amd_xgbe_phy_rx *state) { + struct amd_xgbe_phy_priv *priv = phydev->priv; unsigned int link_support; int ret, ad_reg, lp_reg;
@@ -711,9 +669,9 @@ static enum amd_xgbe_phy_an amd_xgbe_an_rx_bpa(struct phy_device *phydev, return AMD_XGBE_AN_ERROR;
/* Check for a supported mode, otherwise restart in a different one */ - link_support = amd_xgbe_phy_in_kr_mode(phydev) ? 0x80 : 0x20; + link_support = (priv->mode == AMD_XGBE_MODE_KR) ? 0x80 : 0x20; if (!(ret & link_support)) - return AMD_XGBE_AN_INCOMPAT_LINK; + return amd_xgbe_an_switch_mode(phydev);
/* Check Extended Next Page support */ ad_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); @@ -754,7 +712,7 @@ static enum amd_xgbe_phy_an amd_xgbe_an_start(struct phy_device *phydev) int ret;
/* Be sure we aren't looping trying to negotiate */ - if (amd_xgbe_phy_in_kr_mode(phydev)) { + if (priv->mode == AMD_XGBE_MODE_KR) { if (priv->kr_state != AMD_XGBE_RX_READY) return AMD_XGBE_AN_NO_LINK; priv->kr_state = AMD_XGBE_RX_BPA; @@ -817,13 +775,6 @@ static enum amd_xgbe_phy_an amd_xgbe_an_start(struct phy_device *phydev) /* Enable and start auto-negotiation */ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0);
- ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_KR_CTRL); - if (ret < 0) - return AMD_XGBE_AN_ERROR; - - ret |= MDIO_KR_CTRL_PDETECT; - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_KR_CTRL, ret); - ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); if (ret < 0) return AMD_XGBE_AN_ERROR; @@ -864,8 +815,8 @@ static enum amd_xgbe_phy_an amd_xgbe_an_page_received(struct phy_device *phydev) enum amd_xgbe_phy_rx *state; int ret;
- state = amd_xgbe_phy_in_kr_mode(phydev) ? &priv->kr_state - : &priv->kx_state; + state = (priv->mode == AMD_XGBE_MODE_KR) ? &priv->kr_state + : &priv->kx_state;
switch (*state) { case AMD_XGBE_RX_BPA: @@ -885,13 +836,7 @@ static enum amd_xgbe_phy_an amd_xgbe_an_page_received(struct phy_device *phydev)
static enum amd_xgbe_phy_an amd_xgbe_an_incompat_link(struct phy_device *phydev) { - int ret; - - ret = amd_xgbe_phy_switch_mode(phydev); - if (ret) - return AMD_XGBE_AN_ERROR; - - return AMD_XGBE_AN_START; + return amd_xgbe_an_switch_mode(phydev); }
static void amd_xgbe_an_state_machine(struct work_struct *work) @@ -904,10 +849,6 @@ static void amd_xgbe_an_state_machine(struct work_struct *work) int sleep; unsigned int an_supported = 0;
- /* Start in KX mode */ - if (amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KX)) - priv->an_state = AMD_XGBE_AN_ERROR; - while (1) { mutex_lock(&priv->an_mutex);
@@ -915,9 +856,8 @@ static void amd_xgbe_an_state_machine(struct work_struct *work)
switch (priv->an_state) { case AMD_XGBE_AN_START: - an_supported = 0; - priv->parallel_detect = 0; priv->an_state = amd_xgbe_an_start(phydev); + an_supported = 0; break;
case AMD_XGBE_AN_EVENT: @@ -934,7 +874,6 @@ static void amd_xgbe_an_state_machine(struct work_struct *work) break;
case AMD_XGBE_AN_COMPLETE: - priv->parallel_detect = an_supported ? 0 : 1; netdev_info(phydev->attached_dev, "%s successful\n", an_supported ? "Auto negotiation" : "Parallel detection"); @@ -1069,6 +1008,7 @@ static int amd_xgbe_phy_config_aneg(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; u32 mmd_mask = phydev->c45_ids.devices_in_package; + int ret;
if (phydev->autoneg != AUTONEG_ENABLE) return amd_xgbe_phy_setup_forced(phydev); @@ -1077,6 +1017,11 @@ static int amd_xgbe_phy_config_aneg(struct phy_device *phydev) if (!(mmd_mask & MDIO_DEVS_AN)) return -EINVAL;
+ /* Get the current speed mode */ + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (ret < 0) + return ret; + /* Start/Restart the auto-negotiation state machine */ mutex_lock(&priv->an_mutex); priv->an_result = AMD_XGBE_AN_READY; @@ -1166,14 +1111,18 @@ static int amd_xgbe_phy_read_status(struct phy_device *phydev) { struct amd_xgbe_phy_priv *priv = phydev->priv; u32 mmd_mask = phydev->c45_ids.devices_in_package; - int ret, ad_ret, lp_ret; + int ret, mode, ad_ret, lp_ret;
ret = amd_xgbe_phy_update_link(phydev); if (ret) return ret;
- if ((phydev->autoneg == AUTONEG_ENABLE) && - !priv->parallel_detect) { + mode = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (mode < 0) + return mode; + mode &= MDIO_PCS_CTRL2_TYPE; + + if (phydev->autoneg == AUTONEG_ENABLE) { if (!(mmd_mask & MDIO_DEVS_AN)) return -EINVAL;
@@ -1204,39 +1153,40 @@ static int amd_xgbe_phy_read_status(struct phy_device *phydev) ad_ret &= lp_ret; if (ad_ret & 0x80) { phydev->speed = SPEED_10000; - ret = amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KR); - if (ret) - return ret; + if (mode != MDIO_PCS_CTRL2_10GBR) { + ret = amd_xgbe_phy_xgmii_mode(phydev); + if (ret < 0) + return ret; + } } else { - switch (priv->speed_set) { - case AMD_XGBE_PHY_SPEEDSET_1000_10000: - phydev->speed = SPEED_1000; - break; + int (*mode_fcn)(struct phy_device *);
- case AMD_XGBE_PHY_SPEEDSET_2500_10000: + if (priv->speed_set == + AMD_XGBE_PHY_SPEEDSET_1000_10000) { + phydev->speed = SPEED_1000; + mode_fcn = amd_xgbe_phy_gmii_mode; + } else { phydev->speed = SPEED_2500; - break; + mode_fcn = amd_xgbe_phy_gmii_2500_mode; }
- ret = amd_xgbe_phy_set_mode(phydev, AMD_XGBE_MODE_KX); - if (ret) - return ret; + if (mode == MDIO_PCS_CTRL2_10GBR) { + ret = mode_fcn(phydev); + if (ret < 0) + return ret; + } }
phydev->duplex = DUPLEX_FULL; } else { - if (amd_xgbe_phy_in_kr_mode(phydev)) { + if (mode == MDIO_PCS_CTRL2_10GBR) { phydev->speed = SPEED_10000; } else { - switch (priv->speed_set) { - case AMD_XGBE_PHY_SPEEDSET_1000_10000: + if (priv->speed_set == + AMD_XGBE_PHY_SPEEDSET_1000_10000) phydev->speed = SPEED_1000; - break; - - case AMD_XGBE_PHY_SPEEDSET_2500_10000: + else phydev->speed = SPEED_2500; - break; - } } phydev->duplex = DUPLEX_FULL; phydev->pause = 0; @@ -1331,21 +1281,33 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) goto err_priv; }
- priv->sir0_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - priv->sir0_regs = devm_ioremap_resource(dev, priv->sir0_res); - if (IS_ERR(priv->sir0_regs)) { - dev_err(dev, "sir0 ioremap failed\n"); - ret = PTR_ERR(priv->sir0_regs); + /* All xgbe phy devices share the CMU registers so retrieve + * the resource and do the ioremap directly rather than + * the devm_ioremap_resource call + */ + priv->cmu_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!priv->cmu_res) { + dev_err(dev, "cmu invalid resource\n"); + ret = -EINVAL; + goto err_rxtx; + } + priv->cmu_regs = devm_ioremap_nocache(dev, priv->cmu_res->start, + resource_size(priv->cmu_res)); + if (!priv->cmu_regs) { + dev_err(dev, "cmu ioremap failed\n"); + ret = -ENOMEM; goto err_rxtx; }
- priv->sir1_res = platform_get_resource(pdev, IORESOURCE_MEM, 2); - priv->sir1_regs = devm_ioremap_resource(dev, priv->sir1_res); - if (IS_ERR(priv->sir1_regs)) { - dev_err(dev, "sir1 ioremap failed\n"); - ret = PTR_ERR(priv->sir1_regs); - goto err_sir0; + /* Get the device serdes channel property */ + property = of_get_property(dev->of_node, XGBE_PHY_CHANNEL_PROPERTY, + NULL); + if (!property) { + dev_err(dev, "unable to obtain serdes_channel property\n"); + ret = -EINVAL; + goto err_cmu; } + priv->serdes_channel = be32_to_cpu(*property);
/* Get the device speed set property */ speed_set = 0; @@ -1364,17 +1326,25 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) default: dev_err(dev, "invalid amd,speed-set property\n"); ret = -EINVAL; - goto err_sir1; + goto err_cmu; }
priv->link = 1;
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (ret < 0) + goto err_cmu; + if ((ret & MDIO_PCS_CTRL2_TYPE) == MDIO_PCS_CTRL2_10GBR) + priv->mode = AMD_XGBE_MODE_KR; + else + priv->mode = AMD_XGBE_MODE_KX; + mutex_init(&priv->an_mutex); INIT_WORK(&priv->an_work, amd_xgbe_an_state_machine); priv->an_workqueue = create_singlethread_workqueue(wq_name); if (!priv->an_workqueue) { ret = -ENOMEM; - goto err_sir1; + goto err_cmu; }
phydev->priv = priv; @@ -1384,15 +1354,8 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev)
return 0;
-err_sir1: - devm_iounmap(dev, priv->sir1_regs); - devm_release_mem_region(dev, priv->sir1_res->start, - resource_size(priv->sir1_res)); - -err_sir0: - devm_iounmap(dev, priv->sir0_regs); - devm_release_mem_region(dev, priv->sir0_res->start, - resource_size(priv->sir0_res)); +err_cmu: + devm_iounmap(dev, priv->cmu_regs);
err_rxtx: devm_iounmap(dev, priv->rxtx_regs); @@ -1424,14 +1387,7 @@ static void amd_xgbe_phy_remove(struct phy_device *phydev) flush_workqueue(priv->an_workqueue); destroy_workqueue(priv->an_workqueue);
- /* Release resources */ - devm_iounmap(dev, priv->sir1_regs); - devm_release_mem_region(dev, priv->sir1_res->start, - resource_size(priv->sir1_res)); - - devm_iounmap(dev, priv->sir0_regs); - devm_release_mem_region(dev, priv->sir0_res->start, - resource_size(priv->sir0_res)); + devm_iounmap(dev, priv->cmu_regs);
devm_iounmap(dev, priv->rxtx_regs); devm_release_mem_region(dev, priv->rxtx_res->start,
From: Mika Westerberg mika.westerberg@linux.intel.com
Device Tree is used in many embedded systems to describe the system configuration to the OS. It supports attaching properties or name-value pairs to the devices it describe. With these properties one can pass additional information to the drivers that would not be available otherwise.
ACPI is another configuration mechanism (among other things) typically seen, but not limited to, x86 machines. ACPI allows passing arbitrary data from methods but there has not been mechanism equivalent to Device Tree until the introduction of _DSD in the recent publication of the ACPI 5.1 specification.
In order to facilitate ACPI usage in systems where Device Tree is typically used, it would be beneficial to standardize a way to retrieve Device Tree style properties from ACPI devices, which is what we do in this patch.
If a given device described in ACPI namespace wants to export properties it must implement _DSD method (Device Specific Data, introduced with ACPI 5.1) that returns the properties in a package of packages. For example:
Name (_DSD, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () {"name1", <VALUE1>}, Package () {"name2", <VALUE2>}, ... } })
The UUID reserved for properties is daffd814-6eba-4d8c-8a91-bc9bbf4aa301 and is documented in the ACPI 5.1 companion document called "_DSD Implementation Guide" [1], [2].
We add several helper functions that can be used to extract these properties and convert them to different Linux data types.
The ultimate goal is that we only have one device property API that retrieves the requested properties from Device Tree or from ACPI transparent to the caller.
[1] http://www.uefi.org/sites/default/files/resources/_DSD-implementation-guide-... [2] http://www.uefi.org/sites/default/files/resources/_DSD-device-properties-UUI...
Reviewed-by: Hanjun Guo hanjun.guo@linaro.org Reviewed-by: Josh Triplett josh@joshtriplett.org Reviewed-by: Grant Likely grant.likely@linaro.org Signed-off-by: Darren Hart dvhart@linux.intel.com Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/Makefile | 1 + drivers/acpi/internal.h | 6 + drivers/acpi/property.c | 364 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/acpi/scan.c | 2 + include/acpi/acpi_bus.h | 7 + include/linux/acpi.h | 40 ++++++ 6 files changed, 420 insertions(+) create mode 100644 drivers/acpi/property.c
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index f96a2f1..5a21476 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -51,6 +51,7 @@ acpi-y += int340x_thermal.o acpi-y += power.o acpi-y += event.o acpi-y += sysfs.o +acpi-y += property.o acpi-$(CONFIG_X86) += acpi_cmos_rtc.o acpi-$(CONFIG_DEBUG_FS) += debugfs.o acpi-$(CONFIG_ACPI_NUMA) += numa.o diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index ca0ccb4..c5ff8ba 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -178,4 +178,10 @@ static inline void suspend_nvs_restore(void) {} bool acpi_osi_is_win8(void); #endif
+/*-------------------------------------------------------------------------- + Device properties + -------------------------------------------------------------------------- */ +void acpi_init_properties(struct acpi_device *adev); +void acpi_free_properties(struct acpi_device *adev); + #endif /* _ACPI_INTERNAL_H_ */ diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c new file mode 100644 index 0000000..c4a3e80 --- /dev/null +++ b/drivers/acpi/property.c @@ -0,0 +1,364 @@ +/* + * ACPI device specific properties support. + * + * Copyright (C) 2014, Intel Corporation + * All rights reserved. + * + * Authors: Mika Westerberg mika.westerberg@linux.intel.com + * Darren Hart dvhart@linux.intel.com + * Rafael J. Wysocki rafael.j.wysocki@intel.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/export.h> + +#include "internal.h" + +/* ACPI _DSD device properties UUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ +static const u8 prp_uuid[16] = { + 0x14, 0xd8, 0xff, 0xda, 0xba, 0x6e, 0x8c, 0x4d, + 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01 +}; + +static bool acpi_property_value_ok(const union acpi_object *value) +{ + int j; + + /* + * The value must be an integer, a string, a reference, or a package + * whose every element must be an integer, a string, or a reference. + */ + switch (value->type) { + case ACPI_TYPE_INTEGER: + case ACPI_TYPE_STRING: + case ACPI_TYPE_LOCAL_REFERENCE: + return true; + + case ACPI_TYPE_PACKAGE: + for (j = 0; j < value->package.count; j++) + switch (value->package.elements[j].type) { + case ACPI_TYPE_INTEGER: + case ACPI_TYPE_STRING: + case ACPI_TYPE_LOCAL_REFERENCE: + continue; + + default: + return false; + } + + return true; + } + return false; +} + +static bool acpi_properties_format_valid(const union acpi_object *properties) +{ + int i; + + for (i = 0; i < properties->package.count; i++) { + const union acpi_object *property; + + property = &properties->package.elements[i]; + /* + * Only two elements allowed, the first one must be a string and + * the second one has to satisfy certain conditions. + */ + if (property->package.count != 2 + || property->package.elements[0].type != ACPI_TYPE_STRING + || !acpi_property_value_ok(&property->package.elements[1])) + return false; + } + return true; +} + +void acpi_init_properties(struct acpi_device *adev) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + const union acpi_object *desc; + acpi_status status; + int i; + + status = acpi_evaluate_object_typed(adev->handle, "_DSD", NULL, &buf, + ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) + return; + + desc = buf.pointer; + if (desc->package.count % 2) + goto fail; + + /* Look for the device properties UUID. */ + for (i = 0; i < desc->package.count; i += 2) { + const union acpi_object *uuid, *properties; + + uuid = &desc->package.elements[i]; + properties = &desc->package.elements[i + 1]; + + /* + * The first element must be a UUID and the second one must be + * a package. + */ + if (uuid->type != ACPI_TYPE_BUFFER || uuid->buffer.length != 16 + || properties->type != ACPI_TYPE_PACKAGE) + break; + + if (memcmp(uuid->buffer.pointer, prp_uuid, sizeof(prp_uuid))) + continue; + + /* + * We found the matching UUID. Now validate the format of the + * package immediately following it. + */ + if (!acpi_properties_format_valid(properties)) + break; + + adev->data.pointer = buf.pointer; + adev->data.properties = properties; + return; + } + + fail: + dev_warn(&adev->dev, "Returned _DSD data is not valid, skipping\n"); + ACPI_FREE(buf.pointer); +} + +void acpi_free_properties(struct acpi_device *adev) +{ + ACPI_FREE((void *)adev->data.pointer); + adev->data.pointer = NULL; + adev->data.properties = NULL; +} + +/** + * acpi_dev_get_property - return an ACPI property with given name + * @adev: ACPI device to get property + * @name: Name of the property + * @type: Expected property type + * @obj: Location to store the property value (if not %NULL) + * + * Look up a property with @name and store a pointer to the resulting ACPI + * object at the location pointed to by @obj if found. + * + * Callers must not attempt to free the returned objects. These objects will be + * freed by the ACPI core automatically during the removal of @adev. + * + * Return: %0 if property with @name has been found (success), + * %-EINVAL if the arguments are invalid, + * %-ENODATA if the property doesn't exist, + * %-EPROTO if the property value type doesn't match @type. + */ +int acpi_dev_get_property(struct acpi_device *adev, const char *name, + acpi_object_type type, const union acpi_object **obj) +{ + const union acpi_object *properties; + int i; + + if (!adev || !name) + return -EINVAL; + + if (!adev->data.pointer || !adev->data.properties) + return -ENODATA; + + properties = adev->data.properties; + for (i = 0; i < properties->package.count; i++) { + const union acpi_object *propname, *propvalue; + const union acpi_object *property; + + property = &properties->package.elements[i]; + + propname = &property->package.elements[0]; + propvalue = &property->package.elements[1]; + + if (!strcmp(name, propname->string.pointer)) { + if (type != ACPI_TYPE_ANY && propvalue->type != type) + return -EPROTO; + else if (obj) + *obj = propvalue; + + return 0; + } + } + return -ENODATA; +} +EXPORT_SYMBOL_GPL(acpi_dev_get_property); + +/** + * acpi_dev_get_property_array - return an ACPI array property with given name + * @adev: ACPI device to get property + * @name: Name of the property + * @type: Expected type of array elements + * @obj: Location to store a pointer to the property value (if not NULL) + * + * Look up an array property with @name and store a pointer to the resulting + * ACPI object at the location pointed to by @obj if found. + * + * Callers must not attempt to free the returned objects. Those objects will be + * freed by the ACPI core automatically during the removal of @adev. + * + * Return: %0 if array property (package) with @name has been found (success), + * %-EINVAL if the arguments are invalid, + * %-ENODATA if the property doesn't exist, + * %-EPROTO if the property is not a package or the type of its elements + * doesn't match @type. + */ +int acpi_dev_get_property_array(struct acpi_device *adev, const char *name, + acpi_object_type type, + const union acpi_object **obj) +{ + const union acpi_object *prop; + int ret, i; + + ret = acpi_dev_get_property(adev, name, ACPI_TYPE_PACKAGE, &prop); + if (ret) + return ret; + + if (type != ACPI_TYPE_ANY) { + /* Check that all elements are of correct type. */ + for (i = 0; i < prop->package.count; i++) + if (prop->package.elements[i].type != type) + return -EPROTO; + } + if (obj) + *obj = prop; + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_dev_get_property_array); + +/** + * acpi_dev_get_property_reference - returns handle to the referenced object + * @adev: ACPI device to get property + * @name: Name of the property + * @size_prop: Name of the "size" property in referenced object + * @index: Index of the reference to return + * @args: Location to store the returned reference with optional arguments + * + * Find property with @name, verifify that it is a package containing at least + * one object reference and if so, store the ACPI device object pointer to the + * target object in @args->adev. + * + * If the reference includes arguments (@size_prop is not %NULL) follow the + * reference and check whether or not there is an integer property @size_prop + * under the target object and if so, whether or not its value matches the + * number of arguments that follow the reference. If there's more than one + * reference in the property value package, @index is used to select the one to + * return. + * + * Return: %0 on success, negative error code on failure. + */ +int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, + const char *size_prop, size_t index, + struct acpi_reference_args *args) +{ + const union acpi_object *element, *end; + const union acpi_object *obj; + struct acpi_device *device; + int ret, idx = 0; + + ret = acpi_dev_get_property(adev, name, ACPI_TYPE_ANY, &obj); + if (ret) + return ret; + + /* + * The simplest case is when the value is a single reference. Just + * return that reference then. + */ + if (obj->type == ACPI_TYPE_LOCAL_REFERENCE) { + if (size_prop || index) + return -EINVAL; + + ret = acpi_bus_get_device(obj->reference.handle, &device); + if (ret) + return ret; + + args->adev = device; + args->nargs = 0; + return 0; + } + + /* + * If it is not a single reference, then it is a package of + * references followed by number of ints as follows: + * + * Package () { REF, INT, REF, INT, INT } + * + * The index argument is then used to determine which reference + * the caller wants (along with the arguments). + */ + if (obj->type != ACPI_TYPE_PACKAGE || index >= obj->package.count) + return -EPROTO; + + element = obj->package.elements; + end = element + obj->package.count; + + while (element < end) { + u32 nargs, i; + + if (element->type != ACPI_TYPE_LOCAL_REFERENCE) + return -EPROTO; + + ret = acpi_bus_get_device(element->reference.handle, &device); + if (ret) + return -ENODEV; + + element++; + nargs = 0; + + if (size_prop) { + const union acpi_object *prop; + + /* + * Find out how many arguments the refenced object + * expects by reading its size_prop property. + */ + ret = acpi_dev_get_property(device, size_prop, + ACPI_TYPE_INTEGER, &prop); + if (ret) + return ret; + + nargs = prop->integer.value; + if (nargs > MAX_ACPI_REFERENCE_ARGS + || element + nargs > end) + return -EPROTO; + + /* + * Skip to the start of the arguments and verify + * that they all are in fact integers. + */ + for (i = 0; i < nargs; i++) + if (element[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + } else { + /* assume following integer elements are all args */ + for (i = 0; element + i < end; i++) { + int type = element[i].type; + + if (type == ACPI_TYPE_INTEGER) + nargs++; + else if (type == ACPI_TYPE_LOCAL_REFERENCE) + break; + else + return -EPROTO; + } + } + + if (idx++ == index) { + args->adev = device; + args->nargs = nargs; + for (i = 0; i < nargs; i++) + args->args[i] = element[i].integer.value; + + return 0; + } + + element += nargs; + } + + return -EPROTO; +} +EXPORT_SYMBOL_GPL(acpi_dev_get_property_reference); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index d670158..6186a99 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -884,6 +884,7 @@ static void acpi_device_release(struct device *dev) { struct acpi_device *acpi_dev = to_acpi_device(dev);
+ acpi_free_properties(acpi_dev); acpi_free_pnp_ids(&acpi_dev->pnp); acpi_free_power_resources_lists(acpi_dev); kfree(acpi_dev); @@ -1888,6 +1889,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, acpi_set_device_status(device, sta); acpi_device_get_busid(device); acpi_set_pnp_ids(handle, &device->pnp, type); + acpi_init_properties(device); acpi_bus_get_flags(device); device->flags.match_driver = false; device->flags.initialized = true; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index f34a083..4757811 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -337,6 +337,12 @@ struct acpi_device_physical_node { bool put_online:1; };
+/* ACPI Device Specific Data (_DSD) */ +struct acpi_device_data { + const union acpi_object *pointer; + const union acpi_object *properties; +}; + /* Device */ struct acpi_device { int device_type; @@ -353,6 +359,7 @@ struct acpi_device { struct acpi_device_wakeup wakeup; struct acpi_device_perf performance; struct acpi_device_dir dir; + struct acpi_device_data data; struct acpi_scan_handler *handler; struct acpi_hotplug_context *hp; struct acpi_driver *driver; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 5a92d49..f3c1b07 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -664,4 +664,44 @@ do { \ #endif #endif
+/* Device properties */ + +#define MAX_ACPI_REFERENCE_ARGS 8 +struct acpi_reference_args { + struct acpi_device *adev; + size_t nargs; + u64 args[MAX_ACPI_REFERENCE_ARGS]; +}; + +#ifdef CONFIG_ACPI +int acpi_dev_get_property(struct acpi_device *adev, const char *name, + acpi_object_type type, const union acpi_object **obj); +int acpi_dev_get_property_array(struct acpi_device *adev, const char *name, + acpi_object_type type, + const union acpi_object **obj); +int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, + const char *cells_name, size_t index, + struct acpi_reference_args *args); +#else +static inline int acpi_dev_get_property(struct acpi_device *adev, + const char *name, acpi_object_type type, + const union acpi_object **obj) +{ + return -ENXIO; +} +static inline int acpi_dev_get_property_array(struct acpi_device *adev, + const char *name, + acpi_object_type type, + const union acpi_object **obj) +{ + return -ENXIO; +} +static inline int acpi_dev_get_property_reference(struct acpi_device *adev, + const char *name, const char *cells_name, + size_t index, struct acpi_reference_args *args) +{ + return -ENXIO; +} +#endif + #endif /*_LINUX_ACPI_H*/
On Wednesday 05 November 2014 20:32:33 al.stone@linaro.org wrote:
From: Mika Westerberg mika.westerberg@linux.intel.com
Device Tree is used in many embedded systems to describe the system configuration to the OS. It supports attaching properties or name-value pairs to the devices it describe. With these properties one can pass additional information to the drivers that would not be available otherwise.
You have the entire series in this branch, can you say which ones are actually used on Seattle? I'd assume that for instance the GPIO accessors don't apply here, only the trivial properties.
Arnd
On 11/06/2014 03:17 AM, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:33 al.stone@linaro.org wrote:
From: Mika Westerberg mika.westerberg@linux.intel.com
Device Tree is used in many embedded systems to describe the system configuration to the OS. It supports attaching properties or name-value pairs to the devices it describe. With these properties one can pass additional information to the drivers that would not be available otherwise.
You have the entire series in this branch, can you say which ones are actually used on Seattle? I'd assume that for instance the GPIO accessors don't apply here, only the trivial properties.
Arnd
Only the first few, as noted in the cover letter.
From: "Rafael J. Wysocki" rafael.j.wysocki@intel.com
Add a uniform interface by which device drivers can request device properties from the platform firmware by providing a property name and the corresponding data type. The purpose of it is to help to write portable code that won't depend on any particular platform firmware interface.
The following general helper functions are added:
device_property_present() device_property_read_u8() device_property_read_u16() device_property_read_u32() device_property_read_u64() device_property_read_string() device_property_read_u8_array() device_property_read_u16_array() device_property_read_u32_array() device_property_read_u64_array() device_property_read_string_array()
The first one allows the caller to check if the given property is present. The next 5 of them allow single-valued properties of various types to be retrieved in a uniform way. The remaining 5 are for reading properties with multiple values (arrays of either numbers or strings).
The interface covers both ACPI and Device Trees.
This change set includes material from Mika Westerberg and Aaron Lu.
Signed-off-by: Aaron Lu aaron.lu@intel.com Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/property.c | 169 +++++++++++++++++++++++++++++ drivers/base/Makefile | 2 +- drivers/base/property.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/of/base.c | 102 +++++++++++++++--- include/linux/acpi.h | 32 ++++++ include/linux/of.h | 22 ++++ include/linux/property.h | 53 +++++++++ 7 files changed, 641 insertions(+), 15 deletions(-) create mode 100644 drivers/base/property.c create mode 100644 include/linux/property.h
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index c4a3e80..b586b58 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -362,3 +362,172 @@ int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, return -EPROTO; } EXPORT_SYMBOL_GPL(acpi_dev_get_property_reference); + +int acpi_dev_prop_get(struct acpi_device *adev, const char *propname, + void **valptr) +{ + return acpi_dev_get_property(adev, propname, ACPI_TYPE_ANY, + (const union acpi_object **)valptr); +} + +int acpi_dev_prop_read(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val) +{ + const union acpi_object *obj; + int ret = -EINVAL; + + if (!val) + return -EINVAL; + + if (proptype >= DEV_PROP_U8 && proptype <= DEV_PROP_U64) { + ret = acpi_dev_get_property(adev, propname, ACPI_TYPE_INTEGER, &obj); + if (ret) + return ret; + + switch (proptype) { + case DEV_PROP_U8: + if (obj->integer.value > U8_MAX) + return -EOVERFLOW; + *(u8 *)val = obj->integer.value; + break; + case DEV_PROP_U16: + if (obj->integer.value > U16_MAX) + return -EOVERFLOW; + *(u16 *)val = obj->integer.value; + break; + case DEV_PROP_U32: + if (obj->integer.value > U32_MAX) + return -EOVERFLOW; + *(u32 *)val = obj->integer.value; + break; + default: + *(u64 *)val = obj->integer.value; + break; + } + } else if (proptype == DEV_PROP_STRING) { + ret = acpi_dev_get_property(adev, propname, ACPI_TYPE_STRING, &obj); + if (ret) + return ret; + + *(char **)val = obj->string.pointer; + } + return ret; +} + +static int acpi_copy_property_array_u8(const union acpi_object *items, u8 *val, + size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + if (items[i].integer.value > U8_MAX) + return -EOVERFLOW; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_u16(const union acpi_object *items, + u16 *val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + if (items[i].integer.value > U16_MAX) + return -EOVERFLOW; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_u32(const union acpi_object *items, + u32 *val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + if (items[i].integer.value > U32_MAX) + return -EOVERFLOW; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_u64(const union acpi_object *items, + u64 *val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_string(const union acpi_object *items, + char **val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_STRING) + return -EPROTO; + + val[i] = items[i].string.pointer; + } + return 0; +} + +int acpi_dev_prop_read_array(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val, + size_t nval) +{ + const union acpi_object *obj; + const union acpi_object *items; + int ret; + + ret = acpi_dev_get_property_array(adev, propname, ACPI_TYPE_ANY, &obj); + if (ret) + return ret; + + if (!val) + return obj->package.count; + + if (nval > obj->package.count) + return -EOVERFLOW; + + items = obj->package.elements; + switch (proptype) { + case DEV_PROP_U8: + ret = acpi_copy_property_array_u8(items, (u8 *)val, nval); + break; + case DEV_PROP_U16: + ret = acpi_copy_property_array_u16(items, (u16 *)val, nval); + break; + case DEV_PROP_U32: + ret = acpi_copy_property_array_u32(items, (u32 *)val, nval); + break; + case DEV_PROP_U64: + ret = acpi_copy_property_array_u64(items, (u64 *)val, nval); + break; + case DEV_PROP_STRING: + ret = acpi_copy_property_array_string(items, (char **)val, nval); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 6922cd6..53c3fe1 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -4,7 +4,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o + topology.o container.o property.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ diff --git a/drivers/base/property.c b/drivers/base/property.c new file mode 100644 index 0000000..514ca66 --- /dev/null +++ b/drivers/base/property.c @@ -0,0 +1,276 @@ +/* + * property.c - Unified device property interface. + * + * Copyright (C) 2014, Intel Corporation + * Authors: Rafael J. Wysocki rafael.j.wysocki@intel.com + * Mika Westerberg mika.westerberg@linux.intel.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/property.h> +#include <linux/export.h> +#include <linux/acpi.h> +#include <linux/of.h> + +/** + * device_property_present - check if a property of a device is present + * @dev: Device whose property is being checked + * @propname: Name of the property + * + * Check if property @propname is present in the device firmware description. + */ +bool device_property_present(struct device *dev, const char *propname) +{ + if (IS_ENABLED(CONFIG_OF) && dev->of_node) + return of_property_read_bool(dev->of_node, propname); + + return !acpi_dev_prop_get(ACPI_COMPANION(dev), propname, NULL); +} +EXPORT_SYMBOL_GPL(device_property_present); + +#define DEVICE_PROPERTY_READ(_dev_, _propname_, _type_, _proptype_, _val_) \ + IS_ENABLED(CONFIG_OF) && _dev_->of_node ? \ + of_property_read_##_type_(_dev_->of_node, _propname_, _val_) : \ + acpi_dev_prop_read(ACPI_COMPANION(_dev_), _propname_, \ + _proptype_, _val_) + +/** + * device_property_read_u8 - return a u8 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u8. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u8, + * %-EOVERFLOW if the property value is out of bounds of u8. + */ +int device_property_read_u8(struct device *dev, const char *propname, u8 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u8, DEV_PROP_U8, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u8); + +/** + * device_property_read_u16 - return a u16 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u16. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u16, + * %-EOVERFLOW if the property value is out of bounds of u16. + */ +int device_property_read_u16(struct device *dev, const char *propname, u16 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u16, DEV_PROP_U16, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u16); + +/** + * device_property_read_u32 - return a u32 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u32. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u32, + * %-EOVERFLOW if the property value is out of bounds of u32. + */ +int device_property_read_u32(struct device *dev, const char *propname, u32 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u32, DEV_PROP_U32, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u32); + +/** + * device_property_read_u64 - return a u64 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u64. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u64, + * %-EOVERFLOW if the property value is out of bounds of u64. + */ +int device_property_read_u64(struct device *dev, const char *propname, u64 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u64, DEV_PROP_U64, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u64); + +/** + * device_property_read_string - return a string property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be a string. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not a string. + */ +int device_property_read_string(struct device *dev, const char *propname, + const char **val) +{ + return DEVICE_PROPERTY_READ(dev, propname, string, DEV_PROP_STRING, val); +} +EXPORT_SYMBOL_GPL(device_property_read_string); + +#define OF_DEV_PROP_READ_ARRAY(node, propname, type, val, nval) \ + (val) ? of_property_read_##type##_array((node), (propname), (val), (nval)) \ + : of_property_count_elems_of_size((node), (propname), sizeof(type)) + +#define DEVICE_PROPERTY_READ_ARRAY(_dev_, _propname_, _type_, _proptype_, _val_, _nval_) \ + IS_ENABLED(CONFIG_OF) && _dev_->of_node ? \ + (OF_DEV_PROP_READ_ARRAY(_dev_->of_node, _propname_, _type_, \ + _val_, _nval_)) : \ + acpi_dev_prop_read_array(ACPI_COMPANION(_dev_), _propname_, \ + _proptype_, _val_, _nval_) + +/** + * device_property_read_u8_array - return a u8 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u8 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u8_array(struct device *dev, const char *propname, + u8 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u8, DEV_PROP_U8, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u8_array); + +/** + * device_property_read_u16_array - return a u16 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u16 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u16_array(struct device *dev, const char *propname, + u16 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u16, DEV_PROP_U16, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u16_array); + +/** + * device_property_read_u32_array - return a u32 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u32 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u32_array(struct device *dev, const char *propname, + u32 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u32, DEV_PROP_U32, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u32_array); + +/** + * device_property_read_u64_array - return a u64 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u64 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u64_array(struct device *dev, const char *propname, + u64 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u64, DEV_PROP_U64, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u64_array); + +/** + * device_property_read_string_array - return a string array property of device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of string properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property is not an array of strings, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_string_array(struct device *dev, const char *propname, + char **val, size_t nval) +{ + return IS_ENABLED(CONFIG_OF) && dev->of_node ? + of_property_read_string_array(dev->of_node, propname, val, nval) : + acpi_dev_prop_read_array(ACPI_COMPANION(dev), propname, + DEV_PROP_STRING, val, nval); +} +EXPORT_SYMBOL_GPL(device_property_read_string_array); diff --git a/drivers/of/base.c b/drivers/of/base.c index 2305dc0..74ab1b8 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1250,6 +1250,39 @@ int of_property_read_u64(const struct device_node *np, const char *propname, EXPORT_SYMBOL_GPL(of_property_read_u64);
/** + * of_property_read_u64_array - Find and read an array of 64 bit integers + * from a property. + * + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @out_values: pointer to return value, modified only if return value is 0. + * @sz: number of array elements to read + * + * Search for a property in a device node and read 64-bit value(s) from + * it. Returns 0 on success, -EINVAL if the property does not exist, + * -ENODATA if property does not have a value, and -EOVERFLOW if the + * property data isn't large enough. + * + * The out_values is modified only if a valid u64 value can be decoded. + */ +int of_property_read_u64_array(const struct device_node *np, + const char *propname, u64 *out_values, + size_t sz) +{ + const __be32 *val = of_find_property_value_of_size(np, propname, + (sz * sizeof(*out_values))); + + if (IS_ERR(val)) + return PTR_ERR(val); + + while (sz--) { + *out_values++ = of_read_number(val, 2); + val += 2; + } + return 0; +} + +/** * of_property_read_string - Find and read a string from a property * @np: device node from which the property value is to be read. * @propname: name of the property to be searched. @@ -1362,6 +1395,27 @@ int of_property_match_string(struct device_node *np, const char *propname, } EXPORT_SYMBOL_GPL(of_property_match_string);
+static int property_count_strings(struct property *prop) +{ + const char *p; + size_t l = 0, total = 0; + int i = 0; + + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + if (strnlen(prop->value, prop->length) >= prop->length) + return -EILSEQ; + + p = prop->value; + + for (i = 0; total < prop->length; total += l, p += l, i++) + l = strlen(p) + 1; + + return i; +} + /** * of_property_count_strings - Find and return the number of strings from a * multiple strings property. @@ -1376,26 +1430,46 @@ EXPORT_SYMBOL_GPL(of_property_match_string); */ int of_property_count_strings(struct device_node *np, const char *propname) { + return property_count_strings(of_find_property(np, propname, NULL)); +} +EXPORT_SYMBOL_GPL(of_property_count_strings); + +/** + * of_property_read_string_array - Read an array of strings from a multiple + * strings property. + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @out_strs: output array of string pointers. + * @sz: number of array elements to read. + * + * Search for a property in a device tree node and retrieve a list of + * terminated string values (pointer to data, not a copy) in that property. + * + * If @out_strs is NULL, the number of strings in the property is returned. + */ +int of_property_read_string_array(struct device_node *np, const char *propname, + char **out_strs, size_t sz) +{ struct property *prop = of_find_property(np, propname, NULL); - int i = 0; - size_t l = 0, total = 0; - const char *p; + char *p = prop->value; + size_t total = 0; + int i;
- if (!prop) - return -EINVAL; - if (!prop->value) - return -ENODATA; - if (strnlen(prop->value, prop->length) >= prop->length) - return -EILSEQ; + i = property_count_strings(prop); + if (!out_strs || i < 0) + return i;
- p = prop->value; + if (i < sz) + return -EOVERFLOW;
- for (i = 0; total < prop->length; total += l, p += l, i++) - l = strlen(p) + 1; + while (total < prop->length && i < sz) { + size_t l = strlen(p) + 1;
- return i; + out_strs[i++] = p; + total += l; + p += l; + } } -EXPORT_SYMBOL_GPL(of_property_count_strings);
void of_print_phandle_args(const char *msg, const struct of_phandle_args *args) { diff --git a/include/linux/acpi.h b/include/linux/acpi.h index f3c1b07..6eb7c40 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -28,6 +28,7 @@ #include <linux/errno.h> #include <linux/ioport.h> /* for struct resource */ #include <linux/device.h> +#include <linux/property.h>
#ifndef _LINUX #define _LINUX @@ -682,6 +683,14 @@ int acpi_dev_get_property_array(struct acpi_device *adev, const char *name, int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, const char *cells_name, size_t index, struct acpi_reference_args *args); + +int acpi_dev_prop_get(struct acpi_device *adev, const char *propname, + void **valptr); +int acpi_dev_prop_read(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val); +int acpi_dev_prop_read_array(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val, + size_t nval); #else static inline int acpi_dev_get_property(struct acpi_device *adev, const char *name, acpi_object_type type, @@ -702,6 +711,29 @@ static inline int acpi_dev_get_property_reference(struct acpi_device *adev, { return -ENXIO; } + +static inline int acpi_dev_prop_get(struct acpi_device *adev, + const char *propname, + void **valptr) +{ + return -ENXIO; +} + +static inline int acpi_dev_prop_read(struct acpi_device *adev, + const char *propname, + enum dev_prop_type proptype, void *val) +{ + return -ENXIO; +} + +static inline int acpi_dev_prop_read_array(struct acpi_device *adev, + const char *propname, + enum dev_prop_type proptype, + void *val, size_t nval) +{ + return -ENXIO; +} + #endif
#endif /*_LINUX_ACPI_H*/ diff --git a/include/linux/of.h b/include/linux/of.h index 6545e7a..25efe42 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -23,6 +23,7 @@ #include <linux/spinlock.h> #include <linux/topology.h> #include <linux/notifier.h> +#include <linux/property.h>
#include <asm/byteorder.h> #include <asm/errno.h> @@ -263,6 +264,10 @@ extern int of_property_read_u32_array(const struct device_node *np, size_t sz); extern int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value); +extern int of_property_read_u64_array(const struct device_node *np, + const char *propname, + u64 *out_values, + size_t sz);
extern int of_property_read_string(struct device_node *np, const char *propname, @@ -275,6 +280,9 @@ extern int of_property_match_string(struct device_node *np, const char *string); extern int of_property_count_strings(struct device_node *np, const char *propname); +extern int of_property_read_string_array(struct device_node *np, + const char *propname, + char **out_strs, size_t sz); extern int of_device_is_compatible(const struct device_node *device, const char *); extern int of_device_is_available(const struct device_node *device); @@ -479,6 +487,13 @@ static inline int of_property_read_u32_array(const struct device_node *np, return -ENOSYS; }
+static inline int of_property_read_u64_array(const struct device_node *np, + const char *propname, + u64 *out_values, size_t sz) +{ + return -ENOSYS; +} + static inline int of_property_read_string(struct device_node *np, const char *propname, const char **out_string) @@ -499,6 +514,13 @@ static inline int of_property_count_strings(struct device_node *np, return -ENOSYS; }
+static inline int of_property_read_string_array(struct device_node *np, + const char *propname, + char **out_strs, size_t sz) +{ + return -ENOSYS; +} + static inline const void *of_get_property(const struct device_node *node, const char *name, int *lenp) diff --git a/include/linux/property.h b/include/linux/property.h new file mode 100644 index 0000000..7a3555f --- /dev/null +++ b/include/linux/property.h @@ -0,0 +1,53 @@ +/* + * property.h - Unified device property interface. + * + * Copyright (C) 2014, Intel Corporation + * Authors: Rafael J. Wysocki rafael.j.wysocki@intel.com + * Mika Westerberg mika.westerberg@linux.intel.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _LINUX_PROPERTY_H_ +#define _LINUX_PROPERTY_H_ + +#include <linux/types.h> + +struct device; + +enum dev_prop_type { + DEV_PROP_U8, + DEV_PROP_U16, + DEV_PROP_U32, + DEV_PROP_U64, + DEV_PROP_STRING, + DEV_PROP_MAX, +}; + +bool device_property_present(struct device *dev, const char *propname); +int device_property_read_u8(struct device *dev, const char *propname, u8 *val); +int device_property_read_u16(struct device *dev, const char *propname, u16 *val); +int device_property_read_u32(struct device *dev, const char *propname, u32 *val); +int device_property_read_u64(struct device *dev, const char *propname, u64 *val); +int device_property_read_string(struct device *dev, const char *propname, + const char **val); +int device_property_read_u8_array(struct device *dev, const char *propname, + u8 *val, size_t nval); +int device_property_read_u16_array(struct device *dev, const char *propname, + u16 *val, size_t nval); +int device_property_read_u32_array(struct device *dev, const char *propname, + u32 *val, size_t nval); +int device_property_read_u64_array(struct device *dev, const char *propname, + u64 *val, size_t nval); +int device_property_read_string_array(struct device *dev, const char *propname, + char **val, size_t nval); + +static inline bool device_property_read_bool(struct device *dev, + const char *propname) +{ + return device_property_present(dev, propname); +} + +#endif /* _LINUX_PROPERTY_H_ */
From: Mika Westerberg mika.westerberg@linux.intel.com
We have lots of existing Device Tree enabled drivers and allocating separate _HID for each is not feasible. Instead we allocate special _HID "PRP0001" that means that the match should be done using Device Tree compatible property using driver's .of_match_table instead if the driver is missing .acpi_match_table.
If there is a need to distinguish from where the device is enumerated (DT/ACPI) driver can check dev->of_node or ACPI_COMPATION(dev).
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Reviewed-by: Grant Likely grant.likely@linaro.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/property.c | 34 +++++++++++++++++ drivers/acpi/scan.c | 97 +++++++++++++++++++++++++++++++++++++++++++------ include/acpi/acpi_bus.h | 1 + include/linux/acpi.h | 8 +--- 4 files changed, 123 insertions(+), 17 deletions(-)
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index b586b58..f01fb41 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -76,6 +76,37 @@ static bool acpi_properties_format_valid(const union acpi_object *properties) return true; }
+static void acpi_init_of_compatible(struct acpi_device *adev) +{ + const union acpi_object *of_compatible; + struct acpi_hardware_id *hwid; + bool acpi_of = false; + + /* + * Check if the special PRP0001 ACPI ID is present and in that + * case we fill in Device Tree compatible properties for this + * device. + */ + list_for_each_entry(hwid, &adev->pnp.ids, list) { + if (!strcmp(hwid->id, "PRP0001")) { + acpi_of = true; + break; + } + } + + if (!acpi_of) + return; + + if (acpi_dev_get_property_array(adev, "compatible", ACPI_TYPE_STRING, + &of_compatible)) { + acpi_handle_warn(adev->handle, + "PRP0001 requires compatible property\n"); + return; + } + + adev->data.of_compatible = of_compatible; +} + void acpi_init_properties(struct acpi_device *adev) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; @@ -119,6 +150,8 @@ void acpi_init_properties(struct acpi_device *adev)
adev->data.pointer = buf.pointer; adev->data.properties = properties; + + acpi_init_of_compatible(adev); return; }
@@ -130,6 +163,7 @@ void acpi_init_properties(struct acpi_device *adev) void acpi_free_properties(struct acpi_device *adev) { ACPI_FREE((void *)adev->data.pointer); + adev->data.of_compatible = NULL; adev->data.pointer = NULL; adev->data.properties = NULL; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 6186a99..a78584e 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -124,17 +124,51 @@ static int create_modalias(struct acpi_device *acpi_dev, char *modalias, if (list_empty(&acpi_dev->pnp.ids)) return 0;
- len = snprintf(modalias, size, "acpi:"); - size -= len; - - list_for_each_entry(id, &acpi_dev->pnp.ids, list) { - count = snprintf(&modalias[len], size, "%s:", id->id); - if (count < 0) - return -EINVAL; - if (count >= size) - return -ENOMEM; - len += count; - size -= count; + /* + * If the device has PRP0001 we expose DT compatible modalias + * instead in form of of:NnameTCcompatible. + */ + if (acpi_dev->data.of_compatible) { + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + const union acpi_object *of_compatible, *obj; + char *c; + int i; + + acpi_get_name(acpi_dev->handle, ACPI_SINGLE_NAME, &buf); + /* DT strings are all in lower case */ + for (c = buf.pointer; *c != '\0'; c++) + *c = tolower(*c); + + len = snprintf(modalias, size, "of:N%sT", (char *)buf.pointer); + ACPI_FREE(buf.pointer); + + of_compatible = acpi_dev->data.of_compatible; + for (i = 0; i < of_compatible->package.count; i++) { + obj = &of_compatible->package.elements[i]; + + count = snprintf(&modalias[len], size, "C%s", + obj->string.pointer); + if (count < 0) + return -EINVAL; + if (count >= size) + return -ENOMEM; + + len += count; + size -= count; + } + } else { + len = snprintf(modalias, size, "acpi:"); + size -= len; + + list_for_each_entry(id, &acpi_dev->pnp.ids, list) { + count = snprintf(&modalias[len], size, "%s:", id->id); + if (count < 0) + return -EINVAL; + if (count >= size) + return -ENOMEM; + len += count; + size -= count; + } }
modalias[len] = '\0'; @@ -864,6 +898,47 @@ int acpi_match_device_ids(struct acpi_device *device, } EXPORT_SYMBOL(acpi_match_device_ids);
+/* Performs match against special "PRP0001" shoehorn ACPI ID */ +static bool acpi_of_driver_match_device(struct device *dev, + const struct device_driver *drv) +{ + const union acpi_object *of_compatible; + struct acpi_device *adev; + int i; + + adev = ACPI_COMPANION(dev); + if (!adev) + return false; + + of_compatible = adev->data.of_compatible; + if (!drv->of_match_table || !of_compatible) + return false; + + /* Now we can look for the driver DT compatible strings */ + for (i = 0; i < of_compatible->package.count; i++) { + const struct of_device_id *id; + const union acpi_object *obj; + + obj = &of_compatible->package.elements[i]; + + for (id = drv->of_match_table; id->compatible[0]; id++) + if (!strcasecmp(obj->string.pointer, id->compatible)) + return true; + } + + return false; +} + +bool acpi_driver_match_device(struct device *dev, + const struct device_driver *drv) +{ + if (!drv->acpi_match_table) + return acpi_of_driver_match_device(dev, drv); + + return !!acpi_match_device(drv->acpi_match_table, dev); +} +EXPORT_SYMBOL_GPL(acpi_driver_match_device); + static void acpi_free_power_resources_lists(struct acpi_device *device) { int i; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 4757811..f59cbf8 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -341,6 +341,7 @@ struct acpi_device_physical_node { struct acpi_device_data { const union acpi_object *pointer; const union acpi_object *properties; + const union acpi_object *of_compatible; };
/* Device */ diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 6eb7c40..dc3f75f 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -429,12 +429,8 @@ extern int acpi_nvs_for_each_region(int (*func)(__u64, __u64, void *), const struct acpi_device_id *acpi_match_device(const struct acpi_device_id *ids, const struct device *dev);
-static inline bool acpi_driver_match_device(struct device *dev, - const struct device_driver *drv) -{ - return !!acpi_match_device(drv->acpi_match_table, dev); -} - +extern bool acpi_driver_match_device(struct device *dev, + const struct device_driver *drv); int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *); int acpi_device_modalias(struct device *, char *, int);
From: Mika Westerberg mika.westerberg@linux.intel.com
Make use of device property API in this driver so that both DT and ACPI based systems can use this driver.
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/misc/eeprom/at25.c | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-)
diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 634f729..0a1af93 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -18,7 +18,7 @@
#include <linux/spi/spi.h> #include <linux/spi/eeprom.h> -#include <linux/of.h> +#include <linux/property.h>
/* * NOTE: this is an *EEPROM* driver. The vagaries of product naming @@ -301,35 +301,33 @@ static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf,
/*-------------------------------------------------------------------------*/
-static int at25_np_to_chip(struct device *dev, - struct device_node *np, - struct spi_eeprom *chip) +static int at25_fw_to_chip(struct device *dev, struct spi_eeprom *chip) { u32 val;
memset(chip, 0, sizeof(*chip)); - strncpy(chip->name, np->name, sizeof(chip->name)); + strncpy(chip->name, "at25", sizeof(chip->name));
- if (of_property_read_u32(np, "size", &val) == 0 || - of_property_read_u32(np, "at25,byte-len", &val) == 0) { + if (device_property_read_u32(dev, "size", &val) == 0 || + device_property_read_u32(dev, "at25,byte-len", &val) == 0) { chip->byte_len = val; } else { dev_err(dev, "Error: missing "size" property\n"); return -ENODEV; }
- if (of_property_read_u32(np, "pagesize", &val) == 0 || - of_property_read_u32(np, "at25,page-size", &val) == 0) { + if (device_property_read_u32(dev, "pagesize", &val) == 0 || + device_property_read_u32(dev, "at25,page-size", &val) == 0) { chip->page_size = (u16)val; } else { dev_err(dev, "Error: missing "pagesize" property\n"); return -ENODEV; }
- if (of_property_read_u32(np, "at25,addr-mode", &val) == 0) { + if (device_property_read_u32(dev, "at25,addr-mode", &val) == 0) { chip->flags = (u16)val; } else { - if (of_property_read_u32(np, "address-width", &val)) { + if (device_property_read_u32(dev, "address-width", &val)) { dev_err(dev, "Error: missing "address-width" property\n"); return -ENODEV; @@ -350,7 +348,7 @@ static int at25_np_to_chip(struct device *dev, val); return -ENODEV; } - if (of_find_property(np, "read-only", NULL)) + if (device_property_present(dev, "read-only")) chip->flags |= EE_READONLY; } return 0; @@ -360,21 +358,15 @@ static int at25_probe(struct spi_device *spi) { struct at25_data *at25 = NULL; struct spi_eeprom chip; - struct device_node *np = spi->dev.of_node; int err; int sr; int addrlen;
/* Chip description */ if (!spi->dev.platform_data) { - if (np) { - err = at25_np_to_chip(&spi->dev, np, &chip); - if (err) - return err; - } else { - dev_err(&spi->dev, "Error: no chip description\n"); - return -ENODEV; - } + err = at25_fw_to_chip(&spi->dev, &chip); + if (err) + return err; } else chip = *(struct spi_eeprom *)spi->dev.platform_data;
From: Mika Westerberg mika.westerberg@linux.intel.com
With release of ACPI 5.1 and _DSD method we can finally name GPIOs (and other things as well) returned by _CRS. Previously we were only able to use integer index to find the corresponding GPIO, which is pretty error prone if the order changes.
With _DSD we can now query GPIOs using name instead of an integer index, like the below example shows:
// Bluetooth device with reset and shutdown GPIOs Device (BTH) { Name (_HID, ...)
Name (_CRS, ResourceTemplate () { GpioIo (Exclusive, PullUp, 0, 0, IoRestrictionInputOnly, "\_SB.GPO0", 0, ResourceConsumer) {15} GpioIo (Exclusive, PullUp, 0, 0, IoRestrictionInputOnly, "\_SB.GPO0", 0, ResourceConsumer) {27, 31} })
Name (_DSD, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () {"reset-gpio", Package() {^BTH, 1, 1, 0 }}, Package () {"shutdown-gpio", Package() {^BTH, 0, 0, 0 }}, } }) }
The format of the supported GPIO property is:
Package () { "name", Package () { ref, index, pin, active_low }}
ref - The device that has _CRS containing GpioIo()/GpioInt() resources, typically this is the device itself (BTH in our case). index - Index of the GpioIo()/GpioInt() resource in _CRS starting from zero. pin - Pin in the GpioIo()/GpioInt() resource. Typically this is zero. active_low - If 1 the GPIO is marked as active_low.
Since ACPI GpioIo() resource does not have field saying whether it is active low or high, the "active_low" argument can be used here. Setting it to 1 marks the GPIO as active low.
In our Bluetooth example the "reset-gpio" refers to the second GpioIo() resource, second pin in that resource with the GPIO number of 31.
This patch implements necessary support to gpiolib for extracting GPIOs using _DSD device properties.
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Linus Walleij linus.walleij@linaro.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- Documentation/acpi/gpio-properties.txt | 52 +++++++++++++++++++++++ drivers/gpio/gpiolib-acpi.c | 78 ++++++++++++++++++++++++++++------ drivers/gpio/gpiolib.c | 30 +++++++++++-- drivers/gpio/gpiolib.h | 7 +-- 4 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 Documentation/acpi/gpio-properties.txt
diff --git a/Documentation/acpi/gpio-properties.txt b/Documentation/acpi/gpio-properties.txt new file mode 100644 index 0000000..3e45b8b --- /dev/null +++ b/Documentation/acpi/gpio-properties.txt @@ -0,0 +1,52 @@ +_DSD Device Properties Related to GPIO +-------------------------------------- + +With the release of ACPI 5.1 and the _DSD configuration objecte names +can finally be given to GPIOs (and other things as well) returned by +_CRS. Previously, we were only able to use an integer index to find +the corresponding GPIO, which is pretty error prone (it depends on +the _CRS output ordering, for example). + +With _DSD we can now query GPIOs using a name instead of an integer +index, like the ASL example below shows: + + // Bluetooth device with reset and shutdown GPIOs + Device (BTH) + { + Name (_HID, ...) + + Name (_CRS, ResourceTemplate () + { + GpioIo (Exclusive, PullUp, 0, 0, IoRestrictionInputOnly, + "\_SB.GPO0", 0, ResourceConsumer) {15} + GpioIo (Exclusive, PullUp, 0, 0, IoRestrictionInputOnly, + "\_SB.GPO0", 0, ResourceConsumer) {27, 31} + }) + + Name (_DSD, Package () + { + ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), + Package () + { + Package () {"reset-gpio", Package() {^BTH, 1, 1, 0 }}, + Package () {"shutdown-gpio", Package() {^BTH, 0, 0, 0 }}, + } + }) + } + +The format of the supported GPIO property is: + + Package () { "name", Package () { ref, index, pin, active_low }} + + ref - The device that has _CRS containing GpioIo()/GpioInt() resources, + typically this is the device itself (BTH in our case). + index - Index of the GpioIo()/GpioInt() resource in _CRS starting from zero. + pin - Pin in the GpioIo()/GpioInt() resource. Typically this is zero. + active_low - If 1 the GPIO is marked as active_low. + +Since ACPI GpioIo() resource does not have a field saying whether it is +active low or high, the "active_low" argument can be used here. Setting +it to 1 marks the GPIO as active low. + +In our Bluetooth example the "reset-gpio" refers to the second GpioIo() +resource, second pin in that resource with the GPIO number of 31. diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index 05c6275..8aa6ca4 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -290,6 +290,7 @@ void acpi_gpiochip_free_interrupts(struct gpio_chip *chip) struct acpi_gpio_lookup { struct acpi_gpio_info info; int index; + int pin_index; struct gpio_desc *desc; int n; }; @@ -303,13 +304,24 @@ static int acpi_find_gpio(struct acpi_resource *ares, void *data)
if (lookup->n++ == lookup->index && !lookup->desc) { const struct acpi_resource_gpio *agpio = &ares->data.gpio; + int pin_index = lookup->pin_index; + + if (pin_index >= agpio->pin_table_length) + return 1;
lookup->desc = acpi_get_gpiod(agpio->resource_source.string_ptr, - agpio->pin_table[0]); + agpio->pin_table[pin_index]); lookup->info.gpioint = agpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT; - lookup->info.active_low = - agpio->polarity == ACPI_ACTIVE_LOW; + + /* + * ActiveLow is only specified for GpioInt resource. If + * GpioIo is used then the only way to set the flag is + * to use _DSD "gpios" property. + */ + if (lookup->info.gpioint) + lookup->info.active_low = + agpio->polarity == ACPI_ACTIVE_LOW; }
return 1; @@ -317,40 +329,75 @@ static int acpi_find_gpio(struct acpi_resource *ares, void *data)
/** * acpi_get_gpiod_by_index() - get a GPIO descriptor from device resources - * @dev: pointer to a device to get GPIO from + * @adev: pointer to a ACPI device to get GPIO from + * @propname: Property name of the GPIO (optional) * @index: index of GpioIo/GpioInt resource (starting from %0) * @info: info pointer to fill in (optional) * - * Function goes through ACPI resources for @dev and based on @index looks + * Function goes through ACPI resources for @adev and based on @index looks * up a GpioIo/GpioInt resource, translates it to the Linux GPIO descriptor, * and returns it. @index matches GpioIo/GpioInt resources only so if there * are total %3 GPIO resources, the index goes from %0 to %2. * + * If @propname is specified the GPIO is looked using device property. In + * that case @index is used to select the GPIO entry in the property value + * (in case of multiple). + * * If the GPIO cannot be translated or there is an error an ERR_PTR is * returned. * * Note: if the GPIO resource has multiple entries in the pin list, this * function only returns the first. */ -struct gpio_desc *acpi_get_gpiod_by_index(struct device *dev, int index, +struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, + const char *propname, int index, struct acpi_gpio_info *info) { struct acpi_gpio_lookup lookup; struct list_head resource_list; - struct acpi_device *adev; - acpi_handle handle; + bool active_low = false; int ret;
- if (!dev) - return ERR_PTR(-EINVAL); - - handle = ACPI_HANDLE(dev); - if (!handle || acpi_bus_get_device(handle, &adev)) + if (!adev) return ERR_PTR(-ENODEV);
memset(&lookup, 0, sizeof(lookup)); lookup.index = index;
+ if (propname) { + struct acpi_reference_args args; + + dev_dbg(&adev->dev, "GPIO: looking up %s\n", propname); + + memset(&args, 0, sizeof(args)); + ret = acpi_dev_get_property_reference(adev, propname, NULL, + index, &args); + if (ret) + return ERR_PTR(ret); + + /* + * The property was found and resolved so need to + * lookup the GPIO based on returned args instead. + */ + adev = args.adev; + if (args.nargs >= 2) { + lookup.index = args.args[0]; + lookup.pin_index = args.args[1]; + /* + * 3rd argument, if present is used to + * specify active_low. + */ + if (args.nargs >= 3) + active_low = !!args.args[2]; + } + + dev_dbg(&adev->dev, "GPIO: _DSD returned %s %zd %llu %llu %llu\n", + dev_name(&adev->dev), args.nargs, + args.args[0], args.args[1], args.args[2]); + } else { + dev_dbg(&adev->dev, "GPIO: looking up %d in _CRS\n", index); + } + INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_resources(adev, &resource_list, acpi_find_gpio, &lookup); @@ -359,8 +406,11 @@ struct gpio_desc *acpi_get_gpiod_by_index(struct device *dev, int index,
acpi_dev_free_resource_list(&resource_list);
- if (lookup.desc && info) + if (lookup.desc && info) { *info = lookup.info; + if (active_low) + info->active_low = active_low; + }
return lookup.desc ? lookup.desc : ERR_PTR(-ENOENT); } diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e8e98ca..94eb0a6 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1505,14 +1505,36 @@ static struct gpio_desc *acpi_find_gpio(struct device *dev, const char *con_id, unsigned int idx, enum gpio_lookup_flags *flags) { + static const char * const suffixes[] = { "gpios", "gpio" }; + struct acpi_device *adev = ACPI_COMPANION(dev); struct acpi_gpio_info info; struct gpio_desc *desc; + char propname[32]; + int i;
- desc = acpi_get_gpiod_by_index(dev, idx, &info); - if (IS_ERR(desc)) - return desc; + /* Try first from _DSD */ + for (i = 0; i < ARRAY_SIZE(suffixes); i++) { + if (con_id && strcmp(con_id, "gpios")) { + snprintf(propname, sizeof(propname), "%s-%s", + con_id, suffixes[i]); + } else { + snprintf(propname, sizeof(propname), "%s", + suffixes[i]); + } + + desc = acpi_get_gpiod_by_index(adev, propname, 0, &info); + if (!IS_ERR(desc) || (PTR_ERR(desc) == -EPROBE_DEFER)) + break; + } + + /* Then from plain _CRS GPIOs */ + if (IS_ERR(desc)) { + desc = acpi_get_gpiod_by_index(adev, NULL, idx, &info); + if (IS_ERR(desc)) + return desc; + }
- if (info.gpioint && info.active_low) + if (info.active_low) *flags |= GPIO_ACTIVE_LOW;
return desc; diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 9db2b6a..e3a5211 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -34,7 +34,8 @@ void acpi_gpiochip_remove(struct gpio_chip *chip); void acpi_gpiochip_request_interrupts(struct gpio_chip *chip); void acpi_gpiochip_free_interrupts(struct gpio_chip *chip);
-struct gpio_desc *acpi_get_gpiod_by_index(struct device *dev, int index, +struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, + const char *propname, int index, struct acpi_gpio_info *info); #else static inline void acpi_gpiochip_add(struct gpio_chip *chip) { } @@ -47,8 +48,8 @@ static inline void acpi_gpiochip_free_interrupts(struct gpio_chip *chip) { }
static inline struct gpio_desc * -acpi_get_gpiod_by_index(struct device *dev, int index, - struct acpi_gpio_info *info) +acpi_get_gpiod_by_index(struct acpi_device *adev, const char *propname, + int index, struct acpi_gpio_info *info) { return ERR_PTR(-ENOSYS); }
From: Mika Westerberg mika.westerberg@linux.intel.com
This is actually a single device with two sets of identical registers, which just happen to start from a different offset. Instead of having separate GPIO chips created we consolidate them to be single GPIO chip.
In addition having a single GPIO chip allows us to handle ACPI GPIO translation in the core in a more generic way, since the two GPIO chips share the same parent ACPI device.
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Linus Walleij linus.walleij@linaro.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/gpio/gpio-sch.c | 293 ++++++++++++++++++------------------------------ 1 file changed, 112 insertions(+), 181 deletions(-)
diff --git a/drivers/gpio/gpio-sch.c b/drivers/gpio/gpio-sch.c index 41e91d7..99720c8 100644 --- a/drivers/gpio/gpio-sch.c +++ b/drivers/gpio/gpio-sch.c @@ -29,290 +29,221 @@
#include <linux/gpio.h>
-static DEFINE_SPINLOCK(gpio_lock); - -#define CGEN (0x00) -#define CGIO (0x04) -#define CGLV (0x08) - -#define RGEN (0x20) -#define RGIO (0x24) -#define RGLV (0x28) - -static unsigned short gpio_ba; - -static int sch_gpio_core_direction_in(struct gpio_chip *gc, unsigned gpio_num) -{ - u8 curr_dirs; - unsigned short offset, bit; - - spin_lock(&gpio_lock); - - offset = CGIO + gpio_num / 8; - bit = gpio_num % 8; - - curr_dirs = inb(gpio_ba + offset); - - if (!(curr_dirs & (1 << bit))) - outb(curr_dirs | (1 << bit), gpio_ba + offset); +#define GEN 0x00 +#define GIO 0x04 +#define GLV 0x08 + +struct sch_gpio { + struct gpio_chip chip; + spinlock_t lock; + unsigned short iobase; + unsigned short core_base; + unsigned short resume_base; +};
- spin_unlock(&gpio_lock); - return 0; -} +#define to_sch_gpio(c) container_of(c, struct sch_gpio, chip)
-static int sch_gpio_core_get(struct gpio_chip *gc, unsigned gpio_num) +static unsigned sch_gpio_offset(struct sch_gpio *sch, unsigned gpio, + unsigned reg) { - int res; - unsigned short offset, bit; + unsigned base = 0;
- offset = CGLV + gpio_num / 8; - bit = gpio_num % 8; + if (gpio >= sch->resume_base) { + gpio -= sch->resume_base; + base += 0x20; + }
- res = !!(inb(gpio_ba + offset) & (1 << bit)); - return res; + return base + reg + gpio / 8; }
-static void sch_gpio_core_set(struct gpio_chip *gc, unsigned gpio_num, int val) +static unsigned sch_gpio_bit(struct sch_gpio *sch, unsigned gpio) { - u8 curr_vals; - unsigned short offset, bit; - - spin_lock(&gpio_lock); - - offset = CGLV + gpio_num / 8; - bit = gpio_num % 8; - - curr_vals = inb(gpio_ba + offset); - - if (val) - outb(curr_vals | (1 << bit), gpio_ba + offset); - else - outb((curr_vals & ~(1 << bit)), gpio_ba + offset); - spin_unlock(&gpio_lock); + if (gpio >= sch->resume_base) + gpio -= sch->resume_base; + return gpio % 8; }
-static int sch_gpio_core_direction_out(struct gpio_chip *gc, - unsigned gpio_num, int val) +static void sch_gpio_enable(struct sch_gpio *sch, unsigned gpio) { - u8 curr_dirs; unsigned short offset, bit; + u8 enable;
- spin_lock(&gpio_lock); + spin_lock(&sch->lock);
- offset = CGIO + gpio_num / 8; - bit = gpio_num % 8; - - curr_dirs = inb(gpio_ba + offset); - if (curr_dirs & (1 << bit)) - outb(curr_dirs & ~(1 << bit), gpio_ba + offset); + offset = sch_gpio_offset(sch, gpio, GEN); + bit = sch_gpio_bit(sch, gpio);
- spin_unlock(&gpio_lock); + enable = inb(sch->iobase + offset); + if (!(enable & (1 << bit))) + outb(enable | (1 << bit), sch->iobase + offset);
- /* - * according to the datasheet, writing to the level register has no - * effect when GPIO is programmed as input. - * Actually the the level register is read-only when configured as input. - * Thus presetting the output level before switching to output is _NOT_ possible. - * Hence we set the level after configuring the GPIO as output. - * But we cannot prevent a short low pulse if direction is set to high - * and an external pull-up is connected. - */ - sch_gpio_core_set(gc, gpio_num, val); - return 0; + spin_unlock(&sch->lock); }
-static struct gpio_chip sch_gpio_core = { - .label = "sch_gpio_core", - .owner = THIS_MODULE, - .direction_input = sch_gpio_core_direction_in, - .get = sch_gpio_core_get, - .direction_output = sch_gpio_core_direction_out, - .set = sch_gpio_core_set, -}; - -static int sch_gpio_resume_direction_in(struct gpio_chip *gc, - unsigned gpio_num) +static int sch_gpio_direction_in(struct gpio_chip *gc, unsigned gpio_num) { + struct sch_gpio *sch = to_sch_gpio(gc); u8 curr_dirs; unsigned short offset, bit;
- spin_lock(&gpio_lock); + spin_lock(&sch->lock);
- offset = RGIO + gpio_num / 8; - bit = gpio_num % 8; + offset = sch_gpio_offset(sch, gpio_num, GIO); + bit = sch_gpio_bit(sch, gpio_num);
- curr_dirs = inb(gpio_ba + offset); + curr_dirs = inb(sch->iobase + offset);
if (!(curr_dirs & (1 << bit))) - outb(curr_dirs | (1 << bit), gpio_ba + offset); + outb(curr_dirs | (1 << bit), sch->iobase + offset);
- spin_unlock(&gpio_lock); + spin_unlock(&sch->lock); return 0; }
-static int sch_gpio_resume_get(struct gpio_chip *gc, unsigned gpio_num) +static int sch_gpio_get(struct gpio_chip *gc, unsigned gpio_num) { + struct sch_gpio *sch = to_sch_gpio(gc); + int res; unsigned short offset, bit;
- offset = RGLV + gpio_num / 8; - bit = gpio_num % 8; + offset = sch_gpio_offset(sch, gpio_num, GLV); + bit = sch_gpio_bit(sch, gpio_num); + + res = !!(inb(sch->iobase + offset) & (1 << bit));
- return !!(inb(gpio_ba + offset) & (1 << bit)); + return res; }
-static void sch_gpio_resume_set(struct gpio_chip *gc, - unsigned gpio_num, int val) +static void sch_gpio_set(struct gpio_chip *gc, unsigned gpio_num, int val) { + struct sch_gpio *sch = to_sch_gpio(gc); u8 curr_vals; unsigned short offset, bit;
- spin_lock(&gpio_lock); + spin_lock(&sch->lock);
- offset = RGLV + gpio_num / 8; - bit = gpio_num % 8; + offset = sch_gpio_offset(sch, gpio_num, GLV); + bit = sch_gpio_bit(sch, gpio_num);
- curr_vals = inb(gpio_ba + offset); + curr_vals = inb(sch->iobase + offset);
if (val) - outb(curr_vals | (1 << bit), gpio_ba + offset); + outb(curr_vals | (1 << bit), sch->iobase + offset); else - outb((curr_vals & ~(1 << bit)), gpio_ba + offset); + outb((curr_vals & ~(1 << bit)), sch->iobase + offset);
- spin_unlock(&gpio_lock); + spin_unlock(&sch->lock); }
-static int sch_gpio_resume_direction_out(struct gpio_chip *gc, - unsigned gpio_num, int val) +static int sch_gpio_direction_out(struct gpio_chip *gc, unsigned gpio_num, + int val) { + struct sch_gpio *sch = to_sch_gpio(gc); u8 curr_dirs; unsigned short offset, bit;
- offset = RGIO + gpio_num / 8; - bit = gpio_num % 8; + spin_lock(&sch->lock);
- spin_lock(&gpio_lock); + offset = sch_gpio_offset(sch, gpio_num, GIO); + bit = sch_gpio_bit(sch, gpio_num);
- curr_dirs = inb(gpio_ba + offset); + curr_dirs = inb(sch->iobase + offset); if (curr_dirs & (1 << bit)) - outb(curr_dirs & ~(1 << bit), gpio_ba + offset); + outb(curr_dirs & ~(1 << bit), sch->iobase + offset);
- spin_unlock(&gpio_lock); + spin_unlock(&sch->lock);
/* - * according to the datasheet, writing to the level register has no - * effect when GPIO is programmed as input. - * Actually the the level register is read-only when configured as input. - * Thus presetting the output level before switching to output is _NOT_ possible. - * Hence we set the level after configuring the GPIO as output. - * But we cannot prevent a short low pulse if direction is set to high - * and an external pull-up is connected. - */ - sch_gpio_resume_set(gc, gpio_num, val); + * according to the datasheet, writing to the level register has no + * effect when GPIO is programmed as input. + * Actually the the level register is read-only when configured as input. + * Thus presetting the output level before switching to output is _NOT_ possible. + * Hence we set the level after configuring the GPIO as output. + * But we cannot prevent a short low pulse if direction is set to high + * and an external pull-up is connected. + */ + sch_gpio_set(gc, gpio_num, val); return 0; }
-static struct gpio_chip sch_gpio_resume = { - .label = "sch_gpio_resume", +static struct gpio_chip sch_gpio_chip = { + .label = "sch_gpio", .owner = THIS_MODULE, - .direction_input = sch_gpio_resume_direction_in, - .get = sch_gpio_resume_get, - .direction_output = sch_gpio_resume_direction_out, - .set = sch_gpio_resume_set, + .direction_input = sch_gpio_direction_in, + .get = sch_gpio_get, + .direction_output = sch_gpio_direction_out, + .set = sch_gpio_set, };
static int sch_gpio_probe(struct platform_device *pdev) { + struct sch_gpio *sch; struct resource *res; - int err, id;
- id = pdev->id; - if (!id) - return -ENODEV; + sch = devm_kzalloc(&pdev->dev, sizeof(*sch), GFP_KERNEL); + if (!sch) + return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_IO, 0); if (!res) return -EBUSY;
- if (!request_region(res->start, resource_size(res), pdev->name)) + if (!devm_request_region(&pdev->dev, res->start, resource_size(res), + pdev->name)) return -EBUSY;
- gpio_ba = res->start; + spin_lock_init(&sch->lock); + sch->iobase = res->start; + sch->chip = sch_gpio_chip; + sch->chip.label = dev_name(&pdev->dev); + sch->chip.dev = &pdev->dev;
- switch (id) { + switch (pdev->id) { case PCI_DEVICE_ID_INTEL_SCH_LPC: - sch_gpio_core.base = 0; - sch_gpio_core.ngpio = 10; - sch_gpio_resume.base = 10; - sch_gpio_resume.ngpio = 4; + sch->core_base = 0; + sch->resume_base = 10; + sch->chip.ngpio = 14; + /* * GPIO[6:0] enabled by default * GPIO7 is configured by the CMC as SLPIOVR * Enable GPIO[9:8] core powered gpios explicitly */ - outb(0x3, gpio_ba + CGEN + 1); + sch_gpio_enable(sch, 8); + sch_gpio_enable(sch, 9); /* * SUS_GPIO[2:0] enabled by default * Enable SUS_GPIO3 resume powered gpio explicitly */ - outb(0x8, gpio_ba + RGEN); + sch_gpio_enable(sch, 13); break;
case PCI_DEVICE_ID_INTEL_ITC_LPC: - sch_gpio_core.base = 0; - sch_gpio_core.ngpio = 5; - sch_gpio_resume.base = 5; - sch_gpio_resume.ngpio = 9; + sch->core_base = 0; + sch->resume_base = 5; + sch->chip.ngpio = 14; break;
case PCI_DEVICE_ID_INTEL_CENTERTON_ILB: - sch_gpio_core.base = 0; - sch_gpio_core.ngpio = 21; - sch_gpio_resume.base = 21; - sch_gpio_resume.ngpio = 9; + sch->core_base = 0; + sch->resume_base = 21; + sch->chip.ngpio = 30; break;
default: - err = -ENODEV; - goto err_sch_gpio_core; + return -ENODEV; }
- sch_gpio_core.dev = &pdev->dev; - sch_gpio_resume.dev = &pdev->dev; - - err = gpiochip_add(&sch_gpio_core); - if (err < 0) - goto err_sch_gpio_core; + platform_set_drvdata(pdev, sch);
- err = gpiochip_add(&sch_gpio_resume); - if (err < 0) - goto err_sch_gpio_resume; - - return 0; - -err_sch_gpio_resume: - gpiochip_remove(&sch_gpio_core); - -err_sch_gpio_core: - release_region(res->start, resource_size(res)); - gpio_ba = 0; - - return err; + return gpiochip_add(&sch->chip); }
static int sch_gpio_remove(struct platform_device *pdev) { - struct resource *res; - if (gpio_ba) { - - gpiochip_remove(&sch_gpio_core); - gpiochip_remove(&sch_gpio_resume); - - res = platform_get_resource(pdev, IORESOURCE_IO, 0); - - release_region(res->start, resource_size(res)); - gpio_ba = 0; - } + struct sch_gpio *sch = platform_get_drvdata(pdev);
+ gpiochip_remove(&sch->chip); return 0; }
From: Mika Westerberg mika.westerberg@linux.intel.com
GPIO descriptors are the preferred way over legacy GPIO numbers nowadays. Convert the driver to use GPIO descriptors internally but still allow passing legacy GPIO numbers from platform data to support existing platforms.
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Alexandre Courbot acourbot@nvidia.com Acked-by: Bryan Wu cooloney@gmail.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/leds/leds-gpio.c | 82 +++++++++++++++++++++++++++--------------------- include/linux/leds.h | 1 + 2 files changed, 47 insertions(+), 36 deletions(-)
diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index b4518c8..bc30168 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -1,7 +1,7 @@ /* * LEDs driver for GPIOs * - * Copyright (C) 2007 8D Technologies inc. + * Copyright (C) 2007 8, 4D Technologies inc. * Raphael Assenat raph@8d.com * Copyright (C) 2008 Freescale Semiconductor, Inc. * @@ -13,6 +13,7 @@ #include <linux/err.h> #include <linux/gpio.h> #include <linux/kernel.h> +#include <linux/gpio/consumer.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/of.h> @@ -24,11 +25,10 @@
struct gpio_led_data { struct led_classdev cdev; - unsigned gpio; + struct gpio_desc *gpiod; struct work_struct work; u8 new_level; u8 can_sleep; - u8 active_low; u8 blinking; int (*platform_gpio_blink_set)(unsigned gpio, int state, unsigned long *delay_on, unsigned long *delay_off); @@ -40,12 +40,16 @@ static void gpio_led_work(struct work_struct *work) container_of(work, struct gpio_led_data, work);
if (led_dat->blinking) { - led_dat->platform_gpio_blink_set(led_dat->gpio, - led_dat->new_level, - NULL, NULL); + int gpio = desc_to_gpio(led_dat->gpiod); + int level = led_dat->new_level; + + if (gpiod_is_active_low(led_dat->gpiod)) + level = !level; + + led_dat->platform_gpio_blink_set(gpio, level, NULL, NULL); led_dat->blinking = 0; } else - gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level); + gpiod_set_value_cansleep(led_dat->gpiod, led_dat->new_level); }
static void gpio_led_set(struct led_classdev *led_cdev, @@ -60,9 +64,6 @@ static void gpio_led_set(struct led_classdev *led_cdev, else level = 1;
- if (led_dat->active_low) - level = !level; - /* Setting GPIOs with I2C/etc requires a task context, and we don't * seem to have a reliable way to know if we're already in one; so * let's just assume the worst. @@ -72,11 +73,16 @@ static void gpio_led_set(struct led_classdev *led_cdev, schedule_work(&led_dat->work); } else { if (led_dat->blinking) { - led_dat->platform_gpio_blink_set(led_dat->gpio, level, - NULL, NULL); + int gpio = desc_to_gpio(led_dat->gpiod); + + if (gpiod_is_active_low(led_dat->gpiod)) + level = !level; + + led_dat->platform_gpio_blink_set(gpio, level, NULL, + NULL); led_dat->blinking = 0; } else - gpio_set_value(led_dat->gpio, level); + gpiod_set_value(led_dat->gpiod, level); } }
@@ -85,9 +91,10 @@ static int gpio_blink_set(struct led_classdev *led_cdev, { struct gpio_led_data *led_dat = container_of(led_cdev, struct gpio_led_data, cdev); + int gpio = desc_to_gpio(led_dat->gpiod);
led_dat->blinking = 1; - return led_dat->platform_gpio_blink_set(led_dat->gpio, GPIO_LED_BLINK, + return led_dat->platform_gpio_blink_set(gpio, GPIO_LED_BLINK, delay_on, delay_off); }
@@ -97,24 +104,33 @@ static int create_gpio_led(const struct gpio_led *template, { int ret, state;
- led_dat->gpio = -1; + if (!template->gpiod) { + unsigned long flags = 0;
- /* skip leds that aren't available */ - if (!gpio_is_valid(template->gpio)) { - dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n", - template->gpio, template->name); - return 0; - } + /* skip leds that aren't available */ + if (!gpio_is_valid(template->gpio)) { + dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n", + template->gpio, template->name); + return 0; + }
- ret = devm_gpio_request(parent, template->gpio, template->name); - if (ret < 0) - return ret; + if (template->active_low) + flags |= GPIOF_ACTIVE_LOW; + + ret = devm_gpio_request_one(parent, template->gpio, flags, + template->name); + if (ret < 0) + return ret; + + led_dat->gpiod = gpio_to_desc(template->gpio); + if (IS_ERR(led_dat->gpiod)) + return PTR_ERR(led_dat->gpiod); + }
led_dat->cdev.name = template->name; led_dat->cdev.default_trigger = template->default_trigger; - led_dat->gpio = template->gpio; - led_dat->can_sleep = gpio_cansleep(template->gpio); - led_dat->active_low = template->active_low; + led_dat->gpiod = template->gpiod; + led_dat->can_sleep = gpiod_cansleep(template->gpiod); led_dat->blinking = 0; if (blink_set) { led_dat->platform_gpio_blink_set = blink_set; @@ -122,30 +138,24 @@ static int create_gpio_led(const struct gpio_led *template, } led_dat->cdev.brightness_set = gpio_led_set; if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) - state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low; + state = !!gpiod_get_value_cansleep(led_dat->gpiod); else state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); led_dat->cdev.brightness = state ? LED_FULL : LED_OFF; if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
- ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state); + ret = gpiod_direction_output(led_dat->gpiod, state); if (ret < 0) return ret;
INIT_WORK(&led_dat->work, gpio_led_work);
- ret = led_classdev_register(parent, &led_dat->cdev); - if (ret < 0) - return ret; - - return 0; + return led_classdev_register(parent, &led_dat->cdev); }
static void delete_gpio_led(struct gpio_led_data *led) { - if (!gpio_is_valid(led->gpio)) - return; led_classdev_unregister(&led->cdev); cancel_work_sync(&led->work); } diff --git a/include/linux/leds.h b/include/linux/leds.h index a57611d..f3af5c4 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -261,6 +261,7 @@ struct gpio_led { unsigned retain_state_suspended : 1; unsigned default_state : 2; /* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */ + struct gpio_desc *gpiod; }; #define LEDS_GPIO_DEFSTATE_OFF 0 #define LEDS_GPIO_DEFSTATE_ON 1
From: Aaron Lu aaron.lu@intel.com
GPIO descriptors are the preferred way over legacy GPIO numbers nowadays. Convert the driver to use GPIO descriptors internally but still allow passing legacy GPIO numbers from platform data to support existing platforms.
Signed-off-by: Aaron Lu aaron.lu@intel.com Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Alexandre Courbot acourbot@nvidia.com Reviewed-by: Linus Walleij linus.walleij@linaro.org Acked-by: Dmitry Torokhov dmitry.torokhov@gmail.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/input/keyboard/gpio_keys_polled.c | 39 +++++++++++++++++++++---------- include/linux/gpio_keys.h | 3 +++ 2 files changed, 30 insertions(+), 12 deletions(-)
diff --git a/drivers/input/keyboard/gpio_keys_polled.c b/drivers/input/keyboard/gpio_keys_polled.c index 432d363..b7a514c 100644 --- a/drivers/input/keyboard/gpio_keys_polled.c +++ b/drivers/input/keyboard/gpio_keys_polled.c @@ -23,6 +23,7 @@ #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/gpio_keys.h> #include <linux/of.h> #include <linux/of_platform.h> @@ -51,15 +52,14 @@ static void gpio_keys_polled_check_state(struct input_dev *input, int state;
if (bdata->can_sleep) - state = !!gpio_get_value_cansleep(button->gpio); + state = !!gpiod_get_value_cansleep(button->gpiod); else - state = !!gpio_get_value(button->gpio); + state = !!gpiod_get_value(button->gpiod);
if (state != bdata->last_state) { unsigned int type = button->type ?: EV_KEY;
- input_event(input, type, button->code, - !!(state ^ button->active_low)); + input_event(input, type, button->code, state); input_sync(input); bdata->count = 0; bdata->last_state = state; @@ -259,7 +259,6 @@ static int gpio_keys_polled_probe(struct platform_device *pdev) for (i = 0; i < pdata->nbuttons; i++) { struct gpio_keys_button *button = &pdata->buttons[i]; struct gpio_keys_button_data *bdata = &bdev->data[i]; - unsigned int gpio = button->gpio; unsigned int type = button->type ?: EV_KEY;
if (button->wakeup) { @@ -267,15 +266,31 @@ static int gpio_keys_polled_probe(struct platform_device *pdev) return -EINVAL; }
- error = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_IN, - button->desc ? : DRV_NAME); - if (error) { - dev_err(dev, "unable to claim gpio %u, err=%d\n", - gpio, error); - return error; + /* + * Legacy GPIO number so request the GPIO here and + * convert it to descriptor. + */ + if (!button->gpiod && gpio_is_valid(button->gpio)) { + unsigned flags = 0; + + if (button->active_low) + flags |= GPIOF_ACTIVE_LOW; + + error = devm_gpio_request_one(&pdev->dev, button->gpio, + flags, button->desc ? : DRV_NAME); + if (error) { + dev_err(dev, "unable to claim gpio %u, err=%d\n", + button->gpio, error); + return error; + } + + button->gpiod = gpio_to_desc(button->gpio); }
- bdata->can_sleep = gpio_cansleep(gpio); + if (IS_ERR(button->gpiod)) + return PTR_ERR(button->gpiod); + + bdata->can_sleep = gpiod_cansleep(button->gpiod); bdata->last_state = -1; bdata->threshold = DIV_ROUND_UP(button->debounce_interval, pdata->poll_interval); diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h index 8b62246..ee2d8c6 100644 --- a/include/linux/gpio_keys.h +++ b/include/linux/gpio_keys.h @@ -2,6 +2,7 @@ #define _GPIO_KEYS_H
struct device; +struct gpio_desc;
/** * struct gpio_keys_button - configuration parameters @@ -17,6 +18,7 @@ struct device; * disable button via sysfs * @value: axis value for %EV_ABS * @irq: Irq number in case of interrupt keys + * @gpiod: GPIO descriptor */ struct gpio_keys_button { unsigned int code; @@ -29,6 +31,7 @@ struct gpio_keys_button { bool can_disable; int value; unsigned int irq; + struct gpio_desc *gpiod; };
/**
From: "Rafael J. Wysocki" rafael.j.wysocki@intel.com
Add new generic routines are provided for retrieving properties from device description objects in the platform firmware in case there are no struct device objects for them (either those objects have not been created yet or they do not exist at all).
The following functions are provided:
fwnode_property_present() fwnode_property_read_u8() fwnode_property_read_u16() fwnode_property_read_u32() fwnode_property_read_u64() fwnode_property_read_string() fwnode_property_read_u8_array() fwnode_property_read_u16_array() fwnode_property_read_u32_array() fwnode_property_read_u64_array() fwnode_property_read_string_array()
in analogy with the corresponding functions for struct device added previously. For all of them, the first argument is a pointer to struct fwnode_handle (new type) that allows a device description object (depending on what platform firmware interface is in use) to be obtained.
Add a new macro device_for_each_child_node() for iterating over the children of the device description object associated with a given device and a new function device_get_child_node_count() returning the number of a given device's child nodes.
The interface covers both ACPI and Device Trees.
Suggested-by: Grant Likely grant.likely@linaro.org Acked-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/scan.c | 21 +++ drivers/base/property.c | 349 +++++++++++++++++++++++++++++++++++++++++++++++ include/acpi/acpi_bus.h | 17 +++ include/linux/acpi.h | 26 ++++ include/linux/of.h | 22 +++ include/linux/property.h | 54 ++++++++ 6 files changed, 489 insertions(+)
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index a78584e..cb74145 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1342,6 +1342,26 @@ int acpi_device_add(struct acpi_device *device, return result; }
+struct acpi_device *acpi_get_next_child(struct device *dev, + struct acpi_device *child) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + struct list_head *head, *next; + + if (!adev) + return NULL; + + head = &adev->children; + if (list_empty(head)) + return NULL; + + if (!child) + return list_first_entry(head, struct acpi_device, node); + + next = child->node.next; + return next == head ? NULL : list_entry(next, struct acpi_device, node); +} + /* -------------------------------------------------------------------------- Driver Management -------------------------------------------------------------------------- */ @@ -1961,6 +1981,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, device->device_type = type; device->handle = handle; device->parent = acpi_bus_get_parent(handle); + device->fwnode.type = FWNODE_ACPI; acpi_set_device_status(device, sta); acpi_device_get_busid(device); acpi_set_pnp_ids(handle, &device->pnp, type); diff --git a/drivers/base/property.c b/drivers/base/property.c index 514ca66..04b60a7 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -142,6 +142,151 @@ int device_property_read_string(struct device *dev, const char *propname, } EXPORT_SYMBOL_GPL(device_property_read_string);
+/** + * fwnode_property_present - check if a property of a firmware node is present + * @fwnode: Firmware node whose property to check + * @propname: Name of the property + */ +bool fwnode_property_present(struct fwnode_handle *fwnode, const char *propname) +{ + if (is_of_node(fwnode)) + return of_property_read_bool(of_node(fwnode), propname); + else if (is_acpi_node(fwnode)) + return !acpi_dev_prop_get(acpi_node(fwnode), propname, NULL); + + return false; +} +EXPORT_SYMBOL_GPL(fwnode_property_present); + +#define FWNODE_PROPERTY_READ(_fwnode_, _propname_, _type_, _proptype_, _val_) \ +({ \ + int _ret_; \ + if (is_of_node(_fwnode_)) \ + _ret_ = of_property_read_##_type_(of_node(_fwnode_), \ + _propname_, _val_); \ + else if (is_acpi_node(_fwnode_)) \ + _ret_ = acpi_dev_prop_read(acpi_node(_fwnode_), _propname_, \ + _proptype_, _val_); \ + else \ + _ret_ = -ENXIO; \ + _ret_; \ +}) + +/** + * fwnode_property_read_u8 - return a u8 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u8. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u8, + * %-EOVERFLOW if the property value is out of bounds of u8, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u8(struct fwnode_handle *fwnode, const char *propname, + u8 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u8, DEV_PROP_U8, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u8); + +/** + * fwnode_property_read_u16 - return a u16 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u16. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u16, + * %-EOVERFLOW if the property value is out of bounds of u16, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u16(struct fwnode_handle *fwnode, const char *propname, + u16 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u16, DEV_PROP_U16, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u16); + +/** + * fwnode_property_read_u32 - return a u32 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u32. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u32, + * %-EOVERFLOW if the property value is out of bounds of u32, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u32(struct fwnode_handle *fwnode, const char *propname, + u32 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u32, DEV_PROP_U32, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u32); + +/** + * fwnode_property_read_u64 - return a u64 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u64. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u64, + * %-EOVERFLOW if the property value is out of bounds of u64, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u64(struct fwnode_handle *fwnode, const char *propname, + u64 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u64, DEV_PROP_U64, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u64); + +/** + * fwnode_property_read_string - return a string property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be a string. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property is not a string, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_string(struct fwnode_handle *fwnode, + const char *propname, const char **val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, string, DEV_PROP_STRING, + val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_string); + #define OF_DEV_PROP_READ_ARRAY(node, propname, type, val, nval) \ (val) ? of_property_read_##type##_array((node), (propname), (val), (nval)) \ : of_property_count_elems_of_size((node), (propname), sizeof(type)) @@ -274,3 +419,207 @@ int device_property_read_string_array(struct device *dev, const char *propname, DEV_PROP_STRING, val, nval); } EXPORT_SYMBOL_GPL(device_property_read_string_array); + +#define FWNODE_PROPERTY_READ_ARRAY(_fwnode_, _propname_, _type_, _proptype_, _val_, _nval_) \ +({ \ + int _ret_; \ + if (is_of_node(_fwnode_)) \ + _ret_ = OF_DEV_PROP_READ_ARRAY(of_node(_fwnode_), _propname_, \ + _type_, _val_, _nval_); \ + else if (is_acpi_node(_fwnode_)) \ + _ret_ = acpi_dev_prop_read_array(acpi_node(_fwnode_), \ + _propname_, _proptype_, \ + _val_, _nval_); \ + else \ + _ret_ = -ENXIO; \ + _ret_; \ +}) + +/** + * fwnode_property_read_u8_array - return a u8 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u8 properties with @propname from @fwnode and stores them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u8_array(struct fwnode_handle *fwnode, + const char *propname, u8 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u8, DEV_PROP_U8, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u8_array); + +/** + * fwnode_property_read_u16_array - return a u16 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u16 properties with @propname from @fwnode and store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u16_array(struct fwnode_handle *fwnode, + const char *propname, u16 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u16, DEV_PROP_U16, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u16_array); + +/** + * fwnode_property_read_u32_array - return a u32 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u32 properties with @propname from @fwnode store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u32_array(struct fwnode_handle *fwnode, + const char *propname, u32 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u32, DEV_PROP_U32, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u32_array); + +/** + * fwnode_property_read_u64_array - return a u64 array property firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u64 properties with @propname from @fwnode and store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u64_array(struct fwnode_handle *fwnode, + const char *propname, u64 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u64, DEV_PROP_U64, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u64_array); + +/** + * fwnode_property_read_string_array - return string array property of a node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an string list property @propname from the given firmware node and store + * them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of strings, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, char **val, + size_t nval) +{ + if (is_of_node(fwnode)) + return of_property_read_string_array(of_node(fwnode), propname, + val, nval); + else if (is_acpi_node(fwnode)) + return acpi_dev_prop_read_array(acpi_node(fwnode), propname, + DEV_PROP_STRING, val, nval); + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(fwnode_property_read_string_array); + + +/** + * device_get_next_child_node - Return the next child node handle for a device + * @dev: Device to find the next child node for. + * @child: Handle to one of the device's child nodes or a null handle. + */ +struct fwnode_handle *device_get_next_child_node(struct device *dev, + struct fwnode_handle *child) +{ + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + struct device_node *node; + + node = of_get_next_available_child(dev->of_node, of_node(child)); + if (node) + return &node->fwnode; + } else if (ACPI_COMPANION(dev)) { + struct acpi_device *node; + + node = acpi_get_next_child(dev, acpi_node(child)); + if (node) + return acpi_fwnode_handle(node); + } + return NULL; +} +EXPORT_SYMBOL_GPL(device_get_next_child_node); + +/** + * fwnode_handle_put - Drop reference to a device node + * @fwnode: Pointer to the device node to drop the reference to. + * + * This has to be used when terminating device_for_each_child_node() iteration + * with break or return to prevent stale device node references from being left + * behind. + */ +void fwnode_handle_put(struct fwnode_handle *fwnode) +{ + if (is_of_node(fwnode)) + of_node_put(of_node(fwnode)); +} +EXPORT_SYMBOL_GPL(fwnode_handle_put); + +/** + * device_get_child_node_count - return the number of child nodes for device + * @dev: Device to cound the child nodes for + */ +unsigned int device_get_child_node_count(struct device *dev) +{ + struct fwnode_handle *child; + unsigned int count = 0; + + device_for_each_child_node(dev, child) + count++; + + return count; +} +EXPORT_SYMBOL_GPL(device_get_child_node_count); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index f59cbf8..a361f43 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -27,6 +27,7 @@ #define __ACPI_BUS_H__
#include <linux/device.h> +#include <linux/property.h>
/* TBD: Make dynamic */ #define ACPI_MAX_HANDLES 10 @@ -348,6 +349,7 @@ struct acpi_device_data { struct acpi_device { int device_type; acpi_handle handle; /* no handle for fixed hardware */ + struct fwnode_handle fwnode; struct acpi_device *parent; struct list_head children; struct list_head node; @@ -372,6 +374,21 @@ struct acpi_device { void (*remove)(struct acpi_device *); };
+static inline bool is_acpi_node(struct fwnode_handle *fwnode) +{ + return fwnode && fwnode->type == FWNODE_ACPI; +} + +static inline struct acpi_device *acpi_node(struct fwnode_handle *fwnode) +{ + return fwnode ? container_of(fwnode, struct acpi_device, fwnode) : NULL; +} + +static inline struct fwnode_handle *acpi_fwnode_handle(struct acpi_device *adev) +{ + return &adev->fwnode; +} + static inline void *acpi_driver_data(struct acpi_device *d) { return d->driver_data; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index dc3f75f..a979cec 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -445,6 +445,23 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *); #define ACPI_COMPANION_SET(dev, adev) do { } while (0) #define ACPI_HANDLE(dev) (NULL)
+struct fwnode_handle; + +static inline bool is_acpi_node(struct fwnode_handle *fwnode) +{ + return false; +} + +static inline struct acpi_device *acpi_node(struct fwnode_handle *fwnode) +{ + return NULL; +} + +static inline struct fwnode_handle *acpi_fwnode_handle(struct acpi_device *adev) +{ + return NULL; +} + static inline const char *acpi_dev_name(struct acpi_device *adev) { return NULL; @@ -687,6 +704,9 @@ int acpi_dev_prop_read(struct acpi_device *adev, const char *propname, int acpi_dev_prop_read_array(struct acpi_device *adev, const char *propname, enum dev_prop_type proptype, void *val, size_t nval); + +struct acpi_device *acpi_get_next_child(struct device *dev, + struct acpi_device *child); #else static inline int acpi_dev_get_property(struct acpi_device *adev, const char *name, acpi_object_type type, @@ -730,6 +750,12 @@ static inline int acpi_dev_prop_read_array(struct acpi_device *adev, return -ENXIO; }
+static inline struct acpi_device *acpi_get_next_child(struct device *dev, + struct acpi_device *child) +{ + return NULL; +} + #endif
#endif /*_LINUX_ACPI_H*/ diff --git a/include/linux/of.h b/include/linux/of.h index 25efe42..ca873e9 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -50,6 +50,7 @@ struct device_node { const char *type; phandle phandle; const char *full_name; + struct fwnode_handle fwnode;
struct property *properties; struct property *deadprops; /* removed properties */ @@ -80,6 +81,7 @@ extern struct kobj_type of_node_ktype; static inline void of_node_init(struct device_node *node) { kobject_init(&node->kobj, &of_node_ktype); + node->fwnode.type = FWNODE_OF; }
/* true when node is initialized */ @@ -115,6 +117,16 @@ extern struct device_node *of_aliases; extern struct device_node *of_stdout; extern raw_spinlock_t devtree_lock;
+static inline bool is_of_node(struct fwnode_handle *fwnode) +{ + return fwnode && fwnode->type == FWNODE_OF; +} + +static inline struct device_node *of_node(struct fwnode_handle *fwnode) +{ + return fwnode ? container_of(fwnode, struct device_node, fwnode) : NULL; +} + static inline bool of_have_populated_dt(void) { return of_allnodes != NULL; @@ -365,6 +377,16 @@ bool of_console_check(struct device_node *dn, char *name, int index);
#else /* CONFIG_OF */
+static inline bool is_of_node(struct fwnode_handle *fwnode) +{ + return false; +} + +static inline struct device_node *of_node(struct fwnode_handle *fwnode) +{ + return NULL; +} + static inline const char* of_node_full_name(const struct device_node *np) { return "<no-node>"; diff --git a/include/linux/property.h b/include/linux/property.h index 7a3555f..3231fdd 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -44,10 +44,64 @@ int device_property_read_u64_array(struct device *dev, const char *propname, int device_property_read_string_array(struct device *dev, const char *propname, char **val, size_t nval);
+enum fwnode_type { + FWNODE_INVALID = 0, + FWNODE_OF, + FWNODE_ACPI, +}; + +struct fwnode_handle { + enum fwnode_type type; +}; + +bool fwnode_property_present(struct fwnode_handle *fwnode, const char *propname); +int fwnode_property_read_u8(struct fwnode_handle *fwnode, const char *propname, + u8 *val); +int fwnode_property_read_u16(struct fwnode_handle *fwnode, const char *propname, + u16 *val); +int fwnode_property_read_u32(struct fwnode_handle *fwnode, const char *propname, + u32 *val); +int fwnode_property_read_u64(struct fwnode_handle *fwnode, const char *propname, + u64 *val); +int fwnode_property_read_string(struct fwnode_handle *fwnode, + const char *propname, const char **val); +int fwnode_property_read_u8_array(struct fwnode_handle *fwnode, + const char *propname, u8 *val, + size_t nval); +int fwnode_property_read_u16_array(struct fwnode_handle *fwnode, + const char *propname, u16 *val, + size_t nval); +int fwnode_property_read_u32_array(struct fwnode_handle *fwnode, + const char *propname, u32 *val, + size_t nval); +int fwnode_property_read_u64_array(struct fwnode_handle *fwnode, + const char *propname, u64 *val, + size_t nval); +int fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, char **val, + size_t nval); + +struct fwnode_handle *device_get_next_child_node(struct device *dev, + struct fwnode_handle *child); + +#define device_for_each_child_node(dev, child) \ + for (child = device_get_next_child_node(dev, NULL); child; \ + child = device_get_next_child_node(dev, child)) + +void fwnode_handle_put(struct fwnode_handle *fwnode); + +unsigned int device_get_child_node_count(struct device *dev); + static inline bool device_property_read_bool(struct device *dev, const char *propname) { return device_property_present(dev, propname); }
+static inline bool fwnode_property_read_bool(struct fwnode_handle *fwnode, + const char *propname) +{ + return fwnode_property_present(fwnode, propname); +} + #endif /* _LINUX_PROPERTY_H_ */
From: Mika Westerberg mika.westerberg@linux.intel.com
Some drivers need to deal with only firmware representation of its GPIOs. An example would be a GPIO button array driver where each button is described as a separate firmware node in device tree. Typically these child nodes do not have physical representation in the Linux device model.
In order to help device drivers to handle such firmware child nodes we add dev[m]_get_named_gpiod_from_child() that takes a child firmware node pointer as its second argument (the first one is the parent device itself), finds the GPIO using whatever is the underlying firmware method, and requests the GPIO properly.
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Alexandre Courbot acourbot@nvidia.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/gpio/devres.c | 36 ++++++++++++++++++++++++++++ drivers/gpio/gpiolib.c | 56 +++++++++++++++++++++++++++++++++++++++++++ include/linux/gpio/consumer.h | 8 +++++++ 3 files changed, 100 insertions(+)
diff --git a/drivers/gpio/devres.c b/drivers/gpio/devres.c index 954b9f6..8e1d1a3 100644 --- a/drivers/gpio/devres.c +++ b/drivers/gpio/devres.c @@ -109,6 +109,42 @@ struct gpio_desc *__must_check __devm_gpiod_get_index(struct device *dev, EXPORT_SYMBOL(__devm_gpiod_get_index);
/** + * devm_get_named_gpiod_from_child - managed dev_get_named_gpiod_from_child() + * @dev: GPIO consumer + * @child: firmware node (child of @dev) + * @propname: name of the firmware property + * @index: index of the GPIO in the property value in case of many + * + * GPIO descriptors returned from this function are automatically disposed on + * driver detach. + */ +struct gpio_desc *devm_get_named_gpiod_from_child(struct device *dev, + struct fwnode_handle *child, + const char *propname, + int index) +{ + struct gpio_desc **dr; + struct gpio_desc *desc; + + dr = devres_alloc(devm_gpiod_release, sizeof(struct gpio_desc *), + GFP_KERNEL); + if (!dr) + return ERR_PTR(-ENOMEM); + + desc = fwnode_get_named_gpiod(child, propname, index); + if (IS_ERR(desc)) { + devres_free(dr); + return desc; + } + + *dr = desc; + devres_add(dev, dr); + + return desc; +} +EXPORT_SYMBOL(devm_get_named_gpiod_from_child); + +/** * devm_gpiod_get_index_optional - Resource-managed gpiod_get_index_optional() * @dev: GPIO consumer * @con_id: function within the GPIO consumer diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 94eb0a6..b098c7e 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1735,6 +1735,62 @@ struct gpio_desc *__must_check __gpiod_get_index(struct device *dev, EXPORT_SYMBOL_GPL(__gpiod_get_index);
/** + * fwnode_get_named_gpiod - obtain a GPIO from firmware node + * @fwnode: handle of the firmware node + * @propname: name of the firmware property + * @idx: index of the GPIO in the property value in case of many + * + * This function can be used for drivers that get their configuration + * from firmware. + * + * Function properly finds the corresponding GPIO using whatever is the + * underlying firmware interface and then makes sure that the GPIO + * descriptor is requested before it is returned to the caller. + * + * In case of error an ERR_PTR() is returned. + */ +struct gpio_desc *fwnode_get_named_gpiod(struct fwnode_handle *fwnode, + const char *propname, int index) +{ + struct gpio_desc *desc = ERR_PTR(-ENODEV); + bool active_low = false; + int ret; + + if (!fwnode) + return ERR_PTR(-EINVAL); + + if (is_of_node(fwnode)) { + enum of_gpio_flags flags; + + desc = of_get_named_gpiod_flags(of_node(fwnode), propname, + index, &flags); + if (!IS_ERR(desc)) + active_low = flags & OF_GPIO_ACTIVE_LOW; + } else if (is_acpi_node(fwnode)) { + struct acpi_gpio_info info; + + desc = acpi_get_gpiod_by_index(acpi_node(fwnode), propname, + index, &info); + if (!IS_ERR(desc)) + active_low = info.active_low; + } + + if (IS_ERR(desc)) + return desc; + + ret = gpiod_request(desc, NULL); + if (ret) + return ERR_PTR(ret); + + /* Only value flag can be set from both DT and ACPI is active_low */ + if (active_low) + set_bit(FLAG_ACTIVE_LOW, &desc->flags); + + return desc; +} +EXPORT_SYMBOL_GPL(fwnode_get_named_gpiod); + +/** * gpiod_get_index_optional - obtain an optional GPIO from a multi-index GPIO * function * @dev: GPIO consumer, can be NULL for system-global GPIOs diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 12f146f..b255ab4 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -94,6 +94,14 @@ int gpiod_to_irq(const struct gpio_desc *desc); struct gpio_desc *gpio_to_desc(unsigned gpio); int desc_to_gpio(const struct gpio_desc *desc);
+/* Child properties interface */ +struct fwnode_handle; + +struct gpio_desc *fwnode_get_named_gpiod(struct fwnode_handle *fwnode, + const char *propname, int index); +struct gpio_desc *devm_get_named_gpiod_from_child(struct device *dev, + struct fwnode_handle *child, + const char *propname, int index); #else /* CONFIG_GPIOLIB */
static inline struct gpio_desc *__must_check __gpiod_get(struct device *dev,
From: "Rafael J. Wysocki" rafael.j.wysocki@intel.com
Make use of device property API in this driver so that both OF and ACPI based system can use the same driver.
This change contains material from Max Eliaser and Mika Westerberg.
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Bryan Wu cooloney@gmail.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/leds/leds-gpio.c | 108 ++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 57 deletions(-)
diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index bc30168..bbf737d 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -1,7 +1,7 @@ /* * LEDs driver for GPIOs * - * Copyright (C) 2007 8, 4D Technologies inc. + * Copyright (C) 2007 8D Technologies inc. * Raphael Assenat raph@8d.com * Copyright (C) 2008 Freescale Semiconductor, Inc. * @@ -16,12 +16,10 @@ #include <linux/gpio/consumer.h> #include <linux/leds.h> #include <linux/module.h> -#include <linux/of.h> -#include <linux/of_gpio.h> -#include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/workqueue.h> +#include <linux/property.h>
struct gpio_led_data { struct led_classdev cdev; @@ -171,65 +169,59 @@ static inline int sizeof_gpio_leds_priv(int num_leds) (sizeof(struct gpio_led_data) * num_leds); }
-/* Code to create from OpenFirmware platform devices */ -#ifdef CONFIG_OF_GPIO -static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) +static int gpio_leds_create_led(struct device *dev, void *child, void *data) +{ + struct gpio_leds_priv *priv = data; + struct gpio_led led = {}; + const char *state = NULL; + + led.gpiod = devm_get_named_gpiod_from_child(dev, child, "gpios", 0); + if (IS_ERR(led.gpiod)) + return PTR_ERR(led.gpiod); + + device_child_property_read_string(dev, child, "label", &led.name); + device_child_property_read_string(dev, child, "linux,default-trigger", + &led.default_trigger); + + device_child_property_read_string(dev, child, "linux,default_state", + &state); + if (state) { + if (!strcmp(state, "keep")) + led.default_state = LEDS_GPIO_DEFSTATE_KEEP; + else if (!strcmp(state, "on")) + led.default_state = LEDS_GPIO_DEFSTATE_ON; + else + led.default_state = LEDS_GPIO_DEFSTATE_OFF; + } + + if (!device_get_child_property(dev, child, "retain-state-suspended", NULL)) + led.retain_state_suspended = 1; + + return create_gpio_led(&led, &priv->leds[priv->num_leds++], dev, NULL); +} + +static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node, *child; struct gpio_leds_priv *priv; - int count, ret; + int ret, count;
- /* count LEDs in this device, so we know how much to allocate */ - count = of_get_available_child_count(np); + count = device_get_child_node_count(&pdev->dev); if (!count) return ERR_PTR(-ENODEV);
- for_each_available_child_of_node(np, child) - if (of_get_gpio(child, 0) == -EPROBE_DEFER) - return ERR_PTR(-EPROBE_DEFER); - priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count), GFP_KERNEL); if (!priv) return ERR_PTR(-ENOMEM);
- for_each_available_child_of_node(np, child) { - struct gpio_led led = {}; - enum of_gpio_flags flags; - const char *state; - - led.gpio = of_get_gpio_flags(child, 0, &flags); - led.active_low = flags & OF_GPIO_ACTIVE_LOW; - led.name = of_get_property(child, "label", NULL) ? : child->name; - led.default_trigger = - of_get_property(child, "linux,default-trigger", NULL); - state = of_get_property(child, "default-state", NULL); - if (state) { - if (!strcmp(state, "keep")) - led.default_state = LEDS_GPIO_DEFSTATE_KEEP; - else if (!strcmp(state, "on")) - led.default_state = LEDS_GPIO_DEFSTATE_ON; - else - led.default_state = LEDS_GPIO_DEFSTATE_OFF; - } - - if (of_get_property(child, "retain-state-suspended", NULL)) - led.retain_state_suspended = 1; - - ret = create_gpio_led(&led, &priv->leds[priv->num_leds++], - &pdev->dev, NULL); - if (ret < 0) { - of_node_put(child); - goto err; - } + ret = device_for_each_child_node(&pdev->dev, gpio_leds_create_led, priv); + if (ret) { + for (count = priv->num_leds - 2; count >= 0; count--) + delete_gpio_led(&priv->leds[count]); + return ERR_PTR(ret); }
return priv; - -err: - for (count = priv->num_leds - 2; count >= 0; count--) - delete_gpio_led(&priv->leds[count]); - return ERR_PTR(-ENODEV); }
static const struct of_device_id of_gpio_leds_match[] = { @@ -238,12 +230,13 @@ static const struct of_device_id of_gpio_leds_match[] = { };
MODULE_DEVICE_TABLE(of, of_gpio_leds_match); -#else /* CONFIG_OF_GPIO */ -static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) -{ - return ERR_PTR(-ENODEV); -} -#endif /* CONFIG_OF_GPIO */ + +static const struct acpi_device_id acpi_gpio_leds_match[] = { + { "PRP0001" }, /* Device Tree shoehorned into ACPI */ + {}, +}; + +MODULE_DEVICE_TABLE(acpi, acpi_gpio_leds_match);
static int gpio_led_probe(struct platform_device *pdev) { @@ -271,7 +264,7 @@ static int gpio_led_probe(struct platform_device *pdev) } } } else { - priv = gpio_leds_create_of(pdev); + priv = gpio_leds_create(pdev); if (IS_ERR(priv)) return PTR_ERR(priv); } @@ -298,7 +291,8 @@ static struct platform_driver gpio_led_driver = { .driver = { .name = "leds-gpio", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(of_gpio_leds_match), + .of_match_table = of_gpio_leds_match, + .acpi_match_table = acpi_gpio_leds_match, }, };
From: Aaron Lu aaron.lu@intel.com
Make use of device property API in this driver so that both OF based system and ACPI based system can use this driver.
Signed-off-by: Aaron Lu aaron.lu@intel.com Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Dmitry Torokhov dmitry.torokhov@gmail.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/input/keyboard/gpio_keys_polled.c | 73 ++++++++++--------------------- 1 file changed, 24 insertions(+), 49 deletions(-)
diff --git a/drivers/input/keyboard/gpio_keys_polled.c b/drivers/input/keyboard/gpio_keys_polled.c index b7a514c..bf5804b 100644 --- a/drivers/input/keyboard/gpio_keys_polled.c +++ b/drivers/input/keyboard/gpio_keys_polled.c @@ -25,9 +25,7 @@ #include <linux/gpio.h> #include <linux/gpio/consumer.h> #include <linux/gpio_keys.h> -#include <linux/of.h> -#include <linux/of_platform.h> -#include <linux/of_gpio.h> +#include <linux/property.h>
#define DRV_NAME "gpio-keys-polled"
@@ -102,21 +100,15 @@ static void gpio_keys_polled_close(struct input_polled_dev *dev) pdata->disable(bdev->dev); }
-#ifdef CONFIG_OF static struct gpio_keys_platform_data *gpio_keys_polled_get_devtree_pdata(struct device *dev) { - struct device_node *node, *pp; struct gpio_keys_platform_data *pdata; struct gpio_keys_button *button; + struct fwnode_handle *child; int error; int nbuttons; - int i; - - node = dev->of_node; - if (!node) - return NULL;
- nbuttons = of_get_child_count(node); + nbuttons = device_get_child_node_count(dev); if (nbuttons == 0) return NULL;
@@ -126,52 +118,44 @@ static struct gpio_keys_platform_data *gpio_keys_polled_get_devtree_pdata(struct return ERR_PTR(-ENOMEM);
pdata->buttons = (struct gpio_keys_button *)(pdata + 1); - pdata->nbuttons = nbuttons;
- pdata->rep = !!of_get_property(node, "autorepeat", NULL); - of_property_read_u32(node, "poll-interval", &pdata->poll_interval); + pdata->rep = device_property_present(dev, "autorepeat"); + device_property_read_u32(dev, "poll-interval", &pdata->poll_interval);
- i = 0; - for_each_child_of_node(node, pp) { - int gpio; - enum of_gpio_flags flags; - - if (!of_find_property(pp, "gpios", NULL)) { - pdata->nbuttons--; - dev_warn(dev, "Found button without gpios\n"); - continue; - } + device_for_each_child_node(dev, child) { + struct gpio_desc *desc;
- gpio = of_get_gpio_flags(pp, 0, &flags); - if (gpio < 0) { - error = gpio; + desc = devm_get_named_gpiod_from_child(dev, child, "gpios", 0); + if (IS_ERR(desc)) { + error = PTR_ERR(desc); if (error != -EPROBE_DEFER) dev_err(dev, "Failed to get gpio flags, error: %d\n", error); + fwnode_handle_put(child); return ERR_PTR(error); }
- button = &pdata->buttons[i++]; + button = &pdata->buttons[pdata->nbuttons++]; + button->gpiod = desc;
- button->gpio = gpio; - button->active_low = flags & OF_GPIO_ACTIVE_LOW; - - if (of_property_read_u32(pp, "linux,code", &button->code)) { - dev_err(dev, "Button without keycode: 0x%x\n", - button->gpio); + if (fwnode_property_read_u32(child, "linux,code", &button->code)) { + dev_err(dev, "Button without keycode: %d\n", + pdata->nbuttons - 1); + fwnode_handle_put(child); return ERR_PTR(-EINVAL); }
- button->desc = of_get_property(pp, "label", NULL); + fwnode_property_read_string(child, "label", &button->desc);
- if (of_property_read_u32(pp, "linux,input-type", &button->type)) + if (fwnode_property_read_u32(child, "linux,input-type", + &button->type)) button->type = EV_KEY;
- button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL); + button->wakeup = fwnode_property_present(child, "gpio-key,wakeup");
- if (of_property_read_u32(pp, "debounce-interval", - &button->debounce_interval)) + if (fwnode_property_read_u32(child, "debounce-interval", + &button->debounce_interval)) button->debounce_interval = 5; }
@@ -187,15 +171,6 @@ static const struct of_device_id gpio_keys_polled_of_match[] = { }; MODULE_DEVICE_TABLE(of, gpio_keys_polled_of_match);
-#else - -static inline struct gpio_keys_platform_data * -gpio_keys_polled_get_devtree_pdata(struct device *dev) -{ - return NULL; -} -#endif - static int gpio_keys_polled_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -323,7 +298,7 @@ static struct platform_driver gpio_keys_polled_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, - .of_match_table = of_match_ptr(gpio_keys_polled_of_match), + .of_match_table = gpio_keys_polled_of_match, }, }; module_platform_driver(gpio_keys_polled_driver);
From: Tom Lendacky thomas.lendacky@amd.com
This patch provides ACPI support for the AMD 10GbE device driver and AMD 10GbE phy driver.
Signed-off-by: Tom Lendacky thomas.lendacky@amd.com --- drivers/net/ethernet/amd/Kconfig | 2 +- drivers/net/ethernet/amd/xgbe/xgbe-dev.c | 4 +- drivers/net/ethernet/amd/xgbe/xgbe-main.c | 288 +++++++++++++++++++++++------- drivers/net/ethernet/amd/xgbe/xgbe-mdio.c | 20 +-- drivers/net/ethernet/amd/xgbe/xgbe-ptp.c | 4 +- drivers/net/ethernet/amd/xgbe/xgbe.h | 10 ++ drivers/net/phy/Kconfig | 2 +- drivers/net/phy/amd-xgbe-phy.c | 247 ++++++++++++++++++------- 8 files changed, 422 insertions(+), 155 deletions(-)
diff --git a/drivers/net/ethernet/amd/Kconfig b/drivers/net/ethernet/amd/Kconfig index 8319c99..6feb6ef3 100644 --- a/drivers/net/ethernet/amd/Kconfig +++ b/drivers/net/ethernet/amd/Kconfig @@ -179,7 +179,7 @@ config SUNLANCE
config AMD_XGBE tristate "AMD 10GbE Ethernet driver" - depends on OF_NET + depends on OF_NET || ACPI select PHYLIB select AMD_XGBE_PHY select BITREVERSE diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c index 1c03abb..a34cad2 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c @@ -130,7 +130,7 @@ static unsigned int xgbe_usec_to_riwt(struct xgbe_prv_data *pdata,
DBGPR("-->xgbe_usec_to_riwt\n");
- rate = clk_get_rate(pdata->sysclk); + rate = pdata->sysclk_rate;
/* * Convert the input usec value to the watchdog timer value. Each @@ -153,7 +153,7 @@ static unsigned int xgbe_riwt_to_usec(struct xgbe_prv_data *pdata,
DBGPR("-->xgbe_riwt_to_usec\n");
- rate = clk_get_rate(pdata->sysclk); + rate = pdata->sysclk_rate;
/* * Convert the input watchdog timer value to the usec value. Each diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-main.c b/drivers/net/ethernet/amd/xgbe/xgbe-main.c index 3809d1c..338c0ed 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-main.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-main.c @@ -124,6 +124,7 @@ #include <linux/of.h> #include <linux/of_net.h> #include <linux/clk.h> +#include <linux/acpi.h>
#include "xgbe.h" #include "xgbe-common.h" @@ -215,6 +216,205 @@ static void xgbe_init_all_fptrs(struct xgbe_prv_data *pdata) xgbe_init_function_ptrs_desc(&pdata->desc_if); }
+static int xgbe_map_resources(struct xgbe_prv_data *pdata) +{ + struct platform_device *pdev = pdata->pdev; + struct device *dev = pdata->dev; + struct resource *res; + + /* Obtain the mmio areas for the device */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pdata->xgmac_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(pdata->xgmac_regs)) { + dev_err(dev, "xgmac ioremap failed\n"); + return PTR_ERR(pdata->xgmac_regs); + } + DBGPR(" xgmac_regs = %p\n", pdata->xgmac_regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + pdata->xpcs_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(pdata->xpcs_regs)) { + dev_err(dev, "xpcs ioremap failed\n"); + return PTR_ERR(pdata->xpcs_regs); + } + DBGPR(" xpcs_regs = %p\n", pdata->xpcs_regs); + + return 0; +} + +#ifdef CONFIG_ACPI +static int xgbe_acpi_support(struct xgbe_prv_data *pdata) +{ + struct acpi_device *adev = pdata->adev; + struct device *dev = pdata->dev; + const union acpi_object *property; + acpi_status status; + u64 cca; + unsigned int i; + int ret; + + /* Map the memory resources */ + ret = xgbe_map_resources(pdata); + if (ret) + return ret; + + /* Obtain the system clock setting */ + ret = acpi_dev_get_property(adev, XGBE_ACPI_DMA_FREQ, ACPI_TYPE_INTEGER, + &property); + if (ret) { + dev_err(dev, "unable to obtain %s acpi property\n", + XGBE_ACPI_DMA_FREQ); + return ret; + } + pdata->sysclk_rate = property->integer.value; + + /* Obtain the PTP clock setting */ + ret = acpi_dev_get_property(adev, XGBE_ACPI_PTP_FREQ, ACPI_TYPE_INTEGER, + &property); + if (ret) { + dev_err(dev, "unable to obtain %s acpi property\n", + XGBE_ACPI_PTP_FREQ); + return ret; + } + pdata->ptpclk_rate = property->integer.value; + + /* Retrieve the MAC address */ + ret = acpi_dev_get_property_array(adev, XGBE_ACPI_MAC_ADDR, + ACPI_TYPE_INTEGER, &property); + if (ret) { + dev_err(dev, "unable to obtain %s acpi property\n", + XGBE_ACPI_MAC_ADDR); + return ret; + } + if (property->package.count != 6) { + dev_err(dev, "invalid %s acpi property\n", + XGBE_ACPI_MAC_ADDR); + return -EINVAL; + } + for (i = 0; i < property->package.count; i++) { + union acpi_object *obj = &property->package.elements[i]; + + pdata->mac_addr[i] = (u8)obj->integer.value; + } + if (!is_valid_ether_addr(pdata->mac_addr)) { + dev_err(dev, "invalid %s acpi property\n", + XGBE_ACPI_MAC_ADDR); + return -EINVAL; + } + + /* Retrieve the PHY mode - it must be "xgmii" */ + ret = acpi_dev_get_property(adev, XGBE_ACPI_PHY_MODE, ACPI_TYPE_STRING, + &property); + if (ret) { + dev_err(dev, "unable to obtain %s acpi property\n", + XGBE_ACPI_PHY_MODE); + return ret; + } + if (strcmp(property->string.pointer, + phy_modes(PHY_INTERFACE_MODE_XGMII))) { + dev_err(dev, "invalid %s acpi property\n", + XGBE_ACPI_PHY_MODE); + return -EINVAL; + } + pdata->phy_mode = PHY_INTERFACE_MODE_XGMII; + +#ifndef METHOD_NAME__CCA +#define METHOD_NAME__CCA "_CCA" +#endif + /* Set the device cache coherency values */ + if (acpi_has_method(adev->handle, METHOD_NAME__CCA)) { + status = acpi_evaluate_integer(adev->handle, METHOD_NAME__CCA, + NULL, &cca); + if (ACPI_FAILURE(status)) { + dev_err(dev, "error obtaining acpi _CCA method\n"); + return -EINVAL; + } + } else { + cca = 0; + } + + if (cca) { + pdata->axdomain = XGBE_DMA_OS_AXDOMAIN; + pdata->arcache = XGBE_DMA_OS_ARCACHE; + pdata->awcache = XGBE_DMA_OS_AWCACHE; + } else { + pdata->axdomain = XGBE_DMA_SYS_AXDOMAIN; + pdata->arcache = XGBE_DMA_SYS_ARCACHE; + pdata->awcache = XGBE_DMA_SYS_AWCACHE; + } + + return 0; +} +#else /* CONFIG_ACPI */ +static int xgbe_acpi_support(struct xgbe_prv_data *pdata) +{ + return -EINVAL; +} +#endif /* CONFIG_ACPI */ + +#ifdef CONFIG_OF +static int xgbe_of_support(struct xgbe_prv_data *pdata) +{ + struct device *dev = pdata->dev; + const u8 *mac_addr; + int ret; + + /* Map the memory resources */ + ret = xgbe_map_resources(pdata); + if (ret) + return ret; + + /* Obtain the system clock setting */ + pdata->sysclk = devm_clk_get(dev, XGBE_DMA_CLOCK); + if (IS_ERR(pdata->sysclk)) { + dev_err(dev, "dma devm_clk_get failed\n"); + return PTR_ERR(pdata->sysclk); + } + pdata->sysclk_rate = clk_get_rate(pdata->sysclk); + + /* Obtain the PTP clock setting */ + pdata->ptpclk = devm_clk_get(dev, XGBE_PTP_CLOCK); + if (IS_ERR(pdata->ptpclk)) { + dev_err(dev, "ptp devm_clk_get failed\n"); + return PTR_ERR(pdata->ptpclk); + } + pdata->ptpclk_rate = clk_get_rate(pdata->ptpclk); + + /* Retrieve the MAC address */ + mac_addr = of_get_mac_address(dev->of_node); + if (!mac_addr) { + dev_err(dev, "invalid mac address for this device\n"); + return -EINVAL; + } + memcpy(pdata->mac_addr, mac_addr, ETH_ALEN); + + /* Retrieve the PHY mode - it must be "xgmii" */ + pdata->phy_mode = of_get_phy_mode(dev->of_node); + if (pdata->phy_mode != PHY_INTERFACE_MODE_XGMII) { + dev_err(dev, "invalid phy-mode specified for this device\n"); + return -EINVAL; + } + + /* Set the device cache coherency values */ + if (of_property_read_bool(dev->of_node, "dma-coherent")) { + pdata->axdomain = XGBE_DMA_OS_AXDOMAIN; + pdata->arcache = XGBE_DMA_OS_ARCACHE; + pdata->awcache = XGBE_DMA_OS_AWCACHE; + } else { + pdata->axdomain = XGBE_DMA_SYS_AXDOMAIN; + pdata->arcache = XGBE_DMA_SYS_ARCACHE; + pdata->awcache = XGBE_DMA_SYS_AWCACHE; + } + + return 0; +} +#else /* CONFIG_OF */ +static int xgbe_of_support(struct xgbe_prv_data *pdata) +{ + return -EINVAL; +} +#endif /*CONFIG_OF */ + static int xgbe_probe(struct platform_device *pdev) { struct xgbe_prv_data *pdata; @@ -222,8 +422,6 @@ static int xgbe_probe(struct platform_device *pdev) struct xgbe_desc_if *desc_if; struct net_device *netdev; struct device *dev = &pdev->dev; - struct resource *res; - const u8 *mac_addr; int ret;
DBGPR("--> xgbe_probe\n"); @@ -239,6 +437,7 @@ static int xgbe_probe(struct platform_device *pdev) pdata = netdev_priv(netdev); pdata->netdev = netdev; pdata->pdev = pdev; + pdata->adev = ACPI_COMPANION(dev); pdata->dev = dev; platform_set_drvdata(pdev, netdev);
@@ -264,40 +463,13 @@ static int xgbe_probe(struct platform_device *pdev) goto err_io; }
- /* Obtain the system clock setting */ - pdata->sysclk = devm_clk_get(dev, XGBE_DMA_CLOCK); - if (IS_ERR(pdata->sysclk)) { - dev_err(dev, "dma devm_clk_get failed\n"); - ret = PTR_ERR(pdata->sysclk); - goto err_io; - } - - /* Obtain the PTP clock setting */ - pdata->ptpclk = devm_clk_get(dev, XGBE_PTP_CLOCK); - if (IS_ERR(pdata->ptpclk)) { - dev_err(dev, "ptp devm_clk_get failed\n"); - ret = PTR_ERR(pdata->ptpclk); - goto err_io; - } - - /* Obtain the mmio areas for the device */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - pdata->xgmac_regs = devm_ioremap_resource(dev, res); - if (IS_ERR(pdata->xgmac_regs)) { - dev_err(dev, "xgmac ioremap failed\n"); - ret = PTR_ERR(pdata->xgmac_regs); - goto err_io; - } - DBGPR(" xgmac_regs = %p\n", pdata->xgmac_regs); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - pdata->xpcs_regs = devm_ioremap_resource(dev, res); - if (IS_ERR(pdata->xpcs_regs)) { - dev_err(dev, "xpcs ioremap failed\n"); - ret = PTR_ERR(pdata->xpcs_regs); + /* Obtain device settings */ + if (pdata->adev && !acpi_disabled) + ret = xgbe_acpi_support(pdata); + else + ret = xgbe_of_support(pdata); + if (ret) goto err_io; - } - DBGPR(" xpcs_regs = %p\n", pdata->xpcs_regs);
/* Set the DMA mask */ if (!dev->dma_mask) @@ -308,23 +480,16 @@ static int xgbe_probe(struct platform_device *pdev) goto err_io; }
- if (of_property_read_bool(dev->of_node, "dma-coherent")) { - pdata->axdomain = XGBE_DMA_OS_AXDOMAIN; - pdata->arcache = XGBE_DMA_OS_ARCACHE; - pdata->awcache = XGBE_DMA_OS_AWCACHE; - } else { - pdata->axdomain = XGBE_DMA_SYS_AXDOMAIN; - pdata->arcache = XGBE_DMA_SYS_ARCACHE; - pdata->awcache = XGBE_DMA_SYS_AWCACHE; - } - + /* Get the device interrupt */ ret = platform_get_irq(pdev, 0); if (ret < 0) { dev_err(dev, "platform_get_irq failed\n"); goto err_io; } + netdev->irq = ret; netdev->base_addr = (unsigned long)pdata->xgmac_regs; + memcpy(netdev->dev_addr, pdata->mac_addr, netdev->addr_len);
/* Set all the function pointers */ xgbe_init_all_fptrs(pdata); @@ -337,23 +502,6 @@ static int xgbe_probe(struct platform_device *pdev) /* Populate the hardware features */ xgbe_get_all_hw_features(pdata);
- /* Retrieve the MAC address */ - mac_addr = of_get_mac_address(dev->of_node); - if (!mac_addr) { - dev_err(dev, "invalid mac address for this device\n"); - ret = -EINVAL; - goto err_io; - } - memcpy(netdev->dev_addr, mac_addr, netdev->addr_len); - - /* Retrieve the PHY mode - it must be "xgmii" */ - pdata->phy_mode = of_get_phy_mode(dev->of_node); - if (pdata->phy_mode != PHY_INTERFACE_MODE_XGMII) { - dev_err(dev, "invalid phy-mode specified for this device\n"); - ret = -EINVAL; - goto err_io; - } - /* Set default configuration data */ xgbe_default_config(pdata);
@@ -531,11 +679,22 @@ static int xgbe_resume(struct device *dev) } #endif /* CONFIG_PM */
+#ifdef CONFIG_ACPI +static const struct acpi_device_id xgbe_acpi_match[] = { + { "AMDI8000", 0 }, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, xgbe_acpi_match); +#endif + +#ifdef CONFIG_OF static const struct of_device_id xgbe_of_match[] = { { .compatible = "amd,xgbe-seattle-v0a", }, { .compatible = "amd,xgbe-seattle-v1a", }, {}, }; +#endif
MODULE_DEVICE_TABLE(of, xgbe_of_match); static SIMPLE_DEV_PM_OPS(xgbe_pm_ops, xgbe_suspend, xgbe_resume); @@ -543,7 +702,12 @@ static SIMPLE_DEV_PM_OPS(xgbe_pm_ops, xgbe_suspend, xgbe_resume); static struct platform_driver xgbe_driver = { .driver = { .name = "amd-xgbe", +#ifdef CONFIG_ACPI + .acpi_match_table = xgbe_acpi_match, +#endif +#ifdef CONFIG_OF .of_match_table = xgbe_of_match, +#endif .pm = &xgbe_pm_ops, }, .probe = xgbe_probe, diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c b/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c index 363b210..5d2c89b 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-mdio.c @@ -119,6 +119,7 @@ #include <linux/mdio.h> #include <linux/phy.h> #include <linux/of.h> +#include <linux/acpi.h>
#include "xgbe.h" #include "xgbe-common.h" @@ -205,25 +206,16 @@ void xgbe_dump_phy_registers(struct xgbe_prv_data *pdata)
int xgbe_mdio_register(struct xgbe_prv_data *pdata) { - struct device_node *phy_node; struct mii_bus *mii; struct phy_device *phydev; int ret = 0;
DBGPR("-->xgbe_mdio_register\n");
- /* Retrieve the phy-handle */ - phy_node = of_parse_phandle(pdata->dev->of_node, "phy-handle", 0); - if (!phy_node) { - dev_err(pdata->dev, "unable to parse phy-handle\n"); - return -EINVAL; - } - mii = mdiobus_alloc(); if (mii == NULL) { dev_err(pdata->dev, "mdiobus_alloc failed\n"); - ret = -ENOMEM; - goto err_node_get; + return -ENOMEM; }
/* Register on the MDIO bus (don't probe any PHYs) */ @@ -252,12 +244,9 @@ int xgbe_mdio_register(struct xgbe_prv_data *pdata) request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phydev->c45_ids.device_ids[MDIO_MMD_PCS]));
- of_node_get(phy_node); - phydev->dev.of_node = phy_node; ret = phy_device_register(phydev); if (ret) { dev_err(pdata->dev, "phy_device_register failed\n"); - of_node_put(phy_node); goto err_phy_device; }
@@ -283,8 +272,6 @@ int xgbe_mdio_register(struct xgbe_prv_data *pdata)
pdata->phydev = phydev;
- of_node_put(phy_node); - DBGPHY_REGS(pdata);
DBGPR("<--xgbe_mdio_register\n"); @@ -300,9 +287,6 @@ err_mdiobus_register: err_mdiobus_alloc: mdiobus_free(mii);
-err_node_get: - of_node_put(phy_node); - return ret; }
diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-ptp.c b/drivers/net/ethernet/amd/xgbe/xgbe-ptp.c index a1bf9d1c..fa67203 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-ptp.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-ptp.c @@ -239,7 +239,7 @@ void xgbe_ptp_register(struct xgbe_prv_data *pdata) snprintf(info->name, sizeof(info->name), "%s", netdev_name(pdata->netdev)); info->owner = THIS_MODULE; - info->max_adj = clk_get_rate(pdata->ptpclk); + info->max_adj = pdata->ptpclk_rate; info->adjfreq = xgbe_adjfreq; info->adjtime = xgbe_adjtime; info->gettime = xgbe_gettime; @@ -260,7 +260,7 @@ void xgbe_ptp_register(struct xgbe_prv_data *pdata) */ dividend = 50000000; dividend <<= 32; - pdata->tstamp_addend = div_u64(dividend, clk_get_rate(pdata->ptpclk)); + pdata->tstamp_addend = div_u64(dividend, pdata->ptpclk_rate);
/* Setup the timecounter */ cc->read = xgbe_cc_read; diff --git a/drivers/net/ethernet/amd/xgbe/xgbe.h b/drivers/net/ethernet/amd/xgbe/xgbe.h index 5636db4..59498eb 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe.h +++ b/drivers/net/ethernet/amd/xgbe/xgbe.h @@ -172,6 +172,12 @@ #define XGBE_DMA_CLOCK "dma_clk" #define XGBE_PTP_CLOCK "ptp_clk"
+/* ACPI property names */ +#define XGBE_ACPI_MAC_ADDR "mac-address" +#define XGBE_ACPI_PHY_MODE "phy-mode" +#define XGBE_ACPI_DMA_FREQ "amd,dma-freq" +#define XGBE_ACPI_PTP_FREQ "amd,ptp-freq" + /* Timestamp support - values based on 50MHz PTP clock * 50MHz => 20 nsec */ @@ -572,6 +578,7 @@ struct xgbe_hw_features { struct xgbe_prv_data { struct net_device *netdev; struct platform_device *pdev; + struct acpi_device *adev; struct device *dev;
/* XGMAC/XPCS related mmio registers */ @@ -652,6 +659,7 @@ struct xgbe_prv_data { unsigned int phy_rx_pause;
/* Netdev related settings */ + unsigned char mac_addr[MAX_ADDR_LEN]; netdev_features_t netdev_features; struct napi_struct napi; struct xgbe_mmc_stats mmc_stats; @@ -661,7 +669,9 @@ struct xgbe_prv_data {
/* Device clocks */ struct clk *sysclk; + unsigned long sysclk_rate; struct clk *ptpclk; + unsigned long ptpclk_rate;
/* Timestamp support */ spinlock_t tstamp_lock; diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 75472cf7..bacafe2 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -26,7 +26,7 @@ config AMD_PHY
config AMD_XGBE_PHY tristate "Driver for the AMD 10GbE (amd-xgbe) PHYs" - depends on OF + depends on OF || ACPI ---help--- Currently supports the AMD 10GbE PHY
diff --git a/drivers/net/phy/amd-xgbe-phy.c b/drivers/net/phy/amd-xgbe-phy.c index 90145d9..d852c6e 100644 --- a/drivers/net/phy/amd-xgbe-phy.c +++ b/drivers/net/phy/amd-xgbe-phy.c @@ -74,6 +74,7 @@ #include <linux/of_platform.h> #include <linux/of_device.h> #include <linux/uaccess.h> +#include <linux/acpi.h>
MODULE_AUTHOR("Tom Lendacky thomas.lendacky@amd.com"); @@ -252,12 +253,13 @@ enum amd_xgbe_phy_mode { };
enum amd_xgbe_phy_speedset { - AMD_XGBE_PHY_SPEEDSET_1000_10000, + AMD_XGBE_PHY_SPEEDSET_1000_10000 = 0, AMD_XGBE_PHY_SPEEDSET_2500_10000, };
struct amd_xgbe_phy_priv { struct platform_device *pdev; + struct acpi_device *adev; struct device *dev;
struct phy_device *phydev; @@ -1238,54 +1240,28 @@ unlock: return ret; }
-static int amd_xgbe_phy_probe(struct phy_device *phydev) +static int amd_xgbe_phy_map_resources(struct amd_xgbe_phy_priv *priv, + struct platform_device *phy_pdev, + unsigned int phy_resnum) { - struct amd_xgbe_phy_priv *priv; - struct platform_device *pdev; - struct device *dev; - char *wq_name; - const __be32 *property; - unsigned int speed_set; + struct device *dev = priv->dev; int ret;
- if (!phydev->dev.of_node) - return -EINVAL; - - pdev = of_find_device_by_node(phydev->dev.of_node); - if (!pdev) - return -EINVAL; - dev = &pdev->dev; - - wq_name = kasprintf(GFP_KERNEL, "%s-amd-xgbe-phy", phydev->bus->name); - if (!wq_name) { - ret = -ENOMEM; - goto err_pdev; - } - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) { - ret = -ENOMEM; - goto err_name; - } - - priv->pdev = pdev; - priv->dev = dev; - priv->phydev = phydev; - /* Get the device mmio areas */ - priv->rxtx_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->rxtx_res = platform_get_resource(phy_pdev, IORESOURCE_MEM, + phy_resnum++); priv->rxtx_regs = devm_ioremap_resource(dev, priv->rxtx_res); if (IS_ERR(priv->rxtx_regs)) { dev_err(dev, "rxtx ioremap failed\n"); - ret = PTR_ERR(priv->rxtx_regs); - goto err_priv; + return PTR_ERR(priv->rxtx_regs); }
/* All xgbe phy devices share the CMU registers so retrieve * the resource and do the ioremap directly rather than * the devm_ioremap_resource call */ - priv->cmu_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->cmu_res = platform_get_resource(phy_pdev, IORESOURCE_MEM, + phy_resnum++); if (!priv->cmu_res) { dev_err(dev, "cmu invalid resource\n"); ret = -EINVAL; @@ -1299,41 +1275,187 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) goto err_rxtx; }
+ return 0; + +err_rxtx: + devm_iounmap(dev, priv->rxtx_regs); + devm_release_mem_region(dev, priv->rxtx_res->start, + resource_size(priv->rxtx_res)); + + return ret; +} + +static void amd_xgbe_phy_unmap_resources(struct amd_xgbe_phy_priv *priv) +{ + struct device *dev = priv->dev; + + devm_iounmap(dev, priv->cmu_regs); + + devm_iounmap(dev, priv->rxtx_regs); + devm_release_mem_region(dev, priv->rxtx_res->start, + resource_size(priv->rxtx_res)); +} + +#ifdef CONFIG_ACPI +static int amd_xgbe_phy_acpi_support(struct amd_xgbe_phy_priv *priv) +{ + struct platform_device *phy_pdev = priv->pdev; + struct acpi_device *adev = priv->adev; + struct device *dev = priv->dev; + const union acpi_object *property; + int ret; + + /* Map the memory resources */ + ret = amd_xgbe_phy_map_resources(priv, phy_pdev, 2); + if (ret) + return ret; + /* Get the device serdes channel property */ - property = of_get_property(dev->of_node, XGBE_PHY_CHANNEL_PROPERTY, - NULL); + ret = acpi_dev_get_property(adev, XGBE_PHY_CHANNEL_PROPERTY, + ACPI_TYPE_INTEGER, &property); + if (ret) { + dev_err(dev, "unable to obtain %s acpi property\n", + XGBE_PHY_CHANNEL_PROPERTY); + goto err_resources; + } + priv->serdes_channel = property->integer.value; + + /* Get the device speed set property */ + ret = acpi_dev_get_property(adev, XGBE_PHY_SPEEDSET_PROPERTY, + ACPI_TYPE_INTEGER, &property); + if (ret) { + dev_err(dev, "unable to obtain %s acpi property\n", + XGBE_PHY_SPEEDSET_PROPERTY); + goto err_resources; + } + priv->speed_set = property->integer.value; + + return 0; + +err_resources: + amd_xgbe_phy_unmap_resources(priv); + + return ret; +} +#else /* CONFIG_ACPI */ +static int amd_xgbe_phy_acpi_support(struct amd_xgbe_phy_priv *priv) +{ + return -EINVAL; +} +#endif /* CONFIG_ACPI */ + +#ifdef CONFIG_OF +static int amd_xgbe_phy_of_support(struct amd_xgbe_phy_priv *priv) +{ + struct platform_device *phy_pdev; + struct device_node *bus_node; + struct device_node *phy_node; + struct device *dev = priv->dev; + const __be32 *property; + int ret; + + bus_node = priv->dev->of_node; + phy_node = of_parse_phandle(bus_node, "phy-handle", 0); + if (!phy_node) { + dev_err(dev, "unable to parse phy-handle\n"); + return -EINVAL; + } + + phy_pdev = of_find_device_by_node(phy_node); + if (!phy_pdev) { + dev_err(dev, "unable to obtain phy device\n"); + ret = -EINVAL; + goto err_put; + } + + /* Map the memory resources */ + ret = amd_xgbe_phy_map_resources(priv, phy_pdev, 0); + if (ret) + goto err_put; + + /* Get the device serdes channel property */ + property = of_get_property(phy_node, XGBE_PHY_CHANNEL_PROPERTY, NULL); if (!property) { - dev_err(dev, "unable to obtain serdes_channel property\n"); + dev_err(dev, "unable to obtain %s property\n", + XGBE_PHY_CHANNEL_PROPERTY); ret = -EINVAL; - goto err_cmu; + goto err_resources; } priv->serdes_channel = be32_to_cpu(*property);
/* Get the device speed set property */ - speed_set = 0; - property = of_get_property(dev->of_node, XGBE_PHY_SPEEDSET_PROPERTY, - NULL); + property = of_get_property(phy_node, XGBE_PHY_SPEEDSET_PROPERTY, NULL); if (property) - speed_set = be32_to_cpu(*property); + priv->speed_set = be32_to_cpu(*property);
- switch (speed_set) { - case 0: - priv->speed_set = AMD_XGBE_PHY_SPEEDSET_1000_10000; - break; - case 1: - priv->speed_set = AMD_XGBE_PHY_SPEEDSET_2500_10000; + of_node_put(phy_node); + + return 0; + +err_resources: + amd_xgbe_phy_unmap_resources(priv); + +err_put: + of_node_put(phy_node); + + return ret; +} +#else /* CONFIG_OF */ +static int amd_xgbe_phy_of_support(struct amd_xgbe_phy_priv *priv) +{ + return -EINVAL; +} +#endif /* CONFIG_OF */ + +static int amd_xgbe_phy_probe(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv; + struct device *dev; + char *wq_name; + int ret; + + if (!phydev->bus || !phydev->bus->parent) + return -EINVAL; + + dev = phydev->bus->parent; + + wq_name = kasprintf(GFP_KERNEL, "%s-amd-xgbe-phy", phydev->bus->name); + if (!wq_name) + return -ENOMEM; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto err_name; + } + + priv->pdev = to_platform_device(dev); + priv->adev = ACPI_COMPANION(dev); + priv->dev = dev; + priv->phydev = phydev; + + if (priv->adev && !acpi_disabled) + ret = amd_xgbe_phy_acpi_support(priv); + else + ret = amd_xgbe_phy_of_support(priv); + if (ret) + goto err_priv; + + switch (priv->speed_set) { + case AMD_XGBE_PHY_SPEEDSET_1000_10000: + case AMD_XGBE_PHY_SPEEDSET_2500_10000: break; default: dev_err(dev, "invalid amd,speed-set property\n"); ret = -EINVAL; - goto err_cmu; + goto err_resources; }
priv->link = 1;
ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); if (ret < 0) - goto err_cmu; + goto err_resources; if ((ret & MDIO_PCS_CTRL2_TYPE) == MDIO_PCS_CTRL2_10GBR) priv->mode = AMD_XGBE_MODE_KR; else @@ -1344,23 +1466,17 @@ static int amd_xgbe_phy_probe(struct phy_device *phydev) priv->an_workqueue = create_singlethread_workqueue(wq_name); if (!priv->an_workqueue) { ret = -ENOMEM; - goto err_cmu; + goto err_resources; }
phydev->priv = priv;
kfree(wq_name); - of_dev_put(pdev);
return 0;
-err_cmu: - devm_iounmap(dev, priv->cmu_regs); - -err_rxtx: - devm_iounmap(dev, priv->rxtx_regs); - devm_release_mem_region(dev, priv->rxtx_res->start, - resource_size(priv->rxtx_res)); +err_resources: + amd_xgbe_phy_unmap_resources(priv);
err_priv: devm_kfree(dev, priv); @@ -1368,9 +1484,6 @@ err_priv: err_name: kfree(wq_name);
-err_pdev: - of_dev_put(pdev); - return ret; }
@@ -1387,11 +1500,7 @@ static void amd_xgbe_phy_remove(struct phy_device *phydev) flush_workqueue(priv->an_workqueue); destroy_workqueue(priv->an_workqueue);
- devm_iounmap(dev, priv->cmu_regs); - - devm_iounmap(dev, priv->rxtx_regs); - devm_release_mem_region(dev, priv->rxtx_res->start, - resource_size(priv->rxtx_res)); + amd_xgbe_phy_unmap_resources(priv);
devm_kfree(dev, priv); }
On Wednesday 05 November 2014 20:32:45 al.stone@linaro.org wrote:
From: Tom Lendacky thomas.lendacky@amd.com
This patch provides ACPI support for the AMD 10GbE device driver and AMD 10GbE phy driver.
Signed-off-by: Tom Lendacky thomas.lendacky@amd.com
I'm worried about the definition of the binding for the ACPI properties for this one, we really need to discuss how this is supposed to look like, in a larger forum than this mailing list.
What the driver does at the moment is:
- Use the _DSD definition with the DT properties extension to pass data for ACPI based systems. - identify the device as "AMDI8000" on ACPI, and using a compatible string when booting with DT - have some shared properties (phy-mode, mac-address, possibly others) - use the phy and clk binding on DT, but use undocumented properties on ACPI - use the low-level DT and ACPI property accessors, not the generalized device accessors, keeping the two code paths separate in all cases.
Lack of interface documentation is definitely a problem, and that's easy enough to solve. The part that I personally think we should avoid is having partially compatible bindings. Instead we should do one of two things:
a) keep the bindings completely separate, using a binary representation for the ACPI interface that uses normal ACPI tables, whatever they would be, instead of the _DSD named properties.
b) extend the DT binding to describe the superset, use the _PRP0001 ACPI type and use a common implementation with the new device properties API, without the driver having to know where the data comes from. This approach first needs consensus on whether we want to use the DT named properties on ACPI based ARM64 servers or not.
Arnd
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/acpi/utils.c | 26 ++++++++++++++++++++++++++ include/acpi/acpi_bus.h | 2 ++ 2 files changed, 28 insertions(+)
diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 371ac12..af325a7 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -723,3 +723,29 @@ bool acpi_check_dsm(acpi_handle handle, const u8 *uuid, int rev, u64 funcs) return false; } EXPORT_SYMBOL(acpi_check_dsm); + +/** + * acpi_check_coherency - check for memory coherency of a device + * @handle: ACPI device handle + * @val: Pointer to returned value + * + * Search a device and its parents for a _CCA method and return + * its value. + */ +acpi_status acpi_check_coherency(acpi_handle handle, int *val) +{ + unsigned long long data; + acpi_status status; + + do { + status = acpi_evaluate_integer(handle, "_CCA", NULL, &data); + if (!ACPI_FAILURE(status)) { + *val = data; + break; + } + status = acpi_get_parent(handle, &handle); + } while (!ACPI_FAILURE(status)); + + return status; +} +EXPORT_SYMBOL(acpi_check_coherency); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a361f43..0d4ce14 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -69,6 +69,8 @@ bool acpi_check_dsm(acpi_handle handle, const u8 *uuid, int rev, u64 funcs); union acpi_object *acpi_evaluate_dsm(acpi_handle handle, const u8 *uuid, int rev, int func, union acpi_object *argv4);
+acpi_status acpi_check_coherency(acpi_handle handle, int *val); + static inline union acpi_object * acpi_evaluate_dsm_typed(acpi_handle handle, const u8 *uuid, int rev, int func, union acpi_object *argv4, acpi_object_type type)
On Wednesday 05 November 2014 20:32:46 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com
This should probably come before patch 26, so you can use it in that driver.
On a more general note, we also need to change the platform device creation path for ACPI to evaluate this and set the correct dma_map_ops based on it.
Arnd
On Thursday 06 November 2014 11:43:51 Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:46 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com
This should probably come before patch 26, so you can use it in that driver.
On a more general note, we also need to change the platform device creation path for ACPI to evaluate this and set the correct dma_map_ops based on it.
On second thought, I wonder why this is even required on ARM64. Are there any DMA masters that we want to support with ACPI that are not cache coherent? It sounds strange in a server context, so why not assume that everything is coherent and just set dma_mask=NULL for noncoherent devices to prevent them from doing DMA?
Arnd
On Tue, 2014-11-11 at 18:37 +0100, Arnd Bergmann wrote:
On Thursday 06 November 2014 11:43:51 Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:46 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com
This should probably come before patch 26, so you can use it in that driver.
On a more general note, we also need to change the platform device creation path for ACPI to evaluate this and set the correct dma_map_ops based on it.
On second thought, I wonder why this is even required on ARM64. Are there any DMA masters that we want to support with ACPI that are not cache coherent? It sounds strange in a server context, so why not assume that everything is coherent and just set dma_mask=NULL for noncoherent devices to prevent them from doing DMA?
I think we need to support the _CCA info but this patch really isn't the way to do it. Just as the OF code looks for dma-coherent when creating the platform device, the check for _CCA should be in acpi_platform.c when the device gets created. I just haven't gotten around to writing the patch.
On Tuesday 11 November 2014 12:46:18 Mark Salter wrote:
On Tue, 2014-11-11 at 18:37 +0100, Arnd Bergmann wrote:
On Thursday 06 November 2014 11:43:51 Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:46 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com
This should probably come before patch 26, so you can use it in that driver.
On a more general note, we also need to change the platform device creation path for ACPI to evaluate this and set the correct dma_map_ops based on it.
On second thought, I wonder why this is even required on ARM64. Are there any DMA masters that we want to support with ACPI that are not cache coherent? It sounds strange in a server context, so why not assume that everything is coherent and just set dma_mask=NULL for noncoherent devices to prevent them from doing DMA?
I think we need to support the _CCA info but this patch really isn't the way to do it. Just as the OF code looks for dma-coherent when creating the platform device, the check for _CCA should be in acpi_platform.c when the device gets created. I just haven't gotten around to writing the patch.
I think that is correct in general, in particular for embedded x86 devices that may have noncoherent devices. However to repeat my earlier question: are you aware of any noncoherent devices that you think we will have to support on ARM64?
Arnd
On Tue, 2014-11-11 at 19:00 +0100, Arnd Bergmann wrote:
On Tuesday 11 November 2014 12:46:18 Mark Salter wrote:
On Tue, 2014-11-11 at 18:37 +0100, Arnd Bergmann wrote:
On Thursday 06 November 2014 11:43:51 Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:46 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com
This should probably come before patch 26, so you can use it in that driver.
On a more general note, we also need to change the platform device creation path for ACPI to evaluate this and set the correct dma_map_ops based on it.
On second thought, I wonder why this is even required on ARM64. Are there any DMA masters that we want to support with ACPI that are not cache coherent? It sounds strange in a server context, so why not assume that everything is coherent and just set dma_mask=NULL for noncoherent devices to prevent them from doing DMA?
I think we need to support the _CCA info but this patch really isn't the way to do it. Just as the OF code looks for dma-coherent when creating the platform device, the check for _CCA should be in acpi_platform.c when the device gets created. I just haven't gotten around to writing the patch.
I think that is correct in general, in particular for embedded x86 devices that may have noncoherent devices. However to repeat my earlier question: are you aware of any noncoherent devices that you think we will have to support on ARM64?
I'm not personally aware, but that is certainly not definitive.
On 11/11/2014 11:12 AM, Mark Salter wrote:
On Tue, 2014-11-11 at 19:00 +0100, Arnd Bergmann wrote:
On Tuesday 11 November 2014 12:46:18 Mark Salter wrote:
On Tue, 2014-11-11 at 18:37 +0100, Arnd Bergmann wrote:
On Thursday 06 November 2014 11:43:51 Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:46 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com
This should probably come before patch 26, so you can use it in that driver.
On a more general note, we also need to change the platform device creation path for ACPI to evaluate this and set the correct dma_map_ops based on it.
On second thought, I wonder why this is even required on ARM64. Are there any DMA masters that we want to support with ACPI that are not cache coherent? It sounds strange in a server context, so why not assume that everything is coherent and just set dma_mask=NULL for noncoherent devices to prevent them from doing DMA?
I think we need to support the _CCA info but this patch really isn't the way to do it. Just as the OF code looks for dma-coherent when creating the platform device, the check for _CCA should be in acpi_platform.c when the device gets created. I just haven't gotten around to writing the patch.
I think that is correct in general, in particular for embedded x86 devices that may have noncoherent devices. However to repeat my earlier question: are you aware of any noncoherent devices that you think we will have to support on ARM64?
I'm not personally aware, but that is certainly not definitive.
My understanding from ARM is that there is no reasonable default. If we assume on ARM that devices are coherent, we are just as likely to assume wrong as when we assume they are non-coherent. I can only presume they know their customers better than I do.
The next version of the ACPI spec will address this and mandate a _CCA method be defined on ARMv8 for all bus masters.
On Wednesday 12 November 2014 11:12:17 Al Stone wrote:
On 11/11/2014 11:12 AM, Mark Salter wrote:
On Tue, 2014-11-11 at 19:00 +0100, Arnd Bergmann wrote:
On Tuesday 11 November 2014 12:46:18 Mark Salter wrote: I think that is correct in general, in particular for embedded x86 devices that may have noncoherent devices. However to repeat my earlier question: are you aware of any noncoherent devices that you think we will have to support on ARM64?
I'm not personally aware, but that is certainly not definitive.
My understanding from ARM is that there is no reasonable default. If we assume on ARM that devices are coherent, we are just as likely to assume wrong as when we assume they are non-coherent. I can only presume they know their customers better than I do.
The next version of the ACPI spec will address this and mandate a _CCA method be defined on ARMv8 for all bus masters.
Ok, so this is more of a general handwavy feeling that someone at ARM has. In this case, I'd assume that all servers are doing the right thing and that we don't care about ACPI on non-server parts in Linux.
This would also make the implementation of setting the right DMA mask and dma_map_ops straightforward: if the device is marked as cache-coherent, then set a 32-bit DMA mask by default and use the coherent swiotlb ops, otherwise set a NULL dma_mask and don't let the device do any DMA. An alternative would be to always set the 64-bit dma ops and not require swiotlb, but I suspect that we will see devices that are not 64-bit capable and I see no way for ACPI to detect that. Also, setting up the SMMU wouldn't be possible until the ACPI spec gains a way to describe the relation between a non-PCI dma master and the SMMU.
Arnd
Hi Arnd, Mark,
On 2014-11-6 18:43, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:46 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com
This should probably come before patch 26, so you can use it in that driver.
On a more general note, we also need to change the platform device creation path for ACPI to evaluate this and set the correct dma_map_ops based on it.
I rework this patch a little bit and add it to platform creation patch for ACPI, if it is ok to you, I will send it to ACPI maiilist.
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return if device is coherent.
Signed-off-by: Mark Salter msalter@redhat.com Signed-off-by: Hanjun Guo hanjun.guo@linaro.org --- drivers/acpi/utils.c | 31 +++++++++++++++++++++++++++++++ include/acpi/acpi_bus.h | 2 ++ 2 files changed, 33 insertions(+)
diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 371ac12..10faba9 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -723,3 +723,34 @@ bool acpi_check_dsm(acpi_handle handle, const u8 *uuid, int rev, u64 funcs) return false; } EXPORT_SYMBOL(acpi_check_dsm); + +/** + * acpi_dma_is_coherent - check if device is coherent + * @handle: ACPI device handle + * + * Search a device and its parents for a _CCA method and check + * if device is coherent. + */ +bool acpi_dma_is_coherent(acpi_handle handle) +{ + unsigned long long data; + acpi_status status; + + do { + status = acpi_evaluate_integer(handle, "_CCA", NULL, &data); + if (ACPI_SUCCESS(status)) + break; + + status = acpi_get_parent(handle, &handle); + } while (ACPI_SUCCESS(status)); + + /* + * _CCA object returns 1 to indicate that a bus-master device + * supports hardware managed cache coherency + */ + if (data == 1) + return true; + + return false; +} +EXPORT_SYMBOL(acpi_dma_is_coherent); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 7d1ce40..411a765 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -69,6 +69,8 @@ bool acpi_check_dsm(acpi_handle handle, const u8 *uuid, int rev, u64 funcs); union acpi_object *acpi_evaluate_dsm(acpi_handle handle, const u8 *uuid, int rev, int func, union acpi_object *argv4);
+bool acpi_dma_is_coherent(acpi_handle handle); + static inline union acpi_object * acpi_evaluate_dsm_typed(acpi_handle handle, const u8 *uuid, int rev, int func, union acpi_object *argv4, acpi_object_type type)
and I have another patch to use the function above:
use acpi_dma_is_coherent() when platform device is created.
Signed-off-by: Hanjun Guo hanjun.guo@linaro.org --- drivers/acpi/acpi_platform.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c index 24b918b..055ed15 100644 --- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -105,12 +105,20 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev) pdevinfo.acpi_node.companion = adev; pdevinfo.dma_mask = DMA_BIT_MASK(32); pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) + if (IS_ERR(pdev)) { dev_err(&adev->dev, "platform device creation failed: %ld\n", PTR_ERR(pdev)); - else + } else { + /* + * if _CCA (Cache Coherency Attribute) exist, call arch hook to setup + * dma coherent operations. + */ + if (acpi_dma_is_coherent(acpi_device_handle(adev))) + set_arch_dma_coherent_ops(&pdev->dev); + dev_dbg(&adev->dev, "created platform device %s\n", dev_name(&pdev->dev)); + }
kfree(resources); return pdev;
On Thursday 18 December 2014 20:29:43 Hanjun Guo wrote:
--- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -105,12 +105,20 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev) pdevinfo.acpi_node.companion = adev; pdevinfo.dma_mask = DMA_BIT_MASK(32); pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev))
if (IS_ERR(pdev)) { dev_err(&adev->dev, "platform device creation failed: %ld\n", PTR_ERR(pdev));
else
} else {
/*
* if _CCA (Cache Coherency Attribute) exist, call arch hook to setup
* dma coherent operations.
*/
if (acpi_dma_is_coherent(acpi_device_handle(adev)))
set_arch_dma_coherent_ops(&pdev->dev);
dev_dbg(&adev->dev, "created platform device %s\n", dev_name(&pdev->dev));
}
We have replaced set_arch_dma_coherent_ops with arch_setup_dma_ops in 3.19, you should update your patch. The set_arch_dma_coherent_ops callback still exists on arm64, but that is a bug that will be fixed shortly.
Arnd
On 2014年12月18日 21:10, Arnd Bergmann wrote:
On Thursday 18 December 2014 20:29:43 Hanjun Guo wrote:
--- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -105,12 +105,20 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev) pdevinfo.acpi_node.companion = adev; pdevinfo.dma_mask = DMA_BIT_MASK(32); pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev))
if (IS_ERR(pdev)) { dev_err(&adev->dev, "platform device creation failed: %ld\n", PTR_ERR(pdev));
else
} else {
/*
* if _CCA (Cache Coherency Attribute) exist, call arch hook to setup
* dma coherent operations.
*/
if (acpi_dma_is_coherent(acpi_device_handle(adev)))
set_arch_dma_coherent_ops(&pdev->dev);
dev_dbg(&adev->dev, "created platform device %s\n", dev_name(&pdev->dev));
}
We have replaced set_arch_dma_coherent_ops with arch_setup_dma_ops in 3.19, you should update your patch. The set_arch_dma_coherent_ops callback still exists on arm64, but that is a bug that will be fixed shortly.
Thanks for the guidance, I will update this patch based on 3.19-rc1.
Thanks Hanjun
On Thursday 18 December 2014 20:29:43 Hanjun Guo wrote:
I rework this patch a little bit and add it to platform creation patch for ACPI, if it is ok to you, I will send it to ACPI maiilist.
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return if device is coherent.
A few more things about the general approach:
- I'm still not convinced that we want to have this on ARM64: we may want to WARN_ON() in arch_setup_dma_ops() if we get called on an ACPI based system with coherency disabled, so we can get an error report if we ever encounter a server system with broken coherency, and then decide whether we want to support that or not.
- I'm guessing that the algorithm is not what was originally intended here. Note that you can never have a child device that is more coherent than its parent. If you have a device that is marked coherent, and its parent is marked noncoherent, the latter should probably considered an override.
- While the meaning of cache-coherent is clear to me, the meaning of non-coherent is much less defined and is really platform specific rather than CPU architecture specific. On ARM32, most noncoherent systems require the use of uncached memory, or explicit cache flushes to work around the lack of coherency. On big NUMA server systems (e.g. mips, parisc, itanium, x86-numa), but also arm32 Armada XP, noncoherent devices still participate in the normal cache coherency domain but require a device specific synchronization to ensure that cache lines observed by the device are the same that are seen by the CPU. This can be hard to describe in a single object, unless it contains a method for synchronization.
Arnd
On 2014年12月18日 21:29, Arnd Bergmann wrote:
On Thursday 18 December 2014 20:29:43 Hanjun Guo wrote:
I rework this patch a little bit and add it to platform creation patch for ACPI, if it is ok to you, I will send it to ACPI maiilist.
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return if device is coherent.
A few more things about the general approach:
- I'm still not convinced that we want to have this on ARM64: we may want to WARN_ON() in arch_setup_dma_ops() if we get called on an ACPI based system with coherency disabled, so we can get an error report if we ever encounter a server system with broken coherency, and then decide whether we want to support that or not.
I need to discuss with some chip designing guys to get what are they thinking, thanks for point this out.
- I'm guessing that the algorithm is not what was originally intended here. Note that you can never have a child device that is more coherent than its parent. If you have a device that is marked coherent, and its parent is marked noncoherent,
^ I think you mean child here?
the latter should probably considered an override.
The _CCA method means the device and its child are memory coherency. I think you mean the same thing?
- While the meaning of cache-coherent is clear to me, the meaning of non-coherent is much less defined and is really platform specific rather than CPU architecture specific. On ARM32, most noncoherent systems require the use of uncached memory, or explicit cache flushes to work around the lack of coherency. On big NUMA server systems (e.g. mips, parisc, itanium, x86-numa), but also arm32 Armada XP, noncoherent devices still participate in the normal cache coherency domain but require a device specific synchronization to ensure that cache lines observed by the device are the same that are seen by the CPU. This can be hard to describe in a single object, unless it contains a method for synchronization.
Thanks Hanjun
On Friday 19 December 2014 22:14:11 Hanjun Guo wrote:
- I'm guessing that the algorithm is not what was originally intended here. Note that you can never have a child device that is more coherent than its parent. If you have a device that is marked coherent, and its parent is marked noncoherent,
^ I think you mean child here?
the latter should probably considered an override.
The _CCA method means the device and its child are memory coherency. I think you mean the same thing?
No. What I mean is that I'd expect that method to only refer to local coherency: you may have a hierarchy in which a device is coherent for DMA with its parent and therefore it is marked as coherent, but the parent is marked as noncoherent because it does not participate in the coherency domain of the grandparent device.
Coherency is always defined between two points in the system. Usually you only care about coherency between one device and the CPU's view of main memory, but the device itself may not understand the entire hierarchy and only contain the method to describe how it relates to its immediate parent or local NUMA node.
Arnd
On Dec 20, 2014, at 3:28 PM, Arnd Bergmann arnd@arndb.de wrote:
On Friday 19 December 2014 22:14:11 Hanjun Guo wrote:
- I'm guessing that the algorithm is not what was originally intended here. Note that you can never have a child device that is more coherent than its parent. If you have a device that is marked coherent, and its parent is marked noncoherent,
^ I think you mean child here?
the latter should probably considered an override.
The _CCA method means the device and its child are memory coherency. I think you mean the same thing?
No. What I mean is that I'd expect that method to only refer to local coherency: you may have a hierarchy in which a device is coherent for DMA with its parent and therefore it is marked as coherent, but the parent is marked as noncoherent because it does not participate in the coherency domain of the grandparent device.
Coherency is always defined between two points in the system. Usually you only care about coherency between one device and the CPU's view of main memory, but the device itself may not understand the entire hierarchy and only contain the method to describe how it relates to its immediate parent or local NUMA node.
In real world designs that are in flight I don’t think we have to worry much about partially coherent situations. While the above is absolutely possible (especially in an embedded design), I would expect (and indeed require) coherency to mean that a device observes the same view of memory as its grandparents as well as the different processing elements within (what appears to be for our purposes) one single global domain, even in the case of multi-socket SoC designs (i.e. requiring cache coherency to extend to CPU<->device transactions made available through the interconnect).
Jon.
From: Mark Salter msalter@redhat.com
Commit 2189064795dc3fb4101e5:
arm64: Implement set_arch_dma_coherent_ops() to replace bus notifiers
removed the bus notifiers from dma-mapping.c. This patch adds the notifier back for ACPI and PCI devices until a better permanent solution is worked out.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/mm/dma-mapping.c | 103 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+)
diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c index d920942..fda70ab 100644 --- a/arch/arm64/mm/dma-mapping.c +++ b/arch/arm64/mm/dma-mapping.c @@ -23,8 +23,14 @@ #include <linux/genalloc.h> #include <linux/dma-mapping.h> #include <linux/dma-contiguous.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> #include <linux/vmalloc.h> #include <linux/swiotlb.h> +#include <linux/amba/bus.h> +#include <linux/acpi.h> +#include <linux/pci.h>
#include <asm/cacheflush.h>
@@ -423,10 +429,107 @@ out: return -ENOMEM; }
+#ifdef CONFIG_PCI +static void arm64_of_set_dma_ops(void *_dev) +{ + struct device *dev = _dev; + + /* + * PCI devices won't have an ACPI handle but the bridge will. + * Search up the device chain until we find an of_node + * to check. + */ + while (dev) { + if (dev->of_node) { + if (of_dma_is_coherent(dev->of_node)) + set_dma_ops(_dev, &coherent_swiotlb_dma_ops); + break; + } + dev = dev->parent; + } +} +#else +static inline arm64_of_set_dma_ops(void *_dev) {} +#endif + + +#ifdef CONFIG_ACPI +static void arm64_acpi_set_dma_ops(void *_dev) +{ + struct device *dev = _dev; + + /* + * Kernel defaults to noncoherent ops but ACPI 5.1 spec says arm64 + * defaults to coherent. Set coherent ops if _CCA not found or _CCA + * found and non-zero. + * + * PCI devices won't have an of_node but the bridge will. + * Search up the device chain until we find an ACPI handle + * to check. + */ + while (dev) { + if (ACPI_HANDLE(dev)) { + acpi_status status; + int coherent; + status = acpi_check_coherency(ACPI_HANDLE(dev), + &coherent); + if (ACPI_FAILURE(status) || coherent) + set_dma_ops(dev, &coherent_swiotlb_dma_ops); + break; + } + dev = dev->parent; + } +} +#else +static inline arm64_acpi_set_dma_ops(void *_dev) {} +#endif + +static int dma_bus_notifier(struct notifier_block *nb, + unsigned long event, void *_dev) +{ + if (event != BUS_NOTIFY_ADD_DEVICE) + return NOTIFY_DONE; + + if (acpi_disabled) + arm64_of_set_dma_ops(_dev); + else + arm64_acpi_set_dma_ops(_dev); + + return NOTIFY_OK; +} + +#ifdef CONFIG_ACPI +static struct notifier_block platform_bus_nb = { + .notifier_call = dma_bus_notifier, +}; + +static struct notifier_block amba_bus_nb = { + .notifier_call = dma_bus_notifier, +}; +#endif + +#ifdef CONFIG_PCI +static struct notifier_block pci_bus_nb = { + .notifier_call = dma_bus_notifier, +}; +#endif + static int __init swiotlb_late_init(void) { size_t swiotlb_size = min(SZ_64M, MAX_ORDER_NR_PAGES << PAGE_SHIFT);
+ /* + * These must be registered before of_platform_populate(). + */ +#ifdef CONFIG_ACPI + bus_register_notifier(&platform_bus_type, &platform_bus_nb); + bus_register_notifier(&amba_bustype, &amba_bus_nb); +#endif + +#ifdef CONFIG_PCI + bus_register_notifier(&pci_bus_type, &pci_bus_nb); +#endif + dma_ops = &noncoherent_swiotlb_dma_ops;
return swiotlb_late_init_with_default_size(swiotlb_size);
From: Mark Salter msalter@redhat.com
Arm allows for two possible architectural clock sources. One memory mapped and the other coprocessor based. If both timers exist, then the driver waits for both to be probed before registering a clocksource.
Commit c387f07e6205 ("clocksource: arm_arch_timer: Discard unavailable timers correctly") attempted to fix a hang occurring when one of the two possible timers had a device node, but was disabled. In that case, the second probe would never occur and the system would hang without a clocksource being registered.
Unfortunately, incorrect logic in that commit made things worse such that a hang would occur unless both timers had a device node and were enabled. This patch fixes the logic so that we don't wait to probe a second timer unless it exists and is enabled.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/clocksource/arm_arch_timer.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c index 2ce7358..394e06c 100644 --- a/drivers/clocksource/arm_arch_timer.c +++ b/drivers/clocksource/arm_arch_timer.c @@ -665,13 +665,14 @@ static bool __init arch_timer_probed(int type, const struct of_device_id *matches) { struct device_node *dn; - bool probed = false; + bool probed = true;
dn = of_find_matching_node(NULL, matches); - if (dn && of_device_is_available(dn) && (arch_timers_present & type)) - probed = true; - of_node_put(dn); - + if (dn) { + if (of_device_is_available(dn) && !(arch_timers_present & type)) + probed = false; + of_node_put(dn); + } return probed; }
On Wednesday 05 November 2014 20:32:48 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Arm allows for two possible architectural clock sources. One memory mapped and the other coprocessor based. If both timers exist, then the driver waits for both to be probed before registering a clocksource.
Commit c387f07e6205 ("clocksource: arm_arch_timer: Discard unavailable timers correctly") attempted to fix a hang occurring when one of the two possible timers had a device node, but was disabled. In that case, the second probe would never occur and the system would hang without a clocksource being registered.
Unfortunately, incorrect logic in that commit made things worse such that a hang would occur unless both timers had a device node and were enabled. This patch fixes the logic so that we don't wait to probe a second timer unless it exists and is enabled.
Signed-off-by: Mark Salter msalter@redhat.com
This should go upstream and into stable kernels, right?
Arnd
From: Mark Salter msalter@redhat.com
The current code provides placeholder weak functions for for raw_pci_read() and raw_pci_write(). This patch creates a global 'raw_pci_ops' variable which points to a struct containing read and write ops. The weak attribute is removed from raw_pci_read() and raw_pci_write() which will use the ops provided by the raw_pci_ops pointer.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/include/asm/pci.h | 9 +++++++++ arch/arm64/pci/pci.c | 20 ++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index fded096..9e23df3 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -39,5 +39,14 @@ static inline int pci_proc_domain(struct pci_bus *bus) } #endif /* CONFIG_PCI */
+struct pci_raw_ops { + int (*read)(unsigned int domain, unsigned int bus, unsigned int devfn, + int reg, int len, u32 *val); + int (*write)(unsigned int domain, unsigned int bus, unsigned int devfn, + int reg, int len, u32 val); +}; + +extern const struct pci_raw_ops *raw_pci_ops; + #endif /* __KERNEL__ */ #endif /* __ASM_PCI_H */ diff --git a/arch/arm64/pci/pci.c b/arch/arm64/pci/pci.c index b03b0eb..35d99b3 100644 --- a/arch/arm64/pci/pci.c +++ b/arch/arm64/pci/pci.c @@ -3,22 +3,30 @@ #include <linux/kernel.h> #include <linux/pci.h>
+const struct pci_raw_ops *__read_mostly raw_pci_ops; + /** * raw_pci_read - Platform-specific PCI config space access. * * Default empty implementation. Replace with an architecture-specific setup * routine, if necessary. */ -int __weak raw_pci_read(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 *val) +int raw_pci_read(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *val) { - return -EINVAL; + if (!raw_pci_ops) + return -EINVAL; + + return raw_pci_ops->read(domain, bus, devfn, reg, len, val); }
-int __weak raw_pci_write(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 val) +int raw_pci_write(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 val) { - return -EINVAL; + if (!raw_pci_ops) + return -EINVAL; + + return raw_pci_ops->write(domain, bus, devfn, reg, len, val); }
/* Root bridge scanning */
On Wednesday 05 November 2014 20:32:49 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
The current code provides placeholder weak functions for for raw_pci_read() and raw_pci_write(). This patch creates a global 'raw_pci_ops' variable which points to a struct containing read and write ops. The weak attribute is removed from raw_pci_read() and raw_pci_write() which will use the ops provided by the raw_pci_ops pointer.
Signed-off-by: Mark Salter msalter@redhat.com
This is another one that is only needed for X-Gene, right?
Arnd
From: Mark Salter msalter@redhat.com
Add support for parsing MCFG table and provide functions to read/write PCI configuration space based on the parsed info. This provides the low-level raw_pci_read/raw_pci_write functionality.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/Kconfig | 3 + arch/arm64/include/asm/pci.h | 16 ++ arch/arm64/pci/Makefile | 1 + arch/arm64/pci/mmconfig.c | 409 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 429 insertions(+) create mode 100644 arch/arm64/pci/mmconfig.c
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 7789db7..fd2fa10 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -186,6 +186,9 @@ config PCI_DOMAINS_GENERIC config PCI_SYSCALL def_bool PCI
+config PCI_MMCONFIG + def_bool y if PCI && ACPI + source "drivers/pci/Kconfig" source "drivers/pci/pcie/Kconfig" source "drivers/pci/hotplug/Kconfig" diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index 9e23df3..3b04035 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -48,5 +48,21 @@ struct pci_raw_ops {
extern const struct pci_raw_ops *raw_pci_ops;
+/* "PCI MMCONFIG %04x [bus %02x-%02x]" */ +#define PCI_MMCFG_RESOURCE_NAME_LEN (22 + 4 + 2 + 2) + +struct pci_mmcfg_region { + struct list_head list; + struct resource res; + u64 address; + char __iomem *virt; + u16 segment; + u8 start_bus; + u8 end_bus; + char name[PCI_MMCFG_RESOURCE_NAME_LEN]; +}; + +extern struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus); + #endif /* __KERNEL__ */ #endif /* __ASM_PCI_H */ diff --git a/arch/arm64/pci/Makefile b/arch/arm64/pci/Makefile index b8d5dbd..7038b51 100644 --- a/arch/arm64/pci/Makefile +++ b/arch/arm64/pci/Makefile @@ -1 +1,2 @@ obj-y += pci.o +obj-$(CONFIG_ACPI) += mmconfig.o diff --git a/arch/arm64/pci/mmconfig.c b/arch/arm64/pci/mmconfig.c new file mode 100644 index 0000000..0bda9b7 --- /dev/null +++ b/arch/arm64/pci/mmconfig.c @@ -0,0 +1,409 @@ +/* + * mmconfig.c - Low-level direct PCI config space access via MMCONFIG + * + * Borrowed heavily from x86 + */ + +#include <linux/pci.h> +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/bitmap.h> +#include <linux/dmi.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/rculist.h> +#include <linux/rcupdate.h> + +#define PREFIX "PCI: " + +/* Indicate if the mmcfg resources have been placed into the resource table. */ +static bool pci_mmcfg_running_state; +static bool pci_mmcfg_arch_init_failed; +static DEFINE_MUTEX(pci_mmcfg_lock); + +#define PCI_MMCFG_BUS_OFFSET(bus) ((bus) << 20) + +LIST_HEAD(pci_mmcfg_list); + +static inline unsigned char mmio_config_readb(void __iomem *pos) +{ + return readb(pos); +} + +static inline unsigned short mmio_config_readw(void __iomem *pos) +{ + return readw(pos); +} + +static inline unsigned int mmio_config_readl(void __iomem *pos) +{ + return readl(pos); +} + +static inline void mmio_config_writeb(void __iomem *pos, u8 val) +{ + writeb(val, pos); +} + +static inline void mmio_config_writew(void __iomem *pos, u16 val) +{ + writew(val, pos); +} + +static inline void mmio_config_writel(void __iomem *pos, u32 val) +{ + writel(val, pos); +} + +struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus) +{ + struct pci_mmcfg_region *cfg; + + list_for_each_entry_rcu(cfg, &pci_mmcfg_list, list) + if (cfg->segment == segment && + cfg->start_bus <= bus && bus <= cfg->end_bus) + return cfg; + + return NULL; +} + +static char __iomem *pci_dev_base(struct pci_mmcfg_region *cfg, + unsigned int bus, unsigned int devfn) +{ + return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12)); +} + +static int __pci_mmcfg_read(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *value) +{ + char __iomem *addr = pci_dev_base(cfg, bus, devfn); + + switch (len) { + case 1: + *value = mmio_config_readb(addr + reg); + break; + case 2: + *value = mmio_config_readw(addr + reg); + break; + case 4: + *value = mmio_config_readl(addr + reg); + break; + } + return 0; +} + +static int pci_mmcfg_read(unsigned int seg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *value) +{ + struct pci_mmcfg_region *cfg; + int ret; + + /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ + if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) { +err: *value = -1; + return -EINVAL; + } + + rcu_read_lock(); + cfg = pci_mmconfig_lookup(seg, bus); + if (!cfg || !cfg->virt) { + rcu_read_unlock(); + goto err; + } + + ret = __pci_mmcfg_read(cfg, bus, devfn, reg, len, value); + + rcu_read_unlock(); + + return ret; +} + +static int __pci_mmcfg_write(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 value) +{ + char __iomem *addr = pci_dev_base(cfg, bus, devfn); + + switch (len) { + case 1: + mmio_config_writeb(addr + reg, value); + break; + case 2: + mmio_config_writew(addr + reg, value); + break; + case 4: + mmio_config_writel(addr + reg, value); + break; + } + return 0; +} + +static int pci_mmcfg_write(unsigned int seg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 value) +{ + struct pci_mmcfg_region *cfg; + int ret; + + /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ + if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) + return -EINVAL; + + rcu_read_lock(); + cfg = pci_mmconfig_lookup(seg, bus); + if (!cfg || !cfg->virt) { + rcu_read_unlock(); + return -EINVAL; + } + + ret = __pci_mmcfg_write(cfg, bus, devfn, reg, len, value); + + rcu_read_unlock(); + + return ret; +} + +const struct pci_raw_ops pci_mmcfg = { + .read = pci_mmcfg_read, + .write = pci_mmcfg_write, +}; + +static void __iomem *mcfg_ioremap(struct pci_mmcfg_region *cfg) +{ + void __iomem *addr; + u64 start, size; + int num_buses; + + start = cfg->address + PCI_MMCFG_BUS_OFFSET(cfg->start_bus); + num_buses = cfg->end_bus - cfg->start_bus + 1; + size = PCI_MMCFG_BUS_OFFSET(num_buses); + addr = ioremap_nocache(start, size); + if (addr) + addr -= PCI_MMCFG_BUS_OFFSET(cfg->start_bus); + return addr; +} + +void pci_mmcfg_arch_unmap(struct pci_mmcfg_region *cfg) +{ + if (cfg && cfg->virt) { + iounmap(cfg->virt + PCI_MMCFG_BUS_OFFSET(cfg->start_bus)); + cfg->virt = NULL; + } +} + +void __init pci_mmcfg_arch_free(void) +{ + struct pci_mmcfg_region *cfg; + + list_for_each_entry(cfg, &pci_mmcfg_list, list) + pci_mmcfg_arch_unmap(cfg); +} + +int pci_mmcfg_arch_map(struct pci_mmcfg_region *cfg) +{ + cfg->virt = mcfg_ioremap(cfg); + if (!cfg->virt) { + pr_err(PREFIX "can't map MMCONFIG at %pR\n", &cfg->res); + return -ENOMEM; + } + + return 0; +} + +static void __init pci_mmconfig_remove(struct pci_mmcfg_region *cfg) +{ + if (cfg->res.parent) + release_resource(&cfg->res); + list_del(&cfg->list); + kfree(cfg); +} + +static void __init free_all_mmcfg(void) +{ + struct pci_mmcfg_region *cfg, *tmp; + + pci_mmcfg_arch_free(); + list_for_each_entry_safe(cfg, tmp, &pci_mmcfg_list, list) + pci_mmconfig_remove(cfg); +} + +static void list_add_sorted(struct pci_mmcfg_region *new) +{ + struct pci_mmcfg_region *cfg; + + /* keep list sorted by segment and starting bus number */ + list_for_each_entry_rcu(cfg, &pci_mmcfg_list, list) { + if (cfg->segment > new->segment || + (cfg->segment == new->segment && + cfg->start_bus >= new->start_bus)) { + list_add_tail_rcu(&new->list, &cfg->list); + return; + } + } + list_add_tail_rcu(&new->list, &pci_mmcfg_list); +} + +static struct pci_mmcfg_region *pci_mmconfig_alloc(int segment, int start, + int end, u64 addr) +{ + struct pci_mmcfg_region *new; + struct resource *res; + + if (addr == 0) + return NULL; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return NULL; + + new->address = addr; + new->segment = segment; + new->start_bus = start; + new->end_bus = end; + + res = &new->res; + res->start = addr + PCI_MMCFG_BUS_OFFSET(start); + res->end = addr + PCI_MMCFG_BUS_OFFSET(end + 1) - 1; + res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + snprintf(new->name, PCI_MMCFG_RESOURCE_NAME_LEN, + "PCI MMCONFIG %04x [bus %02x-%02x]", segment, start, end); + res->name = new->name; + + return new; +} + +static struct pci_mmcfg_region *__init pci_mmconfig_add(int segment, int start, + int end, u64 addr) +{ + struct pci_mmcfg_region *new; + + new = pci_mmconfig_alloc(segment, start, end, addr); + if (new) { + mutex_lock(&pci_mmcfg_lock); + list_add_sorted(new); + mutex_unlock(&pci_mmcfg_lock); + + pr_info(PREFIX + "MMCONFIG for domain %04x [bus %02x-%02x] at %pR " + "(base %#lx)\n", + segment, start, end, &new->res, (unsigned long)addr); + } + + return new; +} + +static int __init pci_parse_mcfg(struct acpi_table_header *header) +{ + struct acpi_table_mcfg *mcfg; + struct acpi_mcfg_allocation *cfg_table, *cfg; + unsigned long i; + int entries; + + if (!header) + return -EINVAL; + + mcfg = (struct acpi_table_mcfg *)header; + + /* how many config structures do we have */ + free_all_mmcfg(); + entries = 0; + i = header->length - sizeof(struct acpi_table_mcfg); + while (i >= sizeof(struct acpi_mcfg_allocation)) { + entries++; + i -= sizeof(struct acpi_mcfg_allocation); + } + if (entries == 0) { + pr_err(PREFIX "MMCONFIG has no entries\n"); + return -ENODEV; + } + + cfg_table = (struct acpi_mcfg_allocation *) &mcfg[1]; + for (i = 0; i < entries; i++) { + cfg = &cfg_table[i]; + + if (pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number, + cfg->end_bus_number, cfg->address) == NULL) { + pr_warn(PREFIX "no memory for MCFG entries\n"); + free_all_mmcfg(); + return -ENOMEM; + } + } + + return 0; +} + +int __init pci_mmcfg_arch_init(void) +{ + struct pci_mmcfg_region *cfg; + + list_for_each_entry(cfg, &pci_mmcfg_list, list) + if (pci_mmcfg_arch_map(cfg)) { + pci_mmcfg_arch_free(); + return 0; + } + + raw_pci_ops = &pci_mmcfg; + + return 1; +} + +static void __init __pci_mmcfg_init(int early) +{ + if (list_empty(&pci_mmcfg_list)) { + pr_info("No MCFG table found!\n"); + pci_mmcfg_arch_init_failed = true; + return; + } + + if (!pci_mmcfg_arch_init()) { + pr_info("pci_mmcfg_arch_init failed!\n"); + free_all_mmcfg(); + pci_mmcfg_arch_init_failed = true; + } +} + +void __init pci_mmcfg_early_init(void) +{ + acpi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg); + + __pci_mmcfg_init(1); +} + +static int __init pci_mmcfg_init(void) +{ + pci_mmcfg_early_init(); + return 0; +} +arch_initcall(pci_mmcfg_init); + +void __init pci_mmcfg_late_init(void) +{ + /* MMCONFIG hasn't been enabled yet, try again */ + if (pci_mmcfg_arch_init_failed) { + acpi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg); + __pci_mmcfg_init(0); + } +} + +static int __init pci_mmcfg_late_insert_resources(void) +{ + struct pci_mmcfg_region *cfg; + + pci_mmcfg_running_state = true; + + /* + * Attempt to insert the mmcfg resources but not with the busy flag + * marked so it won't cause request errors when __request_region is + * called. + */ + list_for_each_entry(cfg, &pci_mmcfg_list, list) + if (!cfg->res.parent) + insert_resource(&iomem_resource, &cfg->res); + + return 0; +} + +/* + * Perform MMCONFIG resource insertion after PCI initialization to allow for + * misprogrammed MCFG tables that state larger sizes but actually conflict + * with other system resources. + */ +late_initcall(pci_mmcfg_late_insert_resources);
On Wednesday 05 November 2014 20:32:50 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Add support for parsing MCFG table and provide functions to read/write PCI configuration space based on the parsed info. This provides the low-level raw_pci_read/raw_pci_write functionality.
Signed-off-by: Mark Salter msalter@redhat.com
I think we should merge this into drivers/pci/host/pci-host-generic.c, which already contains some of the code. None of this is ARM64 specific.
Arnd
On 06.11.2014 11:49, Arnd Bergmann wrote:
On Wednesday 05 November 2014 20:32:50 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Add support for parsing MCFG table and provide functions to read/write PCI configuration space based on the parsed info. This provides the low-level raw_pci_read/raw_pci_write functionality.
Signed-off-by: Mark Salter msalter@redhat.com
I think we should merge this into drivers/pci/host/pci-host-generic.c, which already contains some of the code. None of this is ARM64 specific.
Right this is not ARM64 specific but should not be merged into the pci-host-generic.c though. I am going to refactor this out so that x86 and arm64 will be able to use the same code.
Tomasz
From: Mark Salter msalter@redhat.com
Some MCFG tables may be broken or the underlying hardware may not be fully compliant with the PCIe ECAM mechanism. This patch provides a mechanism to override the default mmconfig read/write routines and/or do other MCFG related fixups.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/include/asm/pci.h | 24 ++++++++++++++++++++++++ arch/arm64/pci/mmconfig.c | 30 ++++++++++++++++++++++++++---- include/asm-generic/vmlinux.lds.h | 7 +++++++ 3 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index 3b04035..b850d6e 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -51,9 +51,21 @@ extern const struct pci_raw_ops *raw_pci_ops; /* "PCI MMCONFIG %04x [bus %02x-%02x]" */ #define PCI_MMCFG_RESOURCE_NAME_LEN (22 + 4 + 2 + 2)
+struct acpi_pci_root; +struct pci_mmcfg_region; + +typedef int (*acpi_mcfg_fixup_t)(struct acpi_pci_root *root, + struct pci_mmcfg_region *cfg); + struct pci_mmcfg_region { struct list_head list; struct resource res; + int (*read)(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *value); + int (*write)(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 value); + acpi_mcfg_fixup_t fixup; + void *data; u64 address; char __iomem *virt; u16 segment; @@ -62,6 +74,18 @@ struct pci_mmcfg_region { char name[PCI_MMCFG_RESOURCE_NAME_LEN]; };
+struct acpi_mcfg_fixup { + char oem_id[7]; + char oem_table_id[9]; + acpi_mcfg_fixup_t hook; +}; + +/* Designate a routine to fix up buggy MCFG */ +#define DECLARE_ACPI_MCFG_FIXUP(oem_id, table_id, hook) \ + static const struct acpi_mcfg_fixup __acpi_fixup_##hook __used \ + __attribute__((__section__(".acpi_fixup_mcfg"), aligned((sizeof(void *))))) \ + = { {oem_id}, {table_id}, hook }; + extern struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus);
#endif /* __KERNEL__ */ diff --git a/arch/arm64/pci/mmconfig.c b/arch/arm64/pci/mmconfig.c index 0bda9b7..110fec7 100644 --- a/arch/arm64/pci/mmconfig.c +++ b/arch/arm64/pci/mmconfig.c @@ -111,7 +111,10 @@ err: *value = -1; goto err; }
- ret = __pci_mmcfg_read(cfg, bus, devfn, reg, len, value); + if (cfg->read) + ret = (*cfg->read)(cfg, bus, devfn, reg, len, value); + else + ret = __pci_mmcfg_read(cfg, bus, devfn, reg, len, value);
rcu_read_unlock();
@@ -154,7 +157,10 @@ static int pci_mmcfg_write(unsigned int seg, unsigned int bus, return -EINVAL; }
- ret = __pci_mmcfg_write(cfg, bus, devfn, reg, len, value); + if (cfg->write) + ret = (*cfg->write)(cfg, bus, devfn, reg, len, value); + else + ret = __pci_mmcfg_write(cfg, bus, devfn, reg, len, value);
rcu_read_unlock();
@@ -290,10 +296,15 @@ static struct pci_mmcfg_region *__init pci_mmconfig_add(int segment, int start, return new; }
+extern struct acpi_mcfg_fixup __start_acpi_mcfg_fixups[]; +extern struct acpi_mcfg_fixup __end_acpi_mcfg_fixups[]; + static int __init pci_parse_mcfg(struct acpi_table_header *header) { struct acpi_table_mcfg *mcfg; struct acpi_mcfg_allocation *cfg_table, *cfg; + struct acpi_mcfg_fixup *fixup; + struct pci_mmcfg_region *new; unsigned long i; int entries;
@@ -315,16 +326,27 @@ static int __init pci_parse_mcfg(struct acpi_table_header *header) return -ENODEV; }
+ fixup = __start_acpi_mcfg_fixups; + while (fixup < __end_acpi_mcfg_fixups) { + if (!strncmp(fixup->oem_id, header->oem_id, 6) && + !strncmp(fixup->oem_table_id, header->oem_table_id, 8)) + break; + ++fixup; + } + cfg_table = (struct acpi_mcfg_allocation *) &mcfg[1]; for (i = 0; i < entries; i++) { cfg = &cfg_table[i];
- if (pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number, - cfg->end_bus_number, cfg->address) == NULL) { + new = pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number, + cfg->end_bus_number, cfg->address); + if (!new) { pr_warn(PREFIX "no memory for MCFG entries\n"); free_all_mmcfg(); return -ENOMEM; } + if (fixup < __end_acpi_mcfg_fixups) + new->fixup = fixup->hook; }
return 0; diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index aa70cbd..1261fef 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -275,6 +275,13 @@ VMLINUX_SYMBOL(__end_pci_fixups_suspend_late) = .; \ } \ \ + /* ACPI quirks */ \ + .acpi_fixup : AT(ADDR(.acpi_fixup) - LOAD_OFFSET) { \ + VMLINUX_SYMBOL(__start_acpi_mcfg_fixups) = .; \ + *(.acpi_fixup_mcfg) \ + VMLINUX_SYMBOL(__end_acpi_mcfg_fixups) = .; \ + } \ + \ /* Built-in firmware blobs */ \ .builtin_fw : AT(ADDR(.builtin_fw) - LOAD_OFFSET) { \ VMLINUX_SYMBOL(__start_builtin_fw) = .; \
On Wednesday 05 November 2014 20:32:51 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Some MCFG tables may be broken or the underlying hardware may not be fully compliant with the PCIe ECAM mechanism. This patch provides a mechanism to override the default mmconfig read/write routines and/or do other MCFG related fixups.
Signed-off-by: Mark Salter msalter@redhat.com
And one more x-gene specific one. I don't think we ever want to merge this patch. If a PCI host doesn't implement ECAM, it should not use the ACPI way of describing an ECAM compatible PCI host controller.
Arnd
From: Mark Salter msalter@redhat.com
Fix a NULL dereference in find_mmu_master which occurs when booting with ACPI. In that case, PCI bridges with not have an of_node. Add a check for NULL of_node and bail out if that is the case.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/iommu/arm-smmu.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 60558f7..dae8ae3 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -444,7 +444,10 @@ static struct device_node *dev_get_dev_node(struct device *dev)
while (!pci_is_root_bus(bus)) bus = bus->parent; - return bus->bridge->parent->of_node; + if (bus->bridge->parent) + return bus->bridge->parent->of_node; + else + return bus->bridge->of_node; }
return dev->of_node; @@ -560,6 +563,9 @@ static struct arm_smmu_device *find_smmu_for_device(struct device *dev) struct arm_smmu_master *master = NULL; struct device_node *dev_node = dev_get_dev_node(dev);
+ if (!dev_node) + return NULL; + spin_lock(&arm_smmu_devices_lock); list_for_each_entry(smmu, &arm_smmu_devices, list) { master = find_smmu_master(smmu, dev_node);
On Wednesday 05 November 2014 20:32:52 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Fix a NULL dereference in find_mmu_master which occurs when booting with ACPI. In that case, PCI bridges with not have an of_node. Add a check for NULL of_node and bail out if that is the case.
Signed-off-by: Mark Salter msalter@redhat.com
drivers/iommu/arm-smmu.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 60558f7..dae8ae3 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -444,7 +444,10 @@ static struct device_node *dev_get_dev_node(struct device *dev) while (!pci_is_root_bus(bus)) bus = bus->parent;
return bus->bridge->parent->of_node;
if (bus->bridge->parent)
return bus->bridge->parent->of_node;
else
return bus->bridge->of_node; }
return dev->of_node;
I don't think we want to allow PCI host bridges without a parent device in DT, so you can just return NULL here.
@@ -560,6 +563,9 @@ static struct arm_smmu_device *find_smmu_for_device(struct device *dev) struct arm_smmu_master *master = NULL; struct device_node *dev_node = dev_get_dev_node(dev);
if (!dev_node)
return NULL;
spin_lock(&arm_smmu_devices_lock); list_for_each_entry(smmu, &arm_smmu_devices, list) { master = find_smmu_master(smmu, dev_node);
It might be good to mention in the changelog that this code is going to be reworked anyway, as change the iommu API to support the more general cases.
Arnd
From: Al Stone ahs3@redhat.com
This is a first pass of ACPI probing of PCI; much of it comes from existing ia64 and x86 code, and will need refactoring later.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/include/asm/pci.h | 8 + arch/arm64/kernel/pci.c | 108 +++++++++----- arch/arm64/pci/pci.c | 341 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 421 insertions(+), 36 deletions(-)
diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index b850d6e..faad64c 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -53,6 +53,14 @@ extern const struct pci_raw_ops *raw_pci_ops;
struct acpi_pci_root; struct pci_mmcfg_region; +struct acpi_device; + +struct pci_sysdata { + int domain; /* PCI domain */ + int node; /* NUMA node */ + struct acpi_device *companion; /* ACPI companion device */ + void *iommu; /* IOMMU private data */ +};
typedef int (*acpi_mcfg_fixup_t)(struct acpi_pci_root *root, struct pci_mmcfg_region *cfg); diff --git a/arch/arm64/kernel/pci.c b/arch/arm64/kernel/pci.c index 42fb195..47f1db1 100644 --- a/arch/arm64/kernel/pci.c +++ b/arch/arm64/kernel/pci.c @@ -18,6 +18,8 @@ #include <linux/of_pci.h> #include <linux/of_platform.h> #include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/pci-acpi.h>
#include <asm/pci-bridge.h>
@@ -38,61 +40,97 @@ resource_size_t pcibios_align_resource(void *data, const struct resource *res, return res->start; }
+int pcibios_root_bridge_prepare(struct pci_host_bridge *bridge) +{ + struct pci_sysdata *sd; + + if (!acpi_disabled) { + sd = bridge->bus->sysdata; + ACPI_COMPANION_SET(&bridge->dev, sd->companion); + } + return 0; +} + /* * Try to assign the IRQ number from DT when adding a new device */ int pcibios_add_device(struct pci_dev *dev) { - dev->irq = of_irq_parse_and_map_pci(dev, 0, 0); + if (acpi_disabled) + dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
return 0; }
+void pcibios_add_bus(struct pci_bus *bus) +{ + if (!acpi_disabled) + acpi_pci_add_bus(bus); +}
-#ifdef CONFIG_PCI_DOMAINS_GENERIC -static bool dt_domain_found = false; - -void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent) +void pcibios_remove_bus(struct pci_bus *bus) { - int domain = of_get_pci_domain_nr(parent->of_node); - - if (domain >= 0) { - dt_domain_found = true; - } else if (dt_domain_found == true) { - dev_err(parent, "Node %s is missing "linux,pci-domain" property in DT\n", - parent->of_node->full_name); - return; - } else { - domain = pci_get_new_domain_nr(); - } + if (!acpi_disabled) + acpi_pci_remove_bus(bus); +}
- bus->domain_nr = domain; +int pcibios_enable_irq(struct pci_dev *dev) +{ + if (!acpi_disabled && !pci_dev_msi_enabled(dev)) + acpi_pci_irq_enable(dev); + return 0; } -#endif
-/* - * raw_pci_read/write - Platform-specific PCI config space access. - * - * Default empty implementation. Replace with an architecture-specific setup - * routine, if necessary. - */ -int raw_pci_read(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 *val) +int pcibios_disable_irq(struct pci_dev *dev) { - return -EINVAL; + if (!acpi_disabled && !pci_dev_msi_enabled(dev)) + acpi_pci_irq_disable(dev); + return 0; }
-int raw_pci_write(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 val) +int pcibios_enable_device(struct pci_dev *dev, int bars) { - return -EINVAL; + int err; + + if ((err = pci_enable_resources(dev, bars)) < 0) + return err; + + if (!pci_dev_msi_enabled(dev)) + return pcibios_enable_irq(dev); + return 0; }
-#ifdef CONFIG_ACPI -/* Root bridge scanning */ -struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) +#ifdef CONFIG_PCI_DOMAINS_GENERIC +void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent) { - /* TODO: Should be revisited when implementing PCI on ACPI */ - return NULL; + int domain = -1; + + if (acpi_disabled) + domain = of_get_pci_domain_nr(parent->of_node); + else { + struct pci_sysdata *sd = bus->sysdata; + domain = sd->domain; + } + if (domain >= 0) + bus->domain_nr = domain; } #endif + +static int __init pcibios_assign_resources(void) +{ + struct pci_bus *root_bus; + + if (acpi_disabled) + return 0; + + list_for_each_entry(root_bus, &pci_root_buses, node) { + pcibios_resource_survey_bus(root_bus); + pci_assign_unassigned_root_bus_resources(root_bus); + } + return 0; +} +/* + * fs_initcall comes after subsys_initcall, so we know acpi scan + * has run. + */ +fs_initcall(pcibios_assign_resources); diff --git a/arch/arm64/pci/pci.c b/arch/arm64/pci/pci.c index 35d99b3..a1f8b08 100644 --- a/arch/arm64/pci/pci.c +++ b/arch/arm64/pci/pci.c @@ -1,8 +1,21 @@ #include <linux/acpi.h> +#include <linux/of_address.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/pci.h>
+struct pci_root_info { + struct acpi_device *bridge; + char name[16]; + unsigned int res_num; + struct resource *res; + resource_size_t *res_offset; + struct pci_sysdata sd; + u16 segment; + u8 start_bus; + u8 end_bus; +}; + const struct pci_raw_ops *__read_mostly raw_pci_ops;
/** @@ -29,8 +42,334 @@ int raw_pci_write(unsigned int domain, unsigned int bus, return raw_pci_ops->write(domain, bus, devfn, reg, len, val); }
+static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value) +{ + return raw_pci_read(pci_domain_nr(bus), bus->number, + devfn, where, size, value); +} + +static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value) +{ + return raw_pci_write(pci_domain_nr(bus), bus->number, + devfn, where, size, value); +} + +static struct pci_ops pci_root_ops = { + .read = pci_read, + .write = pci_write, +}; + +static acpi_status resource_to_addr(struct acpi_resource *resource, + struct acpi_resource_address64 *addr) +{ + acpi_status status; + + memset(addr, 0, sizeof(*addr)); + switch (resource->type) { + case ACPI_RESOURCE_TYPE_ADDRESS16: + case ACPI_RESOURCE_TYPE_ADDRESS32: + case ACPI_RESOURCE_TYPE_ADDRESS64: + status = acpi_resource_to_address64(resource, addr); + if (ACPI_SUCCESS(status) && + (addr->resource_type == ACPI_MEMORY_RANGE || + addr->resource_type == ACPI_IO_RANGE) && + addr->address_length > 0) { + return AE_OK; + } + break; + } + return AE_ERROR; +} + +static acpi_status count_resource(struct acpi_resource *acpi_res, void *data) +{ + struct pci_root_info *info = data; + struct acpi_resource_address64 addr; + acpi_status status; + + status = resource_to_addr(acpi_res, &addr); + if (ACPI_SUCCESS(status)) + info->res_num++; + return AE_OK; +} + +static acpi_status setup_resource(struct acpi_resource *acpi_res, void *data) +{ + struct pci_root_info *info = data; + struct resource *res; + struct acpi_resource_address64 addr; + acpi_status status; + unsigned long flags; + u64 start, end; + + status = resource_to_addr(acpi_res, &addr); + if (!ACPI_SUCCESS(status)) + return AE_OK; + + if (addr.resource_type == ACPI_MEMORY_RANGE) { + flags = IORESOURCE_MEM; + if (addr.info.mem.caching == ACPI_PREFETCHABLE_MEMORY) + flags |= IORESOURCE_PREFETCH; + } else if (addr.resource_type == ACPI_IO_RANGE) { + flags = IORESOURCE_IO; + } else + return AE_OK; + + start = addr.minimum + addr.translation_offset; + end = addr.maximum + addr.translation_offset; + + res = &info->res[info->res_num]; + res->name = info->name; + res->flags = flags; + res->start = start; + res->end = end; + + if (flags & IORESOURCE_IO) { + unsigned long port; + int err; + + err = pci_register_io_range(start, addr.address_length); + if (err) + return AE_OK; + + port = pci_address_to_pio(start); + if (port == (unsigned long)-1) { + res->start = -1; + res->end = -1; + return AE_OK; + } + + res->start = port; + res->end = res->start + addr.address_length - 1; + + if (pci_remap_iospace(res, start) < 0) + return AE_OK; + + info->res_offset[info->res_num] = 0; + } else + info->res_offset[info->res_num] = addr.translation_offset; + + info->res_num++; + + return AE_OK; +} + +static void coalesce_windows(struct pci_root_info *info, unsigned long type) +{ + int i, j; + struct resource *res1, *res2; + + for (i = 0; i < info->res_num; i++) { + res1 = &info->res[i]; + if (!(res1->flags & type)) + continue; + + for (j = i + 1; j < info->res_num; j++) { + res2 = &info->res[j]; + if (!(res2->flags & type)) + continue; + + /* + * I don't like throwing away windows because then + * our resources no longer match the ACPI _CRS, but + * the kernel resource tree doesn't allow overlaps. + */ + if (resource_overlaps(res1, res2)) { + res2->start = min(res1->start, res2->start); + res2->end = max(res1->end, res2->end); + dev_info(&info->bridge->dev, + "host bridge window expanded to %pR; %pR ignored\n", + res2, res1); + res1->flags = 0; + } + } + } +} + +static void add_resources(struct pci_root_info *info, + struct list_head *resources) +{ + int i; + struct resource *res, *root, *conflict; + + coalesce_windows(info, IORESOURCE_MEM); + coalesce_windows(info, IORESOURCE_IO); + + for (i = 0; i < info->res_num; i++) { + res = &info->res[i]; + + if (res->flags & IORESOURCE_MEM) + root = &iomem_resource; + else if (res->flags & IORESOURCE_IO) + root = &ioport_resource; + else + continue; + + conflict = insert_resource_conflict(root, res); + if (conflict) + dev_info(&info->bridge->dev, + "ignoring host bridge window %pR (conflicts with %s %pR)\n", + res, conflict->name, conflict); + else + pci_add_resource_offset(resources, res, + info->res_offset[i]); + } +} + +static void free_pci_root_info_res(struct pci_root_info *info) +{ + kfree(info->res); + info->res = NULL; + kfree(info->res_offset); + info->res_offset = NULL; + info->res_num = 0; +} + +static void __release_pci_root_info(struct pci_root_info *info) +{ + int i; + struct resource *res; + + for (i = 0; i < info->res_num; i++) { + res = &info->res[i]; + + if (!res->parent) + continue; + + if (!(res->flags & (IORESOURCE_MEM | IORESOURCE_IO))) + continue; + + release_resource(res); + } + + free_pci_root_info_res(info); + + kfree(info); +} + +static void release_pci_root_info(struct pci_host_bridge *bridge) +{ + struct pci_root_info *info = bridge->release_data; + + __release_pci_root_info(info); +} + +static void probe_pci_root_info(struct pci_root_info *info, + struct acpi_device *device, + int busnum, int domain) +{ + size_t size; + + sprintf(info->name, "PCI Bus %04x:%02x", domain, busnum); + info->bridge = device; + + info->res_num = 0; + acpi_walk_resources(device->handle, METHOD_NAME__CRS, count_resource, + info); + if (!info->res_num) + return; + + size = sizeof(*info->res) * info->res_num; + info->res = kzalloc_node(size, GFP_KERNEL, info->sd.node); + if (!info->res) { + info->res_num = 0; + return; + } + + size = sizeof(*info->res_offset) * info->res_num; + info->res_num = 0; + info->res_offset = kzalloc_node(size, GFP_KERNEL, info->sd.node); + if (!info->res_offset) { + kfree(info->res); + info->res = NULL; + return; + } + + acpi_walk_resources(device->handle, METHOD_NAME__CRS, setup_resource, + info); +} + /* Root bridge scanning */ struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) { - return NULL; + struct acpi_device *device = root->device; + struct pci_mmcfg_region *mcfg; + struct pci_root_info *info; + int domain = root->segment; + int busnum = root->secondary.start; + LIST_HEAD(resources); + struct pci_bus *bus; + struct pci_sysdata *sd; + int node; + + /* we need mmconfig */ + mcfg = pci_mmconfig_lookup(domain, busnum); + if (!mcfg) { + pr_err("pci_bus %04x:%02x has no MCFG table\n", + domain, busnum); + return NULL; + } + if (mcfg->fixup) + (*mcfg->fixup)(root, mcfg); + + if (!raw_pci_ops) { + pr_err("ACPI: No raw PCI ops!\n"); + return NULL; + } + + if (domain && !pci_domains_supported) { + printk(KERN_WARNING "pci_bus %04x:%02x: " + "ignored (multiple domains not supported)\n", + domain, busnum); + return NULL; + } + + node = NUMA_NO_NODE; + + info = kzalloc_node(sizeof(*info), GFP_KERNEL, node); + if (!info) { + printk(KERN_WARNING "pci_bus %04x:%02x: " + "ignored (out of memory)\n", domain, busnum); + return NULL; + } + info->segment = domain; + info->start_bus = busnum; + info->end_bus = root->secondary.end; + + sd = &info->sd; + sd->domain = domain; + sd->node = node; + sd->companion = device; + + probe_pci_root_info(info, device, busnum, domain); + + /* insert busn res at first */ + pci_add_resource(&resources, &root->secondary); + + /* then _CRS resources */ + add_resources(info, &resources); + + bus = pci_create_root_bus(NULL, busnum, &pci_root_ops, sd, &resources); + if (bus) { + pci_scan_child_bus(bus); + pci_set_host_bridge_release(to_pci_host_bridge(bus->bridge), + release_pci_root_info, info); + } else { + pci_free_resource_list(&resources); + __release_pci_root_info(info); + } + + /* After the PCI-E bus has been walked and all devices discovered, + * configure any settings of the fabric that might be necessary. + */ + if (bus) { + struct pci_bus *child; + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + } + + if (bus && node != NUMA_NO_NODE) + dev_printk(KERN_DEBUG, &bus->dev, "on NUMA node %d\n", node); + + return bus; }
From: Al Stone ahs3@redhat.com
Ended up with this compiler warning: CC drivers/base/dma-coherent.o drivers/base/dma-coherent.c:303:2: warning: initialization from incompatible pointer type .device_init = rmem_dma_device_init, ^ drivers/base/dma-coherent.c:303:2: warning: (near initialization for ‘rmem_dma_ops.device_init’)
Correct the function definition for rmem_dma_device_init to match what's required for a reserved_mem struct.
Signed-off-by: Al Stone al.stone@linaro.org --- drivers/base/dma-coherent.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/base/dma-coherent.c b/drivers/base/dma-coherent.c index 55b8398..3fac4d6 100644 --- a/drivers/base/dma-coherent.c +++ b/drivers/base/dma-coherent.c @@ -276,7 +276,7 @@ EXPORT_SYMBOL(dma_mmap_from_coherent); #include <linux/of_fdt.h> #include <linux/of_reserved_mem.h>
-static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev) +static void rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev) { struct dma_coherent_mem *mem = rmem->priv;
@@ -286,11 +286,11 @@ static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev) &mem) != DMA_MEMORY_MAP) { pr_err("Reserved memory: failed to init DMA memory pool at %pa, size %ld MiB\n", &rmem->base, (unsigned long)rmem->size / SZ_1M); - return -ENODEV; + return; } rmem->priv = mem; dma_assign_coherent_memory(dev, mem); - return 0; + return; }
static void rmem_dma_device_release(struct reserved_mem *rmem,
On Wednesday 05 November 2014 20:32:54 al.stone@linaro.org wrote:
From: Al Stone ahs3@redhat.com
Ended up with this compiler warning: CC drivers/base/dma-coherent.o drivers/base/dma-coherent.c:303:2: warning: initialization from incompatible pointer type .device_init = rmem_dma_device_init, ^ drivers/base/dma-coherent.c:303:2: warning: (near initialization for ‘rmem_dma_ops.device_init’)
Correct the function definition for rmem_dma_device_init to match what's required for a reserved_mem struct.
Signed-off-by: Al Stone al.stone@linaro.org
We definitely need this in 3.18.
Arnd
From: Al Stone ahs3@redhat.com
Got a compiler warning:
CC drivers/of/base.o drivers/of/base.c: In function ‘of_property_read_string_array’: drivers/of/base.c:1472:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ Added a 'return 0;' so the function always returns a value as it should.
Signed-off-by: Al Stone al.stone@linaro.org --- drivers/of/base.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/of/base.c b/drivers/of/base.c index 74ab1b8..f71d2e4 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1469,6 +1469,7 @@ int of_property_read_string_array(struct device_node *np, const char *propname, total += l; p += l; } + return 0; }
void of_print_phandle_args(const char *msg, const struct of_phandle_args *args)
From: Al Stone ahs3@redhat.com
The function amd_xgbe_an_enable_kr_training ended up being defined twice. Removed the first definition in the file.
Signed-off-by: Al Stone al.stone@linaro.org --- drivers/net/phy/amd-xgbe-phy.c | 14 -------------- 1 file changed, 14 deletions(-)
diff --git a/drivers/net/phy/amd-xgbe-phy.c b/drivers/net/phy/amd-xgbe-phy.c index d852c6e..9184eb0 100644 --- a/drivers/net/phy/amd-xgbe-phy.c +++ b/drivers/net/phy/amd-xgbe-phy.c @@ -289,20 +289,6 @@ struct amd_xgbe_phy_priv { struct workqueue_struct *an_workqueue; };
-static int amd_xgbe_an_enable_kr_training(struct phy_device *phydev) -{ - int ret; - - ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL); - if (ret < 0) - return ret; - - ret |= 0x02; - phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL, ret); - - return 0; -} - static int amd_xgbe_an_disable_kr_training(struct phy_device *phydev) { int ret;
From: Mark Salter msalter@redhat.com
Tell kernel to prefer one of the serial ports on platforms pl011, 8250, or sbsa uarts. console= on command line will override these assumed preferences.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/kernel/setup.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+)
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 070d99a..335adb1 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -519,3 +519,25 @@ const struct seq_operations cpuinfo_op = { .stop = c_stop, .show = c_show }; + +/* + * Temporary hack to avoid need for console= on command line + */ +static int __init arm64_console_setup(void) +{ + /* Allow cmdline to override our assumed preferences */ + if (console_set_on_cmdline) + return 0; + + if (IS_ENABLED(CONFIG_SBSAUART_TTY)) + add_preferred_console("ttySBSA", 0, "115200"); + + if (IS_ENABLED(CONFIG_SERIAL_AMBA_PL011)) + add_preferred_console("ttyAMA", 0, "115200"); + + if (IS_ENABLED(CONFIG_SERIAL_8250)) + add_preferred_console("ttyS", 0, "115200"); + + return 0; +} +early_initcall(arm64_console_setup);
On Wednesday 05 November 2014 20:32:57 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Tell kernel to prefer one of the serial ports on platforms pl011, 8250, or sbsa uarts. console= on command line will override these assumed preferences.
Signed-off-by: Mark Salter msalter@redhat.com
arch/arm64/kernel/setup.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+)
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 070d99a..335adb1 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -519,3 +519,25 @@ const struct seq_operations cpuinfo_op = { .stop = c_stop, .show = c_show };
+/*
- Temporary hack to avoid need for console= on command line
- */
+static int __init arm64_console_setup(void) +{
/* Allow cmdline to override our assumed preferences */
if (console_set_on_cmdline)
return 0;
if (IS_ENABLED(CONFIG_SBSAUART_TTY))
add_preferred_console("ttySBSA", 0, "115200");
if (IS_ENABLED(CONFIG_SERIAL_AMBA_PL011))
add_preferred_console("ttyAMA", 0, "115200");
if (IS_ENABLED(CONFIG_SERIAL_8250))
add_preferred_console("ttyS", 0, "115200");
return 0;
+} +early_initcall(arm64_console_setup);
I don't think this is a good idea. In DT, we have the 'stdout-path' property in the chosen node that points to the correct device to use, and this is already used by earlycon, so we should use the same one for the real console.
I would assume that ACPI also has a way to specify the preferred console in some table, so we should use that there.
Arnd
On Wednesday 05 November 2014 20:32:19 al.stone@linaro.org wrote:
From: Al Stone ahs3@redhat.com
This patch set is in the acpi-topic-seattle branch of acpi.git.
The basic idea is that these patches are an example of how to implement ACPI for the AMD Seattle platform. Many of them have been borrowed from the Fedora arm64 kernel tree which is at:
git://git.fedorahosted.org/git/kernel-arm64.git
Additional patches come from Linaro and LKMLA. With the exception of the PCI patches, or others mentioned below, the intent is that the original authors of these patches will submit them upstream. As always, they rely on the ACPI core patches already being in place.
Looks good for the most part, thanks a lot for posting the series!
The majority of the patches are not actually Seattle specific as far as I can tell, so I think it would be good to separate them into another series, or put them into the base support series.
The most controversial parts that I can see are the X-Gene specific patches, parking protocol and PCI hacks in particular, which IMO have no business in this series.
Patch 0026 adds ACPI support to the AMD XGBE 10Gbe interface. Yes, just one patch.
This is probably the only Seattle specific patch that we need to discuss in more detail. I've replied with some comments on the issues I see here, but I think we should take that discussion to a broader mailing list.
These patches were tested on an AMD Seattle platform, using the 71C version of their firmware (future versions will have needed fixes for PCI windows and some other odds and ends).
Could you also include an email with the DSDT source from that version? There might be some more things in there that we need to discuss that were not obvious from the series.
Things to do: -- Get PCI re-done; there are way too many x86-isms in the code, and some serious refactoring and cleanup is needed; the ia64 seems to have taken some good steps in this direction.
From the discussions we had on the ARM64 PCI support, I think it would
be better to leave x86 and ia64 alone for now and put the new generic code into drivers/pci/. x86 can migrate to that over time if they want to, for ia64 there is probably little point in touching it. ia64 also handles things like I/O space in its own way that is incompatible with most other architectures.
-- MSI(-X) support: a DT version has been posted, and some ACPI work is under way.
I think we are still lacking a binding for GICv3+PCI here even in the DT case. We should definitely come up with a way to describe the translation that works on both DT and ACPI. The problem is that the mapping of domain/bus/device/function numbers to GIC requester IDs is not standardized in hardware, so anyone can do it differently and we need to describe the mapping.
-- KVM support: ACPI support patches are available, but were not included here since I have not had time to test them.
Is this for host or guest?
Arnd
On Wednesday 05 November 2014 20:32:19 al.stone@linaro.org wrote:
From: Al Stone ahs3@redhat.com
This patch set is in the acpi-topic-seattle branch of acpi.git.
The basic idea is that these patches are an example of how to implement ACPI for the AMD Seattle platform. Many of them have been borrowed from the Fedora arm64 kernel tree which is at:
git://git.fedorahosted.org/git/kernel-arm64.git
Additional patches come from Linaro and LKMLA. With the exception of the PCI patches, or others mentioned below, the intent is that the original authors of these patches will submit them upstream. As always, they rely on the ACPI core patches already being in place.
I just looked at the patch that introduces the DT support in the kernel today, and one thing I noticed is the discrepancy between supported devices:
* DT lists gpio, ccp, i2c, spi, sd/mmc and dma devices that don't seem to be enabled on ACPI.
* ACPI enables xgbe and smmu, which in turn are not present in the initial DT file.
Can you explain the full story here? Which of the devices that are missing with ACPI do you expect to enable in the future or leave unsupported?
What is holding up enabling the xgbe and smmu devices in DT?
Arnd