summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-kernel
diff options
context:
space:
mode:
authorEd Tanous <ed.tanous@intel.com>2019-02-14 03:51:50 +0300
committerEd Tanous <ed.tanous@intel.com>2019-03-13 00:58:57 +0300
commita7715486507e75e4a7cee843a48067b15595defa (patch)
tree9fd209d468c42cfb6553a50e2523c1d7e1fb120a /meta-openbmc-mods/meta-common/recipes-kernel
parent9b44ea7e2de71224bce792654cab12b7a5ceaa7d (diff)
downloadopenbmc-a7715486507e75e4a7cee843a48067b15595defa.tar.xz
Initial commit of intel repository
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0005-arm-dts-aspeed-g5-add-espi.patch56
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0007-New-flash-map-for-intel.patch125
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0008-Add-ASPEED-SGPIO-driver.patch474
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0009-SGPIO-DT-and-pinctrl-fixup.patch240
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0010-Update-PECI-drivers-to-sync-with-linux-upstreaming-v.patch276
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0019-Add-I2C-IPMB-support.patch426
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch565
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0022-Add-AST2500-eSPI-driver.patch597
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0025-dts-add-AST2500-LPC-SIO-tree-node.patch32
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0026-Add-support-for-new-PECI-commands.patch392
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0028-Add-AST2500-JTAG-driver.patch1138
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0029-i2c-aspeed-Improve-driver-to-support-multi-master-us.patch291
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0030-Add-dump-debug-code-into-I2C-drivers.patch151
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0031-Add-high-speed-baud-rate-support-for-UART.patch132
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0032-misc-aspeed-Add-Aspeed-UART-routing-control-driver.patch556
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0034-arm-dts-adpeed-Swap-the-mac-nodes-numbering.patch85
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0035-Implement-a-memory-driver-share-memory.patch249
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0036-net-ncsi-backport-ncsi-patches.patch1425
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0038-media-aspeed-backport-ikvm-patches.patch1982
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0039-Add-Aspeed-PWM-driver-which-uses-FTTMR010-timer-IP.patch702
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0040-i2c-Add-mux-hold-unhold-msg-types.patch492
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/intel.cfg1
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed_%.bbappend31
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers/0001-Enable-passthrough-based-gpio-character-device.patch26
-rw-r--r--meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-libc-headers_%.bbappend5
25 files changed, 10449 insertions, 0 deletions
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 <juston.li@intel.com>
+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 <juston.li@intel.com>
+---
+ 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 <vernon.mauery@intel.com>
+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" <james.feist@intel.com>
+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 <james.feist@linux.intel.com>
+Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
+---
+ 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 <asm/mach/irq.h>
++#include <linux/bitfield.h>
++#include <linux/gpio/driver.h>
++#include <linux/init.h>
++#include <linux/io.h>
++#include <linux/module.h>
++#include <linux/of_gpio.h>
++#include <linux/platform_device.h>
++
++#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 <ryan_chen@aspeedtech.com>");
++MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>");
++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 <vernon.mauery@intel.com>
+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 <vernon.mauery@intel.com>
+Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
+---
+ 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 <jae.hyun.yoo@intel.com>
+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 <jae.hyun.yoo@intel.com>
+---
+ 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 <linux/hwmon.h>
+ #include <linux/jiffies.h>
+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 <linux/hwmon.h>
+ #include <linux/jiffies.h>
+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 <linux/bitfield.h>
+ #include <linux/mfd/core.h>
+ #include <linux/mfd/intel-peci-client.h>
+ #include <linux/module.h>
+-#include <linux/peci.h>
+ #include <linux/of_device.h>
++#include <linux/peci.h>
+
+ #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 <linux/bitfield.h>
+ #include <linux/clk.h>
+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 <linux/bitfield.h>
+ #include <linux/crc8.h>
+@@ -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 <haiyue.wang@linux.intel.com>
+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 <haiyue.wang@linux.intel.com>
+---
+ 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 <haiyue.wang@linux.intel.com>
++
++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
++<https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmp-spec-v1.0.pdf>`_
++
++`Management Component Transport Protocol (MCTP)
++SMBus/I2C Transport Binding Specification
++<https://www.dmtf.org/sites/default/files/standards/documents/DSP0237_1.1.0.pdf>`_
++
++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 <sys/types.h>
++ #include <sys/stat.h>
++ #include <unistd.h>
++ #include <poll.h>
++ #include <time.h>
++ #include <fcntl.h>
++ #include <stdio.h>
++
++ 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 <linux/i2c.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/slab.h>
++#include <linux/spinlock.h>
++#include <linux/sysfs.h>
++
++#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 <haiyue.wang@linux.intel.com>");
++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 <yong.b.li@intel.com>
+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 <yong.b.li@intel.com>
+---
+ .../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 <linux/mfd/syscon.h>
++#include <linux/miscdevice.h>
++#include <linux/mm.h>
++#include <linux/module.h>
++#include <linux/of_address.h>
++#include <linux/platform_device.h>
++#include <linux/poll.h>
++#include <linux/regmap.h>
++
++#include <linux/aspeed-lpc-sio.h>
++
++#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 <ryan_chen@aspeedtech.com>");
++MODULE_AUTHOR("Yong Li <yong.blli@linux.intel.com>");
++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 <linux/ioctl.h>
++
++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 <haiyue.wang@linux.intel.com>
+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 <haiyue.wang@linux.intel.com>
+---
+ .../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 <haiyue.wang@linux.intel.com>
++
++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
++<https://www.intel.com/content/www/us/en/chipsets/c620-series-chipset-datasheet.html>`_
++
++ -- 17. Enhanced Serial Peripheral Interface
++
++
++`Enhanced Serial Peripheral Interface (eSPI)
++- Interface Base Specification (for Client and Server Platforms)
++<https://www.intel.com/content/dam/support/us/en/documents/software/chipset-software/327432-004_espi_base_specification_rev1.0.pdf>`_
++
+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 <linux/atomic.h>
++#include <linux/clk.h>
++#include <linux/errno.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/poll.h>
++#include <linux/regmap.h>
++#include <linux/sched.h>
++#include <linux/slab.h>
++#include <linux/timer.h>
++
++#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 <haiyue.wang@linux.intel.com>");
++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 <haiyue.wang@linux.intel.com>
+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 <haiyue.wang@linux.intel.com>
+---
+ 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" <jason.m.bills@intel.com>
+Date: Wed, 4 Apr 2018 13:52:39 -0700
+Subject: [PATCH] Add support for new PECI commands
+
+Signed-off-by: Jason M. Bills <jason.m.bills@intel.com>
+---
+ 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" <bryan.hunt@intel.com>
+Date: Fri, 30 Mar 2018 10:48:01 -0700
+Subject: [PATCH] Add AST2500d JTAG driver
+
+Adding aspeed jtag driver
+
+Signed-off-by: Hunt, Bryan <bryan.hunt@intel.com>
+---
+ 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 <linux/bitfield.h>
++#include <linux/delay.h>
++#include <linux/fs.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/jtag_drv.h>
++#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/uaccess.h>
++
++#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 <ryan_chen@aspeedtech.com>");
++MODULE_AUTHOR("Bryan Hunt <bryan.hunt@intel.com>");
++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 <jae.hyun.yoo@intel.com>
+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 <jae.hyun.yoo@linux.intel.com>
+---
+ 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 <jae.hyun.yoo@intel.com>
+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 <jae.hyun.yoo@intel.com>
+---
+ 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 <brendanhiggins@google.com>");
+ 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 <haiyue.wang@linux.intel.com>");
+ 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 <yong.b.li@linux.intel.com>
+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 <yong.b.li@linux.intel.com>
+---
+ 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 <dt-bindings/clock/aspeed-clock.h>
+
+-#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 <osk@google.com>
+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 <osk@google.com>
+Signed-off-by: Yong Li <yong.b.li@linux.intel.com>
+---
+ .../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 <osk@google.com>
++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 <osk@google.com>
++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 <linux/device.h>
++#include <linux/module.h>
++#include <linux/of_address.h>
++#include <linux/of_platform.h>
++
++/* 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 <osk@google.com>");
++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 <jae.hyun.yoo@intel.com>
+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 <jae.hyun.yoo@intel.com>
+---
+ 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 <cheng.c.yang@intel.com>
+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 <cheng.c.yang@intel.com>
+---
+ .../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 <linux/kernel.h>
++#include <linux/miscdevice.h>
++#include <linux/mm.h>
++#include <linux/module.h>
++#include <linux/of_platform.h>
++
++#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 <cheng.c.yang@intel.com>");
++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 <jae.hyun.yoo@intel.com>
+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 <sam@mendozajonas.com>
+Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
+---
+ 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 <jae.hyun.yoo@intel.com>
+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 <eajames@linux.ibm.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>
+---
+ .../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 <eajames@linux.ibm.com>
++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 <corentin.chary@gmail.com>
+ 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 <linux/atomic.h>
++#include <linux/bitfield.h>
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/interrupt.h>
++#include <linux/jiffies.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/of.h>
++#include <linux/of_irq.h>
++#include <linux/of_reserved_mem.h>
++#include <linux/platform_device.h>
++#include <linux/reset.h>
++#include <linux/sched.h>
++#include <linux/spinlock.h>
++#include <linux/string.h>
++#include <linux/v4l2-controls.h>
++#include <linux/videodev2.h>
++#include <linux/wait.h>
++#include <linux/workqueue.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-dev.h>
++#include <media/v4l2-device.h>
++#include <media/v4l2-dv-timings.h>
++#include <media/v4l2-event.h>
++#include <media/v4l2-ioctl.h>
++#include <media/videobuf2-dma-contig.h>
++
++#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 <jae.hyun.yoo@intel.com>
+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 <jae.hyun.yoo@intel.com>
+---
+ 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 <linux/bitops.h>
+ #include <linux/delay.h>
+
++#include <clocksource/timer-fttmr010.h>
++
+ /*
+ * 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 <linux/clk.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/pwm.h>
++
++/* For timer_fttmr010_lock */
++#include <clocksource/timer-fttmr010.h>
++
++#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 <jae.hyun.yoo@linux.intel.com>");
++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 <linux/spinlock.h>
++
++/*
++ * 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 <jae.hyun.yoo@intel.com>
+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 <jae.hyun.yoo@intel.com>
+---
+ 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 <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/slab.h>
++#include <linux/timer.h>
+
+ /* 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 <kuiying.wang@intel.com>
+Date: Sun, 3 Feb 2019 16:08:11 +0800
+Subject: [PATCH] Enable passthrough based gpio character device.
+
+Signed-off-by: Kuiying Wang <kuiying.wang@intel.com>
+---
+ 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 \
+ "