From a7715486507e75e4a7cee843a48067b15595defa Mon Sep 17 00:00:00 2001 From: Ed Tanous Date: Wed, 13 Feb 2019 16:51:50 -0800 Subject: Initial commit of intel repository Signed-off-by: Ed Tanous --- .../0005-arm-dts-aspeed-g5-add-espi.patch | 56 + .../0007-New-flash-map-for-intel.patch | 125 ++ .../0008-Add-ASPEED-SGPIO-driver.patch | 474 +++++ .../0009-SGPIO-DT-and-pinctrl-fixup.patch | 240 +++ ...-drivers-to-sync-with-linux-upstreaming-v.patch | 276 +++ .../linux-aspeed/0019-Add-I2C-IPMB-support.patch | 426 +++++ ...021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch | 565 ++++++ .../0022-Add-AST2500-eSPI-driver.patch | 597 ++++++ .../0025-dts-add-AST2500-LPC-SIO-tree-node.patch | 32 + .../0026-Add-support-for-new-PECI-commands.patch | 392 ++++ .../0028-Add-AST2500-JTAG-driver.patch | 1138 +++++++++++ ...Improve-driver-to-support-multi-master-us.patch | 291 +++ ...0030-Add-dump-debug-code-into-I2C-drivers.patch | 151 ++ ...Add-high-speed-baud-rate-support-for-UART.patch | 132 ++ ...ed-Add-Aspeed-UART-routing-control-driver.patch | 556 ++++++ ...m-dts-adpeed-Swap-the-mac-nodes-numbering.patch | 85 + ...35-Implement-a-memory-driver-share-memory.patch | 249 +++ .../0036-net-ncsi-backport-ncsi-patches.patch | 1425 ++++++++++++++ .../0038-media-aspeed-backport-ikvm-patches.patch | 1982 ++++++++++++++++++++ ...d-PWM-driver-which-uses-FTTMR010-timer-IP.patch | 702 +++++++ .../0040-i2c-Add-mux-hold-unhold-msg-types.patch | 492 +++++ .../recipes-kernel/linux/linux-aspeed/intel.cfg | 1 + .../recipes-kernel/linux/linux-aspeed_%.bbappend | 31 + ...e-passthrough-based-gpio-character-device.patch | 26 + .../linux/linux-libc-headers_%.bbappend | 5 + 25 files changed, 10449 insertions(+) create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0005-arm-dts-aspeed-g5-add-espi.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0007-New-flash-map-for-intel.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0008-Add-ASPEED-SGPIO-driver.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0009-SGPIO-DT-and-pinctrl-fixup.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0010-Update-PECI-drivers-to-sync-with-linux-upstreaming-v.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0019-Add-I2C-IPMB-support.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0022-Add-AST2500-eSPI-driver.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0025-dts-add-AST2500-LPC-SIO-tree-node.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0026-Add-support-for-new-PECI-commands.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0028-Add-AST2500-JTAG-driver.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0029-i2c-aspeed-Improve-driver-to-support-multi-master-us.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0030-Add-dump-debug-code-into-I2C-drivers.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0031-Add-high-speed-baud-rate-support-for-UART.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0032-misc-aspeed-Add-Aspeed-UART-routing-control-driver.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0034-arm-dts-adpeed-Swap-the-mac-nodes-numbering.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0035-Implement-a-memory-driver-share-memory.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0038-media-aspeed-backport-ikvm-patches.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0039-Add-Aspeed-PWM-driver-which-uses-FTTMR010-timer-IP.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0040-i2c-Add-mux-hold-unhold-msg-types.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/intel.cfg create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed_%.bbappend create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers/0001-Enable-passthrough-based-gpio-character-device.patch create mode 100644 meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers_%.bbappend (limited to 'meta-openbmc-mods/meta-common/recipes-kernel/linux') diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0005-arm-dts-aspeed-g5-add-espi.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0005-arm-dts-aspeed-g5-add-espi.patch new file mode 100644 index 000000000..2a94453b3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0005-arm-dts-aspeed-g5-add-espi.patch @@ -0,0 +1,56 @@ +From 2affc8ab570c9d1e6d6e5ecbdbeddbc5e3b15cc5 Mon Sep 17 00:00:00 2001 +From: Juston Li +Date: Mon, 27 Mar 2017 11:16:00 -0700 +Subject: [PATCH] arm: dts: aspeed-g5: add espi + +Change-Id: I0b607657883619a3acefdbf344d39bf01790c4b1 +Signed-off-by: Juston Li +--- + arch/arm/boot/dts/aspeed-g5.dtsi | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index e4c5de3208e0..a3c456ba3f34 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -260,13 +260,22 @@ + gpio-controller; + compatible = "aspeed,ast2500-gpio"; +- reg = <0x1e780000 0x1000>; ++ reg = <0x1e780000 0x0200>; + interrupts = <20>; + gpio-ranges = <&pinctrl 0 0 220>; + clocks = <&syscon ASPEED_CLK_APB>; + interrupt-controller; + #interrupt-cells = <2>; + }; + ++ sgpio: sgpio@1e780200 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2500-sgpio"; ++ reg = <0x1e780200 0x0100>; ++ interrupts = <40>; ++ interrupt-controller; ++ }; ++ + rtc: rtc@1e781000 { + compatible = "aspeed,ast2500-rtc"; + reg = <0x1e781000 0x18>; +@@ -342,6 +351,13 @@ + status = "disabled"; + }; + ++ espi: espi@1e6ee000 { ++ compatible = "aspeed,ast2500-espi-slave"; ++ reg = <0x1e6ee000 0x100>; ++ interrupts = <23>; ++ status = "disabled"; ++ }; ++ + lpc: lpc@1e789000 { + compatible = "aspeed,ast2500-lpc", "simple-mfd"; + reg = <0x1e789000 0x1000>; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0007-New-flash-map-for-intel.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0007-New-flash-map-for-intel.patch new file mode 100644 index 000000000..2ac429a22 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0007-New-flash-map-for-intel.patch @@ -0,0 +1,125 @@ +From 074f1c74fde88aac3a10059e4928919782cd40d6 Mon Sep 17 00:00:00 2001 +From: Vernon Mauery +Date: Mon, 4 Jun 2018 13:45:42 -0700 +Subject: [PATCH] New flash map for Intel + =================================================================== + +--- + .../boot/dts/openbmc-flash-layout-intel-128MB.dtsi | 58 ++++++++++++++++++++++ + .../boot/dts/openbmc-flash-layout-intel-64MB.dtsi | 39 +++++++++++++++ + 2 files changed, 97 insertions(+) + create mode 100644 arch/arm/boot/dts/openbmc-flash-layout-intel-128MB.dtsi + create mode 100644 arch/arm/boot/dts/openbmc-flash-layout-intel-64MB.dtsi + +diff --git a/arch/arm/boot/dts/openbmc-flash-layout-intel-128MB.dtsi b/arch/arm/boot/dts/openbmc-flash-layout-intel-128MB.dtsi +new file mode 100644 +index 0000000..23426ac +--- /dev/null ++++ b/arch/arm/boot/dts/openbmc-flash-layout-intel-128MB.dtsi +@@ -0,0 +1,58 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++// 128MB flash layout: PFR (active + tmp1/tmp2 + extra) ++// image with common RW partition ++ ++partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x80000>; ++ label = "u-boot"; ++ }; ++ ++ fit-image-a@80000 { ++ reg = <0x80000 0x1b80000>; ++ label = "image-a"; ++ }; ++ ++ sofs@1c00000 { ++ reg = <0x1c00000 0x200000>; ++ label = "sofs"; ++ }; ++ ++ rwfs@1e00000 { ++ reg = <0x1e00000 0x600000>; ++ label = "rwfs"; ++ }; ++ ++ u-boot-env@2400000 { ++ reg = <0x2400000 0x20000>; ++ label = "u-boot-env"; ++ }; ++ ++ /* ++ pfr-resvd@1260000 { ++ reg = <0x2460000 0x20000>; ++ label = "pfr-resvd"; ++ }; ++ */ ++ ++ rc1@2480000 { ++ reg = <0x2480000 0x1b80000>; ++ label = "rc1"; ++ }; ++ ++ rc2@4000000 { ++ reg = <0x4000000 0x1b80000>; ++ label = "rc2"; ++ }; ++ ++ bios-staging@6000000 { ++ reg = <0x6000000 0x2000000>; ++ label = "bios-staging"; ++ }; ++}; ++ ++ +diff --git a/arch/arm/boot/dts/openbmc-flash-layout-intel-64MB.dtsi b/arch/arm/boot/dts/openbmc-flash-layout-intel-64MB.dtsi +new file mode 100644 +index 0000000..6ae8e57 +--- /dev/null ++++ b/arch/arm/boot/dts/openbmc-flash-layout-intel-64MB.dtsi +@@ -0,0 +1,39 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++// 64MB flash layout: redundant image with common RW partition ++ ++partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ u-boot@0 { ++ reg = <0x0 0x80000>; ++ label = "u-boot"; ++ }; ++ ++ fit-image-a@80000 { ++ reg = <0x80000 0x1b80000>; ++ label = "image-a"; ++ }; ++ ++ sofs@1c00000 { ++ reg = <0x1c00000 0x200000>; ++ label = "sofs"; ++ }; ++ ++ rwfs@1e00000 { ++ reg = <0x1e00000 0x600000>; ++ label = "rwfs"; ++ }; ++ ++ u-boot-env@2400000 { ++ reg = <0x2400000 0x20000>; ++ label = "u-boot-env"; ++ }; ++ ++ fit-image-b@2480000 { ++ reg = <0x2480000 0x1b80000>; ++ label = "image-b"; ++ }; ++}; ++ +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0008-Add-ASPEED-SGPIO-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0008-Add-ASPEED-SGPIO-driver.patch new file mode 100644 index 000000000..78824dde7 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0008-Add-ASPEED-SGPIO-driver.patch @@ -0,0 +1,474 @@ +From 42505ffb3c24b3e7f8182af520ab1c10a3b3f3c4 Mon Sep 17 00:00:00 2001 +From: "Feist, James" +Date: Mon, 5 Jun 2017 11:13:52 -0700 +Subject: [PATCH] Add ASPEED SGPIO driver. + +Port aspeed sgpio driver to OBMC Kernel and +enable it on Purley config. Based off AST sdk 4.0. + +Change-Id: I8529c3fb001ea6f93e63b269cdcdde3887a84e40 +Signed-off-by: James Feist +Signed-off-by: Jae Hyun Yoo +--- + drivers/gpio/Kconfig | 8 + + drivers/gpio/Makefile | 1 + + drivers/gpio/sgpio-aspeed.c | 416 ++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 425 insertions(+) + create mode 100644 drivers/gpio/sgpio-aspeed.c + +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index 71c0ab46f216..a0485be99db7 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -124,6 +124,14 @@ config GPIO_ASPEED + help + Say Y here to support Aspeed AST2400 and AST2500 GPIO controllers. + ++config SGPIO_ASPEED ++ bool "ASPEED SGPIO support" ++ depends on ARCH_ASPEED ++ select GPIO_GENERIC ++ select GPIOLIB_IRQCHIP ++ help ++ Say Y here to support ASPEED SGPIO functionality. ++ + config GPIO_ATH79 + tristate "Atheros AR71XX/AR724X/AR913X GPIO support" + default y if ATH79 +diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile +index 1324c8f966a7..23b8d29bef70 100644 +--- a/drivers/gpio/Makefile ++++ b/drivers/gpio/Makefile +@@ -32,6 +32,7 @@ obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o + obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o + obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o + obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o ++obj-$(CONFIG_SGPIO_ASPEED) += sgpio-aspeed.o + obj-$(CONFIG_GPIO_RASPBERRYPI_EXP) += gpio-raspberrypi-exp.o + obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o + obj-$(CONFIG_GPIO_BD9571MWV) += gpio-bd9571mwv.o +diff --git a/drivers/gpio/sgpio-aspeed.c b/drivers/gpio/sgpio-aspeed.c +new file mode 100644 +index 000000000000..9c4add74602a +--- /dev/null ++++ b/drivers/gpio/sgpio-aspeed.c +@@ -0,0 +1,416 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2012-2017 ASPEED Technology Inc. ++// Copyright (c) 2018 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef ARCH_NR_GPIOS ++#undef ARCH_NR_GPIOS ++#endif ++ ++// TODO: move this to aspeed_sgpio_of_table ++#if defined(CONFIG_MACH_ASPEED_G5) ++#define GPIO_PORT_NUM 29 ++#elif defined(CONFIG_MACH_ASPEED_G4) ++#define GPIO_PORT_NUM 28 ++#endif ++ ++// TODO: fix defines ++#define GPIOS_PER_PORT 8 ++#define ARCH_NR_GPIOS (GPIOS_PER_PORT * GPIO_PORT_NUM) ++#define ASPEED_SGPIO_CTRL 0x54 ++#define SGPIO_CHAIN_CHIP_BASE ARCH_NR_GPIOS ++#define SGPIO_GROUP_NUMS 10 ++ ++#define ASPEED_VIC_NUMS 64 ++#define ASPEED_FIQ_NUMS 64 ++#define ARCH_NR_I2C 14 ++ ++#define IRQ_I2C_CHAIN_START (ASPEED_VIC_NUMS + ASPEED_FIQ_NUMS) ++#define IRQ_GPIO_CHAIN_START (IRQ_I2C_CHAIN_START + ARCH_NR_I2C) ++#define IRQ_SGPIO_CHAIN_START (IRQ_GPIO_CHAIN_START + ARCH_NR_GPIOS) ++ ++#define ASPEED_SGPIO_PINS_MASK GENMASK(9, 6) ++#define ASPEED_SGPIO_CLK_DIV_MASK GENMASK(31, 16) ++#define ASPEED_SGPIO_ENABLE BIT(0) ++ ++struct aspeed_sgpio { ++ struct device *dev; ++ int mirq; /* master irq */ ++ void __iomem *base; ++// TODO: make below members as a struct member ++ uint index; ++ uint data_offset; ++ uint data_read_offset; ++ uint int_en_offset; ++ uint int_type_offset; ++ uint int_sts_offset; ++ uint rst_tol_offset; ++ struct gpio_chip chip; ++}; ++ ++uint aspeed_sgpio_to_irq(uint gpio) ++{ ++ return (gpio + IRQ_SGPIO_CHAIN_START); ++} ++EXPORT_SYMBOL(aspeed_sgpio_to_irq); ++ ++uint aspeed_irq_to_sgpio(uint irq) ++{ ++ return (irq - IRQ_SGPIO_CHAIN_START); ++} ++EXPORT_SYMBOL(aspeed_irq_to_sgpio); ++ ++static int aspeed_sgpio_get(struct gpio_chip *chip, unsigned offset) ++{ ++ struct aspeed_sgpio *priv = gpiochip_get_data(chip); ++ uint bit_nr = priv->index * GPIOS_PER_PORT + offset; ++ unsigned long flags; ++ u32 v; ++ ++ local_irq_save(flags); ++ ++ v = readl(priv->base + priv->data_offset); ++ v &= BIT(bit_nr); ++ ++ if (v) ++ v = 1; ++ else ++ v = 0; ++ ++ local_irq_restore(flags); ++ ++ dev_dbg(priv->dev, "%s, %s[%d]: %d\n", __func__, chip->label, ++ offset, v); ++ ++ return v; ++} ++ ++static void aspeed_sgpio_set(struct gpio_chip *chip, unsigned offset, int val) ++{ ++ struct aspeed_sgpio *priv = gpiochip_get_data(chip); ++ uint bit_nr = priv->index * GPIOS_PER_PORT + offset; ++ unsigned long flags; ++ u32 v; ++ ++ local_irq_save(flags); ++ ++ v = readl(priv->base + priv->data_read_offset); ++ ++ if (val) ++ v |= BIT(bit_nr); ++ else ++ v &= ~BIT(bit_nr); ++ ++ writel(v, priv->base + priv->data_offset); ++ ++ dev_dbg(priv->dev, "%s, %s[%d]: %d\n", __func__, chip->label, ++ offset, val); ++ ++ local_irq_restore(flags); ++} ++ ++#define SGPIO_BANK(name, index_no, data, read_data, int_en, int_type, \ ++ int_sts, rst_tol, chip_base_idx) { \ ++ .index = index_no, \ ++ .data_offset = data, \ ++ .data_read_offset = read_data, \ ++ .int_en_offset = int_en, \ ++ .int_type_offset = int_type, \ ++ .int_sts_offset = int_sts, \ ++ .rst_tol_offset = rst_tol, \ ++ .chip = { \ ++ .label = name, \ ++ .get = aspeed_sgpio_get, \ ++ .set = aspeed_sgpio_set, \ ++ .base = SGPIO_CHAIN_CHIP_BASE + chip_base_idx * 8, \ ++ .ngpio = GPIOS_PER_PORT, \ ++ }, \ ++} ++ ++// TODO: use a single priv after changing it as an array member of the priv ++static struct aspeed_sgpio aspeed_sgpio_gp[] = { ++ SGPIO_BANK("SGPIOA", 0, 0x000, 0x070, 0x004, 0x008, 0x014, 0x018, 0), ++ SGPIO_BANK("SGPIOB", 1, 0x000, 0x070, 0x004, 0x008, 0x014, 0x018, 1), ++ SGPIO_BANK("SGPIOC", 2, 0x000, 0x070, 0x004, 0x008, 0x014, 0x018, 2), ++ SGPIO_BANK("SGPIOD", 3, 0x000, 0x070, 0x004, 0x008, 0x014, 0x018, 3), ++ SGPIO_BANK("SGPIOE", 0, 0x01C, 0x074, 0x020, 0x024, 0x030, 0x034, 4), ++ SGPIO_BANK("SGPIOF", 1, 0x01C, 0x074, 0x020, 0x024, 0x030, 0x034, 5), ++ SGPIO_BANK("SGPIOG", 2, 0x01C, 0x074, 0x020, 0x024, 0x030, 0x034, 6), ++ SGPIO_BANK("SGPIOH", 3, 0x01C, 0x074, 0x020, 0x024, 0x030, 0x034, 7), ++ SGPIO_BANK("SGPIOI", 0, 0x038, 0x078, 0x03C, 0x040, 0x04C, 0x050, 8), ++ SGPIO_BANK("SGPIOJ", 1, 0x038, 0x078, 0x03C, 0x040, 0x04C, 0x050, 9), ++}; ++ ++/** ++ * We need to unmask the GPIO bank interrupt as soon as possible to avoid ++ * missing GPIO interrupts for other lines in the bank. Then we need to ++ * mask-read-clear-unmask the triggered GPIO lines in the bank to avoid missing ++ * nested interrupts for a GPIO line. If we wait to unmask individual GPIO lines ++ * in the bank after the line's interrupt handler has been run, we may miss some ++ * nested interrupts. ++ */ ++static void aspeed_sgpio_irq_handler(struct irq_desc *desc) ++{ ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ struct aspeed_sgpio *priv = irq_desc_get_chip_data(desc); ++ u32 isr; ++ int i; ++ ++ chained_irq_enter(chip, desc); ++ ++ isr = readl(priv->base + priv->int_sts_offset); ++ isr = (isr >> (GPIOS_PER_PORT * priv->index)) & 0xff; ++ ++ dev_dbg(priv->dev, "[%s] isr %x \n", priv->chip.label, isr); ++ ++ if (isr != 0) { ++ for (i = 0; i < GPIOS_PER_PORT; i++) { ++ if (BIT(i) & isr) ++ generic_handle_irq(i * GPIOS_PER_PORT + ++ i + IRQ_SGPIO_CHAIN_START); ++ } ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void aspeed_sgpio_ack_irq(struct irq_data *d) ++{ ++ struct aspeed_sgpio *priv = irq_get_chip_data(d->irq); ++ uint sgpio_irq = (d->irq - IRQ_SGPIO_CHAIN_START) % GPIOS_PER_PORT; ++ uint bit_nr = priv->index * GPIOS_PER_PORT + sgpio_irq; ++ ++ dev_dbg(priv->dev, "irq %d: %s [%s] pin %d\n", d->irq, __func__, ++ priv->chip.label, sgpio_irq); ++ ++ writel(BIT(bit_nr), priv->base + priv->int_sts_offset); ++} ++ ++static void aspeed_sgpio_mask_irq(struct irq_data *d) ++{ ++ struct aspeed_sgpio *priv = irq_get_chip_data(d->irq); ++ uint sgpio_irq = (d->irq - IRQ_SGPIO_CHAIN_START) % GPIOS_PER_PORT; ++ uint bit_nr = priv->index * GPIOS_PER_PORT + sgpio_irq; ++ ++ /* Disable IRQ */ ++ writel(readl(priv->base + priv->int_en_offset) & ~BIT(bit_nr), ++ priv->base + priv->int_en_offset); ++ ++ dev_dbg(priv->dev, "irq %d: %s [%s] pin %d\n ", d->irq, __func__, ++ priv->chip.label, sgpio_irq); ++} ++ ++static void aspeed_sgpio_unmask_irq(struct irq_data *d) ++{ ++ struct aspeed_sgpio *priv = irq_data_get_irq_chip_data(d); ++ u32 sgpio_irq = (d->irq - IRQ_SGPIO_CHAIN_START) % GPIOS_PER_PORT; ++ uint bit_nr = priv->index * GPIOS_PER_PORT + sgpio_irq; ++ ++ /* Enable IRQ */ ++ writel(BIT(bit_nr), priv->base + priv->int_sts_offset); ++ writel(readl(priv->base + priv->int_en_offset) | BIT(bit_nr), ++ priv->base + priv->int_en_offset); ++ ++ dev_dbg(priv->dev, "irq %d: %s [%s] pin %d\n", d->irq, __func__, ++ priv->chip.label, sgpio_irq); ++} ++ ++static int aspeed_sgpio_irq_type(struct irq_data *d, unsigned int type) ++{ ++ unsigned int irq = d->irq; ++ struct aspeed_sgpio *priv = irq_get_chip_data(irq); ++ u32 sgpio_irq = (irq - IRQ_SGPIO_CHAIN_START) % GPIOS_PER_PORT; ++ u32 type0, type1, type2; ++ ++ dev_dbg(priv->dev, "irq: %d, sgpio_irq: %d , irq_type: 0x%x\n", ++ irq, sgpio_irq, type); ++ ++ if (type & ~IRQ_TYPE_SENSE_MASK) ++ return -EINVAL; ++ ++ type0 = readl(priv->base + priv->int_type_offset); ++ type1 = readl(priv->base + priv->int_type_offset + 0x04); ++ type2 = readl(priv->base + priv->int_type_offset + 0x08); ++ ++ switch (type) { ++ /* Edge rising type */ ++ case IRQ_TYPE_EDGE_RISING: ++ type0 |= BIT(sgpio_irq); ++ type1 &= ~BIT(sgpio_irq); ++ type2 &= ~BIT(sgpio_irq); ++ break; ++ /* Edge falling type */ ++ case IRQ_TYPE_EDGE_FALLING: ++ type2 |= BIT(sgpio_irq); ++ break; ++ case IRQ_TYPE_EDGE_BOTH: ++ type0 &= ~BIT(sgpio_irq); ++ type1 |= BIT(sgpio_irq); ++ type2 &= ~BIT(sgpio_irq); ++ break; ++ case IRQ_TYPE_LEVEL_HIGH: ++ type0 |= BIT(sgpio_irq); ++ type1 |= BIT(sgpio_irq); ++ type2 &= ~BIT(sgpio_irq); ++ break; ++ case IRQ_TYPE_LEVEL_LOW: ++ type0 &= ~BIT(sgpio_irq); ++ type1 |= BIT(sgpio_irq); ++ type2 &= ~BIT(sgpio_irq); ++ break; ++ default: ++ dev_dbg(priv->dev, "not supported trigger type: %d", type); ++ return -EINVAL; ++ break; ++ } ++ ++ writel(type0, priv->base + priv->int_type_offset); ++ writel(type1, priv->base + priv->int_type_offset + 0x04); ++ writel(type2, priv->base + priv->int_type_offset + 0x08); ++ ++ return 0; ++} ++ ++static struct irq_chip aspeed_sgpio_irq_chip = { ++ .name = "aspeed-sgpio", ++ .irq_ack = aspeed_sgpio_ack_irq, ++ .irq_mask = aspeed_sgpio_mask_irq, ++ .irq_unmask = aspeed_sgpio_unmask_irq, ++ .irq_set_type = aspeed_sgpio_irq_type, ++}; ++ ++static int aspeed_sgpio_config(struct aspeed_sgpio *priv) ++{ ++ /** ++ * There is a limitation that SGPIO clock division has to be larger or ++ * equal to 1. And the value of clock division read back is left shift ++ * 1 bit from actual value. ++ * ++ * GPIO254[31:16] - Serial GPIO clock division ++ * Serial GPIO clock period = period of PCLK * 2 * (GPIO254[31:16] + 1) ++ * ++ * SGPIO master controller updates every data input when SGPMLD is low. ++ * For example, SGPIO clock is 1MHz and number of SGPIO is 80 then each ++ * SGPIO will be updated in every 80us. ++ */ ++ writel(FIELD_PREP(ASPEED_SGPIO_CLK_DIV_MASK, 10) | ++ FIELD_PREP(ASPEED_SGPIO_PINS_MASK, SGPIO_GROUP_NUMS) | ++ ASPEED_SGPIO_ENABLE, ++ priv->base + ASPEED_SGPIO_CTRL); ++ dev_dbg(priv->dev, "sgpio config reg: 0x%08X\n", ++ readl(priv->base + ASPEED_SGPIO_CTRL)); ++ ++ return 0; ++} ++ ++static int aspeed_sgpio_probe(struct platform_device *pdev) ++{ ++ int i, j; ++ uint irq; ++ struct resource *res; ++ struct aspeed_sgpio *priv; ++ void __iomem *base; ++ int mirq; ++ ++ // aspeed_scu_multi_func_sgpio(); done via pinctl ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ++ base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ mirq = platform_get_irq(pdev, 0); ++ if (!mirq) ++ return -ENODEV; ++ ++ for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_gp); i++) { ++ // TODO: use heap allocation and use a single priv ++ priv = &aspeed_sgpio_gp[i]; ++ priv->dev = &pdev->dev; ++ priv->base = base; ++ priv->mirq = mirq; ++ dev_set_drvdata(&pdev->dev, priv); ++ ++ dev_dbg(priv->dev, "add gpio_chip [%s]: %d\n", priv->chip.label, ++ i); ++ ++ devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv); ++ ++ /* Disable Interrupt & Clear Status & Set Level-High Trigger */ ++ writel(0x00000000, priv->base + priv->int_en_offset); ++ writel(0xffffffff, priv->base + priv->int_sts_offset); ++ writel(0xffffffff, priv->base + priv->int_type_offset); ++ writel(0xffffffff, priv->base + priv->int_type_offset + 0x04); ++ writel(0x00000000, priv->base + priv->int_type_offset + 0x08); ++ ++ // TODO: no this many chip registration is needed. fix it. ++ for (j = 0; j < GPIOS_PER_PORT; j++) { ++ irq = i * GPIOS_PER_PORT + j + IRQ_SGPIO_CHAIN_START; ++ dev_dbg(priv->dev, "inst chip data %d\n", irq); ++ irq_set_chip_data(irq, priv); ++ irq_set_chip_and_handler(irq, &aspeed_sgpio_irq_chip, ++ handle_level_irq); ++ irq_clear_status_flags(irq, IRQ_NOREQUEST); ++ } ++ } ++ ++ irq_set_chained_handler(priv->mirq, aspeed_sgpio_irq_handler); ++ ++ aspeed_sgpio_config(priv); ++ ++ dev_info(&pdev->dev, "sgpio controller registered, irq %d\n", ++ priv->mirq); ++ ++ return 0; ++} ++ ++static int aspeed_sgpio_remove(struct platform_device *pdev) ++{ ++ struct aspeed_sgpio *priv = ++ &aspeed_sgpio_gp[ARRAY_SIZE(aspeed_sgpio_gp) - 1]; ++ ++ irq_set_chained_handler(priv->mirq, NULL); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_sgpio_of_table[] = { ++ { .compatible = "aspeed,ast2400-sgpio", }, ++ { .compatible = "aspeed,ast2500-sgpio", }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_sgpio_of_table); ++ ++static struct platform_driver aspeed_sgpio_driver = { ++ .probe = aspeed_sgpio_probe, ++ .remove = aspeed_sgpio_remove, ++ .driver = { ++ .name = "sgpio-aspeed", ++ .of_match_table = of_match_ptr(aspeed_sgpio_of_table), ++ }, ++}; ++ ++static int __init aspeed_sgpio_init(void) ++{ ++ return platform_driver_register(&aspeed_sgpio_driver); ++} ++subsys_initcall(aspeed_sgpio_init); ++ ++static void __exit aspeed_sgpio_exit(void) ++{ ++ platform_driver_unregister(&aspeed_sgpio_driver); ++} ++module_exit(aspeed_sgpio_exit); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_AUTHOR("Jae Hyun Yoo "); ++MODULE_DESCRIPTION("ASPEED SGPIO driver"); ++MODULE_LICENSE("GPL v2"); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0009-SGPIO-DT-and-pinctrl-fixup.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0009-SGPIO-DT-and-pinctrl-fixup.patch new file mode 100644 index 000000000..346b9e3e3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0009-SGPIO-DT-and-pinctrl-fixup.patch @@ -0,0 +1,240 @@ +From f4b91f5c6723e56e106a609cdbcc8da48c56499e Mon Sep 17 00:00:00 2001 +From: Vernon Mauery +Date: Wed, 16 May 2018 10:03:14 -0700 +Subject: [PATCH] SGPIO DT and pinctrl fixup + +This commit fixes DT and pinctrl for SGPIO use. + +Signed-off-by: Vernon Mauery +Signed-off-by: Jae Hyun Yoo +--- + arch/arm/boot/dts/aspeed-g4.dtsi | 54 ++++++++++-------------------- + arch/arm/boot/dts/aspeed-g5.dtsi | 8 +++++ + drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c | 48 +++++++++++++------------- + drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c | 4 +++ + 4 files changed, 54 insertions(+), 60 deletions(-) + +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index 6af12872ee74..9aed0f696a98 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -201,6 +201,18 @@ + interrupt-controller; + }; + ++ sgpio: sgpio@1e780200 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2400-sgpio"; ++ reg = <0x1e780200 0x0100>; ++ interrupts = <40>; ++ interrupt-controller; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgpm_default>; ++ status = "disabled"; ++ }; ++ + timer: timer@1e782000 { + /* This timer is a Faraday FTTMR010 derivative */ + compatible = "aspeed,ast2400-timer"; +@@ -1150,44 +1162,14 @@ + groups = "SD2"; + }; + +- pinctrl_sgpmck_default: sgpmck_default { +- function = "SGPMCK"; +- groups = "SGPMCK"; +- }; +- +- pinctrl_sgpmi_default: sgpmi_default { +- function = "SGPMI"; +- groups = "SGPMI"; +- }; +- +- pinctrl_sgpmld_default: sgpmld_default { +- function = "SGPMLD"; +- groups = "SGPMLD"; +- }; +- +- pinctrl_sgpmo_default: sgpmo_default { +- function = "SGPMO"; +- groups = "SGPMO"; +- }; +- +- pinctrl_sgpsck_default: sgpsck_default { +- function = "SGPSCK"; +- groups = "SGPSCK"; +- }; +- +- pinctrl_sgpsi0_default: sgpsi0_default { +- function = "SGPSI0"; +- groups = "SGPSI0"; +- }; +- +- pinctrl_sgpsi1_default: sgpsi1_default { +- function = "SGPSI1"; +- groups = "SGPSI1"; ++ pinctrl_sgpm_default: sgpm_default { ++ function = "SGPM"; ++ groups = "SGPM"; + }; + +- pinctrl_sgpsld_default: sgpsld_default { +- function = "SGPSLD"; +- groups = "SGPSLD"; ++ pinctrl_sgps_default: sgps_default { ++ function = "SGPS"; ++ groups = "SGPS"; + }; + + pinctrl_sioonctrl_default: sioonctrl_default { +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 01e901031bd4..36d72c91a2ad 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -274,6 +274,9 @@ + reg = <0x1e780200 0x0100>; + interrupts = <40>; + interrupt-controller; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgpm_default>; ++ status = "disabled"; + }; + + timer: timer@1e782000 { +@@ -1324,6 +1327,11 @@ + groups = "SDA2"; + }; + ++ pinctrl_sgpm_default: sgpm_default { ++ function = "SGPM"; ++ groups = "SGPM"; ++ }; ++ + pinctrl_sgps1_default: sgps1_default { + function = "SGPS1"; + groups = "SGPS1"; +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c +index 05b153034517..353af05b8602 100644 +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c +@@ -401,16 +401,22 @@ SSSF_PIN_DECL(E16, GPIOF6, TXD4, SIG_DESC_SET(SCU80, 30)); + SSSF_PIN_DECL(C17, GPIOF7, RXD4, SIG_DESC_SET(SCU80, 31)); + + #define A14 48 +-SSSF_PIN_DECL(A14, GPIOG0, SGPSCK, SIG_DESC_SET(SCU84, 0)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPSCK, SGPS, SIG_DESC_SET(SCU84, 0)); ++SS_PIN_DECL(A14, GPIOG0, SGPSCK); + + #define E13 49 +-SSSF_PIN_DECL(E13, GPIOG1, SGPSLD, SIG_DESC_SET(SCU84, 1)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPSLD, SGPS, SIG_DESC_SET(SCU84, 1)); ++SS_PIN_DECL(E13, GPIOG1, SGPSLD); + + #define D13 50 +-SSSF_PIN_DECL(D13, GPIOG2, SGPSI0, SIG_DESC_SET(SCU84, 2)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPSIO, SGPS, SIG_DESC_SET(SCU84, 2)); ++SS_PIN_DECL(D13, GPIOG2, SGPSIO); + + #define C13 51 +-SSSF_PIN_DECL(C13, GPIOG3, SGPSI1, SIG_DESC_SET(SCU84, 3)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPSI1, SGPS, SIG_DESC_SET(SCU84, 3)); ++SS_PIN_DECL(C13, GPIOG3, SGPSI1); ++ ++FUNC_GROUP_DECL(SGPS, A14, E13, D13, C13); + + #define B13 52 + SIG_EXPR_LIST_DECL_SINGLE(OSCCLK, OSCCLK, SIG_DESC_SET(SCU2C, 1)); +@@ -576,16 +582,22 @@ FUNC_GROUP_DECL(SPI1PASSTHRU, C22, G18, D19, C20, B22, G19, C18, E20); + FUNC_GROUP_DECL(VGABIOS_ROM, B22, G19, C18, E20); + + #define J5 72 +-SSSF_PIN_DECL(J5, GPIOJ0, SGPMCK, SIG_DESC_SET(SCU84, 8)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPMCK, SGPM, SIG_DESC_SET(SCU84, 8)); ++SS_PIN_DECL(J5, GPIOJ0, SGPMCK); + + #define J4 73 +-SSSF_PIN_DECL(J4, GPIOJ1, SGPMLD, SIG_DESC_SET(SCU84, 9)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPMLD, SGPM, SIG_DESC_SET(SCU84, 9)); ++SS_PIN_DECL(J4, GPIOJ1, SGPMLD); + + #define K5 74 +-SSSF_PIN_DECL(K5, GPIOJ2, SGPMO, SIG_DESC_SET(SCU84, 10)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPMO, SGPM, SIG_DESC_SET(SCU84, 10)); ++SS_PIN_DECL(K5, GPIOJ2, SGPMO); + + #define J3 75 +-SSSF_PIN_DECL(J3, GPIOJ3, SGPMI, SIG_DESC_SET(SCU84, 11)); ++SIG_EXPR_LIST_DECL_SINGLE(SGPMI, SGPM, SIG_DESC_SET(SCU84, 11)); ++SS_PIN_DECL(J3, GPIOJ3, SGPMI); ++ ++FUNC_GROUP_DECL(SGPM, J5, J4, K5, J3); + + #define T4 76 + SSSF_PIN_DECL(T4, GPIOJ4, VGAHS, SIG_DESC_SET(SCU84, 12)); +@@ -2083,14 +2095,8 @@ static const struct aspeed_pin_group aspeed_g4_groups[] = { + ASPEED_PINCTRL_GROUP(SALT4), + ASPEED_PINCTRL_GROUP(SD1), + ASPEED_PINCTRL_GROUP(SD2), +- ASPEED_PINCTRL_GROUP(SGPMCK), +- ASPEED_PINCTRL_GROUP(SGPMI), +- ASPEED_PINCTRL_GROUP(SGPMLD), +- ASPEED_PINCTRL_GROUP(SGPMO), +- ASPEED_PINCTRL_GROUP(SGPSCK), +- ASPEED_PINCTRL_GROUP(SGPSI0), +- ASPEED_PINCTRL_GROUP(SGPSI1), +- ASPEED_PINCTRL_GROUP(SGPSLD), ++ ASPEED_PINCTRL_GROUP(SGPM), ++ ASPEED_PINCTRL_GROUP(SGPS), + ASPEED_PINCTRL_GROUP(SIOONCTRL), + ASPEED_PINCTRL_GROUP(SIOPBI), + ASPEED_PINCTRL_GROUP(SIOPBO), +@@ -2238,14 +2244,8 @@ static const struct aspeed_pin_function aspeed_g4_functions[] = { + ASPEED_PINCTRL_FUNC(SALT4), + ASPEED_PINCTRL_FUNC(SD1), + ASPEED_PINCTRL_FUNC(SD2), +- ASPEED_PINCTRL_FUNC(SGPMCK), +- ASPEED_PINCTRL_FUNC(SGPMI), +- ASPEED_PINCTRL_FUNC(SGPMLD), +- ASPEED_PINCTRL_FUNC(SGPMO), +- ASPEED_PINCTRL_FUNC(SGPSCK), +- ASPEED_PINCTRL_FUNC(SGPSI0), +- ASPEED_PINCTRL_FUNC(SGPSI1), +- ASPEED_PINCTRL_FUNC(SGPSLD), ++ ASPEED_PINCTRL_FUNC(SGPM), ++ ASPEED_PINCTRL_FUNC(SGPS), + ASPEED_PINCTRL_FUNC(SIOONCTRL), + ASPEED_PINCTRL_FUNC(SIOPBI), + ASPEED_PINCTRL_FUNC(SIOPBO), +diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c +index 187abd7693cf..0c89647f166f 100644 +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c +@@ -577,6 +577,8 @@ SS_PIN_DECL(N3, GPIOJ2, SGPMO); + SIG_EXPR_LIST_DECL_SINGLE(SGPMI, SGPM, SIG_DESC_SET(SCU84, 11)); + SS_PIN_DECL(N4, GPIOJ3, SGPMI); + ++FUNC_GROUP_DECL(SGPM, R2, L2, N3, N4); ++ + #define N5 76 + SIG_EXPR_LIST_DECL_SINGLE(VGAHS, VGAHS, SIG_DESC_SET(SCU84, 12)); + SIG_EXPR_LIST_DECL_SINGLE(DASHN5, DASHN5, SIG_DESC_SET(SCU94, 8)); +@@ -2127,6 +2129,7 @@ static const struct aspeed_pin_group aspeed_g5_groups[] = { + ASPEED_PINCTRL_GROUP(SD2), + ASPEED_PINCTRL_GROUP(SDA1), + ASPEED_PINCTRL_GROUP(SDA2), ++ ASPEED_PINCTRL_GROUP(SGPM), + ASPEED_PINCTRL_GROUP(SGPS1), + ASPEED_PINCTRL_GROUP(SGPS2), + ASPEED_PINCTRL_GROUP(SIOONCTRL), +@@ -2296,6 +2299,7 @@ static const struct aspeed_pin_function aspeed_g5_functions[] = { + ASPEED_PINCTRL_FUNC(SD2), + ASPEED_PINCTRL_FUNC(SDA1), + ASPEED_PINCTRL_FUNC(SDA2), ++ ASPEED_PINCTRL_FUNC(SGPM), + ASPEED_PINCTRL_FUNC(SGPS1), + ASPEED_PINCTRL_FUNC(SGPS2), + ASPEED_PINCTRL_FUNC(SIOONCTRL), +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0010-Update-PECI-drivers-to-sync-with-linux-upstreaming-v.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0010-Update-PECI-drivers-to-sync-with-linux-upstreaming-v.patch new file mode 100644 index 000000000..232903f2c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0010-Update-PECI-drivers-to-sync-with-linux-upstreaming-v.patch @@ -0,0 +1,276 @@ +From 3b9ab062d0eb781fc767bd15ce58dc7b7990e65b Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Mon, 7 Jan 2019 09:56:10 -0800 +Subject: [PATCH] Update PECI drivers to sync with linux upstreaming version + +This commit updates PECI drivers to with linux community +upstreaming version. + +Signed-off-by: Jae Hyun Yoo +--- + drivers/hwmon/peci-cputemp.c | 2 +- + drivers/hwmon/peci-dimmtemp.c | 2 +- + drivers/hwmon/peci-hwmon.h | 2 +- + drivers/mfd/intel-peci-client.c | 43 +++++++++++++++-------------------- + drivers/peci/peci-aspeed.c | 2 +- + drivers/peci/peci-core.c | 10 ++++---- + include/linux/mfd/intel-peci-client.h | 4 +--- + include/linux/peci.h | 2 +- + 8 files changed, 29 insertions(+), 38 deletions(-) + +diff --git a/drivers/hwmon/peci-cputemp.c b/drivers/hwmon/peci-cputemp.c +index 11880c86a854..63796d883c82 100644 +--- a/drivers/hwmon/peci-cputemp.c ++++ b/drivers/hwmon/peci-cputemp.c +@@ -1,5 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0 +-// Copyright (c) 2018 Intel Corporation ++// Copyright (c) 2018-2019 Intel Corporation + + #include + #include +diff --git a/drivers/hwmon/peci-dimmtemp.c b/drivers/hwmon/peci-dimmtemp.c +index 86a45a90805b..6e90d9bfeb45 100644 +--- a/drivers/hwmon/peci-dimmtemp.c ++++ b/drivers/hwmon/peci-dimmtemp.c +@@ -1,5 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0 +-// Copyright (c) 2018 Intel Corporation ++// Copyright (c) 2018-2019 Intel Corporation + + #include + #include +diff --git a/drivers/hwmon/peci-hwmon.h b/drivers/hwmon/peci-hwmon.h +index 6ca1855a86bb..16e3c195094c 100644 +--- a/drivers/hwmon/peci-hwmon.h ++++ b/drivers/hwmon/peci-hwmon.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0 */ +-/* Copyright (c) 2018 Intel Corporation */ ++/* Copyright (c) 2018-2019 Intel Corporation */ + + #ifndef __PECI_HWMON_H + #define __PECI_HWMON_H +diff --git a/drivers/mfd/intel-peci-client.c b/drivers/mfd/intel-peci-client.c +index d53e4f1078ac..d62442438512 100644 +--- a/drivers/mfd/intel-peci-client.c ++++ b/drivers/mfd/intel-peci-client.c +@@ -1,12 +1,12 @@ + // SPDX-License-Identifier: GPL-2.0 +-// Copyright (c) 2018 Intel Corporation ++// Copyright (c) 2018-2019 Intel Corporation + + #include + #include + #include + #include +-#include + #include ++#include + + #define CPU_ID_MODEL_MASK GENMASK(7, 4) + #define CPU_ID_FAMILY_MASK GENMASK(11, 8) +@@ -18,12 +18,6 @@ + #define LOWER_BYTE_MASK GENMASK(7, 0) + #define UPPER_BYTE_MASK GENMASK(16, 8) + +-enum cpu_gens { +- CPU_GEN_HSX = 0, /* Haswell Xeon */ +- CPU_GEN_BRX, /* Broadwell Xeon */ +- CPU_GEN_SKX, /* Skylake Xeon */ +-}; +- + static struct mfd_cell peci_functions[] = { + { .name = "peci-cputemp", }, + { .name = "peci-dimmtemp", }, +@@ -31,19 +25,19 @@ static struct mfd_cell peci_functions[] = { + }; + + static const struct cpu_gen_info cpu_gen_info_table[] = { +- [CPU_GEN_HSX] = { ++ { /* Haswell Xeon */ + .family = 6, /* Family code */ + .model = INTEL_FAM6_HASWELL_X, + .core_max = CORE_MAX_ON_HSX, + .chan_rank_max = CHAN_RANK_MAX_ON_HSX, + .dimm_idx_max = DIMM_IDX_MAX_ON_HSX }, +- [CPU_GEN_BRX] = { ++ { /* Broadwell Xeon */ + .family = 6, /* Family code */ + .model = INTEL_FAM6_BROADWELL_X, + .core_max = CORE_MAX_ON_BDX, + .chan_rank_max = CHAN_RANK_MAX_ON_BDX, + .dimm_idx_max = DIMM_IDX_MAX_ON_BDX }, +- [CPU_GEN_SKX] = { ++ { /* Skylake Xeon */ + .family = 6, /* Family code */ + .model = INTEL_FAM6_SKYLAKE_X, + .core_max = CORE_MAX_ON_SKX, +@@ -53,16 +47,17 @@ static const struct cpu_gen_info cpu_gen_info_table[] = { + + static int peci_client_get_cpu_gen_info(struct peci_client_manager *priv) + { ++ struct device *dev = &priv->client->dev; + u32 cpu_id; + u16 family; + u8 model; +- int rc; ++ int ret; + int i; + +- rc = peci_get_cpu_id(priv->client->adapter, priv->client->addr, +- &cpu_id); +- if (rc) +- return rc; ++ ret = peci_get_cpu_id(priv->client->adapter, priv->client->addr, ++ &cpu_id); ++ if (ret) ++ return ret; + + family = FIELD_PREP(LOWER_BYTE_MASK, + FIELD_GET(CPU_ID_FAMILY_MASK, cpu_id)) | +@@ -83,11 +78,11 @@ static int peci_client_get_cpu_gen_info(struct peci_client_manager *priv) + } + + if (!priv->gen_info) { +- dev_err(priv->dev, "Can't support this CPU: 0x%x\n", cpu_id); +- rc = -ENODEV; ++ dev_err(dev, "Can't support this CPU: 0x%x\n", cpu_id); ++ ret = -ENODEV; + } + +- return rc; ++ return ret; + } + + static int peci_client_probe(struct peci_client *client) +@@ -103,31 +98,29 @@ static int peci_client_probe(struct peci_client *client) + + dev_set_drvdata(dev, priv); + priv->client = client; +- priv->dev = dev; + cpu_no = client->addr - PECI_BASE_ADDR; + + ret = peci_client_get_cpu_gen_info(priv); + if (ret) + return ret; + +- ret = devm_mfd_add_devices(priv->dev, cpu_no, peci_functions, ++ ret = devm_mfd_add_devices(dev, cpu_no, peci_functions, + ARRAY_SIZE(peci_functions), NULL, 0, NULL); + if (ret < 0) { +- dev_err(priv->dev, "Failed to register child devices: %d\n", +- ret); ++ dev_err(dev, "Failed to register child devices: %d\n", ret); + return ret; + } + + return 0; + } + +-#ifdef CONFIG_OF ++#if IS_ENABLED(CONFIG_OF) + static const struct of_device_id peci_client_of_table[] = { + { .compatible = "intel,peci-client" }, + { } + }; + MODULE_DEVICE_TABLE(of, peci_client_of_table); +-#endif ++#endif /* CONFIG_OF */ + + static const struct peci_device_id peci_client_ids[] = { + { .name = "peci-client" }, +diff --git a/drivers/peci/peci-aspeed.c b/drivers/peci/peci-aspeed.c +index 51cb2563ceb6..2293d4e56e63 100644 +--- a/drivers/peci/peci-aspeed.c ++++ b/drivers/peci/peci-aspeed.c +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0 + // Copyright (C) 2012-2017 ASPEED Technology Inc. +-// Copyright (c) 2018 Intel Corporation ++// Copyright (c) 2018-2019 Intel Corporation + + #include + #include +diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c +index fac8c72dcda8..7ae05ded94bf 100644 +--- a/drivers/peci/peci-core.c ++++ b/drivers/peci/peci-core.c +@@ -1,5 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0 +-// Copyright (c) 2018 Intel Corporation ++// Copyright (c) 2018-2019 Intel Corporation + + #include + #include +@@ -666,9 +666,9 @@ peci_of_match_device(const struct of_device_id *matches, + return NULL; + + return of_match_device(matches, &client->dev); +-#else ++#else /* CONFIG_OF */ + return NULL; +-#endif ++#endif /* CONFIG_OF */ + } + + static const struct peci_device_id * +@@ -1119,7 +1119,7 @@ static void peci_of_register_devices(struct peci_adapter *adapter) + + of_node_put(bus); + } +-#else ++#else /* CONFIG_OF */ + static void peci_of_register_devices(struct peci_adapter *adapter) { } + #endif /* CONFIG_OF */ + +@@ -1216,7 +1216,7 @@ static int peci_of_notify(struct notifier_block *nb, + static struct notifier_block peci_of_notifier = { + .notifier_call = peci_of_notify, + }; +-#else ++#else /* CONFIG_OF_DYNAMIC */ + extern struct notifier_block peci_of_notifier; + #endif /* CONFIG_OF_DYNAMIC */ + +diff --git a/include/linux/mfd/intel-peci-client.h b/include/linux/mfd/intel-peci-client.h +index 8f6d823a59cd..dd5eb36cca75 100644 +--- a/include/linux/mfd/intel-peci-client.h ++++ b/include/linux/mfd/intel-peci-client.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0 */ +-/* Copyright (c) 2018 Intel Corporation */ ++/* Copyright (c) 2018-2019 Intel Corporation */ + + #ifndef __LINUX_MFD_INTEL_PECI_CLIENT_H + #define __LINUX_MFD_INTEL_PECI_CLIENT_H +@@ -58,7 +58,6 @@ struct cpu_gen_info { + /** + * struct peci_client_manager - PECI client manager information + * @client; pointer to the PECI client +- * @dev: pointer to the struct device + * @name: PECI client manager name + * @gen_info: CPU generation info of the detected CPU + * +@@ -67,7 +66,6 @@ struct cpu_gen_info { + */ + struct peci_client_manager { + struct peci_client *client; +- struct device *dev; + char name[PECI_NAME_SIZE]; + const struct cpu_gen_info *gen_info; + }; +diff --git a/include/linux/peci.h b/include/linux/peci.h +index d0e47d45d1d0..4b8be939585c 100644 +--- a/include/linux/peci.h ++++ b/include/linux/peci.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0 */ +-/* Copyright (c) 2018 Intel Corporation */ ++/* Copyright (c) 2018-2019 Intel Corporation */ + + #ifndef __LINUX_PECI_H + #define __LINUX_PECI_H +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0019-Add-I2C-IPMB-support.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0019-Add-I2C-IPMB-support.patch new file mode 100644 index 000000000..391d6f816 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0019-Add-I2C-IPMB-support.patch @@ -0,0 +1,426 @@ +From 59e2471d9bf64fa7d539520ef66cf5f33c0b0e55 Mon Sep 17 00:00:00 2001 +From: Haiyue Wang +Date: Tue, 13 Feb 2018 14:28:12 +0800 +Subject: [PATCH] i2c: slave-mqueue: add mqueue driver to receive ipmi message + +Some protocols over I2C are designed for bi-directional transferring +messages by using I2C Master Write protocol. Like the MCTP (Management +Component Transport Protocol) and IPMB (Intelligent Platform Management +Bus), they both require that the userspace can receive messages from +I2C dirvers under slave mode. + +This new slave mqueue backend is used to receive and queue messages, it +will exposes these messages to userspace by sysfs bin file. + +Signed-off-by: Haiyue Wang +--- + Documentation/i2c/slave-mqueue-backend.rst | 125 +++++++++++++++++ + drivers/i2c/Kconfig | 23 +++ + drivers/i2c/Makefile | 1 + + drivers/i2c/i2c-slave-mqueue.c | 217 +++++++++++++++++++++++++++++ + 4 files changed, 366 insertions(+) + create mode 100644 Documentation/i2c/slave-mqueue-backend.rst + create mode 100644 drivers/i2c/i2c-slave-mqueue.c + +diff --git a/Documentation/i2c/slave-mqueue-backend.rst b/Documentation/i2c/slave-mqueue-backend.rst +new file mode 100644 +index 000000000000..3966cf0ab8da +--- /dev/null ++++ b/Documentation/i2c/slave-mqueue-backend.rst +@@ -0,0 +1,125 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++===================================== ++Linux I2C slave message queue backend ++===================================== ++ ++:Author: Haiyue Wang ++ ++Some protocols over I2C/SMBus are designed for bi-directional transferring ++messages by using I2C Master Write protocol. This requires that both sides ++of the communication have slave addresses. ++ ++Like MCTP (Management Component Transport Protocol) and IPMB (Intelligent ++Platform Management Bus), they both require that the userspace can receive ++messages from i2c dirvers under slave mode. ++ ++This I2C slave mqueue (message queue) backend is used to receive and queue ++messages from the remote i2c intelligent device; and it will add the target ++slave address (with R/W# bit is always 0) into the message at the first byte, ++so that userspace can use this byte to dispatch the messages into different ++handling modules. Also, like IPMB, the address byte is in its message format, ++it needs it to do checksum. ++ ++For messages are time related, so this backend will flush the oldest message ++to queue the newest one. ++ ++Link ++---- ++`Intelligent Platform Management Bus ++Communications Protocol Specification ++`_ ++ ++`Management Component Transport Protocol (MCTP) ++SMBus/I2C Transport Binding Specification ++`_ ++ ++How to use ++---------- ++For example, the I2C5 bus has slave address 0x10, the below command will create ++the related message queue interface: ++ ++ echo slave-mqueue 0x1010 > /sys/bus/i2c/devices/i2c-5/new_device ++ ++Then you can dump the messages like this: ++ ++ hexdump -C /sys/bus/i2c/devices/5-1010/slave-mqueue ++ ++Code Example ++------------ ++*Note: call 'lseek' before 'read', this is a requirement from kernfs' design.* ++ ++:: ++ ++ #include ++ #include ++ #include ++ #include ++ #include ++ #include ++ #include ++ ++ int main(int argc, char *argv[]) ++ { ++ int i, r; ++ struct pollfd pfd; ++ struct timespec ts; ++ unsigned char data[256]; ++ ++ pfd.fd = open(argv[1], O_RDONLY | O_NONBLOCK); ++ if (pfd.fd < 0) ++ return -1; ++ ++ pfd.events = POLLPRI; ++ ++ while (1) { ++ r = poll(&pfd, 1, 5000); ++ ++ if (r < 0) ++ break; ++ ++ if (r == 0 || !(pfd.revents & POLLPRI)) ++ continue; ++ ++ lseek(pfd.fd, 0, SEEK_SET); ++ r = read(pfd.fd, data, sizeof(data)); ++ if (r <= 0) ++ continue; ++ ++ clock_gettime(CLOCK_MONOTONIC, &ts); ++ printf("[%ld.%.9ld] :", ts.tv_sec, ts.tv_nsec); ++ for (i = 0; i < r; i++) ++ printf(" %02x", data[i]); ++ printf("\n"); ++ } ++ ++ close(pfd.fd); ++ ++ return 0; ++ } ++ ++Result ++------ ++*./a.out "/sys/bus/i2c/devices/5-1010/slave-mqueue"* ++ ++:: ++ ++ [10183.232500449] : 20 18 c8 2c 78 01 5b ++ [10183.479358348] : 20 18 c8 2c 78 01 5b ++ [10183.726556812] : 20 18 c8 2c 78 01 5b ++ [10183.972605863] : 20 18 c8 2c 78 01 5b ++ [10184.220124772] : 20 18 c8 2c 78 01 5b ++ [10184.467764166] : 20 18 c8 2c 78 01 5b ++ [10193.233421784] : 20 18 c8 2c 7c 01 57 ++ [10193.480273460] : 20 18 c8 2c 7c 01 57 ++ [10193.726788733] : 20 18 c8 2c 7c 01 57 ++ [10193.972781945] : 20 18 c8 2c 7c 01 57 ++ [10194.220487360] : 20 18 c8 2c 7c 01 57 ++ [10194.468089259] : 20 18 c8 2c 7c 01 57 ++ [10203.233433099] : 20 18 c8 2c 80 01 53 ++ [10203.481058715] : 20 18 c8 2c 80 01 53 ++ [10203.727610472] : 20 18 c8 2c 80 01 53 ++ [10203.974044856] : 20 18 c8 2c 80 01 53 ++ [10204.220734634] : 20 18 c8 2c 80 01 53 ++ [10204.468461664] : 20 18 c8 2c 80 01 53 ++ +diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig +index efc3354d60ae..04fb851f2c82 100644 +--- a/drivers/i2c/Kconfig ++++ b/drivers/i2c/Kconfig +@@ -118,6 +118,29 @@ if I2C_SLAVE + config I2C_SLAVE_EEPROM + tristate "I2C eeprom slave driver" + ++config I2C_SLAVE_MQUEUE_MESSAGE_SIZE ++ int "The message size of I2C mqueue slave" ++ default 120 ++ ++config I2C_SLAVE_MQUEUE_QUEUE_SIZE ++ int "The queue size of I2C mqueue slave" ++ default 32 ++ help ++ This number MUST be power of 2. ++ ++config I2C_SLAVE_MQUEUE ++ tristate "I2C mqueue (message queue) slave driver" ++ help ++ Some protocols over I2C are designed for bi-directional transferring ++ messages by using I2C Master Write protocol. This driver is used to ++ receive and queue messages from the remote I2C device. ++ ++ Userspace can get the messages by reading sysfs file that this driver ++ exposes. ++ ++ This support is also available as a module. If so, the module will be ++ called i2c-slave-mqueue. ++ + endif + + config I2C_DEBUG_CORE +diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile +index bed6ba63c983..9a31bc75a446 100644 +--- a/drivers/i2c/Makefile ++++ b/drivers/i2c/Makefile +@@ -16,5 +16,6 @@ obj-$(CONFIG_I2C_MUX) += i2c-mux.o + obj-y += algos/ busses/ muxes/ + obj-$(CONFIG_I2C_STUB) += i2c-stub.o + obj-$(CONFIG_I2C_SLAVE_EEPROM) += i2c-slave-eeprom.o ++obj-$(CONFIG_I2C_SLAVE_MQUEUE) += i2c-slave-mqueue.o + + ccflags-$(CONFIG_I2C_DEBUG_CORE) := -DDEBUG +diff --git a/drivers/i2c/i2c-slave-mqueue.c b/drivers/i2c/i2c-slave-mqueue.c +new file mode 100644 +index 000000000000..6014bca0ff2a +--- /dev/null ++++ b/drivers/i2c/i2c-slave-mqueue.c +@@ -0,0 +1,217 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2017 - 2018, Intel Corporation. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MQ_MSGBUF_SIZE CONFIG_I2C_SLAVE_MQUEUE_MESSAGE_SIZE ++#define MQ_QUEUE_SIZE CONFIG_I2C_SLAVE_MQUEUE_QUEUE_SIZE ++#define MQ_QUEUE_NEXT(x) (((x) + 1) & (MQ_QUEUE_SIZE - 1)) ++ ++struct mq_msg { ++ int len; ++ u8 *buf; ++}; ++ ++struct mq_queue { ++ struct bin_attribute bin; ++ struct kernfs_node *kn; ++ ++ spinlock_t lock; /* spinlock for queue index handling */ ++ int in; ++ int out; ++ ++ struct mq_msg *curr; ++ int truncated; /* drop current if truncated */ ++ struct mq_msg *queue; ++}; ++ ++static int i2c_slave_mqueue_callback(struct i2c_client *client, ++ enum i2c_slave_event event, u8 *val) ++{ ++ struct mq_queue *mq = i2c_get_clientdata(client); ++ struct mq_msg *msg = mq->curr; ++ int ret = 0; ++ ++ switch (event) { ++ case I2C_SLAVE_WRITE_REQUESTED: ++ mq->truncated = 0; ++ ++ msg->len = 1; ++ msg->buf[0] = client->addr << 1; ++ break; ++ ++ case I2C_SLAVE_WRITE_RECEIVED: ++ if (msg->len < MQ_MSGBUF_SIZE) { ++ msg->buf[msg->len++] = *val; ++ } else { ++ dev_err(&client->dev, "message is truncated!\n"); ++ mq->truncated = 1; ++ ret = -EINVAL; ++ } ++ break; ++ ++ case I2C_SLAVE_STOP: ++ if (unlikely(mq->truncated || msg->len < 2)) ++ break; ++ ++ spin_lock(&mq->lock); ++ mq->in = MQ_QUEUE_NEXT(mq->in); ++ mq->curr = &mq->queue[mq->in]; ++ mq->curr->len = 0; ++ ++ /* Flush the oldest message */ ++ if (mq->out == mq->in) ++ mq->out = MQ_QUEUE_NEXT(mq->out); ++ spin_unlock(&mq->lock); ++ ++ kernfs_notify(mq->kn); ++ break; ++ ++ default: ++ *val = 0xFF; ++ break; ++ } ++ ++ return ret; ++} ++ ++static ssize_t i2c_slave_mqueue_bin_read(struct file *filp, ++ struct kobject *kobj, ++ struct bin_attribute *attr, ++ char *buf, loff_t pos, size_t count) ++{ ++ struct mq_queue *mq; ++ struct mq_msg *msg; ++ unsigned long flags; ++ bool more = false; ++ ssize_t ret = 0; ++ ++ mq = dev_get_drvdata(container_of(kobj, struct device, kobj)); ++ ++ spin_lock_irqsave(&mq->lock, flags); ++ if (mq->out != mq->in) { ++ msg = &mq->queue[mq->out]; ++ ++ if (msg->len <= count) { ++ ret = msg->len; ++ memcpy(buf, msg->buf, ret); ++ } else { ++ ret = -EOVERFLOW; /* Drop this HUGE one. */ ++ } ++ ++ mq->out = MQ_QUEUE_NEXT(mq->out); ++ if (mq->out != mq->in) ++ more = true; ++ } ++ spin_unlock_irqrestore(&mq->lock, flags); ++ ++ if (more) ++ kernfs_notify(mq->kn); ++ ++ return ret; ++} ++ ++static int i2c_slave_mqueue_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct device *dev = &client->dev; ++ struct mq_queue *mq; ++ int ret, i; ++ void *buf; ++ ++ mq = devm_kzalloc(dev, sizeof(*mq), GFP_KERNEL); ++ if (!mq) ++ return -ENOMEM; ++ ++ BUILD_BUG_ON(!is_power_of_2(MQ_QUEUE_SIZE)); ++ ++ buf = devm_kmalloc_array(dev, MQ_QUEUE_SIZE, MQ_MSGBUF_SIZE, ++ GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ mq->queue = devm_kzalloc(dev, sizeof(*mq->queue) * MQ_QUEUE_SIZE, ++ GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ for (i = 0; i < MQ_QUEUE_SIZE; i++) ++ mq->queue[i].buf = buf + i * MQ_MSGBUF_SIZE; ++ ++ i2c_set_clientdata(client, mq); ++ ++ spin_lock_init(&mq->lock); ++ mq->curr = &mq->queue[0]; ++ ++ sysfs_bin_attr_init(&mq->bin); ++ mq->bin.attr.name = "slave-mqueue"; ++ mq->bin.attr.mode = 0400; ++ mq->bin.read = i2c_slave_mqueue_bin_read; ++ mq->bin.size = MQ_MSGBUF_SIZE * MQ_QUEUE_SIZE; ++ ++ ret = sysfs_create_bin_file(&dev->kobj, &mq->bin); ++ if (ret) ++ return ret; ++ ++ mq->kn = kernfs_find_and_get(dev->kobj.sd, mq->bin.attr.name); ++ if (!mq->kn) { ++ sysfs_remove_bin_file(&dev->kobj, &mq->bin); ++ return -EFAULT; ++ } ++ ++ ret = i2c_slave_register(client, i2c_slave_mqueue_callback); ++ if (ret) { ++ kernfs_put(mq->kn); ++ sysfs_remove_bin_file(&dev->kobj, &mq->bin); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int i2c_slave_mqueue_remove(struct i2c_client *client) ++{ ++ struct mq_queue *mq = i2c_get_clientdata(client); ++ ++ i2c_slave_unregister(client); ++ ++ kernfs_put(mq->kn); ++ sysfs_remove_bin_file(&client->dev.kobj, &mq->bin); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id i2c_slave_mqueue_id[] = { ++ { "slave-mqueue", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, i2c_slave_mqueue_id); ++ ++#if IS_ENABLED(CONFIG_OF) ++static const struct of_device_id i2c_slave_mqueue_of_match[] = { ++ { .compatible = "slave-mqueue", .data = (void *)0 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, i2c_slave_mqueue_of_match); ++#endif ++ ++static struct i2c_driver i2c_slave_mqueue_driver = { ++ .driver = { ++ .name = "i2c-slave-mqueue", ++ .of_match_table = of_match_ptr(i2c_slave_mqueue_of_match), ++ }, ++ .probe = i2c_slave_mqueue_probe, ++ .remove = i2c_slave_mqueue_remove, ++ .id_table = i2c_slave_mqueue_id, ++}; ++module_i2c_driver(i2c_slave_mqueue_driver); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Haiyue Wang "); ++MODULE_DESCRIPTION("I2C slave mode for receiving and queuing messages"); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch new file mode 100644 index 000000000..fe50c0aea --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch @@ -0,0 +1,565 @@ +From 4084484a57d9a81b6581455ff144fc4f9c603075 Mon Sep 17 00:00:00 2001 +From: Yong Li +Date: Mon, 13 Nov 2017 16:29:44 +0800 +Subject: [PATCH] Aspeed LPC SIO driver + +Add lpc sio device driver for AST2500/2400 + +Signed-off-by: Yong Li +--- + .../devicetree/bindings/misc/aspeed-sio.txt | 14 + + drivers/misc/Kconfig | 9 + + drivers/misc/Makefile | 1 + + drivers/misc/aspeed-lpc-sio.c | 435 +++++++++++++++++++++ + include/uapi/linux/aspeed-lpc-sio.h | 44 +++ + 5 files changed, 503 insertions(+) + create mode 100644 Documentation/devicetree/bindings/misc/aspeed-sio.txt + create mode 100644 drivers/misc/aspeed-lpc-sio.c + create mode 100644 include/uapi/linux/aspeed-lpc-sio.h + +diff --git a/Documentation/devicetree/bindings/misc/aspeed-sio.txt b/Documentation/devicetree/bindings/misc/aspeed-sio.txt +new file mode 100644 +index 000000000000..7953cd3367df +--- /dev/null ++++ b/Documentation/devicetree/bindings/misc/aspeed-sio.txt +@@ -0,0 +1,14 @@ ++* Aspeed LPC SIO driver. ++ ++Required properties: ++- compatible: "aspeed,ast2500-lpc-sio" ++ - aspeed,ast2500-lpc-sio: Aspeed AST2500 family ++- reg: Should contain lpc-sio registers location and length ++ ++Example: ++lpc_sio: lpc-sio@100 { ++ compatible = "aspeed,ast2500-lpc-sio"; ++ reg = <0x100 0x20>; ++ status = "disabled"; ++}; ++ +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index 689d07ea7ded..fe1e2a4072a8 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -493,6 +493,15 @@ config ASPEED_LPC_CTRL + ioctl()s, the driver also provides a read/write interface to a BMC ram + region where the host LPC read/write region can be buffered. + ++config ASPEED_LPC_SIO ++ depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON ++ tristate "Aspeed ast2400/2500 HOST LPC SIO support" ++ help ++ Provides a driver to control the LPC SIO interface ++ on ASPEED platform ++ through ++ ioctl()s. ++ + config ASPEED_LPC_SNOOP + tristate "Aspeed ast2500 HOST LPC snoop support" + depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index e4170f62ab98..a2b85ec21d09 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -56,6 +56,7 @@ obj-$(CONFIG_CXL_BASE) += cxl/ + obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o + obj-$(CONFIG_ASPEED_LPC_MBOX) += aspeed-lpc-mbox.o ++obj-$(CONFIG_ASPEED_LPC_SIO) += aspeed-lpc-sio.o + obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o + obj-$(CONFIG_OCXL) += ocxl/ + obj-$(CONFIG_MISC_RTSX) += cardreader/ +diff --git a/drivers/misc/aspeed-lpc-sio.c b/drivers/misc/aspeed-lpc-sio.c +new file mode 100644 +index 000000000000..fd9a83bd66d7 +--- /dev/null ++++ b/drivers/misc/aspeed-lpc-sio.c +@@ -0,0 +1,435 @@ ++/* ++ * Copyright (C) 2012-2020 ASPEED Technology Inc. ++ * Copyright (c) 2017 Intel Corporation ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define SOC_NAME "aspeed" ++#define DEVICE_NAME "lpc-sio" ++ ++#define AST_LPC_SWCR0300 0x0 ++#define LPC_PWRGD_STS (1 << 30) ++#define LPC_PWRGD_RISING_EVT_STS (1 << 29) ++#define LPC_PWRGD_FALLING_EVT_STS (1 << 28) ++#define LPC_PWRBTN_STS (1 << 27) ++#define LPC_PWRBTN_RISING_EVT_STS (1 << 26) ++#define LPC_PWRBTN_FALLING_EVT_STS (1 << 25) ++#define LPC_S5N_STS (1 << 21) ++#define LPC_S5N_RISING_EVT_STS (1 << 20) ++#define LPC_S5N_FALLING_EVT_STS (1 << 19) ++#define LPC_S3N_STS (1 << 18) ++#define LPC_S3N_RISING_EVT_STS (1 << 17) ++#define LPC_S3N_FALLING_EVT_STS (1 << 16) ++#define LPC_PWBTO_RAW_STS (1 << 15) ++#define LPC_LAST_ONCTL_STS (1 << 14) ++#define LPC_WAS_PFAIL_STS (1 << 13) ++#define LPC_POWER_UP_FAIL_STS (1 << 12) /* Crowbar */ ++#define LPC_PWRBTN_OVERRIDE_STS (1 << 11) ++ ++#define AST_LPC_SWCR0704 0x4 ++ ++#define AST_LPC_SWCR0B08 0x8 ++#define LPC_PWREQ_OUTPUT_LEVEL (1 << 25) ++#define LPC_PWBTO_OUTPUT_LEVEL (1 << 24) ++#define LPC_ONCTL_STS (1 << 15) ++#define LPC_ONCTL_GPIO_LEVEL (1 << 14) ++#define LPC_ONCTL_EN_GPIO_OUTPUT (1 << 13) ++#define LPC_ONCTL_EN_GPIO_MODE (1 << 12) ++ ++#define AST_LPC_SWCR0F0C 0xC ++#define AST_LPC_SWCR1310 0x10 ++#define AST_LPC_SWCR1714 0x14 ++#define AST_LPC_SWCR1B18 0x18 ++#define AST_LPC_SWCR1F1C 0x1C ++#define AST_LPC_ACPIE3E0 0x20 ++#define AST_LPC_ACPIC1C0 0x24 ++#define AST_LPC_ACPIB3B0 0x28 ++#define AST_LPC_ACPIB7B4 0x2C ++ ++struct aspeed_lpc_sio { ++ struct miscdevice miscdev; ++ struct regmap *regmap; ++ struct semaphore lock; ++ unsigned int reg_base; ++}; ++ ++static struct aspeed_lpc_sio *file_aspeed_lpc_sio(struct file *file) ++{ ++ return container_of(file->private_data, struct aspeed_lpc_sio, ++ miscdev); ++} ++ ++static int aspeed_lpc_sio_open(struct inode *inode, struct file *filp) ++{ ++ return 0; ++} ++ ++#define LPC_SLP3N5N_EVENT_STATUS (\ ++ LPC_S5N_RISING_EVT_STS | \ ++ LPC_S5N_FALLING_EVT_STS | \ ++ LPC_S3N_RISING_EVT_STS | \ ++ LPC_S3N_FALLING_EVT_STS) ++/************************************* ++ * SLPS3n SLPS5n State ++ * --------------------------------- ++ * 1 1 S12 ++ * 0 1 S3I ++ * x 0 S45 ++ ************************************* ++ */ ++ ++static long sio_get_acpi_state(struct aspeed_lpc_sio *lpc_sio, ++ struct sio_ioctl_data *sio_data) ++{ ++ u32 reg; ++ u32 val; ++ int rc; ++ ++ reg = lpc_sio->reg_base + AST_LPC_SWCR0300; ++ rc = regmap_read(lpc_sio->regmap, reg, &val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_read() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ ++ /* update the ACPI state event status */ ++ if (sio_data->param != 0) { ++ if (val & LPC_SLP3N5N_EVENT_STATUS) { ++ sio_data->param = 1; ++ rc = regmap_write(lpc_sio->regmap, reg, ++ LPC_SLP3N5N_EVENT_STATUS); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_write() failed with %d(reg:0x%x)\n", ++ rc, reg); ++ return rc; ++ } ++ } else { ++ sio_data->param = 0; ++ } ++ } ++ ++ if ((val & LPC_S3N_STS) && (val & LPC_S5N_STS)) ++ sio_data->data = ACPI_STATE_S12; ++ else if ((val & LPC_S3N_STS) == 0 && (val & LPC_S5N_STS)) ++ sio_data->data = ACPI_STATE_S3I; ++ else ++ sio_data->data = ACPI_STATE_S45; ++ ++ return 0; ++} ++ ++#define LPC_PWRGD_EVENT_STATUS ( \ ++ LPC_PWRGD_RISING_EVT_STS | \ ++ LPC_PWRGD_FALLING_EVT_STS) ++ ++static long sio_get_pwrgd_status(struct aspeed_lpc_sio *lpc_sio, ++ struct sio_ioctl_data *sio_data) ++{ ++ u32 reg; ++ u32 val; ++ int rc; ++ ++ reg = lpc_sio->reg_base + AST_LPC_SWCR0300; ++ rc = regmap_read(lpc_sio->regmap, reg, &val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_read() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ ++ /* update the PWRGD event status */ ++ if (sio_data->param != 0) { ++ if (val & LPC_PWRGD_EVENT_STATUS) { ++ sio_data->param = 1; ++ rc = regmap_write(lpc_sio->regmap, reg, ++ LPC_PWRGD_EVENT_STATUS); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_write() failed with %d(reg:0x%x)\n", ++ rc, reg); ++ return rc; ++ } ++ } else { ++ sio_data->param = 0; ++ } ++ } ++ ++ sio_data->data = (val & LPC_PWRGD_STS) != 0 ? 1 : 0; ++ ++ return 0; ++} ++ ++static long sio_get_onctl_status(struct aspeed_lpc_sio *lpc_sio, ++ struct sio_ioctl_data *sio_data) ++{ ++ u32 reg; ++ u32 val; ++ int rc; ++ ++ reg = lpc_sio->reg_base + AST_LPC_SWCR0B08; ++ rc = regmap_read(lpc_sio->regmap, reg, &val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_read() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ ++ sio_data->data = (val & LPC_ONCTL_STS) != 0 ? 1 : 0; ++ ++ return 0; ++} ++ ++static long sio_set_onctl_gpio(struct aspeed_lpc_sio *lpc_sio, ++ struct sio_ioctl_data *sio_data) ++{ ++ u32 reg; ++ u32 val; ++ int rc; ++ ++ reg = lpc_sio->reg_base + AST_LPC_SWCR0B08; ++ rc = regmap_read(lpc_sio->regmap, reg, &val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_read() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ ++ /* Enable ONCTL GPIO mode */ ++ if (sio_data->param != 0) { ++ val |= LPC_ONCTL_EN_GPIO_MODE; ++ val |= LPC_ONCTL_EN_GPIO_OUTPUT; ++ ++ if (sio_data->data != 0) ++ val |= LPC_ONCTL_GPIO_LEVEL; ++ else ++ val &= ~LPC_ONCTL_GPIO_LEVEL; ++ ++ rc = regmap_write(lpc_sio->regmap, reg, val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_write() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ } else { ++ val &= ~LPC_ONCTL_EN_GPIO_MODE; ++ rc = regmap_write(lpc_sio->regmap, reg, val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_write() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ } ++ ++ return 0; ++} ++ ++static long sio_get_pwrbtn_override(struct aspeed_lpc_sio *lpc_sio, ++ struct sio_ioctl_data *sio_data) ++{ ++ u32 reg; ++ u32 val; ++ int rc; ++ ++ reg = lpc_sio->reg_base + AST_LPC_SWCR0300; ++ rc = regmap_read(lpc_sio->regmap, reg, &val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_read() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ ++ /* clear the PWRBTN OVERRIDE status */ ++ if (sio_data->param != 0) { ++ if (val & LPC_PWRBTN_OVERRIDE_STS) { ++ rc = regmap_write(lpc_sio->regmap, reg, ++ LPC_PWRBTN_OVERRIDE_STS); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_write() failed with %d(reg:0x%x)\n", ++ rc, reg); ++ return rc; ++ } ++ } ++ } ++ ++ sio_data->data = (val & LPC_PWRBTN_OVERRIDE_STS) != 0 ? 1 : 0; ++ ++ return 0; ++} ++ ++static long sio_get_pfail_status(struct aspeed_lpc_sio *lpc_sio, ++ struct sio_ioctl_data *sio_data) ++{ ++ u32 reg; ++ u32 val; ++ int rc; ++ ++ reg = lpc_sio->reg_base + AST_LPC_SWCR0300; ++ rc = regmap_read(lpc_sio->regmap, reg, &val); ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_read() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ ++ /* [ASPEED]: SWCR_03_00[13] (Was_pfail: default 1) is used to identify ++ * this current booting is from AC loss (not DC loss) if FW cleans this ++ * bit after booting successfully every time. ++ **********************************************************************/ ++ if (val & LPC_WAS_PFAIL_STS) { ++ rc = regmap_write(lpc_sio->regmap, reg, 0); /* W0C */ ++ if (rc) { ++ dev_err(lpc_sio->miscdev.parent, ++ "regmap_write() failed with %d(reg:0x%x)\n", rc, reg); ++ return rc; ++ } ++ sio_data->data = 1; ++ } else { ++ sio_data->data = 0; ++ } ++ ++ return 0; ++} ++ ++typedef long (*sio_cmd_fn) (struct aspeed_lpc_sio *sio_dev, ++ struct sio_ioctl_data *sio_data); ++static sio_cmd_fn sio_cmd_handle[SIO_MAX_CMD] = { ++ [SIO_GET_ACPI_STATE] = sio_get_acpi_state, ++ [SIO_GET_PWRGD_STATUS] = sio_get_pwrgd_status, ++ [SIO_GET_ONCTL_STATUS] = sio_get_onctl_status, ++ [SIO_SET_ONCTL_GPIO] = sio_set_onctl_gpio, ++ [SIO_GET_PWRBTN_OVERRIDE] = sio_get_pwrbtn_override, ++ [SIO_GET_PFAIL_STATUS] = sio_get_pfail_status, ++}; ++ ++static long aspeed_lpc_sio_ioctl(struct file *file, unsigned int cmd, ++ unsigned long param) ++{ ++ struct aspeed_lpc_sio *lpc_sio = file_aspeed_lpc_sio(file); ++ long ret; ++ sio_cmd_fn cmd_fn; ++ struct sio_ioctl_data sio_data; ++ ++ ++ if (copy_from_user(&sio_data, (void __user *)param, sizeof(sio_data))) ++ return -EFAULT; ++ ++ if (cmd != SIO_IOC_COMMAND || sio_data.sio_cmd >= SIO_MAX_CMD) ++ return -EINVAL; ++ ++ cmd_fn = sio_cmd_handle[sio_data.sio_cmd]; ++ if (cmd_fn == NULL) ++ return -EINVAL; ++ ++ if (down_interruptible(&lpc_sio->lock) != 0) ++ return -ERESTARTSYS; ++ ++ ret = cmd_fn(lpc_sio, &sio_data); ++ if (ret == 0) { ++ if (copy_to_user((void __user *)param, &sio_data, ++ sizeof(sio_data))) ++ ret = -EFAULT; ++ } ++ ++ up(&lpc_sio->lock); ++ ++ return ret; ++} ++ ++static const struct file_operations aspeed_lpc_sio_fops = { ++ .owner = THIS_MODULE, ++ .open = aspeed_lpc_sio_open, ++ .unlocked_ioctl = aspeed_lpc_sio_ioctl, ++}; ++ ++static int aspeed_lpc_sio_probe(struct platform_device *pdev) ++{ ++ struct aspeed_lpc_sio *lpc_sio; ++ struct device *dev; ++ int rc; ++ ++ dev = &pdev->dev; ++ ++ lpc_sio = devm_kzalloc(dev, sizeof(*lpc_sio), GFP_KERNEL); ++ if (!lpc_sio) ++ return -ENOMEM; ++ ++ dev_set_drvdata(&pdev->dev, lpc_sio); ++ ++ rc = of_property_read_u32(dev->of_node, "reg", &lpc_sio->reg_base); ++ if (rc) { ++ dev_err(dev, "Couldn't read reg device-tree property\n"); ++ return rc; ++ } ++ ++ lpc_sio->regmap = syscon_node_to_regmap( ++ pdev->dev.parent->of_node); ++ if (IS_ERR(lpc_sio->regmap)) { ++ dev_err(dev, "Couldn't get regmap\n"); ++ return -ENODEV; ++ } ++ ++ sema_init(&lpc_sio->lock, 1); ++ ++ lpc_sio->miscdev.minor = MISC_DYNAMIC_MINOR; ++ lpc_sio->miscdev.name = DEVICE_NAME; ++ lpc_sio->miscdev.fops = &aspeed_lpc_sio_fops; ++ lpc_sio->miscdev.parent = dev; ++ rc = misc_register(&lpc_sio->miscdev); ++ if (rc) ++ dev_err(dev, "Unable to register device\n"); ++ else ++ dev_info(dev, "Loaded at %pap (0x%08x)\n", ++ &lpc_sio->regmap, lpc_sio->reg_base); ++ ++ return rc; ++} ++ ++static int aspeed_lpc_sio_remove(struct platform_device *pdev) ++{ ++ struct aspeed_lpc_sio *lpc_sio = dev_get_drvdata(&pdev->dev); ++ ++ misc_deregister(&lpc_sio->miscdev); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_lpc_sio_match[] = { ++ { .compatible = "aspeed,ast2500-lpc-sio" }, ++ { }, ++}; ++ ++static struct platform_driver aspeed_lpc_sio_driver = { ++ .driver = { ++ .name = SOC_NAME "-" DEVICE_NAME, ++ .of_match_table = aspeed_lpc_sio_match, ++ }, ++ .probe = aspeed_lpc_sio_probe, ++ .remove = aspeed_lpc_sio_remove, ++}; ++ ++module_platform_driver(aspeed_lpc_sio_driver); ++ ++MODULE_DEVICE_TABLE(of, aspeed_lpc_sio_match); ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_AUTHOR("Yong Li "); ++MODULE_DESCRIPTION("ASPEED AST LPC SIO device driver"); +diff --git a/include/uapi/linux/aspeed-lpc-sio.h b/include/uapi/linux/aspeed-lpc-sio.h +new file mode 100644 +index 000000000000..5dc1efd4a426 +--- /dev/null ++++ b/include/uapi/linux/aspeed-lpc-sio.h +@@ -0,0 +1,44 @@ ++/* ++ * Copyright (C) 2012-2020 ASPEED Technology Inc. ++ * Copyright (c) 2017 Intel Corporation ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version ++ * 2 of the License, or (at your option) any later version. ++ * ++ */ ++ ++#ifndef _UAPI_LINUX_ASPEED_LPC_SIO_H ++#define _UAPI_LINUX_ASPEED_LPC_SIO_H ++ ++#include ++ ++enum ACPI_SLP_STATE { ++ ACPI_STATE_S12 = 1, ++ ACPI_STATE_S3I, ++ ACPI_STATE_S45 ++}; ++ ++/* SWC & ACPI for SuperIO IOCTL */ ++enum SIO_CMD { ++ SIO_GET_ACPI_STATE = 0, ++ SIO_GET_PWRGD_STATUS, ++ SIO_GET_ONCTL_STATUS, ++ SIO_SET_ONCTL_GPIO, ++ SIO_GET_PWRBTN_OVERRIDE, ++ SIO_GET_PFAIL_STATUS, /* Start from AC Loss */ ++ ++ SIO_MAX_CMD ++}; ++ ++struct sio_ioctl_data { ++ unsigned short sio_cmd; ++ unsigned short param; ++ unsigned int data; ++}; ++ ++#define SIO_IOC_BASE 'P' ++#define SIO_IOC_COMMAND _IOWR(SIO_IOC_BASE, 1, struct sio_ioctl_data) ++ ++#endif /* _UAPI_LINUX_ASPEED_LPC_SIO_H */ +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0022-Add-AST2500-eSPI-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0022-Add-AST2500-eSPI-driver.patch new file mode 100644 index 000000000..120adbbc8 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0022-Add-AST2500-eSPI-driver.patch @@ -0,0 +1,597 @@ +From a01815b4bb983ede71993d6c761dedd22d148b6b Mon Sep 17 00:00:00 2001 +From: Haiyue Wang +Date: Sat, 24 Feb 2018 11:12:32 +0800 +Subject: [PATCH] eSPI: add ASPEED AST2500 eSPI driver to boot a host with PCH + runs on eSPI + +When PCH works under eSPI mode, the PMC (Power Management Controller) in +PCH is waiting for SUS_ACK from BMC after it alerts SUS_WARN. It is in +dead loop if no SUS_ACK assert. This is the basic requirement for the BMC +works as eSPI slave. + +Also for the host power on / off actions, from BMC side, the following VW +(Virtual Wire) messages are done in firmware: +1. SLAVE_BOOT_LOAD_DONE / SLAVE_BOOT_LOAD_STATUS +2. SUS_ACK +3. OOB_RESET_ACK +4. HOST_RESET_ACK + +Signed-off-by: Haiyue Wang +--- + .../devicetree/bindings/misc/aspeed,espi-slave.txt | 20 ++ + Documentation/misc-devices/espi-slave.rst | 119 +++++++ + arch/arm/boot/dts/aspeed-g5.dtsi | 4 + + drivers/misc/Kconfig | 8 + + drivers/misc/Makefile | 1 + + drivers/misc/aspeed-espi-slave.c | 353 +++++++++++++++++++++ + 6 files changed, 505 insertions(+) + create mode 100644 Documentation/devicetree/bindings/misc/aspeed,espi-slave.txt + create mode 100644 Documentation/misc-devices/espi-slave.rst + create mode 100644 drivers/misc/aspeed-espi-slave.c + +diff --git a/Documentation/devicetree/bindings/misc/aspeed,espi-slave.txt b/Documentation/devicetree/bindings/misc/aspeed,espi-slave.txt +new file mode 100644 +index 000000000000..4f5d47ecc882 +--- /dev/null ++++ b/Documentation/devicetree/bindings/misc/aspeed,espi-slave.txt +@@ -0,0 +1,20 @@ ++ASPEED eSPI Slave Controller ++ ++Required properties: ++ - compatible: must be one of: ++ - "aspeed,ast2500-espi-slave" ++ ++ - reg: physical base address of the controller and length of memory mapped ++ region ++ ++ - interrupts: interrupt generated by the controller ++ ++Example: ++ ++ espi: espi@1e6ee000 { ++ compatible = "aspeed,ast2500-espi-slave"; ++ reg = <0x1e6ee000 0x100>; ++ interrupts = <23>; ++ status = "disabled"; ++}; ++ +diff --git a/Documentation/misc-devices/espi-slave.rst b/Documentation/misc-devices/espi-slave.rst +new file mode 100644 +index 000000000000..185acd71bd26 +--- /dev/null ++++ b/Documentation/misc-devices/espi-slave.rst +@@ -0,0 +1,119 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++========== ++eSPI Slave ++========== ++ ++:Author: Haiyue Wang ++ ++The PCH (**eSPI master**) provides the eSPI to support connection of a ++BMC (**eSPI slave**) to the platform. ++ ++The LPC and eSPI interfaces are mutually exclusive. Both use the same ++pins, but on power-up, a HW strap determines if the eSPI or the LPC bus ++is operational. Once selected, it’s not possible to change to the other ++interface. ++ ++``eSPI Channels and Supported Transactions`` ++ +------+---------------------+----------------------+--------------------+ ++ | CH # | Channel | Posted Cycles | Non-Posted Cycles | ++ +======+=====================+======================+====================+ ++ | 0 | Peripheral | Memory Write, | Memory Read, | ++ | | | Completions | I/O Read/Write | ++ +------+---------------------+----------------------+--------------------+ ++ | 1 | Virtual Wire | Virtual Wire GET/PUT | N/A | ++ +------+---------------------+----------------------+--------------------+ ++ | 2 | Out-of-Band Message | SMBus Packet GET/PUT | N/A | ++ +------+---------------------+----------------------+--------------------+ ++ | 3 | Flash Access | N/A | Flash Read, Write, | ++ | | | | Erase | ++ +------+---------------------+----------------------+--------------------+ ++ | N/A | General | Register Accesses | N/A | ++ +------+---------------------+----------------------+--------------------+ ++ ++Virtual Wire Channel (Channel 1) Overview ++----------------------------------------- ++ ++The Virtual Wire channel uses a standard message format to communicate ++several types of signals between the components on the platform:: ++ ++ - Sideband and GPIO Pins: System events and other dedicated signals ++ between the PCH and eSPI slave. These signals are tunneled between the ++ two components over eSPI. ++ ++ - Serial IRQ Interrupts: Interrupts are tunneled from the eSPI slave to ++ the PCH. Both edge and triggered interrupts are supported. ++ ++When PCH runs on eSPI mode, from BMC side, the following VW messages are ++done in firmware:: ++ ++ 1. SLAVE_BOOT_LOAD_DONE / SLAVE_BOOT_LOAD_STATUS ++ 2. SUS_ACK ++ 3. OOB_RESET_ACK ++ 4. HOST_RESET_ACK ++ ++``eSPI Virtual Wires (VW)`` ++ +----------------------+---------+---------------------------------------+ ++ |Virtual Wire |PCH Pin |Comments | ++ | |Direction| | ++ +======================+=========+=======================================+ ++ |SUS_WARN# |Output |PCH pin is a GPIO when eSPI is enabled.| ++ | | |eSPI controller receives as VW message.| ++ +----------------------+---------+---------------------------------------+ ++ |SUS_ACK# |Input |PCH pin is a GPIO when eSPI is enabled.| ++ | | |eSPI controller receives as VW message.| ++ +----------------------+---------+---------------------------------------+ ++ |SLAVE_BOOT_LOAD_DONE |Input |Sent when the BMC has completed its | ++ | | |boot process as an indication to | ++ | | |eSPI-MC to continue with the G3 to S0 | ++ | | |exit. | ++ | | |The eSPI Master waits for the assertion| ++ | | |of this virtual wire before proceeding | ++ | | |with the SLP_S5# deassertion. | ++ | | |The intent is that it is never changed | ++ | | |except on a G3 exit - it is reset on a | ++ | | |G3 entry. | ++ +----------------------+---------+---------------------------------------+ ++ |SLAVE_BOOT_LOAD_STATUS|Input |Sent upon completion of the Slave Boot | ++ | | |Load from the attached flash. A stat of| ++ | | |1 indicates that the boot code load was| ++ | | |successful and that the integrity of | ++ | | |the image is intact. | ++ +----------------------+---------+---------------------------------------+ ++ |HOST_RESET_WARN |Output |Sent from the MC just before the Host | ++ | | |is about to enter reset. Upon receiving| ++ | | |, the BMC must flush and quiesce its | ++ | | |upstream Peripheral Channel request | ++ | | |queues and assert HOST_RESET_ACK VWire.| ++ | | |The MC subsequently completes any | ++ | | |outstanding posted transactions or | ++ | | |completions and then disables the | ++ | | |Peripheral Channel via a write to | ++ | | |the Slave's Configuration Register. | ++ +----------------------+---------+---------------------------------------+ ++ |HOST_RESET_ACK |Input |ACK for the HOST_RESET_WARN message | ++ +----------------------+---------+---------------------------------------+ ++ |OOB_RESET_WARN |Output |Sent from the MC just before the OOB | ++ | | |processor is about to enter reset. Upon| ++ | | |receiving, the BMC must flush and | ++ | | |quiesce its OOB Channel upstream | ++ | | |request queues and assert OOB_RESET_ACK| ++ | | |VWire. The-MC subsequently completes | ++ | | |any outstanding posted transactions or | ++ | | |completions and then disables the OOB | ++ | | |Channel via a write to the Slave's | ++ | | |Configuration Register. | ++ +----------------------+---------+---------------------------------------+ ++ |OOB_RESET_ACK |Input |ACK for OOB_RESET_WARN message | ++ +----------------------+---------+---------------------------------------+ ++ ++`Intel C620 Series Chipset Platform Controller Hub ++`_ ++ ++ -- 17. Enhanced Serial Peripheral Interface ++ ++ ++`Enhanced Serial Peripheral Interface (eSPI) ++- Interface Base Specification (for Client and Server Platforms) ++`_ ++ +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 4a302d745b09..165a2bddc6cd 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -266,6 +266,7 @@ + clocks = <&syscon ASPEED_CLK_APB>; + interrupt-controller; + #interrupt-cells = <2>; ++ status = "disabled"; + }; + + sgpio: sgpio@1e780200 { +@@ -360,6 +361,9 @@ + reg = <0x1e6ee000 0x100>; + interrupts = <23>; + status = "disabled"; ++ clocks = <&syscon ASPEED_CLK_GATE_ESPICLK>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_espi_default>; + }; + + lpc: lpc@1e789000 { +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index fe1e2a4072a8..f2062546250c 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -485,6 +485,14 @@ config VEXPRESS_SYSCFG + bus. System Configuration interface is one of the possible means + of generating transactions on this bus. + ++config ASPEED_ESPI_SLAVE ++ depends on ARCH_ASPEED || COMPILE_TEST ++ depends on REGMAP_MMIO ++ tristate "Aspeed ast2500 eSPI slave device driver" ++ ---help--- ++ Control Aspeed ast2500 eSPI slave controller to handle event ++ which needs the firmware's processing. ++ + config ASPEED_LPC_CTRL + depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON + tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control" +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index a2b85ec21d09..bb89694e6b4b 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -53,6 +53,7 @@ obj-$(CONFIG_GENWQE) += genwqe/ + obj-$(CONFIG_ECHO) += echo/ + obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o + obj-$(CONFIG_CXL_BASE) += cxl/ ++obj-$(CONFIG_ASPEED_ESPI_SLAVE) += aspeed-espi-slave.o + obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o + obj-$(CONFIG_ASPEED_LPC_MBOX) += aspeed-lpc-mbox.o +diff --git a/drivers/misc/aspeed-espi-slave.c b/drivers/misc/aspeed-espi-slave.c +new file mode 100644 +index 000000000000..36ae867ca6f9 +--- /dev/null ++++ b/drivers/misc/aspeed-espi-slave.c +@@ -0,0 +1,353 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2012-2015, ASPEED Technology Inc. ++ * Copyright (c) 2015-2018, Intel Corporation. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVICE_NAME "aspeed-espi-slave" ++ ++#define ESPI_CTRL 0x00 ++#define ESPI_CTRL_SW_RESET GENMASK(31, 24) ++#define ESPI_CTRL_OOB_CHRDY BIT(4) ++#define ESPI_ISR 0x08 ++#define ESPI_ISR_HW_RESET BIT(31) ++#define ESPI_ISR_VW_SYS_EVT1 BIT(22) ++#define ESPI_ISR_VW_SYS_EVT BIT(8) ++#define ESPI_IER 0x0C ++#define ESPI_DATA_PORT 0x28 ++#define ESPI_DATA_PORT_ASPEED 0xa8 ++#define ESPI_SYS_IER 0x94 ++#define ESPI_SYS_EVENT 0x98 ++#define ESPI_SYS_INT_T0 0x110 ++#define ESPI_SYS_INT_T1 0x114 ++#define ESPI_SYS_INT_T2 0x118 ++#define ESPI_SYS_ISR 0x11C ++#define ESPI_SYSEVT_HOST_RST_ACK BIT(27) ++#define ESPI_SYSEVT_SLAVE_BOOT_STATUS BIT(23) ++#define ESPI_SYSEVT_SLAVE_BOOT_DONE BIT(20) ++#define ESPI_SYSEVT_OOB_RST_ACK BIT(16) ++#define ESPI_SYSEVT_HOST_RST_WARN BIT(8) ++#define ESPI_SYSEVT_OOB_RST_WARN BIT(6) ++#define ESPI_SYSEVT_PLT_RST_N BIT(5) ++#define ESPI_SYS1_IER 0x100 ++#define ESPI_SYS1_EVENT 0x104 ++#define ESPI_SYS1_INT_T0 0x120 ++#define ESPI_SYS1_INT_T1 0x124 ++#define ESPI_SYS1_INT_T2 0x128 ++#define ESPI_SYS1_ISR 0x12C ++#define ESPI_SYSEVT1_SUS_ACK BIT(20) ++#define ESPI_SYSEVT1_SUS_WARN BIT(0) ++ ++struct aspeed_espi_slave_data { ++ struct regmap *map; ++ struct clk *clk; ++}; ++ ++static void aspeed_espi_slave_sys_event(struct platform_device *pdev) ++{ ++ struct aspeed_espi_slave_data *priv = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u32 sts, evt; ++ ++ if (regmap_read(priv->map, ESPI_SYS_ISR, &sts) != 0 || ++ regmap_read(priv->map, ESPI_SYS_EVENT, &evt) != 0) { ++ dev_err(dev, "regmap_read failed\n"); ++ return; ++ } ++ ++ dev_dbg(dev, "sys: sts = %08x, evt = %08x\n", sts, evt); ++ ++ if ((evt & ESPI_SYSEVT_SLAVE_BOOT_STATUS) == 0) { ++ dev_info(dev, "Setting espi slave boot done\n"); ++ regmap_write(priv->map, ESPI_SYS_EVENT, ++ evt | ESPI_SYSEVT_SLAVE_BOOT_STATUS | ++ ESPI_SYSEVT_SLAVE_BOOT_DONE); ++ } ++#if 0 ++ if (sts & ESPI_SYSEVT_HOST_RST_WARN) { ++ dev_info(dev, "ESPI_SYSEVT_HOST_RST_WARN; %s ack\n", ++ (evt & ESPI_SYSEVT_HOST_RST_WARN ? "send" : "clr")); ++ regmap_write_bits(priv->map, ESPI_SYS_EVENT, ++ ESPI_SYSEVT_HOST_RST_ACK, ++ evt & ESPI_SYSEVT_HOST_RST_WARN ? ++ ESPI_SYSEVT_HOST_RST_ACK : 0); ++ } ++ if (sts & ESPI_SYSEVT_OOB_RST_WARN) { ++ dev_info(dev, "ESPI_SYSEVT_OOB_RST_WARN; %s ack\n", ++ (evt & ESPI_SYSEVT_OOB_RST_WARN ? "send" : "clr")); ++ regmap_write_bits(priv->map, ESPI_SYS_EVENT, ++ ESPI_SYSEVT_OOB_RST_ACK, ++ evt & ESPI_SYSEVT_OOB_RST_WARN ? ++ ESPI_SYSEVT_OOB_RST_ACK : 0); ++ } ++#else ++ if (sts & ESPI_SYSEVT_HOST_RST_WARN) { ++ if (evt & ESPI_SYSEVT_HOST_RST_WARN) { ++ dev_info(dev, "ESPI_SYSEVT_HOST_RST_WARN; send ack\n"); ++ regmap_write_bits(priv->map, ESPI_SYS_EVENT, ++ ESPI_SYSEVT_HOST_RST_ACK, ESPI_SYSEVT_HOST_RST_ACK); ++ } ++ } ++ if (sts & ESPI_SYSEVT_OOB_RST_WARN) { ++ if (evt & ESPI_SYSEVT_OOB_RST_WARN) { ++ dev_info(dev, "ESPI_SYSEVT_OOB_RST_WARN; send ack\n"); ++ regmap_write_bits(priv->map, ESPI_SYS_EVENT, ++ ESPI_SYSEVT_OOB_RST_ACK, ESPI_SYSEVT_OOB_RST_ACK); ++ } ++ } ++#endif ++ regmap_write(priv->map, ESPI_SYS_ISR, sts); ++} ++ ++static void aspeed_espi_slave_sys1_event(struct platform_device *pdev) ++{ ++ struct aspeed_espi_slave_data *priv = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u32 sts, evt; ++ ++ if (regmap_read(priv->map, ESPI_SYS1_ISR, &sts) != 0 || ++ regmap_read(priv->map, ESPI_SYS1_EVENT, &evt) != 0) { ++ dev_err(dev, "regmap_read failed\n"); ++ return; ++ } ++ dev_dbg(dev, "sys1: sts = %08x, evt = %08x\n", sts, evt); ++ ++#if 0 ++ if (sts & ESPI_SYSEVT1_SUS_WARN) { ++ dev_info(dev, "ESPI_SYSEVT1_SUS_WARN; %s ack\n", ++ (evt & ESPI_SYSEVT1_SUS_WARN ? "send" : "clr")); ++ regmap_write_bits(priv->map, ESPI_SYS1_EVENT, ++ ESPI_SYSEVT1_SUS_ACK, ++ evt & ESPI_SYSEVT1_SUS_WARN ? ++ ESPI_SYSEVT1_SUS_ACK : 0); ++ } ++#else ++ if (sts & ESPI_SYSEVT1_SUS_WARN) { ++ if (evt & ESPI_SYSEVT1_SUS_WARN) { ++ dev_info(dev, "ESPI_SYSEVT_OOB_RST_WARN; send ack\n"); ++ regmap_write_bits(priv->map, ESPI_SYS1_EVENT, ++ ESPI_SYSEVT1_SUS_ACK, ESPI_SYSEVT1_SUS_ACK); ++ } ++ } ++#endif ++ regmap_write(priv->map, ESPI_SYS1_ISR, sts); ++} ++ ++static void aspeed_espi_slave_boot_ack(struct platform_device *pdev) ++{ ++ struct aspeed_espi_slave_data *priv = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u32 evt; ++ ++ if (regmap_read(priv->map, ESPI_SYS_EVENT, &evt) == 0 && ++ (evt & ESPI_SYSEVT_SLAVE_BOOT_STATUS) == 0) { ++ dev_info(dev, "Setting espi slave boot done\n"); ++ regmap_write(priv->map, ESPI_SYS_EVENT, ++ evt | ESPI_SYSEVT_SLAVE_BOOT_STATUS | ++ ESPI_SYSEVT_SLAVE_BOOT_DONE); ++ } ++ ++ if (regmap_read(priv->map, ESPI_SYS1_EVENT, &evt) == 0 && ++ (evt & ESPI_SYSEVT1_SUS_WARN) != 0 && ++ (evt & ESPI_SYSEVT1_SUS_ACK) == 0) { ++ dev_info(dev, "Boot SUS WARN set; send ack\n"); ++ regmap_write(priv->map, ESPI_SYS1_EVENT, ++ evt | ESPI_SYSEVT1_SUS_ACK); ++ } ++} ++ ++static irqreturn_t aspeed_espi_slave_irq(int irq, void *arg) ++{ ++ struct platform_device *pdev = arg; ++ struct aspeed_espi_slave_data *priv = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ u32 sts; ++ ++ if (regmap_read(priv->map, ESPI_ISR, &sts) != 0) { ++ dev_err(dev, "regmap_read failed\n"); ++ return IRQ_NONE; ++ } ++ ++ dev_dbg(dev, "ESPI_ISR: %08x\n", sts); ++ ++ if (sts & ESPI_ISR_VW_SYS_EVT) ++ aspeed_espi_slave_sys_event(pdev); ++ ++ if (sts & ESPI_ISR_VW_SYS_EVT1) ++ aspeed_espi_slave_sys1_event(pdev); ++ ++ /* ++ if (sts & ESPI_ISR_HW_RESET) { ++ regmap_write_bits(priv->map, ESPI_CTRL, ++ ESPI_CTRL_SW_RESET, 0); ++ regmap_write_bits(priv->map, ESPI_CTRL, ++ ESPI_CTRL_SW_RESET, ESPI_CTRL_SW_RESET); ++ ++ aspeed_espi_slave_boot_ack(pdev); ++ } ++ */ ++ ++ regmap_write(priv->map, ESPI_ISR, sts); ++ ++ return IRQ_HANDLED; ++} ++ ++/* Setup Interrupt Type/Enable of System Event from Master ++ * T2 T1 T0 ++ * 1). HOST_RST_WARN : Dual Edge 1 0 0 ++ * 2). OOB_RST_WARN : Dual Edge 1 0 0 ++ * 3). PLTRST_N : Dual Edge 1 0 0 ++ */ ++#define ESPI_SYS_INT_T0_SET 0x00000000 ++#define ESPI_SYS_INT_T1_SET 0x00000000 ++#define ESPI_SYS_INT_T2_SET \ ++(ESPI_SYSEVT_HOST_RST_WARN | ESPI_SYSEVT_OOB_RST_WARN | ESPI_SYSEVT_PLT_RST_N) ++#define ESPI_SYS_INT_SET \ ++(ESPI_SYSEVT_HOST_RST_WARN | ESPI_SYSEVT_OOB_RST_WARN | ESPI_SYSEVT_PLT_RST_N) ++ ++/* Setup Interrupt Type/Enable of System Event 1 from Master ++ * T2 T1 T0 ++ * 1). SUS_WARN : Rising Edge 0 0 1 ++ */ ++#define ESPI_SYS1_INT_T0_SET ESPI_SYSEVT1_SUS_WARN ++#define ESPI_SYS1_INT_T1_SET 0x00000000 ++#define ESPI_SYS1_INT_T2_SET 0x00000000 ++#define ESPI_SYS1_INT_SET ESPI_SYSEVT1_SUS_WARN ++ ++static int aspeed_espi_slave_config_irq(struct platform_device *pdev) ++{ ++ struct aspeed_espi_slave_data *priv = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ int irq; ++ int rc; ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ regmap_write_bits(priv->map, ESPI_CTRL, ESPI_CTRL_OOB_CHRDY, ++ ESPI_CTRL_OOB_CHRDY); ++ ++ regmap_write(priv->map, ESPI_SYS_INT_T0, ESPI_SYS_INT_T0_SET); ++ regmap_write(priv->map, ESPI_SYS_INT_T1, ESPI_SYS_INT_T1_SET); ++ regmap_write(priv->map, ESPI_SYS_INT_T2, ESPI_SYS_INT_T2_SET); ++ regmap_write(priv->map, ESPI_SYS_IER, ESPI_SYS_INT_SET); ++ ++ regmap_write(priv->map, ESPI_SYS1_INT_T0, ESPI_SYS1_INT_T0_SET); ++ regmap_write(priv->map, ESPI_SYS1_INT_T1, ESPI_SYS1_INT_T1_SET); ++ regmap_write(priv->map, ESPI_SYS1_INT_T2, ESPI_SYS1_INT_T2_SET); ++ regmap_write(priv->map, ESPI_SYS1_IER, ESPI_SYS1_INT_SET); ++ ++ regmap_write(priv->map, ESPI_IER, 0xFFFFFFFF); ++ ++ aspeed_espi_slave_boot_ack(pdev); ++ ++ rc = devm_request_irq(dev, irq, aspeed_espi_slave_irq, IRQF_SHARED, ++ dev_name(dev), pdev); ++ if (rc < 0) ++ return rc; ++ ++ return 0; ++} ++ ++static const struct regmap_config espi_slave_regmap_cfg = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = ESPI_SYS1_ISR, ++}; ++ ++static int aspeed_espi_slave_probe(struct platform_device *pdev) ++{ ++ struct aspeed_espi_slave_data *priv; ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ void __iomem *regs; ++ int rc; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ regs = devm_ioremap_resource(dev, res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->map = devm_regmap_init_mmio(dev, regs, &espi_slave_regmap_cfg); ++ if (IS_ERR(priv->map)) ++ return PTR_ERR(priv->map); ++ ++ priv->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(priv->clk)) { ++ dev_err(dev, "couldn't get clock\n"); ++ return PTR_ERR(priv->clk); ++ } ++ rc = clk_prepare_enable(priv->clk); ++ if (rc) { ++ dev_err(dev, "couldn't enable clock\n"); ++ return rc; ++ } ++ ++ dev_set_name(dev, DEVICE_NAME); ++ ++ platform_set_drvdata(pdev, priv); ++ ++ rc = aspeed_espi_slave_config_irq(pdev); ++ if (rc) { ++ platform_set_drvdata(pdev, NULL); ++ goto err; ++ } ++ ++ dev_info(dev, "aspeed,ast2500-espi-slave probe complete\n"); ++ return 0; ++ ++err: ++ clk_disable_unprepare(priv->clk); ++ return rc; ++} ++ ++ ++static int aspeed_espi_slave_remove(struct platform_device *pdev) ++{ ++ struct aspeed_espi_slave_data *priv = platform_get_drvdata(pdev); ++ ++ clk_disable_unprepare(priv->clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id of_espi_slave_match_table[] = { ++ { .compatible = "aspeed,ast2500-espi-slave" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, of_espi_slave_match_table); ++ ++static struct platform_driver aspeed_espi_slave_driver = { ++ .driver = { ++ .name = DEVICE_NAME, ++ .of_match_table = of_match_ptr(of_espi_slave_match_table), ++ }, ++ .probe = aspeed_espi_slave_probe, ++ .remove = aspeed_espi_slave_remove, ++}; ++module_platform_driver(aspeed_espi_slave_driver); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Haiyue Wang "); ++MODULE_DESCRIPTION("Linux device interface to the eSPI slave"); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0025-dts-add-AST2500-LPC-SIO-tree-node.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0025-dts-add-AST2500-LPC-SIO-tree-node.patch new file mode 100644 index 000000000..73bd68f21 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0025-dts-add-AST2500-LPC-SIO-tree-node.patch @@ -0,0 +1,32 @@ +From ba357b37e1041b6fe0e5012cf09571381207aa9b Mon Sep 17 00:00:00 2001 +From: Haiyue Wang +Date: Sat, 24 Feb 2018 11:23:46 +0800 +Subject: [PATCH] dts: add AST2500 LPC SIO tree node + +Add the AST2500 LPC SIO tree node. + +Signed-off-by: Haiyue Wang +--- + arch/arm/boot/dts/aspeed-g5.dtsi | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index baf230034480..f7e812d36641 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -464,6 +464,12 @@ + compatible = "aspeed,bmc-misc"; + }; + ++ lpc_sio: lpc-sio@100 { ++ compatible = "aspeed,ast2500-lpc-sio"; ++ reg = <0x100 0x20>; ++ status = "disabled"; ++ }; ++ + mbox: mbox@180 { + compatible = "aspeed,ast2500-mbox"; + reg = <0x180 0x5c>; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0026-Add-support-for-new-PECI-commands.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0026-Add-support-for-new-PECI-commands.patch new file mode 100644 index 000000000..3ba6fd56e --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0026-Add-support-for-new-PECI-commands.patch @@ -0,0 +1,392 @@ +From e734cd91f288838a491a75e16f3a09d353079139 Mon Sep 17 00:00:00 2001 +From: "Jason M. Bills" +Date: Wed, 4 Apr 2018 13:52:39 -0700 +Subject: [PATCH] Add support for new PECI commands + +Signed-off-by: Jason M. Bills +--- + drivers/peci/peci-core.c | 200 ++++++++++++++++++++++++++++++++++++++++ + include/uapi/linux/peci-ioctl.h | 109 ++++++++++++++++++++++ + 2 files changed, 309 insertions(+) + +diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c +index fac8c72dcda8..62dada99afee 100644 +--- a/drivers/peci/peci-core.c ++++ b/drivers/peci/peci-core.c +@@ -242,6 +242,9 @@ static int peci_scan_cmd_mask(struct peci_adapter *adapter) + adapter->cmd_mask |= BIT(PECI_CMD_GET_TEMP); + adapter->cmd_mask |= BIT(PECI_CMD_GET_DIB); + adapter->cmd_mask |= BIT(PECI_CMD_PING); ++ adapter->cmd_mask |= BIT(PECI_CMD_RD_END_PT_CFG); ++ adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_DISC); ++ adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_GET_FRAME); + + return rc; + } +@@ -515,6 +518,197 @@ static int peci_ioctl_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) + return rc; + } + ++static int peci_ioctl_rd_end_pt_cfg(struct peci_adapter *adapter, void *vmsg) ++{ ++ struct peci_rd_end_pt_cfg_msg *umsg = vmsg; ++ struct peci_xfer_msg msg; ++ u32 address; ++ int rc = 0; ++ ++ switch (umsg->msg_type) { ++ case RDENDPTCFG_TYPE_LOCAL_PCI: ++ case RDENDPTCFG_TYPE_PCI: ++ /** ++ * Per the PECI spec, the read length must be a byte, word, ++ * or dword ++ */ ++ if (umsg->rx_len != 1 && umsg->rx_len != 2 && ++ umsg->rx_len != 4) { ++ dev_dbg(&adapter->dev, ++ "Invalid read length, rx_len: %d\n", ++ umsg->rx_len); ++ return -EINVAL; ++ } ++ ++ address = umsg->params.pci_cfg.reg; /* [11:0] - Register */ ++ address |= (u32)umsg->params.pci_cfg.function ++ << 12; /* [14:12] - Function */ ++ address |= (u32)umsg->params.pci_cfg.device ++ << 15; /* [19:15] - Device */ ++ address |= (u32)umsg->params.pci_cfg.bus ++ << 20; /* [27:20] - Bus */ ++ /* [31:28] - Reserved */ ++ msg.addr = umsg->addr; ++ msg.tx_len = RDENDPTCFG_PCI_WRITE_LEN; ++ msg.rx_len = RDENDPTCFG_READ_LEN_BASE + umsg->rx_len; ++ msg.tx_buf[0] = RDENDPTCFG_PECI_CMD; ++ msg.tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ ++ msg.tx_buf[2] = umsg->msg_type; /* Message Type */ ++ msg.tx_buf[3] = 0x00; /* Endpoint ID */ ++ msg.tx_buf[4] = 0x00; /* Reserved */ ++ msg.tx_buf[5] = 0x00; /* Reserved */ ++ msg.tx_buf[6] = RDENDPTCFG_ADDR_TYPE_PCI; /* Address Type */ ++ msg.tx_buf[7] = umsg->params.pci_cfg.seg; /* PCI Segment */ ++ msg.tx_buf[8] = (u8)address; /* LSB - PCI Config Address */ ++ msg.tx_buf[9] = (u8)(address >> 8); /* PCI Config Address */ ++ msg.tx_buf[10] = (u8)(address >> 16); /* PCI Config Address */ ++ msg.tx_buf[11] = ++ (u8)(address >> 24); /* MSB - PCI Config Address */ ++ break; ++ case RDENDPTCFG_TYPE_MMIO: ++ /** ++ * Per the PECI spec, the read length must be a byte, word, ++ * dword, or qword ++ */ ++ if (umsg->rx_len != 1 && umsg->rx_len != 2 && ++ umsg->rx_len != 4 && umsg->rx_len != 8) { ++ dev_dbg(&adapter->dev, ++ "Invalid read length, rx_len: %d\n", ++ umsg->rx_len); ++ return -EINVAL; ++ } ++ /** ++ * Per the PECI spec, the address type must specify either DWORD ++ * or QWORD ++ */ ++ if (umsg->params.mmio.addr_type != ++ RDENDPTCFG_ADDR_TYPE_MMIO_D && ++ umsg->params.mmio.addr_type != ++ RDENDPTCFG_ADDR_TYPE_MMIO_Q) { ++ dev_dbg(&adapter->dev, ++ "Invalid address type, addr_type: %d\n", ++ umsg->params.mmio.addr_type); ++ return -EINVAL; ++ } ++ ++ address = umsg->params.mmio.function; /* [2:0] - Function */ ++ address |= (u32)umsg->params.mmio.device ++ << 3; /* [7:3] - Device */ ++ ++ msg.addr = umsg->addr; ++ msg.tx_len = RDENDPTCFG_MMIO_D_WRITE_LEN; ++ msg.rx_len = RDENDPTCFG_READ_LEN_BASE + umsg->rx_len; ++ msg.tx_buf[0] = RDENDPTCFG_PECI_CMD; ++ msg.tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ ++ msg.tx_buf[2] = umsg->msg_type; /* Message Type */ ++ msg.tx_buf[3] = 0x00; /* Endpoint ID */ ++ msg.tx_buf[4] = 0x00; /* Reserved */ ++ msg.tx_buf[5] = umsg->params.mmio.bar; /* BAR # */ ++ msg.tx_buf[6] = umsg->params.mmio.addr_type; /* Address Type */ ++ msg.tx_buf[7] = umsg->params.mmio.seg; /* PCI Segment */ ++ msg.tx_buf[8] = (u8)address; /* Function/Device */ ++ msg.tx_buf[9] = umsg->params.mmio.bus; /* PCI Bus */ ++ msg.tx_buf[10] = (u8)umsg->params.mmio ++ .offset; /* LSB - Register Offset */ ++ msg.tx_buf[11] = (u8)(umsg->params.mmio.offset ++ >> 8); /* Register Offset */ ++ msg.tx_buf[12] = (u8)(umsg->params.mmio.offset ++ >> 16); /* Register Offset */ ++ msg.tx_buf[13] = (u8)(umsg->params.mmio.offset ++ >> 24); /* MSB - DWORD Register Offset */ ++ if (umsg->params.mmio.addr_type ++ == RDENDPTCFG_ADDR_TYPE_MMIO_Q) { ++ msg.tx_len = RDENDPTCFG_MMIO_Q_WRITE_LEN; ++ msg.tx_buf[14] = (u8)(umsg->params.mmio.offset ++ >> 32); /* Register Offset */ ++ msg.tx_buf[15] = (u8)(umsg->params.mmio.offset ++ >> 40); /* Register Offset */ ++ msg.tx_buf[16] = (u8)(umsg->params.mmio.offset ++ >> 48); /* Register Offset */ ++ msg.tx_buf[17] = ++ (u8)(umsg->params.mmio.offset ++ >> 56); /* MSB - QWORD Register Offset */ ++ } ++ break; ++ } ++ ++ rc = peci_xfer_with_retries(adapter, &msg, false); ++ if (!rc) ++ memcpy(umsg->data, &msg.rx_buf[1], umsg->rx_len); ++ ++ return rc; ++} ++ ++static int peci_ioctl_crashdump_disc(struct peci_adapter *adapter, void *vmsg) ++{ ++ struct peci_crashdump_disc_msg *umsg = vmsg; ++ struct peci_xfer_msg msg; ++ int rc = 0; ++ ++ /* Per the EDS, the read length must be a byte, word, or qword */ ++ if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 8) { ++ dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", ++ umsg->rx_len); ++ return -EINVAL; ++ } ++ ++ msg.addr = umsg->addr; ++ msg.tx_len = CRASHDUMP_DISC_WRITE_LEN; ++ msg.rx_len = CRASHDUMP_DISC_READ_LEN_BASE + umsg->rx_len; ++ msg.tx_buf[0] = CRASHDUMP_CMD; ++ msg.tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ ++ /* Host ID is 0 for PECI 3.0 */ ++ msg.tx_buf[2] = CRASHDUMP_DISC_VERSION; ++ msg.tx_buf[3] = CRASHDUMP_DISC_OPCODE; ++ msg.tx_buf[4] = umsg->subopcode; ++ msg.tx_buf[5] = umsg->param0; ++ msg.tx_buf[6] = (u8)umsg->param1; ++ msg.tx_buf[7] = (u8)(umsg->param1 >> 8); ++ msg.tx_buf[8] = umsg->param2; ++ ++ rc = peci_xfer_with_retries(adapter, &msg, false); ++ if (!rc) ++ memcpy(umsg->data, &msg.rx_buf[1], umsg->rx_len); ++ ++ return rc; ++} ++ ++static int peci_ioctl_crashdump_get_frame(struct peci_adapter *adapter, ++ void *vmsg) ++{ ++ struct peci_crashdump_get_frame_msg *umsg = vmsg; ++ struct peci_xfer_msg msg; ++ int rc = 0; ++ ++ /* Per the EDS, the read length must be a qword or dqword */ ++ if (umsg->rx_len != 8 && umsg->rx_len != 16) { ++ dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", ++ umsg->rx_len); ++ return -EINVAL; ++ } ++ ++ msg.addr = umsg->addr; ++ msg.tx_len = CRASHDUMP_GET_FRAME_WRITE_LEN; ++ msg.rx_len = CRASHDUMP_GET_FRAME_READ_LEN_BASE + umsg->rx_len; ++ msg.tx_buf[0] = CRASHDUMP_CMD; ++ msg.tx_buf[1] = 0x00; /* request byte for Host ID | Retry bit */ ++ /* Host ID is 0 for PECI 3.0 */ ++ msg.tx_buf[2] = CRASHDUMP_GET_FRAME_VERSION; ++ msg.tx_buf[3] = CRASHDUMP_GET_FRAME_OPCODE; ++ msg.tx_buf[4] = (u8)umsg->param0; ++ msg.tx_buf[5] = (u8)(umsg->param0 >> 8); ++ msg.tx_buf[6] = (u8)umsg->param1; ++ msg.tx_buf[7] = (u8)(umsg->param1 >> 8); ++ msg.tx_buf[8] = (u8)umsg->param2; ++ msg.tx_buf[8] = (u8)(umsg->param2 >> 8); ++ ++ rc = peci_xfer_with_retries(adapter, &msg, false); ++ if (!rc) ++ memcpy(umsg->data, &msg.rx_buf[1], umsg->rx_len); ++ ++ return rc; ++} ++ + typedef int (*peci_ioctl_fn_type)(struct peci_adapter *, void *); + + static const peci_ioctl_fn_type peci_ioctl_fn[PECI_CMD_MAX] = { +@@ -530,6 +724,9 @@ static const peci_ioctl_fn_type peci_ioctl_fn[PECI_CMD_MAX] = { + NULL, /* Reserved */ + peci_ioctl_rd_pci_cfg_local, + peci_ioctl_wr_pci_cfg_local, ++ peci_ioctl_rd_end_pt_cfg, ++ peci_ioctl_crashdump_disc, ++ peci_ioctl_crashdump_get_frame, + }; + + /** +@@ -592,6 +789,9 @@ static long peci_ioctl(struct file *file, unsigned int iocmd, unsigned long arg) + case PECI_IOC_RD_PCI_CFG: + case PECI_IOC_RD_PCI_CFG_LOCAL: + case PECI_IOC_WR_PCI_CFG_LOCAL: ++ case PECI_IOC_RD_END_PT_CFG: ++ case PECI_IOC_CRASHDUMP_DISC: ++ case PECI_IOC_CRASHDUMP_GET_FRAME: + cmd = _IOC_NR(iocmd); + msg_len = _IOC_SIZE(iocmd); + break; +diff --git a/include/uapi/linux/peci-ioctl.h b/include/uapi/linux/peci-ioctl.h +index a6dae71cbff5..5040d1cb4a3d 100644 +--- a/include/uapi/linux/peci-ioctl.h ++++ b/include/uapi/linux/peci-ioctl.h +@@ -31,6 +31,40 @@ + #define PKG_ID_MICROCODE_REV 0x0004 /* CPU Microcode Update Revision */ + #define PKG_ID_MACHINE_CHECK_STATUS 0x0005 /* Machine Check Status */ + ++/* RdEndPointCfg Parameters */ ++enum rdendptcfg_msg_type { ++ RDENDPTCFG_TYPE_LOCAL_PCI = 0x03, ++ RDENDPTCFG_TYPE_PCI = 0x04, ++ RDENDPTCFG_TYPE_MMIO = 0x05, ++}; ++ ++enum rdendptcfg_addr_type { ++ RDENDPTCFG_ADDR_TYPE_PCI = 0x04, ++ RDENDPTCFG_ADDR_TYPE_MMIO_D = 0x05, ++ RDENDPTCFG_ADDR_TYPE_MMIO_Q = 0x06, ++}; ++ ++/* Crashdump Parameters */ ++enum crashdump_agent { ++ CRASHDUMP_CORE = 0x00, ++ CRASHDUMP_TOR = 0x01, ++}; ++ ++enum crashdump_discovery_sub_opcode { ++ CRASHDUMP_ENABLED = 0x00, ++ CRASHDUMP_NUM_AGENTS = 0x01, ++ CRASHDUMP_AGENT_DATA = 0x02, ++}; ++ ++enum crashdump_agent_data_param { ++ CRASHDUMP_AGENT_ID = 0x00, ++ CRASHDUMP_AGENT_PARAM = 0x01, ++}; ++ ++enum crashdump_agent_param { ++ CRASHDUMP_PAYLOAD_SIZE = 0x00, ++}; ++ + /* RdPkgConfig Index */ + #define MBX_INDEX_CPU_ID 0 /* Package Identifier Read */ + #define MBX_INDEX_VR_DEBUG 1 /* VR Debug */ +@@ -136,6 +170,22 @@ + #define WRPCICFGLOCAL_READ_LEN 1 + #define WRPCICFGLOCAL_PECI_CMD 0xe5 + ++#define RDENDPTCFG_PCI_WRITE_LEN 0x0C ++#define RDENDPTCFG_MMIO_D_WRITE_LEN 0x0E ++#define RDENDPTCFG_MMIO_Q_WRITE_LEN 0x12 ++#define RDENDPTCFG_READ_LEN_BASE 1 ++#define RDENDPTCFG_PECI_CMD 0xC1 ++ ++#define CRASHDUMP_DISC_WRITE_LEN 9 ++#define CRASHDUMP_DISC_READ_LEN_BASE 1 ++#define CRASHDUMP_DISC_VERSION 1 ++#define CRASHDUMP_DISC_OPCODE 1 ++#define CRASHDUMP_GET_FRAME_WRITE_LEN 10 ++#define CRASHDUMP_GET_FRAME_READ_LEN_BASE 1 ++#define CRASHDUMP_GET_FRAME_VERSION 3 ++#define CRASHDUMP_GET_FRAME_OPCODE 3 ++#define CRASHDUMP_CMD 0x71 ++ + #define PECI_BUFFER_SIZE 32 + + /** +@@ -172,6 +222,9 @@ enum peci_cmd { + PECI_CMD_WR_PCI_CFG, + PECI_CMD_RD_PCI_CFG_LOCAL, + PECI_CMD_WR_PCI_CFG_LOCAL, ++ PECI_CMD_RD_END_PT_CFG, ++ PECI_CMD_CRASHDUMP_DISC, ++ PECI_CMD_CRASHDUMP_GET_FRAME, + PECI_CMD_MAX + }; + +@@ -366,6 +419,50 @@ struct peci_wr_pci_cfg_local_msg { + __u32 value; + } __attribute__((__packed__)); + ++struct peci_rd_end_pt_cfg_msg { ++ __u8 addr; ++ __u8 msg_type; ++ union { ++ struct { ++ __u8 seg; ++ __u8 bus; ++ __u8 device; ++ __u8 function; ++ __u16 reg; ++ } pci_cfg; ++ struct { ++ __u8 seg; ++ __u8 bus; ++ __u8 device; ++ __u8 function; ++ __u8 bar; ++ __u8 addr_type; ++ __u64 offset; ++ } mmio; ++ } params; ++ __u8 rx_len; ++ __u8 data[8]; ++} __attribute__((__packed__)); ++ ++struct peci_crashdump_disc_msg { ++ __u8 addr; ++ __u8 subopcode; ++ __u8 param0; ++ __u16 param1; ++ __u8 param2; ++ __u8 rx_len; ++ __u8 data[8]; ++} __attribute__((__packed__)); ++ ++struct peci_crashdump_get_frame_msg { ++ __u8 addr; ++ __u16 param0; ++ __u16 param1; ++ __u16 param2; ++ __u8 rx_len; ++ __u8 data[16]; ++} __attribute__((__packed__)); ++ + #define PECI_IOC_BASE 0xb7 + + #define PECI_IOC_XFER \ +@@ -400,4 +497,16 @@ struct peci_wr_pci_cfg_local_msg { + _IOWR(PECI_IOC_BASE, PECI_CMD_WR_PCI_CFG_LOCAL, \ + struct peci_wr_pci_cfg_local_msg) + ++#define PECI_IOC_RD_END_PT_CFG \ ++ _IOWR(PECI_IOC_BASE, PECI_CMD_RD_END_PT_CFG, \ ++ struct peci_rd_end_pt_cfg_msg) ++ ++#define PECI_IOC_CRASHDUMP_DISC \ ++ _IOWR(PECI_IOC_BASE, PECI_CMD_CRASHDUMP_DISC, \ ++ struct peci_crashdump_disc_msg) ++ ++#define PECI_IOC_CRASHDUMP_GET_FRAME \ ++ _IOWR(PECI_IOC_BASE, PECI_CMD_CRASHDUMP_GET_FRAME, \ ++ struct peci_crashdump_get_frame_msg) ++ + #endif /* __PECI_IOCTL_H */ +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0028-Add-AST2500-JTAG-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0028-Add-AST2500-JTAG-driver.patch new file mode 100644 index 000000000..860a1ba5d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0028-Add-AST2500-JTAG-driver.patch @@ -0,0 +1,1138 @@ +From 43470f186979483ba6c1e6374c7ea3a129622862 Mon Sep 17 00:00:00 2001 +From: "Hunt, Bryan" +Date: Fri, 30 Mar 2018 10:48:01 -0700 +Subject: [PATCH] Add AST2500d JTAG driver + +Adding aspeed jtag driver + +Signed-off-by: Hunt, Bryan +--- + arch/arm/boot/dts/aspeed-g5.dtsi | 9 + + drivers/Kconfig | 1 + + drivers/Makefile | 1 + + drivers/jtag/Kconfig | 13 + + drivers/jtag/Makefile | 1 + + drivers/jtag/jtag_aspeed.c | 963 +++++++++++++++++++++++++++++++++++++++ + include/uapi/linux/jtag_drv.h | 73 +++ + 7 files changed, 1061 insertions(+) + create mode 100644 drivers/jtag/Kconfig + create mode 100644 drivers/jtag/Makefile + create mode 100644 drivers/jtag/jtag_aspeed.c + create mode 100644 include/uapi/linux/jtag_drv.h + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index a7bbc2adecc9..b63003c2c0c7 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -366,6 +366,15 @@ + pinctrl-0 = <&pinctrl_espi_default>; + }; + ++ jtag: jtag@1e6e4000 { ++ compatible = "aspeed,ast2500-jtag"; ++ reg = <0x1e6e2004 0x4 0x1e6e4000 0x1c>; ++ clocks = <&syscon ASPEED_CLK_APB>; ++ resets = <&syscon ASPEED_RESET_JTAG_MASTER>; ++ interrupts = <43>; ++ status = "disabled"; ++ }; ++ + lpc: lpc@1e789000 { + compatible = "aspeed,ast2500-lpc", "simple-mfd"; + reg = <0x1e789000 0x1000>; +diff --git a/drivers/Kconfig b/drivers/Kconfig +index c633db2b41fb..2778a5c33ca5 100644 +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -221,4 +221,5 @@ source "drivers/slimbus/Kconfig" + + source "drivers/peci/Kconfig" + ++source "drivers/jtag/Kconfig" + endmenu +diff --git a/drivers/Makefile b/drivers/Makefile +index 63c9b425e6e1..714067945fd2 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -187,3 +187,4 @@ obj-$(CONFIG_UNISYS_VISORBUS) += visorbus/ + obj-$(CONFIG_SIOX) += siox/ + obj-$(CONFIG_GNSS) += gnss/ + obj-$(CONFIG_PECI) += peci/ ++obj-$(CONFIG_JTAG_ASPEED) += jtag/ +diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig +new file mode 100644 +index 000000000000..2e5d0a5bea90 +--- /dev/null ++++ b/drivers/jtag/Kconfig +@@ -0,0 +1,13 @@ ++menuconfig JTAG_ASPEED ++ tristate "ASPEED SoC JTAG controller support" ++ depends on HAS_IOMEM ++ depends on ARCH_ASPEED || COMPILE_TEST ++ help ++ This provides a support for ASPEED JTAG device, equipped on ++ ASPEED SoC 24xx and 25xx families. Drivers allows programming ++ of hardware devices, connected to SoC through the JTAG interface. ++ ++ If you want this support, you should say Y here. ++ ++ To compile this driver as a module, choose M here: the module will ++ be called jtag_aspeed. +diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile +new file mode 100644 +index 000000000000..db9b660e9f90 +--- /dev/null ++++ b/drivers/jtag/Makefile +@@ -0,0 +1 @@ ++obj-$(CONFIG_JTAG_ASPEED) += jtag_aspeed.o +diff --git a/drivers/jtag/jtag_aspeed.c b/drivers/jtag/jtag_aspeed.c +new file mode 100644 +index 000000000000..42e2a131873c +--- /dev/null ++++ b/drivers/jtag/jtag_aspeed.c +@@ -0,0 +1,963 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2012-2017 ASPEED Technology Inc. ++// Copyright (c) 2018 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SCU_RESET_JTAG BIT(22) ++ ++#define AST_JTAG_DATA 0x00 ++#define AST_JTAG_INST 0x04 ++#define AST_JTAG_CTRL 0x08 ++#define AST_JTAG_ISR 0x0C ++#define AST_JTAG_SW 0x10 ++#define AST_JTAG_TCK 0x14 ++#define AST_JTAG_IDLE 0x18 ++ ++/* AST_JTAG_CTRL - 0x08 : Engine Control */ ++#define JTAG_ENG_EN BIT(31) ++#define JTAG_ENG_OUT_EN BIT(30) ++#define JTAG_ENGINE_EN (JTAG_ENG_EN | JTAG_ENG_OUT_EN) ++#define JTAG_FORCE_TMS BIT(29) ++ ++#define JTAG_IR_UPDATE BIT(26) /* AST2500 only */ ++#define JTAG_INST_LEN_MASK GENMASK(25, 20) ++#define JTAG_LAST_INST BIT(17) ++#define JTAG_INST_EN BIT(16) ++#define JTAG_DATA_LEN_MASK GENMASK(9, 4) ++ ++#define JTAG_DR_UPDATE BIT(10) /* AST2500 only */ ++#define JTAG_LAST_DATA BIT(1) ++#define JTAG_DATA_EN BIT(0) ++ ++/* AST_JTAG_ISR - 0x0C : Interrupt status and enable */ ++#define JTAG_INST_PAUSE BIT(19) ++#define JTAG_INST_COMPLETE BIT(18) ++#define JTAG_DATA_PAUSE BIT(17) ++#define JTAG_DATA_COMPLETE BIT(16) ++ ++#define JTAG_INST_PAUSE_EN BIT(3) ++#define JTAG_INST_COMPLETE_EN BIT(2) ++#define JTAG_DATA_PAUSE_EN BIT(1) ++#define JTAG_DATA_COMPLETE_EN BIT(0) ++ ++/* AST_JTAG_SW - 0x10 : Software Mode and Status */ ++#define JTAG_SW_MODE_EN BIT(19) ++#define JTAG_SW_MODE_TCK BIT(18) ++#define JTAG_SW_MODE_TMS BIT(17) ++#define JTAG_SW_MODE_TDIO BIT(16) ++ ++/* AST_JTAG_TCK - 0x14 : TCK Control */ ++#define JTAG_TCK_DIVISOR_MASK GENMASK(10, 0) ++ ++/* #define USE_INTERRUPTS */ ++#define AST_JTAG_NAME "jtag" ++ ++static DEFINE_SPINLOCK(jtag_state_lock); ++ ++struct ast_jtag_info { ++ void __iomem *reg_base; ++ void __iomem *reg_base_scu; ++ int irq; ++ u32 flag; ++ wait_queue_head_t jtag_wq; ++ bool is_open; ++ struct device *dev; ++ struct miscdevice miscdev; ++}; ++ ++/* ++ * This structure represents a TMS cycle, as expressed in a set of bits and a ++ * count of bits (note: there are no start->end state transitions that require ++ * more than 1 byte of TMS cycles) ++ */ ++struct tms_cycle { ++ unsigned char tmsbits; ++ unsigned char count; ++}; ++ ++/* ++ * These are the string representations of the TAP states corresponding to the ++ * enums literals in JtagStateEncode ++ */ ++static const char * const c_statestr[] = {"TLR", "RTI", "SelDR", "CapDR", ++ "ShfDR", "Ex1DR", "PauDR", "Ex2DR", ++ "UpdDR", "SelIR", "CapIR", "ShfIR", ++ "Ex1IR", "PauIR", "Ex2IR", "UpdIR"}; ++ ++/* ++ * This is the complete set TMS cycles for going from any TAP state to any ++ * other TAP state, following a “shortest path” rule. ++ */ ++static const struct tms_cycle _tms_cycle_lookup[][16] = { ++/* TLR RTI SelDR CapDR SDR Ex1DR PDR Ex2DR UpdDR SelIR CapIR SIR Ex1IR PIR Ex2IR UpdIR*/ ++/* TLR */{ {0x00, 0}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x02, 4}, {0x0a, 4}, {0x0a, 5}, {0x2a, 6}, {0x1a, 5}, {0x06, 3}, {0x06, 4}, {0x06, 5}, {0x16, 5}, {0x16, 6}, {0x56, 7}, {0x36, 6} }, ++/* RTI */{ {0x07, 3}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++/* SelDR*/{ {0x03, 2}, {0x03, 3}, {0x00, 0}, {0x00, 1}, {0x00, 2}, {0x02, 2}, {0x02, 3}, {0x0a, 4}, {0x06, 3}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4} }, ++/* CapDR*/{ {0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x00, 0}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++/* SDR */{ {0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++/* Ex1DR*/{ {0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x02, 3}, {0x00, 0}, {0x00, 1}, {0x02, 2}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++/* PDR */{ {0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x01, 2}, {0x05, 3}, {0x00, 0}, {0x01, 1}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++/* Ex2DR*/{ {0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x00, 0}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++/* UpdDR*/{ {0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x00, 0}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++/* SelIR*/{ {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x05, 4}, {0x05, 5}, {0x15, 5}, {0x15, 6}, {0x55, 7}, {0x35, 6}, {0x00, 0}, {0x00, 1}, {0x00, 2}, {0x02, 2}, {0x02, 3}, {0x0a, 4}, {0x06, 3} }, ++/* CapIR*/{ {0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x00, 0}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++/* SIR */{ {0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++/* Ex1IR*/{ {0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x02, 3}, {0x00, 0}, {0x00, 1}, {0x02, 2}, {0x01, 1} }, ++/* PIR */{ {0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x01, 2}, {0x05, 3}, {0x00, 0}, {0x01, 1}, {0x03, 2} }, ++/* Ex2IR*/{ {0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x00, 0}, {0x01, 1} }, ++/* UpdIR*/{ {0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x00, 0} }, ++}; ++ ++static const char * const regnames[] = { ++ [AST_JTAG_DATA] = "AST_JTAG_DATA", ++ [AST_JTAG_INST] = "AST_JTAG_INST", ++ [AST_JTAG_CTRL] = "AST_JTAG_CTRL", ++ [AST_JTAG_ISR] = "AST_JTAG_ISR", ++ [AST_JTAG_SW] = "AST_JTAG_SW", ++ [AST_JTAG_TCK] = "AST_JTAG_TCK", ++ [AST_JTAG_IDLE] = "AST_JTAG_IDLE", ++}; ++ ++static inline u32 ast_jtag_read(struct ast_jtag_info *ast_jtag, u32 reg) ++{ ++ u32 val = readl(ast_jtag->reg_base + reg); ++ ++ dev_dbg(ast_jtag->dev, "read:%s val = 0x%08x\n", regnames[reg], val); ++ return val; ++} ++ ++static inline void ast_jtag_write(struct ast_jtag_info *ast_jtag, u32 val, ++ u32 reg) ++{ ++ dev_dbg(ast_jtag->dev, "write:%s val = 0x%08x\n", regnames[reg], val); ++ writel(val, ast_jtag->reg_base + reg); ++} ++ ++static void ast_jtag_set_tck(struct ast_jtag_info *ast_jtag, ++ enum xfer_mode mode, uint tck) ++{ ++ u32 read_value; ++ ++ if (tck == 0) ++ tck = 1; ++ else if (tck > JTAG_TCK_DIVISOR_MASK) ++ tck = JTAG_TCK_DIVISOR_MASK; ++ read_value = ast_jtag_read(ast_jtag, AST_JTAG_TCK); ++ ast_jtag_write(ast_jtag, ++ ((read_value & ~JTAG_TCK_DIVISOR_MASK) | tck), ++ AST_JTAG_TCK); ++} ++ ++static void ast_jtag_get_tck(struct ast_jtag_info *ast_jtag, ++ enum xfer_mode mode, uint *tck) ++{ ++ *tck = FIELD_GET(JTAG_TCK_DIVISOR_MASK, ++ ast_jtag_read(ast_jtag, AST_JTAG_TCK)); ++} ++ ++/* ++ * Used only in SW mode to walk the JTAG state machine. ++ */ ++static u8 tck_cycle(struct ast_jtag_info *ast_jtag, u8 TMS, u8 TDI, ++ bool do_read) ++{ ++ u8 result = 0; ++ u32 regwriteval = JTAG_SW_MODE_EN | (TMS * JTAG_SW_MODE_TMS) ++ | (TDI * JTAG_SW_MODE_TDIO); ++ ++ /* TCK = 0 */ ++ ast_jtag_write(ast_jtag, regwriteval, AST_JTAG_SW); ++ ++ ast_jtag_read(ast_jtag, AST_JTAG_SW); ++ ++ /* TCK = 1 */ ++ ast_jtag_write(ast_jtag, JTAG_SW_MODE_TCK | regwriteval, AST_JTAG_SW); ++ ++ if (do_read) { ++ result = (ast_jtag_read(ast_jtag, AST_JTAG_SW) ++ & JTAG_SW_MODE_TDIO) ? 1 : 0; ++ } ++ return result; ++} ++ ++#define WAIT_ITERATIONS 75 ++ ++static int ast_jtag_wait_instr_pause_complete(struct ast_jtag_info *ast_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(ast_jtag->jtag_wq, ++ (ast_jtag->flag == JTAG_INST_PAUSE)); ++ ast_jtag->flag = 0; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & JTAG_INST_PAUSE) == 0) { ++ status = ast_jtag_read(ast_jtag, AST_JTAG_ISR); ++ dev_dbg(ast_jtag->dev, "%s = 0x%08x\n", __func__, status); ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(ast_jtag->dev, ++ "ast_jtag driver timed out waiting for instruction pause complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & JTAG_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ ast_jtag_write(ast_jtag, JTAG_INST_PAUSE | (status & 0xf), ++ AST_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static int ast_jtag_wait_instr_complete(struct ast_jtag_info *ast_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(ast_jtag->jtag_wq, ++ (ast_jtag->flag == JTAG_INST_COMPLETE)); ++ ast_jtag->flag = 0; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & JTAG_INST_COMPLETE) == 0) { ++ status = ast_jtag_read(ast_jtag, AST_JTAG_ISR); ++ dev_dbg(ast_jtag->dev, "%s = 0x%08x\n", __func__, status); ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(ast_jtag->dev, ++ "ast_jtag driver timed out waiting for instruction complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & JTAG_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ ast_jtag_write(ast_jtag, JTAG_INST_COMPLETE | (status & 0xf), ++ AST_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static int ast_jtag_wait_data_pause_complete(struct ast_jtag_info *ast_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(ast_jtag->jtag_wq, ++ (ast_jtag->flag == JTAG_DATA_PAUSE)); ++ ast_jtag->flag = 0; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & JTAG_DATA_PAUSE) == 0) { ++ status = ast_jtag_read(ast_jtag, AST_JTAG_ISR); ++ dev_dbg(ast_jtag->dev, "%s = 0x%08x\n", __func__, status); ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(ast_jtag->dev, ++ "ast_jtag driver timed out waiting for data pause complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & JTAG_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ ast_jtag_write(ast_jtag, JTAG_DATA_PAUSE | (status & 0xf), ++ AST_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static int ast_jtag_wait_data_complete(struct ast_jtag_info *ast_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(ast_jtag->jtag_wq, ++ (ast_jtag->flag == JTAG_DATA_COMPLETE)); ++ ast_jtag->flag = 0; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & JTAG_DATA_COMPLETE) == 0) { ++ status = ast_jtag_read(ast_jtag, AST_JTAG_ISR); ++ dev_dbg(ast_jtag->dev, "%s = 0x%08x\n", __func__, status); ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(ast_jtag->dev, ++ "ast_jtag driver timed out waiting for data complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & JTAG_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ ast_jtag_write(ast_jtag, ++ JTAG_DATA_COMPLETE | (status & 0xf), ++ AST_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static void ast_jtag_bitbang(struct ast_jtag_info *ast_jtag, ++ struct tck_bitbang *bit_bang) ++{ ++ bit_bang->tdo = tck_cycle(ast_jtag, bit_bang->tms, bit_bang->tdi, true); ++} ++ ++static void reset_tap(struct ast_jtag_info *ast_jtag, enum xfer_mode mode) ++{ ++ unsigned char i; ++ ++ if (mode == SW_MODE) { ++ for (i = 0; i < 9; i++) ++ tck_cycle(ast_jtag, 1, 0, false); ++ } else { ++ ast_jtag_write(ast_jtag, 0, AST_JTAG_SW); ++ mdelay(1); ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_FORCE_TMS, ++ AST_JTAG_CTRL); ++ mdelay(1); ++ ast_jtag_write(ast_jtag, ++ JTAG_SW_MODE_EN | JTAG_SW_MODE_TDIO, ++ AST_JTAG_SW); ++ } ++} ++ ++static int ast_jtag_set_tapstate(struct ast_jtag_info *ast_jtag, ++ enum xfer_mode mode, uint from, uint to) ++{ ++ unsigned char num_cycles; ++ unsigned char cycle; ++ unsigned char tms_bits; ++ ++ /* ++ * Ensure that the requested and current tap states are within ++ * 0 to 15. ++ */ ++ if (from >= ARRAY_SIZE(_tms_cycle_lookup[0]) || /* Column */ ++ to >= ARRAY_SIZE(_tms_cycle_lookup)) { /* row */ ++ return -1; ++ } ++ ++ dev_dbg(ast_jtag->dev, "Set TAP state: %s\n", c_statestr[to]); ++ ++ if (mode == SW_MODE) { ++ ast_jtag_write(ast_jtag, ++ JTAG_SW_MODE_EN | JTAG_SW_MODE_TDIO, ++ AST_JTAG_SW); ++ ++ if (to == jtag_tlr) { ++ reset_tap(ast_jtag, mode); ++ } else { ++ tms_bits = _tms_cycle_lookup[from][to].tmsbits; ++ num_cycles = _tms_cycle_lookup[from][to].count; ++ ++ if (num_cycles == 0) ++ return 0; ++ ++ for (cycle = 0; cycle < num_cycles; cycle++) { ++ tck_cycle(ast_jtag, (tms_bits & 1), 0, false); ++ tms_bits >>= 1; ++ } ++ } ++ } else if (to == jtag_tlr) { ++ reset_tap(ast_jtag, mode); ++ } ++ return 0; ++} ++ ++static void software_readwrite_scan(struct ast_jtag_info *ast_jtag, ++ struct scan_xfer *scan_xfer) ++{ ++ uint bit_index = 0; ++ bool is_IR = (scan_xfer->tap_state == jtag_shf_ir); ++ uint exit_tap_state = is_IR ? jtag_ex1_ir : jtag_ex1_dr; ++ unsigned char *tdi = scan_xfer->tdi; ++ unsigned char *tdo = scan_xfer->tdo; ++ ++ dev_dbg(ast_jtag->dev, "SW JTAG SHIFT %s, length = %d\n", ++ is_IR ? "IR" : "DR", scan_xfer->length); ++ ++ ast_jtag_write(ast_jtag, ++ JTAG_SW_MODE_EN | JTAG_SW_MODE_TDIO, ++ AST_JTAG_SW); ++ ++ while (bit_index < scan_xfer->length) { ++ int bit_offset = (bit_index % 8); ++ int this_input_bit = 0; ++ int tms_high_or_low; ++ int this_output_bit; ++ ++ if (bit_index / 8 < scan_xfer->tdi_bytes) { ++ /* ++ * If we are on a byte boundary, increment the byte ++ * pointers. Don't increment on 0, pointer is already ++ * on the first byte. ++ */ ++ if (bit_index % 8 == 0 && bit_index != 0) ++ tdi++; ++ this_input_bit = (*tdi >> bit_offset) & 1; ++ } ++ /* If this is the last bit, leave TMS high */ ++ tms_high_or_low = (bit_index == scan_xfer->length - 1) && ++ (scan_xfer->end_tap_state != jtag_shf_dr) && ++ (scan_xfer->end_tap_state != jtag_shf_ir); ++ this_output_bit = tck_cycle(ast_jtag, tms_high_or_low, ++ this_input_bit, !!tdo); ++ /* ++ * If it was the last bit in the scan and the end_tap_state is ++ * something other than shiftDR or shiftIR then go to Exit1. ++ * IMPORTANT Note: if the end_tap_state is ShiftIR/DR and ++ * the next call to this function is a shiftDR/IR then the ++ * driver will not change state! ++ */ ++ if (tms_high_or_low) ++ scan_xfer->tap_state = exit_tap_state; ++ if (tdo && bit_index / 8 < scan_xfer->tdo_bytes) { ++ if (bit_index % 8 == 0) { ++ if (bit_index != 0) ++ tdo++; ++ *tdo = 0; ++ } ++ *tdo |= this_output_bit << bit_offset; ++ } ++ bit_index++; ++ } ++ ast_jtag_set_tapstate(ast_jtag, scan_xfer->mode, scan_xfer->tap_state, ++ scan_xfer->end_tap_state); ++} ++ ++static int fire_ir_command(struct ast_jtag_info *ast_jtag, bool last, ++ u32 length) ++{ ++ int res; ++ ++ if (last) { ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_LAST_INST ++ | FIELD_PREP(JTAG_INST_LEN_MASK, length), ++ AST_JTAG_CTRL); ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_LAST_INST ++ | FIELD_PREP(JTAG_INST_LEN_MASK, length) ++ | JTAG_INST_EN, ++ AST_JTAG_CTRL); ++ res = ast_jtag_wait_instr_complete(ast_jtag); ++ } else { ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_IR_UPDATE ++ | FIELD_PREP(JTAG_INST_LEN_MASK, length), ++ AST_JTAG_CTRL); ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_IR_UPDATE ++ | FIELD_PREP(JTAG_INST_LEN_MASK, length) ++ | JTAG_INST_EN, ++ AST_JTAG_CTRL); ++ res = ast_jtag_wait_instr_pause_complete(ast_jtag); ++ } ++ return res; ++} ++ ++static int fire_dr_command(struct ast_jtag_info *ast_jtag, bool last, ++ u32 length) ++{ ++ int res; ++ ++ if (last) { ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_LAST_DATA ++ | FIELD_PREP(JTAG_DATA_LEN_MASK, length), ++ AST_JTAG_CTRL); ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_LAST_DATA ++ | FIELD_PREP(JTAG_DATA_LEN_MASK, length) ++ | JTAG_DATA_EN, ++ AST_JTAG_CTRL); ++ res = ast_jtag_wait_data_complete(ast_jtag); ++ } else { ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_DR_UPDATE ++ | FIELD_PREP(JTAG_DATA_LEN_MASK, length), ++ AST_JTAG_CTRL); ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN | JTAG_DR_UPDATE ++ | FIELD_PREP(JTAG_DATA_LEN_MASK, length) ++ | JTAG_DATA_EN, ++ AST_JTAG_CTRL); ++ res = ast_jtag_wait_data_pause_complete(ast_jtag); ++ } ++ return res; ++} ++ ++static int hardware_readwrite_scan(struct ast_jtag_info *ast_jtag, ++ struct scan_xfer *scan_xfer) ++{ ++ int res = 0; ++ u32 bits_received = 0; ++ u32 bits_to_send = 0; ++ u32 chunk_len = 0; ++ bool is_IR = (scan_xfer->tap_state == jtag_shf_ir); ++ bool is_last = false; ++ u32 length = scan_xfer->length; ++ u32 *tdi = (u32 *)scan_xfer->tdi; ++ u32 *tdo = (u32 *)scan_xfer->tdo; ++ u32 remaining_bytes; ++ int scan_end = 0; ++ u32 ast_reg = is_IR ? AST_JTAG_INST : AST_JTAG_DATA; ++ ++ dev_dbg(ast_jtag->dev, "HW JTAG SHIFT %s, length = %d\n", ++ is_IR ? "IR" : "DR", length); ++ ++ ast_jtag_write(ast_jtag, 0, AST_JTAG_SW); ++ if (scan_xfer->end_tap_state == jtag_pau_dr || ++ scan_xfer->end_tap_state == jtag_pau_ir || ++ scan_xfer->end_tap_state == jtag_shf_dr || ++ scan_xfer->end_tap_state == jtag_shf_ir) { ++ scan_end = 0; ++ } else { ++ scan_end = 1; ++ } ++ ++ while (length > 0) { ++ chunk_len = (length > 32) ? 32 : length; ++ ++ if (length <= 32 && scan_end == 1) ++ is_last = true; ++ ++ dev_dbg(ast_jtag->dev, "HW SHIFT, length=%d, scan_end=%d, chunk_len=%d, is_last=%d\n", ++ length, scan_end, chunk_len, is_last); ++ ++ remaining_bytes = (scan_xfer->length - length) / 8; ++ if (tdi && remaining_bytes < scan_xfer->tdi_bytes) { ++ bits_to_send = *tdi++; ++ ast_jtag_write(ast_jtag, bits_to_send, ast_reg); ++ } else { ++ bits_to_send = 0; ++ ast_jtag_write(ast_jtag, 0, ast_reg); ++ } ++ ++ dev_dbg(ast_jtag->dev, "HW SHIFT, len=%d chunk_len=%d is_last=%x bits_to_send=%x\n", ++ length, chunk_len, is_last, bits_to_send); ++ ++ if (is_IR) ++ res = fire_ir_command(ast_jtag, is_last, chunk_len); ++ else ++ res = fire_dr_command(ast_jtag, is_last, chunk_len); ++ if (res != 0) ++ break; ++ ++ if (tdo) { ++ bits_received = ast_jtag_read(ast_jtag, ast_reg); ++ bits_received >>= (32 - chunk_len); ++ *tdo++ = bits_received; ++ } ++ dev_dbg(ast_jtag->dev, ++ "HW SHIFT, len=%d chunk_len=%d is_last=%x bits_received=%x\n", ++ length, chunk_len, is_last, ++ bits_received); ++ length -= chunk_len; ++ } ++ return res; ++} ++ ++static int ast_jtag_readwrite_scan(struct ast_jtag_info *ast_jtag, ++ struct scan_xfer *scan_xfer) ++{ ++ int res = 0; ++ ++ if (scan_xfer->tap_state != jtag_shf_dr && ++ scan_xfer->tap_state != jtag_shf_ir) { ++ if (scan_xfer->tap_state < ARRAY_SIZE(c_statestr)) ++ dev_err(ast_jtag->dev, ++ "readwrite_scan bad current tap state = %s\n", ++ c_statestr[scan_xfer->tap_state]); ++ else ++ dev_err(ast_jtag->dev, ++ "readwrite_scan bad current tap state = %u\n", ++ scan_xfer->tap_state); ++ return -EFAULT; ++ } ++ ++ if (scan_xfer->length == 0) { ++ dev_err(ast_jtag->dev, "readwrite_scan bad length 0\n"); ++ return -EFAULT; ++ } ++ ++ if (!scan_xfer->tdi && scan_xfer->tdi_bytes != 0) { ++ dev_err(ast_jtag->dev, ++ "readwrite_scan null tdi with non-zero length %u!\n", ++ scan_xfer->tdi_bytes); ++ return -EFAULT; ++ } ++ ++ if (!scan_xfer->tdo && scan_xfer->tdo_bytes != 0) { ++ dev_err(ast_jtag->dev, ++ "readwrite_scan null tdo with non-zero length %u!\n", ++ scan_xfer->tdo_bytes); ++ return -EFAULT; ++ } ++ ++ if (!scan_xfer->tdi && !scan_xfer->tdo) { ++ dev_err(ast_jtag->dev, "readwrite_scan null tdo and tdi!\n"); ++ return -EFAULT; ++ } ++ ++ if (scan_xfer->mode == SW_MODE) ++ software_readwrite_scan(ast_jtag, scan_xfer); ++ else ++ res = hardware_readwrite_scan(ast_jtag, scan_xfer); ++ return res; ++} ++ ++#ifdef USE_INTERRUPTS ++static irqreturn_t ast_jtag_interrupt(int this_irq, void *dev_id) ++{ ++ u32 status; ++ struct ast_jtag_info *ast_jtag = dev_id; ++ ++ status = ast_jtag_read(ast_jtag, AST_JTAG_ISR); ++ ++ if (status & JTAG_INST_PAUSE) { ++ ast_jtag_write(ast_jtag, ++ JTAG_INST_PAUSE | (status & 0xf), ++ AST_JTAG_ISR); ++ ast_jtag->flag = JTAG_INST_PAUSE; ++ } ++ ++ if (status & JTAG_INST_COMPLETE) { ++ ast_jtag_write(ast_jtag, ++ JTAG_INST_COMPLETE | (status & 0xf), ++ AST_JTAG_ISR); ++ ast_jtag->flag = JTAG_INST_COMPLETE; ++ } ++ ++ if (status & JTAG_DATA_PAUSE) { ++ ast_jtag_write(ast_jtag, ++ JTAG_DATA_PAUSE | (status & 0xf), AST_JTAG_ISR); ++ ast_jtag->flag = JTAG_DATA_PAUSE; ++ } ++ ++ if (status & JTAG_DATA_COMPLETE) { ++ ast_jtag_write(ast_jtag, ++ JTAG_DATA_COMPLETE | (status & 0xf), ++ AST_JTAG_ISR); ++ ast_jtag->flag = JTAG_DATA_COMPLETE; ++ } ++ ++ if (ast_jtag->flag) { ++ wake_up_interruptible(&ast_jtag->jtag_wq); ++ return IRQ_HANDLED; ++ } else { ++ return IRQ_NONE; ++ } ++} ++#endif ++ ++static inline void ast_jtag_slave(struct ast_jtag_info *ast_jtag) ++{ ++ u32 currReg = readl((void *)(ast_jtag->reg_base_scu)); ++ ++ writel(currReg | SCU_RESET_JTAG, (void *)ast_jtag->reg_base_scu); ++} ++ ++static inline void ast_jtag_master(struct ast_jtag_info *ast_jtag) ++{ ++ u32 currReg = readl((void *)(ast_jtag->reg_base_scu)); ++ ++ writel(currReg & ~SCU_RESET_JTAG, (void *)ast_jtag->reg_base_scu); ++ ast_jtag_write(ast_jtag, JTAG_ENGINE_EN, AST_JTAG_CTRL); ++ ast_jtag_write(ast_jtag, JTAG_SW_MODE_EN | JTAG_SW_MODE_TDIO, ++ AST_JTAG_SW); ++ ast_jtag_write(ast_jtag, JTAG_INST_PAUSE | JTAG_INST_COMPLETE | ++ JTAG_DATA_PAUSE | JTAG_DATA_COMPLETE | ++ JTAG_INST_PAUSE_EN | JTAG_INST_COMPLETE_EN | ++ JTAG_DATA_PAUSE_EN | JTAG_DATA_COMPLETE_EN, ++ AST_JTAG_ISR); /* Enable Interrupt */ ++} ++ ++static long jtag_ioctl(struct file *file, uint cmd, ulong arg) ++{ ++ int ret = 0; ++ struct ast_jtag_info *ast_jtag = file->private_data; ++ void __user *argp = (void __user *)arg; ++ struct tck_bitbang bitbang; ++ struct scan_xfer xfer; ++ struct set_tck_param set_tck_param; ++ struct get_tck_param get_tck_param; ++ struct tap_state_param tap_state_param; ++ unsigned char *kern_tdi = NULL; ++ unsigned char *kern_tdo = NULL; ++ unsigned char *user_tdi; ++ unsigned char *user_tdo; ++ ++ switch (cmd) { ++ case AST_JTAG_SET_TCK: ++ if (copy_from_user(&set_tck_param, argp, ++ sizeof(struct set_tck_param))) { ++ ret = -EFAULT; ++ } else { ++ ast_jtag_set_tck(ast_jtag, ++ set_tck_param.mode, ++ set_tck_param.tck); ++ } ++ break; ++ case AST_JTAG_GET_TCK: ++ if (copy_from_user(&get_tck_param, argp, ++ sizeof(struct get_tck_param))) ++ ret = -EFAULT; ++ else ++ ast_jtag_get_tck(ast_jtag, ++ get_tck_param.mode, ++ &get_tck_param.tck); ++ if (copy_to_user(argp, &get_tck_param, ++ sizeof(struct get_tck_param))) ++ ret = -EFAULT; ++ break; ++ case AST_JTAG_BITBANG: ++ if (copy_from_user(&bitbang, argp, ++ sizeof(struct tck_bitbang))) { ++ ret = -EFAULT; ++ } else { ++ if (bitbang.tms > 1 || bitbang.tdi > 1) ++ ret = -EFAULT; ++ else ++ ast_jtag_bitbang(ast_jtag, &bitbang); ++ } ++ if (copy_to_user(argp, &bitbang, sizeof(struct tck_bitbang))) ++ ret = -EFAULT; ++ break; ++ case AST_JTAG_SET_TAPSTATE: ++ if (copy_from_user(&tap_state_param, argp, ++ sizeof(struct tap_state_param))) ++ ret = -EFAULT; ++ else ++ ast_jtag_set_tapstate(ast_jtag, tap_state_param.mode, ++ tap_state_param.from_state, ++ tap_state_param.to_state); ++ break; ++ case AST_JTAG_READWRITESCAN: ++ if (copy_from_user(&xfer, argp, ++ sizeof(struct scan_xfer))) { ++ ret = -EFAULT; ++ } else { ++ if (xfer.tdi) { ++ user_tdi = xfer.tdi; ++ kern_tdi = memdup_user(user_tdi, ++ xfer.tdi_bytes); ++ if (IS_ERR(kern_tdi)) ++ ret = -EFAULT; ++ else ++ xfer.tdi = kern_tdi; ++ } ++ ++ if (ret == 0 && xfer.tdo) { ++ user_tdo = xfer.tdo; ++ kern_tdo = memdup_user(user_tdo, ++ xfer.tdo_bytes); ++ if (IS_ERR(kern_tdo)) ++ ret = -EFAULT; ++ else ++ xfer.tdo = kern_tdo; ++ } ++ ++ if (ret == 0) ++ ret = ast_jtag_readwrite_scan(ast_jtag, &xfer); ++ ++ kfree(kern_tdi); ++ if (kern_tdo) { ++ if (ret == 0) { ++ if (copy_to_user(user_tdo, ++ xfer.tdo, ++ xfer.tdo_bytes)) ++ ret = -EFAULT; ++ } ++ kfree(kern_tdo); ++ } ++ } ++ break; ++ default: ++ return -ENOTTY; ++ } ++ ++ return ret; ++} ++ ++static int jtag_open(struct inode *inode, struct file *file) ++{ ++ struct ast_jtag_info *ast_jtag = container_of(file->private_data, ++ struct ast_jtag_info, ++ miscdev); ++ ++ spin_lock(&jtag_state_lock); ++ if (ast_jtag->is_open) { ++ spin_unlock(&jtag_state_lock); ++ return -EBUSY; ++ } ++ ++ ast_jtag->is_open = true; ++ file->private_data = ast_jtag; ++ ast_jtag_master(ast_jtag); ++ spin_unlock(&jtag_state_lock); ++ ++ return 0; ++} ++ ++static int jtag_release(struct inode *inode, struct file *file) ++{ ++ struct ast_jtag_info *ast_jtag = file->private_data; ++ ++ spin_lock(&jtag_state_lock); ++ ast_jtag_slave(ast_jtag); ++ ast_jtag->is_open = false; ++ ++ spin_unlock(&jtag_state_lock); ++ ++ return 0; ++} ++ ++static const struct file_operations ast_jtag_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = jtag_ioctl, ++ .open = jtag_open, ++ .release = jtag_release, ++}; ++ ++static int ast_jtag_probe(struct platform_device *pdev) ++{ ++ struct resource *scu_res; ++ struct resource *jtag_res; ++ int ret = 0; ++ struct ast_jtag_info *ast_jtag; ++ ++ dev_dbg(&pdev->dev, "%s started\n", __func__); ++ ++ scu_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!scu_res) { ++ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM for SCU\n"); ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ jtag_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ++ if (!jtag_res) { ++ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM for JTAG\n"); ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ ast_jtag = devm_kzalloc(&pdev->dev, sizeof(*ast_jtag), GFP_KERNEL); ++ if (!ast_jtag) ++ return -ENOMEM; ++ ++ ast_jtag->reg_base_scu = devm_ioremap_resource(&pdev->dev, scu_res); ++ if (!ast_jtag->reg_base_scu) { ++ ret = -EIO; ++ goto out; ++ } ++ ++ ast_jtag->reg_base = devm_ioremap_resource(&pdev->dev, jtag_res); ++ if (!ast_jtag->reg_base) { ++ ret = -EIO; ++ goto out; ++ } ++ ++ ast_jtag->dev = &pdev->dev; ++ ++#ifdef USE_INTERRUPTS ++ ast_jtag->irq = platform_get_irq(pdev, 0); ++ if (ast_jtag->irq < 0) { ++ dev_err(&pdev->dev, "no irq specified.\n"); ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, ast_jtag->irq, ast_jtag_interrupt, ++ IRQF_SHARED, "ast-jtag", ast_jtag); ++ if (ret) { ++ dev_err(ast_jtag->dev, "JTAG Unable to get IRQ.\n"); ++ goto out; ++ } ++#endif ++ ++ ast_jtag->flag = 0; ++ init_waitqueue_head(&ast_jtag->jtag_wq); ++ ++ ast_jtag->miscdev.minor = MISC_DYNAMIC_MINOR, ++ ast_jtag->miscdev.name = AST_JTAG_NAME, ++ ast_jtag->miscdev.fops = &ast_jtag_fops, ++ ast_jtag->miscdev.parent = &pdev->dev; ++ ret = misc_register(&ast_jtag->miscdev); ++ if (ret) { ++ dev_err(ast_jtag->dev, "Unable to register misc device.\n"); ++ goto out; ++ } ++ ++ platform_set_drvdata(pdev, ast_jtag); ++ ++ ast_jtag_slave(ast_jtag); ++ ++ dev_dbg(&pdev->dev, "%s completed\n", __func__); ++ return 0; ++ ++out: ++ dev_warn(&pdev->dev, "ast_jtag: driver init failed (ret=%d).\n", ret); ++ return ret; ++} ++ ++static int ast_jtag_remove(struct platform_device *pdev) ++{ ++ dev_dbg(&pdev->dev, "%s\n", __func__); ++ ++ platform_set_drvdata(pdev, NULL); ++ ++ dev_dbg(&pdev->dev, "JTAG driver removed successfully.\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id ast_jtag_of_match[] = { ++ { .compatible = "aspeed,ast2400-jtag", }, ++ { .compatible = "aspeed,ast2500-jtag", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, ast_jtag_of_match); ++ ++static struct platform_driver ast_jtag_driver = { ++ .probe = ast_jtag_probe, ++ .remove = ast_jtag_remove, ++ .driver = { ++ .name = AST_JTAG_NAME, ++ .of_match_table = of_match_ptr(ast_jtag_of_match), ++ }, ++}; ++module_platform_driver(ast_jtag_driver); ++ ++MODULE_AUTHOR("Ryan Chen "); ++MODULE_AUTHOR("Bryan Hunt "); ++MODULE_DESCRIPTION("ASPEED JTAG driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/uapi/linux/jtag_drv.h b/include/uapi/linux/jtag_drv.h +new file mode 100644 +index 000000000000..4df638f8fa43 +--- /dev/null ++++ b/include/uapi/linux/jtag_drv.h +@@ -0,0 +1,73 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2012-2017 ASPEED Technology Inc. */ ++/* Copyright (c) 2018 Intel Corporation */ ++ ++#ifndef __JTAG_DRV_H__ ++#define __JTAG_DRV_H__ ++ ++enum xfer_mode { ++ HW_MODE = 0, ++ SW_MODE ++} xfer_mode; ++ ++struct tck_bitbang { ++ __u8 tms; ++ __u8 tdi; ++ __u8 tdo; ++} __attribute__((__packed__)); ++ ++struct scan_xfer { ++ __u8 mode; ++ __u32 tap_state; ++ __u32 length; ++ __u8 *tdi; ++ __u32 tdi_bytes; ++ __u8 *tdo; ++ __u32 tdo_bytes; ++ __u32 end_tap_state; ++} __attribute__((__packed__)); ++ ++struct set_tck_param { ++ __u8 mode; ++ __u32 tck; ++} __attribute__((__packed__)); ++ ++struct get_tck_param { ++ __u8 mode; ++ __u32 tck; ++} __attribute__((__packed__)); ++ ++struct tap_state_param { ++ __u8 mode; ++ __u32 from_state; ++ __u32 to_state; ++} __attribute__((__packed__)); ++ ++enum jtag_states { ++ jtag_tlr, ++ jtag_rti, ++ jtag_sel_dr, ++ jtag_cap_dr, ++ jtag_shf_dr, ++ jtag_ex1_dr, ++ jtag_pau_dr, ++ jtag_ex2_dr, ++ jtag_upd_dr, ++ jtag_sel_ir, ++ jtag_cap_ir, ++ jtag_shf_ir, ++ jtag_ex1_ir, ++ jtag_pau_ir, ++ jtag_ex2_ir, ++ jtag_upd_ir ++} jtag_states; ++ ++#define JTAGIOC_BASE 'T' ++ ++#define AST_JTAG_SET_TCK _IOW(JTAGIOC_BASE, 3, struct set_tck_param) ++#define AST_JTAG_GET_TCK _IOR(JTAGIOC_BASE, 4, struct get_tck_param) ++#define AST_JTAG_BITBANG _IOWR(JTAGIOC_BASE, 5, struct tck_bitbang) ++#define AST_JTAG_SET_TAPSTATE _IOW(JTAGIOC_BASE, 6, struct tap_state_param) ++#define AST_JTAG_READWRITESCAN _IOWR(JTAGIOC_BASE, 7, struct scan_xfer) ++ ++#endif +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0029-i2c-aspeed-Improve-driver-to-support-multi-master-us.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0029-i2c-aspeed-Improve-driver-to-support-multi-master-us.patch new file mode 100644 index 000000000..e2dee0d5b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0029-i2c-aspeed-Improve-driver-to-support-multi-master-us.patch @@ -0,0 +1,291 @@ +From a7ad8d09cdf0ec86612df0714d3e69ee92e6140b Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Tue, 20 Nov 2018 09:30:17 -0800 +Subject: [PATCH] i2c: aspeed: Improve driver to support multi-master use cases + stably + +In multi-master environment, this driver's master cannot know +exactly when peer master sends data to this driver's slave so +cases can be happened that this master tries to send data through +the master_xfer function but slave data from a peer master is still +being processed or slave xfer is started by a peer very after it +queues a master command. + +To prevent state corruption in these cases, this patch adds the +'pending' state of master and its handling code so that the pending +master xfer can be continued after slave mode session. + +Signed-off-by: Jae Hyun Yoo +--- + drivers/i2c/busses/i2c-aspeed.c | 119 ++++++++++++++++++++++++++++++---------- + 1 file changed, 91 insertions(+), 28 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index 8dc9161ced38..d11b2ea97259 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -117,6 +117,7 @@ + + enum aspeed_i2c_master_state { + ASPEED_I2C_MASTER_INACTIVE, ++ ASPEED_I2C_MASTER_PENDING, + ASPEED_I2C_MASTER_START, + ASPEED_I2C_MASTER_TX_FIRST, + ASPEED_I2C_MASTER_TX, +@@ -126,12 +127,13 @@ enum aspeed_i2c_master_state { + }; + + enum aspeed_i2c_slave_state { +- ASPEED_I2C_SLAVE_STOP, ++ ASPEED_I2C_SLAVE_INACTIVE, + ASPEED_I2C_SLAVE_START, + ASPEED_I2C_SLAVE_READ_REQUESTED, + ASPEED_I2C_SLAVE_READ_PROCESSED, + ASPEED_I2C_SLAVE_WRITE_REQUESTED, + ASPEED_I2C_SLAVE_WRITE_RECEIVED, ++ ASPEED_I2C_SLAVE_STOP, + }; + + struct aspeed_i2c_bus { +@@ -156,6 +158,8 @@ struct aspeed_i2c_bus { + int cmd_err; + /* Protected only by i2c_lock_bus */ + int master_xfer_result; ++ /* Multi-master */ ++ bool multi_master; + #if IS_ENABLED(CONFIG_I2C_SLAVE) + struct i2c_client *slave; + enum aspeed_i2c_slave_state slave_state; +@@ -251,7 +255,7 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + } + + /* Slave is not currently active, irq was for someone else. */ +- if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) ++ if (bus->slave_state == ASPEED_I2C_SLAVE_INACTIVE) + return irq_handled; + + dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n", +@@ -277,16 +281,15 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + irq_handled |= ASPEED_I2CD_INTR_NORMAL_STOP; + bus->slave_state = ASPEED_I2C_SLAVE_STOP; + } +- if (irq_status & ASPEED_I2CD_INTR_TX_NAK) { ++ if (irq_status & ASPEED_I2CD_INTR_TX_NAK && ++ bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) { + irq_handled |= ASPEED_I2CD_INTR_TX_NAK; + bus->slave_state = ASPEED_I2C_SLAVE_STOP; + } +- if (irq_status & ASPEED_I2CD_INTR_TX_ACK) +- irq_handled |= ASPEED_I2CD_INTR_TX_ACK; + + switch (bus->slave_state) { + case ASPEED_I2C_SLAVE_READ_REQUESTED: +- if (irq_status & ASPEED_I2CD_INTR_TX_ACK) ++ if (unlikely(irq_status & ASPEED_I2CD_INTR_TX_ACK)) + dev_err(bus->dev, "Unexpected ACK on read request.\n"); + bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED; + i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value); +@@ -294,9 +297,12 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG); + break; + case ASPEED_I2C_SLAVE_READ_PROCESSED: +- if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK)) ++ if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_ACK))) { + dev_err(bus->dev, + "Expected ACK after processed read.\n"); ++ break; ++ } ++ irq_handled |= ASPEED_I2CD_INTR_TX_ACK; + i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value); + writel(value, bus->base + ASPEED_I2C_BYTE_BUF_REG); + writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG); +@@ -310,10 +316,15 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + break; + case ASPEED_I2C_SLAVE_STOP: + i2c_slave_event(slave, I2C_SLAVE_STOP, &value); ++ bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE; ++ break; ++ case ASPEED_I2C_SLAVE_START: ++ /* Slave was just started. Waiting for the next event. */; + break; + default: +- dev_err(bus->dev, "unhandled slave_state: %d\n", ++ dev_err(bus->dev, "unknown slave_state: %d\n", + bus->slave_state); ++ bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE; + break; + } + +@@ -328,7 +339,17 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus) + struct i2c_msg *msg = &bus->msgs[bus->msgs_index]; + u8 slave_addr = i2c_8bit_addr_from_msg(msg); + +- bus->master_state = ASPEED_I2C_MASTER_START; ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ /* ++ * If it's requested in the middle of a slave session, set the master ++ * state to 'pending' then H/W will continue handling this master ++ * command when the bus comes back to idle state. ++ */ ++ if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) ++ bus->master_state = ASPEED_I2C_MASTER_PENDING; ++ else ++#endif /* CONFIG_I2C_SLAVE */ ++ bus->master_state = ASPEED_I2C_MASTER_START; + bus->buf_index = 0; + + if (msg->flags & I2C_M_RD) { +@@ -384,10 +405,6 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + bus->master_state = ASPEED_I2C_MASTER_INACTIVE; + irq_handled |= ASPEED_I2CD_INTR_BUS_RECOVER_DONE; + goto out_complete; +- } else { +- /* Master is not currently active, irq was for someone else. */ +- if (bus->master_state == ASPEED_I2C_MASTER_INACTIVE) +- goto out_no_complete; + } + + /* +@@ -399,12 +416,33 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + if (ret) { + dev_dbg(bus->dev, "received error interrupt: 0x%08x\n", + irq_status); +- bus->cmd_err = ret; +- bus->master_state = ASPEED_I2C_MASTER_INACTIVE; + irq_handled |= (irq_status & ASPEED_I2CD_INTR_MASTER_ERRORS); +- goto out_complete; ++ if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) { ++ bus->cmd_err = ret; ++ bus->master_state = ASPEED_I2C_MASTER_INACTIVE; ++ goto out_complete; ++ } + } + ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ /* ++ * A pending master command will be started by H/W when the bus comes ++ * back to idle state after completing a slave operation so change the ++ * master state from 'pending' to 'start' at here if slave is inactive. ++ */ ++ if (bus->master_state == ASPEED_I2C_MASTER_PENDING) { ++ if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) ++ goto out_no_complete; ++ ++ bus->master_state = ASPEED_I2C_MASTER_START; ++ } ++#endif /* CONFIG_I2C_SLAVE */ ++ ++ /* Master is not currently active, irq was for someone else. */ ++ if (bus->master_state == ASPEED_I2C_MASTER_INACTIVE || ++ bus->master_state == ASPEED_I2C_MASTER_PENDING) ++ goto out_no_complete; ++ + /* We are in an invalid state; reset bus to a known state. */ + if (!bus->msgs) { + dev_err(bus->dev, "bus in unknown state. irq_status: 0x%x\n", +@@ -423,6 +461,20 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + * then update the state and handle the new state below. + */ + if (bus->master_state == ASPEED_I2C_MASTER_START) { ++#if IS_ENABLED(CONFIG_I2C_SLAVE) ++ /* ++ * If a peer master starts a xfer very after it queues a master ++ * command, change its state to 'pending' then H/W will continue ++ * the queued master xfer just after completing the slave mode ++ * session. ++ */ ++ if (unlikely(irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH)) { ++ bus->master_state = ASPEED_I2C_MASTER_PENDING; ++ dev_dbg(bus->dev, ++ "master goes pending due to a slave start\n"); ++ goto out_no_complete; ++ } ++#endif /* CONFIG_I2C_SLAVE */ + if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_ACK))) { + if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_NAK))) { + bus->cmd_err = -ENXIO; +@@ -566,7 +618,8 @@ static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id) + * interrupt bits. Each case needs to be handled using corresponding + * handlers depending on the current state. + */ +- if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) { ++ if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE && ++ bus->master_state != ASPEED_I2C_MASTER_PENDING) { + irq_handled = aspeed_i2c_master_irq(bus, irq_remaining); + irq_remaining &= ~irq_handled; + if (irq_remaining) +@@ -601,15 +654,14 @@ static int aspeed_i2c_master_xfer(struct i2c_adapter *adap, + { + struct aspeed_i2c_bus *bus = i2c_get_adapdata(adap); + unsigned long time_left, flags; +- int ret = 0; ++ int ret; + + spin_lock_irqsave(&bus->lock, flags); + bus->cmd_err = 0; + +- /* If bus is busy, attempt recovery. We assume a single master +- * environment. +- */ +- if (readl(bus->base + ASPEED_I2C_CMD_REG) & ASPEED_I2CD_BUS_BUSY_STS) { ++ /* If bus is busy in a single master environment, attempt recovery. */ ++ if (!bus->multi_master && ++ (readl(bus->base + ASPEED_I2C_CMD_REG) & ASPEED_I2CD_BUS_BUSY_STS)) { + spin_unlock_irqrestore(&bus->lock, flags); + ret = aspeed_i2c_recover_bus(bus); + if (ret) +@@ -629,10 +681,20 @@ static int aspeed_i2c_master_xfer(struct i2c_adapter *adap, + time_left = wait_for_completion_timeout(&bus->cmd_complete, + bus->adap.timeout); + +- if (time_left == 0) ++ if (time_left == 0) { ++ /* ++ * If timed out and bus is still busy in a multi master ++ * environment, attempt recovery at here. ++ */ ++ if (bus->multi_master && ++ (readl(bus->base + ASPEED_I2C_CMD_REG) & ++ ASPEED_I2CD_BUS_BUSY_STS)) ++ ret = aspeed_i2c_recover_bus(bus); ++ + return -ETIMEDOUT; +- else +- return bus->master_xfer_result; ++ } ++ ++ return bus->master_xfer_result; + } + + static u32 aspeed_i2c_functionality(struct i2c_adapter *adap) +@@ -672,7 +734,7 @@ static int aspeed_i2c_reg_slave(struct i2c_client *client) + __aspeed_i2c_reg_slave(bus, client->addr); + + bus->slave = client; +- bus->slave_state = ASPEED_I2C_SLAVE_STOP; ++ bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE; + spin_unlock_irqrestore(&bus->lock, flags); + + return 0; +@@ -827,7 +889,9 @@ static int aspeed_i2c_init(struct aspeed_i2c_bus *bus, + if (ret < 0) + return ret; + +- if (!of_property_read_bool(pdev->dev.of_node, "multi-master")) ++ if (of_property_read_bool(pdev->dev.of_node, "multi-master")) ++ bus->multi_master = true; ++ else + fun_ctrl_reg |= ASPEED_I2CD_MULTI_MASTER_DIS; + + /* Enable Master Mode */ +@@ -930,7 +994,6 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + init_completion(&bus->cmd_complete); + bus->adap.owner = THIS_MODULE; + bus->adap.retries = 0; +- bus->adap.timeout = 5 * HZ; + bus->adap.algo = &aspeed_i2c_algo; + bus->adap.dev.parent = &pdev->dev; + bus->adap.dev.of_node = pdev->dev.of_node; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0030-Add-dump-debug-code-into-I2C-drivers.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0030-Add-dump-debug-code-into-I2C-drivers.patch new file mode 100644 index 000000000..b735ab38b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0030-Add-dump-debug-code-into-I2C-drivers.patch @@ -0,0 +1,151 @@ +From 577b65960842f4098cdfc85a311261477c051d84 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Fri, 29 Jun 2018 11:00:02 -0700 +Subject: [PATCH] Add dump debug code into I2C drivers + +This commit enables dump debug of master and slave I2C drivers. +This is only for downstream debug purpose so it shouldn't go to +the upstream. + +Usage (in case of bus 5 for an example): +echo 5 > /sys/module/i2c_aspeed/parameters/dump_debug_bus_id +echo 1 > /sys/module/i2c_aspeed/parameters/dump_debug +echo 5 > /sys/module/i2c_slave_mqueue/parameters/dump_debug_bus_id +echo 1 > /sys/module/i2c_slave_mqueue/parameters/dump_debug + +Signed-off-by: Jae Hyun Yoo +--- + drivers/i2c/busses/i2c-aspeed.c | 25 +++++++++++++++++++++++++ + drivers/i2c/i2c-slave-mqueue.c | 24 ++++++++++++++++++++++++ + 2 files changed, 49 insertions(+) + +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index d11b2ea97259..506d867b43d9 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -166,6 +166,19 @@ struct aspeed_i2c_bus { + #endif /* CONFIG_I2C_SLAVE */ + }; + ++static bool dump_debug __read_mostly; ++static int dump_debug_bus_id __read_mostly; ++ ++#define I2C_HEX_DUMP(bus, addr, flags, buf, len) \ ++ if (dump_debug && bus->adap.nr == dump_debug_bus_id) { \ ++ char dump_info[100] = {0,}; \ ++ snprintf(dump_info, sizeof(dump_info), \ ++ "%s (bus_id:%d, addr:0x%02x, flags:0x%02x): ", \ ++ __func__, bus->adap.nr, addr, flags); \ ++ print_hex_dump(KERN_ERR, dump_info, DUMP_PREFIX_NONE, 16, 1, \ ++ buf, len, true); \ ++ } ++ + static int aspeed_i2c_reset(struct aspeed_i2c_bus *bus); + + static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus) +@@ -655,6 +668,7 @@ static int aspeed_i2c_master_xfer(struct i2c_adapter *adap, + struct aspeed_i2c_bus *bus = i2c_get_adapdata(adap); + unsigned long time_left, flags; + int ret; ++ int i; + + spin_lock_irqsave(&bus->lock, flags); + bus->cmd_err = 0; +@@ -694,6 +708,11 @@ static int aspeed_i2c_master_xfer(struct i2c_adapter *adap, + return -ETIMEDOUT; + } + ++ for (i = 0; i < num; i++) { ++ I2C_HEX_DUMP(bus, msgs[i].addr, msgs[i].flags, ++ msgs[i].buf, msgs[i].len); ++ } ++ + return bus->master_xfer_result; + } + +@@ -1061,6 +1080,12 @@ static struct platform_driver aspeed_i2c_bus_driver = { + }; + module_platform_driver(aspeed_i2c_bus_driver); + ++module_param_named(dump_debug, dump_debug, bool, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(dump_debug, "debug flag for dump printing"); ++module_param_named(dump_debug_bus_id, dump_debug_bus_id, int, ++ S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(dump_debug_bus_id, "bus id for dump debug printing"); ++ + MODULE_AUTHOR("Brendan Higgins "); + MODULE_DESCRIPTION("Aspeed I2C Bus Driver"); + MODULE_LICENSE("GPL v2"); +diff --git a/drivers/i2c/i2c-slave-mqueue.c b/drivers/i2c/i2c-slave-mqueue.c +index 6014bca0ff2a..0140c0dc4c03 100644 +--- a/drivers/i2c/i2c-slave-mqueue.c ++++ b/drivers/i2c/i2c-slave-mqueue.c +@@ -21,6 +21,7 @@ struct mq_msg { + struct mq_queue { + struct bin_attribute bin; + struct kernfs_node *kn; ++ struct i2c_client *client; + + spinlock_t lock; /* spinlock for queue index handling */ + int in; +@@ -31,6 +32,19 @@ struct mq_queue { + struct mq_msg *queue; + }; + ++static bool dump_debug __read_mostly; ++static int dump_debug_bus_id __read_mostly; ++ ++#define I2C_HEX_DUMP(client, buf, len) \ ++ if (dump_debug && client->adapter->nr == dump_debug_bus_id) { \ ++ char dump_info[100] = {0,}; \ ++ snprintf(dump_info, sizeof(dump_info), \ ++ "%s (bus_id:%d, addr:0x%02x): ", \ ++ __func__, client->adapter->nr, client->addr); \ ++ print_hex_dump(KERN_ERR, dump_info, DUMP_PREFIX_NONE, 16, 1, \ ++ buf, len, true); \ ++ } ++ + static int i2c_slave_mqueue_callback(struct i2c_client *client, + enum i2c_slave_event event, u8 *val) + { +@@ -49,6 +63,7 @@ static int i2c_slave_mqueue_callback(struct i2c_client *client, + case I2C_SLAVE_WRITE_RECEIVED: + if (msg->len < MQ_MSGBUF_SIZE) { + msg->buf[msg->len++] = *val; ++ I2C_HEX_DUMP(client, val, 1); + } else { + dev_err(&client->dev, "message is truncated!\n"); + mq->truncated = 1; +@@ -101,6 +116,7 @@ static ssize_t i2c_slave_mqueue_bin_read(struct file *filp, + if (msg->len <= count) { + ret = msg->len; + memcpy(buf, msg->buf, ret); ++ I2C_HEX_DUMP(mq->client, buf, ret); + } else { + ret = -EOVERFLOW; /* Drop this HUGE one. */ + } +@@ -131,6 +147,8 @@ static int i2c_slave_mqueue_probe(struct i2c_client *client, + + BUILD_BUG_ON(!is_power_of_2(MQ_QUEUE_SIZE)); + ++ mq->client = client; ++ + buf = devm_kmalloc_array(dev, MQ_QUEUE_SIZE, MQ_MSGBUF_SIZE, + GFP_KERNEL); + if (!buf) +@@ -212,6 +230,12 @@ static struct i2c_driver i2c_slave_mqueue_driver = { + }; + module_i2c_driver(i2c_slave_mqueue_driver); + ++module_param_named(dump_debug, dump_debug, bool, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(dump_debug, "debug flag for dump printing"); ++module_param_named(dump_debug_bus_id, dump_debug_bus_id, int, ++ S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(dump_debug_bus_id, "bus id for dump debug printing"); ++ + MODULE_LICENSE("GPL v2"); + MODULE_AUTHOR("Haiyue Wang "); + MODULE_DESCRIPTION("I2C slave mode for receiving and queuing messages"); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0031-Add-high-speed-baud-rate-support-for-UART.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0031-Add-high-speed-baud-rate-support-for-UART.patch new file mode 100644 index 000000000..8c9d2dce0 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0031-Add-high-speed-baud-rate-support-for-UART.patch @@ -0,0 +1,132 @@ +From 7baa65c9bf638265874838401e27a7b6179559ff Mon Sep 17 00:00:00 2001 +From: Yong Li +Date: Wed, 2 Jan 2019 15:06:43 +0800 +Subject: [PATCH] Add high speed baud rate support for UART + +In order to support high speed baud rate(921600 bps), +the default UART clock(24MHz) needs to be switched +to 192MHz(from USB2.0 port1 PHY). + +Create a new 192M Hz clock and assign it to uart, +based on uart clock source configuration in SCU4C. + +bootloader(u-boot) will set SCU4C based on the environment configuration + +Signed-off-by: Yong Li +--- + drivers/clk/clk-aspeed.c | 41 +++++++++++++++++++++++++++----- + include/dt-bindings/clock/aspeed-clock.h | 2 ++ + 2 files changed, 37 insertions(+), 6 deletions(-) + +diff --git a/drivers/clk/clk-aspeed.c b/drivers/clk/clk-aspeed.c +index 5961367..3bbb4fb 100644 +--- a/drivers/clk/clk-aspeed.c ++++ b/drivers/clk/clk-aspeed.c +@@ -14,7 +14,9 @@ + + #include + +-#define ASPEED_NUM_CLKS 36 ++#define ASPEED_NUM_CLKS ASPEED_CLK_MAX ++#define UART_HIGH_SPEED_CLK 192000000 ++#define UART_LOW_SPEED_CLK 24000000 + + #define ASPEED_RESET2_OFFSET 32 + +@@ -28,6 +30,12 @@ + #define AST2400_HPLL_BYPASS_EN BIT(17) + #define ASPEED_MISC_CTRL 0x2c + #define UART_DIV13_EN BIT(12) ++#define ASPEED_MISC2_CTRL 0x4c ++#define UART1_HS_CLK_EN BIT(24) ++#define UART2_HS_CLK_EN BIT(25) ++#define UART3_HS_CLK_EN BIT(26) ++#define UART4_HS_CLK_EN BIT(27) ++#define UART5_HS_CLK_EN BIT(28) + #define ASPEED_STRAP 0x70 + #define CLKIN_25MHZ_EN BIT(23) + #define AST2400_CLK_SOURCE_SEL BIT(18) +@@ -425,7 +433,7 @@ static int aspeed_clk_probe(struct platform_device *pdev) + struct aspeed_reset *ar; + struct regmap *map; + struct clk_hw *hw; +- u32 val, rate; ++ u32 val, uart_clock_div; + int i, ret; + + map = syscon_node_to_regmap(dev->of_node); +@@ -460,15 +468,23 @@ static int aspeed_clk_probe(struct platform_device *pdev) + /* UART clock div13 setting */ + regmap_read(map, ASPEED_MISC_CTRL, &val); + if (val & UART_DIV13_EN) +- rate = 24000000 / 13; ++ uart_clock_div = 13; + else +- rate = 24000000; ++ uart_clock_div = 1; ++ + /* TODO: Find the parent data for the uart clock */ +- hw = clk_hw_register_fixed_rate(dev, "uart", NULL, 0, rate); ++ hw = clk_hw_register_fixed_rate(dev, "uart", NULL, 0, ++ UART_LOW_SPEED_CLK / uart_clock_div); + if (IS_ERR(hw)) + return PTR_ERR(hw); + aspeed_clk_data->hws[ASPEED_CLK_UART] = hw; + ++ hw = clk_hw_register_fixed_rate(dev, "uart-hs", "usb-port1-gate", 0, ++ UART_HIGH_SPEED_CLK / uart_clock_div); ++ if (IS_ERR(hw)) ++ return PTR_ERR(hw); ++ aspeed_clk_data->hws[ASPEED_CLK_UART_HS] = hw; ++ + /* + * Memory controller (M-PLL) PLL. This clock is configured by the + * bootloader, and is exposed to Linux as a read-only clock rate. +@@ -534,9 +550,22 @@ static int aspeed_clk_probe(struct platform_device *pdev) + * Video Engine (ECLK) mux and clock divider + */ + ++ /* Get the uart clock source configuration from SCU4C*/ ++ regmap_read(map, ASPEED_MISC2_CTRL, &val); + for (i = 0; i < ARRAY_SIZE(aspeed_gates); i++) { + const struct aspeed_gate_data *gd = &aspeed_gates[i]; + u32 gate_flags; ++ char *parent_name; ++ ++ /* For uart, needs to adjust the clock based on SCU4C value */ ++ if ((i == ASPEED_CLK_GATE_UART1CLK && (val & UART1_HS_CLK_EN)) || ++ (i == ASPEED_CLK_GATE_UART2CLK && (val & UART2_HS_CLK_EN)) || ++ (i == ASPEED_CLK_GATE_UART5CLK && (val & UART5_HS_CLK_EN)) || ++ (i == ASPEED_CLK_GATE_UART3CLK && (val & UART3_HS_CLK_EN)) || ++ (i == ASPEED_CLK_GATE_UART4CLK && (val & UART4_HS_CLK_EN))) ++ parent_name = "uart-hs"; ++ else ++ parent_name = gd->parent_name; + + /* Special case: the USB port 1 clock (bit 14) is always + * working the opposite way from the other ones. +@@ -544,7 +573,7 @@ static int aspeed_clk_probe(struct platform_device *pdev) + gate_flags = (gd->clock_idx == 14) ? 0 : CLK_GATE_SET_TO_DISABLE; + hw = aspeed_clk_hw_register_gate(dev, + gd->name, +- gd->parent_name, ++ parent_name, + gd->flags, + map, + gd->clock_idx, +diff --git a/include/dt-bindings/clock/aspeed-clock.h b/include/dt-bindings/clock/aspeed-clock.h +index f437386..3358795 100644 +--- a/include/dt-bindings/clock/aspeed-clock.h ++++ b/include/dt-bindings/clock/aspeed-clock.h +@@ -39,6 +39,8 @@ + #define ASPEED_CLK_BCLK 33 + #define ASPEED_CLK_MPLL 34 + #define ASPEED_CLK_24M 35 ++#define ASPEED_CLK_UART_HS 36 ++#define ASPEED_CLK_MAX 37 + + #define ASPEED_RESET_XDMA 0 + #define ASPEED_RESET_MCTP 1 +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0032-misc-aspeed-Add-Aspeed-UART-routing-control-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0032-misc-aspeed-Add-Aspeed-UART-routing-control-driver.patch new file mode 100644 index 000000000..539c976c7 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0032-misc-aspeed-Add-Aspeed-UART-routing-control-driver.patch @@ -0,0 +1,556 @@ +From e39e3a3e54cbe8e5a39b4148a9232f4570d009a6 Mon Sep 17 00:00:00 2001 +From: Oskar Senft +Date: Wed, 8 Aug 2018 10:15:05 -0400 +Subject: [PATCH] misc: aspeed: Add Aspeed UART routing control driver. + +This driver adds sysfs files that allow the BMC userspace to configure +how UARTs and physical serial I/O ports are routed. + +Tested: Checked correct behavior (both read & write) on TYAN S7106 +board by manually changing routing settings and confirming that bits +flow as expected. Tested for UART1 and UART3 as this board doesn't have +the other UARTs wired up in a testable way. + +Signed-off-by: Oskar Senft +Signed-off-by: Yong Li +--- + .../ABI/stable/sysfs-driver-aspeed-uart-routing | 14 + + Documentation/misc-devices/aspeed-uart-routing.txt | 49 +++ + arch/arm/boot/dts/aspeed-bmc-intel-purley.dts | 4 + + arch/arm/boot/dts/aspeed-g5.dtsi | 6 + + drivers/misc/Kconfig | 6 + + drivers/misc/Makefile | 1 + + drivers/misc/aspeed-uart-routing.c | 383 +++++++++++++++++++++ + 7 files changed, 463 insertions(+) + create mode 100644 Documentation/ABI/stable/sysfs-driver-aspeed-uart-routing + create mode 100644 Documentation/misc-devices/aspeed-uart-routing.txt + create mode 100644 drivers/misc/aspeed-uart-routing.c + +diff --git a/Documentation/ABI/stable/sysfs-driver-aspeed-uart-routing b/Documentation/ABI/stable/sysfs-driver-aspeed-uart-routing +new file mode 100644 +index 000000000000..5068737d9c12 +--- /dev/null ++++ b/Documentation/ABI/stable/sysfs-driver-aspeed-uart-routing +@@ -0,0 +1,14 @@ ++What: /sys/bus/platform/drivers/aspeed-uart-routing/*/io* ++Date: August 2018 ++Contact: Oskar Senft ++Description: Configures the input source for the specific physical ++ serial I/O port. ++Users: OpenBMC. Proposed changes should be mailed to ++ openbmc@lists.ozlabs.org ++ ++What: /sys/bus/platform/drivers/aspeed-uart-routing/*/uart* ++Date: August 2018 ++Contact: Oskar Senft ++Description: Configures the input source for the specific UART. ++Users: OpenBMC. Proposed changes should be mailed to ++ openbmc@lists.ozlabs.org +diff --git a/Documentation/misc-devices/aspeed-uart-routing.txt b/Documentation/misc-devices/aspeed-uart-routing.txt +new file mode 100644 +index 000000000000..afaf17cb7eda +--- /dev/null ++++ b/Documentation/misc-devices/aspeed-uart-routing.txt +@@ -0,0 +1,49 @@ ++Kernel driver aspeed-uart-routing ++================================= ++ ++Supported chips: ++ASPEED AST2500 ++ ++Author: ++Google LLC ++ ++Description ++----------- ++ ++The Aspeed AST2500 allows to dynamically route the inputs for the built-in ++UARTS and physical serial I/O ports. ++ ++This allows, for example, to connect the output of UART to another UART. ++This can be used to enable host<->BMC communication via UARTs, e.g. to allow ++access to the host's serial console. ++ ++This driver is for the BMC side. The sysfs files allow the BMC userspace ++which owns the system configuration policy, to configure how UARTs and ++physical serial I/O ports are routed. ++ ++The driver provides the following files in sysfs: ++uart1 Configure the input signal to UART1. ++uart2 Configure the input signal to UART2. ++uart3 Configure the input signal to UART3. ++uart4 Configure the input signal to UART4. ++uart5 Configure the input signal to UART5. ++io1 Configure the input signal to physical serial port 1. ++io2 Configure the input signal to physical serial port 2. ++io3 Configure the input signal to physical serial port 3. ++io4 Configure the input signal to physical serial port 4. ++io5 Configure the input signal to physical serial port 5. ++ ++When read, each file shows the list of available options with the currently ++selected option marked by square brackets "[]". The list of available options ++depends on the selected file. ++ ++Example: ++$ cat /sys/bus/platform/drivers/aspeed-uart-routing/*.uart_routing/uart1 ++[io1] io2 io3 io4 uart2 uart3 uart4 io6 ++ ++In this case, UART1 gets its input signal from IO1 (physical serial port 1). ++ ++$ echo -n "uart3" \ ++ >/sys/bus/platform/drivers/aspeed-uart-routing/*.uart_routing/uart1 ++$ cat /sys/bus/platform/drivers/aspeed-uart-routing/*.uart_routing/uart1 ++io1 io2 io3 io4 uart2 [uart3] uart4 io6 +diff --git a/arch/arm/boot/dts/aspeed-bmc-intel-purley.dts b/arch/arm/boot/dts/aspeed-bmc-intel-purley.dts +index 655bb37e422f..eb05f5a2c480 100644 +--- a/arch/arm/boot/dts/aspeed-bmc-intel-purley.dts ++++ b/arch/arm/boot/dts/aspeed-bmc-intel-purley.dts +@@ -174,6 +174,10 @@ + status = "okay"; + }; + ++&uart_routing { ++ status = "okay"; ++}; ++ + &mac1 { + status = "okay"; + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 3bb31c1daf9d..92843cc1a8f4 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -482,6 +482,12 @@ + status = "disabled"; + }; + }; ++ ++ uart_routing: uart_routing@9c { ++ compatible = "aspeed,ast2500-uart-routing"; ++ reg = <0x9c 0x4>; ++ status = "disabled"; ++ }; + }; + + peci: bus@1e78b000 { +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index f2062546250c..8e2fc51dcc44 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -537,6 +537,12 @@ config MISC_RTSX + tristate + default MISC_RTSX_PCI || MISC_RTSX_USB + ++config ASPEED_UART_ROUTING ++ tristate "Aspeed ast2500 UART routing control" ++ help ++ If you want to configure UART routing on Aspeed BMC platforms, enable ++ this option. ++ + source "drivers/misc/c2port/Kconfig" + source "drivers/misc/eeprom/Kconfig" + source "drivers/misc/cb710/Kconfig" +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index bb89694e6b4b..0f00eb63556c 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -56,6 +56,7 @@ obj-$(CONFIG_CXL_BASE) += cxl/ + obj-$(CONFIG_ASPEED_ESPI_SLAVE) += aspeed-espi-slave.o + obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o ++obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o + obj-$(CONFIG_ASPEED_LPC_MBOX) += aspeed-lpc-mbox.o + obj-$(CONFIG_ASPEED_LPC_SIO) += aspeed-lpc-sio.o + obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o +diff --git a/drivers/misc/aspeed-uart-routing.c b/drivers/misc/aspeed-uart-routing.c +new file mode 100644 +index 000000000000..21ef5d98c317 +--- /dev/null ++++ b/drivers/misc/aspeed-uart-routing.c +@@ -0,0 +1,383 @@ ++/* ++ * UART Routing driver for Aspeed AST2500 ++ * ++ * Copyright (c) 2018 Google LLC ++ * ++ * 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. ++ */ ++#include ++#include ++#include ++#include ++ ++/* The Aspeed AST2500 allows to dynamically route the inputs for the built-in ++ * UARTS and physical serial I/O ports. ++ * ++ * This allows, for example, to connect the output of UART to another UART. ++ * This can be used to enable host<->BMC communication via UARTs, e.g. to allow ++ * access to the host's serial console. ++ * ++ * This driver is for the BMC side. The sysfs files allow the BMC userspace ++ * which owns the system configuration policy, to configure how UARTs and ++ * physical serial I/O ports are routed. ++ */ ++ ++#define ASPEED_HICRA_IO1 "io1" ++#define ASPEED_HICRA_IO2 "io2" ++#define ASPEED_HICRA_IO3 "io3" ++#define ASPEED_HICRA_IO4 "io4" ++#define ASPEED_HICRA_IO5 "io5" ++#define ASPEED_HICRA_IO6 "io6" ++#define ASPEED_HICRA_UART1 "uart1" ++#define ASPEED_HICRA_UART2 "uart2" ++#define ASPEED_HICRA_UART3 "uart3" ++#define ASPEED_HICRA_UART4 "uart4" ++#define ASPEED_HICRA_UART5 "uart5" ++ ++struct aspeed_uart_routing { ++ struct device *dev; ++ void __iomem *regs; ++ spinlock_t lock; ++}; ++ ++struct aspeed_uart_routing_selector { ++ struct device_attribute dev_attr; ++ int shift; ++ int mask; ++ const char * const options[]; ++}; ++ ++#define to_routing_selector(_dev_attr) \ ++ container_of(_dev_attr, struct aspeed_uart_routing_selector, dev_attr) ++ ++ ++static ssize_t aspeed_uart_routing_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf); ++ ++static ssize_t aspeed_uart_routing_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count); ++ ++#define ROUTING_ATTR(_name) { \ ++ .attr = {.name = _name, \ ++ .mode = VERIFY_OCTAL_PERMISSIONS(S_IWUSR | S_IRUGO) }, \ ++ .show = aspeed_uart_routing_show, \ ++ .store = aspeed_uart_routing_store, \ ++} ++ ++static struct aspeed_uart_routing_selector uart5_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_UART5), ++ .shift = 28, ++ .mask = 0xf, ++ .options = { ++ ASPEED_HICRA_IO5, // 0 ++ ASPEED_HICRA_IO1, // 1 ++ ASPEED_HICRA_IO2, // 2 ++ ASPEED_HICRA_IO3, // 3 ++ ASPEED_HICRA_IO4, // 4 ++ ASPEED_HICRA_UART1, // 5 ++ ASPEED_HICRA_UART2, // 6 ++ ASPEED_HICRA_UART3, // 7 ++ ASPEED_HICRA_UART4, // 8 ++ ASPEED_HICRA_IO6, // 9 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector uart4_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_UART4), ++ .shift = 25, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_IO4, // 0 ++ ASPEED_HICRA_IO1, // 1 ++ ASPEED_HICRA_IO2, // 2 ++ ASPEED_HICRA_IO3, // 3 ++ ASPEED_HICRA_UART1, // 4 ++ ASPEED_HICRA_UART2, // 5 ++ ASPEED_HICRA_UART3, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector uart3_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_UART3), ++ .shift = 22, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_IO3, // 0 ++ ASPEED_HICRA_IO4, // 1 ++ ASPEED_HICRA_IO1, // 2 ++ ASPEED_HICRA_IO2, // 3 ++ ASPEED_HICRA_UART4, // 4 ++ ASPEED_HICRA_UART1, // 5 ++ ASPEED_HICRA_UART2, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector uart2_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_UART2), ++ .shift = 19, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_IO2, // 0 ++ ASPEED_HICRA_IO3, // 1 ++ ASPEED_HICRA_IO4, // 2 ++ ASPEED_HICRA_IO1, // 3 ++ ASPEED_HICRA_UART3, // 4 ++ ASPEED_HICRA_UART4, // 5 ++ ASPEED_HICRA_UART1, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector uart1_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_UART1), ++ .shift = 16, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_IO1, // 0 ++ ASPEED_HICRA_IO2, // 1 ++ ASPEED_HICRA_IO3, // 2 ++ ASPEED_HICRA_IO4, // 3 ++ ASPEED_HICRA_UART2, // 4 ++ ASPEED_HICRA_UART3, // 5 ++ ASPEED_HICRA_UART4, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector io5_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_IO5), ++ .shift = 12, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_UART5, // 0 ++ ASPEED_HICRA_UART1, // 1 ++ ASPEED_HICRA_UART2, // 2 ++ ASPEED_HICRA_UART3, // 3 ++ ASPEED_HICRA_UART4, // 4 ++ ASPEED_HICRA_IO1, // 5 ++ ASPEED_HICRA_IO3, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector io4_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_IO4), ++ .shift = 9, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_UART4, // 0 ++ ASPEED_HICRA_UART5, // 1 ++ ASPEED_HICRA_UART1, // 2 ++ ASPEED_HICRA_UART2, // 3 ++ ASPEED_HICRA_UART3, // 4 ++ ASPEED_HICRA_IO1, // 5 ++ ASPEED_HICRA_IO2, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector io3_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_IO3), ++ .shift = 6, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_UART3, // 0 ++ ASPEED_HICRA_UART4, // 1 ++ ASPEED_HICRA_UART5, // 2 ++ ASPEED_HICRA_UART1, // 3 ++ ASPEED_HICRA_UART2, // 4 ++ ASPEED_HICRA_IO1, // 5 ++ ASPEED_HICRA_IO2, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector io2_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_IO2), ++ .shift = 3, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_UART2, // 0 ++ ASPEED_HICRA_UART3, // 1 ++ ASPEED_HICRA_UART4, // 2 ++ ASPEED_HICRA_UART5, // 3 ++ ASPEED_HICRA_UART1, // 4 ++ ASPEED_HICRA_IO3, // 5 ++ ASPEED_HICRA_IO4, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++static struct aspeed_uart_routing_selector io1_sel = { ++ .dev_attr = ROUTING_ATTR(ASPEED_HICRA_IO1), ++ .shift = 0, ++ .mask = 0x7, ++ .options = { ++ ASPEED_HICRA_UART1, // 0 ++ ASPEED_HICRA_UART2, // 1 ++ ASPEED_HICRA_UART3, // 2 ++ ASPEED_HICRA_UART4, // 3 ++ ASPEED_HICRA_UART5, // 4 ++ ASPEED_HICRA_IO3, // 5 ++ ASPEED_HICRA_IO4, // 6 ++ ASPEED_HICRA_IO6, // 7 ++ NULL, // NULL termination ++ }, ++}; ++ ++ ++static struct attribute *aspeed_uart_routing_attrs[] = { ++ &uart1_sel.dev_attr.attr, ++ &uart2_sel.dev_attr.attr, ++ &uart3_sel.dev_attr.attr, ++ &uart4_sel.dev_attr.attr, ++ &uart5_sel.dev_attr.attr, ++ &io1_sel.dev_attr.attr, ++ &io2_sel.dev_attr.attr, ++ &io3_sel.dev_attr.attr, ++ &io4_sel.dev_attr.attr, ++ &io5_sel.dev_attr.attr, ++ NULL, ++}; ++ ++static const struct attribute_group aspeed_uart_routing_attr_group = { ++ .attrs = aspeed_uart_routing_attrs, ++}; ++ ++static ssize_t aspeed_uart_routing_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct aspeed_uart_routing *uart_routing = dev_get_drvdata(dev); ++ struct aspeed_uart_routing_selector *sel = to_routing_selector(attr); ++ int val, pos, len; ++ ++ val = (readl(uart_routing->regs) >> sel->shift) & sel->mask; ++ ++ len = 0; ++ for (pos = 0; sel->options[pos] != NULL; ++pos) { ++ if (pos == val) { ++ len += snprintf(buf + len, PAGE_SIZE - 1 - len, ++ "[%s] ", sel->options[pos]); ++ } else { ++ len += snprintf(buf + len, PAGE_SIZE - 1 - len, ++ "%s ", sel->options[pos]); ++ } ++ } ++ ++ if (val >= pos) { ++ len += snprintf(buf + len, PAGE_SIZE - 1 - len, ++ "[unknown(%d)]", val); ++ } ++ ++ len += snprintf(buf + len, PAGE_SIZE - 1 - len, "\n"); ++ ++ return len; ++} ++ ++static ssize_t aspeed_uart_routing_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct aspeed_uart_routing *uart_routing = dev_get_drvdata(dev); ++ struct aspeed_uart_routing_selector *sel = to_routing_selector(attr); ++ int val; ++ u32 reg; ++ ++ val = match_string(sel->options, -1, buf); ++ if (val < 0) { ++ dev_err(dev, "invalid value \"%s\"\n", buf); ++ return -EINVAL; ++ } ++ ++ spin_lock(&uart_routing->lock); ++ reg = readl(uart_routing->regs); ++ // Zero out existing value in specified bits. ++ reg &= ~(sel->mask << sel->shift); ++ // Set new value in specified bits. ++ reg |= (val & sel->mask) << sel->shift; ++ writel(reg, uart_routing->regs); ++ spin_unlock(&uart_routing->lock); ++ ++ return count; ++} ++ ++static int aspeed_uart_routing_probe(struct platform_device *pdev) ++{ ++ struct aspeed_uart_routing *uart_routing; ++ struct resource *res; ++ int rc; ++ ++ uart_routing = devm_kzalloc(&pdev->dev, ++ sizeof(*uart_routing), ++ GFP_KERNEL); ++ if (!uart_routing) ++ return -ENOMEM; ++ ++ spin_lock_init(&uart_routing->lock); ++ uart_routing->dev = &pdev->dev; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ uart_routing->regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(uart_routing->regs)) ++ return PTR_ERR(uart_routing->regs); ++ ++ rc = sysfs_create_group(&uart_routing->dev->kobj, ++ &aspeed_uart_routing_attr_group); ++ if (rc < 0) ++ return rc; ++ ++ platform_set_drvdata(pdev, uart_routing); ++ ++ return 0; ++} ++ ++static int aspeed_uart_routing_remove(struct platform_device *pdev) ++{ ++ struct aspeed_uart_routing *uart_routing = platform_get_drvdata(pdev); ++ ++ sysfs_remove_group(&uart_routing->dev->kobj, ++ &aspeed_uart_routing_attr_group); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_uart_routing_table[] = { ++ { .compatible = "aspeed,ast2500-uart-routing" }, ++ { }, ++}; ++ ++static struct platform_driver aspeed_uart_routing_driver = { ++ .driver = { ++ .name = "aspeed-uart-routing", ++ .of_match_table = aspeed_uart_routing_table, ++ }, ++ .probe = aspeed_uart_routing_probe, ++ .remove = aspeed_uart_routing_remove, ++}; ++ ++module_platform_driver(aspeed_uart_routing_driver); ++ ++MODULE_AUTHOR("Oskar Senft "); ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Driver to configure Aspeed UART routing"); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0034-arm-dts-adpeed-Swap-the-mac-nodes-numbering.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0034-arm-dts-adpeed-Swap-the-mac-nodes-numbering.patch new file mode 100644 index 000000000..eef3bee6f --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0034-arm-dts-adpeed-Swap-the-mac-nodes-numbering.patch @@ -0,0 +1,85 @@ +From 9c509b9450f641c169ee3aeb60e398c43810dcb2 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Wed, 3 Oct 2018 10:17:58 -0700 +Subject: [PATCH] arm: dts: adpeed: Swap the mac nodes numbering + +This patch swaps the numbering of mac0 and mac1 to make a dedicated +nic get assigned the first ethernet device number. + +Signed-off-by: Jae Hyun Yoo +--- + arch/arm/boot/dts/aspeed-g4.dtsi | 16 ++++++++-------- + arch/arm/boot/dts/aspeed-g5.dtsi | 16 ++++++++-------- + 2 files changed, 16 insertions(+), 16 deletions(-) + +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index 22eab8a952ed..004bbb08dd4a 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -101,14 +101,6 @@ + reg = <0x1e6c2000 0x80>; + }; + +- mac0: ethernet@1e660000 { +- compatible = "aspeed,ast2400-mac", "faraday,ftgmac100"; +- reg = <0x1e660000 0x180>; +- interrupts = <2>; +- clocks = <&syscon ASPEED_CLK_GATE_MAC1CLK>; +- status = "disabled"; +- }; +- + mac1: ethernet@1e680000 { + compatible = "aspeed,ast2400-mac", "faraday,ftgmac100"; + reg = <0x1e680000 0x180>; +@@ -117,6 +109,14 @@ + status = "disabled"; + }; + ++ mac0: ethernet@1e660000 { ++ compatible = "aspeed,ast2400-mac", "faraday,ftgmac100"; ++ reg = <0x1e660000 0x180>; ++ interrupts = <2>; ++ clocks = <&syscon ASPEED_CLK_GATE_MAC1CLK>; ++ status = "disabled"; ++ }; ++ + ehci0: usb@1e6a1000 { + compatible = "aspeed,ast2400-ehci", "generic-ehci"; + reg = <0x1e6a1000 0x100>; +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 92843cc1a8f4..30a7f349feeb 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -142,14 +142,6 @@ + reg = <0x1e6c2000 0x80>; + }; + +- mac0: ethernet@1e660000 { +- compatible = "aspeed,ast2500-mac", "faraday,ftgmac100"; +- reg = <0x1e660000 0x180>; +- interrupts = <2>; +- clocks = <&syscon ASPEED_CLK_GATE_MAC1CLK>; +- status = "disabled"; +- }; +- + mac1: ethernet@1e680000 { + compatible = "aspeed,ast2500-mac", "faraday,ftgmac100"; + reg = <0x1e680000 0x180>; +@@ -158,6 +150,14 @@ + status = "disabled"; + }; + ++ mac0: ethernet@1e660000 { ++ compatible = "aspeed,ast2500-mac", "faraday,ftgmac100"; ++ reg = <0x1e660000 0x180>; ++ interrupts = <2>; ++ clocks = <&syscon ASPEED_CLK_GATE_MAC1CLK>; ++ status = "disabled"; ++ }; ++ + ehci0: usb@1e6a1000 { + compatible = "aspeed,ast2500-ehci", "generic-ehci"; + reg = <0x1e6a1000 0x100>; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0035-Implement-a-memory-driver-share-memory.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0035-Implement-a-memory-driver-share-memory.patch new file mode 100644 index 000000000..51ddbb18e --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0035-Implement-a-memory-driver-share-memory.patch @@ -0,0 +1,249 @@ +From 1d459c15998c9a79ba7a758cef6129ed29f3b958 Mon Sep 17 00:00:00 2001 +From: cyang29 +Date: Fri, 9 Nov 2018 10:24:37 +0800 +Subject: [PATCH] Implement a memory driver share memory + +Implement a memory driver for BMC to access VGA share memory. +The driver is used by MDRV2. In MDRV2 BIOS will send whole +SMBIOS table to VGA memory and BMC can get the table from VGA +memory through this driver. + +Signed-off-by: cyang29 +--- + .../devicetree/bindings/misc/vga-shared-memory.txt | 20 +++ + drivers/misc/Kconfig | 10 ++ + drivers/misc/Makefile | 1 + + drivers/misc/aspeed-vga-sharedmem.c | 164 +++++++++++++++++++++ + 4 files changed, 195 insertions(+) + create mode 100644 Documentation/devicetree/bindings/misc/vga-shared-memory.txt + create mode 100644 drivers/misc/aspeed-vga-sharedmem.c + +diff --git a/Documentation/devicetree/bindings/misc/vga-shared-memory.txt b/Documentation/devicetree/bindings/misc/vga-shared-memory.txt +new file mode 100644 +index 000000000000..03f57c53e844 +--- /dev/null ++++ b/Documentation/devicetree/bindings/misc/vga-shared-memory.txt +@@ -0,0 +1,20 @@ ++* Aspeed VGA shared memory driver ++ ++Aspeed VGA shared memory driver allow user to read data from AST2500 ++VGA memory. This driver is required by ManagedDataRegionlV2 ++specification. In the spec, BIOS will transfer whole SMBIOS table to ++VGA memroy and BMC get the table from VGA memory. 0penBMC project do ++not allow to use /dev/mem for security concerns. To get the data in ++VGA shared memory in user space, implement this driver only allowed ++user to mmap limited memory area. ++ ++Required properties: ++- compatible: "aspeed,ast2500-vga-sharedmem" ++ - aspeed,ast2500-vga-sharedmem: Aspeed AST2500 family ++- reg: Should contain VGA shared memory start address and length ++ ++Example: ++vga-shared-memory { ++ compatible = "aspeed,ast2500-vga-sharedmem"; ++ reg = <0x9ff00000 0x100000>; ++}; +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index 8e2fc51dcc44..1279a9674537 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -543,6 +543,16 @@ config ASPEED_UART_ROUTING + If you want to configure UART routing on Aspeed BMC platforms, enable + this option. + ++config ASPEED_VGA_SHAREDMEM ++ tristate "Aspeed VGA Shared memory" ++ depends on (ARCH_ASPEED || COMPILE_TEST) ++ help ++ To access VGA shared memory on Aspeed BMC, enable this option. ++ This driver used by ManagedDataRegionlV2 specification. In the ++ specification, BIOS will transfer whole SMBIOS table to VGA ++ memory, and BMC can get the table from VGA memory through this ++ driver. ++ + source "drivers/misc/c2port/Kconfig" + source "drivers/misc/eeprom/Kconfig" + source "drivers/misc/cb710/Kconfig" +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index 0f00eb63556c..f4951a6e435b 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -62,3 +62,4 @@ obj-$(CONFIG_ASPEED_LPC_SIO) += aspeed-lpc-sio.o + obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o + obj-$(CONFIG_OCXL) += ocxl/ + obj-$(CONFIG_MISC_RTSX) += cardreader/ ++obj-$(CONFIG_ASPEED_VGA_SHAREDMEM) += aspeed-vga-sharedmem.o +diff --git a/drivers/misc/aspeed-vga-sharedmem.c b/drivers/misc/aspeed-vga-sharedmem.c +new file mode 100644 +index 000000000000..76f60cd67d3a +--- /dev/null ++++ b/drivers/misc/aspeed-vga-sharedmem.c +@@ -0,0 +1,164 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2018 Intel Corporation ++ * VGA Shared Memory driver for Aspeed AST2500 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define SHAREDMEM_NAME "vgasharedmem" ++ ++struct aspeed_vga_sharedmem { ++ struct miscdevice miscdev; ++ unsigned int addr; ++ unsigned int size; ++ bool mmap_enable; ++}; ++ ++static struct aspeed_vga_sharedmem *file_sharemem(struct file *file) ++{ ++ return container_of(file->private_data, ++ struct aspeed_vga_sharedmem, miscdev); ++} ++ ++static int vga_open(struct inode *inode, struct file *file) ++{ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ struct aspeed_vga_sharedmem *vga_sharedmem = file_sharemem(file); ++ ++ if (!vga_sharedmem->mmap_enable) ++ return -EPERM; ++ ++ return 0; ++} ++ ++static int vga_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ struct aspeed_vga_sharedmem *vga_sharedmem = file_sharemem(file); ++ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ vma->vm_flags = (vma->vm_flags & (~VM_WRITE)); ++ remap_pfn_range(vma, vma->vm_start, vga_sharedmem->addr >> PAGE_SHIFT, ++ vga_sharedmem->size, vma->vm_page_prot); ++ return 0; ++} ++ ++static ssize_t enable_mmap_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct aspeed_vga_sharedmem *vga_sharedmem = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%u\n", vga_sharedmem->mmap_enable); ++} ++ ++static ssize_t enable_mmap_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct aspeed_vga_sharedmem *vga_sharedmem = ++ dev_get_drvdata(dev); ++ bool val; ++ ++ if (kstrtobool(buf, &val)) ++ return -EINVAL; ++ ++ vga_sharedmem->mmap_enable = val; ++ ++ return count; ++} ++static DEVICE_ATTR_RW(enable_mmap); ++ ++static struct attribute *sharedmem_attrs[] = { ++ &dev_attr_enable_mmap.attr, ++ NULL ++}; ++ ++static const struct attribute_group sharedmem_attr_group = { ++ .attrs = sharedmem_attrs, ++}; ++ ++static const struct attribute_group *sharedmem_attr_groups[] = { ++ &sharedmem_attr_group, ++ NULL ++}; ++ ++static const struct file_operations vga_sharedmem_fops = { ++ .owner = THIS_MODULE, ++ .open = vga_open, ++ .mmap = vga_mmap, ++}; ++ ++static struct miscdevice vga_sharedmem_miscdev = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = SHAREDMEM_NAME, ++ .fops = &vga_sharedmem_fops, ++ .groups = sharedmem_attr_groups, ++}; ++ ++static int vga_sharedmem_probe(struct platform_device *pdev) ++{ ++ struct aspeed_vga_sharedmem *vga_sharedmem; ++ struct device *dev = &pdev->dev; ++ u32 reg[2]; ++ struct resource *rc; ++ ++ vga_sharedmem = devm_kzalloc(dev, sizeof(*vga_sharedmem), GFP_KERNEL); ++ if (!vga_sharedmem) ++ return -ENOMEM; ++ ++ dev_set_drvdata(&pdev->dev, vga_sharedmem); ++ ++ rc = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!rc) { ++ dev_err(dev, "Couldn't read size device-tree property\n"); ++ return -ENXIO; ++ } ++ ++ vga_sharedmem->addr = rc->start; ++ vga_sharedmem->size = resource_size(rc); ++ vga_sharedmem->mmap_enable = true; ++ ++ vga_sharedmem->miscdev = vga_sharedmem_miscdev; ++ ++ return misc_register(&vga_sharedmem->miscdev); ++} ++ ++static int vga_sharedmem_remove(struct platform_device *pdev) ++{ ++ struct aspeed_vga_sharedmem *vga_sharedmem = ++ dev_get_drvdata(&pdev->dev); ++ ++ misc_deregister(&vga_sharedmem->miscdev); ++ ++ return 0; ++} ++ ++static const struct of_device_id vga_sharedmem_match[] = { ++ { .compatible = "aspeed,ast2500-vga-sharedmem", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, vga_sharedmem_match); ++ ++static struct platform_driver vga_sharedmem_driver = { ++ .driver = { ++ .name = "VGA-SHAREDMEM", ++ .of_match_table = vga_sharedmem_match, ++ }, ++ .probe = vga_sharedmem_probe, ++ .remove = vga_sharedmem_remove, ++}; ++ ++module_platform_driver(vga_sharedmem_driver); ++ ++MODULE_AUTHOR("Yang Cheng "); ++MODULE_DESCRIPTION("Shared VGA memory"); ++MODULE_LICENSE("GPL v2"); +-- +2.16.2 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch new file mode 100644 index 000000000..83717369c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch @@ -0,0 +1,1425 @@ +From 58c3299017c5e6022fb2a2a74b662b2a4c0306f5 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Tue, 20 Nov 2018 10:14:47 -0800 +Subject: [PATCH] net/ncsi: backport ncsi patches + +net/ncsi: Allow enabling multiple packages & channels + +This series extends the NCSI driver to configure multiple packages +and/or channels simultaneously. Since the RFC series this includes a few +extra changes to fix areas in the driver that either made this harder or +were roadblocks due to deviations from the NCSI specification. + +Patches 1 & 2 fix two issues where the driver made assumptions about the +capabilities of the NCSI topology. +Patches 3 & 4 change some internal semantics slightly to make multi-mode +easier. +Patch 5 introduces a cleaner way of reconfiguring the NCSI configuration +and keeping track of channel states. +Patch 6 implements the main multi-package/multi-channel configuration, +configured via the Netlink interface. + +Readers who have an interesting NCSI setup - especially multi-package +with HWA - please test! I think I've covered all permutations but I +don't have infinite hardware to test on. + +net/ncsi: Don't enable all channels when HWA available + +NCSI hardware arbitration allows multiple packages to be enabled at once +and share the same wiring. If the NCSI driver recognises that HWA is +available it unconditionally enables all packages and channels; but that +is a configuration decision rather than something required by HWA. +Additionally the current implementation will not failover on link events +which can cause connectivity to be lost unless the interface is manually +bounced. + +Retain basic HWA support but remove the separate configuration path to +enable all channels, leaving this to be handled by a later +implementation. + +net/ncsi: Probe single packages to avoid conflict + +Currently the NCSI driver sends a select-package command to all possible +packages simultaneously to discover what packages are available. However +at this stage in the probe process the driver does not know if +hardware arbitration is available: if it isn't then this process could +cause collisions on the RMII bus when packages try to respond. + +Update the probe loop to probe each package one by one, and once +complete check if HWA is universally supported. + +net/ncsi: Don't deselect package in suspend if active + +When a package is deselected all channels of that package cease +communication. If there are other channels active on the package of the +suspended channel this will disable them as well, so only send a +deselect-package command if no other channels are active. + +net/ncsi: Don't mark configured channels inactive + +The concepts of a channel being 'active' and it having link are slightly +muddled in the NCSI driver. Tweak this slightly so that +NCSI_CHANNEL_ACTIVE represents a channel that has been configured and +enabled, and NCSI_CHANNEL_INACTIVE represents a de-configured channel. +This distinction is important because a channel can be 'active' but have +its link down; in this case the channel may still need to be configured +so that it may receive AEN link-state-change packets. + +net/ncsi: Reset channel state in ncsi_start_dev() + +When the NCSI driver is stopped with ncsi_stop_dev() the channel +monitors are stopped and the state set to "inactive". However the +channels are still configured and active from the perspective of the +network controller. We should suspend each active channel but in the +context of ncsi_stop_dev() the transmit queue has been or is about to be +stopped so we won't have time to do so. + +Instead when ncsi_start_dev() is called if the NCSI topology has already +been probed then call ncsi_reset_dev() to suspend any channels that were +previously active. This resets the network controller to a known state, +provides an up to date view of channel link state, and makes sure that +mode flags such as NCSI_MODE_TX_ENABLE are properly reset. + +In addition to ncsi_start_dev() use ncsi_reset_dev() in ncsi-netlink.c +to update the channel configuration more cleanly. + +net/ncsi: Configure multi-package, multi-channel modes with failover + +This patch extends the ncsi-netlink interface with two new commands and +three new attributes to configure multiple packages and/or channels at +once, and configure specific failover modes. + +NCSI_CMD_SET_PACKAGE mask and NCSI_CMD_SET_CHANNEL_MASK set a whitelist +of packages or channels allowed to be configured with the +NCSI_ATTR_PACKAGE_MASK and NCSI_ATTR_CHANNEL_MASK attributes +respectively. If one of these whitelists is set only packages or +channels matching the whitelist are considered for the channel queue in +ncsi_choose_active_channel(). + +These commands may also use the NCSI_ATTR_MULTI_FLAG to signal that +multiple packages or channels may be configured simultaneously. NCSI +hardware arbitration (HWA) must be available in order to enable +multi-package mode. Multi-channel mode is always available. + +If the NCSI_ATTR_CHANNEL_ID attribute is present in the +NCSI_CMD_SET_CHANNEL_MASK command the it sets the preferred channel as +with the NCSI_CMD_SET_INTERFACE command. The combination of preferred +channel and channel whitelist defines a primary channel and the allowed +failover channels. +If the NCSI_ATTR_MULTI_FLAG attribute is also present then the preferred +channel is configured for Tx/Rx and the other channels are enabled only +for Rx. + +Signed-off-by: Samuel Mendoza-Jonas +Signed-off-by: Jae Hyun Yoo +--- + include/uapi/linux/ncsi.h | 15 ++ + net/ncsi/internal.h | 19 +- + net/ncsi/ncsi-aen.c | 75 +++++-- + net/ncsi/ncsi-manage.c | 522 ++++++++++++++++++++++++++++++++-------------- + net/ncsi/ncsi-netlink.c | 233 ++++++++++++++++++--- + net/ncsi/ncsi-rsp.c | 2 +- + 6 files changed, 660 insertions(+), 206 deletions(-) + +diff --git a/include/uapi/linux/ncsi.h b/include/uapi/linux/ncsi.h +index 0a26a5576645..a3f87c54fdb3 100644 +--- a/include/uapi/linux/ncsi.h ++++ b/include/uapi/linux/ncsi.h +@@ -26,6 +26,12 @@ + * @NCSI_CMD_SEND_CMD: send NC-SI command to network card. + * Requires NCSI_ATTR_IFINDEX, NCSI_ATTR_PACKAGE_ID + * and NCSI_ATTR_CHANNEL_ID. ++ * @NCSI_CMD_SET_PACKAGE_MASK: set a whitelist of allowed packages. ++ * Requires NCSI_ATTR_IFINDEX and NCSI_ATTR_PACKAGE_MASK. ++ * @NCSI_CMD_SET_CHANNEL_MASK: set a whitelist of allowed channels. ++ * Requires NCSI_ATTR_IFINDEX, NCSI_ATTR_PACKAGE_ID, and ++ * NCSI_ATTR_CHANNEL_MASK. If NCSI_ATTR_CHANNEL_ID is present it sets ++ * the primary channel. + * @NCSI_CMD_MAX: highest command number + */ + enum ncsi_nl_commands { +@@ -34,6 +40,8 @@ enum ncsi_nl_commands { + NCSI_CMD_SET_INTERFACE, + NCSI_CMD_CLEAR_INTERFACE, + NCSI_CMD_SEND_CMD, ++ NCSI_CMD_SET_PACKAGE_MASK, ++ NCSI_CMD_SET_CHANNEL_MASK, + + __NCSI_CMD_AFTER_LAST, + NCSI_CMD_MAX = __NCSI_CMD_AFTER_LAST - 1 +@@ -48,6 +56,10 @@ enum ncsi_nl_commands { + * @NCSI_ATTR_PACKAGE_ID: package ID + * @NCSI_ATTR_CHANNEL_ID: channel ID + * @NCSI_ATTR_DATA: command payload ++ * @NCSI_ATTR_MULTI_FLAG: flag to signal that multi-mode should be enabled with ++ * NCSI_CMD_SET_PACKAGE_MASK or NCSI_CMD_SET_CHANNEL_MASK. ++ * @NCSI_ATTR_PACKAGE_MASK: 32-bit mask of allowed packages. ++ * @NCSI_ATTR_CHANNEL_MASK: 32-bit mask of allowed channels. + * @NCSI_ATTR_MAX: highest attribute number + */ + enum ncsi_nl_attrs { +@@ -57,6 +69,9 @@ enum ncsi_nl_attrs { + NCSI_ATTR_PACKAGE_ID, + NCSI_ATTR_CHANNEL_ID, + NCSI_ATTR_DATA, ++ NCSI_ATTR_MULTI_FLAG, ++ NCSI_ATTR_PACKAGE_MASK, ++ NCSI_ATTR_CHANNEL_MASK, + + __NCSI_ATTR_AFTER_LAST, + NCSI_ATTR_MAX = __NCSI_ATTR_AFTER_LAST - 1 +diff --git a/net/ncsi/internal.h b/net/ncsi/internal.h +index 1dae77c54009..9e3642b802c4 100644 +--- a/net/ncsi/internal.h ++++ b/net/ncsi/internal.h +@@ -222,6 +222,10 @@ struct ncsi_package { + unsigned int channel_num; /* Number of channels */ + struct list_head channels; /* List of chanels */ + struct list_head node; /* Form list of packages */ ++ ++ bool multi_channel; /* Enable multiple channels */ ++ u32 channel_whitelist; /* Channels to configure */ ++ struct ncsi_channel *preferred_channel; /* Primary channel */ + }; + + struct ncsi_request { +@@ -287,16 +291,16 @@ struct ncsi_dev_priv { + #define NCSI_DEV_PROBED 1 /* Finalized NCSI topology */ + #define NCSI_DEV_HWA 2 /* Enabled HW arbitration */ + #define NCSI_DEV_RESHUFFLE 4 ++#define NCSI_DEV_RESET 8 /* Reset state of NC */ + unsigned int gma_flag; /* OEM GMA flag */ + spinlock_t lock; /* Protect the NCSI device */ + #if IS_ENABLED(CONFIG_IPV6) + unsigned int inet6_addr_num; /* Number of IPv6 addresses */ + #endif ++ unsigned int package_probe_id;/* Current ID during probe */ + unsigned int package_num; /* Number of packages */ + struct list_head packages; /* List of packages */ + struct ncsi_channel *hot_channel; /* Channel was ever active */ +- struct ncsi_package *force_package; /* Force a specific package */ +- struct ncsi_channel *force_channel; /* Force a specific channel */ + struct ncsi_request requests[256]; /* Request table */ + unsigned int request_id; /* Last used request ID */ + #define NCSI_REQ_START_IDX 1 +@@ -309,6 +313,9 @@ struct ncsi_dev_priv { + struct list_head node; /* Form NCSI device list */ + #define NCSI_MAX_VLAN_VIDS 15 + struct list_head vlan_vids; /* List of active VLAN IDs */ ++ ++ bool multi_package; /* Enable multiple packages */ ++ u32 package_whitelist; /* Packages to configure */ + }; + + struct ncsi_cmd_arg { +@@ -341,6 +348,7 @@ extern spinlock_t ncsi_dev_lock; + list_for_each_entry_rcu(nc, &np->channels, node) + + /* Resources */ ++int ncsi_reset_dev(struct ncsi_dev *nd); + void ncsi_start_channel_monitor(struct ncsi_channel *nc); + void ncsi_stop_channel_monitor(struct ncsi_channel *nc); + struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np, +@@ -361,6 +369,13 @@ struct ncsi_request *ncsi_alloc_request(struct ncsi_dev_priv *ndp, + void ncsi_free_request(struct ncsi_request *nr); + struct ncsi_dev *ncsi_find_dev(struct net_device *dev); + int ncsi_process_next_channel(struct ncsi_dev_priv *ndp); ++bool ncsi_channel_has_link(struct ncsi_channel *channel); ++bool ncsi_channel_is_last(struct ncsi_dev_priv *ndp, ++ struct ncsi_channel *channel); ++int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, ++ struct ncsi_package *np, ++ struct ncsi_channel *disable, ++ struct ncsi_channel *enable); + + /* Packet handlers */ + u32 ncsi_calculate_checksum(unsigned char *data, int len); +diff --git a/net/ncsi/ncsi-aen.c b/net/ncsi/ncsi-aen.c +index 25e483e8278b..26d67e27551f 100644 +--- a/net/ncsi/ncsi-aen.c ++++ b/net/ncsi/ncsi-aen.c +@@ -50,13 +50,15 @@ static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h, + static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, + struct ncsi_aen_pkt_hdr *h) + { +- struct ncsi_aen_lsc_pkt *lsc; +- struct ncsi_channel *nc; ++ struct ncsi_channel *nc, *tmp; + struct ncsi_channel_mode *ncm; +- bool chained; +- int state; + unsigned long old_data, data; ++ struct ncsi_aen_lsc_pkt *lsc; ++ struct ncsi_package *np; ++ bool had_link, has_link; + unsigned long flags; ++ bool chained; ++ int state; + + /* Find the NCSI channel */ + ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc); +@@ -73,6 +75,9 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, + ncm->data[2] = data; + ncm->data[4] = ntohl(lsc->oem_status); + ++ had_link = !!(old_data & 0x1); ++ has_link = !!(data & 0x1); ++ + netdev_dbg(ndp->ndev.dev, "NCSI: LSC AEN - channel %u state %s\n", + nc->id, data & 0x1 ? "up" : "down"); + +@@ -80,22 +85,60 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, + state = nc->state; + spin_unlock_irqrestore(&nc->lock, flags); + +- if (!((old_data ^ data) & 0x1) || chained) +- return 0; +- if (!(state == NCSI_CHANNEL_INACTIVE && (data & 0x1)) && +- !(state == NCSI_CHANNEL_ACTIVE && !(data & 0x1))) ++ if (state == NCSI_CHANNEL_INACTIVE) ++ netdev_warn(ndp->ndev.dev, ++ "NCSI: Inactive channel %u received AEN!\n", ++ nc->id); ++ ++ if ((had_link == has_link) || chained) + return 0; + +- if (!(ndp->flags & NCSI_DEV_HWA) && +- state == NCSI_CHANNEL_ACTIVE) +- ndp->flags |= NCSI_DEV_RESHUFFLE; ++ if (!ndp->multi_package && !nc->package->multi_channel) { ++ if (had_link) { ++ ndp->flags |= NCSI_DEV_RESHUFFLE; ++ ncsi_stop_channel_monitor(nc); ++ spin_lock_irqsave(&ndp->lock, flags); ++ list_add_tail_rcu(&nc->link, &ndp->channel_queue); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return ncsi_process_next_channel(ndp); ++ } ++ /* Configured channel came up */ ++ return 0; ++ } + +- ncsi_stop_channel_monitor(nc); +- spin_lock_irqsave(&ndp->lock, flags); +- list_add_tail_rcu(&nc->link, &ndp->channel_queue); +- spin_unlock_irqrestore(&ndp->lock, flags); ++ if (had_link) { ++ ncm = &nc->modes[NCSI_MODE_TX_ENABLE]; ++ if (ncsi_channel_is_last(ndp, nc)) { ++ /* No channels left, reconfigure */ ++ return ncsi_reset_dev(&ndp->ndev); ++ } else if (ncm->enable) { ++ /* Need to failover Tx channel */ ++ ncsi_update_tx_channel(ndp, nc->package, nc, NULL); ++ } ++ } else if (has_link && nc->package->preferred_channel == nc) { ++ /* Return Tx to preferred channel */ ++ ncsi_update_tx_channel(ndp, nc->package, NULL, nc); ++ } else if (has_link) { ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ NCSI_FOR_EACH_CHANNEL(np, tmp) { ++ /* Enable Tx on this channel if the current Tx ++ * channel is down. ++ */ ++ ncm = &tmp->modes[NCSI_MODE_TX_ENABLE]; ++ if (ncm->enable && ++ !ncsi_channel_has_link(tmp)) { ++ ncsi_update_tx_channel(ndp, nc->package, ++ tmp, nc); ++ break; ++ } ++ } ++ } ++ } + +- return ncsi_process_next_channel(ndp); ++ /* Leave configured channels active in a multi-channel scenario so ++ * AEN events are still received. ++ */ ++ return 0; + } + + static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp, +diff --git a/net/ncsi/ncsi-manage.c b/net/ncsi/ncsi-manage.c +index bfc43b28c7a6..92e59f07f9a7 100644 +--- a/net/ncsi/ncsi-manage.c ++++ b/net/ncsi/ncsi-manage.c +@@ -28,6 +28,29 @@ + LIST_HEAD(ncsi_dev_list); + DEFINE_SPINLOCK(ncsi_dev_lock); + ++bool ncsi_channel_has_link(struct ncsi_channel *channel) ++{ ++ return !!(channel->modes[NCSI_MODE_LINK].data[2] & 0x1); ++} ++ ++bool ncsi_channel_is_last(struct ncsi_dev_priv *ndp, ++ struct ncsi_channel *channel) ++{ ++ struct ncsi_package *np; ++ struct ncsi_channel *nc; ++ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) ++ NCSI_FOR_EACH_CHANNEL(np, nc) { ++ if (nc == channel) ++ continue; ++ if (nc->state == NCSI_CHANNEL_ACTIVE && ++ ncsi_channel_has_link(nc)) ++ return false; ++ } ++ ++ return true; ++} ++ + static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down) + { + struct ncsi_dev *nd = &ndp->ndev; +@@ -52,7 +75,7 @@ static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down) + continue; + } + +- if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) { ++ if (ncsi_channel_has_link(nc)) { + spin_unlock_irqrestore(&nc->lock, flags); + nd->link_up = 1; + goto report; +@@ -113,10 +136,8 @@ static void ncsi_channel_monitor(struct timer_list *t) + default: + netdev_err(ndp->ndev.dev, "NCSI Channel %d timed out!\n", + nc->id); +- if (!(ndp->flags & NCSI_DEV_HWA)) { +- ncsi_report_link(ndp, true); +- ndp->flags |= NCSI_DEV_RESHUFFLE; +- } ++ ncsi_report_link(ndp, true); ++ ndp->flags |= NCSI_DEV_RESHUFFLE; + + ncsi_stop_channel_monitor(nc); + +@@ -269,6 +290,7 @@ struct ncsi_package *ncsi_add_package(struct ncsi_dev_priv *ndp, + np->ndp = ndp; + spin_lock_init(&np->lock); + INIT_LIST_HEAD(&np->channels); ++ np->channel_whitelist = UINT_MAX; + + spin_lock_irqsave(&ndp->lock, flags); + tmp = ncsi_find_package(ndp, id); +@@ -442,12 +464,14 @@ static void ncsi_request_timeout(struct timer_list *t) + static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) + { + struct ncsi_dev *nd = &ndp->ndev; +- struct ncsi_package *np = ndp->active_package; +- struct ncsi_channel *nc = ndp->active_channel; ++ struct ncsi_package *np; ++ struct ncsi_channel *nc, *tmp; + struct ncsi_cmd_arg nca; + unsigned long flags; + int ret; + ++ np = ndp->active_package; ++ nc = ndp->active_channel; + nca.ndp = ndp; + nca.req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN; + switch (nd->state) { +@@ -523,6 +547,15 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) + if (ret) + goto error; + ++ NCSI_FOR_EACH_CHANNEL(np, tmp) { ++ /* If there is another channel active on this package ++ * do not deselect the package. ++ */ ++ if (tmp != nc && tmp->state == NCSI_CHANNEL_ACTIVE) { ++ nd->state = ncsi_dev_state_suspend_done; ++ break; ++ } ++ } + break; + case ncsi_dev_state_suspend_deselect: + ndp->pending_req_num = 1; +@@ -541,8 +574,10 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) + spin_lock_irqsave(&nc->lock, flags); + nc->state = NCSI_CHANNEL_INACTIVE; + spin_unlock_irqrestore(&nc->lock, flags); +- ncsi_process_next_channel(ndp); +- ++ if (ndp->flags & NCSI_DEV_RESET) ++ ncsi_reset_dev(nd); ++ else ++ ncsi_process_next_channel(ndp); + break; + default: + netdev_warn(nd->dev, "Wrong NCSI state 0x%x in suspend\n", +@@ -717,13 +752,144 @@ static int ncsi_gma_handler(struct ncsi_cmd_arg *nca, unsigned int mf_id) + + #endif /* CONFIG_NCSI_OEM_CMD_GET_MAC */ + ++/* Determine if a given channel from the channel_queue should be used for Tx */ ++static bool ncsi_channel_is_tx(struct ncsi_dev_priv *ndp, ++ struct ncsi_channel *nc) ++{ ++ struct ncsi_channel_mode *ncm; ++ struct ncsi_channel *channel; ++ struct ncsi_package *np; ++ ++ /* Check if any other channel has Tx enabled; a channel may have already ++ * been configured and removed from the channel queue. ++ */ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ if (!ndp->multi_package && np != nc->package) ++ continue; ++ NCSI_FOR_EACH_CHANNEL(np, channel) { ++ ncm = &channel->modes[NCSI_MODE_TX_ENABLE]; ++ if (ncm->enable) ++ return false; ++ } ++ } ++ ++ /* This channel is the preferred channel and has link */ ++ list_for_each_entry_rcu(channel, &ndp->channel_queue, link) { ++ np = channel->package; ++ if (np->preferred_channel && ++ ncsi_channel_has_link(np->preferred_channel)) { ++ return np->preferred_channel == nc; ++ } ++ } ++ ++ /* This channel has link */ ++ if (ncsi_channel_has_link(nc)) ++ return true; ++ ++ list_for_each_entry_rcu(channel, &ndp->channel_queue, link) ++ if (ncsi_channel_has_link(channel)) ++ return false; ++ ++ /* No other channel has link; default to this one */ ++ return true; ++} ++ ++/* Change the active Tx channel in a multi-channel setup */ ++int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, ++ struct ncsi_package *package, ++ struct ncsi_channel *disable, ++ struct ncsi_channel *enable) ++{ ++ struct ncsi_cmd_arg nca; ++ struct ncsi_channel *nc; ++ struct ncsi_package *np; ++ int ret = 0; ++ ++ if (!package->multi_channel && !ndp->multi_package) ++ netdev_warn(ndp->ndev.dev, ++ "NCSI: Trying to update Tx channel in single-channel mode\n"); ++ nca.ndp = ndp; ++ nca.req_flags = 0; ++ ++ /* Find current channel with Tx enabled */ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ if (disable) ++ break; ++ if (!ndp->multi_package && np != package) ++ continue; ++ ++ NCSI_FOR_EACH_CHANNEL(np, nc) ++ if (nc->modes[NCSI_MODE_TX_ENABLE].enable) { ++ disable = nc; ++ break; ++ } ++ } ++ ++ /* Find a suitable channel for Tx */ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ if (enable) ++ break; ++ if (!ndp->multi_package && np != package) ++ continue; ++ if (!(ndp->package_whitelist & (0x1 << np->id))) ++ continue; ++ ++ if (np->preferred_channel && ++ ncsi_channel_has_link(np->preferred_channel)) { ++ enable = np->preferred_channel; ++ break; ++ } ++ ++ NCSI_FOR_EACH_CHANNEL(np, nc) { ++ if (!(np->channel_whitelist & 0x1 << nc->id)) ++ continue; ++ if (nc->state != NCSI_CHANNEL_ACTIVE) ++ continue; ++ if (ncsi_channel_has_link(nc)) { ++ enable = nc; ++ break; ++ } ++ } ++ } ++ ++ if (disable == enable) ++ return -1; ++ ++ if (!enable) ++ return -1; ++ ++ if (disable) { ++ nca.channel = disable->id; ++ nca.package = disable->package->id; ++ nca.type = NCSI_PKT_CMD_DCNT; ++ ret = ncsi_xmit_cmd(&nca); ++ if (ret) ++ netdev_err(ndp->ndev.dev, ++ "Error %d sending DCNT\n", ++ ret); ++ } ++ ++ netdev_info(ndp->ndev.dev, "NCSI: channel %u enables Tx\n", enable->id); ++ ++ nca.channel = enable->id; ++ nca.package = enable->package->id; ++ nca.type = NCSI_PKT_CMD_ECNT; ++ ret = ncsi_xmit_cmd(&nca); ++ if (ret) ++ netdev_err(ndp->ndev.dev, ++ "Error %d sending ECNT\n", ++ ret); ++ ++ return ret; ++} ++ + static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + { +- struct ncsi_dev *nd = &ndp->ndev; +- struct net_device *dev = nd->dev; + struct ncsi_package *np = ndp->active_package; + struct ncsi_channel *nc = ndp->active_channel; + struct ncsi_channel *hot_nc = NULL; ++ struct ncsi_dev *nd = &ndp->ndev; ++ struct net_device *dev = nd->dev; + struct ncsi_cmd_arg nca; + unsigned char index; + unsigned long flags; +@@ -845,20 +1011,29 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + } else if (nd->state == ncsi_dev_state_config_ebf) { + nca.type = NCSI_PKT_CMD_EBF; + nca.dwords[0] = nc->caps[NCSI_CAP_BC].cap; +- nd->state = ncsi_dev_state_config_ecnt; ++ if (ncsi_channel_is_tx(ndp, nc)) ++ nd->state = ncsi_dev_state_config_ecnt; ++ else ++ nd->state = ncsi_dev_state_config_ec; + #if IS_ENABLED(CONFIG_IPV6) + if (ndp->inet6_addr_num > 0 && + (nc->caps[NCSI_CAP_GENERIC].cap & + NCSI_CAP_GENERIC_MC)) + nd->state = ncsi_dev_state_config_egmf; +- else +- nd->state = ncsi_dev_state_config_ecnt; + } else if (nd->state == ncsi_dev_state_config_egmf) { + nca.type = NCSI_PKT_CMD_EGMF; + nca.dwords[0] = nc->caps[NCSI_CAP_MC].cap; +- nd->state = ncsi_dev_state_config_ecnt; ++ if (ncsi_channel_is_tx(ndp, nc)) ++ nd->state = ncsi_dev_state_config_ecnt; ++ else ++ nd->state = ncsi_dev_state_config_ec; + #endif /* CONFIG_IPV6 */ + } else if (nd->state == ncsi_dev_state_config_ecnt) { ++ if (np->preferred_channel && ++ nc != np->preferred_channel) ++ netdev_info(ndp->ndev.dev, ++ "NCSI: Tx failed over to channel %u\n", ++ nc->id); + nca.type = NCSI_PKT_CMD_ECNT; + nd->state = ncsi_dev_state_config_ec; + } else if (nd->state == ncsi_dev_state_config_ec) { +@@ -889,6 +1064,16 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + netdev_dbg(ndp->ndev.dev, "NCSI: channel %u config done\n", + nc->id); + spin_lock_irqsave(&nc->lock, flags); ++ nc->state = NCSI_CHANNEL_ACTIVE; ++ ++ if (ndp->flags & NCSI_DEV_RESET) { ++ /* A reset event happened during config, start it now */ ++ nc->reconfigure_needed = false; ++ spin_unlock_irqrestore(&nc->lock, flags); ++ ncsi_reset_dev(nd); ++ break; ++ } ++ + if (nc->reconfigure_needed) { + /* This channel's configuration has been updated + * part-way during the config state - start the +@@ -909,10 +1094,8 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + + if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) { + hot_nc = nc; +- nc->state = NCSI_CHANNEL_ACTIVE; + } else { + hot_nc = NULL; +- nc->state = NCSI_CHANNEL_INACTIVE; + netdev_dbg(ndp->ndev.dev, + "NCSI: channel %u link down after config\n", + nc->id); +@@ -940,43 +1123,35 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) + + static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp) + { +- struct ncsi_package *np, *force_package; +- struct ncsi_channel *nc, *found, *hot_nc, *force_channel; ++ struct ncsi_channel *nc, *found, *hot_nc; + struct ncsi_channel_mode *ncm; +- unsigned long flags; ++ unsigned long flags, cflags; ++ struct ncsi_package *np; ++ bool with_link; + + spin_lock_irqsave(&ndp->lock, flags); + hot_nc = ndp->hot_channel; +- force_channel = ndp->force_channel; +- force_package = ndp->force_package; + spin_unlock_irqrestore(&ndp->lock, flags); + +- /* Force a specific channel whether or not it has link if we have been +- * configured to do so +- */ +- if (force_package && force_channel) { +- found = force_channel; +- ncm = &found->modes[NCSI_MODE_LINK]; +- if (!(ncm->data[2] & 0x1)) +- netdev_info(ndp->ndev.dev, +- "NCSI: Channel %u forced, but it is link down\n", +- found->id); +- goto out; +- } +- +- /* The search is done once an inactive channel with up +- * link is found. ++ /* By default the search is done once an inactive channel with up ++ * link is found, unless a preferred channel is set. ++ * If multi_package or multi_channel are configured all channels in the ++ * whitelist are added to the channel queue. + */ + found = NULL; ++ with_link = false; + NCSI_FOR_EACH_PACKAGE(ndp, np) { +- if (ndp->force_package && np != ndp->force_package) ++ if (!(ndp->package_whitelist & (0x1 << np->id))) + continue; + NCSI_FOR_EACH_CHANNEL(np, nc) { +- spin_lock_irqsave(&nc->lock, flags); ++ if (!(np->channel_whitelist & (0x1 << nc->id))) ++ continue; ++ ++ spin_lock_irqsave(&nc->lock, cflags); + + if (!list_empty(&nc->link) || + nc->state != NCSI_CHANNEL_INACTIVE) { +- spin_unlock_irqrestore(&nc->lock, flags); ++ spin_unlock_irqrestore(&nc->lock, cflags); + continue; + } + +@@ -988,32 +1163,49 @@ static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp) + + ncm = &nc->modes[NCSI_MODE_LINK]; + if (ncm->data[2] & 0x1) { +- spin_unlock_irqrestore(&nc->lock, flags); + found = nc; +- goto out; ++ with_link = true; + } + +- spin_unlock_irqrestore(&nc->lock, flags); ++ /* If multi_channel is enabled configure all valid ++ * channels whether or not they currently have link ++ * so they will have AENs enabled. ++ */ ++ if (with_link || np->multi_channel) { ++ spin_lock_irqsave(&ndp->lock, flags); ++ list_add_tail_rcu(&nc->link, ++ &ndp->channel_queue); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ netdev_dbg(ndp->ndev.dev, ++ "NCSI: Channel %u added to queue (link %s)\n", ++ nc->id, ++ ncm->data[2] & 0x1 ? "up" : "down"); ++ } ++ ++ spin_unlock_irqrestore(&nc->lock, cflags); ++ ++ if (with_link && !np->multi_channel) ++ break; + } ++ if (with_link && !ndp->multi_package) ++ break; + } + +- if (!found) { ++ if (list_empty(&ndp->channel_queue) && found) { ++ netdev_info(ndp->ndev.dev, ++ "NCSI: No channel with link found, configuring channel %u\n", ++ found->id); ++ spin_lock_irqsave(&ndp->lock, flags); ++ list_add_tail_rcu(&found->link, &ndp->channel_queue); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ } else if (!found) { + netdev_warn(ndp->ndev.dev, +- "NCSI: No channel found with link\n"); ++ "NCSI: No channel found to configure!\n"); + ncsi_report_link(ndp, true); + return -ENODEV; + } + +- ncm = &found->modes[NCSI_MODE_LINK]; +- netdev_dbg(ndp->ndev.dev, +- "NCSI: Channel %u added to queue (link %s)\n", +- found->id, ncm->data[2] & 0x1 ? "up" : "down"); +- +-out: +- spin_lock_irqsave(&ndp->lock, flags); +- list_add_tail_rcu(&found->link, &ndp->channel_queue); +- spin_unlock_irqrestore(&ndp->lock, flags); +- + return ncsi_process_next_channel(ndp); + } + +@@ -1050,35 +1242,6 @@ static bool ncsi_check_hwa(struct ncsi_dev_priv *ndp) + return false; + } + +-static int ncsi_enable_hwa(struct ncsi_dev_priv *ndp) +-{ +- struct ncsi_package *np; +- struct ncsi_channel *nc; +- unsigned long flags; +- +- /* Move all available channels to processing queue */ +- spin_lock_irqsave(&ndp->lock, flags); +- NCSI_FOR_EACH_PACKAGE(ndp, np) { +- NCSI_FOR_EACH_CHANNEL(np, nc) { +- WARN_ON_ONCE(nc->state != NCSI_CHANNEL_INACTIVE || +- !list_empty(&nc->link)); +- ncsi_stop_channel_monitor(nc); +- list_add_tail_rcu(&nc->link, &ndp->channel_queue); +- } +- } +- spin_unlock_irqrestore(&ndp->lock, flags); +- +- /* We can have no channels in extremely case */ +- if (list_empty(&ndp->channel_queue)) { +- netdev_err(ndp->ndev.dev, +- "NCSI: No available channels for HWA\n"); +- ncsi_report_link(ndp, false); +- return -ENOENT; +- } +- +- return ncsi_process_next_channel(ndp); +-} +- + static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) + { + struct ncsi_dev *nd = &ndp->ndev; +@@ -1110,70 +1273,28 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) + nd->state = ncsi_dev_state_probe_package; + break; + case ncsi_dev_state_probe_package: +- ndp->pending_req_num = 16; ++ ndp->pending_req_num = 1; + +- /* Select all possible packages */ + nca.type = NCSI_PKT_CMD_SP; + nca.bytes[0] = 1; ++ nca.package = ndp->package_probe_id; + nca.channel = NCSI_RESERVED_CHANNEL; +- for (index = 0; index < 8; index++) { +- nca.package = index; +- ret = ncsi_xmit_cmd(&nca); +- if (ret) +- goto error; +- } +- +- /* Disable all possible packages */ +- nca.type = NCSI_PKT_CMD_DP; +- for (index = 0; index < 8; index++) { +- nca.package = index; +- ret = ncsi_xmit_cmd(&nca); +- if (ret) +- goto error; +- } +- ++ ret = ncsi_xmit_cmd(&nca); ++ if (ret) ++ goto error; + nd->state = ncsi_dev_state_probe_channel; + break; + case ncsi_dev_state_probe_channel: +- if (!ndp->active_package) +- ndp->active_package = list_first_or_null_rcu( +- &ndp->packages, struct ncsi_package, node); +- else if (list_is_last(&ndp->active_package->node, +- &ndp->packages)) +- ndp->active_package = NULL; +- else +- ndp->active_package = list_next_entry( +- ndp->active_package, node); +- +- /* All available packages and channels are enumerated. The +- * enumeration happens for once when the NCSI interface is +- * started. So we need continue to start the interface after +- * the enumeration. +- * +- * We have to choose an active channel before configuring it. +- * Note that we possibly don't have active channel in extreme +- * situation. +- */ ++ ndp->active_package = ncsi_find_package(ndp, ++ ndp->package_probe_id); + if (!ndp->active_package) { +- ndp->flags |= NCSI_DEV_PROBED; +- if (ncsi_check_hwa(ndp)) +- ncsi_enable_hwa(ndp); +- else +- ncsi_choose_active_channel(ndp); +- return; ++ /* No response */ ++ nd->state = ncsi_dev_state_probe_dp; ++ schedule_work(&ndp->work); ++ break; + } +- +- /* Select the active package */ +- ndp->pending_req_num = 1; +- nca.type = NCSI_PKT_CMD_SP; +- nca.bytes[0] = 1; +- nca.package = ndp->active_package->id; +- nca.channel = NCSI_RESERVED_CHANNEL; +- ret = ncsi_xmit_cmd(&nca); +- if (ret) +- goto error; +- + nd->state = ncsi_dev_state_probe_cis; ++ schedule_work(&ndp->work); + break; + case ncsi_dev_state_probe_cis: + ndp->pending_req_num = NCSI_RESERVED_CHANNEL; +@@ -1222,22 +1343,35 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) + case ncsi_dev_state_probe_dp: + ndp->pending_req_num = 1; + +- /* Deselect the active package */ ++ /* Deselect the current package */ + nca.type = NCSI_PKT_CMD_DP; +- nca.package = ndp->active_package->id; ++ nca.package = ndp->package_probe_id; + nca.channel = NCSI_RESERVED_CHANNEL; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + +- /* Scan channels in next package */ +- nd->state = ncsi_dev_state_probe_channel; ++ /* Probe next package */ ++ ndp->package_probe_id++; ++ if (ndp->package_probe_id >= 8) { ++ /* Probe finished */ ++ ndp->flags |= NCSI_DEV_PROBED; ++ break; ++ } ++ nd->state = ncsi_dev_state_probe_package; ++ ndp->active_package = NULL; + break; + default: + netdev_warn(nd->dev, "Wrong NCSI state 0x%0x in enumeration\n", + nd->state); + } + ++ if (ndp->flags & NCSI_DEV_PROBED) { ++ /* Check if all packages have HWA support */ ++ ncsi_check_hwa(ndp); ++ ncsi_choose_active_channel(ndp); ++ } ++ + return; + error: + netdev_err(ndp->ndev.dev, +@@ -1556,6 +1690,7 @@ struct ncsi_dev *ncsi_register_dev(struct net_device *dev, + INIT_LIST_HEAD(&ndp->channel_queue); + INIT_LIST_HEAD(&ndp->vlan_vids); + INIT_WORK(&ndp->work, ncsi_dev_work); ++ ndp->package_whitelist = UINT_MAX; + + /* Initialize private NCSI device */ + spin_lock_init(&ndp->lock); +@@ -1592,26 +1727,19 @@ EXPORT_SYMBOL_GPL(ncsi_register_dev); + int ncsi_start_dev(struct ncsi_dev *nd) + { + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); +- int ret; + + if (nd->state != ncsi_dev_state_registered && + nd->state != ncsi_dev_state_functional) + return -ENOTTY; + + if (!(ndp->flags & NCSI_DEV_PROBED)) { ++ ndp->package_probe_id = 0; + nd->state = ncsi_dev_state_probe; + schedule_work(&ndp->work); + return 0; + } + +- if (ndp->flags & NCSI_DEV_HWA) { +- netdev_info(ndp->ndev.dev, "NCSI: Enabling HWA mode\n"); +- ret = ncsi_enable_hwa(ndp); +- } else { +- ret = ncsi_choose_active_channel(ndp); +- } +- +- return ret; ++ return ncsi_reset_dev(nd); + } + EXPORT_SYMBOL_GPL(ncsi_start_dev); + +@@ -1624,7 +1752,10 @@ void ncsi_stop_dev(struct ncsi_dev *nd) + int old_state; + unsigned long flags; + +- /* Stop the channel monitor and reset channel's state */ ++ /* Stop the channel monitor on any active channels. Don't reset the ++ * channel state so we know which were active when ncsi_start_dev() ++ * is next called. ++ */ + NCSI_FOR_EACH_PACKAGE(ndp, np) { + NCSI_FOR_EACH_CHANNEL(np, nc) { + ncsi_stop_channel_monitor(nc); +@@ -1632,7 +1763,6 @@ void ncsi_stop_dev(struct ncsi_dev *nd) + spin_lock_irqsave(&nc->lock, flags); + chained = !list_empty(&nc->link); + old_state = nc->state; +- nc->state = NCSI_CHANNEL_INACTIVE; + spin_unlock_irqrestore(&nc->lock, flags); + + WARN_ON_ONCE(chained || +@@ -1645,6 +1775,92 @@ void ncsi_stop_dev(struct ncsi_dev *nd) + } + EXPORT_SYMBOL_GPL(ncsi_stop_dev); + ++int ncsi_reset_dev(struct ncsi_dev *nd) ++{ ++ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); ++ struct ncsi_channel *nc, *active, *tmp; ++ struct ncsi_package *np; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ndp->lock, flags); ++ ++ if (!(ndp->flags & NCSI_DEV_RESET)) { ++ /* Haven't been called yet, check states */ ++ switch (nd->state & ncsi_dev_state_major) { ++ case ncsi_dev_state_registered: ++ case ncsi_dev_state_probe: ++ /* Not even probed yet - do nothing */ ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return 0; ++ case ncsi_dev_state_suspend: ++ case ncsi_dev_state_config: ++ /* Wait for the channel to finish its suspend/config ++ * operation; once it finishes it will check for ++ * NCSI_DEV_RESET and reset the state. ++ */ ++ ndp->flags |= NCSI_DEV_RESET; ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return 0; ++ } ++ } else { ++ switch (nd->state) { ++ case ncsi_dev_state_suspend_done: ++ case ncsi_dev_state_config_done: ++ case ncsi_dev_state_functional: ++ /* Ok */ ++ break; ++ default: ++ /* Current reset operation happening */ ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return 0; ++ } ++ } ++ ++ if (!list_empty(&ndp->channel_queue)) { ++ /* Clear any channel queue we may have interrupted */ ++ list_for_each_entry_safe(nc, tmp, &ndp->channel_queue, link) ++ list_del_init(&nc->link); ++ } ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ active = NULL; ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ NCSI_FOR_EACH_CHANNEL(np, nc) { ++ spin_lock_irqsave(&nc->lock, flags); ++ ++ if (nc->state == NCSI_CHANNEL_ACTIVE) { ++ active = nc; ++ nc->state = NCSI_CHANNEL_INVISIBLE; ++ spin_unlock_irqrestore(&nc->lock, flags); ++ ncsi_stop_channel_monitor(nc); ++ break; ++ } ++ ++ spin_unlock_irqrestore(&nc->lock, flags); ++ } ++ if (active) ++ break; ++ } ++ ++ if (!active) { ++ /* Done */ ++ spin_lock_irqsave(&ndp->lock, flags); ++ ndp->flags &= ~NCSI_DEV_RESET; ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ return ncsi_choose_active_channel(ndp); ++ } ++ ++ spin_lock_irqsave(&ndp->lock, flags); ++ ndp->flags |= NCSI_DEV_RESET; ++ ndp->active_channel = active; ++ ndp->active_package = active->package; ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ nd->state = ncsi_dev_state_suspend; ++ schedule_work(&ndp->work); ++ return 0; ++} ++ + void ncsi_unregister_dev(struct ncsi_dev *nd) + { + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); +diff --git a/net/ncsi/ncsi-netlink.c b/net/ncsi/ncsi-netlink.c +index 33314381b4f5..5d782445d2fc 100644 +--- a/net/ncsi/ncsi-netlink.c ++++ b/net/ncsi/ncsi-netlink.c +@@ -30,6 +30,9 @@ static const struct nla_policy ncsi_genl_policy[NCSI_ATTR_MAX + 1] = { + [NCSI_ATTR_PACKAGE_ID] = { .type = NLA_U32 }, + [NCSI_ATTR_CHANNEL_ID] = { .type = NLA_U32 }, + [NCSI_ATTR_DATA] = { .type = NLA_BINARY, .len = 2048 }, ++ [NCSI_ATTR_MULTI_FLAG] = { .type = NLA_FLAG }, ++ [NCSI_ATTR_PACKAGE_MASK] = { .type = NLA_U32 }, ++ [NCSI_ATTR_CHANNEL_MASK] = { .type = NLA_U32 }, + }; + + static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex) +@@ -69,7 +72,7 @@ static int ncsi_write_channel_info(struct sk_buff *skb, + nla_put_u32(skb, NCSI_CHANNEL_ATTR_LINK_STATE, m->data[2]); + if (nc->state == NCSI_CHANNEL_ACTIVE) + nla_put_flag(skb, NCSI_CHANNEL_ATTR_ACTIVE); +- if (ndp->force_channel == nc) ++ if (nc == nc->package->preferred_channel) + nla_put_flag(skb, NCSI_CHANNEL_ATTR_FORCED); + + nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MAJOR, nc->version.version); +@@ -114,7 +117,7 @@ static int ncsi_write_package_info(struct sk_buff *skb, + if (!pnest) + return -ENOMEM; + nla_put_u32(skb, NCSI_PKG_ATTR_ID, np->id); +- if (ndp->force_package == np) ++ if ((0x1 << np->id) == ndp->package_whitelist) + nla_put_flag(skb, NCSI_PKG_ATTR_FORCED); + cnest = nla_nest_start(skb, NCSI_PKG_ATTR_CHANNEL_LIST); + if (!cnest) { +@@ -290,49 +293,58 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) + package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); + package = NULL; + +- spin_lock_irqsave(&ndp->lock, flags); +- + NCSI_FOR_EACH_PACKAGE(ndp, np) + if (np->id == package_id) + package = np; + if (!package) { + /* The user has set a package that does not exist */ +- spin_unlock_irqrestore(&ndp->lock, flags); + return -ERANGE; + } + + channel = NULL; +- if (!info->attrs[NCSI_ATTR_CHANNEL_ID]) { +- /* Allow any channel */ +- channel_id = NCSI_RESERVED_CHANNEL; +- } else { ++ if (info->attrs[NCSI_ATTR_CHANNEL_ID]) { + channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); + NCSI_FOR_EACH_CHANNEL(package, nc) +- if (nc->id == channel_id) ++ if (nc->id == channel_id) { + channel = nc; ++ break; ++ } ++ if (!channel) { ++ netdev_info(ndp->ndev.dev, ++ "NCSI: Channel %u does not exist!\n", ++ channel_id); ++ return -ERANGE; ++ } + } + +- if (channel_id != NCSI_RESERVED_CHANNEL && !channel) { +- /* The user has set a channel that does not exist on this +- * package +- */ +- spin_unlock_irqrestore(&ndp->lock, flags); +- netdev_info(ndp->ndev.dev, "NCSI: Channel %u does not exist!\n", +- channel_id); +- return -ERANGE; +- } +- +- ndp->force_package = package; +- ndp->force_channel = channel; ++ spin_lock_irqsave(&ndp->lock, flags); ++ ndp->package_whitelist = 0x1 << package->id; ++ ndp->multi_package = false; + spin_unlock_irqrestore(&ndp->lock, flags); + +- netdev_info(ndp->ndev.dev, "Set package 0x%x, channel 0x%x%s as preferred\n", +- package_id, channel_id, +- channel_id == NCSI_RESERVED_CHANNEL ? " (any)" : ""); ++ spin_lock_irqsave(&package->lock, flags); ++ package->multi_channel = false; ++ if (channel) { ++ package->channel_whitelist = 0x1 << channel->id; ++ package->preferred_channel = channel; ++ } else { ++ /* Allow any channel */ ++ package->channel_whitelist = UINT_MAX; ++ package->preferred_channel = NULL; ++ } ++ spin_unlock_irqrestore(&package->lock, flags); ++ ++ if (channel) ++ netdev_info(ndp->ndev.dev, ++ "Set package 0x%x, channel 0x%x as preferred\n", ++ package_id, channel_id); ++ else ++ netdev_info(ndp->ndev.dev, "Set package 0x%x as preferred\n", ++ package_id); + +- /* Bounce the NCSI channel to set changes */ +- ncsi_stop_dev(&ndp->ndev); +- ncsi_start_dev(&ndp->ndev); ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); + + return 0; + } +@@ -340,6 +352,7 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) + static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) + { + struct ncsi_dev_priv *ndp; ++ struct ncsi_package *np; + unsigned long flags; + + if (!info || !info->attrs) +@@ -353,16 +366,24 @@ static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) + if (!ndp) + return -ENODEV; + +- /* Clear any override */ ++ /* Reset any whitelists and disable multi mode */ + spin_lock_irqsave(&ndp->lock, flags); +- ndp->force_package = NULL; +- ndp->force_channel = NULL; ++ ndp->package_whitelist = UINT_MAX; ++ ndp->multi_package = false; + spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ NCSI_FOR_EACH_PACKAGE(ndp, np) { ++ spin_lock_irqsave(&np->lock, flags); ++ np->multi_channel = false; ++ np->channel_whitelist = UINT_MAX; ++ np->preferred_channel = NULL; ++ spin_unlock_irqrestore(&np->lock, flags); ++ } + netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n"); + +- /* Bounce the NCSI channel to set changes */ +- ncsi_stop_dev(&ndp->ndev); +- ncsi_start_dev(&ndp->ndev); ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); + + return 0; + } +@@ -563,6 +584,138 @@ int ncsi_send_netlink_err(struct net_device *dev, + return nlmsg_unicast(net->genl_sock, skb, snd_portid); + } + ++static int ncsi_set_package_mask_nl(struct sk_buff *msg, ++ struct genl_info *info) ++{ ++ struct ncsi_dev_priv *ndp; ++ unsigned long flags; ++ int rc; ++ ++ if (!info || !info->attrs) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_IFINDEX]) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_PACKAGE_MASK]) ++ return -EINVAL; ++ ++ ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), ++ nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); ++ if (!ndp) ++ return -ENODEV; ++ ++ spin_lock_irqsave(&ndp->lock, flags); ++ if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) { ++ if (ndp->flags & NCSI_DEV_HWA) { ++ ndp->multi_package = true; ++ rc = 0; ++ } else { ++ netdev_err(ndp->ndev.dev, ++ "NCSI: Can't use multiple packages without HWA\n"); ++ rc = -EPERM; ++ } ++ } else { ++ ndp->multi_package = false; ++ rc = 0; ++ } ++ ++ if (!rc) ++ ndp->package_whitelist = ++ nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_MASK]); ++ spin_unlock_irqrestore(&ndp->lock, flags); ++ ++ if (!rc) { ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); ++ } ++ ++ return rc; ++} ++ ++static int ncsi_set_channel_mask_nl(struct sk_buff *msg, ++ struct genl_info *info) ++{ ++ struct ncsi_package *np, *package; ++ struct ncsi_channel *nc, *channel; ++ u32 package_id, channel_id; ++ struct ncsi_dev_priv *ndp; ++ unsigned long flags; ++ ++ if (!info || !info->attrs) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_IFINDEX]) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) ++ return -EINVAL; ++ ++ if (!info->attrs[NCSI_ATTR_CHANNEL_MASK]) ++ return -EINVAL; ++ ++ ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), ++ nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); ++ if (!ndp) ++ return -ENODEV; ++ ++ package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); ++ package = NULL; ++ NCSI_FOR_EACH_PACKAGE(ndp, np) ++ if (np->id == package_id) { ++ package = np; ++ break; ++ } ++ if (!package) ++ return -ERANGE; ++ ++ spin_lock_irqsave(&package->lock, flags); ++ ++ channel = NULL; ++ if (info->attrs[NCSI_ATTR_CHANNEL_ID]) { ++ channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]); ++ NCSI_FOR_EACH_CHANNEL(np, nc) ++ if (nc->id == channel_id) { ++ channel = nc; ++ break; ++ } ++ if (!channel) { ++ spin_unlock_irqrestore(&package->lock, flags); ++ return -ERANGE; ++ } ++ netdev_dbg(ndp->ndev.dev, ++ "NCSI: Channel %u set as preferred channel\n", ++ channel->id); ++ } ++ ++ package->channel_whitelist = ++ nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_MASK]); ++ if (package->channel_whitelist == 0) ++ netdev_dbg(ndp->ndev.dev, ++ "NCSI: Package %u set to all channels disabled\n", ++ package->id); ++ ++ package->preferred_channel = channel; ++ ++ if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) { ++ package->multi_channel = true; ++ netdev_info(ndp->ndev.dev, ++ "NCSI: Multi-channel enabled on package %u\n", ++ package_id); ++ } else { ++ package->multi_channel = false; ++ } ++ ++ spin_unlock_irqrestore(&package->lock, flags); ++ ++ /* Update channel configuration */ ++ if (!(ndp->flags & NCSI_DEV_RESET)) ++ ncsi_reset_dev(&ndp->ndev); ++ ++ return 0; ++} ++ + static const struct genl_ops ncsi_ops[] = { + { + .cmd = NCSI_CMD_PKG_INFO, +@@ -589,6 +742,18 @@ static const struct genl_ops ncsi_ops[] = { + .doit = ncsi_send_cmd_nl, + .flags = GENL_ADMIN_PERM, + }, ++ { ++ .cmd = NCSI_CMD_SET_PACKAGE_MASK, ++ .policy = ncsi_genl_policy, ++ .doit = ncsi_set_package_mask_nl, ++ .flags = GENL_ADMIN_PERM, ++ }, ++ { ++ .cmd = NCSI_CMD_SET_CHANNEL_MASK, ++ .policy = ncsi_genl_policy, ++ .doit = ncsi_set_channel_mask_nl, ++ .flags = GENL_ADMIN_PERM, ++ }, + }; + + static struct genl_family ncsi_genl_family __ro_after_init = { +diff --git a/net/ncsi/ncsi-rsp.c b/net/ncsi/ncsi-rsp.c +index 77e07ba3f493..de7737a27889 100644 +--- a/net/ncsi/ncsi-rsp.c ++++ b/net/ncsi/ncsi-rsp.c +@@ -256,7 +256,7 @@ static int ncsi_rsp_handler_dcnt(struct ncsi_request *nr) + if (!ncm->enable) + return 0; + +- ncm->enable = 1; ++ ncm->enable = 0; + return 0; + } + +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0038-media-aspeed-backport-ikvm-patches.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0038-media-aspeed-backport-ikvm-patches.patch new file mode 100644 index 000000000..b618c49da --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0038-media-aspeed-backport-ikvm-patches.patch @@ -0,0 +1,1982 @@ +From ba52b9e7f76879f888afce1f8e7e5ff180b7849b Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Fri, 9 Nov 2018 11:32:27 -0800 +Subject: [PATCH] Add Aspeed Video Engine Driver + +The Video Engine (VE) embedded in the Aspeed AST2400 and AST2500 SOCs +can capture and compress video data from digital or analog sources. With +the Aspeed chip acting a service processor, the Video Engine can capture +the host processor graphics output. + +Add a V4L2 driver to capture video data and compress it to JPEG images. +Make the video frames available through the V4L2 streaming interface. + +Signed-off-by: Eddie James +Reviewed-by: Rob Herring +Signed-off-by: Jae Hyun Yoo +--- + .../devicetree/bindings/media/aspeed-video.txt | 26 + + MAINTAINERS | 8 + + arch/arm/boot/dts/aspeed-g5.dtsi | 11 + + drivers/clk/clk-aspeed.c | 41 +- + drivers/media/platform/Kconfig | 9 + + drivers/media/platform/Makefile | 1 + + drivers/media/platform/aspeed-video.c | 1729 ++++++++++++++++++++ + include/dt-bindings/clock/aspeed-clock.h | 1 + + 8 files changed, 1824 insertions(+), 2 deletions(-) + create mode 100644 Documentation/devicetree/bindings/media/aspeed-video.txt + create mode 100644 drivers/media/platform/aspeed-video.c + +diff --git a/Documentation/devicetree/bindings/media/aspeed-video.txt b/Documentation/devicetree/bindings/media/aspeed-video.txt +new file mode 100644 +index 000000000000..78b464ae2672 +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/aspeed-video.txt +@@ -0,0 +1,26 @@ ++* Device tree bindings for Aspeed Video Engine ++ ++The Video Engine (VE) embedded in the Aspeed AST2400 and AST2500 SOCs can ++capture and compress video data from digital or analog sources. ++ ++Required properties: ++ - compatible: "aspeed,ast2400-video-engine" or ++ "aspeed,ast2500-video-engine" ++ - reg: contains the offset and length of the VE memory region ++ - clocks: clock specifiers for the syscon clocks associated with ++ the VE (ordering must match the clock-names property) ++ - clock-names: "vclk" and "eclk" ++ - resets: reset specifier for the syscon reset associated with ++ the VE ++ - interrupts: the interrupt associated with the VE on this platform ++ ++Example: ++ ++video-engine@1e700000 { ++ compatible = "aspeed,ast2500-video-engine"; ++ reg = <0x1e700000 0x20000>; ++ clocks = <&syscon ASPEED_CLK_GATE_VCLK>, <&syscon ASPEED_CLK_GATE_ECLK>; ++ clock-names = "vclk", "eclk"; ++ resets = <&syscon ASPEED_RESET_VIDEO>; ++ interrupts = <7>; ++}; +diff --git a/MAINTAINERS b/MAINTAINERS +index 9e9b19ecf6f7..fd4fdb3e6474 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -2350,6 +2350,14 @@ S: Maintained + F: Documentation/hwmon/asc7621 + F: drivers/hwmon/asc7621.c + ++ASPEED VIDEO ENGINE DRIVER ++M: Eddie James ++L: linux-media@vger.kernel.org ++L: openbmc@lists.ozlabs.org (moderated for non-subscribers) ++S: Maintained ++F: drivers/media/platform/aspeed-video.c ++F: Documentation/devicetree/bindings/media/aspeed-video.txt ++ + ASUS NOTEBOOKS AND EEEPC ACPI/WMI EXTRAS DRIVERS + M: Corentin Chary + L: acpi4asus-user@lists.sourceforge.net +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 0144d8bfa3fb..e55da933a70b 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -243,6 +243,17 @@ + interrupts = <0x19>; + }; + ++ video: video@1e700000 { ++ compatible = "aspeed,ast2500-video-engine"; ++ reg = <0x1e700000 0x20000>; ++ clocks = <&syscon ASPEED_CLK_GATE_VCLK>, ++ <&syscon ASPEED_CLK_GATE_ECLK>; ++ clock-names = "vclk", "eclk"; ++ resets = <&syscon ASPEED_RESET_VIDEO>; ++ interrupts = <7>; ++ status = "disabled"; ++ }; ++ + adc: adc@1e6e9000 { + compatible = "aspeed,ast2500-adc"; + reg = <0x1e6e9000 0xb0>; +diff --git a/drivers/clk/clk-aspeed.c b/drivers/clk/clk-aspeed.c +index 61d41645e4fe..2429a2464556 100644 +--- a/drivers/clk/clk-aspeed.c ++++ b/drivers/clk/clk-aspeed.c +@@ -96,7 +96,7 @@ struct aspeed_clk_gate { + /* TODO: ask Aspeed about the actual parent data */ + static const struct aspeed_gate_data aspeed_gates[] = { + /* clk rst name parent flags */ +- [ASPEED_CLK_GATE_ECLK] = { 0, -1, "eclk-gate", "eclk", 0 }, /* Video Engine */ ++ [ASPEED_CLK_GATE_ECLK] = { 0, 6, "eclk-gate", "eclk", 0 }, /* Video Engine */ + [ASPEED_CLK_GATE_GCLK] = { 1, 7, "gclk-gate", NULL, 0 }, /* 2D engine */ + [ASPEED_CLK_GATE_MCLK] = { 2, -1, "mclk-gate", "mpll", CLK_IS_CRITICAL }, /* SDRAM */ + [ASPEED_CLK_GATE_VCLK] = { 3, 6, "vclk-gate", NULL, 0 }, /* Video Capture */ +@@ -122,6 +122,24 @@ static const struct aspeed_gate_data aspeed_gates[] = { + [ASPEED_CLK_GATE_LHCCLK] = { 28, -1, "lhclk-gate", "lhclk", 0 }, /* LPC master/LPC+ */ + }; + ++static const char * const eclk_parent_names[] = { ++ "mpll", ++ "hpll", ++ "dpll", ++}; ++ ++static const struct clk_div_table ast2500_eclk_div_table[] = { ++ { 0x0, 2 }, ++ { 0x1, 2 }, ++ { 0x2, 3 }, ++ { 0x3, 4 }, ++ { 0x4, 5 }, ++ { 0x5, 6 }, ++ { 0x6, 7 }, ++ { 0x7, 8 }, ++ { 0 } ++}; ++ + static const struct clk_div_table ast2500_mac_div_table[] = { + { 0x0, 4 }, /* Yep, really. Aspeed confirmed this is correct */ + { 0x1, 4 }, +@@ -201,18 +219,21 @@ static struct clk_hw *aspeed_ast2500_calc_pll(const char *name, u32 val) + + struct aspeed_clk_soc_data { + const struct clk_div_table *div_table; ++ const struct clk_div_table *eclk_div_table; + const struct clk_div_table *mac_div_table; + struct clk_hw *(*calc_pll)(const char *name, u32 val); + }; + + static const struct aspeed_clk_soc_data ast2500_data = { + .div_table = ast2500_div_table, ++ .eclk_div_table = ast2500_eclk_div_table, + .mac_div_table = ast2500_mac_div_table, + .calc_pll = aspeed_ast2500_calc_pll, + }; + + static const struct aspeed_clk_soc_data ast2400_data = { + .div_table = ast2400_div_table, ++ .eclk_div_table = ast2400_div_table, + .mac_div_table = ast2400_div_table, + .calc_pll = aspeed_ast2400_calc_pll, + }; +@@ -326,6 +347,7 @@ static const u8 aspeed_resets[] = { + [ASPEED_RESET_PECI] = 10, + [ASPEED_RESET_I2C] = 2, + [ASPEED_RESET_AHB] = 1, ++ [ASPEED_RESET_VIDEO] = 6, + + /* + * SCUD4 resets start at an offset to separate them from +@@ -548,6 +570,22 @@ static int aspeed_clk_probe(struct platform_device *pdev) + return PTR_ERR(hw); + aspeed_clk_data->hws[ASPEED_CLK_24M] = hw; + ++ hw = clk_hw_register_mux(dev, "eclk-mux", eclk_parent_names, ++ ARRAY_SIZE(eclk_parent_names), 0, ++ scu_base + ASPEED_CLK_SELECTION, 2, 0x3, 0, ++ &aspeed_clk_lock); ++ if (IS_ERR(hw)) ++ return PTR_ERR(hw); ++ aspeed_clk_data->hws[ASPEED_CLK_ECLK_MUX] = hw; ++ ++ hw = clk_hw_register_divider_table(dev, "eclk", "eclk-mux", 0, ++ scu_base + ASPEED_CLK_SELECTION, 28, ++ 3, 0, soc_data->eclk_div_table, ++ &aspeed_clk_lock); ++ if (IS_ERR(hw)) ++ return PTR_ERR(hw); ++ aspeed_clk_data->hws[ASPEED_CLK_ECLK] = hw; ++ + /* + * TODO: There are a number of clocks that not included in this driver + * as more information is required: +@@ -557,7 +595,6 @@ static int aspeed_clk_probe(struct platform_device *pdev) + * RGMII + * RMII + * UART[1..5] clock source mux +- * Video Engine (ECLK) mux and clock divider + */ + + for (i = 0; i < ARRAY_SIZE(aspeed_gates); i++) { +diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig +index 54fe90acb5b2..d6edf2d28f9b 100644 +--- a/drivers/media/platform/Kconfig ++++ b/drivers/media/platform/Kconfig +@@ -32,6 +32,15 @@ source "drivers/media/platform/davinci/Kconfig" + + source "drivers/media/platform/omap/Kconfig" + ++config VIDEO_ASPEED ++ tristate "Aspeed AST2400 and AST2500 Video Engine driver" ++ depends on VIDEO_V4L2 ++ select VIDEOBUF2_DMA_CONTIG ++ help ++ Support for the Aspeed Video Engine (VE) embedded in the Aspeed ++ AST2400 and AST2500 SOCs. The VE can capture and compress video data ++ from digital or analog sources. ++ + config VIDEO_SH_VOU + tristate "SuperH VOU video output driver" + depends on MEDIA_CAMERA_SUPPORT +diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile +index 41322ab65802..205c33a004fc 100644 +--- a/drivers/media/platform/Makefile ++++ b/drivers/media/platform/Makefile +@@ -3,6 +3,7 @@ + # Makefile for the video capture/playback device drivers. + # + ++obj-$(CONFIG_VIDEO_ASPEED) += aspeed-video.o + obj-$(CONFIG_VIDEO_CADENCE) += cadence/ + obj-$(CONFIG_VIDEO_VIA_CAMERA) += via-camera.o + obj-$(CONFIG_VIDEO_CAFE_CCIC) += marvell-ccic/ +diff --git a/drivers/media/platform/aspeed-video.c b/drivers/media/platform/aspeed-video.c +new file mode 100644 +index 000000000000..dfec813f50a9 +--- /dev/null ++++ b/drivers/media/platform/aspeed-video.c +@@ -0,0 +1,1729 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DEVICE_NAME "aspeed-video" ++ ++#define ASPEED_VIDEO_JPEG_NUM_QUALITIES 12 ++#define ASPEED_VIDEO_JPEG_HEADER_SIZE 10 ++#define ASPEED_VIDEO_JPEG_QUANT_SIZE 116 ++#define ASPEED_VIDEO_JPEG_DCT_SIZE 34 ++ ++#define MAX_FRAME_RATE 60 ++#define MAX_HEIGHT 1200 ++#define MAX_WIDTH 1920 ++#define MIN_HEIGHT 480 ++#define MIN_WIDTH 640 ++ ++#define NUM_POLARITY_CHECKS 10 ++#define INVALID_RESOLUTION_RETRIES 2 ++#define INVALID_RESOLUTION_DELAY msecs_to_jiffies(250) ++#define RESOLUTION_CHANGE_DELAY msecs_to_jiffies(500) ++#define MODE_DETECT_TIMEOUT msecs_to_jiffies(500) ++#define STOP_TIMEOUT msecs_to_jiffies(1000) ++#define DIRECT_FETCH_THRESHOLD 0x0c0000 /* 1024 * 768 */ ++ ++#define VE_MAX_SRC_BUFFER_SIZE 0x8ca000 /* 1920 * 1200, 32bpp */ ++#define VE_JPEG_HEADER_SIZE 0x006000 /* 512 * 12 * 4 */ ++ ++#define VE_PROTECTION_KEY 0x000 ++#define VE_PROTECTION_KEY_UNLOCK 0x1a038aa8 ++ ++#define VE_SEQ_CTRL 0x004 ++#define VE_SEQ_CTRL_TRIG_MODE_DET BIT(0) ++#define VE_SEQ_CTRL_TRIG_CAPTURE BIT(1) ++#define VE_SEQ_CTRL_FORCE_IDLE BIT(2) ++#define VE_SEQ_CTRL_MULT_FRAME BIT(3) ++#define VE_SEQ_CTRL_TRIG_COMP BIT(4) ++#define VE_SEQ_CTRL_AUTO_COMP BIT(5) ++#define VE_SEQ_CTRL_EN_WATCHDOG BIT(7) ++#define VE_SEQ_CTRL_YUV420 BIT(10) ++#define VE_SEQ_CTRL_COMP_FMT GENMASK(11, 10) ++#define VE_SEQ_CTRL_HALT BIT(12) ++#define VE_SEQ_CTRL_EN_WATCHDOG_COMP BIT(14) ++#define VE_SEQ_CTRL_TRIG_JPG BIT(15) ++#define VE_SEQ_CTRL_CAP_BUSY BIT(16) ++#define VE_SEQ_CTRL_COMP_BUSY BIT(18) ++ ++#ifdef CONFIG_MACH_ASPEED_G5 ++#define VE_SEQ_CTRL_JPEG_MODE BIT(13) /* AST2500 */ ++#else ++#define VE_SEQ_CTRL_JPEG_MODE BIT(8) /* AST2400 */ ++#endif /* CONFIG_MACH_ASPEED_G5 */ ++ ++#define VE_CTRL 0x008 ++#define VE_CTRL_HSYNC_POL BIT(0) ++#define VE_CTRL_VSYNC_POL BIT(1) ++#define VE_CTRL_SOURCE BIT(2) ++#define VE_CTRL_INT_DE BIT(4) ++#define VE_CTRL_DIRECT_FETCH BIT(5) ++#define VE_CTRL_YUV BIT(6) ++#define VE_CTRL_RGB BIT(7) ++#define VE_CTRL_CAPTURE_FMT GENMASK(7, 6) ++#define VE_CTRL_AUTO_OR_CURSOR BIT(8) ++#define VE_CTRL_CLK_INVERSE BIT(11) ++#define VE_CTRL_CLK_DELAY GENMASK(11, 9) ++#define VE_CTRL_INTERLACE BIT(14) ++#define VE_CTRL_HSYNC_POL_CTRL BIT(15) ++#define VE_CTRL_FRC GENMASK(23, 16) ++ ++#define VE_TGS_0 0x00c ++#define VE_TGS_1 0x010 ++#define VE_TGS_FIRST GENMASK(28, 16) ++#define VE_TGS_LAST GENMASK(12, 0) ++ ++#define VE_SCALING_FACTOR 0x014 ++#define VE_SCALING_FILTER0 0x018 ++#define VE_SCALING_FILTER1 0x01c ++#define VE_SCALING_FILTER2 0x020 ++#define VE_SCALING_FILTER3 0x024 ++ ++#define VE_CAP_WINDOW 0x030 ++#define VE_COMP_WINDOW 0x034 ++#define VE_COMP_PROC_OFFSET 0x038 ++#define VE_COMP_OFFSET 0x03c ++#define VE_JPEG_ADDR 0x040 ++#define VE_SRC0_ADDR 0x044 ++#define VE_SRC_SCANLINE_OFFSET 0x048 ++#define VE_SRC1_ADDR 0x04c ++#define VE_COMP_ADDR 0x054 ++ ++#define VE_STREAM_BUF_SIZE 0x058 ++#define VE_STREAM_BUF_SIZE_N_PACKETS GENMASK(5, 3) ++#define VE_STREAM_BUF_SIZE_P_SIZE GENMASK(2, 0) ++ ++#define VE_COMP_CTRL 0x060 ++#define VE_COMP_CTRL_VQ_DCT_ONLY BIT(0) ++#define VE_COMP_CTRL_VQ_4COLOR BIT(1) ++#define VE_COMP_CTRL_QUANTIZE BIT(2) ++#define VE_COMP_CTRL_EN_BQ BIT(4) ++#define VE_COMP_CTRL_EN_CRYPTO BIT(5) ++#define VE_COMP_CTRL_DCT_CHR GENMASK(10, 6) ++#define VE_COMP_CTRL_DCT_LUM GENMASK(15, 11) ++#define VE_COMP_CTRL_EN_HQ BIT(16) ++#define VE_COMP_CTRL_RSVD BIT(19) ++#define VE_COMP_CTRL_ENCODE GENMASK(21, 20) ++#define VE_COMP_CTRL_HQ_DCT_CHR GENMASK(26, 22) ++#define VE_COMP_CTRL_HQ_DCT_LUM GENMASK(31, 27) ++ ++#define VE_OFFSET_COMP_STREAM 0x078 ++ ++#define VE_SRC_LR_EDGE_DET 0x090 ++#define VE_SRC_LR_EDGE_DET_LEFT GENMASK(11, 0) ++#define VE_SRC_LR_EDGE_DET_NO_V BIT(12) ++#define VE_SRC_LR_EDGE_DET_NO_H BIT(13) ++#define VE_SRC_LR_EDGE_DET_NO_DISP BIT(14) ++#define VE_SRC_LR_EDGE_DET_NO_CLK BIT(15) ++#define VE_SRC_LR_EDGE_DET_RT_SHF 16 ++#define VE_SRC_LR_EDGE_DET_RT GENMASK(27, VE_SRC_LR_EDGE_DET_RT_SHF) ++#define VE_SRC_LR_EDGE_DET_INTERLACE BIT(31) ++ ++#define VE_SRC_TB_EDGE_DET 0x094 ++#define VE_SRC_TB_EDGE_DET_TOP GENMASK(12, 0) ++#define VE_SRC_TB_EDGE_DET_BOT_SHF 16 ++#define VE_SRC_TB_EDGE_DET_BOT GENMASK(28, VE_SRC_TB_EDGE_DET_BOT_SHF) ++ ++#define VE_MODE_DETECT_STATUS 0x098 ++#define VE_MODE_DETECT_H_PIXELS GENMASK(11, 0) ++#define VE_MODE_DETECT_V_LINES_SHF 16 ++#define VE_MODE_DETECT_V_LINES GENMASK(27, VE_MODE_DETECT_V_LINES_SHF) ++#define VE_MODE_DETECT_STATUS_VSYNC BIT(28) ++#define VE_MODE_DETECT_STATUS_HSYNC BIT(29) ++ ++#define VE_SYNC_STATUS 0x09c ++#define VE_SYNC_STATUS_HSYNC GENMASK(11, 0) ++#define VE_SYNC_STATUS_VSYNC_SHF 16 ++#define VE_SYNC_STATUS_VSYNC GENMASK(27, VE_SYNC_STATUS_VSYNC_SHF) ++ ++#define VE_INTERRUPT_CTRL 0x304 ++#define VE_INTERRUPT_STATUS 0x308 ++#define VE_INTERRUPT_MODE_DETECT_WD BIT(0) ++#define VE_INTERRUPT_CAPTURE_COMPLETE BIT(1) ++#define VE_INTERRUPT_COMP_READY BIT(2) ++#define VE_INTERRUPT_COMP_COMPLETE BIT(3) ++#define VE_INTERRUPT_MODE_DETECT BIT(4) ++#define VE_INTERRUPT_FRAME_COMPLETE BIT(5) ++#define VE_INTERRUPT_DECODE_ERR BIT(6) ++#define VE_INTERRUPT_HALT_READY BIT(8) ++#define VE_INTERRUPT_HANG_WD BIT(9) ++#define VE_INTERRUPT_STREAM_DESC BIT(10) ++#define VE_INTERRUPT_VSYNC_DESC BIT(11) ++ ++#define VE_MODE_DETECT 0x30c ++#define VE_MEM_RESTRICT_START 0x310 ++#define VE_MEM_RESTRICT_END 0x314 ++ ++enum { ++ VIDEO_MODE_DETECT_DONE, ++ VIDEO_RES_CHANGE, ++ VIDEO_RES_DETECT, ++ VIDEO_STREAMING, ++ VIDEO_FRAME_INPRG, ++ VIDEO_STOPPED, ++}; ++ ++struct aspeed_video_addr { ++ unsigned int size; ++ dma_addr_t dma; ++ void *virt; ++}; ++ ++struct aspeed_video_buffer { ++ struct vb2_v4l2_buffer vb; ++ struct list_head link; ++}; ++ ++#define to_aspeed_video_buffer(x) \ ++ container_of((x), struct aspeed_video_buffer, vb) ++ ++struct aspeed_video { ++ void __iomem *base; ++ struct clk *eclk; ++ struct clk *vclk; ++ struct reset_control *rst; ++ ++ struct device *dev; ++ struct v4l2_ctrl_handler ctrl_handler; ++ struct v4l2_device v4l2_dev; ++ struct v4l2_pix_format pix_fmt; ++ struct v4l2_bt_timings active_timings; ++ struct v4l2_bt_timings detected_timings; ++ u32 v4l2_input_status; ++ struct vb2_queue queue; ++ struct video_device vdev; ++ struct mutex video_lock; /* v4l2 and videobuf2 lock */ ++ ++ wait_queue_head_t wait; ++ spinlock_t lock; /* buffer list lock */ ++ struct delayed_work res_work; ++ struct list_head buffers; ++ unsigned long flags; ++ unsigned int sequence; ++ ++ unsigned int max_compressed_size; ++ struct aspeed_video_addr srcs[2]; ++ struct aspeed_video_addr jpeg; ++ ++ bool yuv420; ++ unsigned int frame_rate; ++ unsigned int jpeg_quality; ++ ++ unsigned int frame_bottom; ++ unsigned int frame_left; ++ unsigned int frame_right; ++ unsigned int frame_top; ++}; ++ ++#define to_aspeed_video(x) container_of((x), struct aspeed_video, v4l2_dev) ++ ++static const u32 aspeed_video_jpeg_header[ASPEED_VIDEO_JPEG_HEADER_SIZE] = { ++ 0xe0ffd8ff, 0x464a1000, 0x01004649, 0x60000101, 0x00006000, 0x0f00feff, ++ 0x00002d05, 0x00000000, 0x00000000, 0x00dbff00 ++}; ++ ++static const u32 aspeed_video_jpeg_quant[ASPEED_VIDEO_JPEG_QUANT_SIZE] = { ++ 0x081100c0, 0x00000000, 0x00110103, 0x03011102, 0xc4ff0111, 0x00001f00, ++ 0x01010501, 0x01010101, 0x00000000, 0x00000000, 0x04030201, 0x08070605, ++ 0xff0b0a09, 0x10b500c4, 0x03010200, 0x03040203, 0x04040505, 0x7d010000, ++ 0x00030201, 0x12051104, 0x06413121, 0x07615113, 0x32147122, 0x08a19181, ++ 0xc1b14223, 0xf0d15215, 0x72623324, 0x160a0982, 0x1a191817, 0x28272625, ++ 0x35342a29, 0x39383736, 0x4544433a, 0x49484746, 0x5554534a, 0x59585756, ++ 0x6564635a, 0x69686766, 0x7574736a, 0x79787776, 0x8584837a, 0x89888786, ++ 0x9493928a, 0x98979695, 0xa3a29a99, 0xa7a6a5a4, 0xb2aaa9a8, 0xb6b5b4b3, ++ 0xbab9b8b7, 0xc5c4c3c2, 0xc9c8c7c6, 0xd4d3d2ca, 0xd8d7d6d5, 0xe2e1dad9, ++ 0xe6e5e4e3, 0xeae9e8e7, 0xf4f3f2f1, 0xf8f7f6f5, 0xc4fffaf9, 0x00011f00, ++ 0x01010103, 0x01010101, 0x00000101, 0x00000000, 0x04030201, 0x08070605, ++ 0xff0b0a09, 0x11b500c4, 0x02010200, 0x04030404, 0x04040507, 0x77020100, ++ 0x03020100, 0x21050411, 0x41120631, 0x71610751, 0x81322213, 0x91421408, ++ 0x09c1b1a1, 0xf0523323, 0xd1726215, 0x3424160a, 0x17f125e1, 0x261a1918, ++ 0x2a292827, 0x38373635, 0x44433a39, 0x48474645, 0x54534a49, 0x58575655, ++ 0x64635a59, 0x68676665, 0x74736a69, 0x78777675, 0x83827a79, 0x87868584, ++ 0x928a8988, 0x96959493, 0x9a999897, 0xa5a4a3a2, 0xa9a8a7a6, 0xb4b3b2aa, ++ 0xb8b7b6b5, 0xc3c2bab9, 0xc7c6c5c4, 0xd2cac9c8, 0xd6d5d4d3, 0xdad9d8d7, ++ 0xe5e4e3e2, 0xe9e8e7e6, 0xf4f3f2ea, 0xf8f7f6f5, 0xdafffaf9, 0x01030c00, ++ 0x03110200, 0x003f0011 ++}; ++ ++static const u32 aspeed_video_jpeg_dct[ASPEED_VIDEO_JPEG_NUM_QUALITIES] ++ [ASPEED_VIDEO_JPEG_DCT_SIZE] = { ++ { 0x0d140043, 0x0c0f110f, 0x11101114, 0x17141516, 0x1e20321e, ++ 0x3d1e1b1b, 0x32242e2b, 0x4b4c3f48, 0x44463f47, 0x61735a50, ++ 0x566c5550, 0x88644644, 0x7a766c65, 0x4d808280, 0x8c978d60, ++ 0x7e73967d, 0xdbff7b80, 0x1f014300, 0x272d2121, 0x3030582d, ++ 0x697bb958, 0xb8b9b97b, 0xb9b8a6a6, 0xb9b9b9b9, 0xb9b9b9b9, ++ 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, ++ 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, 0xffb9b9b9 }, ++ { 0x0c110043, 0x0a0d0f0d, 0x0f0e0f11, 0x14111213, 0x1a1c2b1a, ++ 0x351a1818, 0x2b1f2826, 0x4142373f, 0x3c3d373e, 0x55644e46, ++ 0x4b5f4a46, 0x77573d3c, 0x6b675f58, 0x43707170, 0x7a847b54, ++ 0x6e64836d, 0xdbff6c70, 0x1b014300, 0x22271d1d, 0x2a2a4c27, ++ 0x5b6ba04c, 0xa0a0a06b, 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, ++ 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, ++ 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, 0xffa0a0a0 }, ++ { 0x090e0043, 0x090a0c0a, 0x0c0b0c0e, 0x110e0f10, 0x15172415, ++ 0x2c151313, 0x241a211f, 0x36372e34, 0x31322e33, 0x4653413a, ++ 0x3e4e3d3a, 0x62483231, 0x58564e49, 0x385d5e5d, 0x656d6645, ++ 0x5b536c5a, 0xdbff595d, 0x16014300, 0x1c201818, 0x22223f20, ++ 0x4b58853f, 0x85858558, 0x85858585, 0x85858585, 0x85858585, ++ 0x85858585, 0x85858585, 0x85858585, 0x85858585, 0x85858585, ++ 0x85858585, 0x85858585, 0x85858585, 0xff858585 }, ++ { 0x070b0043, 0x07080a08, 0x0a090a0b, 0x0d0b0c0c, 0x11121c11, ++ 0x23110f0f, 0x1c141a19, 0x2b2b2429, 0x27282428, 0x3842332e, ++ 0x313e302e, 0x4e392827, 0x46443e3a, 0x2c4a4a4a, 0x50565137, ++ 0x48425647, 0xdbff474a, 0x12014300, 0x161a1313, 0x1c1c331a, ++ 0x3d486c33, 0x6c6c6c48, 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, ++ 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, ++ 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, 0xff6c6c6c }, ++ { 0x06090043, 0x05060706, 0x07070709, 0x0a09090a, 0x0d0e160d, ++ 0x1b0d0c0c, 0x16101413, 0x21221c20, 0x1e1f1c20, 0x2b332824, ++ 0x26302624, 0x3d2d1f1e, 0x3735302d, 0x22393a39, 0x3f443f2b, ++ 0x38334338, 0xdbff3739, 0x0d014300, 0x11130e0e, 0x15152613, ++ 0x2d355026, 0x50505035, 0x50505050, 0x50505050, 0x50505050, ++ 0x50505050, 0x50505050, 0x50505050, 0x50505050, 0x50505050, ++ 0x50505050, 0x50505050, 0x50505050, 0xff505050 }, ++ { 0x04060043, 0x03040504, 0x05040506, 0x07060606, 0x09090f09, ++ 0x12090808, 0x0f0a0d0d, 0x16161315, 0x14151315, 0x1d221b18, ++ 0x19201918, 0x281e1514, 0x2423201e, 0x17262726, 0x2a2d2a1c, ++ 0x25222d25, 0xdbff2526, 0x09014300, 0x0b0d0a0a, 0x0e0e1a0d, ++ 0x1f25371a, 0x37373725, 0x37373737, 0x37373737, 0x37373737, ++ 0x37373737, 0x37373737, 0x37373737, 0x37373737, 0x37373737, ++ 0x37373737, 0x37373737, 0x37373737, 0xff373737 }, ++ { 0x02030043, 0x01020202, 0x02020203, 0x03030303, 0x04040704, ++ 0x09040404, 0x07050606, 0x0b0b090a, 0x0a0a090a, 0x0e110d0c, ++ 0x0c100c0c, 0x140f0a0a, 0x1211100f, 0x0b131313, 0x1516150e, ++ 0x12111612, 0xdbff1213, 0x04014300, 0x05060505, 0x07070d06, ++ 0x0f121b0d, 0x1b1b1b12, 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, ++ 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, ++ 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, 0xff1b1b1b }, ++ { 0x01020043, 0x01010101, 0x01010102, 0x02020202, 0x03030503, ++ 0x06030202, 0x05030404, 0x07070607, 0x06070607, 0x090b0908, ++ 0x080a0808, 0x0d0a0706, 0x0c0b0a0a, 0x070c0d0c, 0x0e0f0e09, ++ 0x0c0b0f0c, 0xdbff0c0c, 0x03014300, 0x03040303, 0x04040804, ++ 0x0a0c1208, 0x1212120c, 0x12121212, 0x12121212, 0x12121212, ++ 0x12121212, 0x12121212, 0x12121212, 0x12121212, 0x12121212, ++ 0x12121212, 0x12121212, 0x12121212, 0xff121212 }, ++ { 0x01020043, 0x01010101, 0x01010102, 0x02020202, 0x03030503, ++ 0x06030202, 0x05030404, 0x07070607, 0x06070607, 0x090b0908, ++ 0x080a0808, 0x0d0a0706, 0x0c0b0a0a, 0x070c0d0c, 0x0e0f0e09, ++ 0x0c0b0f0c, 0xdbff0c0c, 0x02014300, 0x03030202, 0x04040703, ++ 0x080a0f07, 0x0f0f0f0a, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, ++ 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, ++ 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0xff0f0f0f }, ++ { 0x01010043, 0x01010101, 0x01010101, 0x01010101, 0x02020302, ++ 0x04020202, 0x03020303, 0x05050405, 0x05050405, 0x07080606, ++ 0x06080606, 0x0a070505, 0x09080807, 0x05090909, 0x0a0b0a07, ++ 0x09080b09, 0xdbff0909, 0x02014300, 0x02030202, 0x03030503, ++ 0x07080c05, 0x0c0c0c08, 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, ++ 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, ++ 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, 0xff0c0c0c }, ++ { 0x01010043, 0x01010101, 0x01010101, 0x01010101, 0x01010201, ++ 0x03010101, 0x02010202, 0x03030303, 0x03030303, 0x04050404, ++ 0x04050404, 0x06050303, 0x06050505, 0x03060606, 0x07070704, ++ 0x06050706, 0xdbff0606, 0x01014300, 0x01020101, 0x02020402, ++ 0x05060904, 0x09090906, 0x09090909, 0x09090909, 0x09090909, ++ 0x09090909, 0x09090909, 0x09090909, 0x09090909, 0x09090909, ++ 0x09090909, 0x09090909, 0x09090909, 0xff090909 }, ++ { 0x01010043, 0x01010101, 0x01010101, 0x01010101, 0x01010101, ++ 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x02020202, ++ 0x02020202, 0x03020101, 0x03020202, 0x01030303, 0x03030302, ++ 0x03020303, 0xdbff0403, 0x01014300, 0x01010101, 0x01010201, ++ 0x03040602, 0x06060604, 0x06060606, 0x06060606, 0x06060606, ++ 0x06060606, 0x06060606, 0x06060606, 0x06060606, 0x06060606, ++ 0x06060606, 0x06060606, 0x06060606, 0xff060606 } ++}; ++ ++static const struct v4l2_dv_timings_cap aspeed_video_timings_cap = { ++ .type = V4L2_DV_BT_656_1120, ++ .bt = { ++ .min_width = MIN_WIDTH, ++ .max_width = MAX_WIDTH, ++ .min_height = MIN_HEIGHT, ++ .max_height = MAX_HEIGHT, ++ .min_pixelclock = 6574080, /* 640 x 480 x 24Hz */ ++ .max_pixelclock = 138240000, /* 1920 x 1200 x 60Hz */ ++ .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | ++ V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF, ++ .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | ++ V4L2_DV_BT_CAP_REDUCED_BLANKING | ++ V4L2_DV_BT_CAP_CUSTOM, ++ }, ++}; ++ ++static void aspeed_video_init_jpeg_table(u32 *table, bool yuv420) ++{ ++ int i; ++ unsigned int base; ++ ++ for (i = 0; i < ASPEED_VIDEO_JPEG_NUM_QUALITIES; i++) { ++ base = 256 * i; /* AST HW requires this header spacing */ ++ memcpy(&table[base], aspeed_video_jpeg_header, ++ sizeof(aspeed_video_jpeg_header)); ++ ++ base += ASPEED_VIDEO_JPEG_HEADER_SIZE; ++ memcpy(&table[base], aspeed_video_jpeg_dct[i], ++ sizeof(aspeed_video_jpeg_dct[i])); ++ ++ base += ASPEED_VIDEO_JPEG_DCT_SIZE; ++ memcpy(&table[base], aspeed_video_jpeg_quant, ++ sizeof(aspeed_video_jpeg_quant)); ++ ++ if (yuv420) ++ table[base + 2] = 0x00220103; ++ } ++} ++ ++static void aspeed_video_update(struct aspeed_video *video, u32 reg, u32 clear, ++ u32 bits) ++{ ++ u32 t = readl(video->base + reg); ++ u32 before = t; ++ ++ t &= ~clear; ++ t |= bits; ++ writel(t, video->base + reg); ++ dev_dbg(video->dev, "update %03x[%08x -> %08x]\n", reg, before, ++ readl(video->base + reg)); ++} ++ ++static u32 aspeed_video_read(struct aspeed_video *video, u32 reg) ++{ ++ u32 t = readl(video->base + reg); ++ ++ dev_dbg(video->dev, "read %03x[%08x]\n", reg, t); ++ return t; ++} ++ ++static void aspeed_video_write(struct aspeed_video *video, u32 reg, u32 val) ++{ ++ writel(val, video->base + reg); ++ dev_dbg(video->dev, "write %03x[%08x]\n", reg, ++ readl(video->base + reg)); ++} ++ ++static int aspeed_video_start_frame(struct aspeed_video *video) ++{ ++ dma_addr_t addr; ++ unsigned long flags; ++ struct aspeed_video_buffer *buf; ++ u32 seq_ctrl = aspeed_video_read(video, VE_SEQ_CTRL); ++ ++ if (video->v4l2_input_status) { ++ dev_dbg(video->dev, "No signal; don't start frame\n"); ++ return 0; ++ } ++ ++ if (!(seq_ctrl & VE_SEQ_CTRL_COMP_BUSY) || ++ !(seq_ctrl & VE_SEQ_CTRL_CAP_BUSY)) { ++ dev_err(video->dev, "Engine busy; don't start frame\n"); ++ return -EBUSY; ++ } ++ ++ spin_lock_irqsave(&video->lock, flags); ++ buf = list_first_entry_or_null(&video->buffers, ++ struct aspeed_video_buffer, link); ++ if (!buf) { ++ spin_unlock_irqrestore(&video->lock, flags); ++ dev_dbg(video->dev, "No buffers; don't start frame\n"); ++ return -EPROTO; ++ } ++ ++ set_bit(VIDEO_FRAME_INPRG, &video->flags); ++ addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); ++ spin_unlock_irqrestore(&video->lock, flags); ++ ++ aspeed_video_write(video, VE_COMP_PROC_OFFSET, 0); ++ aspeed_video_write(video, VE_COMP_OFFSET, 0); ++ aspeed_video_write(video, VE_COMP_ADDR, addr); ++ ++ aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, ++ VE_INTERRUPT_COMP_COMPLETE | ++ VE_INTERRUPT_CAPTURE_COMPLETE); ++ ++ aspeed_video_update(video, VE_SEQ_CTRL, 0, ++ VE_SEQ_CTRL_TRIG_CAPTURE | VE_SEQ_CTRL_TRIG_COMP); ++ ++ return 0; ++} ++ ++static void aspeed_video_enable_mode_detect(struct aspeed_video *video) ++{ ++ /* Enable mode detect interrupts */ ++ aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, ++ VE_INTERRUPT_MODE_DETECT); ++ ++ /* Trigger mode detect */ ++ aspeed_video_update(video, VE_SEQ_CTRL, 0, VE_SEQ_CTRL_TRIG_MODE_DET); ++} ++ ++static void aspeed_video_reset(struct aspeed_video *video) ++{ ++ /* Reset the engine */ ++ reset_control_assert(video->rst); ++ ++ /* Don't usleep here; function may be called in interrupt context */ ++ udelay(100); ++ reset_control_deassert(video->rst); ++} ++ ++static void aspeed_video_off(struct aspeed_video *video) ++{ ++ aspeed_video_reset(video); ++ ++ /* Turn off the relevant clocks */ ++ clk_disable_unprepare(video->vclk); ++ clk_disable_unprepare(video->eclk); ++} ++ ++static void aspeed_video_on(struct aspeed_video *video) ++{ ++ /* Turn on the relevant clocks */ ++ clk_prepare_enable(video->eclk); ++ clk_prepare_enable(video->vclk); ++ ++ aspeed_video_reset(video); ++} ++ ++static void aspeed_video_bufs_done(struct aspeed_video *video, ++ enum vb2_buffer_state state) ++{ ++ unsigned long flags; ++ struct aspeed_video_buffer *buf; ++ ++ spin_lock_irqsave(&video->lock, flags); ++ list_for_each_entry(buf, &video->buffers, link) ++ vb2_buffer_done(&buf->vb.vb2_buf, state); ++ INIT_LIST_HEAD(&video->buffers); ++ spin_unlock_irqrestore(&video->lock, flags); ++} ++ ++static void aspeed_video_irq_res_change(struct aspeed_video *video) ++{ ++ dev_dbg(video->dev, "Resolution changed; resetting\n"); ++ ++ set_bit(VIDEO_RES_CHANGE, &video->flags); ++ clear_bit(VIDEO_FRAME_INPRG, &video->flags); ++ ++ aspeed_video_off(video); ++ aspeed_video_bufs_done(video, VB2_BUF_STATE_ERROR); ++ ++ schedule_delayed_work(&video->res_work, RESOLUTION_CHANGE_DELAY); ++} ++ ++static irqreturn_t aspeed_video_irq(int irq, void *arg) ++{ ++ struct aspeed_video *video = arg; ++ u32 sts = aspeed_video_read(video, VE_INTERRUPT_STATUS); ++ ++ /* ++ * Resolution changed or signal was lost; reset the engine and ++ * re-initialize ++ */ ++ if (sts & VE_INTERRUPT_MODE_DETECT_WD) { ++ aspeed_video_irq_res_change(video); ++ return IRQ_HANDLED; ++ } ++ ++ if (sts & VE_INTERRUPT_MODE_DETECT) { ++ if (test_bit(VIDEO_RES_DETECT, &video->flags)) { ++ aspeed_video_update(video, VE_INTERRUPT_CTRL, ++ VE_INTERRUPT_MODE_DETECT, 0); ++ aspeed_video_write(video, VE_INTERRUPT_STATUS, ++ VE_INTERRUPT_MODE_DETECT); ++ ++ set_bit(VIDEO_MODE_DETECT_DONE, &video->flags); ++ wake_up_interruptible_all(&video->wait); ++ } else { ++ /* ++ * Signal acquired while NOT doing resolution ++ * detection; reset the engine and re-initialize ++ */ ++ aspeed_video_irq_res_change(video); ++ return IRQ_HANDLED; ++ } ++ } ++ ++ if ((sts & VE_INTERRUPT_COMP_COMPLETE) && ++ (sts & VE_INTERRUPT_CAPTURE_COMPLETE)) { ++ struct aspeed_video_buffer *buf; ++ u32 frame_size = aspeed_video_read(video, ++ VE_OFFSET_COMP_STREAM); ++ ++ spin_lock(&video->lock); ++ clear_bit(VIDEO_FRAME_INPRG, &video->flags); ++ buf = list_first_entry_or_null(&video->buffers, ++ struct aspeed_video_buffer, ++ link); ++ if (buf) { ++ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size); ++ ++ if (!list_is_last(&buf->link, &video->buffers)) { ++ buf->vb.vb2_buf.timestamp = ktime_get_ns(); ++ buf->vb.sequence = video->sequence++; ++ buf->vb.field = V4L2_FIELD_NONE; ++ vb2_buffer_done(&buf->vb.vb2_buf, ++ VB2_BUF_STATE_DONE); ++ list_del(&buf->link); ++ } ++ } ++ spin_unlock(&video->lock); ++ ++ aspeed_video_update(video, VE_SEQ_CTRL, ++ VE_SEQ_CTRL_TRIG_CAPTURE | ++ VE_SEQ_CTRL_FORCE_IDLE | ++ VE_SEQ_CTRL_TRIG_COMP, 0); ++ aspeed_video_update(video, VE_INTERRUPT_CTRL, ++ VE_INTERRUPT_COMP_COMPLETE | ++ VE_INTERRUPT_CAPTURE_COMPLETE, 0); ++ aspeed_video_write(video, VE_INTERRUPT_STATUS, ++ VE_INTERRUPT_COMP_COMPLETE | ++ VE_INTERRUPT_CAPTURE_COMPLETE); ++ ++ if (test_bit(VIDEO_STREAMING, &video->flags) && buf) ++ aspeed_video_start_frame(video); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static void aspeed_video_check_and_set_polarity(struct aspeed_video *video) ++{ ++ int i; ++ int hsync_counter = 0; ++ int vsync_counter = 0; ++ u32 sts; ++ ++ for (i = 0; i < NUM_POLARITY_CHECKS; ++i) { ++ sts = aspeed_video_read(video, VE_MODE_DETECT_STATUS); ++ if (sts & VE_MODE_DETECT_STATUS_VSYNC) ++ vsync_counter--; ++ else ++ vsync_counter++; ++ ++ if (sts & VE_MODE_DETECT_STATUS_HSYNC) ++ hsync_counter--; ++ else ++ hsync_counter++; ++ } ++ ++ if (hsync_counter < 0 || vsync_counter < 0) { ++ u32 ctrl; ++ ++ if (hsync_counter < 0) { ++ ctrl = VE_CTRL_HSYNC_POL; ++ video->detected_timings.polarities &= ++ ~V4L2_DV_HSYNC_POS_POL; ++ } else { ++ video->detected_timings.polarities |= ++ V4L2_DV_HSYNC_POS_POL; ++ } ++ ++ if (vsync_counter < 0) { ++ ctrl = VE_CTRL_VSYNC_POL; ++ video->detected_timings.polarities &= ++ ~V4L2_DV_VSYNC_POS_POL; ++ } else { ++ video->detected_timings.polarities |= ++ V4L2_DV_VSYNC_POS_POL; ++ } ++ ++ aspeed_video_update(video, VE_CTRL, 0, ctrl); ++ } ++} ++ ++static bool aspeed_video_alloc_buf(struct aspeed_video *video, ++ struct aspeed_video_addr *addr, ++ unsigned int size) ++{ ++ addr->virt = dma_alloc_coherent(video->dev, size, &addr->dma, ++ GFP_KERNEL); ++ if (!addr->virt) ++ return false; ++ ++ addr->size = size; ++ return true; ++} ++ ++static void aspeed_video_free_buf(struct aspeed_video *video, ++ struct aspeed_video_addr *addr) ++{ ++ dma_free_coherent(video->dev, addr->size, addr->virt, addr->dma); ++ addr->size = 0; ++ addr->dma = 0ULL; ++ addr->virt = NULL; ++} ++ ++/* ++ * Get the minimum HW-supported compression buffer size for the frame size. ++ * Assume worst-case JPEG compression size is 1/8 raw size. This should be ++ * plenty even for maximum quality; any worse and the engine will simply return ++ * incomplete JPEGs. ++ */ ++static void aspeed_video_calc_compressed_size(struct aspeed_video *video, ++ unsigned int frame_size) ++{ ++ int i, j; ++ u32 compression_buffer_size_reg = 0; ++ unsigned int size; ++ const unsigned int num_compression_packets = 4; ++ const unsigned int compression_packet_size = 1024; ++ const unsigned int max_compressed_size = frame_size / 2; /* 4bpp / 8 */ ++ ++ video->max_compressed_size = UINT_MAX; ++ ++ for (i = 0; i < 6; ++i) { ++ for (j = 0; j < 8; ++j) { ++ size = (num_compression_packets << i) * ++ (compression_packet_size << j); ++ if (size < max_compressed_size) ++ continue; ++ ++ if (size < video->max_compressed_size) { ++ compression_buffer_size_reg = (i << 3) | j; ++ video->max_compressed_size = size; ++ } ++ } ++ } ++ ++ aspeed_video_write(video, VE_STREAM_BUF_SIZE, ++ compression_buffer_size_reg); ++ ++ dev_dbg(video->dev, "Max compressed size: %x\n", ++ video->max_compressed_size); ++} ++ ++#define res_check(v) test_and_clear_bit(VIDEO_MODE_DETECT_DONE, &(v)->flags) ++ ++static void aspeed_video_get_resolution(struct aspeed_video *video) ++{ ++ bool invalid_resolution = true; ++ int rc; ++ int tries = 0; ++ u32 mds; ++ u32 src_lr_edge; ++ u32 src_tb_edge; ++ u32 sync; ++ struct v4l2_bt_timings *det = &video->detected_timings; ++ ++ det->width = MIN_WIDTH; ++ det->height = MIN_HEIGHT; ++ video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; ++ ++ /* ++ * Since we need max buffer size for detection, free the second source ++ * buffer first. ++ */ ++ if (video->srcs[1].size) ++ aspeed_video_free_buf(video, &video->srcs[1]); ++ ++ if (video->srcs[0].size < VE_MAX_SRC_BUFFER_SIZE) { ++ if (video->srcs[0].size) ++ aspeed_video_free_buf(video, &video->srcs[0]); ++ ++ if (!aspeed_video_alloc_buf(video, &video->srcs[0], ++ VE_MAX_SRC_BUFFER_SIZE)) { ++ dev_err(video->dev, ++ "Failed to allocate source buffers\n"); ++ return; ++ } ++ } ++ ++ aspeed_video_write(video, VE_SRC0_ADDR, video->srcs[0].dma); ++ ++ do { ++ if (tries) { ++ set_current_state(TASK_INTERRUPTIBLE); ++ if (schedule_timeout(INVALID_RESOLUTION_DELAY)) ++ return; ++ } ++ ++ set_bit(VIDEO_RES_DETECT, &video->flags); ++ aspeed_video_enable_mode_detect(video); ++ ++ rc = wait_event_interruptible_timeout(video->wait, ++ res_check(video), ++ MODE_DETECT_TIMEOUT); ++ if (!rc) { ++ dev_err(video->dev, "Timed out; first mode detect\n"); ++ clear_bit(VIDEO_RES_DETECT, &video->flags); ++ return; ++ } ++ ++ /* Disable mode detect in order to re-trigger */ ++ aspeed_video_update(video, VE_SEQ_CTRL, ++ VE_SEQ_CTRL_TRIG_MODE_DET, 0); ++ ++ aspeed_video_check_and_set_polarity(video); ++ ++ aspeed_video_enable_mode_detect(video); ++ ++ rc = wait_event_interruptible_timeout(video->wait, ++ res_check(video), ++ MODE_DETECT_TIMEOUT); ++ clear_bit(VIDEO_RES_DETECT, &video->flags); ++ if (!rc) { ++ dev_err(video->dev, "Timed out; second mode detect\n"); ++ return; ++ } ++ ++ src_lr_edge = aspeed_video_read(video, VE_SRC_LR_EDGE_DET); ++ src_tb_edge = aspeed_video_read(video, VE_SRC_TB_EDGE_DET); ++ mds = aspeed_video_read(video, VE_MODE_DETECT_STATUS); ++ sync = aspeed_video_read(video, VE_SYNC_STATUS); ++ ++ video->frame_bottom = (src_tb_edge & VE_SRC_TB_EDGE_DET_BOT) >> ++ VE_SRC_TB_EDGE_DET_BOT_SHF; ++ video->frame_top = src_tb_edge & VE_SRC_TB_EDGE_DET_TOP; ++ det->vfrontporch = video->frame_top; ++ det->vbackporch = ((mds & VE_MODE_DETECT_V_LINES) >> ++ VE_MODE_DETECT_V_LINES_SHF) - video->frame_bottom; ++ det->vsync = (sync & VE_SYNC_STATUS_VSYNC) >> ++ VE_SYNC_STATUS_VSYNC_SHF; ++ if (video->frame_top > video->frame_bottom) ++ continue; ++ ++ video->frame_right = (src_lr_edge & VE_SRC_LR_EDGE_DET_RT) >> ++ VE_SRC_LR_EDGE_DET_RT_SHF; ++ video->frame_left = src_lr_edge & VE_SRC_LR_EDGE_DET_LEFT; ++ det->hfrontporch = video->frame_left; ++ det->hbackporch = (mds & VE_MODE_DETECT_H_PIXELS) - ++ video->frame_right; ++ det->hsync = sync & VE_SYNC_STATUS_HSYNC; ++ if (video->frame_left > video->frame_right) ++ continue; ++ ++ invalid_resolution = false; ++ } while (invalid_resolution && (tries++ < INVALID_RESOLUTION_RETRIES)); ++ ++ if (invalid_resolution) { ++ dev_err(video->dev, "Invalid resolution detected\n"); ++ return; ++ } ++ ++ det->height = (video->frame_bottom - video->frame_top) + 1; ++ det->width = (video->frame_right - video->frame_left) + 1; ++ video->v4l2_input_status = 0; ++ ++ /* ++ * Enable mode-detect watchdog, resolution-change watchdog and ++ * automatic compression after frame capture. ++ */ ++ aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, ++ VE_INTERRUPT_MODE_DETECT_WD); ++ aspeed_video_update(video, VE_SEQ_CTRL, 0, ++ VE_SEQ_CTRL_AUTO_COMP | VE_SEQ_CTRL_EN_WATCHDOG); ++ ++ dev_dbg(video->dev, "Got resolution: %dx%d\n", det->width, ++ det->height); ++} ++ ++static void aspeed_video_set_resolution(struct aspeed_video *video) ++{ ++ struct v4l2_bt_timings *act = &video->active_timings; ++ unsigned int size = act->width * act->height; ++ ++ aspeed_video_calc_compressed_size(video, size); ++ ++ /* Don't use direct mode below 1024 x 768 (irqs don't fire) */ ++ if (size < DIRECT_FETCH_THRESHOLD) { ++ aspeed_video_write(video, VE_TGS_0, ++ FIELD_PREP(VE_TGS_FIRST, ++ video->frame_left - 1) | ++ FIELD_PREP(VE_TGS_LAST, ++ video->frame_right)); ++ aspeed_video_write(video, VE_TGS_1, ++ FIELD_PREP(VE_TGS_FIRST, video->frame_top) | ++ FIELD_PREP(VE_TGS_LAST, ++ video->frame_bottom + 1)); ++ aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_INT_DE); ++ } else { ++ aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_DIRECT_FETCH); ++ } ++ ++ /* Set capture/compression frame sizes */ ++ aspeed_video_write(video, VE_CAP_WINDOW, ++ act->width << 16 | act->height); ++ aspeed_video_write(video, VE_COMP_WINDOW, ++ act->width << 16 | act->height); ++ aspeed_video_write(video, VE_SRC_SCANLINE_OFFSET, act->width * 4); ++ ++ size *= 4; ++ ++ if (size == video->srcs[0].size / 2) { ++ aspeed_video_write(video, VE_SRC1_ADDR, ++ video->srcs[0].dma + size); ++ } else if (size == video->srcs[0].size) { ++ if (!aspeed_video_alloc_buf(video, &video->srcs[1], size)) ++ goto err_mem; ++ ++ aspeed_video_write(video, VE_SRC1_ADDR, video->srcs[1].dma); ++ } else { ++ aspeed_video_free_buf(video, &video->srcs[0]); ++ ++ if (!aspeed_video_alloc_buf(video, &video->srcs[0], size)) ++ goto err_mem; ++ ++ if (!aspeed_video_alloc_buf(video, &video->srcs[1], size)) ++ goto err_mem; ++ ++ aspeed_video_write(video, VE_SRC0_ADDR, video->srcs[0].dma); ++ aspeed_video_write(video, VE_SRC1_ADDR, video->srcs[1].dma); ++ } ++ ++ return; ++ ++err_mem: ++ dev_err(video->dev, "Failed to allocate source buffers\n"); ++ ++ if (video->srcs[0].size) ++ aspeed_video_free_buf(video, &video->srcs[0]); ++} ++ ++static void aspeed_video_init_regs(struct aspeed_video *video) ++{ ++ u32 comp_ctrl = VE_COMP_CTRL_RSVD | ++ FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | ++ FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); ++ u32 ctrl = VE_CTRL_AUTO_OR_CURSOR; ++ u32 seq_ctrl = VE_SEQ_CTRL_JPEG_MODE; ++ ++ if (video->frame_rate) ++ ctrl |= FIELD_PREP(VE_CTRL_FRC, video->frame_rate); ++ ++ if (video->yuv420) ++ seq_ctrl |= VE_SEQ_CTRL_YUV420; ++ ++ /* Unlock VE registers */ ++ aspeed_video_write(video, VE_PROTECTION_KEY, VE_PROTECTION_KEY_UNLOCK); ++ ++ /* Disable interrupts */ ++ aspeed_video_write(video, VE_INTERRUPT_CTRL, 0); ++ aspeed_video_write(video, VE_INTERRUPT_STATUS, 0xffffffff); ++ ++ /* Clear the offset */ ++ aspeed_video_write(video, VE_COMP_PROC_OFFSET, 0); ++ aspeed_video_write(video, VE_COMP_OFFSET, 0); ++ ++ aspeed_video_write(video, VE_JPEG_ADDR, video->jpeg.dma); ++ ++ /* Set control registers */ ++ aspeed_video_write(video, VE_SEQ_CTRL, seq_ctrl); ++ aspeed_video_write(video, VE_CTRL, ctrl); ++ aspeed_video_write(video, VE_COMP_CTRL, comp_ctrl); ++ ++ /* Don't downscale */ ++ aspeed_video_write(video, VE_SCALING_FACTOR, 0x10001000); ++ aspeed_video_write(video, VE_SCALING_FILTER0, 0x00200000); ++ aspeed_video_write(video, VE_SCALING_FILTER1, 0x00200000); ++ aspeed_video_write(video, VE_SCALING_FILTER2, 0x00200000); ++ aspeed_video_write(video, VE_SCALING_FILTER3, 0x00200000); ++ ++ /* Set mode detection defaults */ ++ aspeed_video_write(video, VE_MODE_DETECT, 0x22666500); ++} ++ ++static void aspeed_video_start(struct aspeed_video *video) ++{ ++ aspeed_video_on(video); ++ ++ aspeed_video_init_regs(video); ++ ++ /* Resolution set to 640x480 if no signal found */ ++ aspeed_video_get_resolution(video); ++ ++ /* Set timings since the device is being opened for the first time */ ++ video->active_timings = video->detected_timings; ++ aspeed_video_set_resolution(video); ++ ++ video->pix_fmt.width = video->active_timings.width; ++ video->pix_fmt.height = video->active_timings.height; ++ video->pix_fmt.sizeimage = video->max_compressed_size; ++} ++ ++static void aspeed_video_stop(struct aspeed_video *video) ++{ ++ set_bit(VIDEO_STOPPED, &video->flags); ++ cancel_delayed_work_sync(&video->res_work); ++ ++ aspeed_video_off(video); ++ ++ if (video->srcs[0].size) ++ aspeed_video_free_buf(video, &video->srcs[0]); ++ ++ if (video->srcs[1].size) ++ aspeed_video_free_buf(video, &video->srcs[1]); ++ ++ video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; ++ video->flags = 0; ++} ++ ++static int aspeed_video_querycap(struct file *file, void *fh, ++ struct v4l2_capability *cap) ++{ ++ strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver)); ++ strscpy(cap->card, "Aspeed Video Engine", sizeof(cap->card)); ++ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", ++ DEVICE_NAME); ++ ++ return 0; ++} ++ ++static int aspeed_video_enum_format(struct file *file, void *fh, ++ struct v4l2_fmtdesc *f) ++{ ++ if (f->index) ++ return -EINVAL; ++ ++ f->pixelformat = V4L2_PIX_FMT_JPEG; ++ ++ return 0; ++} ++ ++static int aspeed_video_get_format(struct file *file, void *fh, ++ struct v4l2_format *f) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ ++ f->fmt.pix = video->pix_fmt; ++ ++ return 0; ++} ++ ++static int aspeed_video_enum_input(struct file *file, void *fh, ++ struct v4l2_input *inp) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ ++ if (inp->index) ++ return -EINVAL; ++ ++ strscpy(inp->name, "Host VGA capture", sizeof(inp->name)); ++ inp->type = V4L2_INPUT_TYPE_CAMERA; ++ inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; ++ inp->status = video->v4l2_input_status; ++ ++ return 0; ++} ++ ++static int aspeed_video_get_input(struct file *file, void *fh, unsigned int *i) ++{ ++ *i = 0; ++ ++ return 0; ++} ++ ++static int aspeed_video_set_input(struct file *file, void *fh, unsigned int i) ++{ ++ if (i) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static int aspeed_video_get_parm(struct file *file, void *fh, ++ struct v4l2_streamparm *a) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ ++ a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; ++ a->parm.capture.readbuffers = 3; ++ a->parm.capture.timeperframe.numerator = 1; ++ if (!video->frame_rate) ++ a->parm.capture.timeperframe.denominator = MAX_FRAME_RATE; ++ else ++ a->parm.capture.timeperframe.denominator = video->frame_rate; ++ ++ return 0; ++} ++ ++static int aspeed_video_set_parm(struct file *file, void *fh, ++ struct v4l2_streamparm *a) ++{ ++ unsigned int frame_rate = 0; ++ struct aspeed_video *video = video_drvdata(file); ++ ++ a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; ++ a->parm.capture.readbuffers = 3; ++ ++ if (a->parm.capture.timeperframe.numerator) ++ frame_rate = a->parm.capture.timeperframe.denominator / ++ a->parm.capture.timeperframe.numerator; ++ ++ if (!frame_rate || frame_rate > MAX_FRAME_RATE) { ++ frame_rate = 0; ++ a->parm.capture.timeperframe.denominator = MAX_FRAME_RATE; ++ a->parm.capture.timeperframe.numerator = 1; ++ } ++ ++ if (video->frame_rate != frame_rate) { ++ video->frame_rate = frame_rate; ++ aspeed_video_update(video, VE_CTRL, VE_CTRL_FRC, ++ FIELD_PREP(VE_CTRL_FRC, frame_rate)); ++ } ++ ++ return 0; ++} ++ ++static int aspeed_video_enum_framesizes(struct file *file, void *fh, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ ++ if (fsize->index) ++ return -EINVAL; ++ ++ if (fsize->pixel_format != V4L2_PIX_FMT_JPEG) ++ return -EINVAL; ++ ++ fsize->discrete.width = video->pix_fmt.width; ++ fsize->discrete.height = video->pix_fmt.height; ++ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; ++ ++ return 0; ++} ++ ++static int aspeed_video_enum_frameintervals(struct file *file, void *fh, ++ struct v4l2_frmivalenum *fival) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ ++ if (fival->index) ++ return -EINVAL; ++ ++ if (fival->width != video->detected_timings.width || ++ fival->height != video->detected_timings.height) ++ return -EINVAL; ++ ++ if (fival->pixel_format != V4L2_PIX_FMT_JPEG) ++ return -EINVAL; ++ ++ fival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; ++ ++ fival->stepwise.min.denominator = MAX_FRAME_RATE; ++ fival->stepwise.min.numerator = 1; ++ fival->stepwise.max.denominator = 1; ++ fival->stepwise.max.numerator = 1; ++ fival->stepwise.step = fival->stepwise.max; ++ ++ return 0; ++} ++ ++static int aspeed_video_set_dv_timings(struct file *file, void *fh, ++ struct v4l2_dv_timings *timings) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ ++ if (timings->bt.width == video->active_timings.width && ++ timings->bt.height == video->active_timings.height) ++ return 0; ++ ++ if (vb2_is_busy(&video->queue)) ++ return -EBUSY; ++ ++ video->active_timings = timings->bt; ++ ++ aspeed_video_set_resolution(video); ++ ++ video->pix_fmt.width = timings->bt.width; ++ video->pix_fmt.height = timings->bt.height; ++ video->pix_fmt.sizeimage = video->max_compressed_size; ++ ++ timings->type = V4L2_DV_BT_656_1120; ++ ++ return 0; ++} ++ ++static int aspeed_video_get_dv_timings(struct file *file, void *fh, ++ struct v4l2_dv_timings *timings) ++{ ++ struct aspeed_video *video = video_drvdata(file); ++ ++ timings->type = V4L2_DV_BT_656_1120; ++ timings->bt = video->active_timings; ++ ++ return 0; ++} ++ ++static int aspeed_video_query_dv_timings(struct file *file, void *fh, ++ struct v4l2_dv_timings *timings) ++{ ++ int rc; ++ struct aspeed_video *video = video_drvdata(file); ++ ++ /* ++ * This blocks only if the driver is currently in the process of ++ * detecting a new resolution; in the event of no signal or timeout ++ * this function is woken up. ++ */ ++ if (file->f_flags & O_NONBLOCK) { ++ if (test_bit(VIDEO_RES_CHANGE, &video->flags)) ++ return -EAGAIN; ++ } else { ++ rc = wait_event_interruptible(video->wait, ++ !test_bit(VIDEO_RES_CHANGE, ++ &video->flags)); ++ if (rc) ++ return -EINTR; ++ } ++ ++ timings->type = V4L2_DV_BT_656_1120; ++ timings->bt = video->detected_timings; ++ ++ return video->v4l2_input_status ? -ENOLINK : 0; ++} ++ ++static int aspeed_video_enum_dv_timings(struct file *file, void *fh, ++ struct v4l2_enum_dv_timings *timings) ++{ ++ return v4l2_enum_dv_timings_cap(timings, &aspeed_video_timings_cap, ++ NULL, NULL); ++} ++ ++static int aspeed_video_dv_timings_cap(struct file *file, void *fh, ++ struct v4l2_dv_timings_cap *cap) ++{ ++ *cap = aspeed_video_timings_cap; ++ ++ return 0; ++} ++ ++static int aspeed_video_sub_event(struct v4l2_fh *fh, ++ const struct v4l2_event_subscription *sub) ++{ ++ switch (sub->type) { ++ case V4L2_EVENT_SOURCE_CHANGE: ++ return v4l2_src_change_event_subscribe(fh, sub); ++ } ++ ++ return v4l2_ctrl_subscribe_event(fh, sub); ++} ++ ++static const struct v4l2_ioctl_ops aspeed_video_ioctl_ops = { ++ .vidioc_querycap = aspeed_video_querycap, ++ ++ .vidioc_enum_fmt_vid_cap = aspeed_video_enum_format, ++ .vidioc_g_fmt_vid_cap = aspeed_video_get_format, ++ .vidioc_s_fmt_vid_cap = aspeed_video_get_format, ++ .vidioc_try_fmt_vid_cap = aspeed_video_get_format, ++ ++ .vidioc_reqbufs = vb2_ioctl_reqbufs, ++ .vidioc_querybuf = vb2_ioctl_querybuf, ++ .vidioc_qbuf = vb2_ioctl_qbuf, ++ .vidioc_expbuf = vb2_ioctl_expbuf, ++ .vidioc_dqbuf = vb2_ioctl_dqbuf, ++ .vidioc_create_bufs = vb2_ioctl_create_bufs, ++ .vidioc_prepare_buf = vb2_ioctl_prepare_buf, ++ .vidioc_streamon = vb2_ioctl_streamon, ++ .vidioc_streamoff = vb2_ioctl_streamoff, ++ ++ .vidioc_enum_input = aspeed_video_enum_input, ++ .vidioc_g_input = aspeed_video_get_input, ++ .vidioc_s_input = aspeed_video_set_input, ++ ++ .vidioc_g_parm = aspeed_video_get_parm, ++ .vidioc_s_parm = aspeed_video_set_parm, ++ .vidioc_enum_framesizes = aspeed_video_enum_framesizes, ++ .vidioc_enum_frameintervals = aspeed_video_enum_frameintervals, ++ ++ .vidioc_s_dv_timings = aspeed_video_set_dv_timings, ++ .vidioc_g_dv_timings = aspeed_video_get_dv_timings, ++ .vidioc_query_dv_timings = aspeed_video_query_dv_timings, ++ .vidioc_enum_dv_timings = aspeed_video_enum_dv_timings, ++ .vidioc_dv_timings_cap = aspeed_video_dv_timings_cap, ++ ++ .vidioc_subscribe_event = aspeed_video_sub_event, ++ .vidioc_unsubscribe_event = v4l2_event_unsubscribe, ++}; ++ ++static void aspeed_video_update_jpeg_quality(struct aspeed_video *video) ++{ ++ u32 comp_ctrl = FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | ++ FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); ++ ++ aspeed_video_update(video, VE_COMP_CTRL, ++ VE_COMP_CTRL_DCT_LUM | VE_COMP_CTRL_DCT_CHR, ++ comp_ctrl); ++} ++ ++static void aspeed_video_update_subsampling(struct aspeed_video *video) ++{ ++ if (video->jpeg.virt) ++ aspeed_video_init_jpeg_table(video->jpeg.virt, video->yuv420); ++ ++ if (video->yuv420) ++ aspeed_video_update(video, VE_SEQ_CTRL, 0, VE_SEQ_CTRL_YUV420); ++ else ++ aspeed_video_update(video, VE_SEQ_CTRL, VE_SEQ_CTRL_YUV420, 0); ++} ++ ++static int aspeed_video_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct aspeed_video *video = container_of(ctrl->handler, ++ struct aspeed_video, ++ ctrl_handler); ++ ++ switch (ctrl->id) { ++ case V4L2_CID_JPEG_COMPRESSION_QUALITY: ++ video->jpeg_quality = ctrl->val; ++ aspeed_video_update_jpeg_quality(video); ++ break; ++ case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: ++ if (ctrl->val == V4L2_JPEG_CHROMA_SUBSAMPLING_420) { ++ video->yuv420 = true; ++ aspeed_video_update_subsampling(video); ++ } else { ++ video->yuv420 = false; ++ aspeed_video_update_subsampling(video); ++ } ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static const struct v4l2_ctrl_ops aspeed_video_ctrl_ops = { ++ .s_ctrl = aspeed_video_set_ctrl, ++}; ++ ++static void aspeed_video_resolution_work(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct aspeed_video *video = container_of(dwork, struct aspeed_video, ++ res_work); ++ u32 input_status = video->v4l2_input_status; ++ ++ aspeed_video_on(video); ++ ++ /* Exit early in case no clients remain */ ++ if (test_bit(VIDEO_STOPPED, &video->flags)) ++ goto done; ++ ++ aspeed_video_init_regs(video); ++ ++ aspeed_video_get_resolution(video); ++ ++ if (video->detected_timings.width != video->active_timings.width || ++ video->detected_timings.height != video->active_timings.height || ++ input_status != video->v4l2_input_status) { ++ static const struct v4l2_event ev = { ++ .type = V4L2_EVENT_SOURCE_CHANGE, ++ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, ++ }; ++ ++ v4l2_event_queue(&video->vdev, &ev); ++ } else if (test_bit(VIDEO_STREAMING, &video->flags)) { ++ /* No resolution change so just restart streaming */ ++ aspeed_video_start_frame(video); ++ } ++ ++done: ++ clear_bit(VIDEO_RES_CHANGE, &video->flags); ++ wake_up_interruptible_all(&video->wait); ++} ++ ++static int aspeed_video_open(struct file *file) ++{ ++ int rc; ++ struct aspeed_video *video = video_drvdata(file); ++ ++ mutex_lock(&video->video_lock); ++ ++ rc = v4l2_fh_open(file); ++ if (rc) { ++ mutex_unlock(&video->video_lock); ++ return rc; ++ } ++ ++ if (v4l2_fh_is_singular_file(file)) ++ aspeed_video_start(video); ++ ++ mutex_unlock(&video->video_lock); ++ ++ return 0; ++} ++ ++static int aspeed_video_release(struct file *file) ++{ ++ int rc; ++ struct aspeed_video *video = video_drvdata(file); ++ ++ mutex_lock(&video->video_lock); ++ ++ if (v4l2_fh_is_singular_file(file)) ++ aspeed_video_stop(video); ++ ++ rc = _vb2_fop_release(file, NULL); ++ ++ mutex_unlock(&video->video_lock); ++ ++ return rc; ++} ++ ++static const struct v4l2_file_operations aspeed_video_v4l2_fops = { ++ .owner = THIS_MODULE, ++ .read = vb2_fop_read, ++ .poll = vb2_fop_poll, ++ .unlocked_ioctl = video_ioctl2, ++ .mmap = vb2_fop_mmap, ++ .open = aspeed_video_open, ++ .release = aspeed_video_release, ++}; ++ ++static int aspeed_video_queue_setup(struct vb2_queue *q, ++ unsigned int *num_buffers, ++ unsigned int *num_planes, ++ unsigned int sizes[], ++ struct device *alloc_devs[]) ++{ ++ struct aspeed_video *video = vb2_get_drv_priv(q); ++ ++ if (*num_planes) { ++ if (sizes[0] < video->max_compressed_size) ++ return -EINVAL; ++ ++ return 0; ++ } ++ ++ *num_planes = 1; ++ sizes[0] = video->max_compressed_size; ++ ++ return 0; ++} ++ ++static int aspeed_video_buf_prepare(struct vb2_buffer *vb) ++{ ++ struct aspeed_video *video = vb2_get_drv_priv(vb->vb2_queue); ++ ++ if (vb2_plane_size(vb, 0) < video->max_compressed_size) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static int aspeed_video_start_streaming(struct vb2_queue *q, ++ unsigned int count) ++{ ++ int rc; ++ struct aspeed_video *video = vb2_get_drv_priv(q); ++ ++ video->sequence = 0; ++ ++ rc = aspeed_video_start_frame(video); ++ if (rc) { ++ aspeed_video_bufs_done(video, VB2_BUF_STATE_QUEUED); ++ return rc; ++ } ++ ++ set_bit(VIDEO_STREAMING, &video->flags); ++ return 0; ++} ++ ++static void aspeed_video_stop_streaming(struct vb2_queue *q) ++{ ++ int rc; ++ struct aspeed_video *video = vb2_get_drv_priv(q); ++ ++ clear_bit(VIDEO_STREAMING, &video->flags); ++ ++ rc = wait_event_timeout(video->wait, ++ !test_bit(VIDEO_FRAME_INPRG, &video->flags), ++ STOP_TIMEOUT); ++ if (!rc) { ++ dev_err(video->dev, "Timed out when stopping streaming\n"); ++ ++ /* ++ * Need to force stop any DMA and try and get HW into a good ++ * state for future calls to start streaming again. ++ */ ++ aspeed_video_reset(video); ++ aspeed_video_init_regs(video); ++ ++ aspeed_video_get_resolution(video); ++ } ++ ++ aspeed_video_bufs_done(video, VB2_BUF_STATE_ERROR); ++} ++ ++static void aspeed_video_buf_queue(struct vb2_buffer *vb) ++{ ++ bool empty; ++ struct aspeed_video *video = vb2_get_drv_priv(vb->vb2_queue); ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct aspeed_video_buffer *avb = to_aspeed_video_buffer(vbuf); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&video->lock, flags); ++ empty = list_empty(&video->buffers); ++ list_add_tail(&avb->link, &video->buffers); ++ spin_unlock_irqrestore(&video->lock, flags); ++ ++ if (test_bit(VIDEO_STREAMING, &video->flags) && ++ !test_bit(VIDEO_FRAME_INPRG, &video->flags) && empty) ++ aspeed_video_start_frame(video); ++} ++ ++static const struct vb2_ops aspeed_video_vb2_ops = { ++ .queue_setup = aspeed_video_queue_setup, ++ .wait_prepare = vb2_ops_wait_prepare, ++ .wait_finish = vb2_ops_wait_finish, ++ .buf_prepare = aspeed_video_buf_prepare, ++ .start_streaming = aspeed_video_start_streaming, ++ .stop_streaming = aspeed_video_stop_streaming, ++ .buf_queue = aspeed_video_buf_queue, ++}; ++ ++static int aspeed_video_setup_video(struct aspeed_video *video) ++{ ++ const u64 mask = ~(BIT(V4L2_JPEG_CHROMA_SUBSAMPLING_444) | ++ BIT(V4L2_JPEG_CHROMA_SUBSAMPLING_420)); ++ struct v4l2_device *v4l2_dev = &video->v4l2_dev; ++ struct vb2_queue *vbq = &video->queue; ++ struct video_device *vdev = &video->vdev; ++ int rc; ++ ++ video->pix_fmt.pixelformat = V4L2_PIX_FMT_JPEG; ++ video->pix_fmt.field = V4L2_FIELD_NONE; ++ video->pix_fmt.colorspace = V4L2_COLORSPACE_SRGB; ++ video->pix_fmt.quantization = V4L2_QUANTIZATION_FULL_RANGE; ++ video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; ++ ++ rc = v4l2_device_register(video->dev, v4l2_dev); ++ if (rc) { ++ dev_err(video->dev, "Failed to register v4l2 device\n"); ++ return rc; ++ } ++ ++ v4l2_ctrl_handler_init(&video->ctrl_handler, 2); ++ v4l2_ctrl_new_std(&video->ctrl_handler, &aspeed_video_ctrl_ops, ++ V4L2_CID_JPEG_COMPRESSION_QUALITY, 0, ++ ASPEED_VIDEO_JPEG_NUM_QUALITIES - 1, 1, 0); ++ v4l2_ctrl_new_std_menu(&video->ctrl_handler, &aspeed_video_ctrl_ops, ++ V4L2_CID_JPEG_CHROMA_SUBSAMPLING, ++ V4L2_JPEG_CHROMA_SUBSAMPLING_420, mask, ++ V4L2_JPEG_CHROMA_SUBSAMPLING_444); ++ ++ if (video->ctrl_handler.error) { ++ v4l2_ctrl_handler_free(&video->ctrl_handler); ++ v4l2_device_unregister(v4l2_dev); ++ ++ dev_err(video->dev, "Failed to init controls: %d\n", ++ video->ctrl_handler.error); ++ return rc; ++ } ++ ++ v4l2_dev->ctrl_handler = &video->ctrl_handler; ++ ++ vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ++ vbq->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; ++ vbq->dev = v4l2_dev->dev; ++ vbq->lock = &video->video_lock; ++ vbq->ops = &aspeed_video_vb2_ops; ++ vbq->mem_ops = &vb2_dma_contig_memops; ++ vbq->drv_priv = video; ++ vbq->buf_struct_size = sizeof(struct aspeed_video_buffer); ++ vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ++ vbq->min_buffers_needed = 3; ++ ++ rc = vb2_queue_init(vbq); ++ if (rc) { ++ v4l2_ctrl_handler_free(&video->ctrl_handler); ++ v4l2_device_unregister(v4l2_dev); ++ ++ dev_err(video->dev, "Failed to init vb2 queue\n"); ++ return rc; ++ } ++ ++ vdev->queue = vbq; ++ vdev->fops = &aspeed_video_v4l2_fops; ++ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | ++ V4L2_CAP_STREAMING; ++ vdev->v4l2_dev = v4l2_dev; ++ strscpy(vdev->name, DEVICE_NAME, sizeof(vdev->name)); ++ vdev->vfl_type = VFL_TYPE_GRABBER; ++ vdev->vfl_dir = VFL_DIR_RX; ++ vdev->release = video_device_release_empty; ++ vdev->ioctl_ops = &aspeed_video_ioctl_ops; ++ vdev->lock = &video->video_lock; ++ ++ video_set_drvdata(vdev, video); ++ rc = video_register_device(vdev, VFL_TYPE_GRABBER, 0); ++ if (rc) { ++ vb2_queue_release(vbq); ++ v4l2_ctrl_handler_free(&video->ctrl_handler); ++ v4l2_device_unregister(v4l2_dev); ++ ++ dev_err(video->dev, "Failed to register video device\n"); ++ return rc; ++ } ++ ++ return 0; ++} ++ ++static int aspeed_video_init(struct aspeed_video *video) ++{ ++ int irq; ++ int rc; ++ struct device *dev = video->dev; ++ ++ irq = irq_of_parse_and_map(dev->of_node, 0); ++ if (!irq) { ++ dev_err(dev, "Unable to find IRQ\n"); ++ return -ENODEV; ++ } ++ ++ rc = devm_request_irq(dev, irq, aspeed_video_irq, IRQF_SHARED, ++ DEVICE_NAME, video); ++ if (rc < 0) { ++ dev_err(dev, "Unable to request IRQ %d\n", irq); ++ return rc; ++ } ++ ++ video->eclk = devm_clk_get(dev, "eclk"); ++ if (IS_ERR(video->eclk)) { ++ dev_err(dev, "Unable to get ECLK\n"); ++ return PTR_ERR(video->eclk); ++ } ++ ++ video->vclk = devm_clk_get(dev, "vclk"); ++ if (IS_ERR(video->vclk)) { ++ dev_err(dev, "Unable to get VCLK\n"); ++ return PTR_ERR(video->vclk); ++ } ++ ++ video->rst = devm_reset_control_get_exclusive(dev, NULL); ++ if (IS_ERR(video->rst)) { ++ dev_err(dev, "Unable to get VE reset\n"); ++ return PTR_ERR(video->rst); ++ } ++ ++ rc = of_reserved_mem_device_init(dev); ++ if (rc) { ++ dev_err(dev, "Unable to reserve memory\n"); ++ return rc; ++ } ++ ++ rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); ++ if (rc) { ++ dev_err(dev, "Failed to set DMA mask\n"); ++ of_reserved_mem_device_release(dev); ++ return rc; ++ } ++ ++ if (!aspeed_video_alloc_buf(video, &video->jpeg, ++ VE_JPEG_HEADER_SIZE)) { ++ dev_err(dev, "Failed to allocate DMA for JPEG header\n"); ++ of_reserved_mem_device_release(dev); ++ return rc; ++ } ++ ++ aspeed_video_init_jpeg_table(video->jpeg.virt, video->yuv420); ++ ++ return 0; ++} ++ ++static int aspeed_video_probe(struct platform_device *pdev) ++{ ++ int rc; ++ struct resource *res; ++ struct aspeed_video *video = kzalloc(sizeof(*video), GFP_KERNEL); ++ ++ if (!video) ++ return -ENOMEM; ++ ++ video->frame_rate = 30; ++ video->dev = &pdev->dev; ++ mutex_init(&video->video_lock); ++ init_waitqueue_head(&video->wait); ++ INIT_DELAYED_WORK(&video->res_work, aspeed_video_resolution_work); ++ INIT_LIST_HEAD(&video->buffers); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ++ video->base = devm_ioremap_resource(video->dev, res); ++ ++ if (IS_ERR(video->base)) ++ return PTR_ERR(video->base); ++ ++ rc = aspeed_video_init(video); ++ if (rc) ++ return rc; ++ ++ rc = aspeed_video_setup_video(video); ++ if (rc) ++ return rc; ++ ++ return 0; ++} ++ ++static int aspeed_video_remove(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); ++ struct aspeed_video *video = to_aspeed_video(v4l2_dev); ++ ++ video_unregister_device(&video->vdev); ++ ++ vb2_queue_release(&video->queue); ++ ++ v4l2_ctrl_handler_free(&video->ctrl_handler); ++ ++ v4l2_device_unregister(v4l2_dev); ++ ++ dma_free_coherent(video->dev, VE_JPEG_HEADER_SIZE, video->jpeg.virt, ++ video->jpeg.dma); ++ ++ of_reserved_mem_device_release(dev); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_video_of_match[] = { ++ { .compatible = "aspeed,ast2400-video-engine" }, ++ { .compatible = "aspeed,ast2500-video-engine" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, aspeed_video_of_match); ++ ++static struct platform_driver aspeed_video_driver = { ++ .driver = { ++ .name = DEVICE_NAME, ++ .of_match_table = aspeed_video_of_match, ++ }, ++ .probe = aspeed_video_probe, ++ .remove = aspeed_video_remove, ++}; ++ ++module_platform_driver(aspeed_video_driver); ++ ++MODULE_DESCRIPTION("ASPEED Video Engine Driver"); ++MODULE_AUTHOR("Eddie James"); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/dt-bindings/clock/aspeed-clock.h b/include/dt-bindings/clock/aspeed-clock.h +index f43738607d77..15a9059d0303 100644 +--- a/include/dt-bindings/clock/aspeed-clock.h ++++ b/include/dt-bindings/clock/aspeed-clock.h +@@ -50,5 +50,6 @@ + #define ASPEED_RESET_I2C 7 + #define ASPEED_RESET_AHB 8 + #define ASPEED_RESET_CRT1 9 ++#define ASPEED_RESET_VIDEO 10 + + #endif +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0039-Add-Aspeed-PWM-driver-which-uses-FTTMR010-timer-IP.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0039-Add-Aspeed-PWM-driver-which-uses-FTTMR010-timer-IP.patch new file mode 100644 index 000000000..7400e2848 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0039-Add-Aspeed-PWM-driver-which-uses-FTTMR010-timer-IP.patch @@ -0,0 +1,702 @@ +From a771e5448ed259f768434d498daf8d8b292713de Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Mon, 11 Feb 2019 17:02:35 -0800 +Subject: [PATCH] Add Aspeed PWM driver which uses FTTMR010 timer IP + +This commit adds Aspeed PWM driver which uses timer pulse output +feature in Aspeed SoCs. The timer IP is derived from Faraday +Technologies FTTMR010 IP but has some customized register +structure changes only for Aspeed SoCs. + +Signed-off-by: Jae Hyun Yoo +--- + arch/arm/boot/dts/aspeed-g5.dtsi | 2 +- + drivers/clocksource/timer-fttmr010.c | 25 ++ + drivers/input/misc/pwm-beeper.c | 8 +- + drivers/pwm/Kconfig | 9 + + drivers/pwm/Makefile | 1 + + drivers/pwm/pwm-fttmr010.c | 465 +++++++++++++++++++++++++++++++++++ + include/clocksource/timer-fttmr010.h | 17 ++ + 7 files changed, 522 insertions(+), 5 deletions(-) + create mode 100644 drivers/pwm/pwm-fttmr010.c + create mode 100644 include/clocksource/timer-fttmr010.h + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 6686a13a5354..ccf2845cd788 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -301,7 +301,7 @@ + + timer: timer@1e782000 { + /* This timer is a Faraday FTTMR010 derivative */ +- compatible = "aspeed,ast2400-timer"; ++ compatible = "aspeed,ast2500-timer"; + reg = <0x1e782000 0x90>; + interrupts = <16 17 18 35 36 37 38 39>; + clocks = <&syscon ASPEED_CLK_APB>; +diff --git a/drivers/clocksource/timer-fttmr010.c b/drivers/clocksource/timer-fttmr010.c +index cf93f6419b51..8226ccf5cc2c 100644 +--- a/drivers/clocksource/timer-fttmr010.c ++++ b/drivers/clocksource/timer-fttmr010.c +@@ -20,6 +20,8 @@ + #include + #include + ++#include ++ + /* + * Register definitions for the timers + */ +@@ -77,6 +79,9 @@ + #define TIMER_3_INT_OVERFLOW BIT(8) + #define TIMER_INT_ALL_MASK 0x1ff + ++DEFINE_SPINLOCK(timer_fttmr010_lock); ++EXPORT_SYMBOL(timer_fttmr010_lock); ++ + struct fttmr010 { + void __iomem *base; + unsigned int tick_rate; +@@ -123,8 +128,11 @@ static int fttmr010_timer_set_next_event(unsigned long cycles, + struct clock_event_device *evt) + { + struct fttmr010 *fttmr010 = to_fttmr010(evt); ++ unsigned long flags; + u32 cr; + ++ spin_lock_irqsave(&timer_fttmr010_lock, flags); ++ + /* Stop */ + cr = readl(fttmr010->base + TIMER_CR); + cr &= ~fttmr010->t1_enable_val; +@@ -147,27 +155,37 @@ static int fttmr010_timer_set_next_event(unsigned long cycles, + cr |= fttmr010->t1_enable_val; + writel(cr, fttmr010->base + TIMER_CR); + ++ spin_unlock_irqrestore(&timer_fttmr010_lock, flags); ++ + return 0; + } + + static int fttmr010_timer_shutdown(struct clock_event_device *evt) + { + struct fttmr010 *fttmr010 = to_fttmr010(evt); ++ unsigned long flags; + u32 cr; + ++ spin_lock_irqsave(&timer_fttmr010_lock, flags); ++ + /* Stop */ + cr = readl(fttmr010->base + TIMER_CR); + cr &= ~fttmr010->t1_enable_val; + writel(cr, fttmr010->base + TIMER_CR); + ++ spin_unlock_irqrestore(&timer_fttmr010_lock, flags); ++ + return 0; + } + + static int fttmr010_timer_set_oneshot(struct clock_event_device *evt) + { + struct fttmr010 *fttmr010 = to_fttmr010(evt); ++ unsigned long flags; + u32 cr; + ++ spin_lock_irqsave(&timer_fttmr010_lock, flags); ++ + /* Stop */ + cr = readl(fttmr010->base + TIMER_CR); + cr &= ~fttmr010->t1_enable_val; +@@ -186,6 +204,8 @@ static int fttmr010_timer_set_oneshot(struct clock_event_device *evt) + cr |= TIMER_1_INT_MATCH1; + writel(cr, fttmr010->base + TIMER_INTR_MASK); + ++ spin_unlock_irqrestore(&timer_fttmr010_lock, flags); ++ + return 0; + } + +@@ -193,8 +213,11 @@ static int fttmr010_timer_set_periodic(struct clock_event_device *evt) + { + struct fttmr010 *fttmr010 = to_fttmr010(evt); + u32 period = DIV_ROUND_CLOSEST(fttmr010->tick_rate, HZ); ++ unsigned long flags; + u32 cr; + ++ spin_lock_irqsave(&timer_fttmr010_lock, flags); ++ + /* Stop */ + cr = readl(fttmr010->base + TIMER_CR); + cr &= ~fttmr010->t1_enable_val; +@@ -221,6 +244,8 @@ static int fttmr010_timer_set_periodic(struct clock_event_device *evt) + cr |= fttmr010->t1_enable_val; + writel(cr, fttmr010->base + TIMER_CR); + ++ spin_unlock_irqrestore(&timer_fttmr010_lock, flags); ++ + return 0; + } + +diff --git a/drivers/input/misc/pwm-beeper.c b/drivers/input/misc/pwm-beeper.c +index edca0d737750..a3baa52f187f 100644 +--- a/drivers/input/misc/pwm-beeper.c ++++ b/drivers/input/misc/pwm-beeper.c +@@ -52,7 +52,7 @@ static int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period) + if (error) + return error; + +- if (!beeper->amplifier_on) { ++ if (beeper->amplifier && !beeper->amplifier_on) { + error = regulator_enable(beeper->amplifier); + if (error) { + pwm_disable(beeper->pwm); +@@ -67,7 +67,7 @@ static int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period) + + static void pwm_beeper_off(struct pwm_beeper *beeper) + { +- if (beeper->amplifier_on) { ++ if (beeper->amplifier && beeper->amplifier_on) { + regulator_disable(beeper->amplifier); + beeper->amplifier_on = false; + } +@@ -163,9 +163,9 @@ static int pwm_beeper_probe(struct platform_device *pdev) + if (IS_ERR(beeper->amplifier)) { + error = PTR_ERR(beeper->amplifier); + if (error != -EPROBE_DEFER) +- dev_err(dev, "Failed to get 'amp' regulator: %d\n", ++ dev_dbg(dev, "Failed to get 'amp' regulator: %d\n", + error); +- return error; ++ beeper->amplifier = NULL; + } + + INIT_WORK(&beeper->work, pwm_beeper_work); +diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig +index 504d252716f2..9d4642c668c9 100644 +--- a/drivers/pwm/Kconfig ++++ b/drivers/pwm/Kconfig +@@ -168,6 +168,15 @@ config PWM_FSL_FTM + To compile this driver as a module, choose M here: the module + will be called pwm-fsl-ftm. + ++config PWM_FTTMR010 ++ tristate "Faraday Technology FTTMR010 timer PWM support" ++ help ++ Generic PWM framework driver for Faraday Technology FTTMR010 Timer ++ PWM output ++ ++ To compile this driver as a module, choose M here: the module ++ will be called pwm-fttmr010 ++ + config PWM_HIBVT + tristate "HiSilicon BVT PWM support" + depends on ARCH_HISI || COMPILE_TEST +diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile +index 9c676a0dadf5..13b7b20ad5ab 100644 +--- a/drivers/pwm/Makefile ++++ b/drivers/pwm/Makefile +@@ -15,6 +15,7 @@ obj-$(CONFIG_PWM_CRC) += pwm-crc.o + obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o + obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o + obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o ++obj-$(CONFIG_PWM_FTTMR010) += pwm-fttmr010.o + obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o + obj-$(CONFIG_PWM_IMG) += pwm-img.o + obj-$(CONFIG_PWM_IMX) += pwm-imx.o +diff --git a/drivers/pwm/pwm-fttmr010.c b/drivers/pwm/pwm-fttmr010.c +new file mode 100644 +index 000000000000..459ace3eba6a +--- /dev/null ++++ b/drivers/pwm/pwm-fttmr010.c +@@ -0,0 +1,465 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2019 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* For timer_fttmr010_lock */ ++#include ++ ++#define TIMER_CR 0x30 ++ ++#define TIMER5_ASPEED_COUNT 0x50 ++#define TIMER5_ASPEED_LOAD 0x54 ++#define TIMER5_ASPEED_MATCH1 0x58 ++#define TIMER5_ASPEED_MATCH2 0x5c ++#define TIMER6_ASPEED_COUNT 0x60 ++#define TIMER6_ASPEED_LOAD 0x64 ++#define TIMER6_ASPEED_MATCH1 0x68 ++#define TIMER6_ASPEED_MATCH2 0x6c ++#define TIMER7_ASPEED_COUNT 0x70 ++#define TIMER7_ASPEED_LOAD 0x74 ++#define TIMER7_ASPEED_MATCH1 0x78 ++#define TIMER7_ASPEED_MATCH2 0x7c ++#define TIMER8_ASPEED_COUNT 0x80 ++#define TIMER8_ASPEED_LOAD 0x84 ++#define TIMER8_ASPEED_MATCH1 0x88 ++#define TIMER8_ASPEED_MATCH2 0x8c ++ ++#define TIMER_5_CR_ASPEED_ENABLE BIT(16) ++#define TIMER_5_CR_ASPEED_CLOCK BIT(17) ++#define TIMER_5_CR_ASPEED_INT BIT(18) ++#define TIMER_5_CR_ASPEED_PULSE_OUT BIT(19) ++#define TIMER_6_CR_ASPEED_ENABLE BIT(20) ++#define TIMER_6_CR_ASPEED_CLOCK BIT(21) ++#define TIMER_6_CR_ASPEED_INT BIT(22) ++#define TIMER_6_CR_ASPEED_PULSE_OUT BIT(23) ++#define TIMER_7_CR_ASPEED_ENABLE BIT(24) ++#define TIMER_7_CR_ASPEED_CLOCK BIT(25) ++#define TIMER_7_CR_ASPEED_INT BIT(26) ++#define TIMER_7_CR_ASPEED_PULSE_OUT BIT(27) ++#define TIMER_8_CR_ASPEED_ENABLE BIT(28) ++#define TIMER_8_CR_ASPEED_CLOCK BIT(29) ++#define TIMER_8_CR_ASPEED_INT BIT(30) ++#define TIMER_8_CR_ASPEED_PULSE_OUT BIT(31) ++ ++/** ++ * struct pwm_fttmr010_variant - variant data depends on SoC ++ * @bits: timer counter resolution ++ * @chan_min: lowest timer channel which has pwm pulse output ++ * @chan_max: highest timer channel which has pwm pulse output ++ * @output_mask: pwm pulse output mask which is defined in device tree ++ */ ++struct pwm_fttmr010_variant { ++ u8 bits; ++ u8 chan_min; ++ u8 chan_max; ++ u8 output_mask; ++}; ++ ++/** ++ * struct pwm_fttmr010_chan - private data of FTTMR010 PWM channel ++ * @period_ns: current period in nanoseconds programmed to the hardware ++ * @duty_ns: current duty time in nanoseconds programmed to the hardware ++ */ ++struct pwm_fttmr010_chan { ++ u32 period_ns; ++ u32 duty_ns; ++}; ++ ++/** ++ * struct pwm_fttmr010 - private data of FTTMR010 PWM ++ * @chip: generic PWM chip ++ * @variant: local copy of hardware variant data ++ * @disabled_mask: disabled status for all channels - one bit per channel ++ * @base: base address of mapped PWM registers ++ * @clk: clock used to drive the timers ++ */ ++struct pwm_fttmr010 { ++ struct pwm_chip chip; ++ struct pwm_fttmr010_variant variant; ++ u8 disabled_mask; ++ void __iomem *base; ++ struct clk *clk; ++ u32 clk_tick_ns; ++}; ++ ++#if !defined(CONFIG_FTTMR010_TIMER) ++/* ++ * Timer block is shared between timer-fttmr010 and pwm-fttmr010 drivers ++ * and some registers need access synchronization. If both drivers are ++ * compiled in, the spinlock is defined in the clocksource driver, ++ * otherwise following definition is used. ++ * ++ * Currently we do not need any more complex synchronization method ++ * because all the supported SoCs contain only one instance of the Timer ++ * IP. Once this changes, both drivers will need to be modified to ++ * properly synchronize accesses to particular instances. ++ */ ++static DEFINE_SPINLOCK(timer_fttmr010_lock); ++#endif ++ ++static inline ++struct pwm_fttmr010 *to_pwm_fttmr010(struct pwm_chip *chip) ++{ ++ return container_of(chip, struct pwm_fttmr010, chip); ++} ++ ++static int pwm_fttmr010_request(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); ++ struct pwm_fttmr010_chan *chan; ++ ++ if (!(priv->variant.output_mask & BIT(pwm->hwpwm))) { ++ dev_warn(chip->dev, ++ "tried to request PWM channel %d without output\n", ++ pwm->hwpwm); ++ return -EINVAL; ++ } ++ ++ chan = devm_kzalloc(chip->dev, sizeof(*chan), GFP_KERNEL); ++ if (!chan) ++ return -ENOMEM; ++ ++ pwm_set_chip_data(pwm, chan); ++ ++ return 0; ++} ++ ++static void pwm_fttmr010_free(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ devm_kfree(chip->dev, pwm_get_chip_data(pwm)); ++ pwm_set_chip_data(pwm, NULL); ++} ++ ++static int pwm_fttmr010_enable(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); ++ ulong flags; ++ u32 cr; ++ ++ spin_lock_irqsave(&timer_fttmr010_lock, flags); ++ ++ cr = readl(priv->base + TIMER_CR); ++ ++ switch (pwm->hwpwm) { ++ case 5: ++ cr |= (TIMER_5_CR_ASPEED_ENABLE | TIMER_5_CR_ASPEED_PULSE_OUT); ++ break; ++ case 6: ++ cr |= (TIMER_6_CR_ASPEED_ENABLE | TIMER_6_CR_ASPEED_PULSE_OUT); ++ break; ++ case 7: ++ cr |= (TIMER_7_CR_ASPEED_ENABLE | TIMER_7_CR_ASPEED_PULSE_OUT); ++ break; ++ case 8: ++ cr |= (TIMER_8_CR_ASPEED_ENABLE | TIMER_8_CR_ASPEED_PULSE_OUT); ++ break; ++ } ++ ++ writel(cr, priv->base + TIMER_CR); ++ ++ spin_unlock_irqrestore(&timer_fttmr010_lock, flags); ++ ++ priv->disabled_mask &= ~BIT(pwm->hwpwm); ++ ++ return 0; ++} ++ ++static void pwm_fttmr010_disable(struct pwm_chip *chip, struct pwm_device *pwm) ++{ ++ struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); ++ ulong flags; ++ u32 cr; ++ ++ spin_lock_irqsave(&timer_fttmr010_lock, flags); ++ ++ cr = readl(priv->base + TIMER_CR); ++ ++ switch (pwm->hwpwm) { ++ case 5: ++ cr &= ~(TIMER_5_CR_ASPEED_ENABLE | TIMER_5_CR_ASPEED_PULSE_OUT); ++ break; ++ case 6: ++ cr &= ~(TIMER_6_CR_ASPEED_ENABLE | TIMER_6_CR_ASPEED_PULSE_OUT); ++ break; ++ case 7: ++ cr &= ~(TIMER_7_CR_ASPEED_ENABLE | TIMER_7_CR_ASPEED_PULSE_OUT); ++ break; ++ case 8: ++ cr &= ~(TIMER_8_CR_ASPEED_ENABLE | TIMER_8_CR_ASPEED_PULSE_OUT); ++ break; ++ } ++ ++ writel(cr, priv->base + TIMER_CR); ++ ++ spin_unlock_irqrestore(&timer_fttmr010_lock, flags); ++ ++ priv->disabled_mask |= BIT(pwm->hwpwm); ++} ++ ++static int pwm_fttmr010_config(struct pwm_chip *chip, struct pwm_device *pwm, ++ int duty_ns, int period_ns) ++{ ++ u32 tload, tmatch, creg_offset, lreg_offset, mreg_offset; ++ struct pwm_fttmr010_chan *chan = pwm_get_chip_data(pwm); ++ struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); ++ ++ /* ++ * We currently avoid using 64bit arithmetic by using the ++ * fact that anything faster than 1Hz is easily representable ++ * by 32bits. ++ */ ++ if (period_ns > NSEC_PER_SEC) ++ return -ERANGE; ++ ++ /* No need to update */ ++ if (chan->period_ns == period_ns || chan->duty_ns == duty_ns) ++ return 0; ++ ++ tload = period_ns / priv->clk_tick_ns; ++ ++ /* Period is too short */ ++ if (tload <= 1) ++ return -ERANGE; ++ ++ tmatch = duty_ns / priv->clk_tick_ns; ++ ++ /* 0% duty is not available */ ++ if (!tmatch) ++ ++tmatch; ++ ++ tmatch = tload - tmatch; ++ ++ /* Decrement to get tick numbers, instead of tick counts */ ++ --tload; ++ --tmatch; ++ ++ if (tload == 0 || tmatch == 0) ++ return -ERANGE; ++ ++ dev_dbg(priv->chip.dev, "clk_tick_ns:%u, tload:%u, tmatch:%u\n", ++ priv->clk_tick_ns, tload, tmatch); ++ ++ switch (pwm->hwpwm) { ++ case 5: ++ creg_offset = TIMER5_ASPEED_COUNT; ++ lreg_offset = TIMER5_ASPEED_LOAD; ++ mreg_offset = TIMER5_ASPEED_MATCH1; ++ break; ++ case 6: ++ creg_offset = TIMER6_ASPEED_COUNT; ++ lreg_offset = TIMER6_ASPEED_LOAD; ++ mreg_offset = TIMER6_ASPEED_MATCH1; ++ break; ++ case 7: ++ creg_offset = TIMER7_ASPEED_COUNT; ++ lreg_offset = TIMER7_ASPEED_LOAD; ++ mreg_offset = TIMER7_ASPEED_MATCH1; ++ break; ++ case 8: ++ creg_offset = TIMER8_ASPEED_COUNT; ++ lreg_offset = TIMER8_ASPEED_LOAD; ++ mreg_offset = TIMER8_ASPEED_MATCH1; ++ break; ++ } ++ ++ writel(tload, priv->base + creg_offset); ++ writel(tload, priv->base + lreg_offset); ++ writel(tmatch, priv->base + mreg_offset); ++ ++ chan->period_ns = period_ns; ++ chan->duty_ns = duty_ns; ++ ++ return 0; ++} ++ ++static const struct pwm_ops pwm_fttmr010_ops = { ++ .request = pwm_fttmr010_request, ++ .free = pwm_fttmr010_free, ++ .enable = pwm_fttmr010_enable, ++ .disable = pwm_fttmr010_disable, ++ .config = pwm_fttmr010_config, ++ .owner = THIS_MODULE, ++}; ++ ++#ifdef CONFIG_OF ++static const struct pwm_fttmr010_variant aspeed_variant = { ++ .bits = 32, ++ .chan_min = 5, ++ .chan_max = 8, ++}; ++ ++static const struct of_device_id pwm_fttmr010_matches[] = { ++ { .compatible = "aspeed,ast2400-timer", .data = &aspeed_variant }, ++ { .compatible = "aspeed,ast2500-timer", .data = &aspeed_variant }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, pwm_fttmr010_matches); ++ ++static int pwm_fttmr010_parse_dt(struct pwm_fttmr010 *priv) ++{ ++ struct device_node *np = priv->chip.dev->of_node; ++ const struct of_device_id *match; ++ struct property *prop; ++ const __be32 *cur; ++ u32 val; ++ ++ match = of_match_node(pwm_fttmr010_matches, np); ++ if (!match) ++ return -ENODEV; ++ ++ memcpy(&priv->variant, match->data, sizeof(priv->variant)); ++ ++ of_property_for_each_u32(np, "fttmr010,pwm-outputs", prop, cur, val) { ++ if (val < priv->variant.chan_min || ++ val > priv->variant.chan_max) { ++ dev_err(priv->chip.dev, ++ "invalid channel index in fttmr010,pwm-outputs property\n"); ++ continue; ++ } ++ priv->variant.output_mask |= BIT(val); ++ } ++ ++ return 0; ++} ++#else ++static int pwm_fttmr010_parse_dt(struct pwm_fttmr010 *priv) ++{ ++ return -ENODEV; ++} ++#endif ++ ++static int pwm_fttmr010_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct pwm_fttmr010 *priv; ++ struct resource *res; ++ ulong clk_rate; ++ int ret; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->chip.dev = &pdev->dev; ++ priv->chip.ops = &pwm_fttmr010_ops; ++ priv->chip.base = -1; ++ ++ if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { ++ ret = pwm_fttmr010_parse_dt(priv); ++ if (ret) ++ return ret; ++ ++ priv->chip.of_xlate = of_pwm_xlate_with_flags; ++ priv->chip.of_pwm_n_cells = 3; ++ } else { ++ if (!pdev->dev.platform_data) { ++ dev_err(&pdev->dev, "no platform data specified\n"); ++ return -EINVAL; ++ } ++ ++ memcpy(&priv->variant, pdev->dev.platform_data, ++ sizeof(priv->variant)); ++ } ++ ++ priv->chip.npwm = priv->variant.chan_max + 1; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ priv->base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(priv->base)) ++ return PTR_ERR(priv->base); ++ ++ priv->clk = devm_clk_get(&pdev->dev, "PCLK"); ++ if (IS_ERR(priv->clk)) { ++ dev_err(dev, "failed to get timer base clk\n"); ++ return PTR_ERR(priv->clk); ++ } ++ ++ ret = clk_prepare_enable(priv->clk); ++ if (ret < 0) { ++ dev_err(dev, "failed to enable base clock\n"); ++ return ret; ++ } ++ ++ clk_rate = clk_get_rate(priv->clk); ++ priv->clk_tick_ns = NSEC_PER_SEC / clk_rate; ++ ++ platform_set_drvdata(pdev, priv); ++ ++ ret = pwmchip_add(&priv->chip); ++ if (ret < 0) { ++ dev_err(dev, "failed to register PWM chip\n"); ++ clk_disable_unprepare(priv->clk); ++ return ret; ++ } ++ ++ dev_dbg(dev, "clk at %lu\n", clk_rate); ++ ++ return 0; ++} ++ ++static int pwm_fttmr010_remove(struct platform_device *pdev) ++{ ++ struct pwm_fttmr010 *priv = platform_get_drvdata(pdev); ++ int ret; ++ ++ ret = pwmchip_remove(&priv->chip); ++ if (ret < 0) ++ return ret; ++ ++ clk_disable_unprepare(priv->clk); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM_SLEEP ++static int pwm_fttmr010_resume(struct device *dev) ++{ ++ struct pwm_fttmr010 *priv = dev_get_drvdata(dev); ++ struct pwm_chip *chip = &priv->chip; ++ unsigned int i; ++ ++ for (i = chip->variant.chan_min; i < chip->variant.chan_max; i++) { ++ struct pwm_device *pwm = &chip->pwms[i]; ++ struct pwm_fttmr010_chan *chan = pwm_get_chip_data(pwm); ++ ++ if (!chan) ++ continue; ++ ++ if (chan->period_ns) { ++ pwm_fttmr010_config(chip, pwm, chan->duty_ns, ++ chan->period_ns); ++ } ++ ++ if (priv->disabled_mask & BIT(i)) ++ pwm_fttmr010_disable(chip, pwm); ++ else ++ pwm_fttmr010_enable(chip, pwm); ++ } ++ ++ return 0; ++} ++#endif ++ ++static SIMPLE_DEV_PM_OPS(pwm_fttmr010_pm_ops, NULL, pwm_fttmr010_resume); ++ ++static struct platform_driver pwm_fttmr010_driver = { ++ .driver = { ++ .name = "fttmr010-timer-pwm", ++ .pm = &pwm_fttmr010_pm_ops, ++ .of_match_table = of_match_ptr(pwm_fttmr010_matches), ++ }, ++ .probe = pwm_fttmr010_probe, ++ .remove = pwm_fttmr010_remove, ++}; ++module_platform_driver(pwm_fttmr010_driver); ++ ++MODULE_AUTHOR("Jae Hyun Yoo "); ++MODULE_DESCRIPTION("FTTMR010 PWM Driver for timer pulse outputs"); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/clocksource/timer-fttmr010.h b/include/clocksource/timer-fttmr010.h +new file mode 100644 +index 000000000000..d8d6a2f14130 +--- /dev/null ++++ b/include/clocksource/timer-fttmr010.h +@@ -0,0 +1,17 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++ ++#ifndef __CLOCKSOURCE_TIMER_FTTMR010_H ++#define __CLOCKSOURCE_TIMER_FTTMR010_H ++ ++#include ++ ++/* ++ * Following declaration must be in an ifdef due to this symbol being static ++ * in timer-fttmr010 driver if the clocksource driver is not compiled in and the ++ * spinlock is not shared between both drivers. ++ */ ++#ifdef CONFIG_FTTMR010_TIMER ++extern spinlock_t timer_fttmr010_lock; ++#endif ++ ++#endif /* __CLOCKSOURCE_TIMER_FTTMR010_H */ +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0040-i2c-Add-mux-hold-unhold-msg-types.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0040-i2c-Add-mux-hold-unhold-msg-types.patch new file mode 100644 index 000000000..85b2f45a1 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0040-i2c-Add-mux-hold-unhold-msg-types.patch @@ -0,0 +1,492 @@ +From 38ba0a960fcd17f7b3480fe3025c261fd60fe979 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo +Date: Fri, 15 Feb 2019 16:05:09 -0800 +Subject: [PATCH] i2c: Add mux hold/unhold msg types + +This commit adds mux hold/unhold message types to support extended +mux control for IPMB and MCTP devices. A hold or an unhold message +can be added at the end of I2C message stream wrapped by +repeated-start, also can be used as a single message independantly. + +This mux hold/unhold message will be delivered throughout all mux +levels in the path. Means that if it goes to multi-level mux path, +all muxes will be held/unheld by this message. + +1. Hold message + struct i2c_msg msg; + uint16_t timeout = 5000; // timeout in ms. 5 secs in this example. + + msg.addr = 0x0; // any value can be used. addr will be ignored in this packet. + msg.flags = I2C_M_HOLD; // set this flag to indicate it's a hold message. + msg.len = sizeof(uint16_t); // timeout value will be delivered using two bytes buffer. + msg.buf = (uint8_t *)&timeout; // set timeout value. + +2. Unhold message + struct i2c_msg msg; + uint16_t timeout = 0; // set 0 for an unhold message. + + msg.addr = 0x0; // any value can be used. addr will be ignored in this packet. + msg.flags = I2C_M_HOLD; // set this flag to indicate it's an unhold message. + msg.len = sizeof(uint16_t); // timeout value will be delivered using two bytes buffer. + msg.buf = (uint8_t *)&timeout; // set timeout value. + + This unhold message can be delivered to a mux adapter even when + a bus is locked so that any holding state can be unheld + immediately by invoking this unhold message. + +This patch would not be welcomed from upstream so it should be kept +in downstream only. + +Signed-off-by: Jae Hyun Yoo +--- + drivers/i2c/i2c-core-base.c | 79 +++++++++++++++++++++++++++---- + drivers/i2c/i2c-core-smbus.c | 17 ++++++- + drivers/i2c/i2c-mux.c | 109 +++++++++++++++++++++++++++++++++++++++---- + include/linux/i2c-mux.h | 3 ++ + include/linux/i2c.h | 25 ++++++++++ + include/uapi/linux/i2c.h | 1 + + 6 files changed, 215 insertions(+), 19 deletions(-) + +diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c +index 9200e349f29e..728b818501b1 100644 +--- a/drivers/i2c/i2c-core-base.c ++++ b/drivers/i2c/i2c-core-base.c +@@ -1211,6 +1211,25 @@ int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr) + } + EXPORT_SYMBOL_GPL(i2c_handle_smbus_host_notify); + ++static void i2c_adapter_hold(struct i2c_adapter *adapter, unsigned long timeout) ++{ ++ mutex_lock(&adapter->hold_lock); ++ mod_timer(&adapter->hold_timer, jiffies + timeout); ++} ++ ++static void i2c_adapter_unhold(struct i2c_adapter *adapter) ++{ ++ del_timer_sync(&adapter->hold_timer); ++ mutex_unlock(&adapter->hold_lock); ++} ++ ++static void i2c_adapter_hold_timer_callback(struct timer_list *t) ++{ ++ struct i2c_adapter *adapter = from_timer(adapter, t, hold_timer); ++ ++ i2c_adapter_unhold(adapter); ++} ++ + static int i2c_register_adapter(struct i2c_adapter *adap) + { + int res = -EINVAL; +@@ -1292,6 +1311,9 @@ static int i2c_register_adapter(struct i2c_adapter *adap) + bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); + mutex_unlock(&core_lock); + ++ mutex_init(&adap->hold_lock); ++ timer_setup(&adap->hold_timer, i2c_adapter_hold_timer_callback, 0); ++ + return 0; + + out_reg: +@@ -1512,6 +1534,8 @@ void i2c_del_adapter(struct i2c_adapter *adap) + idr_remove(&i2c_adapter_idr, adap->nr); + mutex_unlock(&core_lock); + ++ i2c_adapter_unhold(adap); ++ + /* Clear the device structure in case this adapter is ever going to be + added again */ + memset(&adap->dev, 0, sizeof(adap->dev)); +@@ -1861,7 +1885,9 @@ static int i2c_check_for_quirks(struct i2c_adapter *adap, struct i2c_msg *msgs, + */ + int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) + { ++ enum i2c_hold_msg_type hold_msg; + unsigned long orig_jiffies; ++ unsigned long timeout; + int ret, try; + + if (WARN_ON(!msgs || num < 1)) +@@ -1870,6 +1896,25 @@ int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) + if (adap->quirks && i2c_check_for_quirks(adap, msgs, num)) + return -EOPNOTSUPP; + ++ /* Do not deliver a mux hold msg to root bus adapter */ ++ if (!i2c_parent_is_i2c_adapter(adap)) { ++ hold_msg = i2c_check_hold_msg(msgs[num - 1].flags, ++ msgs[num - 1].len, ++ (u16 *)msgs[num - 1].buf); ++ if (hold_msg == I2C_HOLD_MSG_SET) { ++ timeout = msecs_to_jiffies(*(u16 *)msgs[num - 1].buf); ++ i2c_adapter_hold(adap, timeout); ++ ++ if (--num == 0) ++ return 0; ++ } else if (hold_msg == I2C_HOLD_MSG_RESET) { ++ i2c_adapter_unhold(adap); ++ return 0; ++ } else if (hold_msg == I2C_HOLD_MSG_NONE) { ++ mutex_lock(&adap->hold_lock); ++ } ++ } ++ + /* + * i2c_trace_msg_key gets enabled when tracepoint i2c_transfer gets + * enabled. This is an efficient way of keeping the for-loop from +@@ -1902,6 +1947,9 @@ int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) + trace_i2c_result(adap, num, ret); + } + ++ if (!i2c_parent_is_i2c_adapter(adap) && hold_msg == I2C_HOLD_MSG_NONE) ++ mutex_unlock(&adap->hold_lock); ++ + return ret; + } + EXPORT_SYMBOL(__i2c_transfer); +@@ -1920,6 +1968,7 @@ EXPORT_SYMBOL(__i2c_transfer); + */ + int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) + { ++ bool do_bus_lock = true; + int ret; + + /* REVISIT the fault reporting model here is weak: +@@ -1949,18 +1998,30 @@ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) + (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : ""); + } + #endif +- +- if (in_atomic() || irqs_disabled()) { +- ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT); +- if (!ret) +- /* I2C activity is ongoing. */ +- return -EAGAIN; +- } else { +- i2c_lock_bus(adap, I2C_LOCK_SEGMENT); ++ /* ++ * Do not lock a bus for delivering an unhold msg to a mux ++ * adpater. This is just for a single length unhold msg case. ++ */ ++ if (num == 1 && i2c_parent_is_i2c_adapter(adap) && ++ i2c_check_hold_msg(msgs[0].flags, msgs[0].len, ++ (u16 *)msgs[0].buf) == ++ I2C_HOLD_MSG_RESET) ++ do_bus_lock = false; ++ ++ if (do_bus_lock) { ++ if (in_atomic() || irqs_disabled()) { ++ ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT); ++ if (!ret) ++ /* I2C activity is ongoing. */ ++ return -EAGAIN; ++ } else { ++ i2c_lock_bus(adap, I2C_LOCK_SEGMENT); ++ } + } + + ret = __i2c_transfer(adap, msgs, num); +- i2c_unlock_bus(adap, I2C_LOCK_SEGMENT); ++ if (do_bus_lock) ++ i2c_unlock_bus(adap, I2C_LOCK_SEGMENT); + + return ret; + } else { +diff --git a/drivers/i2c/i2c-core-smbus.c b/drivers/i2c/i2c-core-smbus.c +index 9cd66cabb84f..64c58911bf21 100644 +--- a/drivers/i2c/i2c-core-smbus.c ++++ b/drivers/i2c/i2c-core-smbus.c +@@ -528,12 +528,25 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, + u8 command, int protocol, union i2c_smbus_data *data) + { ++ bool do_bus_lock = true; + s32 res; + +- i2c_lock_bus(adapter, I2C_LOCK_SEGMENT); ++ /* ++ * Do not lock a bus for delivering an unhold msg to a mux adpater. ++ * This is just for a single length unhold msg case. ++ */ ++ if (i2c_parent_is_i2c_adapter(adapter) && ++ i2c_check_hold_msg(flags, ++ protocol == I2C_SMBUS_WORD_DATA ? 2 : 0, ++ &data->word) == I2C_HOLD_MSG_RESET) ++ do_bus_lock = false; ++ ++ if (do_bus_lock) ++ i2c_lock_bus(adapter, I2C_LOCK_SEGMENT); + res = __i2c_smbus_xfer(adapter, addr, flags, read_write, + command, protocol, data); +- i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT); ++ if (do_bus_lock) ++ i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT); + + return res; + } +diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c +index f330690b4125..4d8909a0f90a 100644 +--- a/drivers/i2c/i2c-mux.c ++++ b/drivers/i2c/i2c-mux.c +@@ -26,6 +26,7 @@ + #include + #include + #include ++#include + + /* multiplexer per channel data */ + struct i2c_mux_priv { +@@ -35,21 +36,57 @@ struct i2c_mux_priv { + u32 chan_id; + }; + ++static void i2c_mux_hold(struct i2c_mux_core *muxc, unsigned long timeout) ++{ ++ mutex_lock(&muxc->hold_lock); ++ mod_timer(&muxc->hold_timer, jiffies + timeout); ++} ++ ++static void i2c_mux_unhold(struct i2c_mux_core *muxc) ++{ ++ del_timer_sync(&muxc->hold_timer); ++ mutex_unlock(&muxc->hold_lock); ++} ++ ++static void i2c_mux_hold_timer_callback(struct timer_list *t) ++{ ++ struct i2c_mux_core *muxc = from_timer(muxc, t, hold_timer); ++ ++ i2c_mux_unhold(muxc); ++} ++ + static int __i2c_mux_master_xfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) + { + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; ++ enum i2c_hold_msg_type hold_msg; ++ unsigned long timeout; + int ret; + + /* Switch to the right mux port and perform the transfer. */ + ++ hold_msg = i2c_check_hold_msg(msgs[num - 1].flags, ++ msgs[num - 1].len, ++ (u16 *)msgs[num - 1].buf); ++ if (hold_msg == I2C_HOLD_MSG_SET) { ++ timeout = msecs_to_jiffies(*(u16 *)msgs[num - 1].buf); ++ i2c_mux_hold(muxc, timeout); ++ } else if (hold_msg == I2C_HOLD_MSG_NONE) { ++ mutex_lock(&muxc->hold_lock); ++ } + ret = muxc->select(muxc, priv->chan_id); + if (ret >= 0) + ret = __i2c_transfer(parent, msgs, num); +- if (muxc->deselect) +- muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg != I2C_HOLD_MSG_SET) { ++ if (muxc->deselect) ++ muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg == I2C_HOLD_MSG_RESET) ++ i2c_mux_unhold(muxc); ++ else ++ mutex_unlock(&muxc->hold_lock); ++ } + + return ret; + } +@@ -60,15 +97,32 @@ static int i2c_mux_master_xfer(struct i2c_adapter *adap, + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; ++ enum i2c_hold_msg_type hold_msg; ++ unsigned long timeout; + int ret; + + /* Switch to the right mux port and perform the transfer. */ + ++ hold_msg = i2c_check_hold_msg(msgs[num - 1].flags, ++ msgs[num - 1].len, ++ (u16 *)msgs[num - 1].buf); ++ if (hold_msg == I2C_HOLD_MSG_SET) { ++ timeout = msecs_to_jiffies(*(u16 *)msgs[num - 1].buf); ++ i2c_mux_hold(muxc, timeout); ++ } else if (hold_msg == I2C_HOLD_MSG_NONE) { ++ mutex_lock(&muxc->hold_lock); ++ } + ret = muxc->select(muxc, priv->chan_id); + if (ret >= 0) + ret = i2c_transfer(parent, msgs, num); +- if (muxc->deselect) +- muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg != I2C_HOLD_MSG_SET) { ++ if (muxc->deselect) ++ muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg == I2C_HOLD_MSG_RESET) ++ i2c_mux_unhold(muxc); ++ else ++ mutex_unlock(&muxc->hold_lock); ++ } + + return ret; + } +@@ -81,16 +135,33 @@ static int __i2c_mux_smbus_xfer(struct i2c_adapter *adap, + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; ++ enum i2c_hold_msg_type hold_msg; ++ unsigned long timeout; + int ret; + + /* Select the right mux port and perform the transfer. */ + ++ hold_msg = i2c_check_hold_msg(flags, ++ size == I2C_SMBUS_WORD_DATA ? 2 : 0, ++ &data->word); ++ if (hold_msg == I2C_HOLD_MSG_SET) { ++ timeout = msecs_to_jiffies(data->word); ++ i2c_mux_hold(muxc, timeout); ++ } else if (hold_msg == I2C_HOLD_MSG_NONE) { ++ mutex_lock(&muxc->hold_lock); ++ } + ret = muxc->select(muxc, priv->chan_id); + if (ret >= 0) + ret = __i2c_smbus_xfer(parent, addr, flags, + read_write, command, size, data); +- if (muxc->deselect) +- muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg != I2C_HOLD_MSG_SET) { ++ if (muxc->deselect) ++ muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg == I2C_HOLD_MSG_RESET) ++ i2c_mux_unhold(muxc); ++ else ++ mutex_unlock(&muxc->hold_lock); ++ } + + return ret; + } +@@ -103,16 +174,33 @@ static int i2c_mux_smbus_xfer(struct i2c_adapter *adap, + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; ++ enum i2c_hold_msg_type hold_msg; ++ unsigned long timeout; + int ret; + + /* Select the right mux port and perform the transfer. */ + ++ hold_msg = i2c_check_hold_msg(flags, ++ size == I2C_SMBUS_WORD_DATA ? 2 : 0, ++ &data->word); ++ if (hold_msg == I2C_HOLD_MSG_SET) { ++ timeout = msecs_to_jiffies(data->word); ++ i2c_mux_hold(muxc, timeout); ++ } else if (hold_msg == I2C_HOLD_MSG_NONE) { ++ mutex_lock(&muxc->hold_lock); ++ } + ret = muxc->select(muxc, priv->chan_id); + if (ret >= 0) + ret = i2c_smbus_xfer(parent, addr, flags, + read_write, command, size, data); +- if (muxc->deselect) +- muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg != I2C_HOLD_MSG_SET) { ++ if (muxc->deselect) ++ muxc->deselect(muxc, priv->chan_id); ++ if (hold_msg == I2C_HOLD_MSG_RESET) ++ i2c_mux_unhold(muxc); ++ else ++ mutex_unlock(&muxc->hold_lock); ++ } + + return ret; + } +@@ -263,6 +351,9 @@ struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent, + muxc->deselect = deselect; + muxc->max_adapters = max_adapters; + ++ mutex_init(&muxc->hold_lock); ++ timer_setup(&muxc->hold_timer, i2c_mux_hold_timer_callback, 0); ++ + return muxc; + } + EXPORT_SYMBOL_GPL(i2c_mux_alloc); +@@ -435,6 +526,8 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) + { + char symlink_name[20]; + ++ i2c_mux_unhold(muxc); ++ + while (muxc->num_adapters) { + struct i2c_adapter *adap = muxc->adapter[--muxc->num_adapters]; + struct i2c_mux_priv *priv = adap->algo_data; +diff --git a/include/linux/i2c-mux.h b/include/linux/i2c-mux.h +index bd74d5706f3b..bc6f778eaf9d 100644 +--- a/include/linux/i2c-mux.h ++++ b/include/linux/i2c-mux.h +@@ -41,6 +41,9 @@ struct i2c_mux_core { + int (*select)(struct i2c_mux_core *, u32 chan_id); + int (*deselect)(struct i2c_mux_core *, u32 chan_id); + ++ struct mutex hold_lock; /* mutex for channel holding */ ++ struct timer_list hold_timer; ++ + int num_adapters; + int max_adapters; + struct i2c_adapter *adapter[0]; +diff --git a/include/linux/i2c.h b/include/linux/i2c.h +index 65b4eaed1d96..eadde70c0d4a 100644 +--- a/include/linux/i2c.h ++++ b/include/linux/i2c.h +@@ -692,6 +692,13 @@ struct i2c_adapter { + const struct i2c_adapter_quirks *quirks; + + struct irq_domain *host_notify_domain; ++ ++ /* ++ * These will be used by root adpaters only. For muxes, each mux core ++ * has these individually. ++ */ ++ struct mutex hold_lock; /* mutex for bus holding */ ++ struct timer_list hold_timer; + }; + #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev) + +@@ -949,4 +956,22 @@ static inline struct i2c_client *i2c_acpi_new_device(struct device *dev, + } + #endif /* CONFIG_ACPI */ + ++enum i2c_hold_msg_type { ++ I2C_HOLD_MSG_NONE, ++ I2C_HOLD_MSG_SET, ++ I2C_HOLD_MSG_RESET ++}; ++ ++static inline enum i2c_hold_msg_type i2c_check_hold_msg(u16 flags, u16 len, u16 *buf) ++{ ++ if (flags & I2C_M_HOLD && len == sizeof(u16)) { ++ if (*buf) ++ return I2C_HOLD_MSG_SET; ++ ++ return I2C_HOLD_MSG_RESET; ++ } ++ ++ return I2C_HOLD_MSG_NONE; ++} ++ + #endif /* _LINUX_I2C_H */ +diff --git a/include/uapi/linux/i2c.h b/include/uapi/linux/i2c.h +index f71a1751cacf..a1db9b17ed36 100644 +--- a/include/uapi/linux/i2c.h ++++ b/include/uapi/linux/i2c.h +@@ -72,6 +72,7 @@ struct i2c_msg { + #define I2C_M_RD 0x0001 /* read data, from slave to master */ + /* I2C_M_RD is guaranteed to be 0x0001! */ + #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ ++#define I2C_M_HOLD 0x0100 /* for holding a mux path */ + #define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */ + /* makes only sense in kernelspace */ + /* userspace buffers are copied anyway */ +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/intel.cfg b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/intel.cfg new file mode 100644 index 000000000..41530dd6e --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/intel.cfg @@ -0,0 +1 @@ +CONFIG_BLK_DEV_RAM=y diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed_%.bbappend b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed_%.bbappend new file mode 100644 index 000000000..cdf4325d9 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed_%.bbappend @@ -0,0 +1,31 @@ +FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" + +do_compile_prepend(){ + # device tree compiler flags + export DTC_FLAGS=-@ +} + +SRC_URI += " \ + file://intel.cfg \ + file://0005-arm-dts-aspeed-g5-add-espi.patch \ + file://0007-New-flash-map-for-intel.patch \ + file://0008-Add-ASPEED-SGPIO-driver.patch \ + file://0009-SGPIO-DT-and-pinctrl-fixup.patch \ + file://0010-Update-PECI-drivers-to-sync-with-linux-upstreaming-v.patch \ + file://0019-Add-I2C-IPMB-support.patch \ + file://0021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch \ + file://0022-Add-AST2500-eSPI-driver.patch \ + file://0025-dts-add-AST2500-LPC-SIO-tree-node.patch \ + file://0026-Add-support-for-new-PECI-commands.patch \ + file://0028-Add-AST2500-JTAG-driver.patch \ + file://0029-i2c-aspeed-Improve-driver-to-support-multi-master-us.patch \ + file://0030-Add-dump-debug-code-into-I2C-drivers.patch \ + file://0031-Add-high-speed-baud-rate-support-for-UART.patch \ + file://0032-misc-aspeed-Add-Aspeed-UART-routing-control-driver.patch \ + file://0034-arm-dts-adpeed-Swap-the-mac-nodes-numbering.patch \ + file://0035-Implement-a-memory-driver-share-memory.patch \ + file://0036-net-ncsi-backport-ncsi-patches.patch \ + file://0038-media-aspeed-backport-ikvm-patches.patch \ + file://0039-Add-Aspeed-PWM-driver-which-uses-FTTMR010-timer-IP.patch \ + file://0040-i2c-Add-mux-hold-unhold-msg-types.patch \ + " diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers/0001-Enable-passthrough-based-gpio-character-device.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers/0001-Enable-passthrough-based-gpio-character-device.patch new file mode 100644 index 000000000..15e845ebc --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers/0001-Enable-passthrough-based-gpio-character-device.patch @@ -0,0 +1,26 @@ +From 61a5796d151337fd537d1aeb3fb072dcdf96abbe Mon Sep 17 00:00:00 2001 +From: Kuiying Wang +Date: Sun, 3 Feb 2019 16:08:11 +0800 +Subject: [PATCH] Enable passthrough based gpio character device. + +Signed-off-by: Kuiying Wang +--- + include/uapi/linux/gpio.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h +index 1bf6e6df..8883ca90 100644 +--- a/include/uapi/linux/gpio.h ++++ b/include/uapi/linux/gpio.h +@@ -62,7 +62,7 @@ struct gpioline_info { + #define GPIOHANDLE_REQUEST_ACTIVE_LOW (1UL << 2) + #define GPIOHANDLE_REQUEST_OPEN_DRAIN (1UL << 3) + #define GPIOHANDLE_REQUEST_OPEN_SOURCE (1UL << 4) +- ++#define GPIOHANDLE_REQUEST_PASS_THROUGH (1UL << 5) + /** + * struct gpiohandle_request - Information about a GPIO handle request + * @lineoffsets: an array desired lines, specified by offset index for the +-- +2.16.2 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers_%.bbappend b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers_%.bbappend new file mode 100644 index 000000000..703af71df --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers_%.bbappend @@ -0,0 +1,5 @@ +FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" + +SRC_URI += " \ + file://0001-Enable-passthrough-based-gpio-character-device.patch \ + " -- cgit v1.2.3