diff options
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-kernel')
43 files changed, 17305 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0001-set-idle-disconnect-to-true-in-all-cases.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0001-set-idle-disconnect-to-true-in-all-cases.patch new file mode 100644 index 000000000..925880eff --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0001-set-idle-disconnect-to-true-in-all-cases.patch @@ -0,0 +1,34 @@ +From 7854a5e094ac49bebf9b2bfdd44db2f8cdd37543 Mon Sep 17 00:00:00 2001 +From: James Feist <james.feist@linux.intel.com> +Date: Fri, 31 May 2019 15:05:13 -0700 +Subject: [PATCH] set idle-disconnect to true in all cases + +From sysfs this parameter can't be set. We want the +muxes to clean themselves up if possible. Set this to +true. + +Signed-off-by: James Feist <james.feist@linux.intel.com> +--- + drivers/i2c/muxes/i2c-mux-pca954x.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c +index 923aa3a5a3dc..084c10951890 100644 +--- a/drivers/i2c/muxes/i2c-mux-pca954x.c ++++ b/drivers/i2c/muxes/i2c-mux-pca954x.c +@@ -474,8 +474,12 @@ static int pca954x_probe(struct i2c_client *client, + data->last_chan = 0; /* force the first selection */ + data->idle_state = MUX_IDLE_AS_IS; + ++#if 1 /* Forcibly set the self-disconnect flag */ ++ idle_disconnect_dt = true; ++#else + idle_disconnect_dt = np && + of_property_read_bool(np, "i2c-mux-idle-disconnect"); ++#endif + if (idle_disconnect_dt) + data->idle_state = MUX_IDLE_DISCONNECT; + +-- +2.7.4 + 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..165596f25 --- /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 f72d4767835e530ce6bc4673ff30cc1099c88af5 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 + +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 dc58eb8c6969..db82f47339aa 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -283,7 +283,7 @@ + #gpio-cells = <2>; + 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>; +@@ -291,6 +291,15 @@ + #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>; +@@ -366,6 +375,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..695491d28 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0007-New-flash-map-for-intel.patch @@ -0,0 +1,117 @@ +From f57d473a30f208754457bdb63512c307f7499ac8 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 + +Signed-off-by: Vernon Mauery <vernon.mauery@intel.com> +Signed-off-by: Vikram Bodireddy <vikram.bodireddy@intel.com> +--- + .../boot/dts/openbmc-flash-layout-intel-128MB.dtsi | 50 ++++++++++++++++++++++ + .../boot/dts/openbmc-flash-layout-intel-64MB.dtsi | 38 ++++++++++++++++ + 2 files changed, 88 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 000000000000..0d3794423aed +--- /dev/null ++++ b/arch/arm/boot/dts/openbmc-flash-layout-intel-128MB.dtsi +@@ -0,0 +1,50 @@ ++// 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"; ++ }; ++ ++ pfm@80000 { ++ reg = <0x80000 0x20000>; ++ label = "pfm"; ++ }; ++ ++ u-boot-env@a0000 { ++ reg = <0xa0000 0x20000>; ++ label = "u-boot-env"; ++ }; ++ ++ sofs@c0000 { ++ reg = <0xc0000 0x200000>; ++ label = "sofs"; ++ }; ++ ++ rwfs@2c0000 { ++ reg = <0x2c0000 0x840000>; ++ label = "rwfs"; ++ }; ++ ++ fit-image-a@b00000 { ++ reg = <0xb00000 0x1f00000>; ++ label = "image-a"; ++ }; ++ ++ rc-image@2a00000 { ++ reg = <0x2a00000 0x2000000>; ++ label = "rc-image"; ++ }; ++ ++ image-staging@4a00000 { ++ reg = <0x4a00000 0x3600000>; ++ label = "image-stg"; ++ }; ++ ++}; +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 000000000000..092708f5021f +--- /dev/null ++++ b/arch/arm/boot/dts/openbmc-flash-layout-intel-64MB.dtsi +@@ -0,0 +1,38 @@ ++// 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..146a725dd --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0008-Add-ASPEED-SGPIO-driver.patch @@ -0,0 +1,764 @@ +From e6923abbc90b5b00bc9ea401fbb2a28971d19cbe Mon Sep 17 00:00:00 2001 +From: "Feist, James" <james.feist@intel.com> +Date: Tue, 4 Jun 2019 14:00:39 -0700 +Subject: [PATCH] gpio: aspeed: add ASPEED SGPIO driver + +Add SGPIO driver support for Aspeed SoCs. + +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 | 708 ++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 717 insertions(+) + create mode 100644 drivers/gpio/sgpio-aspeed.c + +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index acd40eb51c46..20808e48229a 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 6700eee860b7..77c6ec0ee98f 100644 +--- a/drivers/gpio/Makefile ++++ b/drivers/gpio/Makefile +@@ -33,6 +33,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..6fb402a3f74d +--- /dev/null ++++ b/drivers/gpio/sgpio-aspeed.c +@@ -0,0 +1,708 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++// Copyright (c) 2019 Intel Corporation ++ ++#include <linux/bitfield.h> ++#include <linux/clk.h> ++#include <linux/gpio/driver.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/spinlock.h> ++ ++#define ASPEED_SGPIO_CTRL 0x54 ++#define ASPEED_SGPIO_CLK_DIV_MASK GENMASK(31, 16) ++#define ASPEED_SGPIO_CLK_DIV_MIN 1 ++#define ASPEED_SGPIO_CLK_DIV_MAX 65535 ++#define ASPEED_SGPIO_PINBYTES_MASK GENMASK(9, 6) ++#define ASPEED_SGPIO_PINBYTES_MIN 1 ++#define ASPEED_SGPIO_PINBYTES_MAX 10 ++#define ASPEED_SGPIO_ENABLE BIT(0) ++ ++#define ASPEED_SGPIO_BUS_FREQ_DEFAULT 1000000 ++ ++struct aspeed_bank_props { ++ unsigned int bank; ++ u32 input; ++ u32 output; ++}; ++ ++struct aspeed_sgpio_config { ++ unsigned int nr_pgpios; ++ unsigned int nr_gpios; ++ const struct aspeed_bank_props *props; ++}; ++ ++struct aspeed_sgpio { ++ struct gpio_chip chip; ++ spinlock_t lock; ++ void __iomem *base; ++ int irq; ++ const struct aspeed_sgpio_config *config; ++ ++ u32 *dcache; ++}; ++ ++struct aspeed_sgpio_bank { ++ uint16_t val_reg; ++ uint16_t rdata_reg; ++ uint16_t tolerance_reg; ++ uint16_t irq_regs; ++ bool support_irq; ++ const char names[4][3]; ++}; ++ ++/* ++ * Note: The "val" register returns the input value sampled on the line. ++ * Or, it can be used for writing a value on the line. ++ * ++ * The "rdata" register returns the content of the write latch and thus ++ * can be used to read back what was last written reliably. ++ */ ++ ++static const struct aspeed_sgpio_bank aspeed_sgpio_banks[] = { ++ { ++ .val_reg = 0x0000, ++ .rdata_reg = 0x0070, ++ .tolerance_reg = 0x0018, ++ .irq_regs = 0x0004, ++ .support_irq = false, ++ .names = { "OA", "OB", "OC", "OD" }, ++ }, ++ { ++ .val_reg = 0x001C, ++ .rdata_reg = 0x0074, ++ .tolerance_reg = 0x0034, ++ .irq_regs = 0x0020, ++ .support_irq = false, ++ .names = { "OE", "OF", "OG", "OH" }, ++ }, ++ { ++ .val_reg = 0x0038, ++ .rdata_reg = 0x0078, ++ .tolerance_reg = 0x0050, ++ .irq_regs = 0x003C, ++ .support_irq = false, ++ .names = { "OI", "OJ" }, ++ }, ++ { ++ .val_reg = 0x0000, ++ .rdata_reg = 0x0070, ++ .tolerance_reg = 0x0018, ++ .irq_regs = 0x0004, ++ .support_irq = true, ++ .names = { "IA", "IB", "IC", "ID" }, ++ }, ++ { ++ .val_reg = 0x001C, ++ .rdata_reg = 0x0074, ++ .tolerance_reg = 0x0034, ++ .irq_regs = 0x0020, ++ .support_irq = true, ++ .names = { "IE", "IF", "IG", "IH" }, ++ }, ++ { ++ .val_reg = 0x0038, ++ .rdata_reg = 0x0078, ++ .tolerance_reg = 0x0050, ++ .irq_regs = 0x003C, ++ .support_irq = true, ++ .names = { "II", "IJ" }, ++ }, ++}; ++ ++enum aspeed_sgpio_reg { ++ reg_val, ++ reg_rdata, ++ reg_irq_enable, ++ reg_irq_type0, ++ reg_irq_type1, ++ reg_irq_type2, ++ reg_irq_status, ++ reg_tolerance, ++}; ++ ++#define GPIO_IRQ_ENABLE 0x00 ++#define GPIO_IRQ_TYPE0 0x04 ++#define GPIO_IRQ_TYPE1 0x08 ++#define GPIO_IRQ_TYPE2 0x0c ++#define GPIO_IRQ_STATUS 0x10 ++ ++/* This will be resolved at compile time */ ++static inline void __iomem *bank_reg(struct aspeed_sgpio *gpio, ++ const struct aspeed_sgpio_bank *bank, ++ const enum aspeed_sgpio_reg reg) ++{ ++ switch (reg) { ++ case reg_val: ++ return gpio->base + bank->val_reg; ++ case reg_rdata: ++ return gpio->base + bank->rdata_reg; ++ case reg_irq_enable: ++ return gpio->base + bank->irq_regs + GPIO_IRQ_ENABLE; ++ case reg_irq_type0: ++ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE0; ++ case reg_irq_type1: ++ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE1; ++ case reg_irq_type2: ++ return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE2; ++ case reg_irq_status: ++ return gpio->base + bank->irq_regs + GPIO_IRQ_STATUS; ++ case reg_tolerance: ++ return gpio->base + bank->tolerance_reg; ++ default: ++ WARN_ON(1); ++ } ++ ++ return NULL; ++} ++ ++#define GPIO_BANK(x) ((x) >> 5) ++#define GPIO_OFFSET(x) ((x) & 0x1f) ++#define GPIO_BIT(x) BIT(GPIO_OFFSET(x)) ++ ++static const struct aspeed_sgpio_bank *to_bank(unsigned int offset) ++{ ++ unsigned int bank = GPIO_BANK(offset); ++ ++ WARN_ON(bank >= ARRAY_SIZE(aspeed_sgpio_banks)); ++ return &aspeed_sgpio_banks[bank]; ++} ++ ++static inline bool is_bank_props_sentinel(const struct aspeed_bank_props *props) ++{ ++ return !(props->input || props->output); ++} ++ ++static inline const struct aspeed_bank_props *find_bank_props( ++ struct aspeed_sgpio *gpio, unsigned int offset) ++{ ++ const struct aspeed_bank_props *props = gpio->config->props; ++ ++ while (!is_bank_props_sentinel(props)) { ++ if (props->bank == GPIO_BANK(offset)) ++ return props; ++ props++; ++ } ++ ++ return NULL; ++} ++ ++static inline bool have_input(struct aspeed_sgpio *gpio, unsigned int offset) ++{ ++ const struct aspeed_bank_props *props = find_bank_props(gpio, offset); ++ ++ return !props || (props->input & GPIO_BIT(offset)); ++} ++ ++static inline bool have_output(struct aspeed_sgpio *gpio, unsigned int offset) ++{ ++ const struct aspeed_bank_props *props = find_bank_props(gpio, offset); ++ ++ return !props || (props->output & GPIO_BIT(offset)); ++} ++ ++static int aspeed_sgpio_get(struct gpio_chip *gc, unsigned int offset) ++{ ++ struct aspeed_sgpio *gpio = gpiochip_get_data(gc); ++ const struct aspeed_sgpio_bank *bank = to_bank(offset); ++ enum aspeed_sgpio_reg reg; ++ ++ if (have_output(gpio, offset)) ++ reg = reg_rdata; ++ else ++ reg = reg_val; ++ ++ return !!(ioread32(bank_reg(gpio, bank, reg)) & GPIO_BIT(offset)); ++} ++ ++static void aspeed_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val) ++{ ++ const struct aspeed_sgpio_bank *bank = to_bank(offset); ++ struct aspeed_sgpio *gpio = gpiochip_get_data(gc); ++ unsigned long flags; ++ u32 reg; ++ ++ if (!have_output(gpio, offset)) ++ return; ++ ++ spin_lock_irqsave(&gpio->lock, flags); ++ ++ reg = ioread32(bank_reg(gpio, bank, reg_rdata)); ++ ++ if (val) ++ reg |= GPIO_BIT(offset); ++ else ++ reg &= ~GPIO_BIT(offset); ++ ++ iowrite32(reg, bank_reg(gpio, bank, reg_val)); ++ ++ spin_unlock_irqrestore(&gpio->lock, flags); ++} ++ ++static int aspeed_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset) ++{ ++ struct aspeed_sgpio *gpio = gpiochip_get_data(gc); ++ ++ if (!have_input(gpio, offset)) ++ return -ENOTSUPP; ++ ++ return 0; ++} ++ ++static int aspeed_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, ++ int val) ++{ ++ struct aspeed_sgpio *gpio = gpiochip_get_data(gc); ++ ++ if (!have_output(gpio, offset)) ++ return -ENOTSUPP; ++ ++ return 0; ++} ++ ++static int aspeed_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset) ++{ ++ struct aspeed_sgpio *gpio = gpiochip_get_data(gc); ++ ++ if (have_output(gpio, offset)) ++ return 0; ++ else if (have_input(gpio, offset)) ++ return 1; ++ ++ return -ENOTSUPP; ++} ++ ++static inline int ++irqd_to_aspeed_sgpio_data(struct irq_data *d, struct aspeed_sgpio **gpio, ++ const struct aspeed_sgpio_bank **bank, ++ u32 *bit, int *offset) ++{ ++ struct aspeed_sgpio *internal; ++ ++ *offset = irqd_to_hwirq(d); ++ ++ internal = irq_data_get_irq_chip_data(d); ++ ++ *gpio = internal; ++ *bank = to_bank(*offset); ++ *bit = GPIO_BIT(*offset); ++ ++ return 0; ++} ++ ++static void aspeed_sgpio_irq_ack(struct irq_data *d) ++{ ++ const struct aspeed_sgpio_bank *bank; ++ struct aspeed_sgpio *gpio; ++ void __iomem *status_addr; ++ unsigned long flags; ++ int rc, offset; ++ u32 bit; ++ ++ rc = irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset); ++ if (rc) ++ return; ++ ++ status_addr = bank_reg(gpio, bank, reg_irq_status); ++ ++ spin_lock_irqsave(&gpio->lock, flags); ++ ++ iowrite32(bit, status_addr); ++ ++ spin_unlock_irqrestore(&gpio->lock, flags); ++} ++ ++static void aspeed_sgpio_irq_set_mask(struct irq_data *d, bool set) ++{ ++ const struct aspeed_sgpio_bank *bank; ++ struct aspeed_sgpio *gpio; ++ unsigned long flags; ++ u32 reg, bit; ++ void __iomem *addr; ++ int rc, offset; ++ ++ rc = irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset); ++ if (rc) ++ return; ++ ++ if (!bank->support_irq) ++ return; ++ ++ addr = bank_reg(gpio, bank, reg_irq_enable); ++ ++ spin_lock_irqsave(&gpio->lock, flags); ++ ++ reg = ioread32(addr); ++ if (set) ++ reg |= bit; ++ else ++ reg &= ~bit; ++ ++ iowrite32(reg, addr); ++ ++ spin_unlock_irqrestore(&gpio->lock, flags); ++} ++ ++static void aspeed_sgpio_irq_mask(struct irq_data *d) ++{ ++ aspeed_sgpio_irq_set_mask(d, false); ++} ++ ++static void aspeed_sgpio_irq_unmask(struct irq_data *d) ++{ ++ aspeed_sgpio_irq_set_mask(d, true); ++} ++ ++static int aspeed_sgpio_set_type(struct irq_data *d, unsigned int type) ++{ ++ u32 type0 = 0; ++ u32 type1 = 0; ++ u32 type2 = 0; ++ u32 bit, reg; ++ const struct aspeed_sgpio_bank *bank; ++ irq_flow_handler_t handler; ++ struct aspeed_sgpio *gpio; ++ unsigned long flags; ++ void __iomem *addr; ++ int rc, offset; ++ ++ rc = irqd_to_aspeed_sgpio_data(d, &gpio, &bank, &bit, &offset); ++ if (rc) ++ return -EINVAL; ++ ++ if (!bank->support_irq) ++ return -ENOTSUPP; ++ ++ switch (type & IRQ_TYPE_SENSE_MASK) { ++ case IRQ_TYPE_EDGE_BOTH: ++ type2 |= bit; ++ /* fall through */ ++ case IRQ_TYPE_EDGE_RISING: ++ type0 |= bit; ++ /* fall through */ ++ case IRQ_TYPE_EDGE_FALLING: ++ handler = handle_edge_irq; ++ break; ++ case IRQ_TYPE_LEVEL_HIGH: ++ type0 |= bit; ++ /* fall through */ ++ case IRQ_TYPE_LEVEL_LOW: ++ type1 |= bit; ++ handler = handle_level_irq; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ spin_lock_irqsave(&gpio->lock, flags); ++ ++ addr = bank_reg(gpio, bank, reg_irq_type0); ++ reg = ioread32(addr); ++ reg = (reg & ~bit) | type0; ++ iowrite32(reg, addr); ++ ++ addr = bank_reg(gpio, bank, reg_irq_type1); ++ reg = ioread32(addr); ++ reg = (reg & ~bit) | type1; ++ iowrite32(reg, addr); ++ ++ addr = bank_reg(gpio, bank, reg_irq_type2); ++ reg = ioread32(addr); ++ reg = (reg & ~bit) | type2; ++ iowrite32(reg, addr); ++ ++ spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ irq_set_handler_locked(d, handler); ++ ++ return 0; ++} ++ ++static void aspeed_sgpio_irq_handler(struct irq_desc *desc) ++{ ++ struct gpio_chip *gc = irq_desc_get_handler_data(desc); ++ struct aspeed_sgpio *data = gpiochip_get_data(gc); ++ struct irq_chip *ic = irq_desc_get_chip(desc); ++ unsigned int i, p, girq; ++ unsigned long reg; ++ ++ chained_irq_enter(ic, desc); ++ ++ for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) { ++ const struct aspeed_sgpio_bank *bank = &aspeed_sgpio_banks[i]; ++ ++ if (!bank->support_irq) ++ continue; ++ ++ reg = ioread32(bank_reg(data, bank, reg_irq_status)); ++ ++ for_each_set_bit(p, ®, 32) { ++ girq = irq_find_mapping(gc->irq.domain, i * 32 + p); ++ generic_handle_irq(girq); ++ } ++ } ++ ++ chained_irq_exit(ic, desc); ++} ++ ++static struct irq_chip aspeed_sgpio_irqchip = { ++ .name = "aspeed-sgpio", ++ .irq_ack = aspeed_sgpio_irq_ack, ++ .irq_mask = aspeed_sgpio_irq_mask, ++ .irq_unmask = aspeed_sgpio_irq_unmask, ++ .irq_set_type = aspeed_sgpio_set_type, ++}; ++ ++static void set_irq_valid_mask(struct aspeed_sgpio *gpio) ++{ ++ const struct aspeed_bank_props *props = gpio->config->props; ++ ++ while (!is_bank_props_sentinel(props)) { ++ unsigned int offset; ++ const unsigned long int input = props->input; ++ ++ /* Pretty crummy approach, but similar to GPIO core */ ++ for_each_clear_bit(offset, &input, 32) { ++ unsigned int i = props->bank * 32 + offset; ++ ++ if (i >= gpio->config->nr_gpios) ++ break; ++ ++ clear_bit(i, gpio->chip.irq.valid_mask); ++ } ++ ++ props++; ++ } ++} ++ ++static int aspeed_sgpio_setup_irqs(struct aspeed_sgpio *gpio, ++ struct platform_device *pdev) ++{ ++ const struct aspeed_sgpio_bank *bank; ++ int rc, i; ++ ++ /* Initialize IRQ and tolerant settings */ ++ for (i = 0; i < ARRAY_SIZE(aspeed_sgpio_banks); i++) { ++ bank = &aspeed_sgpio_banks[i]; ++ ++ /* Value will be reset by WDT reset */ ++ iowrite32(0x00000000, bank_reg(gpio, bank, reg_tolerance)); ++ ++ if (!bank->support_irq) ++ continue; ++ ++ /* disable irq enable bits */ ++ iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_enable)); ++ /* clear status bits */ ++ iowrite32(0xffffffff, bank_reg(gpio, bank, reg_irq_status)); ++ /* set rising or level-high irq */ ++ iowrite32(0xffffffff, bank_reg(gpio, bank, reg_irq_type0)); ++ /* trigger type is level */ ++ iowrite32(0xffffffff, bank_reg(gpio, bank, reg_irq_type1)); ++ /* single trigger mode */ ++ iowrite32(0x00000000, bank_reg(gpio, bank, reg_irq_type2)); ++ } ++ ++ rc = platform_get_irq(pdev, 0); ++ if (rc < 0) ++ return rc; ++ ++ gpio->irq = rc; ++ ++ set_irq_valid_mask(gpio); ++ ++ rc = gpiochip_irqchip_add(&gpio->chip, &aspeed_sgpio_irqchip, ++ 0, handle_bad_irq, IRQ_TYPE_NONE); ++ if (rc) { ++ dev_info(&pdev->dev, "Could not add irqchip\n"); ++ return rc; ++ } ++ ++ gpiochip_set_chained_irqchip(&gpio->chip, &aspeed_sgpio_irqchip, ++ gpio->irq, aspeed_sgpio_irq_handler); ++ ++ return 0; ++} ++ ++static int aspeed_sgpio_reset_tolerance(struct gpio_chip *chip, ++ unsigned int offset, bool enable) ++{ ++ struct aspeed_sgpio *gpio = gpiochip_get_data(chip); ++ unsigned long flags; ++ void __iomem *treg; ++ u32 val; ++ ++ treg = bank_reg(gpio, to_bank(offset), reg_tolerance); ++ ++ spin_lock_irqsave(&gpio->lock, flags); ++ ++ val = readl(treg); ++ ++ if (enable) ++ val |= GPIO_BIT(offset); ++ else ++ val &= ~GPIO_BIT(offset); ++ ++ writel(val, treg); ++ ++ spin_unlock_irqrestore(&gpio->lock, flags); ++ ++ return 0; ++} ++ ++static int aspeed_sgpio_set_config(struct gpio_chip *chip, unsigned int offset, ++ unsigned long config) ++{ ++ unsigned long param = pinconf_to_config_param(config); ++ u32 arg = pinconf_to_config_argument(config); ++ ++ if (param == PIN_CONFIG_PERSIST_STATE) ++ return aspeed_sgpio_reset_tolerance(chip, offset, arg); ++ ++ return -ENOTSUPP; ++} ++ ++/* ++ * Any banks not specified in a struct aspeed_bank_props array are assumed to ++ * have the properties: ++ * ++ * { .input = 0xffffffff, .output = 0xffffffff } ++ */ ++ ++static const struct aspeed_bank_props ast_sgpio_bank_props[] = { ++ /* input output */ ++ { 0, 0x00000000, 0xffffffff }, /* OA/OB/OC/OD */ ++ { 1, 0x00000000, 0xffffffff }, /* OE/OF/OG/OH */ ++ { 2, 0x00000000, 0x0000ffff }, /* OI/OJ */ ++ { 3, 0xffffffff, 0x00000000 }, /* IA/IB/IC/ID */ ++ { 4, 0xffffffff, 0x00000000 }, /* IE/IF/IG/IH */ ++ { 5, 0x0000ffff, 0x00000000 }, /* II/IJ */ ++ { } ++}; ++ ++/* ++ * This H/W has 80 bidirectional lines so this driver provides total 160 lines ++ * for 80 outputs and 80 inputs. To simplify bank register manipulation, it ++ * uses 96 lines per each input and output set so total 192 lines it has. ++ */ ++static const struct aspeed_sgpio_config ast2400_config = ++ { .nr_pgpios = 224, .nr_gpios = 192, .props = ast_sgpio_bank_props }; ++ ++static const struct aspeed_sgpio_config ast2500_config = ++ { .nr_pgpios = 232, .nr_gpios = 192, .props = ast_sgpio_bank_props }; ++ ++static const struct of_device_id aspeed_sgpio_of_table[] = { ++ { .compatible = "aspeed,ast2400-sgpio", .data = &ast2400_config }, ++ { .compatible = "aspeed,ast2500-sgpio", .data = &ast2500_config }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_sgpio_of_table); ++ ++static int __init aspeed_sgpio_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *gpio_id; ++ u32 sgpio_freq, clk_div, nb_gpios; ++ struct aspeed_sgpio *gpio; ++ unsigned long src_freq; ++ struct clk *clk; ++ int rc, banks; ++ ++ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); ++ if (!gpio) ++ return -ENOMEM; ++ ++ gpio->base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(gpio->base)) ++ return PTR_ERR(gpio->base); ++ ++ spin_lock_init(&gpio->lock); ++ ++ gpio_id = of_match_node(aspeed_sgpio_of_table, pdev->dev.of_node); ++ if (!gpio_id) ++ return -EINVAL; ++ ++ gpio->config = gpio_id->data; ++ ++ rc = device_property_read_u32(&pdev->dev, "bus-frequency", &sgpio_freq); ++ if (rc < 0) { ++ dev_warn(&pdev->dev, "Could not read bus-frequency property. Use default.\n"); ++ sgpio_freq = ASPEED_SGPIO_BUS_FREQ_DEFAULT; ++ } ++ ++ clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(clk)) { ++ dev_err(&pdev->dev, "Failed to get clk source.\n"); ++ return PTR_ERR(clk); ++ } ++ ++ /* ++ * There is a limitation that SGPIO clock division has to be larger or ++ * equal to 1. And a read back value of clock division is 1-bit left ++ * shifted from the 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 an example, SGPIO clock is 1MHz and number of SGPIO is 80. Each ++ * SGPIO will be updated every 80us. ++ */ ++ src_freq = clk_get_rate(clk); ++ clk_div = src_freq / (2 * sgpio_freq) - 1; ++ if (clk_div < ASPEED_SGPIO_CLK_DIV_MIN) ++ clk_div = ASPEED_SGPIO_CLK_DIV_MIN; ++ else if (clk_div > ASPEED_SGPIO_CLK_DIV_MAX) ++ clk_div = ASPEED_SGPIO_CLK_DIV_MAX; ++ ++ nb_gpios = gpio->config->nr_gpios / 16; ++ if (nb_gpios < ASPEED_SGPIO_PINBYTES_MIN) ++ nb_gpios = ASPEED_SGPIO_PINBYTES_MIN; ++ else if (nb_gpios > ASPEED_SGPIO_PINBYTES_MAX) ++ nb_gpios = ASPEED_SGPIO_PINBYTES_MAX; ++ ++ iowrite32(FIELD_PREP(ASPEED_SGPIO_CLK_DIV_MASK, clk_div) | ++ FIELD_PREP(ASPEED_SGPIO_PINBYTES_MASK, nb_gpios) | ++ ASPEED_SGPIO_ENABLE, ++ gpio->base + ASPEED_SGPIO_CTRL); ++ ++ gpio->chip.parent = &pdev->dev; ++ gpio->chip.ngpio = gpio->config->nr_gpios; ++ ++ gpio->chip.direction_input = aspeed_sgpio_dir_in; ++ gpio->chip.direction_output = aspeed_sgpio_dir_out; ++ gpio->chip.get_direction = aspeed_sgpio_get_direction; ++ gpio->chip.get = aspeed_sgpio_get; ++ gpio->chip.set = aspeed_sgpio_set; ++ gpio->chip.set_config = aspeed_sgpio_set_config; ++ gpio->chip.label = dev_name(&pdev->dev); ++ gpio->chip.base = -1; ++ gpio->chip.irq.need_valid_mask = true; ++ ++ /* Allocate a cache of the output registers */ ++ banks = gpio->config->nr_gpios >> 5; ++ ++ gpio->dcache = devm_kcalloc(&pdev->dev, ++ banks, sizeof(u32), GFP_KERNEL); ++ ++ if (!gpio->dcache) ++ return -ENOMEM; ++ ++ rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio); ++ if (rc < 0) ++ return rc; ++ ++ return aspeed_sgpio_setup_irqs(gpio, pdev); ++} ++ ++static struct platform_driver aspeed_sgpio_driver = { ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = aspeed_sgpio_of_table, ++ }, ++}; ++ ++module_platform_driver_probe(aspeed_sgpio_driver, aspeed_sgpio_probe); ++ ++MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); ++MODULE_DESCRIPTION("Aspeed SGPIO Master 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..4210d9f67 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0009-SGPIO-DT-and-pinctrl-fixup.patch @@ -0,0 +1,244 @@ +From 0f63ecc25766fdf66610d08441e59147a4cbde78 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 | 56 +++++++++++------------------- + arch/arm/boot/dts/aspeed-g5.dtsi | 10 ++++++ + drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c | 48 ++++++++++++------------- + drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c | 4 +++ + 4 files changed, 58 insertions(+), 60 deletions(-) + +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index fd857be397bf..58306a8232c9 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -207,6 +207,20 @@ + #interrupt-cells = <2>; + }; + ++ sgpio: sgpio@1e780200 { ++ #gpio-cells = <2>; ++ gpio-controller; ++ compatible = "aspeed,ast2400-sgpio"; ++ reg = <0x1e780200 0x0100>; ++ interrupts = <40>; ++ interrupt-controller; ++ clocks = <&syscon ASPEED_CLK_APB>; ++ bus-frequency = <1000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgpm_default>; ++ status = "disabled"; ++ }; ++ + timer: timer@1e782000 { + /* This timer is a Faraday FTTMR010 derivative */ + compatible = "aspeed,ast2400-timer"; +@@ -1180,44 +1194,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 db82f47339aa..425a542690de 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -298,6 +298,11 @@ + reg = <0x1e780200 0x0100>; + interrupts = <40>; + interrupt-controller; ++ clocks = <&syscon ASPEED_CLK_APB>; ++ bus-frequency = <1000000>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pinctrl_sgpm_default>; ++ status = "disabled"; + }; + + rtc: rtc@1e781000 { +@@ -1403,6 +1408,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 384396cbb22d..a78ed8c33e96 100644 +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g4.c +@@ -423,16 +423,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)); +@@ -598,16 +604,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)); +@@ -2105,14 +2117,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), +@@ -2260,14 +2266,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 6f151e7c8d81..c088f010c554 100644 +--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c ++++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c +@@ -599,6 +599,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)); +@@ -2149,6 +2151,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), +@@ -2318,6 +2321,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..329a92cd6 --- /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,5566 @@ +From 691b8580a1592eddb919e8dd295e6c1f136a4c00 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 + +Upstreaming is in holding. It's for adding DTS sensor with PECI +subsystem code update. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + Documentation/hwmon/peci-cputemp | 34 +- + drivers/hwmon/Kconfig | 4 +- + drivers/hwmon/peci-cputemp.c | 171 ++++--- + drivers/hwmon/peci-dimmtemp.c | 184 +++++-- + drivers/hwmon/peci-hwmon.h | 9 +- + drivers/mfd/Kconfig | 5 +- + drivers/mfd/intel-peci-client.c | 51 +- + drivers/peci/Kconfig | 46 +- + drivers/peci/Makefile | 7 +- + drivers/peci/busses/Kconfig | 32 ++ + drivers/peci/busses/Makefile | 7 + + drivers/peci/busses/peci-aspeed.c | 492 ++++++++++++++++++ + drivers/peci/busses/peci-npcm.c | 410 +++++++++++++++ + drivers/peci/peci-aspeed.c | 505 ------------------- + drivers/peci/peci-core.c | 914 ++++++++++++++++++---------------- + drivers/peci/peci-dev.c | 346 +++++++++++++ + drivers/peci/peci-npcm.c | 410 --------------- + include/linux/mfd/intel-peci-client.h | 31 +- + include/linux/peci.h | 30 +- + include/uapi/linux/peci-ioctl.h | 416 +++++++++------- + 20 files changed, 2402 insertions(+), 1702 deletions(-) + create mode 100644 drivers/peci/busses/Kconfig + create mode 100644 drivers/peci/busses/Makefile + create mode 100644 drivers/peci/busses/peci-aspeed.c + create mode 100644 drivers/peci/busses/peci-npcm.c + delete mode 100644 drivers/peci/peci-aspeed.c + create mode 100644 drivers/peci/peci-dev.c + delete mode 100644 drivers/peci/peci-npcm.c + +diff --git a/Documentation/hwmon/peci-cputemp b/Documentation/hwmon/peci-cputemp +index 821a9258f2e6..a3a3e465c888 100644 +--- a/Documentation/hwmon/peci-cputemp ++++ b/Documentation/hwmon/peci-cputemp +@@ -51,28 +51,38 @@ temp1_crit Provides shutdown temperature of the CPU package which + temp1_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of + the CPU package. + +-temp2_label "Tcontrol" +-temp2_input Provides current Tcontrol temperature of the CPU ++temp2_label "DTS" ++temp2_input Provides current DTS temperature of the CPU package. ++temp2_max Provides thermal control temperature of the CPU package ++ which is also known as Tcontrol. ++temp2_crit Provides shutdown temperature of the CPU package which ++ is also known as the maximum processor junction ++ temperature, Tjmax or Tprochot. ++temp2_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of ++ the CPU package. ++ ++temp3_label "Tcontrol" ++temp3_input Provides current Tcontrol temperature of the CPU + package which is also known as Fan Temperature target. + Indicates the relative value from thermal monitor trip + temperature at which fans should be engaged. +-temp2_crit Provides Tcontrol critical value of the CPU package ++temp3_crit Provides Tcontrol critical value of the CPU package + which is same to Tjmax. + +-temp3_label "Tthrottle" +-temp3_input Provides current Tthrottle temperature of the CPU ++temp4_label "Tthrottle" ++temp4_input Provides current Tthrottle temperature of the CPU + package. Used for throttling temperature. If this value + is allowed and lower than Tjmax - the throttle will + occur and reported at lower than Tjmax. + +-temp4_label "Tjmax" +-temp4_input Provides the maximum junction temperature, Tjmax of the ++temp5_label "Tjmax" ++temp5_input Provides the maximum junction temperature, Tjmax of the + CPU package. + +-temp[5-*]_label Provides string "Core X", where X is resolved core ++temp[6-*]_label Provides string "Core X", where X is resolved core + number. +-temp[5-*]_input Provides current temperature of each core. +-temp[5-*]_max Provides thermal control temperature of the core. +-temp[5-*]_crit Provides shutdown temperature of the core. +-temp[5-*]_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of ++temp[6-*]_input Provides current temperature of each core. ++temp[6-*]_max Provides thermal control temperature of the core. ++temp[6-*]_crit Provides shutdown temperature of the core. ++temp[6-*]_crit_hyst Provides the hysteresis value from Tcontrol to Tjmax of + the core. +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index c0623fa5ba41..7399c3cef30c 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1333,7 +1333,7 @@ config SENSORS_PECI_CPUTEMP + the PECI Client Command Suite via the processor PECI client. + Check Documentation/hwmon/peci-cputemp for details. + +- This driver can also be built as a module. If so, the module ++ This driver can also be built as a module. If so, the module + will be called peci-cputemp. + + config SENSORS_PECI_DIMMTEMP +@@ -1347,7 +1347,7 @@ config SENSORS_PECI_DIMMTEMP + Suite via the processor PECI client. + Check Documentation/hwmon/peci-dimmtemp for details. + +- This driver can also be built as a module. If so, the module ++ This driver can also be built as a module. If so, the module + will be called peci-dimmtemp. + + source "drivers/hwmon/pmbus/Kconfig" +diff --git a/drivers/hwmon/peci-cputemp.c b/drivers/hwmon/peci-cputemp.c +index 11880c86a854..d0d68e88889f 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> +@@ -9,18 +9,13 @@ + #include <linux/platform_device.h> + #include "peci-hwmon.h" + +-#define DEFAULT_CHANNEL_NUMS 4 ++#define DEFAULT_CHANNEL_NUMS 5 + #define CORETEMP_CHANNEL_NUMS CORE_NUMS_MAX + #define CPUTEMP_CHANNEL_NUMS (DEFAULT_CHANNEL_NUMS + CORETEMP_CHANNEL_NUMS) + +-/* The RESOLVED_CORES register in PCU of a client CPU */ +-#define REG_RESOLVED_CORES_BUS 1 +-#define REG_RESOLVED_CORES_DEVICE 30 +-#define REG_RESOLVED_CORES_FUNCTION 3 +-#define REG_RESOLVED_CORES_OFFSET 0xB4 +- + struct temp_group { + struct temp_data die; ++ struct temp_data dts; + struct temp_data tcontrol; + struct temp_data tthrottle; + struct temp_data tjmax; +@@ -43,6 +38,7 @@ struct peci_cputemp { + + enum cputemp_channels { + channel_die, ++ channel_dts, + channel_tcontrol, + channel_tthrottle, + channel_tjmax, +@@ -54,6 +50,10 @@ static const u32 config_table[DEFAULT_CHANNEL_NUMS + 1] = { + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_CRIT_HYST, + ++ /* DTS margin */ ++ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | ++ HWMON_T_CRIT_HYST, ++ + /* Tcontrol temperature */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT, + +@@ -70,6 +70,7 @@ static const u32 config_table[DEFAULT_CHANNEL_NUMS + 1] = { + + static const char *cputemp_label[CPUTEMP_CHANNEL_NUMS] = { + "Die", ++ "DTS", + "Tcontrol", + "Tthrottle", + "Tjmax", +@@ -92,19 +93,20 @@ static int get_temp_targets(struct peci_cputemp *priv) + s32 tthrottle_offset; + s32 tcontrol_margin; + u8 pkg_cfg[4]; +- int rc; ++ int ret; + +- /** ++ /* + * Just use only the tcontrol marker to determine if target values need + * update. + */ + if (!peci_temp_need_update(&priv->temp.tcontrol)) + return 0; + +- rc = peci_client_read_package_config(priv->mgr, +- MBX_INDEX_TEMP_TARGET, 0, pkg_cfg); +- if (rc) +- return rc; ++ ret = peci_client_read_package_config(priv->mgr, ++ PECI_MBX_INDEX_TEMP_TARGET, 0, ++ pkg_cfg); ++ if (ret) ++ return ret; + + priv->temp.tjmax.value = pkg_cfg[2] * 1000; + +@@ -123,17 +125,16 @@ static int get_temp_targets(struct peci_cputemp *priv) + static int get_die_temp(struct peci_cputemp *priv) + { + struct peci_get_temp_msg msg; +- int rc; ++ int ret; + + if (!peci_temp_need_update(&priv->temp.die)) + return 0; + + msg.addr = priv->mgr->client->addr; + +- rc = peci_command(priv->mgr->client->adapter, PECI_CMD_GET_TEMP, +- &msg); +- if (rc) +- return rc; ++ ret = peci_command(priv->mgr->client->adapter, PECI_CMD_GET_TEMP, &msg); ++ if (ret) ++ return ret; + + /* Note that the tjmax should be available before calling it */ + priv->temp.die.value = priv->temp.tjmax.value + +@@ -144,24 +145,64 @@ static int get_die_temp(struct peci_cputemp *priv) + return 0; + } + ++static int get_dts(struct peci_cputemp *priv) ++{ ++ s32 dts_margin; ++ u8 pkg_cfg[4]; ++ int ret; ++ ++ if (!peci_temp_need_update(&priv->temp.dts)) ++ return 0; ++ ++ ret = peci_client_read_package_config(priv->mgr, ++ PECI_MBX_INDEX_DTS_MARGIN, 0, ++ pkg_cfg); ++ ++ if (ret) ++ return ret; ++ ++ dts_margin = (pkg_cfg[1] << 8) | pkg_cfg[0]; ++ ++ /** ++ * Processors return a value of DTS reading in 10.6 format ++ * (10 bits signed decimal, 6 bits fractional). ++ * Error codes: ++ * 0x8000: General sensor error ++ * 0x8001: Reserved ++ * 0x8002: Underflow on reading value ++ * 0x8003-0x81ff: Reserved ++ */ ++ if (dts_margin >= 0x8000 && dts_margin <= 0x81ff) ++ return -EIO; ++ ++ dts_margin = ten_dot_six_to_millidegree(dts_margin); ++ ++ /* Note that the tcontrol should be available before calling it */ ++ priv->temp.dts.value = priv->temp.tcontrol.value - dts_margin; ++ ++ peci_temp_mark_updated(&priv->temp.dts); ++ ++ return 0; ++} ++ + static int get_core_temp(struct peci_cputemp *priv, int core_index) + { + s32 core_dts_margin; + u8 pkg_cfg[4]; +- int rc; ++ int ret; + + if (!peci_temp_need_update(&priv->temp.core[core_index])) + return 0; + +- rc = peci_client_read_package_config(priv->mgr, +- MBX_INDEX_PER_CORE_DTS_TEMP, +- core_index, pkg_cfg); +- if (rc) +- return rc; ++ ret = peci_client_read_package_config(priv->mgr, ++ PECI_MBX_INDEX_PER_CORE_DTS_TEMP, ++ core_index, pkg_cfg); ++ if (ret) ++ return ret; + + core_dts_margin = le16_to_cpup((__le16 *)pkg_cfg); + +- /** ++ /* + * Processors return a value of the core DTS reading in 10.6 format + * (10 bits signed decimal, 6 bits fractional). + * Error codes: +@@ -192,6 +233,7 @@ static int cputemp_read_string(struct device *dev, + return -EOPNOTSUPP; + + *str = cputemp_label[channel]; ++ + return 0; + } + +@@ -200,26 +242,33 @@ static int cputemp_read(struct device *dev, + u32 attr, int channel, long *val) + { + struct peci_cputemp *priv = dev_get_drvdata(dev); +- int rc, core_index; ++ int ret, core_index; + + if (channel >= CPUTEMP_CHANNEL_NUMS || + !(priv->temp_config[channel] & BIT(attr))) + return -EOPNOTSUPP; + +- rc = get_temp_targets(priv); +- if (rc) +- return rc; ++ ret = get_temp_targets(priv); ++ if (ret) ++ return ret; + + switch (attr) { + case hwmon_temp_input: + switch (channel) { + case channel_die: +- rc = get_die_temp(priv); +- if (rc) ++ ret = get_die_temp(priv); ++ if (ret) + break; + + *val = priv->temp.die.value; + break; ++ case channel_dts: ++ ret = get_dts(priv); ++ if (ret) ++ break; ++ ++ *val = priv->temp.dts.value; ++ break; + case channel_tcontrol: + *val = priv->temp.tcontrol.value; + break; +@@ -231,8 +280,8 @@ static int cputemp_read(struct device *dev, + break; + default: + core_index = channel - DEFAULT_CHANNEL_NUMS; +- rc = get_core_temp(priv, core_index); +- if (rc) ++ ret = get_core_temp(priv, core_index); ++ if (ret) + break; + + *val = priv->temp.core[core_index].value; +@@ -249,11 +298,11 @@ static int cputemp_read(struct device *dev, + *val = priv->temp.tjmax.value - priv->temp.tcontrol.value; + break; + default: +- rc = -EOPNOTSUPP; ++ ret = -EOPNOTSUPP; + break; + } + +- return rc; ++ return ret; + } + + static umode_t cputemp_is_visible(const void *data, +@@ -262,11 +311,11 @@ static umode_t cputemp_is_visible(const void *data, + { + const struct peci_cputemp *priv = data; + +- if (priv->temp_config[channel] & BIT(attr)) +- if (channel < DEFAULT_CHANNEL_NUMS || +- (channel >= DEFAULT_CHANNEL_NUMS && +- (priv->core_mask & BIT(channel - DEFAULT_CHANNEL_NUMS)))) +- return 0444; ++ if ((priv->temp_config[channel] & BIT(attr)) && ++ (channel < DEFAULT_CHANNEL_NUMS || ++ (channel >= DEFAULT_CHANNEL_NUMS && ++ (priv->core_mask & BIT(channel - DEFAULT_CHANNEL_NUMS))))) ++ return 0444; + + return 0; + } +@@ -280,40 +329,43 @@ static const struct hwmon_ops cputemp_ops = { + static int check_resolved_cores(struct peci_cputemp *priv) + { + struct peci_rd_pci_cfg_local_msg msg; +- int rc; ++ int ret; + + /* Get the RESOLVED_CORES register value */ + msg.addr = priv->mgr->client->addr; +- msg.bus = REG_RESOLVED_CORES_BUS; +- msg.device = REG_RESOLVED_CORES_DEVICE; +- msg.function = REG_RESOLVED_CORES_FUNCTION; +- msg.reg = REG_RESOLVED_CORES_OFFSET; ++ msg.bus = 1; ++ msg.device = 30; ++ msg.function = 3; ++ msg.reg = 0xb4; + msg.rx_len = 4; + +- rc = peci_command(priv->mgr->client->adapter, +- PECI_CMD_RD_PCI_CFG_LOCAL, &msg); +- if (rc) +- return rc; ++ ret = peci_command(priv->mgr->client->adapter, ++ PECI_CMD_RD_PCI_CFG_LOCAL, &msg); ++ if (msg.cc != PECI_DEV_CC_SUCCESS) ++ ret = -EAGAIN; ++ if (ret) ++ return ret; + + priv->core_mask = le32_to_cpup((__le32 *)msg.pci_config); + if (!priv->core_mask) + return -EAGAIN; + + dev_dbg(priv->dev, "Scanned resolved cores: 0x%x\n", priv->core_mask); ++ + return 0; + } + + static int create_core_temp_info(struct peci_cputemp *priv) + { +- int rc, i; ++ int ret, i; + +- rc = check_resolved_cores(priv); +- if (rc) +- return rc; ++ ret = check_resolved_cores(priv); ++ if (ret) ++ return ret; + + for (i = 0; i < priv->gen_info->core_max; i++) + if (priv->core_mask & BIT(i)) +- while (i + DEFAULT_CHANNEL_NUMS >= priv->config_idx) ++ while (priv->config_idx <= i + DEFAULT_CHANNEL_NUMS) + priv->temp_config[priv->config_idx++] = + config_table[channel_core]; + +@@ -326,7 +378,7 @@ static int peci_cputemp_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct peci_cputemp *priv; + struct device *hwmon_dev; +- int rc; ++ int ret; + + if ((mgr->client->adapter->cmd_mask & + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) != +@@ -346,12 +398,13 @@ static int peci_cputemp_probe(struct platform_device *pdev) + mgr->client->addr - PECI_BASE_ADDR); + + priv->temp_config[priv->config_idx++] = config_table[channel_die]; ++ priv->temp_config[priv->config_idx++] = config_table[channel_dts]; + priv->temp_config[priv->config_idx++] = config_table[channel_tcontrol]; + priv->temp_config[priv->config_idx++] = config_table[channel_tthrottle]; + priv->temp_config[priv->config_idx++] = config_table[channel_tjmax]; + +- rc = create_core_temp_info(priv); +- if (rc) ++ ret = create_core_temp_info(priv); ++ if (ret) + dev_dbg(dev, "Skipped creating core temp info\n"); + + priv->chip.ops = &cputemp_ops; +@@ -385,7 +438,7 @@ MODULE_DEVICE_TABLE(platform, peci_cputemp_ids); + static struct platform_driver peci_cputemp_driver = { + .probe = peci_cputemp_probe, + .id_table = peci_cputemp_ids, +- .driver = { .name = "peci-cputemp", }, ++ .driver = { .name = KBUILD_MODNAME, }, + }; + module_platform_driver(peci_cputemp_driver); + +diff --git a/drivers/hwmon/peci-dimmtemp.c b/drivers/hwmon/peci-dimmtemp.c +index 86a45a90805b..a404b6ea4ba3 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> +@@ -21,6 +21,8 @@ struct peci_dimmtemp { + struct workqueue_struct *work_queue; + struct delayed_work work_handler; + struct temp_data temp[DIMM_NUMS_MAX]; ++ long temp_max[DIMM_NUMS_MAX]; ++ long temp_crit[DIMM_NUMS_MAX]; + u32 dimm_mask; + int retry_count; + u32 temp_config[DIMM_NUMS_MAX + 1]; +@@ -44,20 +46,106 @@ static int get_dimm_temp(struct peci_dimmtemp *priv, int dimm_no) + { + int dimm_order = dimm_no % priv->gen_info->dimm_idx_max; + int chan_rank = dimm_no / priv->gen_info->dimm_idx_max; ++ struct peci_rd_pci_cfg_local_msg rp_msg; + u8 cfg_data[4]; +- int rc; ++ int ret; + + if (!peci_temp_need_update(&priv->temp[dimm_no])) + return 0; + +- rc = peci_client_read_package_config(priv->mgr, +- MBX_INDEX_DDR_DIMM_TEMP, +- chan_rank, cfg_data); +- if (rc) +- return rc; ++ ret = peci_client_read_package_config(priv->mgr, ++ PECI_MBX_INDEX_DDR_DIMM_TEMP, ++ chan_rank, cfg_data); ++ if (ret) ++ return ret; + + priv->temp[dimm_no].value = cfg_data[dimm_order] * 1000; + ++ switch (priv->gen_info->model) { ++ case INTEL_FAM6_SKYLAKE_X: ++ rp_msg.addr = priv->mgr->client->addr; ++ rp_msg.bus = 2; ++ /* ++ * Device 10, Function 2: IMC 0 channel 0 -> rank 0 ++ * Device 10, Function 6: IMC 0 channel 1 -> rank 1 ++ * Device 11, Function 2: IMC 0 channel 2 -> rank 2 ++ * Device 12, Function 2: IMC 1 channel 0 -> rank 3 ++ * Device 12, Function 6: IMC 1 channel 1 -> rank 4 ++ * Device 13, Function 2: IMC 1 channel 2 -> rank 5 ++ */ ++ rp_msg.device = 10 + chan_rank / 3 * 2 + ++ (chan_rank % 3 == 2 ? 1 : 0); ++ rp_msg.function = chan_rank % 3 == 1 ? 6 : 2; ++ rp_msg.reg = 0x120 + dimm_order * 4; ++ rp_msg.rx_len = 4; ++ ++ ret = peci_command(priv->mgr->client->adapter, ++ PECI_CMD_RD_PCI_CFG_LOCAL, &rp_msg); ++ if (rp_msg.cc != PECI_DEV_CC_SUCCESS) ++ ret = -EAGAIN; ++ if (ret) ++ return ret; ++ ++ priv->temp_max[dimm_no] = rp_msg.pci_config[1] * 1000; ++ priv->temp_crit[dimm_no] = rp_msg.pci_config[2] * 1000; ++ break; ++ case INTEL_FAM6_SKYLAKE_XD: ++ rp_msg.addr = priv->mgr->client->addr; ++ rp_msg.bus = 2; ++ /* ++ * Device 10, Function 2: IMC 0 channel 0 -> rank 0 ++ * Device 10, Function 6: IMC 0 channel 1 -> rank 1 ++ * Device 12, Function 2: IMC 1 channel 0 -> rank 2 ++ * Device 12, Function 6: IMC 1 channel 1 -> rank 3 ++ */ ++ rp_msg.device = 10 + chan_rank / 2 * 2; ++ rp_msg.function = chan_rank % 2 ? 6 : 2; ++ rp_msg.reg = 0x120 + dimm_order * 4; ++ rp_msg.rx_len = 4; ++ ++ ret = peci_command(priv->mgr->client->adapter, ++ PECI_CMD_RD_PCI_CFG_LOCAL, &rp_msg); ++ if (rp_msg.cc != PECI_DEV_CC_SUCCESS) ++ ret = -EAGAIN; ++ if (ret) ++ return ret; ++ ++ priv->temp_max[dimm_no] = rp_msg.pci_config[1] * 1000; ++ priv->temp_crit[dimm_no] = rp_msg.pci_config[2] * 1000; ++ break; ++ case INTEL_FAM6_HASWELL_X: ++ case INTEL_FAM6_BROADWELL_X: ++ rp_msg.addr = priv->mgr->client->addr; ++ rp_msg.bus = 1; ++ /* ++ * Device 20, Function 0: IMC 0 channel 0 -> rank 0 ++ * Device 20, Function 1: IMC 0 channel 1 -> rank 1 ++ * Device 21, Function 0: IMC 0 channel 2 -> rank 2 ++ * Device 21, Function 1: IMC 0 channel 3 -> rank 3 ++ * Device 23, Function 0: IMC 1 channel 0 -> rank 4 ++ * Device 23, Function 1: IMC 1 channel 1 -> rank 5 ++ * Device 24, Function 0: IMC 1 channel 2 -> rank 6 ++ * Device 24, Function 1: IMC 1 channel 3 -> rank 7 ++ */ ++ rp_msg.device = 20 + chan_rank / 2 + chan_rank / 4; ++ rp_msg.function = chan_rank % 2; ++ rp_msg.reg = 0x120 + dimm_order * 4; ++ rp_msg.rx_len = 4; ++ ++ ret = peci_command(priv->mgr->client->adapter, ++ PECI_CMD_RD_PCI_CFG_LOCAL, &rp_msg); ++ if (rp_msg.cc != PECI_DEV_CC_SUCCESS) ++ ret = -EAGAIN; ++ if (ret) ++ return ret; ++ ++ priv->temp_max[dimm_no] = rp_msg.pci_config[1] * 1000; ++ priv->temp_crit[dimm_no] = rp_msg.pci_config[2] * 1000; ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ + peci_temp_mark_updated(&priv->temp[dimm_no]); + + return 0; +@@ -77,6 +165,7 @@ static int dimmtemp_read_string(struct device *dev, + chan_rank = channel / dimm_idx_max; + dimm_idx = channel % dimm_idx_max; + *str = dimmtemp_label[chan_rank][dimm_idx]; ++ + return 0; + } + +@@ -84,17 +173,28 @@ static int dimmtemp_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { + struct peci_dimmtemp *priv = dev_get_drvdata(dev); +- int rc; +- +- if (attr != hwmon_temp_input) +- return -EOPNOTSUPP; +- +- rc = get_dimm_temp(priv, channel); +- if (rc) +- return rc; ++ int ret; ++ ++ ret = get_dimm_temp(priv, channel); ++ if (ret) ++ return ret; ++ ++ switch (attr) { ++ case hwmon_temp_input: ++ *val = priv->temp[channel].value; ++ break; ++ case hwmon_temp_max: ++ *val = priv->temp_max[channel]; ++ break; ++ case hwmon_temp_crit: ++ *val = priv->temp_crit[channel]; ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ break; ++ } + +- *val = priv->temp[channel].value; +- return 0; ++ return ret; + } + + static umode_t dimmtemp_is_visible(const void *data, +@@ -120,16 +220,16 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) + { + u32 chan_rank_max = priv->gen_info->chan_rank_max; + u32 dimm_idx_max = priv->gen_info->dimm_idx_max; +- int chan_rank, dimm_idx, rc; ++ int chan_rank, dimm_idx, ret; + u8 cfg_data[4]; + + for (chan_rank = 0; chan_rank < chan_rank_max; chan_rank++) { +- rc = peci_client_read_package_config(priv->mgr, +- MBX_INDEX_DDR_DIMM_TEMP, +- chan_rank, cfg_data); +- if (rc) { ++ ret = peci_client_read_package_config(priv->mgr, ++ PECI_MBX_INDEX_DDR_DIMM_TEMP, ++ chan_rank, cfg_data); ++ if (ret) { + priv->dimm_mask = 0; +- return rc; ++ return ret; + } + + for (dimm_idx = 0; dimm_idx < dimm_idx_max; dimm_idx++) +@@ -143,17 +243,18 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) + return -EAGAIN; + + dev_dbg(priv->dev, "Scanned populated DIMMs: 0x%x\n", priv->dimm_mask); ++ + return 0; + } + + static int create_dimm_temp_info(struct peci_dimmtemp *priv) + { +- int rc, i, config_idx, channels; ++ int ret, i, config_idx, channels; + struct device *hwmon_dev; + +- rc = check_populated_dimms(priv); +- if (rc) { +- if (rc == -EAGAIN) { ++ ret = check_populated_dimms(priv); ++ if (ret) { ++ if (ret == -EAGAIN) { + if (priv->retry_count < DIMM_MASK_CHECK_RETRY_MAX) { + queue_delayed_work(priv->work_queue, + &priv->work_handler, +@@ -164,11 +265,11 @@ static int create_dimm_temp_info(struct peci_dimmtemp *priv) + } else { + dev_err(priv->dev, + "Timeout DIMM temp info creation\n"); +- rc = -ETIMEDOUT; ++ ret = -ETIMEDOUT; + } + } + +- return rc; ++ return ret; + } + + channels = priv->gen_info->chan_rank_max * +@@ -177,7 +278,8 @@ static int create_dimm_temp_info(struct peci_dimmtemp *priv) + if (priv->dimm_mask & BIT(i)) + while (i >= config_idx) + priv->temp_config[config_idx++] = +- HWMON_T_LABEL | HWMON_T_INPUT; ++ HWMON_T_LABEL | HWMON_T_INPUT | ++ HWMON_T_MAX | HWMON_T_CRIT; + + priv->chip.ops = &dimmtemp_ops; + priv->chip.info = priv->info; +@@ -192,12 +294,12 @@ static int create_dimm_temp_info(struct peci_dimmtemp *priv) + priv, + &priv->chip, + NULL); +- rc = PTR_ERR_OR_ZERO(hwmon_dev); +- if (!rc) ++ ret = PTR_ERR_OR_ZERO(hwmon_dev); ++ if (!ret) + dev_dbg(priv->dev, "%s: sensor '%s'\n", + dev_name(hwmon_dev), priv->name); + +- return rc; ++ return ret; + } + + static void create_dimm_temp_info_delayed(struct work_struct *work) +@@ -205,10 +307,10 @@ static void create_dimm_temp_info_delayed(struct work_struct *work) + struct delayed_work *dwork = to_delayed_work(work); + struct peci_dimmtemp *priv = container_of(dwork, struct peci_dimmtemp, + work_handler); +- int rc; ++ int ret; + +- rc = create_dimm_temp_info(priv); +- if (rc && rc != -EAGAIN) ++ ret = create_dimm_temp_info(priv); ++ if (ret && ret != -EAGAIN) + dev_dbg(priv->dev, "Failed to create DIMM temp info\n"); + } + +@@ -217,7 +319,7 @@ static int peci_dimmtemp_probe(struct platform_device *pdev) + struct peci_client_manager *mgr = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct peci_dimmtemp *priv; +- int rc; ++ int ret; + + if ((mgr->client->adapter->cmd_mask & + (BIT(PECI_CMD_GET_TEMP) | BIT(PECI_CMD_RD_PKG_CFG))) != +@@ -242,8 +344,8 @@ static int peci_dimmtemp_probe(struct platform_device *pdev) + + INIT_DELAYED_WORK(&priv->work_handler, create_dimm_temp_info_delayed); + +- rc = create_dimm_temp_info(priv); +- if (rc && rc != -EAGAIN) { ++ ret = create_dimm_temp_info(priv); ++ if (ret && ret != -EAGAIN) { + dev_err(dev, "Failed to create DIMM temp info\n"); + goto err_free_wq; + } +@@ -252,7 +354,7 @@ static int peci_dimmtemp_probe(struct platform_device *pdev) + + err_free_wq: + destroy_workqueue(priv->work_queue); +- return rc; ++ return ret; + } + + static int peci_dimmtemp_remove(struct platform_device *pdev) +@@ -275,7 +377,7 @@ static struct platform_driver peci_dimmtemp_driver = { + .probe = peci_dimmtemp_probe, + .remove = peci_dimmtemp_remove, + .id_table = peci_dimmtemp_ids, +- .driver = { .name = "peci-dimmtemp", }, ++ .driver = { .name = KBUILD_MODNAME, }, + }; + module_platform_driver(peci_dimmtemp_driver); + +diff --git a/drivers/hwmon/peci-hwmon.h b/drivers/hwmon/peci-hwmon.h +index 6ca1855a86bb..ce6b470eae63 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 +@@ -29,11 +29,8 @@ struct temp_data { + */ + static inline bool peci_temp_need_update(struct temp_data *temp) + { +- if (temp->valid && +- time_before(jiffies, temp->last_updated + UPDATE_INTERVAL)) +- return false; +- +- return true; ++ return !temp->valid || ++ time_after(jiffies, temp->last_updated + UPDATE_INTERVAL); + } + + /** +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index 75dbcc7da87c..6f7ee4dd08f6 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -616,7 +616,7 @@ config MFD_INTEL_MSIC + devices used in Intel Medfield platforms. + + config MFD_INTEL_PECI_CLIENT +- bool "Intel PECI client" ++ tristate "Intel PECI client" + depends on (PECI || COMPILE_TEST) + select MFD_CORE + help +@@ -629,6 +629,9 @@ config MFD_INTEL_PECI_CLIENT + Additional drivers must be enabled in order to use the functionality + of the device. + ++ This driver can also be built as a module. If so, the module ++ will be called intel-peci-client. ++ + config MFD_IPAQ_MICRO + bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support" + depends on SA1100_H3100 || SA1100_H3600 +diff --git a/drivers/mfd/intel-peci-client.c b/drivers/mfd/intel-peci-client.c +index d53e4f1078ac..18bf0af0e09e 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,38 +25,45 @@ 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, + .chan_rank_max = CHAN_RANK_MAX_ON_SKX, + .dimm_idx_max = DIMM_IDX_MAX_ON_SKX }, ++ { /* Skylake Xeon D */ ++ .family = 6, /* Family code */ ++ .model = INTEL_FAM6_SKYLAKE_XD, ++ .core_max = CORE_MAX_ON_SKXD, ++ .chan_rank_max = CHAN_RANK_MAX_ON_SKXD, ++ .dimm_idx_max = DIMM_IDX_MAX_ON_SKXD }, + }; + + 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 +84,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 +104,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" }, +@@ -139,7 +138,7 @@ static struct peci_driver peci_client_driver = { + .probe = peci_client_probe, + .id_table = peci_client_ids, + .driver = { +- .name = "peci-client", ++ .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(peci_client_of_table), + }, + }; +diff --git a/drivers/peci/Kconfig b/drivers/peci/Kconfig +index 7293108fb543..9752feee2454 100644 +--- a/drivers/peci/Kconfig ++++ b/drivers/peci/Kconfig +@@ -2,10 +2,12 @@ + # Platform Environment Control Interface (PECI) subsystem configuration + # + ++menu "PECI support" ++ + config PECI +- bool "PECI support" +- select RT_MUTEXES ++ tristate "PECI support" + select CRC8 ++ default n + help + The Platform Environment Control Interface (PECI) is a one-wire bus + interface that provides a communication channel from Intel processors +@@ -14,37 +16,23 @@ config PECI + If you want PECI support, you should say Y here and also to the + specific driver for your bus adapter(s) below. + +-if PECI +- +-# +-# PECI hardware bus configuration +-# +- +-menu "PECI Hardware Bus support" +- +-config PECI_ASPEED +- tristate "ASPEED PECI support" +- select REGMAP_MMIO +- depends on OF +- depends on ARCH_ASPEED || COMPILE_TEST +- help +- Say Y here if you want support for the Platform Environment Control +- Interface (PECI) bus adapter driver on the ASPEED SoCs. ++ This support is also available as a module. If so, the module ++ will be called peci-core. + +- This support is also available as a module. If so, the module +- will be called peci-aspeed. ++if PECI + +-config PECI_NPCM +- tristate "Nuvoton NPCM PECI support" +- select REGMAP_MMIO +- depends on OF +- depends on ARCH_NPCM || COMPILE_TEST ++config PECI_CHARDEV ++ tristate "PECI device interface" + help +- Say Y here if you want support for the Platform Environment Control +- Interface (PECI) bus adapter driver on the Nuvoton NPCM SoCs. ++ Say Y here to use peci-* device files, usually found in the /dev ++ directory on your system. They make it possible to have user-space ++ programs use the PECI bus. + + This support is also available as a module. If so, the module +- will be called peci-npcm. +-endmenu ++ will be called peci-dev. ++ ++source "drivers/peci/busses/Kconfig" + + endif # PECI ++ ++endmenu +diff --git a/drivers/peci/Makefile b/drivers/peci/Makefile +index 3326da54a21a..da8b0a33fa42 100644 +--- a/drivers/peci/Makefile ++++ b/drivers/peci/Makefile +@@ -1,10 +1,11 @@ ++# SPDX-License-Identifier: GPL-2.0 + # +-# Makefile for the PECI core and bus drivers. ++# Makefile for the PECI core drivers. + # + + # Core functionality + obj-$(CONFIG_PECI) += peci-core.o ++obj-$(CONFIG_PECI_CHARDEV) += peci-dev.o + + # Hardware specific bus drivers +-obj-$(CONFIG_PECI_ASPEED) += peci-aspeed.o +-obj-$(CONFIG_PECI_NPCM) += peci-npcm.o ++obj-y += busses/ +diff --git a/drivers/peci/busses/Kconfig b/drivers/peci/busses/Kconfig +new file mode 100644 +index 000000000000..bfacafb7a7ba +--- /dev/null ++++ b/drivers/peci/busses/Kconfig +@@ -0,0 +1,32 @@ ++# ++# PECI hardware bus configuration ++# ++ ++menu "PECI Hardware Bus support" ++ ++config PECI_ASPEED ++ tristate "ASPEED PECI support" ++ depends on ARCH_ASPEED || COMPILE_TEST ++ depends on OF ++ depends on PECI ++ help ++ Say Y here if you want support for the Platform Environment Control ++ Interface (PECI) bus adapter driver on the ASPEED SoCs. ++ ++ This support is also available as a module. If so, the module ++ will be called peci-aspeed. ++ ++config PECI_NPCM ++ tristate "Nuvoton NPCM PECI support" ++ select REGMAP_MMIO ++ depends on OF ++ depends on ARCH_NPCM || COMPILE_TEST ++ depends on PECI ++ help ++ Say Y here if you want support for the Platform Environment Control ++ Interface (PECI) bus adapter driver on the Nuvoton NPCM SoCs. ++ ++ This support is also available as a module. If so, the module ++ will be called peci-npcm. ++ ++endmenu +diff --git a/drivers/peci/busses/Makefile b/drivers/peci/busses/Makefile +new file mode 100644 +index 000000000000..aa8ce3ae5947 +--- /dev/null ++++ b/drivers/peci/busses/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# ++# Makefile for the PECI hardware bus drivers. ++# ++ ++obj-$(CONFIG_PECI_ASPEED) += peci-aspeed.o ++obj-$(CONFIG_PECI_NPCM) += peci-npcm.o +diff --git a/drivers/peci/busses/peci-aspeed.c b/drivers/peci/busses/peci-aspeed.c +new file mode 100644 +index 000000000000..851b71e32eac +--- /dev/null ++++ b/drivers/peci/busses/peci-aspeed.c +@@ -0,0 +1,492 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2012-2017 ASPEED Technology Inc. ++// Copyright (c) 2018-2019 Intel Corporation ++ ++#include <linux/bitfield.h> ++#include <linux/clk.h> ++#include <linux/delay.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/jiffies.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/peci.h> ++#include <linux/platform_device.h> ++#include <linux/reset.h> ++ ++/* ASPEED PECI Registers */ ++/* Control Register */ ++#define ASPEED_PECI_CTRL 0x00 ++#define ASPEED_PECI_CTRL_SAMPLING_MASK GENMASK(19, 16) ++#define ASPEED_PECI_CTRL_READ_MODE_MASK GENMASK(13, 12) ++#define ASPEED_PECI_CTRL_READ_MODE_COUNT BIT(12) ++#define ASPEED_PECI_CTRL_READ_MODE_DBG BIT(13) ++#define ASPEED_PECI_CTRL_CLK_SOURCE_MASK BIT(11) ++#define ASPEED_PECI_CTRL_CLK_DIV_MASK GENMASK(10, 8) ++#define ASPEED_PECI_CTRL_INVERT_OUT BIT(7) ++#define ASPEED_PECI_CTRL_INVERT_IN BIT(6) ++#define ASPEED_PECI_CTRL_BUS_CONTENT_EN BIT(5) ++#define ASPEED_PECI_CTRL_PECI_EN BIT(4) ++#define ASPEED_PECI_CTRL_PECI_CLK_EN BIT(0) ++ ++/* Timing Negotiation Register */ ++#define ASPEED_PECI_TIMING_NEGOTIATION 0x04 ++#define ASPEED_PECI_TIMING_MESSAGE_MASK GENMASK(15, 8) ++#define ASPEED_PECI_TIMING_ADDRESS_MASK GENMASK(7, 0) ++ ++/* Command Register */ ++#define ASPEED_PECI_CMD 0x08 ++#define ASPEED_PECI_CMD_PIN_MON BIT(31) ++#define ASPEED_PECI_CMD_STS_MASK GENMASK(27, 24) ++#define ASPEED_PECI_CMD_IDLE_MASK (ASPEED_PECI_CMD_STS_MASK | \ ++ ASPEED_PECI_CMD_PIN_MON) ++#define ASPEED_PECI_CMD_FIRE BIT(0) ++ ++/* Read/Write Length Register */ ++#define ASPEED_PECI_RW_LENGTH 0x0c ++#define ASPEED_PECI_AW_FCS_EN BIT(31) ++#define ASPEED_PECI_READ_LEN_MASK GENMASK(23, 16) ++#define ASPEED_PECI_WRITE_LEN_MASK GENMASK(15, 8) ++#define ASPEED_PECI_TAGET_ADDR_MASK GENMASK(7, 0) ++ ++/* Expected FCS Data Register */ ++#define ASPEED_PECI_EXP_FCS 0x10 ++#define ASPEED_PECI_EXP_READ_FCS_MASK GENMASK(23, 16) ++#define ASPEED_PECI_EXP_AW_FCS_AUTO_MASK GENMASK(15, 8) ++#define ASPEED_PECI_EXP_WRITE_FCS_MASK GENMASK(7, 0) ++ ++/* Captured FCS Data Register */ ++#define ASPEED_PECI_CAP_FCS 0x14 ++#define ASPEED_PECI_CAP_READ_FCS_MASK GENMASK(23, 16) ++#define ASPEED_PECI_CAP_WRITE_FCS_MASK GENMASK(7, 0) ++ ++/* Interrupt Register */ ++#define ASPEED_PECI_INT_CTRL 0x18 ++#define ASPEED_PECI_TIMING_NEGO_SEL_MASK GENMASK(31, 30) ++#define ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO 0 ++#define ASPEED_PECI_2ND_BIT_OF_ADDR_NEGO 1 ++#define ASPEED_PECI_MESSAGE_NEGO 2 ++#define ASPEED_PECI_INT_MASK GENMASK(4, 0) ++#define ASPEED_PECI_INT_BUS_TIMEOUT BIT(4) ++#define ASPEED_PECI_INT_BUS_CONNECT BIT(3) ++#define ASPEED_PECI_INT_W_FCS_BAD BIT(2) ++#define ASPEED_PECI_INT_W_FCS_ABORT BIT(1) ++#define ASPEED_PECI_INT_CMD_DONE BIT(0) ++ ++/* Interrupt Status Register */ ++#define ASPEED_PECI_INT_STS 0x1c ++#define ASPEED_PECI_INT_TIMING_RESULT_MASK GENMASK(29, 16) ++ /* bits[4..0]: Same bit fields in the 'Interrupt Register' */ ++ ++/* Rx/Tx Data Buffer Registers */ ++#define ASPEED_PECI_W_DATA0 0x20 ++#define ASPEED_PECI_W_DATA1 0x24 ++#define ASPEED_PECI_W_DATA2 0x28 ++#define ASPEED_PECI_W_DATA3 0x2c ++#define ASPEED_PECI_R_DATA0 0x30 ++#define ASPEED_PECI_R_DATA1 0x34 ++#define ASPEED_PECI_R_DATA2 0x38 ++#define ASPEED_PECI_R_DATA3 0x3c ++#define ASPEED_PECI_W_DATA4 0x40 ++#define ASPEED_PECI_W_DATA5 0x44 ++#define ASPEED_PECI_W_DATA6 0x48 ++#define ASPEED_PECI_W_DATA7 0x4c ++#define ASPEED_PECI_R_DATA4 0x50 ++#define ASPEED_PECI_R_DATA5 0x54 ++#define ASPEED_PECI_R_DATA6 0x58 ++#define ASPEED_PECI_R_DATA7 0x5c ++#define ASPEED_PECI_DATA_BUF_SIZE_MAX 32 ++ ++/* Timing Negotiation */ ++#define ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT 8 ++#define ASPEED_PECI_RD_SAMPLING_POINT_MAX 15 ++#define ASPEED_PECI_CLK_DIV_DEFAULT 0 ++#define ASPEED_PECI_CLK_DIV_MAX 7 ++#define ASPEED_PECI_MSG_TIMING_DEFAULT 1 ++#define ASPEED_PECI_MSG_TIMING_MAX 255 ++#define ASPEED_PECI_ADDR_TIMING_DEFAULT 1 ++#define ASPEED_PECI_ADDR_TIMING_MAX 255 ++ ++/* Timeout */ ++#define ASPEED_PECI_IDLE_CHECK_TIMEOUT_USEC 50000 ++#define ASPEED_PECI_IDLE_CHECK_INTERVAL_USEC 10000 ++#define ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 ++#define ASPEED_PECI_CMD_TIMEOUT_MS_MAX 60000 ++ ++struct aspeed_peci { ++ struct peci_adapter *adapter; ++ struct device *dev; ++ void __iomem *base; ++ struct clk *clk; ++ struct reset_control *rst; ++ int irq; ++ spinlock_t lock; /* to sync completion status handling */ ++ struct completion xfer_complete; ++ u32 status; ++ u32 cmd_timeout_ms; ++}; ++ ++static int aspeed_peci_check_idle(struct aspeed_peci *priv) ++{ ++ ulong timeout = jiffies + usecs_to_jiffies(ASPEED_PECI_IDLE_CHECK_TIMEOUT_USEC); ++ u32 cmd_sts; ++ ++ for (;;) { ++ cmd_sts = readl(priv->base + ASPEED_PECI_CMD); ++ if (!(cmd_sts & ASPEED_PECI_CMD_IDLE_MASK)) ++ break; ++ if (time_after(jiffies, timeout)) { ++ cmd_sts = readl(priv->base + ASPEED_PECI_CMD); ++ break; ++ } ++ usleep_range((ASPEED_PECI_IDLE_CHECK_INTERVAL_USEC >> 2) + 1, ++ ASPEED_PECI_IDLE_CHECK_INTERVAL_USEC); ++ } ++ ++ return !(cmd_sts & ASPEED_PECI_CMD_IDLE_MASK) ? 0 : -ETIMEDOUT; ++} ++ ++static int aspeed_peci_xfer(struct peci_adapter *adapter, ++ struct peci_xfer_msg *msg) ++{ ++ struct aspeed_peci *priv = peci_get_adapdata(adapter); ++ long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); ++ u32 peci_head, peci_state, rx_data = 0; ++ ulong flags; ++ int i, ret; ++ uint reg; ++ ++ if (msg->tx_len > ASPEED_PECI_DATA_BUF_SIZE_MAX || ++ msg->rx_len > ASPEED_PECI_DATA_BUF_SIZE_MAX) ++ return -EINVAL; ++ ++ /* Check command sts and bus idle state */ ++ ret = aspeed_peci_check_idle(priv); ++ if (ret) ++ return ret; /* -ETIMEDOUT */ ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ reinit_completion(&priv->xfer_complete); ++ ++ peci_head = FIELD_PREP(ASPEED_PECI_TAGET_ADDR_MASK, msg->addr) | ++ FIELD_PREP(ASPEED_PECI_WRITE_LEN_MASK, msg->tx_len) | ++ FIELD_PREP(ASPEED_PECI_READ_LEN_MASK, msg->rx_len); ++ ++ writel(peci_head, priv->base + ASPEED_PECI_RW_LENGTH); ++ ++ for (i = 0; i < msg->tx_len; i += 4) { ++ reg = i < 16 ? ASPEED_PECI_W_DATA0 + i % 16 : ++ ASPEED_PECI_W_DATA4 + i % 16; ++ writel(le32_to_cpup((__le32 *)&msg->tx_buf[i]), ++ priv->base + reg); ++ } ++ ++ dev_dbg(priv->dev, "HEAD : 0x%08x\n", peci_head); ++ print_hex_dump_debug("TX : ", DUMP_PREFIX_NONE, 16, 1, ++ msg->tx_buf, msg->tx_len, true); ++ ++ priv->status = 0; ++ writel(ASPEED_PECI_CMD_FIRE, priv->base + ASPEED_PECI_CMD); ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, ++ timeout); ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ dev_dbg(priv->dev, "INT_STS : 0x%08x\n", priv->status); ++ peci_state = readl(priv->base + ASPEED_PECI_CMD); ++ dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", ++ FIELD_GET(ASPEED_PECI_CMD_STS_MASK, peci_state)); ++ ++ writel(0, priv->base + ASPEED_PECI_CMD); ++ ++ if (err <= 0 || priv->status != ASPEED_PECI_INT_CMD_DONE) { ++ if (err < 0) { /* -ERESTARTSYS */ ++ ret = (int)err; ++ goto err_irqrestore; ++ } else if (err == 0) { ++ dev_dbg(priv->dev, "Timeout waiting for a response!\n"); ++ ret = -ETIMEDOUT; ++ goto err_irqrestore; ++ } ++ ++ dev_dbg(priv->dev, "No valid response!\n"); ++ ret = -EIO; ++ goto err_irqrestore; ++ } ++ ++ /* ++ * Note that rx_len and rx_buf size can be an odd number. ++ * Byte handling is more efficient. ++ */ ++ for (i = 0; i < msg->rx_len; i++) { ++ u8 byte_offset = i % 4; ++ ++ if (byte_offset == 0) { ++ reg = i < 16 ? ASPEED_PECI_R_DATA0 + i % 16 : ++ ASPEED_PECI_R_DATA4 + i % 16; ++ rx_data = readl(priv->base + reg); ++ } ++ ++ msg->rx_buf[i] = (u8)(rx_data >> (byte_offset << 3)); ++ } ++ ++ print_hex_dump_debug("RX : ", DUMP_PREFIX_NONE, 16, 1, ++ msg->rx_buf, msg->rx_len, true); ++ ++ peci_state = readl(priv->base + ASPEED_PECI_CMD); ++ dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", ++ FIELD_GET(ASPEED_PECI_CMD_STS_MASK, peci_state)); ++ dev_dbg(priv->dev, "------------------------\n"); ++ ++err_irqrestore: ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return ret; ++} ++ ++static irqreturn_t aspeed_peci_irq_handler(int irq, void *arg) ++{ ++ struct aspeed_peci *priv = arg; ++ u32 status; ++ ++ spin_lock(&priv->lock); ++ status = readl(priv->base + ASPEED_PECI_INT_STS); ++ writel(status, priv->base + ASPEED_PECI_INT_STS); ++ priv->status |= (status & ASPEED_PECI_INT_MASK); ++ ++ /* ++ * In most cases, interrupt bits will be set one by one but also note ++ * that multiple interrupt bits could be set at the same time. ++ */ ++ if (status & ASPEED_PECI_INT_BUS_TIMEOUT) { ++ dev_dbg(priv->dev, "ASPEED_PECI_INT_BUS_TIMEOUT\n"); ++ } ++ ++ if (status & ASPEED_PECI_INT_BUS_CONNECT) { ++ dev_dbg(priv->dev, "ASPEED_PECI_INT_BUS_CONNECT\n"); ++ } ++ ++ if (status & ASPEED_PECI_INT_W_FCS_BAD) { ++ dev_dbg(priv->dev, "ASPEED_PECI_INT_W_FCS_BAD\n"); ++ } ++ ++ if (status & ASPEED_PECI_INT_W_FCS_ABORT) { ++ dev_dbg(priv->dev, "ASPEED_PECI_INT_W_FCS_ABORT\n"); ++ } ++ ++ /* ++ * All commands should be ended up with a ASPEED_PECI_INT_CMD_DONE bit ++ * set even in an error case. ++ */ ++ if (status & ASPEED_PECI_INT_CMD_DONE) { ++ dev_dbg(priv->dev, "ASPEED_PECI_INT_CMD_DONE\n"); ++ complete(&priv->xfer_complete); ++ } ++ ++ spin_unlock(&priv->lock); ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_peci_init_ctrl(struct aspeed_peci *priv) ++{ ++ u32 msg_timing, addr_timing, rd_sampling_point; ++ u32 clk_freq, clk_divisor, clk_div_val = 0; ++ int ret; ++ ++ priv->clk = devm_clk_get(priv->dev, NULL); ++ if (IS_ERR(priv->clk)) { ++ dev_err(priv->dev, "Failed to get clk source.\n"); ++ return PTR_ERR(priv->clk); ++ } ++ ++ ret = clk_prepare_enable(priv->clk); ++ if (ret) { ++ dev_err(priv->dev, "Failed to enable clock.\n"); ++ return ret; ++ } ++ ++ ret = device_property_read_u32(priv->dev, "clock-frequency", &clk_freq); ++ if (ret) { ++ dev_err(priv->dev, ++ "Could not read clock-frequency property.\n"); ++ clk_disable_unprepare(priv->clk); ++ return ret; ++ } ++ ++ clk_divisor = clk_get_rate(priv->clk) / clk_freq; ++ ++ while ((clk_divisor >> 1) && (clk_div_val < ASPEED_PECI_CLK_DIV_MAX)) ++ clk_div_val++; ++ ++ ret = device_property_read_u32(priv->dev, "msg-timing", &msg_timing); ++ if (ret || msg_timing > ASPEED_PECI_MSG_TIMING_MAX) { ++ if (!ret) ++ dev_warn(priv->dev, ++ "Invalid msg-timing : %u, Use default : %u\n", ++ msg_timing, ASPEED_PECI_MSG_TIMING_DEFAULT); ++ msg_timing = ASPEED_PECI_MSG_TIMING_DEFAULT; ++ } ++ ++ ret = device_property_read_u32(priv->dev, "addr-timing", &addr_timing); ++ if (ret || addr_timing > ASPEED_PECI_ADDR_TIMING_MAX) { ++ if (!ret) ++ dev_warn(priv->dev, ++ "Invalid addr-timing : %u, Use default : %u\n", ++ addr_timing, ASPEED_PECI_ADDR_TIMING_DEFAULT); ++ addr_timing = ASPEED_PECI_ADDR_TIMING_DEFAULT; ++ } ++ ++ ret = device_property_read_u32(priv->dev, "rd-sampling-point", ++ &rd_sampling_point); ++ if (ret || rd_sampling_point > ASPEED_PECI_RD_SAMPLING_POINT_MAX) { ++ if (!ret) ++ dev_warn(priv->dev, ++ "Invalid rd-sampling-point : %u. Use default : %u\n", ++ rd_sampling_point, ++ ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT); ++ rd_sampling_point = ASPEED_PECI_RD_SAMPLING_POINT_DEFAULT; ++ } ++ ++ ret = device_property_read_u32(priv->dev, "cmd-timeout-ms", ++ &priv->cmd_timeout_ms); ++ if (ret || priv->cmd_timeout_ms > ASPEED_PECI_CMD_TIMEOUT_MS_MAX || ++ priv->cmd_timeout_ms == 0) { ++ if (!ret) ++ dev_warn(priv->dev, ++ "Invalid cmd-timeout-ms : %u. Use default : %u\n", ++ priv->cmd_timeout_ms, ++ ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT); ++ priv->cmd_timeout_ms = ASPEED_PECI_CMD_TIMEOUT_MS_DEFAULT; ++ } ++ ++ writel(FIELD_PREP(ASPEED_PECI_CTRL_CLK_DIV_MASK, ++ ASPEED_PECI_CLK_DIV_DEFAULT) | ++ ASPEED_PECI_CTRL_PECI_CLK_EN, priv->base + ASPEED_PECI_CTRL); ++ ++ /* ++ * Timing negotiation period setting. ++ * The unit of the programmed value is 4 times of PECI clock period. ++ */ ++ writel(FIELD_PREP(ASPEED_PECI_TIMING_MESSAGE_MASK, msg_timing) | ++ FIELD_PREP(ASPEED_PECI_TIMING_ADDRESS_MASK, addr_timing), ++ priv->base + ASPEED_PECI_TIMING_NEGOTIATION); ++ ++ /* Clear interrupts */ ++ writel(readl(priv->base + ASPEED_PECI_INT_STS) | ASPEED_PECI_INT_MASK, ++ priv->base + ASPEED_PECI_INT_STS); ++ ++ /* Set timing negotiation mode and enable interrupts */ ++ writel(FIELD_PREP(ASPEED_PECI_TIMING_NEGO_SEL_MASK, ++ ASPEED_PECI_1ST_BIT_OF_ADDR_NEGO) | ++ ASPEED_PECI_INT_MASK, priv->base + ASPEED_PECI_INT_CTRL); ++ ++ /* Read sampling point and clock speed setting */ ++ writel(FIELD_PREP(ASPEED_PECI_CTRL_SAMPLING_MASK, rd_sampling_point) | ++ FIELD_PREP(ASPEED_PECI_CTRL_CLK_DIV_MASK, clk_div_val) | ++ ASPEED_PECI_CTRL_PECI_EN | ASPEED_PECI_CTRL_PECI_CLK_EN, ++ priv->base + ASPEED_PECI_CTRL); ++ ++ return 0; ++} ++ ++static int aspeed_peci_probe(struct platform_device *pdev) ++{ ++ struct peci_adapter *adapter; ++ struct aspeed_peci *priv; ++ int ret; ++ ++ adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); ++ if (!adapter) ++ return -ENOMEM; ++ ++ priv = peci_get_adapdata(adapter); ++ priv->adapter = adapter; ++ priv->dev = &pdev->dev; ++ dev_set_drvdata(&pdev->dev, priv); ++ ++ priv->base = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(priv->base)) { ++ ret = PTR_ERR(priv->base); ++ goto err_put_adapter_dev; ++ } ++ ++ priv->irq = platform_get_irq(pdev, 0); ++ if (!priv->irq) { ++ ret = -ENODEV; ++ goto err_put_adapter_dev; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, priv->irq, aspeed_peci_irq_handler, ++ 0, "peci-aspeed-irq", priv); ++ if (ret) ++ goto err_put_adapter_dev; ++ ++ init_completion(&priv->xfer_complete); ++ spin_lock_init(&priv->lock); ++ ++ priv->adapter->owner = THIS_MODULE; ++ priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); ++ strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); ++ priv->adapter->xfer = aspeed_peci_xfer; ++ priv->adapter->use_dma = false; ++ ++ priv->rst = devm_reset_control_get(&pdev->dev, NULL); ++ if (IS_ERR(priv->rst)) { ++ dev_err(&pdev->dev, ++ "missing or invalid reset controller entry\n"); ++ ret = PTR_ERR(priv->rst); ++ goto err_put_adapter_dev; ++ } ++ reset_control_deassert(priv->rst); ++ ++ ret = aspeed_peci_init_ctrl(priv); ++ if (ret) ++ goto err_put_adapter_dev; ++ ++ ret = peci_add_adapter(priv->adapter); ++ if (ret) ++ goto err_put_adapter_dev; ++ ++ dev_info(&pdev->dev, "peci bus %d registered, irq %d\n", ++ priv->adapter->nr, priv->irq); ++ ++ return 0; ++ ++err_put_adapter_dev: ++ put_device(&adapter->dev); ++ return ret; ++} ++ ++static int aspeed_peci_remove(struct platform_device *pdev) ++{ ++ struct aspeed_peci *priv = dev_get_drvdata(&pdev->dev); ++ ++ clk_disable_unprepare(priv->clk); ++ reset_control_assert(priv->rst); ++ peci_del_adapter(priv->adapter); ++ of_node_put(priv->adapter->dev.of_node); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_peci_of_table[] = { ++ { .compatible = "aspeed,ast2400-peci", }, ++ { .compatible = "aspeed,ast2500-peci", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, aspeed_peci_of_table); ++ ++static struct platform_driver aspeed_peci_driver = { ++ .probe = aspeed_peci_probe, ++ .remove = aspeed_peci_remove, ++ .driver = { ++ .name = KBUILD_MODNAME, ++ .of_match_table = of_match_ptr(aspeed_peci_of_table), ++ }, ++}; ++module_platform_driver(aspeed_peci_driver); ++ ++MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>"); ++MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); ++MODULE_DESCRIPTION("ASPEED PECI driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/peci/busses/peci-npcm.c b/drivers/peci/busses/peci-npcm.c +new file mode 100644 +index 000000000000..f632365b1416 +--- /dev/null ++++ b/drivers/peci/busses/peci-npcm.c +@@ -0,0 +1,410 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2019 Nuvoton Technology corporation. ++ ++#include <linux/bitfield.h> ++#include <linux/clk.h> ++#include <linux/interrupt.h> ++#include <linux/jiffies.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/peci.h> ++#include <linux/platform_device.h> ++#include <linux/regmap.h> ++#include <linux/mfd/syscon.h> ++#include <linux/reset.h> ++ ++/* NPCM7xx GCR module */ ++#define NPCM7XX_INTCR3_OFFSET 0x9C ++#define NPCM7XX_INTCR3_PECIVSEL BIT(19) ++ ++/* NPCM PECI Registers */ ++#define NPCM_PECI_CTL_STS 0x00 ++#define NPCM_PECI_RD_LENGTH 0x04 ++#define NPCM_PECI_ADDR 0x08 ++#define NPCM_PECI_CMD 0x0C ++#define NPCM_PECI_CTL2 0x10 ++#define NPCM_PECI_WR_LENGTH 0x1C ++#define NPCM_PECI_PDDR 0x2C ++#define NPCM_PECI_DAT_INOUT(n) (0x100 + ((n) * 4)) ++ ++#define NPCM_PECI_MAX_REG 0x200 ++ ++/* NPCM_PECI_CTL_STS - 0x00 : Control Register */ ++#define NPCM_PECI_CTRL_DONE_INT_EN BIT(6) ++#define NPCM_PECI_CTRL_ABRT_ERR BIT(4) ++#define NPCM_PECI_CTRL_CRC_ERR BIT(3) ++#define NPCM_PECI_CTRL_DONE BIT(1) ++#define NPCM_PECI_CTRL_START_BUSY BIT(0) ++ ++/* NPCM_PECI_RD_LENGTH - 0x04 : Command Register */ ++#define NPCM_PECI_RD_LEN_MASK GENMASK(6, 0) ++ ++/* NPCM_PECI_CMD - 0x10 : Command Register */ ++#define NPCM_PECI_CTL2_MASK GENMASK(7, 6) ++ ++/* NPCM_PECI_WR_LENGTH - 0x1C : Command Register */ ++#define NPCM_PECI_WR_LEN_MASK GENMASK(6, 0) ++ ++/* NPCM_PECI_PDDR - 0x2C : Command Register */ ++#define NPCM_PECI_PDDR_MASK GENMASK(4, 0) ++ ++#define NPCM_PECI_INT_MASK (NPCM_PECI_CTRL_ABRT_ERR | \ ++ NPCM_PECI_CTRL_CRC_ERR | \ ++ NPCM_PECI_CTRL_DONE) ++ ++#define NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC 50000 ++#define NPCM_PECI_IDLE_CHECK_INTERVAL_USEC 10000 ++#define NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 ++#define NPCM_PECI_CMD_TIMEOUT_MS_MAX 60000 ++#define NPCM_PECI_HOST_NEG_BIT_RATE_MAX 31 ++#define NPCM_PECI_HOST_NEG_BIT_RATE_MIN 7 ++#define NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT 15 ++#define NPCM_PECI_PULL_DOWN_DEFAULT 0 ++#define NPCM_PECI_PULL_DOWN_MAX 2 ++ ++struct npcm_peci { ++ u32 cmd_timeout_ms; ++ u32 host_bit_rate; ++ struct completion xfer_complete; ++ struct regmap *gcr_regmap; ++ struct peci_adapter *adapter; ++ struct regmap *regmap; ++ u32 status; ++ spinlock_t lock; /* to sync completion status handling */ ++ struct device *dev; ++ struct clk *clk; ++ int irq; ++}; ++ ++static int npcm_peci_xfer_native(struct npcm_peci *priv, ++ struct peci_xfer_msg *msg) ++{ ++ long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); ++ unsigned long flags; ++ unsigned int msg_rd; ++ u32 cmd_sts; ++ int i, rc; ++ ++ /* Check command sts and bus idle state */ ++ rc = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, ++ !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), ++ NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, ++ NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); ++ if (rc) ++ return rc; /* -ETIMEDOUT */ ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ reinit_completion(&priv->xfer_complete); ++ ++ regmap_write(priv->regmap, NPCM_PECI_ADDR, msg->addr); ++ regmap_write(priv->regmap, NPCM_PECI_RD_LENGTH, ++ NPCM_PECI_WR_LEN_MASK & msg->rx_len); ++ regmap_write(priv->regmap, NPCM_PECI_WR_LENGTH, ++ NPCM_PECI_WR_LEN_MASK & msg->tx_len); ++ ++ if (msg->tx_len) { ++ regmap_write(priv->regmap, NPCM_PECI_CMD, msg->tx_buf[0]); ++ ++ for (i = 0; i < (msg->tx_len - 1); i++) ++ regmap_write(priv->regmap, NPCM_PECI_DAT_INOUT(i), ++ msg->tx_buf[i + 1]); ++ } ++ ++ priv->status = 0; ++ regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, ++ NPCM_PECI_CTRL_START_BUSY, ++ NPCM_PECI_CTRL_START_BUSY); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, ++ timeout); ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ regmap_write(priv->regmap, NPCM_PECI_CMD, 0); ++ ++ if (err <= 0 || priv->status != NPCM_PECI_CTRL_DONE) { ++ if (err < 0) { /* -ERESTARTSYS */ ++ rc = (int)err; ++ goto err_irqrestore; ++ } else if (err == 0) { ++ dev_dbg(priv->dev, "Timeout waiting for a response!\n"); ++ rc = -ETIMEDOUT; ++ goto err_irqrestore; ++ } ++ ++ dev_dbg(priv->dev, "No valid response!\n"); ++ rc = -EIO; ++ goto err_irqrestore; ++ } ++ ++ for (i = 0; i < msg->rx_len; i++) { ++ regmap_read(priv->regmap, NPCM_PECI_DAT_INOUT(i), &msg_rd); ++ msg->rx_buf[i] = (u8)msg_rd; ++ } ++ ++err_irqrestore: ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return rc; ++} ++ ++static irqreturn_t npcm_peci_irq_handler(int irq, void *arg) ++{ ++ struct npcm_peci *priv = arg; ++ u32 status_ack = 0; ++ u32 status; ++ ++ spin_lock(&priv->lock); ++ regmap_read(priv->regmap, NPCM_PECI_CTL_STS, &status); ++ priv->status |= (status & NPCM_PECI_INT_MASK); ++ ++ if (status & NPCM_PECI_CTRL_CRC_ERR) { ++ dev_dbg(priv->dev, "PECI_INT_W_FCS_BAD\n"); ++ status_ack |= NPCM_PECI_CTRL_CRC_ERR; ++ } ++ ++ if (status & NPCM_PECI_CTRL_ABRT_ERR) { ++ dev_dbg(priv->dev, "NPCM_PECI_CTRL_ABRT_ERR\n"); ++ status_ack |= NPCM_PECI_CTRL_ABRT_ERR; ++ } ++ ++ /* ++ * All commands should be ended up with a NPCM_PECI_CTRL_DONE ++ * bit set even in an error case. ++ */ ++ if (status & NPCM_PECI_CTRL_DONE) { ++ dev_dbg(priv->dev, "NPCM_PECI_CTRL_DONE\n"); ++ status_ack |= NPCM_PECI_CTRL_DONE; ++ complete(&priv->xfer_complete); ++ } ++ ++ regmap_write_bits(priv->regmap, NPCM_PECI_CTL_STS, ++ NPCM_PECI_INT_MASK, status_ack); ++ ++ spin_unlock(&priv->lock); ++ return IRQ_HANDLED; ++} ++ ++static int npcm_peci_init_ctrl(struct npcm_peci *priv) ++{ ++ u32 cmd_sts, host_neg_bit_rate = 0, pull_down = 0; ++ int ret; ++ bool volt; ++ ++ priv->clk = devm_clk_get(priv->dev, NULL); ++ if (IS_ERR(priv->clk)) { ++ dev_err(priv->dev, "Failed to get clk source.\n"); ++ return PTR_ERR(priv->clk); ++ } ++ ++ ret = clk_prepare_enable(priv->clk); ++ if (ret) { ++ dev_err(priv->dev, "Failed to enable clock.\n"); ++ return ret; ++ } ++ ++ ret = of_property_read_u32(priv->dev->of_node, "cmd-timeout-ms", ++ &priv->cmd_timeout_ms); ++ if (ret || priv->cmd_timeout_ms > NPCM_PECI_CMD_TIMEOUT_MS_MAX || ++ priv->cmd_timeout_ms == 0) { ++ if (ret) ++ dev_warn(priv->dev, ++ "cmd-timeout-ms not found, use default : %u\n", ++ NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); ++ else ++ dev_warn(priv->dev, ++ "Invalid cmd-timeout-ms : %u. Use default : %u\n", ++ priv->cmd_timeout_ms, ++ NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); ++ ++ priv->cmd_timeout_ms = NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT; ++ } ++ ++ if (of_device_is_compatible(priv->dev->of_node, ++ "nuvoton,npcm750-peci")) { ++ priv->gcr_regmap = syscon_regmap_lookup_by_compatible ++ ("nuvoton,npcm750-gcr"); ++ if (!IS_ERR(priv->gcr_regmap)) { ++ volt = of_property_read_bool(priv->dev->of_node, ++ "high-volt-range"); ++ if (volt) ++ regmap_update_bits(priv->gcr_regmap, ++ NPCM7XX_INTCR3_OFFSET, ++ NPCM7XX_INTCR3_PECIVSEL, ++ NPCM7XX_INTCR3_PECIVSEL); ++ else ++ regmap_update_bits(priv->gcr_regmap, ++ NPCM7XX_INTCR3_OFFSET, ++ NPCM7XX_INTCR3_PECIVSEL, 0); ++ } ++ } ++ ++ ret = of_property_read_u32(priv->dev->of_node, "pull-down", ++ &pull_down); ++ if (ret || pull_down > NPCM_PECI_PULL_DOWN_MAX) { ++ if (ret) ++ dev_warn(priv->dev, ++ "pull-down not found, use default : %u\n", ++ NPCM_PECI_PULL_DOWN_DEFAULT); ++ else ++ dev_warn(priv->dev, ++ "Invalid pull-down : %u. Use default : %u\n", ++ pull_down, ++ NPCM_PECI_PULL_DOWN_DEFAULT); ++ pull_down = NPCM_PECI_PULL_DOWN_DEFAULT; ++ } ++ ++ regmap_update_bits(priv->regmap, NPCM_PECI_CTL2, NPCM_PECI_CTL2_MASK, ++ pull_down << 6); ++ ++ ret = of_property_read_u32(priv->dev->of_node, "host-neg-bit-rate", ++ &host_neg_bit_rate); ++ if (ret || host_neg_bit_rate > NPCM_PECI_HOST_NEG_BIT_RATE_MAX || ++ host_neg_bit_rate < NPCM_PECI_HOST_NEG_BIT_RATE_MIN) { ++ if (ret) ++ dev_warn(priv->dev, ++ "host-neg-bit-rate not found, use default : %u\n", ++ NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); ++ else ++ dev_warn(priv->dev, ++ "Invalid host-neg-bit-rate : %u. Use default : %u\n", ++ host_neg_bit_rate, ++ NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); ++ host_neg_bit_rate = NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT; ++ } ++ ++ regmap_update_bits(priv->regmap, NPCM_PECI_PDDR, NPCM_PECI_PDDR_MASK, ++ host_neg_bit_rate); ++ ++ priv->host_bit_rate = clk_get_rate(priv->clk) / ++ (4 * (host_neg_bit_rate + 1)); ++ ++ ret = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, ++ !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), ++ NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, ++ NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); ++ if (ret) ++ return ret; /* -ETIMEDOUT */ ++ ++ /* PECI interrupt enable */ ++ regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, ++ NPCM_PECI_CTRL_DONE_INT_EN, ++ NPCM_PECI_CTRL_DONE_INT_EN); ++ ++ return 0; ++} ++ ++static const struct regmap_config npcm_peci_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = NPCM_PECI_MAX_REG, ++ .fast_io = true, ++}; ++ ++static int npcm_peci_xfer(struct peci_adapter *adapter, ++ struct peci_xfer_msg *msg) ++{ ++ struct npcm_peci *priv = peci_get_adapdata(adapter); ++ ++ return npcm_peci_xfer_native(priv, msg); ++} ++ ++static int npcm_peci_probe(struct platform_device *pdev) ++{ ++ struct peci_adapter *adapter; ++ struct npcm_peci *priv; ++ struct resource *res; ++ void __iomem *base; ++ int ret; ++ ++ adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); ++ if (!adapter) ++ return -ENOMEM; ++ ++ priv = peci_get_adapdata(adapter); ++ priv->adapter = adapter; ++ priv->dev = &pdev->dev; ++ dev_set_drvdata(&pdev->dev, priv); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(base)) { ++ ret = PTR_ERR(base); ++ goto err_put_adapter_dev; ++ } ++ ++ priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, ++ &npcm_peci_regmap_config); ++ if (IS_ERR(priv->regmap)) { ++ ret = PTR_ERR(priv->regmap); ++ goto err_put_adapter_dev; ++ } ++ ++ priv->irq = platform_get_irq(pdev, 0); ++ if (!priv->irq) { ++ ret = -ENODEV; ++ goto err_put_adapter_dev; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, priv->irq, npcm_peci_irq_handler, ++ 0, "peci-npcm-irq", priv); ++ if (ret) ++ goto err_put_adapter_dev; ++ ++ init_completion(&priv->xfer_complete); ++ spin_lock_init(&priv->lock); ++ ++ priv->adapter->owner = THIS_MODULE; ++ priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); ++ strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); ++ priv->adapter->xfer = npcm_peci_xfer; ++ ++ ret = npcm_peci_init_ctrl(priv); ++ if (ret) ++ goto err_put_adapter_dev; ++ ++ ret = peci_add_adapter(priv->adapter); ++ if (ret) ++ goto err_put_adapter_dev; ++ ++ dev_info(&pdev->dev, "peci bus %d registered, host negotiation bit rate %dHz", ++ priv->adapter->nr, priv->host_bit_rate); ++ ++ return 0; ++ ++err_put_adapter_dev: ++ put_device(&adapter->dev); ++ return ret; ++} ++ ++static int npcm_peci_remove(struct platform_device *pdev) ++{ ++ struct npcm_peci *priv = dev_get_drvdata(&pdev->dev); ++ ++ clk_disable_unprepare(priv->clk); ++ peci_del_adapter(priv->adapter); ++ of_node_put(priv->adapter->dev.of_node); ++ ++ return 0; ++} ++ ++static const struct of_device_id npcm_peci_of_table[] = { ++ { .compatible = "nuvoton,npcm750-peci", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, npcm_peci_of_table); ++ ++static struct platform_driver npcm_peci_driver = { ++ .probe = npcm_peci_probe, ++ .remove = npcm_peci_remove, ++ .driver = { ++ .name = "peci-npcm", ++ .of_match_table = of_match_ptr(npcm_peci_of_table), ++ }, ++}; ++module_platform_driver(npcm_peci_driver); ++ ++MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); ++MODULE_DESCRIPTION("NPCM Platform Environment Control Interface (PECI) driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/peci/peci-aspeed.c b/drivers/peci/peci-aspeed.c +deleted file mode 100644 +index 51cb2563ceb6..000000000000 +--- a/drivers/peci/peci-aspeed.c ++++ /dev/null +@@ -1,505 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-// Copyright (C) 2012-2017 ASPEED Technology Inc. +-// Copyright (c) 2018 Intel Corporation +- +-#include <linux/bitfield.h> +-#include <linux/clk.h> +-#include <linux/interrupt.h> +-#include <linux/jiffies.h> +-#include <linux/module.h> +-#include <linux/of.h> +-#include <linux/peci.h> +-#include <linux/platform_device.h> +-#include <linux/regmap.h> +-#include <linux/reset.h> +- +-/* ASPEED PECI Registers */ +-#define ASPEED_PECI_CTRL 0x00 +-#define ASPEED_PECI_TIMING 0x04 +-#define ASPEED_PECI_CMD 0x08 +-#define ASPEED_PECI_CMD_CTRL 0x0c +-#define ASPEED_PECI_EXP_FCS 0x10 +-#define ASPEED_PECI_CAP_FCS 0x14 +-#define ASPEED_PECI_INT_CTRL 0x18 +-#define ASPEED_PECI_INT_STS 0x1c +-#define ASPEED_PECI_W_DATA0 0x20 +-#define ASPEED_PECI_W_DATA1 0x24 +-#define ASPEED_PECI_W_DATA2 0x28 +-#define ASPEED_PECI_W_DATA3 0x2c +-#define ASPEED_PECI_R_DATA0 0x30 +-#define ASPEED_PECI_R_DATA1 0x34 +-#define ASPEED_PECI_R_DATA2 0x38 +-#define ASPEED_PECI_R_DATA3 0x3c +-#define ASPEED_PECI_W_DATA4 0x40 +-#define ASPEED_PECI_W_DATA5 0x44 +-#define ASPEED_PECI_W_DATA6 0x48 +-#define ASPEED_PECI_W_DATA7 0x4c +-#define ASPEED_PECI_R_DATA4 0x50 +-#define ASPEED_PECI_R_DATA5 0x54 +-#define ASPEED_PECI_R_DATA6 0x58 +-#define ASPEED_PECI_R_DATA7 0x5c +- +-/* ASPEED_PECI_CTRL - 0x00 : Control Register */ +-#define PECI_CTRL_SAMPLING_MASK GENMASK(19, 16) +-#define PECI_CTRL_READ_MODE_MASK GENMASK(13, 12) +-#define PECI_CTRL_READ_MODE_COUNT BIT(12) +-#define PECI_CTRL_READ_MODE_DBG BIT(13) +-#define PECI_CTRL_CLK_SOURCE_MASK BIT(11) +-#define PECI_CTRL_CLK_DIV_MASK GENMASK(10, 8) +-#define PECI_CTRL_INVERT_OUT BIT(7) +-#define PECI_CTRL_INVERT_IN BIT(6) +-#define PECI_CTRL_BUS_CONTENT_EN BIT(5) +-#define PECI_CTRL_PECI_EN BIT(4) +-#define PECI_CTRL_PECI_CLK_EN BIT(0) +- +-/* ASPEED_PECI_TIMING - 0x04 : Timing Negotiation Register */ +-#define PECI_TIMING_MESSAGE_MASK GENMASK(15, 8) +-#define PECI_TIMING_ADDRESS_MASK GENMASK(7, 0) +- +-/* ASPEED_PECI_CMD - 0x08 : Command Register */ +-#define PECI_CMD_PIN_MON BIT(31) +-#define PECI_CMD_STS_MASK GENMASK(27, 24) +-#define PECI_CMD_IDLE_MASK (PECI_CMD_STS_MASK | PECI_CMD_PIN_MON) +-#define PECI_CMD_FIRE BIT(0) +- +-/* ASPEED_PECI_LEN - 0x0C : Read/Write Length Register */ +-#define PECI_AW_FCS_EN BIT(31) +-#define PECI_READ_LEN_MASK GENMASK(23, 16) +-#define PECI_WRITE_LEN_MASK GENMASK(15, 8) +-#define PECI_TAGET_ADDR_MASK GENMASK(7, 0) +- +-/* ASPEED_PECI_EXP_FCS - 0x10 : Expected FCS Data Register */ +-#define PECI_EXPECT_READ_FCS_MASK GENMASK(23, 16) +-#define PECI_EXPECT_AW_FCS_AUTO_MASK GENMASK(15, 8) +-#define PECI_EXPECT_WRITE_FCS_MASK GENMASK(7, 0) +- +-/* ASPEED_PECI_CAP_FCS - 0x14 : Captured FCS Data Register */ +-#define PECI_CAPTURE_READ_FCS_MASK GENMASK(23, 16) +-#define PECI_CAPTURE_WRITE_FCS_MASK GENMASK(7, 0) +- +-/* ASPEED_PECI_INT_CTRL/STS - 0x18/0x1c : Interrupt Register */ +-#define PECI_INT_TIMING_RESULT_MASK GENMASK(31, 30) +-#define PECI_INT_TIMEOUT BIT(4) +-#define PECI_INT_CONNECT BIT(3) +-#define PECI_INT_W_FCS_BAD BIT(2) +-#define PECI_INT_W_FCS_ABORT BIT(1) +-#define PECI_INT_CMD_DONE BIT(0) +- +-#define PECI_INT_MASK (PECI_INT_TIMEOUT | PECI_INT_CONNECT | \ +- PECI_INT_W_FCS_BAD | PECI_INT_W_FCS_ABORT | \ +- PECI_INT_CMD_DONE) +- +-#define PECI_IDLE_CHECK_TIMEOUT_USEC 50000 +-#define PECI_IDLE_CHECK_INTERVAL_USEC 10000 +- +-#define PECI_RD_SAMPLING_POINT_DEFAULT 8 +-#define PECI_RD_SAMPLING_POINT_MAX 15 +-#define PECI_CLK_DIV_DEFAULT 0 +-#define PECI_CLK_DIV_MAX 7 +-#define PECI_MSG_TIMING_DEFAULT 1 +-#define PECI_MSG_TIMING_MAX 255 +-#define PECI_ADDR_TIMING_DEFAULT 1 +-#define PECI_ADDR_TIMING_MAX 255 +-#define PECI_CMD_TIMEOUT_MS_DEFAULT 1000 +-#define PECI_CMD_TIMEOUT_MS_MAX 60000 +- +-struct aspeed_peci { +- struct peci_adapter *adapter; +- struct device *dev; +- struct regmap *regmap; +- struct clk *clk; +- struct reset_control *rst; +- int irq; +- spinlock_t lock; /* to sync completion status handling */ +- struct completion xfer_complete; +- u32 status; +- u32 cmd_timeout_ms; +-}; +- +-static int aspeed_peci_xfer_native(struct aspeed_peci *priv, +- struct peci_xfer_msg *msg) +-{ +- long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); +- u32 peci_head, peci_state, rx_data, cmd_sts; +- unsigned long flags; +- int i, rc; +- uint reg; +- +- /* Check command sts and bus idle state */ +- rc = regmap_read_poll_timeout(priv->regmap, ASPEED_PECI_CMD, cmd_sts, +- !(cmd_sts & PECI_CMD_IDLE_MASK), +- PECI_IDLE_CHECK_INTERVAL_USEC, +- PECI_IDLE_CHECK_TIMEOUT_USEC); +- if (rc) +- return rc; /* -ETIMEDOUT */ +- +- spin_lock_irqsave(&priv->lock, flags); +- reinit_completion(&priv->xfer_complete); +- +- peci_head = FIELD_PREP(PECI_TAGET_ADDR_MASK, msg->addr) | +- FIELD_PREP(PECI_WRITE_LEN_MASK, msg->tx_len) | +- FIELD_PREP(PECI_READ_LEN_MASK, msg->rx_len); +- +- regmap_write(priv->regmap, ASPEED_PECI_CMD_CTRL, peci_head); +- +- for (i = 0; i < msg->tx_len; i += 4) { +- reg = i < 16 ? ASPEED_PECI_W_DATA0 + i % 16 : +- ASPEED_PECI_W_DATA4 + i % 16; +- regmap_write(priv->regmap, reg, +- le32_to_cpup((__le32 *)&msg->tx_buf[i])); +- } +- +- dev_dbg(priv->dev, "HEAD : 0x%08x\n", peci_head); +- print_hex_dump_debug("TX : ", DUMP_PREFIX_NONE, 16, 1, +- msg->tx_buf, msg->tx_len, true); +- +- priv->status = 0; +- regmap_write(priv->regmap, ASPEED_PECI_CMD, PECI_CMD_FIRE); +- spin_unlock_irqrestore(&priv->lock, flags); +- +- err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, +- timeout); +- +- spin_lock_irqsave(&priv->lock, flags); +- dev_dbg(priv->dev, "INT_STS : 0x%08x\n", priv->status); +- regmap_read(priv->regmap, ASPEED_PECI_CMD, &peci_state); +- dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", +- FIELD_GET(PECI_CMD_STS_MASK, peci_state)); +- +- regmap_write(priv->regmap, ASPEED_PECI_CMD, 0); +- +- if (err <= 0 || priv->status != PECI_INT_CMD_DONE) { +- if (err < 0) { /* -ERESTARTSYS */ +- rc = (int)err; +- goto err_irqrestore; +- } else if (err == 0) { +- dev_dbg(priv->dev, "Timeout waiting for a response!\n"); +- rc = -ETIMEDOUT; +- goto err_irqrestore; +- } +- +- dev_dbg(priv->dev, "No valid response!\n"); +- rc = -EIO; +- goto err_irqrestore; +- } +- +- /** +- * Note that rx_len and rx_buf size can be an odd number. +- * Byte handling is more efficient. +- */ +- for (i = 0; i < msg->rx_len; i++) { +- u8 byte_offset = i % 4; +- +- if (byte_offset == 0) { +- reg = i < 16 ? ASPEED_PECI_R_DATA0 + i % 16 : +- ASPEED_PECI_R_DATA4 + i % 16; +- regmap_read(priv->regmap, reg, &rx_data); +- } +- +- msg->rx_buf[i] = (u8)(rx_data >> (byte_offset << 3)); +- } +- +- print_hex_dump_debug("RX : ", DUMP_PREFIX_NONE, 16, 1, +- msg->rx_buf, msg->rx_len, true); +- +- regmap_read(priv->regmap, ASPEED_PECI_CMD, &peci_state); +- dev_dbg(priv->dev, "PECI_STATE : 0x%lx\n", +- FIELD_GET(PECI_CMD_STS_MASK, peci_state)); +- dev_dbg(priv->dev, "------------------------\n"); +- +-err_irqrestore: +- spin_unlock_irqrestore(&priv->lock, flags); +- return rc; +-} +- +-static irqreturn_t aspeed_peci_irq_handler(int irq, void *arg) +-{ +- struct aspeed_peci *priv = arg; +- u32 status_ack = 0; +- u32 status; +- +- spin_lock(&priv->lock); +- regmap_read(priv->regmap, ASPEED_PECI_INT_STS, &status); +- priv->status |= (status & PECI_INT_MASK); +- +- /** +- * In most cases, interrupt bits will be set one by one but also note +- * that multiple interrupt bits could be set at the same time. +- */ +- if (status & PECI_INT_TIMEOUT) { +- dev_dbg(priv->dev, "PECI_INT_TIMEOUT\n"); +- status_ack |= PECI_INT_TIMEOUT; +- } +- +- if (status & PECI_INT_CONNECT) { +- dev_dbg(priv->dev, "PECI_INT_CONNECT\n"); +- status_ack |= PECI_INT_CONNECT; +- } +- +- if (status & PECI_INT_W_FCS_BAD) { +- dev_dbg(priv->dev, "PECI_INT_W_FCS_BAD\n"); +- status_ack |= PECI_INT_W_FCS_BAD; +- } +- +- if (status & PECI_INT_W_FCS_ABORT) { +- dev_dbg(priv->dev, "PECI_INT_W_FCS_ABORT\n"); +- status_ack |= PECI_INT_W_FCS_ABORT; +- } +- +- /** +- * All commands should be ended up with a PECI_INT_CMD_DONE bit set +- * even in an error case. +- */ +- if (status & PECI_INT_CMD_DONE) { +- dev_dbg(priv->dev, "PECI_INT_CMD_DONE\n"); +- status_ack |= PECI_INT_CMD_DONE; +- complete(&priv->xfer_complete); +- } +- +- regmap_write(priv->regmap, ASPEED_PECI_INT_STS, status_ack); +- spin_unlock(&priv->lock); +- return IRQ_HANDLED; +-} +- +-static int aspeed_peci_init_ctrl(struct aspeed_peci *priv) +-{ +- u32 msg_timing, addr_timing, rd_sampling_point; +- u32 clk_freq, clk_divisor, clk_div_val = 0; +- int ret; +- +- priv->clk = devm_clk_get(priv->dev, NULL); +- if (IS_ERR(priv->clk)) { +- dev_err(priv->dev, "Failed to get clk source.\n"); +- return PTR_ERR(priv->clk); +- } +- +- ret = clk_prepare_enable(priv->clk); +- if (ret) { +- dev_err(priv->dev, "Failed to enable clock.\n"); +- return ret; +- } +- +- ret = of_property_read_u32(priv->dev->of_node, "clock-frequency", +- &clk_freq); +- if (ret) { +- dev_err(priv->dev, +- "Could not read clock-frequency property.\n"); +- clk_disable_unprepare(priv->clk); +- return ret; +- } +- +- clk_divisor = clk_get_rate(priv->clk) / clk_freq; +- +- while ((clk_divisor >> 1) && (clk_div_val < PECI_CLK_DIV_MAX)) +- clk_div_val++; +- +- ret = of_property_read_u32(priv->dev->of_node, "msg-timing", +- &msg_timing); +- if (ret || msg_timing > PECI_MSG_TIMING_MAX) { +- if (!ret) +- dev_warn(priv->dev, +- "Invalid msg-timing : %u, Use default : %u\n", +- msg_timing, PECI_MSG_TIMING_DEFAULT); +- msg_timing = PECI_MSG_TIMING_DEFAULT; +- } +- +- ret = of_property_read_u32(priv->dev->of_node, "addr-timing", +- &addr_timing); +- if (ret || addr_timing > PECI_ADDR_TIMING_MAX) { +- if (!ret) +- dev_warn(priv->dev, +- "Invalid addr-timing : %u, Use default : %u\n", +- addr_timing, PECI_ADDR_TIMING_DEFAULT); +- addr_timing = PECI_ADDR_TIMING_DEFAULT; +- } +- +- ret = of_property_read_u32(priv->dev->of_node, "rd-sampling-point", +- &rd_sampling_point); +- if (ret || rd_sampling_point > PECI_RD_SAMPLING_POINT_MAX) { +- if (!ret) +- dev_warn(priv->dev, +- "Invalid rd-sampling-point : %u. Use default : %u\n", +- rd_sampling_point, +- PECI_RD_SAMPLING_POINT_DEFAULT); +- rd_sampling_point = PECI_RD_SAMPLING_POINT_DEFAULT; +- } +- +- ret = of_property_read_u32(priv->dev->of_node, "cmd-timeout-ms", +- &priv->cmd_timeout_ms); +- if (ret || priv->cmd_timeout_ms > PECI_CMD_TIMEOUT_MS_MAX || +- priv->cmd_timeout_ms == 0) { +- if (!ret) +- dev_warn(priv->dev, +- "Invalid cmd-timeout-ms : %u. Use default : %u\n", +- priv->cmd_timeout_ms, +- PECI_CMD_TIMEOUT_MS_DEFAULT); +- priv->cmd_timeout_ms = PECI_CMD_TIMEOUT_MS_DEFAULT; +- } +- +- regmap_write(priv->regmap, ASPEED_PECI_CTRL, +- FIELD_PREP(PECI_CTRL_CLK_DIV_MASK, PECI_CLK_DIV_DEFAULT) | +- PECI_CTRL_PECI_CLK_EN); +- +- /** +- * Timing negotiation period setting. +- * The unit of the programmed value is 4 times of PECI clock period. +- */ +- regmap_write(priv->regmap, ASPEED_PECI_TIMING, +- FIELD_PREP(PECI_TIMING_MESSAGE_MASK, msg_timing) | +- FIELD_PREP(PECI_TIMING_ADDRESS_MASK, addr_timing)); +- +- /* Clear interrupts */ +- regmap_write(priv->regmap, ASPEED_PECI_INT_STS, PECI_INT_MASK); +- +- /* Enable interrupts */ +- regmap_write(priv->regmap, ASPEED_PECI_INT_CTRL, PECI_INT_MASK); +- +- /* Read sampling point and clock speed setting */ +- regmap_write(priv->regmap, ASPEED_PECI_CTRL, +- FIELD_PREP(PECI_CTRL_SAMPLING_MASK, rd_sampling_point) | +- FIELD_PREP(PECI_CTRL_CLK_DIV_MASK, clk_div_val) | +- PECI_CTRL_PECI_EN | PECI_CTRL_PECI_CLK_EN); +- +- return 0; +-} +- +-static const struct regmap_config aspeed_peci_regmap_config = { +- .reg_bits = 32, +- .val_bits = 32, +- .reg_stride = 4, +- .max_register = ASPEED_PECI_R_DATA7, +- .val_format_endian = REGMAP_ENDIAN_LITTLE, +- .fast_io = true, +-}; +- +-static int aspeed_peci_xfer(struct peci_adapter *adapter, +- struct peci_xfer_msg *msg) +-{ +- struct aspeed_peci *priv = peci_get_adapdata(adapter); +- +- return aspeed_peci_xfer_native(priv, msg); +-} +- +-static int aspeed_peci_probe(struct platform_device *pdev) +-{ +- struct peci_adapter *adapter; +- struct aspeed_peci *priv; +- struct resource *res; +- void __iomem *base; +- u32 cmd_sts; +- int ret; +- +- adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); +- if (!adapter) +- return -ENOMEM; +- +- priv = peci_get_adapdata(adapter); +- priv->adapter = adapter; +- priv->dev = &pdev->dev; +- dev_set_drvdata(&pdev->dev, priv); +- +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- base = devm_ioremap_resource(&pdev->dev, res); +- if (IS_ERR(base)) { +- ret = PTR_ERR(base); +- goto err_put_adapter_dev; +- } +- +- priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, +- &aspeed_peci_regmap_config); +- if (IS_ERR(priv->regmap)) { +- ret = PTR_ERR(priv->regmap); +- goto err_put_adapter_dev; +- } +- +- /** +- * We check that the regmap works on this very first access, +- * but as this is an MMIO-backed regmap, subsequent regmap +- * access is not going to fail and we skip error checks from +- * this point. +- */ +- ret = regmap_read(priv->regmap, ASPEED_PECI_CMD, &cmd_sts); +- if (ret) { +- ret = -EIO; +- goto err_put_adapter_dev; +- } +- +- priv->irq = platform_get_irq(pdev, 0); +- if (!priv->irq) { +- ret = -ENODEV; +- goto err_put_adapter_dev; +- } +- +- ret = devm_request_irq(&pdev->dev, priv->irq, aspeed_peci_irq_handler, +- 0, "peci-aspeed-irq", priv); +- if (ret) +- goto err_put_adapter_dev; +- +- init_completion(&priv->xfer_complete); +- spin_lock_init(&priv->lock); +- +- priv->adapter->owner = THIS_MODULE; +- priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); +- strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); +- priv->adapter->xfer = aspeed_peci_xfer; +- +- priv->rst = devm_reset_control_get(&pdev->dev, NULL); +- if (IS_ERR(priv->rst)) { +- dev_err(&pdev->dev, +- "missing or invalid reset controller entry"); +- ret = PTR_ERR(priv->rst); +- goto err_put_adapter_dev; +- } +- reset_control_deassert(priv->rst); +- +- ret = aspeed_peci_init_ctrl(priv); +- if (ret) +- goto err_put_adapter_dev; +- +- ret = peci_add_adapter(priv->adapter); +- if (ret) +- goto err_put_adapter_dev; +- +- dev_info(&pdev->dev, "peci bus %d registered, irq %d\n", +- priv->adapter->nr, priv->irq); +- +- return 0; +- +-err_put_adapter_dev: +- put_device(&adapter->dev); +- return ret; +-} +- +-static int aspeed_peci_remove(struct platform_device *pdev) +-{ +- struct aspeed_peci *priv = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(priv->clk); +- reset_control_assert(priv->rst); +- peci_del_adapter(priv->adapter); +- of_node_put(priv->adapter->dev.of_node); +- +- return 0; +-} +- +-static const struct of_device_id aspeed_peci_of_table[] = { +- { .compatible = "aspeed,ast2400-peci", }, +- { .compatible = "aspeed,ast2500-peci", }, +- { } +-}; +-MODULE_DEVICE_TABLE(of, aspeed_peci_of_table); +- +-static struct platform_driver aspeed_peci_driver = { +- .probe = aspeed_peci_probe, +- .remove = aspeed_peci_remove, +- .driver = { +- .name = "peci-aspeed", +- .of_match_table = of_match_ptr(aspeed_peci_of_table), +- }, +-}; +-module_platform_driver(aspeed_peci_driver); +- +-MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>"); +-MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +-MODULE_DESCRIPTION("ASPEED PECI driver"); +-MODULE_LICENSE("GPL v2"); +diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c +index 6f241469ec7e..48f29de09311 100644 +--- a/drivers/peci/peci-core.c ++++ b/drivers/peci/peci-core.c +@@ -1,38 +1,31 @@ + // 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> + #include <linux/delay.h> +-#include <linux/fs.h> ++#include <linux/mm.h> + #include <linux/module.h> + #include <linux/of_device.h> + #include <linux/peci.h> + #include <linux/pm_domain.h> + #include <linux/pm_runtime.h> ++#include <linux/sched/task_stack.h> + #include <linux/slab.h> +-#include <linux/uaccess.h> + + /* Mask for getting minor revision number from DIB */ + #define REVISION_NUM_MASK GENMASK(15, 8) + +-/* CRC8 table for Assure Write Frame Check */ ++/* CRC8 table for Assured Write Frame Check */ + #define PECI_CRC8_POLYNOMIAL 0x07 + DECLARE_CRC8_TABLE(peci_crc8_table); + +-static struct device_type peci_adapter_type; +-static struct device_type peci_client_type; +- +-/* Max number of peci cdev */ +-#define PECI_CDEV_MAX 16 +- +-static dev_t peci_devt; + static bool is_registered; + + static DEFINE_MUTEX(core_lock); + static DEFINE_IDR(peci_adapter_idr); + +-static struct peci_adapter *peci_get_adapter(int nr) ++struct peci_adapter *peci_get_adapter(int nr) + { + struct peci_adapter *adapter; + +@@ -48,10 +41,12 @@ static struct peci_adapter *peci_get_adapter(int nr) + + out_unlock: + mutex_unlock(&core_lock); ++ + return adapter; + } ++EXPORT_SYMBOL_GPL(peci_get_adapter); + +-static void peci_put_adapter(struct peci_adapter *adapter) ++void peci_put_adapter(struct peci_adapter *adapter) + { + if (!adapter) + return; +@@ -59,6 +54,7 @@ static void peci_put_adapter(struct peci_adapter *adapter) + put_device(&adapter->dev); + module_put(adapter->owner); + } ++EXPORT_SYMBOL_GPL(peci_put_adapter); + + static ssize_t name_show(struct device *dev, + struct device_attribute *attr, +@@ -84,10 +80,11 @@ static struct attribute *peci_device_attrs[] = { + }; + ATTRIBUTE_GROUPS(peci_device); + +-static struct device_type peci_client_type = { ++struct device_type peci_client_type = { + .groups = peci_device_groups, + .release = peci_client_dev_release, + }; ++EXPORT_SYMBOL_GPL(peci_client_type); + + /** + * peci_verify_client - return parameter as peci_client, or NULL +@@ -103,19 +100,120 @@ struct peci_client *peci_verify_client(struct device *dev) + } + EXPORT_SYMBOL_GPL(peci_verify_client); + +-static u8 peci_aw_fcs(u8 *data, int len) ++/** ++ * peci_get_xfer_msg() - get a DMA safe peci_xfer_msg for the given tx and rx ++ * length ++ * @tx_len: the length of tx_buf. May be 0 if tx_buf isn't needed. ++ * @rx_len: the length of rx_buf. May be 0 if rx_buf isn't needed. ++ * ++ * Return: NULL if a DMA safe buffer was not obtained. ++ * Or a valid pointer to be used with DMA. After use, release it by ++ * calling peci_put_xfer_msg(). ++ * ++ * This function must only be called from process context! ++ */ ++struct peci_xfer_msg *peci_get_xfer_msg(u8 tx_len, u8 rx_len) + { +- return crc8(peci_crc8_table, data, (size_t)len, 0); ++ struct peci_xfer_msg *msg; ++ u8 *tx_buf, *rx_buf; ++ ++ if (tx_len) { ++ tx_buf = kzalloc(tx_len, GFP_KERNEL); ++ if (!tx_buf) ++ return NULL; ++ } else { ++ tx_buf = NULL; ++ } ++ ++ if (rx_len) { ++ rx_buf = kzalloc(rx_len, GFP_KERNEL); ++ if (!rx_buf) ++ goto err_free_tx_buf; ++ } else { ++ rx_buf = NULL; ++ } ++ ++ msg = kzalloc(sizeof(struct peci_xfer_msg), GFP_KERNEL); ++ if (!msg) ++ goto err_free_tx_rx_buf; ++ ++ msg->tx_len = tx_len; ++ msg->tx_buf = tx_buf; ++ msg->rx_len = rx_len; ++ msg->rx_buf = rx_buf; ++ ++ return msg; ++ ++err_free_tx_rx_buf: ++ kfree(rx_buf); ++err_free_tx_buf: ++ kfree(tx_buf); ++ ++ return NULL; ++} ++EXPORT_SYMBOL_GPL(peci_get_xfer_msg); ++ ++/** ++ * peci_put_xfer_msg - release a DMA safe peci_xfer_msg ++ * @msg: the message obtained from peci_get_xfer_msg(). May be NULL. ++ */ ++void peci_put_xfer_msg(struct peci_xfer_msg *msg) ++{ ++ if (!msg) ++ return; ++ ++ kfree(msg->rx_buf); ++ kfree(msg->tx_buf); ++ kfree(msg); ++} ++EXPORT_SYMBOL_GPL(peci_put_xfer_msg); ++ ++/* Calculate an Assured Write Frame Check Sequence byte */ ++static int peci_aw_fcs(struct peci_xfer_msg *msg, int len, u8 *aw_fcs) ++{ ++ u8 *tmp_buf; ++ ++ /* Allocate a temporary buffer to use a contiguous byte array */ ++ tmp_buf = kmalloc(len, GFP_KERNEL); ++ if (!tmp_buf) ++ return -ENOMEM; ++ ++ tmp_buf[0] = msg->addr; ++ tmp_buf[1] = msg->tx_len; ++ tmp_buf[2] = msg->rx_len; ++ memcpy(&tmp_buf[3], msg->tx_buf, len - 3); ++ ++ *aw_fcs = crc8(peci_crc8_table, tmp_buf, (size_t)len, 0); ++ ++ kfree(tmp_buf); ++ ++ return 0; + } + + static int __peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg, + bool do_retry, bool has_aw_fcs) + { +- ktime_t start, end; +- s64 elapsed_ms; +- int rc = 0; ++ ulong timeout = jiffies; ++ u8 aw_fcs; ++ int ret; ++ ++ /* ++ * In case if adapter uses DMA, check at here whether tx and rx buffers ++ * are DMA capable or not. ++ */ ++ if (IS_ENABLED(CONFIG_HAS_DMA) && adapter->use_dma) { ++ if (is_vmalloc_addr(msg->tx_buf) || ++ is_vmalloc_addr(msg->rx_buf)) { ++ WARN_ONCE(1, "xfer msg is not dma capable\n"); ++ return -EAGAIN; ++ } else if (object_is_on_stack(msg->tx_buf) || ++ object_is_on_stack(msg->rx_buf)) { ++ WARN_ONCE(1, "xfer msg is on stack\n"); ++ return -EAGAIN; ++ } ++ } + +- /** ++ /* + * For some commands, the PECI originator may need to retry a command if + * the processor PECI client responds with a 0x8x completion code. In + * each instance, the processor PECI client may have started the +@@ -125,55 +223,51 @@ static int __peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg, + */ + + if (do_retry) +- start = ktime_get(); ++ timeout += msecs_to_jiffies(PECI_DEV_RETRY_TIME_MS); + +- do { +- rc = adapter->xfer(adapter, msg); ++ for (;;) { ++ ret = adapter->xfer(adapter, msg); + +- if (!do_retry || rc) +- break; +- +- if (msg->rx_buf[0] == DEV_PECI_CC_SUCCESS) ++ if (!do_retry || ret || !msg->rx_buf) + break; + + /* Retry is needed when completion code is 0x8x */ +- if ((msg->rx_buf[0] & DEV_PECI_CC_RETRY_CHECK_MASK) != +- DEV_PECI_CC_NEED_RETRY) { +- rc = -EIO; ++ if ((msg->rx_buf[0] & PECI_DEV_CC_RETRY_CHECK_MASK) != ++ PECI_DEV_CC_NEED_RETRY) + break; +- } + + /* Set the retry bit to indicate a retry attempt */ +- msg->tx_buf[1] |= DEV_PECI_RETRY_BIT; ++ msg->tx_buf[1] |= PECI_DEV_RETRY_BIT; + + /* Recalculate the AW FCS if it has one */ +- if (has_aw_fcs) +- msg->tx_buf[msg->tx_len - 1] = 0x80 ^ +- peci_aw_fcs((u8 *)msg, +- 2 + msg->tx_len); ++ if (has_aw_fcs) { ++ ret = peci_aw_fcs(msg, 2 + msg->tx_len, &aw_fcs); ++ if (ret) ++ break; ++ ++ msg->tx_buf[msg->tx_len - 1] = 0x80 ^ aw_fcs; ++ } + +- /** ++ /* + * Retry for at least 250ms before returning an error. + * Retry interval guideline: + * No minimum < Retry Interval < No maximum + * (recommend 10ms) + */ +- end = ktime_get(); +- elapsed_ms = ktime_to_ms(ktime_sub(end, start)); +- if (elapsed_ms >= DEV_PECI_RETRY_TIME_MS) { ++ if (time_after(jiffies, timeout)) { + dev_dbg(&adapter->dev, "Timeout retrying xfer!\n"); +- rc = -ETIMEDOUT; ++ ret = -ETIMEDOUT; + break; + } + +- usleep_range((DEV_PECI_RETRY_INTERVAL_USEC >> 2) + 1, +- DEV_PECI_RETRY_INTERVAL_USEC); +- } while (true); ++ usleep_range((PECI_DEV_RETRY_INTERVAL_USEC >> 2) + 1, ++ PECI_DEV_RETRY_INTERVAL_USEC); ++ } + +- if (rc) +- dev_dbg(&adapter->dev, "xfer error, rc: %d\n", rc); ++ if (ret) ++ dev_dbg(&adapter->dev, "xfer error: %d\n", ret); + +- return rc; ++ return ret; + } + + static int peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg) +@@ -190,34 +284,37 @@ static int peci_xfer_with_retries(struct peci_adapter *adapter, + + static int peci_scan_cmd_mask(struct peci_adapter *adapter) + { +- struct peci_xfer_msg msg; ++ struct peci_xfer_msg *msg; + u8 revision; +- int rc = 0; ++ int ret; + u64 dib; + + /* Update command mask just once */ + if (adapter->cmd_mask & BIT(PECI_CMD_XFER)) + return 0; + +- msg.addr = PECI_BASE_ADDR; +- msg.tx_len = GET_DIB_WR_LEN; +- msg.rx_len = GET_DIB_RD_LEN; +- msg.tx_buf[0] = GET_DIB_PECI_CMD; ++ msg = peci_get_xfer_msg(PECI_GET_DIB_WR_LEN, PECI_GET_DIB_RD_LEN); ++ if (!msg) ++ return -ENOMEM; ++ ++ msg->addr = PECI_BASE_ADDR; ++ msg->tx_buf[0] = PECI_GET_DIB_CMD; + +- rc = peci_xfer(adapter, &msg); +- if (rc) +- return rc; ++ ret = peci_xfer(adapter, msg); ++ if (ret) ++ return ret; + +- dib = le64_to_cpup((__le64 *)msg.rx_buf); ++ dib = le64_to_cpup((__le64 *)msg->rx_buf); + + /* Check special case for Get DIB command */ + if (dib == 0) { + dev_dbg(&adapter->dev, "DIB read as 0\n"); +- return -EIO; ++ ret = -EIO; ++ goto out; + } + +- /** +- * Setting up the supporting commands based on minor revision number. ++ /* ++ * Setting up the supporting commands based on revision number. + * See PECI Spec Table 3-1. + */ + revision = FIELD_GET(REVISION_NUM_MASK, dib); +@@ -243,10 +340,14 @@ static int peci_scan_cmd_mask(struct peci_adapter *adapter) + adapter->cmd_mask |= BIT(PECI_CMD_GET_DIB); + adapter->cmd_mask |= BIT(PECI_CMD_PING); + +- return rc; ++out: ++ peci_put_xfer_msg(msg); ++ ++ return ret; + } + +-static int peci_cmd_support(struct peci_adapter *adapter, enum peci_cmd cmd) ++static int peci_check_cmd_support(struct peci_adapter *adapter, ++ enum peci_cmd cmd) + { + if (!(adapter->cmd_mask & BIT(PECI_CMD_PING)) && + peci_scan_cmd_mask(adapter) < 0) { +@@ -262,70 +363,87 @@ static int peci_cmd_support(struct peci_adapter *adapter, enum peci_cmd cmd) + return 0; + } + +-static int peci_ioctl_xfer(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_xfer(struct peci_adapter *adapter, void *vmsg) + { + struct peci_xfer_msg *msg = vmsg; + + return peci_xfer(adapter, msg); + } + +-static int peci_ioctl_ping(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_ping(struct peci_adapter *adapter, void *vmsg) + { + struct peci_ping_msg *umsg = vmsg; +- struct peci_xfer_msg msg; ++ struct peci_xfer_msg *msg; ++ int ret; ++ ++ msg = peci_get_xfer_msg(0, 0); ++ if (!msg) ++ return -ENOMEM; ++ ++ msg->addr = umsg->addr; + +- msg.addr = umsg->addr; +- msg.tx_len = 0; +- msg.rx_len = 0; ++ ret = peci_xfer(adapter, msg); + +- return peci_xfer(adapter, &msg); ++ peci_put_xfer_msg(msg); ++ ++ return ret; + } + +-static int peci_ioctl_get_dib(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_get_dib(struct peci_adapter *adapter, void *vmsg) + { + struct peci_get_dib_msg *umsg = vmsg; +- struct peci_xfer_msg msg; +- int rc; ++ struct peci_xfer_msg *msg; ++ int ret; + +- msg.addr = umsg->addr; +- msg.tx_len = GET_DIB_WR_LEN; +- msg.rx_len = GET_DIB_RD_LEN; +- msg.tx_buf[0] = GET_DIB_PECI_CMD; ++ msg = peci_get_xfer_msg(PECI_GET_DIB_WR_LEN, PECI_GET_DIB_RD_LEN); ++ if (!msg) ++ return -ENOMEM; + +- rc = peci_xfer(adapter, &msg); +- if (rc) +- return rc; ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_GET_DIB_CMD; + +- umsg->dib = le64_to_cpup((__le64 *)msg.rx_buf); ++ ret = peci_xfer(adapter, msg); ++ if (ret) ++ goto out; + +- return 0; ++ umsg->dib = le64_to_cpup((__le64 *)msg->rx_buf); ++ ++out: ++ peci_put_xfer_msg(msg); ++ ++ return ret; + } + +-static int peci_ioctl_get_temp(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_get_temp(struct peci_adapter *adapter, void *vmsg) + { + struct peci_get_temp_msg *umsg = vmsg; +- struct peci_xfer_msg msg; +- int rc; ++ struct peci_xfer_msg *msg; ++ int ret; + +- msg.addr = umsg->addr; +- msg.tx_len = GET_TEMP_WR_LEN; +- msg.rx_len = GET_TEMP_RD_LEN; +- msg.tx_buf[0] = GET_TEMP_PECI_CMD; ++ msg = peci_get_xfer_msg(PECI_GET_TEMP_WR_LEN, PECI_GET_TEMP_RD_LEN); ++ if (!msg) ++ return -ENOMEM; + +- rc = peci_xfer(adapter, &msg); +- if (rc) +- return rc; ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_GET_TEMP_CMD; + +- umsg->temp_raw = le16_to_cpup((__le16 *)msg.rx_buf); ++ ret = peci_xfer(adapter, msg); ++ if (ret) ++ goto out; + +- return 0; ++ umsg->temp_raw = le16_to_cpup((__le16 *)msg->rx_buf); ++ ++out: ++ peci_put_xfer_msg(msg); ++ ++ return ret; + } + +-static int peci_ioctl_rd_pkg_cfg(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_rd_pkg_cfg(struct peci_adapter *adapter, void *vmsg) + { + struct peci_rd_pkg_cfg_msg *umsg = vmsg; +- struct peci_xfer_msg msg; +- int rc = 0; ++ struct peci_xfer_msg *msg; ++ int ret; + + /* 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) { +@@ -334,29 +452,35 @@ static int peci_ioctl_rd_pkg_cfg(struct peci_adapter *adapter, void *vmsg) + return -EINVAL; + } + +- msg.addr = umsg->addr; +- msg.tx_len = RDPKGCFG_WRITE_LEN; +- /* read lengths of 1 and 2 result in an error, so only use 4 for now */ +- msg.rx_len = RDPKGCFG_READ_LEN_BASE + umsg->rx_len; +- msg.tx_buf[0] = RDPKGCFG_PECI_CMD; +- msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ +- /* Host ID is 0 for PECI 3.0 */ +- msg.tx_buf[2] = umsg->index; /* RdPkgConfig index */ +- msg.tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ +- msg.tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ ++ msg = peci_get_xfer_msg(PECI_RDPKGCFG_WRITE_LEN, ++ PECI_RDPKGCFG_READ_LEN_BASE + umsg->rx_len); ++ if (!msg) ++ return -ENOMEM; ++ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_RDPKGCFG_CMD; ++ msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ ++ /* Host ID is 0 for PECI 3.0 */ ++ msg->tx_buf[2] = umsg->index; /* RdPkgConfig index */ ++ msg->tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ ++ msg->tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ + +- rc = peci_xfer_with_retries(adapter, &msg, false); +- if (!rc) +- memcpy(umsg->pkg_config, &msg.rx_buf[1], umsg->rx_len); ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ if (!ret) ++ memcpy(umsg->pkg_config, &msg->rx_buf[1], umsg->rx_len); + +- return rc; ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); ++ ++ return ret; + } + +-static int peci_ioctl_wr_pkg_cfg(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_wr_pkg_cfg(struct peci_adapter *adapter, void *vmsg) + { + struct peci_wr_pkg_cfg_msg *umsg = vmsg; +- struct peci_xfer_msg msg; +- int rc = 0, i; ++ struct peci_xfer_msg *msg; ++ int ret, i; ++ u8 aw_fcs; + + /* Per the PECI spec, the write length must be a dword */ + if (umsg->tx_len != 4) { +@@ -365,86 +489,116 @@ static int peci_ioctl_wr_pkg_cfg(struct peci_adapter *adapter, void *vmsg) + return -EINVAL; + } + +- msg.addr = umsg->addr; +- msg.tx_len = WRPKGCFG_WRITE_LEN_BASE + umsg->tx_len; +- /* read lengths of 1 and 2 result in an error, so only use 4 for now */ +- msg.rx_len = WRPKGCFG_READ_LEN; +- msg.tx_buf[0] = WRPKGCFG_PECI_CMD; +- msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ ++ msg = peci_get_xfer_msg(PECI_WRPKGCFG_WRITE_LEN_BASE + umsg->tx_len, ++ PECI_WRPKGCFG_READ_LEN); ++ if (!msg) ++ return -ENOMEM; ++ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_WRPKGCFG_CMD; ++ msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ +- msg.tx_buf[2] = umsg->index; /* RdPkgConfig index */ +- msg.tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ +- msg.tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ ++ msg->tx_buf[2] = umsg->index; /* RdPkgConfig index */ ++ msg->tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ ++ msg->tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ + for (i = 0; i < umsg->tx_len; i++) +- msg.tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); ++ msg->tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); ++ ++ /* Add an Assured Write Frame Check Sequence byte */ ++ ret = peci_aw_fcs(msg, 8 + umsg->tx_len, &aw_fcs); ++ if (ret) ++ goto out; + +- /* Add an Assure Write Frame Check Sequence byte */ +- msg.tx_buf[5 + i] = 0x80 ^ +- peci_aw_fcs((u8 *)&msg, 8 + umsg->tx_len); ++ msg->tx_buf[5 + i] = 0x80 ^ aw_fcs; + +- rc = peci_xfer_with_retries(adapter, &msg, true); ++ ret = peci_xfer_with_retries(adapter, msg, true); + +- return rc; ++out: ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); ++ ++ return ret; + } + +-static int peci_ioctl_rd_ia_msr(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_rd_ia_msr(struct peci_adapter *adapter, void *vmsg) + { + struct peci_rd_ia_msr_msg *umsg = vmsg; +- struct peci_xfer_msg msg; +- int rc = 0; +- +- msg.addr = umsg->addr; +- msg.tx_len = RDIAMSR_WRITE_LEN; +- msg.rx_len = RDIAMSR_READ_LEN; +- msg.tx_buf[0] = RDIAMSR_PECI_CMD; +- msg.tx_buf[1] = 0; +- msg.tx_buf[2] = umsg->thread_id; +- msg.tx_buf[3] = (u8)umsg->address; +- msg.tx_buf[4] = (u8)(umsg->address >> 8); +- +- rc = peci_xfer_with_retries(adapter, &msg, false); +- if (!rc) +- memcpy(&umsg->value, &msg.rx_buf[1], sizeof(uint64_t)); +- +- return rc; ++ struct peci_xfer_msg *msg; ++ int ret; ++ ++ msg = peci_get_xfer_msg(PECI_RDIAMSR_WRITE_LEN, PECI_RDIAMSR_READ_LEN); ++ if (!msg) ++ return -ENOMEM; ++ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_RDIAMSR_CMD; ++ msg->tx_buf[1] = 0; ++ msg->tx_buf[2] = umsg->thread_id; ++ msg->tx_buf[3] = (u8)umsg->address; ++ msg->tx_buf[4] = (u8)(umsg->address >> 8); ++ ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ if (!ret) ++ memcpy(&umsg->value, &msg->rx_buf[1], sizeof(uint64_t)); ++ ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); ++ ++ return ret; + } + +-static int peci_ioctl_rd_pci_cfg(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_wr_ia_msr(struct peci_adapter *adapter, void *vmsg) ++{ ++ return -ENOSYS; /* Not implemented yet */ ++} ++ ++static int peci_cmd_rd_pci_cfg(struct peci_adapter *adapter, void *vmsg) + { + struct peci_rd_pci_cfg_msg *umsg = vmsg; +- struct peci_xfer_msg msg; ++ struct peci_xfer_msg *msg; + u32 address; +- int rc = 0; ++ int ret; ++ ++ msg = peci_get_xfer_msg(PECI_RDPCICFG_WRITE_LEN, ++ PECI_RDPCICFG_READ_LEN); ++ if (!msg) ++ return -ENOMEM; + + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [27:20] - Bus */ + /* [31:28] - Reserved */ +- msg.addr = umsg->addr; +- msg.tx_len = RDPCICFG_WRITE_LEN; +- msg.rx_len = RDPCICFG_READ_LEN; +- msg.tx_buf[0] = RDPCICFG_PECI_CMD; +- msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_RDPCICFG_CMD; ++ msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ + /* Host ID is 0 for PECI 3.0 */ +- msg.tx_buf[2] = (u8)address; /* LSB - PCI Config Address */ +- msg.tx_buf[3] = (u8)(address >> 8); /* PCI Config Address */ +- msg.tx_buf[4] = (u8)(address >> 16); /* PCI Config Address */ +- msg.tx_buf[5] = (u8)(address >> 24); /* MSB - PCI Config Address */ ++ msg->tx_buf[2] = (u8)address; /* LSB - PCI Config Address */ ++ msg->tx_buf[3] = (u8)(address >> 8); /* PCI Config Address */ ++ msg->tx_buf[4] = (u8)(address >> 16); /* PCI Config Address */ ++ msg->tx_buf[5] = (u8)(address >> 24); /* MSB - PCI Config Address */ ++ ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ if (!ret) ++ memcpy(umsg->pci_config, &msg->rx_buf[1], 4); + +- rc = peci_xfer_with_retries(adapter, &msg, false); +- if (!rc) +- memcpy(umsg->pci_config, &msg.rx_buf[1], 4); ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); + +- return rc; ++ return ret; + } + +-static int peci_ioctl_rd_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_wr_pci_cfg(struct peci_adapter *adapter, void *vmsg) ++{ ++ return -ENOSYS; /* Not implemented yet */ ++} ++ ++static int peci_cmd_rd_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) + { + struct peci_rd_pci_cfg_local_msg *umsg = vmsg; +- struct peci_xfer_msg msg; ++ struct peci_xfer_msg *msg; + u32 address; +- int rc = 0; ++ int ret; + + /* 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) { +@@ -453,34 +607,42 @@ static int peci_ioctl_rd_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) + return -EINVAL; + } + ++ msg = peci_get_xfer_msg(PECI_RDPCICFGLOCAL_WRITE_LEN, ++ PECI_RDPCICFGLOCAL_READ_LEN_BASE + ++ umsg->rx_len); ++ if (!msg) ++ return -ENOMEM; ++ + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ + +- msg.addr = umsg->addr; +- msg.tx_len = RDPCICFGLOCAL_WRITE_LEN; +- msg.rx_len = RDPCICFGLOCAL_READ_LEN_BASE + umsg->rx_len; +- msg.tx_buf[0] = RDPCICFGLOCAL_PECI_CMD; +- msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ +- /* Host ID is 0 for PECI 3.0 */ +- msg.tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ +- msg.tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ +- msg.tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_RDPCICFGLOCAL_CMD; ++ msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ ++ /* Host ID is 0 for PECI 3.0 */ ++ msg->tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ ++ msg->tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ ++ msg->tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ ++ ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ if (!ret) ++ memcpy(umsg->pci_config, &msg->rx_buf[1], umsg->rx_len); + +- rc = peci_xfer_with_retries(adapter, &msg, false); +- if (!rc) +- memcpy(umsg->pci_config, &msg.rx_buf[1], umsg->rx_len); ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); + +- return rc; ++ return ret; + } + +-static int peci_ioctl_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) ++static int peci_cmd_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) + { + struct peci_wr_pci_cfg_local_msg *umsg = vmsg; +- struct peci_xfer_msg msg; +- int rc = 0, i; ++ struct peci_xfer_msg *msg; + u32 address; ++ int ret, i; ++ u8 aw_fcs; + + /* Per the PECI spec, the write length must be a byte, word, or dword */ + if (umsg->tx_len != 1 && umsg->tx_len != 2 && umsg->tx_len != 4) { +@@ -489,47 +651,57 @@ static int peci_ioctl_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) + return -EINVAL; + } + ++ msg = peci_get_xfer_msg(PECI_WRPCICFGLOCAL_WRITE_LEN_BASE + ++ umsg->tx_len, PECI_WRPCICFGLOCAL_READ_LEN); ++ if (!msg) ++ return -ENOMEM; ++ + address = umsg->reg; /* [11:0] - Register */ + address |= (u32)umsg->function << 12; /* [14:12] - Function */ + address |= (u32)umsg->device << 15; /* [19:15] - Device */ + address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ + +- msg.addr = umsg->addr; +- msg.tx_len = WRPCICFGLOCAL_WRITE_LEN_BASE + umsg->tx_len; +- msg.rx_len = WRPCICFGLOCAL_READ_LEN; +- msg.tx_buf[0] = WRPCICFGLOCAL_PECI_CMD; +- msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ +- /* Host ID is 0 for PECI 3.0 */ +- msg.tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ +- msg.tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ +- msg.tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_WRPCICFGLOCAL_CMD; ++ msg->tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ ++ /* Host ID is 0 for PECI 3.0 */ ++ msg->tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ ++ msg->tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ ++ msg->tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ + for (i = 0; i < umsg->tx_len; i++) +- msg.tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); ++ msg->tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); ++ ++ /* Add an Assured Write Frame Check Sequence byte */ ++ ret = peci_aw_fcs(msg, 8 + umsg->tx_len, &aw_fcs); ++ if (ret) ++ goto out; ++ ++ msg->tx_buf[5 + i] = 0x80 ^ aw_fcs; + +- /* Add an Assure Write Frame Check Sequence byte */ +- msg.tx_buf[5 + i] = 0x80 ^ +- peci_aw_fcs((u8 *)&msg, 8 + umsg->tx_len); ++ ret = peci_xfer_with_retries(adapter, msg, true); + +- rc = peci_xfer_with_retries(adapter, &msg, true); ++out: ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); + +- return rc; ++ return ret; + } + +-typedef int (*peci_ioctl_fn_type)(struct peci_adapter *, void *); +- +-static const peci_ioctl_fn_type peci_ioctl_fn[PECI_CMD_MAX] = { +- peci_ioctl_xfer, +- peci_ioctl_ping, +- peci_ioctl_get_dib, +- peci_ioctl_get_temp, +- peci_ioctl_rd_pkg_cfg, +- peci_ioctl_wr_pkg_cfg, +- peci_ioctl_rd_ia_msr, +- NULL, /* Reserved */ +- peci_ioctl_rd_pci_cfg, +- NULL, /* Reserved */ +- peci_ioctl_rd_pci_cfg_local, +- peci_ioctl_wr_pci_cfg_local, ++typedef int (*peci_cmd_fn_type)(struct peci_adapter *, void *); ++ ++static const peci_cmd_fn_type peci_cmd_fn[PECI_CMD_MAX] = { ++ peci_cmd_xfer, ++ peci_cmd_ping, ++ peci_cmd_get_dib, ++ peci_cmd_get_temp, ++ peci_cmd_rd_pkg_cfg, ++ peci_cmd_wr_pkg_cfg, ++ peci_cmd_rd_ia_msr, ++ peci_cmd_wr_ia_msr, ++ peci_cmd_rd_pci_cfg, ++ peci_cmd_wr_pci_cfg, ++ peci_cmd_rd_pci_cfg_local, ++ peci_cmd_wr_pci_cfg_local, + }; + + /** +@@ -545,109 +717,28 @@ static const peci_ioctl_fn_type peci_ioctl_fn[PECI_CMD_MAX] = { + */ + int peci_command(struct peci_adapter *adapter, enum peci_cmd cmd, void *vmsg) + { +- int rc = 0; ++ int ret; + + if (cmd >= PECI_CMD_MAX || cmd < PECI_CMD_XFER) +- return -EINVAL; ++ return -ENOTTY; + + dev_dbg(&adapter->dev, "%s, cmd=0x%02x\n", __func__, cmd); + +- if (!peci_ioctl_fn[cmd]) ++ if (!peci_cmd_fn[cmd]) + return -EINVAL; + +- rt_mutex_lock(&adapter->bus_lock); ++ mutex_lock(&adapter->bus_lock); + +- rc = peci_cmd_support(adapter, cmd); +- if (!rc) +- rc = peci_ioctl_fn[cmd](adapter, vmsg); ++ ret = peci_check_cmd_support(adapter, cmd); ++ if (!ret) ++ ret = peci_cmd_fn[cmd](adapter, vmsg); + +- rt_mutex_unlock(&adapter->bus_lock); ++ mutex_unlock(&adapter->bus_lock); + +- return rc; ++ return ret; + } + EXPORT_SYMBOL_GPL(peci_command); + +-static long peci_ioctl(struct file *file, unsigned int iocmd, unsigned long arg) +-{ +- struct peci_adapter *adapter = file->private_data; +- void __user *argp = (void __user *)arg; +- unsigned int msg_len; +- enum peci_cmd cmd; +- int rc = 0; +- u8 *msg; +- +- if (!capable(CAP_SYS_ADMIN)) +- return -EPERM; +- +- dev_dbg(&adapter->dev, "ioctl, cmd=0x%x, arg=0x%lx\n", iocmd, arg); +- +- switch (iocmd) { +- case PECI_IOC_XFER: +- case PECI_IOC_PING: +- case PECI_IOC_GET_DIB: +- case PECI_IOC_GET_TEMP: +- case PECI_IOC_RD_PKG_CFG: +- case PECI_IOC_WR_PKG_CFG: +- case PECI_IOC_RD_IA_MSR: +- case PECI_IOC_RD_PCI_CFG: +- case PECI_IOC_RD_PCI_CFG_LOCAL: +- case PECI_IOC_WR_PCI_CFG_LOCAL: +- cmd = _IOC_NR(iocmd); +- msg_len = _IOC_SIZE(iocmd); +- break; +- +- default: +- dev_dbg(&adapter->dev, "Invalid ioctl cmd : 0x%x\n", iocmd); +- return -ENOTTY; +- } +- +- if (!access_ok(argp, msg_len)) +- return -EFAULT; +- +- msg = memdup_user(argp, msg_len); +- if (IS_ERR(msg)) +- return PTR_ERR(msg); +- +- rc = peci_command(adapter, cmd, msg); +- +- if (!rc && copy_to_user(argp, msg, msg_len)) +- rc = -EFAULT; +- +- kfree(msg); +- return (long)rc; +-} +- +-static int peci_open(struct inode *inode, struct file *file) +-{ +- unsigned int minor = iminor(inode); +- struct peci_adapter *adapter; +- +- adapter = peci_get_adapter(minor); +- if (!adapter) +- return -ENODEV; +- +- file->private_data = adapter; +- +- return 0; +-} +- +-static int peci_release(struct inode *inode, struct file *file) +-{ +- struct peci_adapter *adapter = file->private_data; +- +- peci_put_adapter(adapter); +- file->private_data = NULL; +- +- return 0; +-} +- +-static const struct file_operations peci_fops = { +- .owner = THIS_MODULE, +- .unlocked_ioctl = peci_ioctl, +- .open = peci_open, +- .release = peci_release, +-}; +- + static int peci_detect(struct peci_adapter *adapter, u8 addr) + { + struct peci_ping_msg msg; +@@ -666,9 +757,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 * +@@ -737,6 +828,7 @@ static int peci_device_probe(struct device *dev) + + err_detach_pm_domain: + dev_pm_domain_detach(&client->dev, true); ++ + return status; + } + +@@ -775,13 +867,14 @@ static void peci_device_shutdown(struct device *dev) + driver->shutdown(client); + } + +-static struct bus_type peci_bus_type = { ++struct bus_type peci_bus_type = { + .name = "peci", + .match = peci_device_match, + .probe = peci_device_probe, + .remove = peci_device_remove, + .shutdown = peci_device_shutdown, + }; ++EXPORT_SYMBOL_GPL(peci_bus_type); + + static int peci_check_addr_validity(u8 addr) + { +@@ -814,18 +907,22 @@ static int peci_check_client_busy(struct device *dev, void *client_new_p) + int peci_get_cpu_id(struct peci_adapter *adapter, u8 addr, u32 *cpu_id) + { + struct peci_rd_pkg_cfg_msg msg; +- int rc; ++ int ret; + + msg.addr = addr; +- msg.index = MBX_INDEX_CPU_ID; +- msg.param = PKG_ID_CPU_ID; ++ msg.index = PECI_MBX_INDEX_CPU_ID; ++ msg.param = PECI_PKG_ID_CPU_ID; + msg.rx_len = 4; + +- rc = peci_command(adapter, PECI_CMD_RD_PKG_CFG, &msg); +- if (!rc) +- *cpu_id = le32_to_cpup((__le32 *)msg.pkg_config); ++ ret = peci_command(adapter, PECI_CMD_RD_PKG_CFG, &msg); ++ if (msg.cc != PECI_DEV_CC_SUCCESS) ++ ret = -EAGAIN; ++ if (ret) ++ return ret; ++ ++ *cpu_id = le32_to_cpup((__le32 *)msg.pkg_config); + +- return rc; ++ return 0; + } + EXPORT_SYMBOL_GPL(peci_get_cpu_id); + +@@ -833,7 +930,7 @@ static struct peci_client *peci_new_device(struct peci_adapter *adapter, + struct peci_board_info const *info) + { + struct peci_client *client; +- int rc; ++ int ret; + + /* Increase reference count for the adapter assigned */ + if (!peci_get_adapter(adapter->nr)) +@@ -847,46 +944,49 @@ static struct peci_client *peci_new_device(struct peci_adapter *adapter, + client->addr = info->addr; + strlcpy(client->name, info->type, sizeof(client->name)); + +- rc = peci_check_addr_validity(client->addr); +- if (rc) { ++ ret = peci_check_addr_validity(client->addr); ++ if (ret) { + dev_err(&adapter->dev, "Invalid PECI CPU address 0x%02hx\n", + client->addr); + goto err_free_client_silent; + } + + /* Check online status of client */ +- rc = peci_detect(adapter, client->addr); +- if (rc) ++ ret = peci_detect(adapter, client->addr); ++ if (ret) + goto err_free_client; + +- rc = device_for_each_child(&adapter->dev, client, +- peci_check_client_busy); +- if (rc) ++ ret = device_for_each_child(&adapter->dev, client, ++ peci_check_client_busy); ++ if (ret) + goto err_free_client; + + client->dev.parent = &client->adapter->dev; + client->dev.bus = &peci_bus_type; + client->dev.type = &peci_client_type; +- client->dev.of_node = info->of_node; ++ client->dev.of_node = of_node_get(info->of_node); + dev_set_name(&client->dev, "%d-%02x", adapter->nr, client->addr); + +- rc = device_register(&client->dev); +- if (rc) +- goto err_free_client; ++ ret = device_register(&client->dev); ++ if (ret) ++ goto err_put_of_node; + + dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n", + client->name, dev_name(&client->dev)); + + return client; + ++err_put_of_node: ++ of_node_put(info->of_node); + err_free_client: + dev_err(&adapter->dev, + "Failed to register peci client %s at 0x%02x (%d)\n", +- client->name, client->addr, rc); ++ client->name, client->addr, ret); + err_free_client_silent: + kfree(client); + err_put_adapter: + peci_put_adapter(adapter); ++ + return NULL; + } + +@@ -895,8 +995,10 @@ static void peci_unregister_device(struct peci_client *client) + if (!client) + return; + +- if (client->dev.of_node) ++ if (client->dev.of_node) { + of_node_clear_flag(client->dev.of_node, OF_POPULATED); ++ of_node_put(client->dev.of_node); ++ } + + device_unregister(&client->dev); + } +@@ -916,7 +1018,7 @@ static void peci_adapter_dev_release(struct device *dev) + + dev_dbg(dev, "%s: %s\n", __func__, adapter->name); + mutex_destroy(&adapter->userspace_clients_lock); +- rt_mutex_destroy(&adapter->bus_lock); ++ mutex_destroy(&adapter->bus_lock); + kfree(adapter); + } + +@@ -928,7 +1030,8 @@ static ssize_t peci_sysfs_new_device(struct device *dev, + struct peci_board_info info = {}; + struct peci_client *client; + char *blank, end; +- int rc; ++ short addr; ++ int ret; + + /* Parse device type */ + blank = strchr(buf, ' '); +@@ -943,16 +1046,17 @@ static ssize_t peci_sysfs_new_device(struct device *dev, + memcpy(info.type, buf, blank - buf); + + /* Parse remaining parameters, reject extra parameters */ +- rc = sscanf(++blank, "%hi%c", &info.addr, &end); +- if (rc < 1) { ++ ret = sscanf(++blank, "%hi%c", &addr, &end); ++ if (ret < 1) { + dev_err(dev, "%s: Can't parse client address\n", "new_device"); + return -EINVAL; + } +- if (rc > 1 && end != '\n') { ++ if (ret > 1 && end != '\n') { + dev_err(dev, "%s: Extra parameters\n", "new_device"); + return -EINVAL; + } + ++ info.addr = (u8)addr; + client = peci_new_device(adapter, &info); + if (!client) + return -EINVAL; +@@ -961,8 +1065,8 @@ static ssize_t peci_sysfs_new_device(struct device *dev, + mutex_lock(&adapter->userspace_clients_lock); + list_add_tail(&client->detected, &adapter->userspace_clients); + mutex_unlock(&adapter->userspace_clients_lock); +- dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device", +- info.type, info.addr); ++ dev_dbg(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device", ++ info.type, info.addr); + + return count; + } +@@ -975,9 +1079,9 @@ static ssize_t peci_sysfs_delete_device(struct device *dev, + struct peci_adapter *adapter = to_peci_adapter(dev); + struct peci_client *client, *next; + struct peci_board_info info = {}; +- struct peci_driver *driver; + char *blank, end; +- int rc; ++ short addr; ++ int ret; + + /* Parse device type */ + blank = strchr(buf, ' '); +@@ -992,41 +1096,41 @@ static ssize_t peci_sysfs_delete_device(struct device *dev, + memcpy(info.type, buf, blank - buf); + + /* Parse remaining parameters, reject extra parameters */ +- rc = sscanf(++blank, "%hi%c", &info.addr, &end); +- if (rc < 1) { ++ ret = sscanf(++blank, "%hi%c", &addr, &end); ++ if (ret < 1) { + dev_err(dev, "%s: Can't parse client address\n", + "delete_device"); + return -EINVAL; + } +- if (rc > 1 && end != '\n') { ++ if (ret > 1 && end != '\n') { + dev_err(dev, "%s: Extra parameters\n", "delete_device"); + return -EINVAL; + } + ++ info.addr = (u8)addr; ++ + /* Make sure the device was added through sysfs */ +- rc = -ENOENT; ++ ret = -ENOENT; + mutex_lock(&adapter->userspace_clients_lock); + list_for_each_entry_safe(client, next, &adapter->userspace_clients, + detected) { +- driver = to_peci_driver(client->dev.driver); +- + if (client->addr == info.addr && + !strncmp(client->name, info.type, PECI_NAME_SIZE)) { +- dev_info(dev, "%s: Deleting device %s at 0x%02hx\n", +- "delete_device", client->name, client->addr); ++ dev_dbg(dev, "%s: Deleting device %s at 0x%02hx\n", ++ "delete_device", client->name, client->addr); + list_del(&client->detected); + peci_unregister_device(client); +- rc = count; ++ ret = count; + break; + } + } + mutex_unlock(&adapter->userspace_clients_lock); + +- if (rc < 0) +- dev_err(dev, "%s: Can't find device in list\n", ++ if (ret < 0) ++ dev_dbg(dev, "%s: Can't find device in list\n", + "delete_device"); + +- return rc; ++ return ret; + } + static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL, + peci_sysfs_delete_device); +@@ -1039,10 +1143,11 @@ static struct attribute *peci_adapter_attrs[] = { + }; + ATTRIBUTE_GROUPS(peci_adapter); + +-static struct device_type peci_adapter_type = { ++struct device_type peci_adapter_type = { + .groups = peci_adapter_groups, + .release = peci_adapter_dev_release, + }; ++EXPORT_SYMBOL_GPL(peci_adapter_type); + + /** + * peci_verify_adapter - return parameter as peci_adapter, or NULL +@@ -1063,32 +1168,26 @@ static struct peci_client *peci_of_register_device(struct peci_adapter *adapter, + struct device_node *node) + { + struct peci_board_info info = {}; +- struct peci_client *result; +- const __be32 *addr_be; +- int len; ++ struct peci_client *client; ++ u32 addr; ++ int ret; + + dev_dbg(&adapter->dev, "register %pOF\n", node); + +- if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) { +- dev_err(&adapter->dev, "modalias failure on %pOF\n", node); +- return ERR_PTR(-EINVAL); +- } +- +- addr_be = of_get_property(node, "reg", &len); +- if (!addr_be || len < sizeof(*addr_be)) { ++ ret = of_property_read_u32(node, "reg", &addr); ++ if (ret) { + dev_err(&adapter->dev, "invalid reg on %pOF\n", node); +- return ERR_PTR(-EINVAL); ++ return ERR_PTR(ret); + } + +- info.addr = be32_to_cpup(addr_be); +- info.of_node = of_node_get(node); ++ info.addr = addr; ++ info.of_node = node; + +- result = peci_new_device(adapter, &info); +- if (!result) +- result = ERR_PTR(-EINVAL); ++ client = peci_new_device(adapter, &info); ++ if (!client) ++ client = ERR_PTR(-EINVAL); + +- of_node_put(node); +- return result; ++ return client; + } + + static void peci_of_register_devices(struct peci_adapter *adapter) +@@ -1119,7 +1218,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 */ + +@@ -1163,9 +1262,7 @@ static struct peci_adapter *peci_of_find_adapter(struct device_node *node) + return adapter; + } + +-static int peci_of_notify(struct notifier_block *nb, +- unsigned long action, +- void *arg) ++static int peci_of_notify(struct notifier_block *nb, ulong action, void *arg) + { + struct of_reconfig_data *rd = arg; + struct peci_adapter *adapter; +@@ -1216,7 +1313,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 */ + +@@ -1240,7 +1337,7 @@ extern struct notifier_block peci_of_notifier; + * + * Return: the peci_adapter structure on success, else NULL. + */ +-struct peci_adapter *peci_alloc_adapter(struct device *dev, unsigned int size) ++struct peci_adapter *peci_alloc_adapter(struct device *dev, uint size) + { + struct peci_adapter *adapter; + +@@ -1263,7 +1360,7 @@ EXPORT_SYMBOL_GPL(peci_alloc_adapter); + + static int peci_register_adapter(struct peci_adapter *adapter) + { +- int rc = -EINVAL; ++ int ret = -EINVAL; + + /* Can't register until after driver model init */ + if (WARN_ON(!is_registered)) +@@ -1275,27 +1372,17 @@ static int peci_register_adapter(struct peci_adapter *adapter) + if (WARN(!adapter->xfer, "peci adapter has no xfer function\n")) + goto err_free_idr; + +- rt_mutex_init(&adapter->bus_lock); ++ mutex_init(&adapter->bus_lock); + mutex_init(&adapter->userspace_clients_lock); + INIT_LIST_HEAD(&adapter->userspace_clients); + + dev_set_name(&adapter->dev, "peci-%d", adapter->nr); + +- /* cdev */ +- cdev_init(&adapter->cdev, &peci_fops); +- adapter->cdev.owner = THIS_MODULE; +- adapter->dev.devt = MKDEV(MAJOR(peci_devt), adapter->nr); +- rc = cdev_add(&adapter->cdev, adapter->dev.devt, 1); +- if (rc) { +- pr_err("adapter '%s': can't add cdev (%d)\n", +- adapter->name, rc); +- goto err_free_idr; +- } +- rc = device_add(&adapter->dev); +- if (rc) { ++ ret = device_add(&adapter->dev); ++ if (ret) { + pr_err("adapter '%s': can't add device (%d)\n", +- adapter->name, rc); +- goto err_del_cdev; ++ adapter->name, ret); ++ goto err_free_idr; + } + + dev_dbg(&adapter->dev, "adapter [%s] registered\n", adapter->name); +@@ -1309,13 +1396,11 @@ static int peci_register_adapter(struct peci_adapter *adapter) + + return 0; + +-err_del_cdev: +- cdev_del(&adapter->cdev); + err_free_idr: + mutex_lock(&core_lock); + idr_remove(&peci_adapter_idr, adapter->nr); + mutex_unlock(&core_lock); +- return rc; ++ return ret; + } + + static int peci_add_numbered_adapter(struct peci_adapter *adapter) +@@ -1354,12 +1439,10 @@ int peci_add_adapter(struct peci_adapter *adapter) + struct device *dev = &adapter->dev; + int id; + +- if (dev->of_node) { +- id = of_alias_get_id(dev->of_node, "peci"); +- if (id >= 0) { +- adapter->nr = id; +- return peci_add_numbered_adapter(adapter); +- } ++ id = of_alias_get_id(dev->of_node, "peci"); ++ if (id >= 0) { ++ adapter->nr = id; ++ return peci_add_numbered_adapter(adapter); + } + + mutex_lock(&core_lock); +@@ -1411,7 +1494,7 @@ void peci_del_adapter(struct peci_adapter *adapter) + } + mutex_unlock(&adapter->userspace_clients_lock); + +- /** ++ /* + * Detach any active clients. This can't fail, thus we do not + * check the returned value. + */ +@@ -1420,13 +1503,8 @@ void peci_del_adapter(struct peci_adapter *adapter) + /* device name is gone after device_unregister */ + dev_dbg(&adapter->dev, "adapter [%s] unregistered\n", adapter->name); + +- /* free cdev */ +- cdev_del(&adapter->cdev); +- + pm_runtime_disable(&adapter->dev); +- + nr = adapter->nr; +- + device_unregister(&adapter->dev); + + /* free bus id */ +@@ -1436,6 +1514,18 @@ void peci_del_adapter(struct peci_adapter *adapter) + } + EXPORT_SYMBOL_GPL(peci_del_adapter); + ++int peci_for_each_dev(void *data, int (*fn)(struct device *, void *)) ++{ ++ int ret; ++ ++ mutex_lock(&core_lock); ++ ret = bus_for_each_dev(&peci_bus_type, NULL, data, fn); ++ mutex_unlock(&core_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(peci_for_each_dev); ++ + /** + * peci_register_driver - register a PECI driver + * @owner: owner module of the driver being registered +@@ -1446,7 +1536,7 @@ EXPORT_SYMBOL_GPL(peci_del_adapter); + */ + int peci_register_driver(struct module *owner, struct peci_driver *driver) + { +- int rc; ++ int ret; + + /* Can't register until after driver model init */ + if (WARN_ON(!is_registered)) +@@ -1456,13 +1546,13 @@ int peci_register_driver(struct module *owner, struct peci_driver *driver) + driver->driver.owner = owner; + driver->driver.bus = &peci_bus_type; + +- /** ++ /* + * When registration returns, the driver core + * will have called probe() for all matching-but-unbound devices. + */ +- rc = driver_register(&driver->driver); +- if (rc) +- return rc; ++ ret = driver_register(&driver->driver); ++ if (ret) ++ return ret; + + pr_debug("driver [%s] registered\n", driver->driver.name); + +@@ -1492,13 +1582,6 @@ static int __init peci_init(void) + return ret; + } + +- ret = alloc_chrdev_region(&peci_devt, 0, PECI_CDEV_MAX, "peci"); +- if (ret < 0) { +- pr_err("peci: Failed to allocate chr dev region!\n"); +- bus_unregister(&peci_bus_type); +- return ret; +- } +- + crc8_populate_msb(peci_crc8_table, PECI_CRC8_POLYNOMIAL); + + if (IS_ENABLED(CONFIG_OF_DYNAMIC)) +@@ -1514,11 +1597,10 @@ static void __exit peci_exit(void) + if (IS_ENABLED(CONFIG_OF_DYNAMIC)) + WARN_ON(of_reconfig_notifier_unregister(&peci_of_notifier)); + +- unregister_chrdev_region(peci_devt, PECI_CDEV_MAX); + bus_unregister(&peci_bus_type); + } + +-postcore_initcall(peci_init); ++subsys_initcall(peci_init); + module_exit(peci_exit); + + MODULE_AUTHOR("Jason M Biils <jason.m.bills@linux.intel.com>"); +diff --git a/drivers/peci/peci-dev.c b/drivers/peci/peci-dev.c +new file mode 100644 +index 000000000000..ac9cba0fb429 +--- /dev/null ++++ b/drivers/peci/peci-dev.c +@@ -0,0 +1,346 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2018-2019 Intel Corporation ++ ++#include <linux/cdev.h> ++#include <linux/fs.h> ++#include <linux/list.h> ++#include <linux/module.h> ++#include <linux/notifier.h> ++#include <linux/peci.h> ++#include <linux/slab.h> ++#include <linux/uaccess.h> ++ ++/* ++ * A peci_dev represents an peci_adapter ... an PECI or SMBus master, not a ++ * slave (peci_client) with which messages will be exchanged. It's coupled ++ * with a character special file which is accessed by user mode drivers. ++ * ++ * The list of peci_dev structures is parallel to the peci_adapter lists ++ * maintained by the driver model, and is updated using bus notifications. ++ */ ++struct peci_dev { ++ struct list_head list; ++ struct peci_adapter *adapter; ++ struct device *dev; ++ struct cdev cdev; ++}; ++ ++#define PECI_MINORS MINORMASK ++ ++static dev_t peci_devt; ++static LIST_HEAD(peci_dev_list); ++static DEFINE_SPINLOCK(peci_dev_list_lock); ++ ++static struct peci_dev *peci_dev_get_by_minor(uint index) ++{ ++ struct peci_dev *peci_dev; ++ ++ spin_lock(&peci_dev_list_lock); ++ list_for_each_entry(peci_dev, &peci_dev_list, list) { ++ if (peci_dev->adapter->nr == index) ++ goto found; ++ } ++ peci_dev = NULL; ++found: ++ spin_unlock(&peci_dev_list_lock); ++ ++ return peci_dev; ++} ++ ++static struct peci_dev *peci_dev_alloc(struct peci_adapter *adapter) ++{ ++ struct peci_dev *peci_dev; ++ ++ if (adapter->nr >= PECI_MINORS) { ++ printk(KERN_ERR "peci-dev: Out of device minors (%d)\n", ++ adapter->nr); ++ return ERR_PTR(-ENODEV); ++ } ++ ++ peci_dev = kzalloc(sizeof(*peci_dev), GFP_KERNEL); ++ if (!peci_dev) ++ return ERR_PTR(-ENOMEM); ++ peci_dev->adapter = adapter; ++ ++ spin_lock(&peci_dev_list_lock); ++ list_add_tail(&peci_dev->list, &peci_dev_list); ++ spin_unlock(&peci_dev_list_lock); ++ ++ return peci_dev; ++} ++ ++static void peci_dev_put(struct peci_dev *peci_dev) ++{ ++ spin_lock(&peci_dev_list_lock); ++ list_del(&peci_dev->list); ++ spin_unlock(&peci_dev_list_lock); ++ kfree(peci_dev); ++} ++ ++static ssize_t name_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct peci_dev *peci_dev = peci_dev_get_by_minor(MINOR(dev->devt)); ++ ++ if (!peci_dev) ++ return -ENODEV; ++ ++ return sprintf(buf, "%s\n", peci_dev->adapter->name); ++} ++static DEVICE_ATTR_RO(name); ++ ++static struct attribute *peci_dev_attrs[] = { ++ &dev_attr_name.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(peci_dev); ++ ++static long peci_dev_ioctl(struct file *file, uint iocmd, ulong arg) ++{ ++ struct peci_dev *peci_dev = file->private_data; ++ void __user *umsg = (void __user *)arg; ++ struct peci_xfer_msg *xmsg = NULL; ++ struct peci_xfer_msg uxmsg; ++ enum peci_cmd cmd; ++ u8 *msg = NULL; ++ uint msg_len; ++ int ret; ++ ++ cmd = _IOC_NR(iocmd); ++ msg_len = _IOC_SIZE(iocmd); ++ ++ switch (cmd) { ++ case PECI_CMD_XFER: ++ if (msg_len != sizeof(struct peci_xfer_msg)) { ++ ret = -EFAULT; ++ break; ++ } ++ ++ if (copy_from_user(&uxmsg, umsg, msg_len)) { ++ ret = -EFAULT; ++ break; ++ } ++ ++ xmsg = peci_get_xfer_msg(uxmsg.tx_len, uxmsg.rx_len); ++ if (IS_ERR(xmsg)) { ++ ret = PTR_ERR(xmsg); ++ break; ++ } ++ ++ if (uxmsg.tx_len && ++ copy_from_user(xmsg->tx_buf, uxmsg.tx_buf, uxmsg.tx_len)) { ++ ret = -EFAULT; ++ break; ++ } ++ ++ xmsg->addr = uxmsg.addr; ++ xmsg->tx_len = uxmsg.tx_len; ++ xmsg->rx_len = uxmsg.rx_len; ++ ++ ret = peci_command(peci_dev->adapter, cmd, xmsg); ++ if (!ret && xmsg->rx_len && ++ copy_to_user(uxmsg.rx_buf, xmsg->rx_buf, xmsg->rx_len)) ++ ret = -EFAULT; ++ ++ break; ++ ++ default: ++ msg = memdup_user(umsg, msg_len); ++ if (IS_ERR(msg)) { ++ ret = PTR_ERR(msg); ++ break; ++ } ++ ++ ret = peci_command(peci_dev->adapter, cmd, msg); ++ if ((!ret || ret == -ETIMEDOUT) && ++ copy_to_user(umsg, msg, msg_len)) ++ ret = -EFAULT; ++ ++ break; ++ } ++ ++ peci_put_xfer_msg(xmsg); ++ kfree(msg); ++ ++ return (long)ret; ++} ++ ++static int peci_dev_open(struct inode *inode, struct file *file) ++{ ++ struct peci_adapter *adapter; ++ struct peci_dev *peci_dev; ++ ++ peci_dev = peci_dev_get_by_minor(iminor(inode)); ++ if (!peci_dev) ++ return -ENODEV; ++ ++ adapter = peci_get_adapter(peci_dev->adapter->nr); ++ if (!adapter) ++ return -ENODEV; ++ ++ file->private_data = peci_dev; ++ ++ return 0; ++} ++ ++static int peci_dev_release(struct inode *inode, struct file *file) ++{ ++ struct peci_dev *peci_dev = file->private_data; ++ ++ peci_put_adapter(peci_dev->adapter); ++ file->private_data = NULL; ++ ++ return 0; ++} ++ ++static const struct file_operations peci_dev_fops = { ++ .owner = THIS_MODULE, ++ .unlocked_ioctl = peci_dev_ioctl, ++ .open = peci_dev_open, ++ .release = peci_dev_release, ++ .llseek = no_llseek, ++}; ++ ++static struct class *peci_dev_class; ++ ++static int peci_dev_attach_adapter(struct device *dev, void *dummy) ++{ ++ struct peci_adapter *adapter; ++ struct peci_dev *peci_dev; ++ dev_t devt; ++ int ret; ++ ++ if (dev->type != &peci_adapter_type) ++ return 0; ++ ++ adapter = to_peci_adapter(dev); ++ peci_dev = peci_dev_alloc(adapter); ++ if (IS_ERR(peci_dev)) ++ return PTR_ERR(peci_dev); ++ ++ cdev_init(&peci_dev->cdev, &peci_dev_fops); ++ peci_dev->cdev.owner = THIS_MODULE; ++ devt = MKDEV(MAJOR(peci_devt), adapter->nr); ++ ++ ret = cdev_add(&peci_dev->cdev, devt, 1); ++ if (ret) ++ goto err_put_dev; ++ ++ /* register this peci device with the driver core */ ++ peci_dev->dev = device_create(peci_dev_class, &adapter->dev, devt, NULL, ++ "peci-%d", adapter->nr); ++ if (IS_ERR(peci_dev->dev)) { ++ ret = PTR_ERR(peci_dev->dev); ++ goto err_del_cdev; ++ } ++ ++ pr_info("peci-dev: adapter [%s] registered as minor %d\n", ++ adapter->name, adapter->nr); ++ ++ return 0; ++ ++err_del_cdev: ++ cdev_del(&peci_dev->cdev); ++err_put_dev: ++ peci_dev_put(peci_dev); ++ ++ return ret; ++} ++ ++static int peci_dev_detach_adapter(struct device *dev, void *dummy) ++{ ++ struct peci_adapter *adapter; ++ struct peci_dev *peci_dev; ++ dev_t devt; ++ ++ if (dev->type != &peci_adapter_type) ++ return 0; ++ ++ adapter = to_peci_adapter(dev); ++ peci_dev = peci_dev_get_by_minor(adapter->nr); ++ if (!peci_dev) ++ return 0; ++ ++ cdev_del(&peci_dev->cdev); ++ devt = peci_dev->dev->devt; ++ peci_dev_put(peci_dev); ++ device_destroy(peci_dev_class, devt); ++ ++ pr_info("peci-dev: adapter [%s] unregistered\n", adapter->name); ++ ++ return 0; ++} ++ ++static int peci_dev_notifier_call(struct notifier_block *nb, ulong action, ++ void *data) ++{ ++ struct device *dev = data; ++ ++ switch (action) { ++ case BUS_NOTIFY_ADD_DEVICE: ++ return peci_dev_attach_adapter(dev, NULL); ++ case BUS_NOTIFY_DEL_DEVICE: ++ return peci_dev_detach_adapter(dev, NULL); ++ } ++ ++ return 0; ++} ++ ++static struct notifier_block peci_dev_notifier = { ++ .notifier_call = peci_dev_notifier_call, ++}; ++ ++static int __init peci_dev_init(void) ++{ ++ int ret; ++ ++ printk(KERN_INFO "peci /dev entries driver\n"); ++ ++ ret = alloc_chrdev_region(&peci_devt, 0, PECI_MINORS, "peci"); ++ if (ret < 0) { ++ pr_err("peci: Failed to allocate chr dev region!\n"); ++ bus_unregister(&peci_bus_type); ++ goto err; ++ } ++ ++ peci_dev_class = class_create(THIS_MODULE, "peci-dev"); ++ if (IS_ERR(peci_dev_class)) { ++ ret = PTR_ERR(peci_dev_class); ++ goto err_unreg_chrdev; ++ } ++ peci_dev_class->dev_groups = peci_dev_groups; ++ ++ /* Keep track of adapters which will be added or removed later */ ++ ret = bus_register_notifier(&peci_bus_type, &peci_dev_notifier); ++ if (ret) ++ goto err_destroy_class; ++ ++ /* Bind to already existing adapters right away */ ++ peci_for_each_dev(NULL, peci_dev_attach_adapter); ++ ++ return 0; ++ ++err_destroy_class: ++ class_destroy(peci_dev_class); ++err_unreg_chrdev: ++ unregister_chrdev_region(peci_devt, PECI_MINORS); ++err: ++ printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__); ++ ++ return ret; ++} ++ ++static void __exit peci_dev_exit(void) ++{ ++ bus_unregister_notifier(&peci_bus_type, &peci_dev_notifier); ++ peci_for_each_dev(NULL, peci_dev_detach_adapter); ++ class_destroy(peci_dev_class); ++ unregister_chrdev_region(peci_devt, PECI_MINORS); ++} ++ ++module_init(peci_dev_init); ++module_exit(peci_dev_exit); ++ ++MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); ++MODULE_DESCRIPTION("PECI /dev entries driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/peci/peci-npcm.c b/drivers/peci/peci-npcm.c +deleted file mode 100644 +index f632365b1416..000000000000 +--- a/drivers/peci/peci-npcm.c ++++ /dev/null +@@ -1,410 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-// Copyright (c) 2019 Nuvoton Technology corporation. +- +-#include <linux/bitfield.h> +-#include <linux/clk.h> +-#include <linux/interrupt.h> +-#include <linux/jiffies.h> +-#include <linux/module.h> +-#include <linux/of.h> +-#include <linux/peci.h> +-#include <linux/platform_device.h> +-#include <linux/regmap.h> +-#include <linux/mfd/syscon.h> +-#include <linux/reset.h> +- +-/* NPCM7xx GCR module */ +-#define NPCM7XX_INTCR3_OFFSET 0x9C +-#define NPCM7XX_INTCR3_PECIVSEL BIT(19) +- +-/* NPCM PECI Registers */ +-#define NPCM_PECI_CTL_STS 0x00 +-#define NPCM_PECI_RD_LENGTH 0x04 +-#define NPCM_PECI_ADDR 0x08 +-#define NPCM_PECI_CMD 0x0C +-#define NPCM_PECI_CTL2 0x10 +-#define NPCM_PECI_WR_LENGTH 0x1C +-#define NPCM_PECI_PDDR 0x2C +-#define NPCM_PECI_DAT_INOUT(n) (0x100 + ((n) * 4)) +- +-#define NPCM_PECI_MAX_REG 0x200 +- +-/* NPCM_PECI_CTL_STS - 0x00 : Control Register */ +-#define NPCM_PECI_CTRL_DONE_INT_EN BIT(6) +-#define NPCM_PECI_CTRL_ABRT_ERR BIT(4) +-#define NPCM_PECI_CTRL_CRC_ERR BIT(3) +-#define NPCM_PECI_CTRL_DONE BIT(1) +-#define NPCM_PECI_CTRL_START_BUSY BIT(0) +- +-/* NPCM_PECI_RD_LENGTH - 0x04 : Command Register */ +-#define NPCM_PECI_RD_LEN_MASK GENMASK(6, 0) +- +-/* NPCM_PECI_CMD - 0x10 : Command Register */ +-#define NPCM_PECI_CTL2_MASK GENMASK(7, 6) +- +-/* NPCM_PECI_WR_LENGTH - 0x1C : Command Register */ +-#define NPCM_PECI_WR_LEN_MASK GENMASK(6, 0) +- +-/* NPCM_PECI_PDDR - 0x2C : Command Register */ +-#define NPCM_PECI_PDDR_MASK GENMASK(4, 0) +- +-#define NPCM_PECI_INT_MASK (NPCM_PECI_CTRL_ABRT_ERR | \ +- NPCM_PECI_CTRL_CRC_ERR | \ +- NPCM_PECI_CTRL_DONE) +- +-#define NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC 50000 +-#define NPCM_PECI_IDLE_CHECK_INTERVAL_USEC 10000 +-#define NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 +-#define NPCM_PECI_CMD_TIMEOUT_MS_MAX 60000 +-#define NPCM_PECI_HOST_NEG_BIT_RATE_MAX 31 +-#define NPCM_PECI_HOST_NEG_BIT_RATE_MIN 7 +-#define NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT 15 +-#define NPCM_PECI_PULL_DOWN_DEFAULT 0 +-#define NPCM_PECI_PULL_DOWN_MAX 2 +- +-struct npcm_peci { +- u32 cmd_timeout_ms; +- u32 host_bit_rate; +- struct completion xfer_complete; +- struct regmap *gcr_regmap; +- struct peci_adapter *adapter; +- struct regmap *regmap; +- u32 status; +- spinlock_t lock; /* to sync completion status handling */ +- struct device *dev; +- struct clk *clk; +- int irq; +-}; +- +-static int npcm_peci_xfer_native(struct npcm_peci *priv, +- struct peci_xfer_msg *msg) +-{ +- long err, timeout = msecs_to_jiffies(priv->cmd_timeout_ms); +- unsigned long flags; +- unsigned int msg_rd; +- u32 cmd_sts; +- int i, rc; +- +- /* Check command sts and bus idle state */ +- rc = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, +- !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), +- NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, +- NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); +- if (rc) +- return rc; /* -ETIMEDOUT */ +- +- spin_lock_irqsave(&priv->lock, flags); +- reinit_completion(&priv->xfer_complete); +- +- regmap_write(priv->regmap, NPCM_PECI_ADDR, msg->addr); +- regmap_write(priv->regmap, NPCM_PECI_RD_LENGTH, +- NPCM_PECI_WR_LEN_MASK & msg->rx_len); +- regmap_write(priv->regmap, NPCM_PECI_WR_LENGTH, +- NPCM_PECI_WR_LEN_MASK & msg->tx_len); +- +- if (msg->tx_len) { +- regmap_write(priv->regmap, NPCM_PECI_CMD, msg->tx_buf[0]); +- +- for (i = 0; i < (msg->tx_len - 1); i++) +- regmap_write(priv->regmap, NPCM_PECI_DAT_INOUT(i), +- msg->tx_buf[i + 1]); +- } +- +- priv->status = 0; +- regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, +- NPCM_PECI_CTRL_START_BUSY, +- NPCM_PECI_CTRL_START_BUSY); +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- err = wait_for_completion_interruptible_timeout(&priv->xfer_complete, +- timeout); +- +- spin_lock_irqsave(&priv->lock, flags); +- +- regmap_write(priv->regmap, NPCM_PECI_CMD, 0); +- +- if (err <= 0 || priv->status != NPCM_PECI_CTRL_DONE) { +- if (err < 0) { /* -ERESTARTSYS */ +- rc = (int)err; +- goto err_irqrestore; +- } else if (err == 0) { +- dev_dbg(priv->dev, "Timeout waiting for a response!\n"); +- rc = -ETIMEDOUT; +- goto err_irqrestore; +- } +- +- dev_dbg(priv->dev, "No valid response!\n"); +- rc = -EIO; +- goto err_irqrestore; +- } +- +- for (i = 0; i < msg->rx_len; i++) { +- regmap_read(priv->regmap, NPCM_PECI_DAT_INOUT(i), &msg_rd); +- msg->rx_buf[i] = (u8)msg_rd; +- } +- +-err_irqrestore: +- spin_unlock_irqrestore(&priv->lock, flags); +- return rc; +-} +- +-static irqreturn_t npcm_peci_irq_handler(int irq, void *arg) +-{ +- struct npcm_peci *priv = arg; +- u32 status_ack = 0; +- u32 status; +- +- spin_lock(&priv->lock); +- regmap_read(priv->regmap, NPCM_PECI_CTL_STS, &status); +- priv->status |= (status & NPCM_PECI_INT_MASK); +- +- if (status & NPCM_PECI_CTRL_CRC_ERR) { +- dev_dbg(priv->dev, "PECI_INT_W_FCS_BAD\n"); +- status_ack |= NPCM_PECI_CTRL_CRC_ERR; +- } +- +- if (status & NPCM_PECI_CTRL_ABRT_ERR) { +- dev_dbg(priv->dev, "NPCM_PECI_CTRL_ABRT_ERR\n"); +- status_ack |= NPCM_PECI_CTRL_ABRT_ERR; +- } +- +- /* +- * All commands should be ended up with a NPCM_PECI_CTRL_DONE +- * bit set even in an error case. +- */ +- if (status & NPCM_PECI_CTRL_DONE) { +- dev_dbg(priv->dev, "NPCM_PECI_CTRL_DONE\n"); +- status_ack |= NPCM_PECI_CTRL_DONE; +- complete(&priv->xfer_complete); +- } +- +- regmap_write_bits(priv->regmap, NPCM_PECI_CTL_STS, +- NPCM_PECI_INT_MASK, status_ack); +- +- spin_unlock(&priv->lock); +- return IRQ_HANDLED; +-} +- +-static int npcm_peci_init_ctrl(struct npcm_peci *priv) +-{ +- u32 cmd_sts, host_neg_bit_rate = 0, pull_down = 0; +- int ret; +- bool volt; +- +- priv->clk = devm_clk_get(priv->dev, NULL); +- if (IS_ERR(priv->clk)) { +- dev_err(priv->dev, "Failed to get clk source.\n"); +- return PTR_ERR(priv->clk); +- } +- +- ret = clk_prepare_enable(priv->clk); +- if (ret) { +- dev_err(priv->dev, "Failed to enable clock.\n"); +- return ret; +- } +- +- ret = of_property_read_u32(priv->dev->of_node, "cmd-timeout-ms", +- &priv->cmd_timeout_ms); +- if (ret || priv->cmd_timeout_ms > NPCM_PECI_CMD_TIMEOUT_MS_MAX || +- priv->cmd_timeout_ms == 0) { +- if (ret) +- dev_warn(priv->dev, +- "cmd-timeout-ms not found, use default : %u\n", +- NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); +- else +- dev_warn(priv->dev, +- "Invalid cmd-timeout-ms : %u. Use default : %u\n", +- priv->cmd_timeout_ms, +- NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); +- +- priv->cmd_timeout_ms = NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT; +- } +- +- if (of_device_is_compatible(priv->dev->of_node, +- "nuvoton,npcm750-peci")) { +- priv->gcr_regmap = syscon_regmap_lookup_by_compatible +- ("nuvoton,npcm750-gcr"); +- if (!IS_ERR(priv->gcr_regmap)) { +- volt = of_property_read_bool(priv->dev->of_node, +- "high-volt-range"); +- if (volt) +- regmap_update_bits(priv->gcr_regmap, +- NPCM7XX_INTCR3_OFFSET, +- NPCM7XX_INTCR3_PECIVSEL, +- NPCM7XX_INTCR3_PECIVSEL); +- else +- regmap_update_bits(priv->gcr_regmap, +- NPCM7XX_INTCR3_OFFSET, +- NPCM7XX_INTCR3_PECIVSEL, 0); +- } +- } +- +- ret = of_property_read_u32(priv->dev->of_node, "pull-down", +- &pull_down); +- if (ret || pull_down > NPCM_PECI_PULL_DOWN_MAX) { +- if (ret) +- dev_warn(priv->dev, +- "pull-down not found, use default : %u\n", +- NPCM_PECI_PULL_DOWN_DEFAULT); +- else +- dev_warn(priv->dev, +- "Invalid pull-down : %u. Use default : %u\n", +- pull_down, +- NPCM_PECI_PULL_DOWN_DEFAULT); +- pull_down = NPCM_PECI_PULL_DOWN_DEFAULT; +- } +- +- regmap_update_bits(priv->regmap, NPCM_PECI_CTL2, NPCM_PECI_CTL2_MASK, +- pull_down << 6); +- +- ret = of_property_read_u32(priv->dev->of_node, "host-neg-bit-rate", +- &host_neg_bit_rate); +- if (ret || host_neg_bit_rate > NPCM_PECI_HOST_NEG_BIT_RATE_MAX || +- host_neg_bit_rate < NPCM_PECI_HOST_NEG_BIT_RATE_MIN) { +- if (ret) +- dev_warn(priv->dev, +- "host-neg-bit-rate not found, use default : %u\n", +- NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); +- else +- dev_warn(priv->dev, +- "Invalid host-neg-bit-rate : %u. Use default : %u\n", +- host_neg_bit_rate, +- NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); +- host_neg_bit_rate = NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT; +- } +- +- regmap_update_bits(priv->regmap, NPCM_PECI_PDDR, NPCM_PECI_PDDR_MASK, +- host_neg_bit_rate); +- +- priv->host_bit_rate = clk_get_rate(priv->clk) / +- (4 * (host_neg_bit_rate + 1)); +- +- ret = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, +- !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), +- NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, +- NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); +- if (ret) +- return ret; /* -ETIMEDOUT */ +- +- /* PECI interrupt enable */ +- regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS, +- NPCM_PECI_CTRL_DONE_INT_EN, +- NPCM_PECI_CTRL_DONE_INT_EN); +- +- return 0; +-} +- +-static const struct regmap_config npcm_peci_regmap_config = { +- .reg_bits = 8, +- .val_bits = 8, +- .max_register = NPCM_PECI_MAX_REG, +- .fast_io = true, +-}; +- +-static int npcm_peci_xfer(struct peci_adapter *adapter, +- struct peci_xfer_msg *msg) +-{ +- struct npcm_peci *priv = peci_get_adapdata(adapter); +- +- return npcm_peci_xfer_native(priv, msg); +-} +- +-static int npcm_peci_probe(struct platform_device *pdev) +-{ +- struct peci_adapter *adapter; +- struct npcm_peci *priv; +- struct resource *res; +- void __iomem *base; +- int ret; +- +- adapter = peci_alloc_adapter(&pdev->dev, sizeof(*priv)); +- if (!adapter) +- return -ENOMEM; +- +- priv = peci_get_adapdata(adapter); +- priv->adapter = adapter; +- priv->dev = &pdev->dev; +- dev_set_drvdata(&pdev->dev, priv); +- +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- base = devm_ioremap_resource(&pdev->dev, res); +- if (IS_ERR(base)) { +- ret = PTR_ERR(base); +- goto err_put_adapter_dev; +- } +- +- priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, +- &npcm_peci_regmap_config); +- if (IS_ERR(priv->regmap)) { +- ret = PTR_ERR(priv->regmap); +- goto err_put_adapter_dev; +- } +- +- priv->irq = platform_get_irq(pdev, 0); +- if (!priv->irq) { +- ret = -ENODEV; +- goto err_put_adapter_dev; +- } +- +- ret = devm_request_irq(&pdev->dev, priv->irq, npcm_peci_irq_handler, +- 0, "peci-npcm-irq", priv); +- if (ret) +- goto err_put_adapter_dev; +- +- init_completion(&priv->xfer_complete); +- spin_lock_init(&priv->lock); +- +- priv->adapter->owner = THIS_MODULE; +- priv->adapter->dev.of_node = of_node_get(dev_of_node(priv->dev)); +- strlcpy(priv->adapter->name, pdev->name, sizeof(priv->adapter->name)); +- priv->adapter->xfer = npcm_peci_xfer; +- +- ret = npcm_peci_init_ctrl(priv); +- if (ret) +- goto err_put_adapter_dev; +- +- ret = peci_add_adapter(priv->adapter); +- if (ret) +- goto err_put_adapter_dev; +- +- dev_info(&pdev->dev, "peci bus %d registered, host negotiation bit rate %dHz", +- priv->adapter->nr, priv->host_bit_rate); +- +- return 0; +- +-err_put_adapter_dev: +- put_device(&adapter->dev); +- return ret; +-} +- +-static int npcm_peci_remove(struct platform_device *pdev) +-{ +- struct npcm_peci *priv = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(priv->clk); +- peci_del_adapter(priv->adapter); +- of_node_put(priv->adapter->dev.of_node); +- +- return 0; +-} +- +-static const struct of_device_id npcm_peci_of_table[] = { +- { .compatible = "nuvoton,npcm750-peci", }, +- { } +-}; +-MODULE_DEVICE_TABLE(of, npcm_peci_of_table); +- +-static struct platform_driver npcm_peci_driver = { +- .probe = npcm_peci_probe, +- .remove = npcm_peci_remove, +- .driver = { +- .name = "peci-npcm", +- .of_match_table = of_match_ptr(npcm_peci_of_table), +- }, +-}; +-module_platform_driver(npcm_peci_driver); +- +-MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); +-MODULE_DESCRIPTION("NPCM Platform Environment Control Interface (PECI) driver"); +-MODULE_LICENSE("GPL v2"); +diff --git a/include/linux/mfd/intel-peci-client.h b/include/linux/mfd/intel-peci-client.h +index 8f6d823a59cd..9854303bbc26 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 +@@ -9,14 +9,15 @@ + #if IS_ENABLED(CONFIG_X86) + #include <asm/intel-family.h> + #else +-/** ++/* + * Architectures other than x86 cannot include the header file so define these + * at here. These are needed for detecting type of client x86 CPUs behind a PECI + * connection. + */ +-#define INTEL_FAM6_HASWELL_X 0x3F +-#define INTEL_FAM6_BROADWELL_X 0x4F +-#define INTEL_FAM6_SKYLAKE_X 0x55 ++#define INTEL_FAM6_HASWELL_X 0x3F ++#define INTEL_FAM6_BROADWELL_X 0x4F ++#define INTEL_FAM6_SKYLAKE_X 0x55 ++#define INTEL_FAM6_SKYLAKE_XD 0x56 + #endif + + #define CORE_MAX_ON_HSX 18 /* Max number of cores on Haswell */ +@@ -31,6 +32,10 @@ + #define CHAN_RANK_MAX_ON_SKX 6 /* Max number of channel ranks on Skylake */ + #define DIMM_IDX_MAX_ON_SKX 2 /* Max DIMM index per channel on Skylake */ + ++#define CORE_MAX_ON_SKXD 16 /* Max number of cores on Skylake D */ ++#define CHAN_RANK_MAX_ON_SKXD 2 /* Max number of channel ranks on Skylake D */ ++#define DIMM_IDX_MAX_ON_SKXD 2 /* Max DIMM index per channel on Skylake D */ ++ + #define CORE_NUMS_MAX CORE_MAX_ON_SKX + #define CHAN_RANK_MAX CHAN_RANK_MAX_ON_HSX + #define DIMM_IDX_MAX DIMM_IDX_MAX_ON_HSX +@@ -58,7 +63,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 +71,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; + }; +@@ -93,18 +96,22 @@ peci_client_read_package_config(struct peci_client_manager *priv, + u8 index, u16 param, u8 *data) + { + struct peci_rd_pkg_cfg_msg msg; +- int rc; ++ int ret; + + msg.addr = priv->client->addr; + msg.index = index; + msg.param = param; + msg.rx_len = 4; + +- rc = peci_command(priv->client->adapter, PECI_CMD_RD_PKG_CFG, &msg); +- if (!rc) +- memcpy(data, msg.pkg_config, 4); ++ ret = peci_command(priv->client->adapter, PECI_CMD_RD_PKG_CFG, &msg); ++ if (msg.cc != PECI_DEV_CC_SUCCESS) ++ ret = -EAGAIN; ++ if (ret) ++ return ret; ++ ++ memcpy(data, msg.pkg_config, 4); + +- return rc; ++ return 0; + } + + #endif /* __LINUX_MFD_INTEL_PECI_CLIENT_H */ +diff --git a/include/linux/peci.h b/include/linux/peci.h +index d0e47d45d1d0..6fc424dc2a73 100644 +--- a/include/linux/peci.h ++++ b/include/linux/peci.h +@@ -1,19 +1,18 @@ + /* 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 + +-#include <linux/cdev.h> + #include <linux/device.h> ++#include <linux/mutex.h> + #include <linux/peci-ioctl.h> +-#include <linux/rtmutex.h> + + #define PECI_NAME_SIZE 32 + + struct peci_board_info { + char type[PECI_NAME_SIZE]; +- unsigned short addr; /* CPU client address */ ++ u8 addr; /* CPU client address */ + struct device_node *of_node; + }; + +@@ -22,29 +21,29 @@ struct peci_board_info { + * @owner: owner module of the PECI adpater + * @bus_lock: mutex for exclusion of multiple callers + * @dev: device interface to this driver +- * @cdev: character device object to create character device + * @nr: the bus number to map + * @name: name of the adapter + * @userspace_clients_lock: mutex for exclusion of clients handling + * @userspace_clients: list of registered clients + * @xfer: low-level transfer function pointer of the adapter + * @cmd_mask: mask for supportable PECI commands ++ * @use_dma: flag for indicating that adapter uses DMA + * + * Each PECI adapter can communicate with one or more PECI client children. + * These make a small bus, sharing a single wired PECI connection. + */ + struct peci_adapter { + struct module *owner; +- struct rt_mutex bus_lock; ++ struct mutex bus_lock; + struct device dev; +- struct cdev cdev; + int nr; + char name[PECI_NAME_SIZE]; + struct mutex userspace_clients_lock; /* clients list mutex */ + struct list_head userspace_clients; + int (*xfer)(struct peci_adapter *adapter, + struct peci_xfer_msg *msg); +- uint cmd_mask; ++ u32 cmd_mask; ++ bool use_dma; + }; + + static inline struct peci_adapter *to_peci_adapter(void *d) +@@ -87,8 +86,8 @@ static inline struct peci_client *to_peci_client(void *d) + } + + struct peci_device_id { +- char name[PECI_NAME_SIZE]; +- unsigned long driver_data; /* Data private to the driver */ ++ char name[PECI_NAME_SIZE]; ++ ulong driver_data; /* Data private to the driver */ + }; + + /** +@@ -129,13 +128,22 @@ static inline struct peci_driver *to_peci_driver(void *d) + /* use a define to avoid include chaining to get THIS_MODULE */ + #define peci_add_driver(driver) peci_register_driver(THIS_MODULE, driver) + ++extern struct bus_type peci_bus_type; ++extern struct device_type peci_adapter_type; ++extern struct device_type peci_client_type; ++ + int peci_register_driver(struct module *owner, struct peci_driver *drv); + void peci_del_driver(struct peci_driver *driver); + struct peci_client *peci_verify_client(struct device *dev); +-struct peci_adapter *peci_alloc_adapter(struct device *dev, unsigned int size); ++struct peci_adapter *peci_alloc_adapter(struct device *dev, uint size); ++struct peci_adapter *peci_get_adapter(int nr); ++void peci_put_adapter(struct peci_adapter *adapter); + int peci_add_adapter(struct peci_adapter *adapter); + void peci_del_adapter(struct peci_adapter *adapter); + struct peci_adapter *peci_verify_adapter(struct device *dev); ++int peci_for_each_dev(void *data, int (*fn)(struct device *, void *)); ++struct peci_xfer_msg *peci_get_xfer_msg(u8 tx_len, u8 rx_len); ++void peci_put_xfer_msg(struct peci_xfer_msg *msg); + int peci_command(struct peci_adapter *adpater, enum peci_cmd cmd, void *vmsg); + int peci_get_cpu_id(struct peci_adapter *adapter, u8 addr, u32 *cpu_id); + +diff --git a/include/uapi/linux/peci-ioctl.h b/include/uapi/linux/peci-ioctl.h +index a6dae71cbff5..253fb42e38b7 100644 +--- a/include/uapi/linux/peci-ioctl.h ++++ b/include/uapi/linux/peci-ioctl.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0 */ +-/* Copyright (c) 2018 Intel Corporation */ ++/* Copyright (c) 2018-2019 Intel Corporation */ + + #ifndef __PECI_IOCTL_H + #define __PECI_IOCTL_H +@@ -7,136 +7,35 @@ + #include <linux/ioctl.h> + #include <linux/types.h> + +-/* Base Address of 48d */ +-#define PECI_BASE_ADDR 0x30 /* The PECI client's default address of 0x30 */ +-#define PECI_OFFSET_MAX 8 /* Max numver of CPU clients */ +- +-/* PCI Access */ +-#define MAX_PCI_READ_LEN 24 /* Number of bytes of the PCI Space read */ +- +-#define PCI_BUS0_CPU0 0x00 +-#define PCI_BUS0_CPU1 0x80 +-#define PCI_CPUBUSNO_BUS 0x00 +-#define PCI_CPUBUSNO_DEV 0x08 +-#define PCI_CPUBUSNO_FUNC 0x02 +-#define PCI_CPUBUSNO 0xcc +-#define PCI_CPUBUSNO_1 0xd0 +-#define PCI_CPUBUSNO_VALID 0xd4 +- +-/* Package Identifier Read Parameter Value */ +-#define PKG_ID_CPU_ID 0x0000 /* CPUID Info */ +-#define PKG_ID_PLATFORM_ID 0x0001 /* Platform ID */ +-#define PKG_ID_UNCORE_ID 0x0002 /* Uncore Device ID */ +-#define PKG_ID_MAX_THREAD_ID 0x0003 /* Max Thread ID */ +-#define PKG_ID_MICROCODE_REV 0x0004 /* CPU Microcode Update Revision */ +-#define PKG_ID_MACHINE_CHECK_STATUS 0x0005 /* Machine Check Status */ +- +-/* RdPkgConfig Index */ +-#define MBX_INDEX_CPU_ID 0 /* Package Identifier Read */ +-#define MBX_INDEX_VR_DEBUG 1 /* VR Debug */ +-#define MBX_INDEX_PKG_TEMP_READ 2 /* Package Temperature Read */ +-#define MBX_INDEX_ENERGY_COUNTER 3 /* Energy counter */ +-#define MBX_INDEX_ENERGY_STATUS 4 /* DDR Energy Status */ +-#define MBX_INDEX_WAKE_MODE_BIT 5 /* "Wake on PECI" Mode bit */ +-#define MBX_INDEX_EPI 6 /* Efficient Performance Indication */ +-#define MBX_INDEX_PKG_RAPL_PERF 8 /* Pkg RAPL Performance Status Read */ +-#define MBX_INDEX_PER_CORE_DTS_TEMP 9 /* Per Core DTS Temperature Read */ +-#define MBX_INDEX_DTS_MARGIN 10 /* DTS thermal margin */ +-#define MBX_INDEX_SKT_PWR_THRTL_DUR 11 /* Socket Power Throttled Duration */ +-#define MBX_INDEX_CFG_TDP_CONTROL 12 /* TDP Config Control */ +-#define MBX_INDEX_CFG_TDP_LEVELS 13 /* TDP Config Levels */ +-#define MBX_INDEX_DDR_DIMM_TEMP 14 /* DDR DIMM Temperature */ +-#define MBX_INDEX_CFG_ICCMAX 15 /* Configurable ICCMAX */ +-#define MBX_INDEX_TEMP_TARGET 16 /* Temperature Target Read */ +-#define MBX_INDEX_CURR_CFG_LIMIT 17 /* Current Config Limit */ +-#define MBX_INDEX_DIMM_TEMP_READ 20 /* Package Thermal Status Read */ +-#define MBX_INDEX_DRAM_IMC_TMP_READ 22 /* DRAM IMC Temperature Read */ +-#define MBX_INDEX_DDR_CH_THERM_STAT 23 /* DDR Channel Thermal Status */ +-#define MBX_INDEX_PKG_POWER_LIMIT1 26 /* Package Power Limit1 */ +-#define MBX_INDEX_PKG_POWER_LIMIT2 27 /* Package Power Limit2 */ +-#define MBX_INDEX_TDP 28 /* Thermal design power minimum */ +-#define MBX_INDEX_TDP_HIGH 29 /* Thermal design power maximum */ +-#define MBX_INDEX_TDP_UNITS 30 /* Units for power/energy registers */ +-#define MBX_INDEX_RUN_TIME 31 /* Accumulated Run Time */ +-#define MBX_INDEX_CONSTRAINED_TIME 32 /* Thermally Constrained Time Read */ +-#define MBX_INDEX_TURBO_RATIO 33 /* Turbo Activation Ratio */ +-#define MBX_INDEX_DDR_RAPL_PL1 34 /* DDR RAPL PL1 */ +-#define MBX_INDEX_DDR_PWR_INFO_HIGH 35 /* DRAM Power Info Read (high) */ +-#define MBX_INDEX_DDR_PWR_INFO_LOW 36 /* DRAM Power Info Read (low) */ +-#define MBX_INDEX_DDR_RAPL_PL2 37 /* DDR RAPL PL2 */ +-#define MBX_INDEX_DDR_RAPL_STATUS 38 /* DDR RAPL Performance Status */ +-#define MBX_INDEX_DDR_HOT_ABSOLUTE 43 /* DDR Hottest Dimm Absolute Temp */ +-#define MBX_INDEX_DDR_HOT_RELATIVE 44 /* DDR Hottest Dimm Relative Temp */ +-#define MBX_INDEX_DDR_THROTTLE_TIME 45 /* DDR Throttle Time */ +-#define MBX_INDEX_DDR_THERM_STATUS 46 /* DDR Thermal Status */ +-#define MBX_INDEX_TIME_AVG_TEMP 47 /* Package time-averaged temperature */ +-#define MBX_INDEX_TURBO_RATIO_LIMIT 49 /* Turbo Ratio Limit Read */ +-#define MBX_INDEX_HWP_AUTO_OOB 53 /* HWP Autonomous Out-of-band */ +-#define MBX_INDEX_DDR_WARM_BUDGET 55 /* DDR Warm Power Budget */ +-#define MBX_INDEX_DDR_HOT_BUDGET 56 /* DDR Hot Power Budget */ +-#define MBX_INDEX_PKG_PSYS_PWR_LIM3 57 /* Package/Psys Power Limit3 */ +-#define MBX_INDEX_PKG_PSYS_PWR_LIM1 58 /* Package/Psys Power Limit1 */ +-#define MBX_INDEX_PKG_PSYS_PWR_LIM2 59 /* Package/Psys Power Limit2 */ +-#define MBX_INDEX_PKG_PSYS_PWR_LIM4 60 /* Package/Psys Power Limit4 */ +-#define MBX_INDEX_PERF_LIMIT_REASON 65 /* Performance Limit Reasons */ +- +-/* WrPkgConfig Index */ +-#define MBX_INDEX_DIMM_AMBIENT 19 +-#define MBX_INDEX_DIMM_TEMP 24 ++/* The PECI client's default address of 0x30 */ ++#define PECI_BASE_ADDR 0x30 ++ ++/* Max number of CPU clients */ ++#define PECI_OFFSET_MAX 8 ++ ++/* PECI read/write data buffer size max */ ++#define PECI_BUFFER_SIZE 255 + + /* Device Specific Completion Code (CC) Definition */ +-#define DEV_PECI_CC_SUCCESS 0x40 +-#define DEV_PECI_CC_TIMEOUT 0x80 +-#define DEV_PECI_CC_OUT_OF_RESOURCE 0x81 +-#define DEV_PECI_CC_UNAVAIL_RESOURCE 0x82 +-#define DEV_PECI_CC_INVALID_REQ 0x90 ++#define PECI_DEV_CC_SUCCESS 0x40 ++#define PECI_DEV_CC_NEED_RETRY 0x80 ++#define PECI_DEV_CC_OUT_OF_RESOURCE 0x81 ++#define PECI_DEV_CC_UNAVAIL_RESOURCE 0x82 ++#define PECI_DEV_CC_INVALID_REQ 0x90 ++#define PECI_DEV_CC_MCA_ERROR 0x91 ++#define PECI_DEV_CC_CATASTROPHIC_MCA_ERROR 0x93 ++#define PECI_DEV_CC_FATAL_MCA_DETECTED 0x94 ++#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB 0x98 ++#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB_IERR 0x9B ++#define PECI_DEV_CC_PARITY_ERROR_ON_GPSB_OR_PMSB_MCA 0x9C + + /* Completion Code mask to check retry needs */ +-#define DEV_PECI_CC_RETRY_CHECK_MASK 0xf0 +-#define DEV_PECI_CC_NEED_RETRY 0x80 ++#define PECI_DEV_CC_RETRY_CHECK_MASK 0xf0 + + /* Skylake EDS says to retry for 250ms */ +-#define DEV_PECI_RETRY_TIME_MS 250 +-#define DEV_PECI_RETRY_INTERVAL_USEC 10000 +-#define DEV_PECI_RETRY_BIT 0x01 +- +-#define GET_TEMP_WR_LEN 1 +-#define GET_TEMP_RD_LEN 2 +-#define GET_TEMP_PECI_CMD 0x01 +- +-#define GET_DIB_WR_LEN 1 +-#define GET_DIB_RD_LEN 8 +-#define GET_DIB_PECI_CMD 0xf7 +- +-#define RDPKGCFG_WRITE_LEN 5 +-#define RDPKGCFG_READ_LEN_BASE 1 +-#define RDPKGCFG_PECI_CMD 0xa1 +- +-#define WRPKGCFG_WRITE_LEN_BASE 6 +-#define WRPKGCFG_READ_LEN 1 +-#define WRPKGCFG_PECI_CMD 0xa5 +- +-#define RDIAMSR_WRITE_LEN 5 +-#define RDIAMSR_READ_LEN 9 +-#define RDIAMSR_PECI_CMD 0xb1 +- +-#define WRIAMSR_PECI_CMD 0xb5 +- +-#define RDPCICFG_WRITE_LEN 6 +-#define RDPCICFG_READ_LEN 5 +-#define RDPCICFG_PECI_CMD 0x61 +- +-#define WRPCICFG_PECI_CMD 0x65 +- +-#define RDPCICFGLOCAL_WRITE_LEN 5 +-#define RDPCICFGLOCAL_READ_LEN_BASE 1 +-#define RDPCICFGLOCAL_PECI_CMD 0xe1 +- +-#define WRPCICFGLOCAL_WRITE_LEN_BASE 6 +-#define WRPCICFGLOCAL_READ_LEN 1 +-#define WRPCICFGLOCAL_PECI_CMD 0xe5 +- +-#define PECI_BUFFER_SIZE 32 ++#define PECI_DEV_RETRY_TIME_MS 250 ++#define PECI_DEV_RETRY_INTERVAL_USEC 10000 ++#define PECI_DEV_RETRY_BIT 0x01 + + /** + * enum peci_cmd - PECI client commands +@@ -186,11 +85,12 @@ enum peci_cmd { + * raw PECI transfer + */ + struct peci_xfer_msg { +- __u8 addr; +- __u8 tx_len; +- __u8 rx_len; +- __u8 tx_buf[PECI_BUFFER_SIZE]; +- __u8 rx_buf[PECI_BUFFER_SIZE]; ++ __u8 addr; ++ __u8 tx_len; ++ __u8 rx_len; ++ __u8 padding; ++ __u8 *tx_buf; ++ __u8 *rx_buf; + } __attribute__((__packed__)); + + /** +@@ -202,7 +102,8 @@ struct peci_xfer_msg { + * powered-off, etc. + */ + struct peci_ping_msg { +- __u8 addr; ++ __u8 addr; ++ __u8 padding[3]; + } __attribute__((__packed__)); + + /** +@@ -216,8 +117,13 @@ struct peci_ping_msg { + * command. + */ + struct peci_get_dib_msg { +- __u8 addr; +- __u64 dib; ++#define PECI_GET_DIB_WR_LEN 1 ++#define PECI_GET_DIB_RD_LEN 8 ++#define PECI_GET_DIB_CMD 0xf7 ++ ++ __u8 addr; ++ __u8 padding[3]; ++ __u64 dib; + } __attribute__((__packed__)); + + /** +@@ -232,8 +138,13 @@ struct peci_get_dib_msg { + * below the maximum processor junction temperature. + */ + struct peci_get_temp_msg { +- __u8 addr; +- __s16 temp_raw; ++#define PECI_GET_TEMP_WR_LEN 1 ++#define PECI_GET_TEMP_RD_LEN 2 ++#define PECI_GET_TEMP_CMD 0x01 ++ ++ __u8 addr; ++ __u8 padding; ++ __s16 temp_raw; + } __attribute__((__packed__)); + + /** +@@ -242,6 +153,7 @@ struct peci_get_temp_msg { + * @index: encoding index for the requested service + * @param: specific data being requested + * @rx_len: number of data to be read in bytes ++ * @cc: completion code + * @pkg_config: package config data to be read + * + * The RdPkgConfig() command provides read access to the Package Configuration +@@ -251,11 +163,73 @@ struct peci_get_temp_msg { + * DIMM temperatures and so on. + */ + struct peci_rd_pkg_cfg_msg { +- __u8 addr; +- __u8 index; +- __u16 param; +- __u8 rx_len; +- __u8 pkg_config[4]; ++#define PECI_RDPKGCFG_WRITE_LEN 5 ++#define PECI_RDPKGCFG_READ_LEN_BASE 1 ++#define PECI_RDPKGCFG_CMD 0xa1 ++ ++ __u8 addr; ++ __u8 index; ++#define PECI_MBX_INDEX_CPU_ID 0 /* Package Identifier Read */ ++#define PECI_MBX_INDEX_VR_DEBUG 1 /* VR Debug */ ++#define PECI_MBX_INDEX_PKG_TEMP_READ 2 /* Package Temperature Read */ ++#define PECI_MBX_INDEX_ENERGY_COUNTER 3 /* Energy counter */ ++#define PECI_MBX_INDEX_ENERGY_STATUS 4 /* DDR Energy Status */ ++#define PECI_MBX_INDEX_WAKE_MODE_BIT 5 /* "Wake on PECI" Mode bit */ ++#define PECI_MBX_INDEX_EPI 6 /* Efficient Performance Indication */ ++#define PECI_MBX_INDEX_PKG_RAPL_PERF 8 /* Pkg RAPL Performance Status Read */ ++#define PECI_MBX_INDEX_PER_CORE_DTS_TEMP 9 /* Per Core DTS Temperature Read */ ++#define PECI_MBX_INDEX_DTS_MARGIN 10 /* DTS thermal margin */ ++#define PECI_MBX_INDEX_SKT_PWR_THRTL_DUR 11 /* Socket Power Throttled Duration */ ++#define PECI_MBX_INDEX_CFG_TDP_CONTROL 12 /* TDP Config Control */ ++#define PECI_MBX_INDEX_CFG_TDP_LEVELS 13 /* TDP Config Levels */ ++#define PECI_MBX_INDEX_DDR_DIMM_TEMP 14 /* DDR DIMM Temperature */ ++#define PECI_MBX_INDEX_CFG_ICCMAX 15 /* Configurable ICCMAX */ ++#define PECI_MBX_INDEX_TEMP_TARGET 16 /* Temperature Target Read */ ++#define PECI_MBX_INDEX_CURR_CFG_LIMIT 17 /* Current Config Limit */ ++#define PECI_MBX_INDEX_DIMM_TEMP_READ 20 /* Package Thermal Status Read */ ++#define PECI_MBX_INDEX_DRAM_IMC_TMP_READ 22 /* DRAM IMC Temperature Read */ ++#define PECI_MBX_INDEX_DDR_CH_THERM_STAT 23 /* DDR Channel Thermal Status */ ++#define PECI_MBX_INDEX_PKG_POWER_LIMIT1 26 /* Package Power Limit1 */ ++#define PECI_MBX_INDEX_PKG_POWER_LIMIT2 27 /* Package Power Limit2 */ ++#define PECI_MBX_INDEX_TDP 28 /* Thermal design power minimum */ ++#define PECI_MBX_INDEX_TDP_HIGH 29 /* Thermal design power maximum */ ++#define PECI_MBX_INDEX_TDP_UNITS 30 /* Units for power/energy registers */ ++#define PECI_MBX_INDEX_RUN_TIME 31 /* Accumulated Run Time */ ++#define PECI_MBX_INDEX_CONSTRAINED_TIME 32 /* Thermally Constrained Time Read */ ++#define PECI_MBX_INDEX_TURBO_RATIO 33 /* Turbo Activation Ratio */ ++#define PECI_MBX_INDEX_DDR_RAPL_PL1 34 /* DDR RAPL PL1 */ ++#define PECI_MBX_INDEX_DDR_PWR_INFO_HIGH 35 /* DRAM Power Info Read (high) */ ++#define PECI_MBX_INDEX_DDR_PWR_INFO_LOW 36 /* DRAM Power Info Read (low) */ ++#define PECI_MBX_INDEX_DDR_RAPL_PL2 37 /* DDR RAPL PL2 */ ++#define PECI_MBX_INDEX_DDR_RAPL_STATUS 38 /* DDR RAPL Performance Status */ ++#define PECI_MBX_INDEX_DDR_HOT_ABSOLUTE 43 /* DDR Hottest Dimm Absolute Temp */ ++#define PECI_MBX_INDEX_DDR_HOT_RELATIVE 44 /* DDR Hottest Dimm Relative Temp */ ++#define PECI_MBX_INDEX_DDR_THROTTLE_TIME 45 /* DDR Throttle Time */ ++#define PECI_MBX_INDEX_DDR_THERM_STATUS 46 /* DDR Thermal Status */ ++#define PECI_MBX_INDEX_TIME_AVG_TEMP 47 /* Package time-averaged temperature */ ++#define PECI_MBX_INDEX_TURBO_RATIO_LIMIT 49 /* Turbo Ratio Limit Read */ ++#define PECI_MBX_INDEX_HWP_AUTO_OOB 53 /* HWP Autonomous Out-of-band */ ++#define PECI_MBX_INDEX_DDR_WARM_BUDGET 55 /* DDR Warm Power Budget */ ++#define PECI_MBX_INDEX_DDR_HOT_BUDGET 56 /* DDR Hot Power Budget */ ++#define PECI_MBX_INDEX_PKG_PSYS_PWR_LIM3 57 /* Package/Psys Power Limit3 */ ++#define PECI_MBX_INDEX_PKG_PSYS_PWR_LIM1 58 /* Package/Psys Power Limit1 */ ++#define PECI_MBX_INDEX_PKG_PSYS_PWR_LIM2 59 /* Package/Psys Power Limit2 */ ++#define PECI_MBX_INDEX_PKG_PSYS_PWR_LIM4 60 /* Package/Psys Power Limit4 */ ++#define PECI_MBX_INDEX_PERF_LIMIT_REASON 65 /* Performance Limit Reasons */ ++ ++ __u16 param; ++/* When index is PECI_MBX_INDEX_CPU_ID */ ++#define PECI_PKG_ID_CPU_ID 0x0000 /* CPUID Info */ ++#define PECI_PKG_ID_PLATFORM_ID 0x0001 /* Platform ID */ ++#define PECI_PKG_ID_UNCORE_ID 0x0002 /* Uncore Device ID */ ++#define PECI_PKG_ID_MAX_THREAD_ID 0x0003 /* Max Thread ID */ ++#define PECI_PKG_ID_MICROCODE_REV 0x0004 /* CPU Microcode Update Revision */ ++#define PECI_PKG_ID_MACHINE_CHECK_STATUS 0x0005 /* Machine Check Status */ ++ ++ __u8 rx_len; ++ __u8 cc; ++ __u8 padding[2]; ++ __u8 pkg_config[4]; + } __attribute__((__packed__)); + + /** +@@ -264,6 +238,7 @@ struct peci_rd_pkg_cfg_msg { + * @index: encoding index for the requested service + * @param: specific data being requested + * @tx_len: number of data to be written in bytes ++ * @cc: completion code + * @value: package config data to be written + * + * The WrPkgConfig() command provides write access to the Package Configuration +@@ -272,11 +247,20 @@ struct peci_rd_pkg_cfg_msg { + * may include power limiting, thermal averaging constant programming and so on. + */ + struct peci_wr_pkg_cfg_msg { +- __u8 addr; +- __u8 index; +- __u16 param; +- __u8 tx_len; +- __u32 value; ++#define PECI_WRPKGCFG_WRITE_LEN_BASE 6 ++#define PECI_WRPKGCFG_READ_LEN 1 ++#define PECI_WRPKGCFG_CMD 0xa5 ++ ++ __u8 addr; ++ __u8 index; ++#define PECI_MBX_INDEX_DIMM_AMBIENT 19 ++#define PECI_MBX_INDEX_DIMM_TEMP 24 ++ ++ __u16 param; ++ __u8 tx_len; ++ __u8 cc; ++ __u8 padding[2]; ++ __u32 value; + } __attribute__((__packed__)); + + /** +@@ -284,16 +268,47 @@ struct peci_wr_pkg_cfg_msg { + * @addr: address of the client + * @thread_id: ID of the specific logical processor + * @address: address of MSR to read from ++ * @cc: completion code + * @value: data to be read + * + * The RdIAMSR() PECI command provides read access to Model Specific Registers + * (MSRs) defined in the processor's Intel Architecture (IA). + */ + struct peci_rd_ia_msr_msg { +- __u8 addr; +- __u8 thread_id; +- __u16 address; +- __u64 value; ++#define PECI_RDIAMSR_WRITE_LEN 5 ++#define PECI_RDIAMSR_READ_LEN 9 ++#define PECI_RDIAMSR_CMD 0xb1 ++ ++ __u8 addr; ++ __u8 thread_id; ++ __u16 address; ++ __u8 cc; ++ __u8 padding[3]; ++ __u64 value; ++} __attribute__((__packed__)); ++ ++/** ++ * struct peci_wr_ia_msr_msg - WrIAMSR command ++ * @addr: address of the client ++ * @thread_id: ID of the specific logical processor ++ * @address: address of MSR to write to ++ * @tx_len: number of data to be written in bytes ++ * @cc: completion code ++ * @value: data to be written ++ * ++ * The WrIAMSR() PECI command provides write access to Model Specific Registers ++ * (MSRs) defined in the processor's Intel Architecture (IA). ++ */ ++struct peci_wr_ia_msr_msg { ++#define PECI_WRIAMSR_CMD 0xb5 ++ ++ __u8 addr; ++ __u8 thread_id; ++ __u16 address; ++ __u8 tx_len; ++ __u8 cc; ++ __u8 padding[2]; ++ __u64 value; + } __attribute__((__packed__)); + + /** +@@ -303,6 +318,7 @@ struct peci_rd_ia_msr_msg { + * @device: PCI device number + * @function: specific function to read from + * @reg: specific register to read from ++ * @cc: completion code + * @pci_config: config data to be read + * + * The RdPCIConfig() command provides sideband read access to the PCI +@@ -310,12 +326,56 @@ struct peci_rd_ia_msr_msg { + * processor. + */ + struct peci_rd_pci_cfg_msg { +- __u8 addr; +- __u8 bus; +- __u8 device; +- __u8 function; +- __u16 reg; +- __u8 pci_config[4]; ++#define PECI_RDPCICFG_WRITE_LEN 6 ++#define PECI_RDPCICFG_READ_LEN 5 ++#define PECI_RDPCICFG_READ_LEN_MAX 24 ++#define PECI_RDPCICFG_CMD 0x61 ++ ++ __u8 addr; ++ __u8 bus; ++#define PECI_PCI_BUS0_CPU0 0x00 ++#define PECI_PCI_BUS0_CPU1 0x80 ++#define PECI_PCI_CPUBUSNO_BUS 0x00 ++#define PECI_PCI_CPUBUSNO_DEV 0x08 ++#define PECI_PCI_CPUBUSNO_FUNC 0x02 ++#define PECI_PCI_CPUBUSNO 0xcc ++#define PECI_PCI_CPUBUSNO_1 0xd0 ++#define PECI_PCI_CPUBUSNO_VALID 0xd4 ++ ++ __u8 device; ++ __u8 function; ++ __u16 reg; ++ __u8 cc; ++ __u8 padding[1]; ++ __u8 pci_config[4]; ++} __attribute__((__packed__)); ++ ++/** ++ * struct peci_wr_pci_cfg_msg - WrPCIConfig command ++ * @addr: address of the client ++ * @bus: PCI bus number ++ * @device: PCI device number ++ * @function: specific function to write to ++ * @reg: specific register to write to ++ * @tx_len: number of data to be written in bytes ++ * @cc: completion code ++ * @pci_config: config data to be written ++ * ++ * The RdPCIConfig() command provides sideband write access to the PCI ++ * configuration space maintained in downstream devices external to the ++ * processor. ++ */ ++struct peci_wr_pci_cfg_msg { ++#define PECI_WRPCICFG_CMD 0x65 ++ ++ __u8 addr; ++ __u8 bus; ++ __u8 device; ++ __u8 function; ++ __u16 reg; ++ __u8 tx_len; ++ __u8 cc; ++ __u8 pci_config[4]; + } __attribute__((__packed__)); + + /** +@@ -326,6 +386,7 @@ struct peci_rd_pci_cfg_msg { + * @function: specific function to read from + * @reg: specific register to read from + * @rx_len: number of data to be read in bytes ++ * @cc: completion code + * @pci_config: config data to be read + * + * The RdPCIConfigLocal() command provides sideband read access to the PCI +@@ -333,13 +394,18 @@ struct peci_rd_pci_cfg_msg { + * processor IIO and uncore registers within the PCI configuration space. + */ + struct peci_rd_pci_cfg_local_msg { +- __u8 addr; +- __u8 bus; +- __u8 device; +- __u8 function; +- __u16 reg; +- __u8 rx_len; +- __u8 pci_config[4]; ++#define PECI_RDPCICFGLOCAL_WRITE_LEN 5 ++#define PECI_RDPCICFGLOCAL_READ_LEN_BASE 1 ++#define PECI_RDPCICFGLOCAL_CMD 0xe1 ++ ++ __u8 addr; ++ __u8 bus; ++ __u8 device; ++ __u8 function; ++ __u16 reg; ++ __u8 rx_len; ++ __u8 cc; ++ __u8 pci_config[4]; + } __attribute__((__packed__)); + + /** +@@ -350,6 +416,7 @@ struct peci_rd_pci_cfg_local_msg { + * @function: specific function to read from + * @reg: specific register to read from + * @tx_len: number of data to be written in bytes ++ * @cc: completion code + * @value: config data to be written + * + * The WrPCIConfigLocal() command provides sideband write access to the PCI +@@ -357,13 +424,18 @@ struct peci_rd_pci_cfg_local_msg { + * access this space even before BIOS enumeration of the system buses. + */ + struct peci_wr_pci_cfg_local_msg { +- __u8 addr; +- __u8 bus; +- __u8 device; +- __u8 function; +- __u16 reg; +- __u8 tx_len; +- __u32 value; ++#define PECI_WRPCICFGLOCAL_WRITE_LEN_BASE 6 ++#define PECI_WRPCICFGLOCAL_READ_LEN 1 ++#define PECI_WRPCICFGLOCAL_CMD 0xe5 ++ ++ __u8 addr; ++ __u8 bus; ++ __u8 device; ++ __u8 function; ++ __u16 reg; ++ __u8 tx_len; ++ __u8 cc; ++ __u32 value; + } __attribute__((__packed__)); + + #define PECI_IOC_BASE 0xb7 +@@ -389,9 +461,15 @@ struct peci_wr_pci_cfg_local_msg { + #define PECI_IOC_RD_IA_MSR \ + _IOWR(PECI_IOC_BASE, PECI_CMD_RD_IA_MSR, struct peci_rd_ia_msr_msg) + ++#define PECI_IOC_WR_IA_MSR \ ++ _IOWR(PECI_IOC_BASE, PECI_CMD_WR_IA_MSR, struct peci_wr_ia_msr_msg) ++ + #define PECI_IOC_RD_PCI_CFG \ + _IOWR(PECI_IOC_BASE, PECI_CMD_RD_PCI_CFG, struct peci_rd_pci_cfg_msg) + ++#define PECI_IOC_WR_PCI_CFG \ ++ _IOWR(PECI_IOC_BASE, PECI_CMD_WR_PCI_CFG, struct peci_wr_pci_cfg_msg) ++ + #define PECI_IOC_RD_PCI_CFG_LOCAL \ + _IOWR(PECI_IOC_BASE, PECI_CMD_RD_PCI_CFG_LOCAL, \ + struct peci_rd_pci_cfg_local_msg) +-- +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..675125322 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0019-Add-I2C-IPMB-support.patch @@ -0,0 +1,425 @@ +From f588865f8180a6370ac639bdfc186ffc5b926246 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 | 124 +++++++++++++++++ + drivers/i2c/Kconfig | 23 +++ + drivers/i2c/Makefile | 1 + + drivers/i2c/i2c-slave-mqueue.c | 217 +++++++++++++++++++++++++++++ + 4 files changed, 365 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..2d0d06d8df9d +--- /dev/null ++++ b/Documentation/i2c/slave-mqueue-backend.rst +@@ -0,0 +1,124 @@ ++.. 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 abedd55a1264..49a2379876c6 100644 +--- a/drivers/i2c/Kconfig ++++ b/drivers/i2c/Kconfig +@@ -119,6 +119,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/0020-misc-aspeed-add-lpc-mbox-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0020-misc-aspeed-add-lpc-mbox-driver.patch new file mode 100644 index 000000000..884a7018c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0020-misc-aspeed-add-lpc-mbox-driver.patch @@ -0,0 +1,475 @@ +From efa7ad95db45494b2c3685c0688f00174e517e34 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Wed, 10 Jul 2019 16:19:33 -0700 +Subject: [PATCH] misc: aspeed: add lpc mbox driver + +This commit adds back the lpc mbox driver which was removed from +the openbmc linux dev-5.2 tree. + +This driver should be rewritten later. + +Signed-off-by: Cyril Bur <cyrilbur@gmail.com>" +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + arch/arm/boot/dts/aspeed-g4.dtsi | 9 + + arch/arm/boot/dts/aspeed-g5.dtsi | 9 + + drivers/soc/aspeed/Kconfig | 7 + + drivers/soc/aspeed/Makefile | 1 + + drivers/soc/aspeed/aspeed-lpc-mbox.c | 376 +++++++++++++++++++++++++++++++++++ + 5 files changed, 402 insertions(+) + create mode 100644 drivers/soc/aspeed/aspeed-lpc-mbox.c + +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index 00848693f455..4b8b0be22cd4 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -344,6 +344,15 @@ + sio_regs: regs { + compatible = "aspeed,bmc-misc"; + }; ++ ++ mbox: mbox@180 { ++ compatible = "aspeed,ast2400-mbox"; ++ reg = <0x180 0x5c>; ++ interrupts = <46>; ++ #mbox-cells = <1>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; + }; + }; + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 8406da6f62a5..9f3bd5bafab5 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -473,6 +473,15 @@ + sio_regs: regs { + compatible = "aspeed,bmc-misc"; + }; ++ ++ mbox: mbox@180 { ++ compatible = "aspeed,ast2500-mbox"; ++ reg = <0x180 0x5c>; ++ interrupts = <46>; ++ #mbox-cells = <1>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; + }; + }; + +diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig +index 78dd74c49ddb..a4be8e566bc7 100644 +--- a/drivers/soc/aspeed/Kconfig ++++ b/drivers/soc/aspeed/Kconfig +@@ -21,6 +21,13 @@ 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_MBOX ++ tristate "Aspeed LPC Mailbox Controller" ++ depends on SOC_ASPEED && REGMAP && MFD_SYSCON ++ ---help--- ++ Expose the ASPEED LPC MBOX registers found on Aspeed SOCs (AST2400 ++ and AST2500) to userspace. ++ + config ASPEED_LPC_SNOOP + tristate "Aspeed ast2500 HOST LPC snoop support" + depends on SOC_ASPEED && REGMAP && MFD_SYSCON +diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile +index e631b23d519b..f3ff29b874ed 100644 +--- a/drivers/soc/aspeed/Makefile ++++ b/drivers/soc/aspeed/Makefile +@@ -1,5 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0-only + obj-$(CONFIG_ASPEED_BMC_MISC) += aspeed-bmc-misc.o + obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o ++obj-$(CONFIG_ASPEED_LPC_MBOX) += aspeed-lpc-mbox.o + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o + obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o +diff --git a/drivers/soc/aspeed/aspeed-lpc-mbox.c b/drivers/soc/aspeed/aspeed-lpc-mbox.c +new file mode 100644 +index 000000000000..795107206022 +--- /dev/null ++++ b/drivers/soc/aspeed/aspeed-lpc-mbox.c +@@ -0,0 +1,376 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++// Copyright 2017 IBM Corporation ++// TODO: Rewrite this driver ++ ++#include <linux/clk.h> ++#include <linux/interrupt.h> ++#include <linux/mfd/syscon.h> ++#include <linux/miscdevice.h> ++#include <linux/module.h> ++#include <linux/of_irq.h> ++#include <linux/platform_device.h> ++#include <linux/poll.h> ++#include <linux/regmap.h> ++#include <linux/slab.h> ++ ++#define DEVICE_NAME "aspeed-mbox" ++ ++#define MBX_USE_INTERRUPT 0 ++ ++#define ASPEED_MBOX_NUM_REGS 16 ++ ++#define ASPEED_MBOX_DATA_0 0x00 ++#define ASPEED_MBOX_STATUS_0 0x40 ++#define ASPEED_MBOX_STATUS_1 0x44 ++#define ASPEED_MBOX_BMC_CTRL 0x48 ++#define ASPEED_MBOX_CTRL_RECV BIT(7) ++#define ASPEED_MBOX_CTRL_MASK BIT(1) ++#define ASPEED_MBOX_CTRL_SEND BIT(0) ++#define ASPEED_MBOX_HOST_CTRL 0x4c ++#define ASPEED_MBOX_INTERRUPT_0 0x50 ++#define ASPEED_MBOX_INTERRUPT_1 0x54 ++ ++struct aspeed_mbox { ++ struct miscdevice miscdev; ++ struct regmap *regmap; ++ struct clk *clk; ++ unsigned int base; ++ int irq; ++ wait_queue_head_t queue; ++ struct mutex mutex; ++}; ++ ++static atomic_t aspeed_mbox_open_count = ATOMIC_INIT(0); ++ ++static u8 aspeed_mbox_inb(struct aspeed_mbox *mbox, int reg) ++{ ++ /* ++ * The mbox registers are actually only one byte but are addressed ++ * four bytes apart. The other three bytes are marked 'reserved', ++ * they *should* be zero but lets not rely on it. ++ * I am going to rely on the fact we can casually read/write to them... ++ */ ++ unsigned int val = 0xff; /* If regmap throws an error return 0xff */ ++ int rc = regmap_read(mbox->regmap, mbox->base + reg, &val); ++ ++ if (rc) ++ dev_err(mbox->miscdev.parent, "regmap_read() failed with " ++ "%d (reg: 0x%08x)\n", rc, reg); ++ ++ return val & 0xff; ++} ++ ++static void aspeed_mbox_outb(struct aspeed_mbox *mbox, u8 data, int reg) ++{ ++ int rc = regmap_write(mbox->regmap, mbox->base + reg, data); ++ ++ if (rc) ++ dev_err(mbox->miscdev.parent, "regmap_write() failed with " ++ "%d (data: %u reg: 0x%08x)\n", rc, data, reg); ++} ++ ++static struct aspeed_mbox *file_mbox(struct file *file) ++{ ++ return container_of(file->private_data, struct aspeed_mbox, miscdev); ++} ++ ++static int aspeed_mbox_open(struct inode *inode, struct file *file) ++{ ++#if MBX_USE_INTERRUPT ++ struct aspeed_mbox *mbox = file_mbox(file); ++#endif ++ ++ if (atomic_inc_return(&aspeed_mbox_open_count) == 1) { ++#if MBX_USE_INTERRUPT ++ /* ++ * Clear the interrupt status bit if it was left on and unmask ++ * interrupts. ++ * ASPEED_MBOX_CTRL_RECV bit is W1C, this also unmasks in 1 step ++ */ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV, ASPEED_MBOX_BMC_CTRL); ++#endif ++ return 0; ++ } ++ ++ atomic_dec(&aspeed_mbox_open_count); ++ return -EBUSY; ++} ++ ++static ssize_t aspeed_mbox_read(struct file *file, char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct aspeed_mbox *mbox = file_mbox(file); ++ char __user *p = buf; ++ ssize_t ret; ++ int i; ++ ++ if (!access_ok(buf, count)) ++ return -EFAULT; ++ ++ if (count + *ppos > ASPEED_MBOX_NUM_REGS) ++ return -EINVAL; ++ ++#if MBX_USE_INTERRUPT ++ if (file->f_flags & O_NONBLOCK) { ++ if (!(aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & ++ ASPEED_MBOX_CTRL_RECV)) ++ return -EAGAIN; ++ } else if (wait_event_interruptible(mbox->queue, ++ aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & ++ ASPEED_MBOX_CTRL_RECV)) { ++ return -ERESTARTSYS; ++ } ++#endif ++ ++ mutex_lock(&mbox->mutex); ++ ++ for (i = *ppos; count > 0 && i < ASPEED_MBOX_NUM_REGS; i++) { ++ uint8_t reg = aspeed_mbox_inb(mbox, ASPEED_MBOX_DATA_0 + (i * 4)); ++ ++ ret = __put_user(reg, p); ++ if (ret) ++ goto out_unlock; ++ ++ p++; ++ count--; ++ } ++ ++#if MBX_USE_INTERRUPT ++ /* ASPEED_MBOX_CTRL_RECV bit is write to clear, this also unmasks in 1 step */ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV, ASPEED_MBOX_BMC_CTRL); ++#endif ++ ret = p - buf; ++ ++out_unlock: ++ mutex_unlock(&mbox->mutex); ++ return ret; ++} ++ ++static ssize_t aspeed_mbox_write(struct file *file, const char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct aspeed_mbox *mbox = file_mbox(file); ++ const char __user *p = buf; ++ ssize_t ret; ++ char c; ++ int i; ++ ++ if (!access_ok(buf, count)) ++ return -EFAULT; ++ ++ if (count + *ppos > ASPEED_MBOX_NUM_REGS) ++ return -EINVAL; ++ ++ mutex_lock(&mbox->mutex); ++ ++ for (i = *ppos; count > 0 && i < ASPEED_MBOX_NUM_REGS; i++) { ++ ret = __get_user(c, p); ++ if (ret) ++ goto out_unlock; ++ ++ aspeed_mbox_outb(mbox, c, ASPEED_MBOX_DATA_0 + (i * 4)); ++ p++; ++ count--; ++ } ++ ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_0); ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_1); ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV | ASPEED_MBOX_CTRL_MASK | ASPEED_MBOX_CTRL_SEND, ASPEED_MBOX_BMC_CTRL); ++ ret = p - buf; ++ ++out_unlock: ++ mutex_unlock(&mbox->mutex); ++ return ret; ++} ++ ++static unsigned int aspeed_mbox_poll(struct file *file, poll_table *wait) ++{ ++ struct aspeed_mbox *mbox = file_mbox(file); ++ unsigned int mask = 0; ++ ++ poll_wait(file, &mbox->queue, wait); ++ ++#if MBX_USE_INTERRUPT ++ if (aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & ASPEED_MBOX_CTRL_RECV) ++#endif ++ mask |= POLLIN; ++ ++ return mask; ++} ++ ++static int aspeed_mbox_release(struct inode *inode, struct file *file) ++{ ++ atomic_dec(&aspeed_mbox_open_count); ++ return 0; ++} ++ ++static const struct file_operations aspeed_mbox_fops = { ++ .owner = THIS_MODULE, ++ .llseek = no_seek_end_llseek, ++ .read = aspeed_mbox_read, ++ .write = aspeed_mbox_write, ++ .open = aspeed_mbox_open, ++ .release = aspeed_mbox_release, ++ .poll = aspeed_mbox_poll, ++}; ++ ++static irqreturn_t aspeed_mbox_irq(int irq, void *arg) ++{ ++ struct aspeed_mbox *mbox = arg; ++#if MBX_USE_INTERRUPT ++ int i; ++ ++// if (!(aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & ASPEED_MBOX_CTRL_RECV)) ++// return IRQ_NONE; ++ ++ printk(KERN_ERR "BMC_CTRL: 0x%02x\n", ++ aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL)); ++ printk(KERN_ERR "STATUS_0: 0x%02x\n", ++ aspeed_mbox_inb(mbox, ASPEED_MBOX_STATUS_0)); ++ printk(KERN_ERR "STATUS_1: 0x%02x\n", ++ aspeed_mbox_inb(mbox, ASPEED_MBOX_STATUS_1)); ++ for (i = 0; i < ASPEED_MBOX_NUM_REGS; i++) { ++ printk(KERN_ERR "DATA_%d: 0x%02x\n", i, ++ aspeed_mbox_inb(mbox, ASPEED_MBOX_DATA_0 + (i * 4))); ++ } ++#endif ++ ++ /* Clear interrupt status */ ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_0); ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_1); ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV, ASPEED_MBOX_BMC_CTRL); ++ ++ wake_up(&mbox->queue); ++ return IRQ_HANDLED; ++} ++ ++static int aspeed_mbox_config_irq(struct aspeed_mbox *mbox, ++ struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ int rc; ++ ++ mbox->irq = platform_get_irq(pdev, 0); ++ if (!mbox->irq) ++ return -ENODEV; ++ ++ rc = devm_request_irq(dev, mbox->irq, aspeed_mbox_irq, ++ IRQF_SHARED, DEVICE_NAME, mbox); ++ if (rc < 0) { ++ dev_err(dev, "Unable to request IRQ %d\n", mbox->irq); ++ return rc; ++ } ++ ++ /* Disable all register based interrupts. */ ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_INTERRUPT_0); /* regs 0 - 7 */ ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_INTERRUPT_1); /* regs 8 - 15 */ ++ ++ /* These registers are write one to clear. Clear them. */ ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_0); ++ aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_1); ++ ++ aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV, ASPEED_MBOX_BMC_CTRL); ++ return 0; ++} ++ ++static int aspeed_mbox_probe(struct platform_device *pdev) ++{ ++ struct aspeed_mbox *mbox; ++ struct device *dev; ++ int rc; ++ ++ dev = &pdev->dev; ++ ++ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); ++ if (!mbox) ++ return -ENOMEM; ++ ++ dev_set_drvdata(&pdev->dev, mbox); ++ ++ rc = of_property_read_u32(dev->of_node, "reg", &mbox->base); ++ if (rc) { ++ dev_err(dev, "Couldn't read reg device-tree property\n"); ++ return rc; ++ } ++ ++ mbox->regmap = syscon_node_to_regmap( ++ pdev->dev.parent->of_node); ++ if (IS_ERR(mbox->regmap)) { ++ dev_err(dev, "Couldn't get regmap\n"); ++ return -ENODEV; ++ } ++ ++ mutex_init(&mbox->mutex); ++ init_waitqueue_head(&mbox->queue); ++ ++ mbox->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(mbox->clk)) { ++ rc = PTR_ERR(mbox->clk); ++ if (rc != -EPROBE_DEFER) ++ dev_err(dev, "couldn't get clock\n"); ++ return rc; ++ } ++ rc = clk_prepare_enable(mbox->clk); ++ if (rc) { ++ dev_err(dev, "couldn't enable clock\n"); ++ return rc; ++ } ++ ++ mbox->miscdev.minor = MISC_DYNAMIC_MINOR; ++ mbox->miscdev.name = DEVICE_NAME; ++ mbox->miscdev.fops = &aspeed_mbox_fops; ++ mbox->miscdev.parent = dev; ++ rc = misc_register(&mbox->miscdev); ++ if (rc) { ++ dev_err(dev, "Unable to register device\n"); ++ goto err; ++ } ++ ++ rc = aspeed_mbox_config_irq(mbox, pdev); ++ if (rc) { ++ dev_err(dev, "Failed to configure IRQ\n"); ++ misc_deregister(&mbox->miscdev); ++ goto err; ++ } ++ ++ dev_info(&pdev->dev, "LPC mbox registered, irq %d\n", mbox->irq); ++ ++ return 0; ++ ++err: ++ clk_disable_unprepare(mbox->clk); ++ ++ return rc; ++} ++ ++static int aspeed_mbox_remove(struct platform_device *pdev) ++{ ++ struct aspeed_mbox *mbox = dev_get_drvdata(&pdev->dev); ++ ++ misc_deregister(&mbox->miscdev); ++ clk_disable_unprepare(mbox->clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_mbox_match[] = { ++ { .compatible = "aspeed,ast2400-mbox" }, ++ { .compatible = "aspeed,ast2500-mbox" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_mbox_match); ++ ++static struct platform_driver aspeed_mbox_driver = { ++ .driver = { ++ .name = DEVICE_NAME, ++ .of_match_table = aspeed_mbox_match, ++ }, ++ .probe = aspeed_mbox_probe, ++ .remove = aspeed_mbox_remove, ++}; ++ ++module_platform_driver(aspeed_mbox_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>"); ++MODULE_DESCRIPTION("Aspeed mailbox device driver"); +-- +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..b347894be --- /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,619 @@ +From 3d1ddeb903f6eaeb06ddc1d6cfbed6374ef31f36 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> +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + .../bindings/soc/aspeed/aspeed-lpc-sio.txt | 17 + + arch/arm/boot/dts/aspeed-g4.dtsi | 7 + + arch/arm/boot/dts/aspeed-g5.dtsi | 7 + + drivers/soc/aspeed/Kconfig | 7 + + drivers/soc/aspeed/Makefile | 1 + + drivers/soc/aspeed/aspeed-lpc-sio.c | 450 +++++++++++++++++++++ + include/uapi/linux/aspeed-lpc-sio.h | 44 ++ + 7 files changed, 533 insertions(+) + create mode 100644 Documentation/devicetree/bindings/soc/aspeed/aspeed-lpc-sio.txt + create mode 100644 drivers/soc/aspeed/aspeed-lpc-sio.c + create mode 100644 include/uapi/linux/aspeed-lpc-sio.h + +diff --git a/Documentation/devicetree/bindings/soc/aspeed/aspeed-lpc-sio.txt b/Documentation/devicetree/bindings/soc/aspeed/aspeed-lpc-sio.txt +new file mode 100644 +index 000000000000..c74ea3a4e5ac +--- /dev/null ++++ b/Documentation/devicetree/bindings/soc/aspeed/aspeed-lpc-sio.txt +@@ -0,0 +1,17 @@ ++* Aspeed LPC SIO driver. ++ ++Required properties: ++- compatible : Should be one of: ++ "aspeed,ast2400-lpc-sio" ++ "aspeed,ast2500-lpc-sio" ++- reg : Should contain lpc-sio registers location and length ++- clocks: contains a phandle to the syscon node describing the clocks. ++ There should then be one cell representing the clock to use. ++ ++Example: ++lpc_sio: lpc-sio@100 { ++ compatible = "aspeed,ast2500-lpc-sio"; ++ reg = <0x100 0x20>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++}; +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index 07758c9b900b..3df43f925848 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -345,6 +345,13 @@ + compatible = "aspeed,bmc-misc"; + }; + ++ lpc_sio: lpc-sio@100 { ++ compatible = "aspeed,ast2400-lpc-sio"; ++ reg = <0x100 0x20>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; ++ + mbox: mbox@180 { + compatible = "aspeed,ast2400-mbox"; + reg = <0x180 0x5c>; +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 2387e2d1ff9e..2d6f730a7101 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -474,6 +474,13 @@ + compatible = "aspeed,bmc-misc"; + }; + ++ lpc_sio: lpc-sio@100 { ++ compatible = "aspeed,ast2500-lpc-sio"; ++ reg = <0x100 0x20>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; ++ + mbox: mbox@180 { + compatible = "aspeed,ast2500-mbox"; + reg = <0x180 0x5c>; +diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig +index a4be8e566bc7..285c19042c65 100644 +--- a/drivers/soc/aspeed/Kconfig ++++ b/drivers/soc/aspeed/Kconfig +@@ -28,6 +28,13 @@ config ASPEED_LPC_MBOX + Expose the ASPEED LPC MBOX registers found on Aspeed SOCs (AST2400 + and AST2500) to userspace. + ++config ASPEED_LPC_SIO ++ tristate "Aspeed ast2400/2500 HOST LPC SIO support" ++ depends on SOC_ASPEED && REGMAP && MFD_SYSCON ++ 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 SOC_ASPEED && REGMAP && MFD_SYSCON +diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile +index f3ff29b874ed..2e547cc47e62 100644 +--- a/drivers/soc/aspeed/Makefile ++++ b/drivers/soc/aspeed/Makefile +@@ -2,5 +2,6 @@ + obj-$(CONFIG_ASPEED_BMC_MISC) += aspeed-bmc-misc.o + obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o + obj-$(CONFIG_ASPEED_LPC_MBOX) += aspeed-lpc-mbox.o ++obj-$(CONFIG_ASPEED_LPC_SIO) += aspeed-lpc-sio.o + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o + obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o +diff --git a/drivers/soc/aspeed/aspeed-lpc-sio.c b/drivers/soc/aspeed/aspeed-lpc-sio.c +new file mode 100644 +index 000000000000..c717a3182320 +--- /dev/null ++++ b/drivers/soc/aspeed/aspeed-lpc-sio.c +@@ -0,0 +1,450 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2012-2017 ASPEED Technology Inc. ++// Copyright (c) 2017-2019 Intel Corporation ++ ++#include <linux/clk.h> ++#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 clk *clk; ++ 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->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(lpc_sio->clk)) { ++ rc = PTR_ERR(lpc_sio->clk); ++ if (rc != -EPROBE_DEFER) ++ dev_err(dev, "couldn't get clock\n"); ++ return rc; ++ } ++ rc = clk_prepare_enable(lpc_sio->clk); ++ if (rc) { ++ dev_err(dev, "couldn't enable clock\n"); ++ return rc; ++ } ++ ++ 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"); ++ goto err; ++ } ++ ++ dev_info(dev, "Loaded at %pap (0x%08x)\n", &lpc_sio->regmap, ++ lpc_sio->reg_base); ++ ++ return 0; ++ ++err: ++ clk_disable_unprepare(lpc_sio->clk); ++ ++ 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); ++ clk_disable_unprepare(lpc_sio->clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id aspeed_lpc_sio_match[] = { ++ { .compatible = "aspeed,ast2500-lpc-sio" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, aspeed_lpc_sio_match); ++ ++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_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..a44667436 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0022-Add-AST2500-eSPI-driver.patch @@ -0,0 +1,595 @@ +From 0f0e0a03218f6a7bdcdd78d319959e6bb974502c 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 | 19 ++ + Documentation/misc-devices/espi-slave.rst | 118 +++++++ + 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, 503 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..8660e2ffbb89 +--- /dev/null ++++ b/Documentation/devicetree/bindings/misc/aspeed,espi-slave.txt +@@ -0,0 +1,19 @@ ++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..887a69a7130a +--- /dev/null ++++ b/Documentation/misc-devices/espi-slave.rst +@@ -0,0 +1,118 @@ ++.. 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 2d6f730a7101..0e7f1d2fa08e 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -289,6 +289,7 @@ + clocks = <&syscon ASPEED_CLK_APB>; + interrupt-controller; + #interrupt-cells = <2>; ++ status = "disabled"; + }; + + sgpio: sgpio@1e780200 { +@@ -383,6 +384,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 9d96469fb41c..b6acddaa9421 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -473,6 +473,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 PCI_ENDPOINT_TEST + depends on PCI + select CRC32 +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index e13de4f0798f..f91f66a15484 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -54,6 +54,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_PCI_ENDPOINT_TEST) += pci_endpoint_test.o + obj-$(CONFIG_OCXL) += ocxl/ + obj-y += cardreader/ +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/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..53060daf7 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0026-Add-support-for-new-PECI-commands.patch @@ -0,0 +1,605 @@ +From 8d5f080b38d1b652eb5fdd3a7b74164906680ab7 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> +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/peci/peci-core.c | 396 ++++++++++++++++++++++++++++++++++++++++ + include/uapi/linux/peci-ioctl.h | 146 +++++++++++++++ + 2 files changed, 542 insertions(+) + +diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c +index d1f0df8b139a..b99ba788a032 100644 +--- a/drivers/peci/peci-core.c ++++ b/drivers/peci/peci-core.c +@@ -318,6 +318,12 @@ static int peci_scan_cmd_mask(struct peci_adapter *adapter) + * See PECI Spec Table 3-1. + */ + revision = FIELD_GET(REVISION_NUM_MASK, dib); ++ if (revision >= 0x40) { /* Rev. 4.0 */ ++ adapter->cmd_mask |= BIT(PECI_CMD_RD_END_PT_CFG); ++ adapter->cmd_mask |= BIT(PECI_CMD_WR_END_PT_CFG); ++ adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_DISC); ++ adapter->cmd_mask |= BIT(PECI_CMD_CRASHDUMP_GET_FRAME); ++ } + if (revision >= 0x36) /* Rev. 3.6 */ + adapter->cmd_mask |= BIT(PECI_CMD_WR_IA_MSR); + if (revision >= 0x35) /* Rev. 3.5 */ +@@ -687,6 +693,392 @@ static int peci_cmd_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) + return ret; + } + ++static int peci_cmd_rd_end_pt_cfg(struct peci_adapter *adapter, void *vmsg) ++{ ++ struct peci_rd_end_pt_cfg_msg *umsg = vmsg; ++ struct peci_xfer_msg *msg = NULL; ++ u32 address; ++ u8 tx_size; ++ int ret; ++ ++ switch (umsg->msg_type) { ++ case PECI_ENDPTCFG_TYPE_LOCAL_PCI: ++ case PECI_ENDPTCFG_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; ++ } ++ ++ msg = peci_get_xfer_msg(PECI_RDENDPTCFG_PCI_WRITE_LEN, ++ PECI_RDENDPTCFG_READ_LEN_BASE + ++ umsg->rx_len); ++ if (!msg) ++ return -ENOMEM; ++ ++ 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_buf[0] = PECI_RDENDPTCFG_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] = PECI_ENDPTCFG_ADDR_TYPE_PCI; /* Addr 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 PECI_ENDPTCFG_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 != ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_D && ++ umsg->params.mmio.addr_type != ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { ++ dev_dbg(&adapter->dev, ++ "Invalid address type, addr_type: %d\n", ++ umsg->params.mmio.addr_type); ++ return -EINVAL; ++ } ++ ++ if (umsg->params.mmio.addr_type == ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_D) ++ tx_size = PECI_RDENDPTCFG_MMIO_D_WRITE_LEN; ++ else ++ tx_size = PECI_RDENDPTCFG_MMIO_Q_WRITE_LEN; ++ msg = peci_get_xfer_msg(tx_size, ++ PECI_RDENDPTCFG_READ_LEN_BASE + ++ umsg->rx_len); ++ if (!msg) ++ return -ENOMEM; ++ ++ address = umsg->params.mmio.function; /* [2:0] - Function */ ++ address |= (u32)umsg->params.mmio.device ++ << 3; /* [7:3] - Device */ ++ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_RDENDPTCFG_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 == ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { ++ 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; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ if (!ret) ++ memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); ++ ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); ++ ++ return ret; ++} ++ ++static int peci_cmd_wr_end_pt_cfg(struct peci_adapter *adapter, void *vmsg) ++{ ++ struct peci_wr_end_pt_cfg_msg *umsg = vmsg; ++ struct peci_xfer_msg *msg = NULL; ++ u8 tx_size, aw_fcs; ++ int ret, i, idx; ++ u32 address; ++ ++ switch (umsg->msg_type) { ++ case PECI_ENDPTCFG_TYPE_LOCAL_PCI: ++ case PECI_ENDPTCFG_TYPE_PCI: ++ /* ++ * Per the PECI spec, the write length must be a byte, word, ++ * or dword ++ */ ++ if (umsg->tx_len != 1 && umsg->tx_len != 2 && ++ umsg->tx_len != 4) { ++ dev_dbg(&adapter->dev, ++ "Invalid write length, tx_len: %d\n", ++ umsg->tx_len); ++ return -EINVAL; ++ } ++ ++ msg = peci_get_xfer_msg(PECI_WRENDPTCFG_PCI_WRITE_LEN_BASE + ++ umsg->tx_len, PECI_WRENDPTCFG_READ_LEN); ++ if (!msg) ++ return -ENOMEM; ++ ++ 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_buf[0] = PECI_WRENDPTCFG_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] = PECI_ENDPTCFG_ADDR_TYPE_PCI; /* Addr 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 */ ++ for (i = 0; i < umsg->tx_len; i++) ++ msg->tx_buf[12 + i] = (u8)(umsg->value >> (i << 3)); ++ ++ /* Add an Assured Write Frame Check Sequence byte */ ++ ret = peci_aw_fcs(msg, 15 + umsg->tx_len, &aw_fcs); ++ if (ret) ++ goto out; ++ ++ msg->tx_buf[12 + i] = 0x80 ^ aw_fcs; ++ break; ++ ++ case PECI_ENDPTCFG_TYPE_MMIO: ++ /* ++ * Per the PECI spec, the write length must be a byte, word, ++ * dword, or qword ++ */ ++ if (umsg->tx_len != 1 && umsg->tx_len != 2 && ++ umsg->tx_len != 4 && umsg->tx_len != 8) { ++ dev_dbg(&adapter->dev, ++ "Invalid write length, tx_len: %d\n", ++ umsg->tx_len); ++ return -EINVAL; ++ } ++ /* ++ * Per the PECI spec, the address type must specify either DWORD ++ * or QWORD ++ */ ++ if (umsg->params.mmio.addr_type != ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_D && ++ umsg->params.mmio.addr_type != ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { ++ dev_dbg(&adapter->dev, ++ "Invalid address type, addr_type: %d\n", ++ umsg->params.mmio.addr_type); ++ return -EINVAL; ++ } ++ ++ if (umsg->params.mmio.addr_type == ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_D) ++ tx_size = PECI_WRENDPTCFG_MMIO_D_WRITE_LEN_BASE + ++ umsg->tx_len; ++ else ++ tx_size = PECI_WRENDPTCFG_MMIO_Q_WRITE_LEN_BASE + ++ umsg->tx_len; ++ msg = peci_get_xfer_msg(tx_size, PECI_WRENDPTCFG_READ_LEN); ++ if (!msg) ++ return -ENOMEM; ++ ++ address = umsg->params.mmio.function; /* [2:0] - Function */ ++ address |= (u32)umsg->params.mmio.device ++ << 3; /* [7:3] - Device */ ++ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_WRENDPTCFG_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 == ++ PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q) { ++ msg->tx_len = PECI_WRENDPTCFG_MMIO_Q_WRITE_LEN_BASE; ++ 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 */ ++ idx = 18; ++ } else { ++ idx = 14; ++ } ++ for (i = 0; i < umsg->tx_len; i++) ++ msg->tx_buf[idx + i] = (u8)(umsg->value >> (i << 3)); ++ ++ /* Add an Assured Write Frame Check Sequence byte */ ++ ret = peci_aw_fcs(msg, idx + 3 + umsg->tx_len, &aw_fcs); ++ if (ret) ++ goto out; ++ ++ msg->tx_buf[idx + i] = 0x80 ^ aw_fcs; ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ ++out: ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); ++ ++ return ret; ++} ++ ++static int peci_cmd_crashdump_disc(struct peci_adapter *adapter, void *vmsg) ++{ ++ struct peci_crashdump_disc_msg *umsg = vmsg; ++ struct peci_xfer_msg *msg; ++ int ret; ++ ++ /* 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 = peci_get_xfer_msg(PECI_CRASHDUMP_DISC_WRITE_LEN, ++ PECI_CRASHDUMP_DISC_READ_LEN_BASE + ++ umsg->rx_len); ++ if (!msg) ++ return -ENOMEM; ++ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_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] = PECI_CRASHDUMP_DISC_VERSION; ++ msg->tx_buf[3] = PECI_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; ++ ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ if (!ret) ++ memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); ++ ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); ++ ++ return ret; ++} ++ ++static int peci_cmd_crashdump_get_frame(struct peci_adapter *adapter, ++ void *vmsg) ++{ ++ struct peci_crashdump_get_frame_msg *umsg = vmsg; ++ struct peci_xfer_msg *msg; ++ int ret; ++ ++ /* 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 = peci_get_xfer_msg(PECI_CRASHDUMP_GET_FRAME_WRITE_LEN, ++ PECI_CRASHDUMP_GET_FRAME_READ_LEN_BASE + ++ umsg->rx_len); ++ if (!msg) ++ return -ENOMEM; ++ ++ msg->addr = umsg->addr; ++ msg->tx_buf[0] = PECI_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] = PECI_CRASHDUMP_GET_FRAME_VERSION; ++ msg->tx_buf[3] = PECI_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); ++ ++ ret = peci_xfer_with_retries(adapter, msg, false); ++ if (!ret) ++ memcpy(umsg->data, &msg->rx_buf[1], umsg->rx_len); ++ ++ umsg->cc = msg->rx_buf[0]; ++ peci_put_xfer_msg(msg); ++ ++ return ret; ++} ++ + typedef int (*peci_cmd_fn_type)(struct peci_adapter *, void *); + + static const peci_cmd_fn_type peci_cmd_fn[PECI_CMD_MAX] = { +@@ -702,6 +1094,10 @@ static const peci_cmd_fn_type peci_cmd_fn[PECI_CMD_MAX] = { + 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_wr_end_pt_cfg, ++ peci_cmd_crashdump_disc, ++ peci_cmd_crashdump_get_frame, + }; + + /** +diff --git a/include/uapi/linux/peci-ioctl.h b/include/uapi/linux/peci-ioctl.h +index 253fb42e38b7..405cd8edbcbf 100644 +--- a/include/uapi/linux/peci-ioctl.h ++++ b/include/uapi/linux/peci-ioctl.h +@@ -71,6 +71,10 @@ 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_WR_END_PT_CFG, ++ PECI_CMD_CRASHDUMP_DISC, ++ PECI_CMD_CRASHDUMP_GET_FRAME, + PECI_CMD_MAX + }; + +@@ -438,6 +442,132 @@ struct peci_wr_pci_cfg_local_msg { + __u32 value; + } __attribute__((__packed__)); + ++struct peci_rd_end_pt_cfg_msg { ++#define PECI_RDENDPTCFG_PCI_WRITE_LEN 12 ++#define PECI_RDENDPTCFG_MMIO_D_WRITE_LEN 14 ++#define PECI_RDENDPTCFG_MMIO_Q_WRITE_LEN 18 ++#define PECI_RDENDPTCFG_READ_LEN_BASE 1 ++#define PECI_RDENDPTCFG_CMD 0xc1 ++ ++ __u8 addr; ++ __u8 msg_type; ++#define PECI_ENDPTCFG_TYPE_LOCAL_PCI 0x03 ++#define PECI_ENDPTCFG_TYPE_PCI 0x04 ++#define PECI_ENDPTCFG_TYPE_MMIO 0x05 ++ ++ 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; ++#define PECI_ENDPTCFG_ADDR_TYPE_PCI 0x04 ++#define PECI_ENDPTCFG_ADDR_TYPE_MMIO_D 0x05 ++#define PECI_ENDPTCFG_ADDR_TYPE_MMIO_Q 0x06 ++ ++ __u64 offset; ++ } mmio; ++ } params; ++ __u8 rx_len; ++ __u8 cc; ++ __u8 padding[2]; ++ __u8 data[8]; ++} __attribute__((__packed__)); ++ ++struct peci_wr_end_pt_cfg_msg { ++#define PECI_WRENDPTCFG_PCI_WRITE_LEN_BASE 13 ++#define PECI_WRENDPTCFG_MMIO_D_WRITE_LEN_BASE 15 ++#define PECI_WRENDPTCFG_MMIO_Q_WRITE_LEN_BASE 19 ++#define PECI_WRENDPTCFG_READ_LEN 1 ++#define PECI_WRENDPTCFG_CMD 0xc5 ++ ++ __u8 addr; ++ __u8 msg_type; ++ /* See msg_type in struct peci_rd_end_pt_cfg_msg */ ++ ++ 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; ++ /* See addr_type in struct peci_rd_end_pt_cfg_msg */ ++ ++ __u64 offset; ++ } mmio; ++ } params; ++ __u8 tx_len; ++ __u8 cc; ++ __u8 padding[2]; ++ __u64 value; ++} __attribute__((__packed__)); ++ ++/* Crashdump Agent */ ++#define PECI_CRASHDUMP_CORE 0x00 ++#define PECI_CRASHDUMP_TOR 0x01 ++ ++/* Crashdump Agent Param */ ++#define PECI_CRASHDUMP_PAYLOAD_SIZE 0x00 ++ ++/* Crashdump Agent Data Param */ ++#define PECI_CRASHDUMP_AGENT_ID 0x00 ++#define PECI_CRASHDUMP_AGENT_PARAM 0x01 ++ ++struct peci_crashdump_disc_msg { ++ __u8 addr; ++ __u8 subopcode; ++#define PECI_CRASHDUMP_ENABLED 0x00 ++#define PECI_CRASHDUMP_NUM_AGENTS 0x01 ++#define PECI_CRASHDUMP_AGENT_DATA 0x02 ++ ++ __u8 cc; ++ __u8 param0; ++ __u16 param1; ++ __u8 param2; ++ __u8 rx_len; ++ __u8 data[8]; ++} __attribute__((__packed__)); ++ ++struct peci_crashdump_get_frame_msg { ++#define PECI_CRASHDUMP_DISC_WRITE_LEN 9 ++#define PECI_CRASHDUMP_DISC_READ_LEN_BASE 1 ++#define PECI_CRASHDUMP_DISC_VERSION 0 ++#define PECI_CRASHDUMP_DISC_OPCODE 1 ++#define PECI_CRASHDUMP_GET_FRAME_WRITE_LEN 10 ++#define PECI_CRASHDUMP_GET_FRAME_READ_LEN_BASE 1 ++#define PECI_CRASHDUMP_GET_FRAME_VERSION 0 ++#define PECI_CRASHDUMP_GET_FRAME_OPCODE 3 ++#define PECI_CRASHDUMP_CMD 0x71 ++ ++ __u8 addr; ++ __u8 padding0; ++ __u16 param0; ++ __u16 param1; ++ __u16 param2; ++ __u8 rx_len; ++ __u8 cc; ++ __u8 padding1[2]; ++ __u8 data[16]; ++} __attribute__((__packed__)); ++ + #define PECI_IOC_BASE 0xb7 + + #define PECI_IOC_XFER \ +@@ -478,4 +608,20 @@ 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_WR_END_PT_CFG \ ++ _IOWR(PECI_IOC_BASE, PECI_CMD_WR_END_PT_CFG, \ ++ struct peci_wr_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..28bc8d36c --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0028-Add-AST2500-JTAG-driver.patch @@ -0,0 +1,95 @@ +From a4413fe3ba94906243b632e0b9e0e9a91620dd22 Mon Sep 17 00:00:00 2001 +From: "Corona, Ernesto" <ernesto.corona@intel.com> +Date: Fri, 1 Mar 2019 11:46:09 -0700 +Subject: [PATCH] Update AST2500d JTAG driver. Step 1 + +Update AST2500d JTAG driver. Remove Legacy driver but keep headers. + +Signed-off-by: Corona, Ernesto <ernesto.corona@intel.com> +--- + include/uapi/linux/jtag_drv.h | 73 +++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 73 insertions(+) + create mode 100644 include/uapi/linux/jtag_drv.h + +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/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..d828233fe --- /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,145 @@ +From 38c04788647a95e3951674eb802f1f99ecdde164 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 | 26 ++++++++++++++++++++++++++ + drivers/i2c/i2c-slave-mqueue.c | 24 ++++++++++++++++++++++++ + 2 files changed, 50 insertions(+) + +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index fa66951b05d0..434b753def3b 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -163,6 +163,21 @@ 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) \ ++ do { \ ++ if (dump_debug && (bus)->adap.nr == dump_debug_bus_id) { \ ++ char dump_info[100] = {0,}; \ ++ snprintf(dump_info, sizeof(dump_info), \ ++ "bus_id:%d, addr:0x%02x, flags:0x%02x: ", \ ++ (bus)->adap.nr, addr, flags); \ ++ print_hex_dump(KERN_ERR, dump_info, DUMP_PREFIX_NONE, \ ++ 16, 1, buf, len, true); \ ++ } \ ++ } while (0) ++ + static int aspeed_i2c_reset(struct aspeed_i2c_bus *bus); + + static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus) +@@ -652,6 +667,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 i; + + spin_lock_irqsave(&bus->lock, flags); + bus->cmd_err = 0; +@@ -694,6 +710,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 +1082,11 @@ static struct platform_driver aspeed_i2c_bus_driver = { + }; + module_platform_driver(aspeed_i2c_bus_driver); + ++module_param_named(dump_debug, dump_debug, bool, 0644); ++MODULE_PARM_DESC(dump_debug, "debug flag for dump printing"); ++module_param_named(dump_debug_bus_id, dump_debug_bus_id, int, 0644); ++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..2c7a6038409c 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,21 @@ 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) \ ++ do { \ ++ if (dump_debug && \ ++ (client)->adapter->nr == dump_debug_bus_id) { \ ++ char dump_info[100] = {0,}; \ ++ snprintf(dump_info, sizeof(dump_info), \ ++ "bus_id:%d: ", (client)->adapter->nr); \ ++ print_hex_dump(KERN_ERR, dump_info, DUMP_PREFIX_NONE, \ ++ 16, 1, buf, len, true); \ ++ } \ ++ } while (0) ++ + static int i2c_slave_mqueue_callback(struct i2c_client *client, + enum i2c_slave_event event, u8 *val) + { +@@ -101,6 +117,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 +148,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 +231,11 @@ static struct i2c_driver i2c_slave_mqueue_driver = { + }; + module_i2c_driver(i2c_slave_mqueue_driver); + ++module_param_named(dump_debug, dump_debug, bool, 0644); ++MODULE_PARM_DESC(dump_debug, "debug flag for dump printing"); ++module_param_named(dump_debug_bus_id, dump_debug_bus_id, int, 0644); ++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..803701286 --- /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 01c8d6a5146cd39c2286f659e21f1a1042aa741a 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 42b4df6ba249..97c27820db3e 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) +@@ -446,7 +454,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); +@@ -481,15 +489,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. +@@ -570,9 +586,22 @@ static int aspeed_clk_probe(struct platform_device *pdev) + * UART[1..5] clock source mux + */ + ++ /* 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. +@@ -580,7 +609,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 f43738607d77..335879505a72 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..8c18e87ef --- /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 0928704f3b593217662f980221406fcf6db39377 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 4815104459f1..3094e90c9def 100644 +--- a/arch/arm/boot/dts/aspeed-bmc-intel-purley.dts ++++ b/arch/arm/boot/dts/aspeed-bmc-intel-purley.dts +@@ -233,6 +233,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 0e7f1d2fa08e..6e0b40eb939f 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -493,6 +493,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 b6acddaa9421..78e8fc892209 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -481,6 +481,12 @@ config ASPEED_ESPI_SLAVE + Control Aspeed ast2500 eSPI slave controller to handle event + which needs the firmware's processing. + ++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. ++ + config PCI_ENDPOINT_TEST + depends on PCI + select CRC32 +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index f91f66a15484..7337b8bcd0a3 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -55,6 +55,7 @@ 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_UART_ROUTING) += aspeed-uart-routing.o + obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o + obj-$(CONFIG_OCXL) += ocxl/ + obj-y += cardreader/ +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-aspeed-Swap-the-mac-nodes-numbering.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0034-arm-dts-aspeed-Swap-the-mac-nodes-numbering.patch new file mode 100644 index 000000000..7f556de64 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0034-arm-dts-aspeed-Swap-the-mac-nodes-numbering.patch @@ -0,0 +1,85 @@ +From fcea191a6e0eb869c62b5d1ea8a6e914a540fcfb 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: aspeed: 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 3df43f925848..618abeab539b 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 6e0b40eb939f..b8c85fad2a39 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -149,14 +149,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>; +@@ -165,6 +157,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..798038a8e --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0035-Implement-a-memory-driver-share-memory.patch @@ -0,0 +1,246 @@ +From 5ccf8e3e397edf195aa65e91af6e9ea16ed88882 Mon Sep 17 00:00:00 2001 +From: Cheng C Yang <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: Cheng C Yang <cheng.c.yang@intel.com> +--- + .../bindings/soc/aspeed/aspeed-vga-sharedmem.txt | 20 +++ + drivers/soc/aspeed/Kconfig | 9 ++ + drivers/soc/aspeed/Makefile | 1 + + drivers/soc/aspeed/aspeed-vga-sharedmem.c | 163 +++++++++++++++++++++ + 4 files changed, 193 insertions(+) + create mode 100644 Documentation/devicetree/bindings/soc/aspeed/aspeed-vga-sharedmem.txt + create mode 100644 drivers/soc/aspeed/aspeed-vga-sharedmem.c + +diff --git a/Documentation/devicetree/bindings/soc/aspeed/aspeed-vga-sharedmem.txt b/Documentation/devicetree/bindings/soc/aspeed/aspeed-vga-sharedmem.txt +new file mode 100644 +index 000000000000..03f57c53e844 +--- /dev/null ++++ b/Documentation/devicetree/bindings/soc/aspeed/aspeed-vga-sharedmem.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/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig +index 285c19042c65..59f25d16d6e7 100644 +--- a/drivers/soc/aspeed/Kconfig ++++ b/drivers/soc/aspeed/Kconfig +@@ -51,4 +51,13 @@ config ASPEED_P2A_CTRL + ioctl()s, the driver also provides an interface for userspace mappings to + a pre-defined region. + ++config ASPEED_VGA_SHAREDMEM ++ tristate "Aspeed VGA Shared memory" ++ depends on SOC_ASPEED ++ 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. ++ + endmenu +diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile +index 2e547cc47e62..ae4ef10914be 100644 +--- a/drivers/soc/aspeed/Makefile ++++ b/drivers/soc/aspeed/Makefile +@@ -5,3 +5,4 @@ obj-$(CONFIG_ASPEED_LPC_MBOX) += aspeed-lpc-mbox.o + obj-$(CONFIG_ASPEED_LPC_SIO) += aspeed-lpc-sio.o + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o + obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o ++obj-$(CONFIG_ASPEED_VGA_SHAREDMEM) += aspeed-vga-sharedmem.o +\ No newline at end of file +diff --git a/drivers/soc/aspeed/aspeed-vga-sharedmem.c b/drivers/soc/aspeed/aspeed-vga-sharedmem.c +new file mode 100644 +index 000000000000..cd1f5431378c +--- /dev/null ++++ b/drivers/soc/aspeed/aspeed-vga-sharedmem.c +@@ -0,0 +1,163 @@ ++// 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) ++{ ++ struct aspeed_vga_sharedmem *vga_sharedmem = file_sharemem(file); ++ ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ 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; ++ 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.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..c115d23d0 --- /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,514 @@ +From 7ac4709dd92c608ca4c8ff0046a434c8f465a80c 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/pwm/Kconfig | 9 + + drivers/pwm/Makefile | 1 + + drivers/pwm/pwm-fttmr010.c | 441 +++++++++++++++++++++++++++++++++++++++ + 4 files changed, 452 insertions(+), 1 deletion(-) + create mode 100644 drivers/pwm/pwm-fttmr010.c + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index b8c85fad2a39..88ac8e08b6ae 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -312,7 +312,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/pwm/Kconfig b/drivers/pwm/Kconfig +index dff5a93f7daa..5759439a3947 100644 +--- a/drivers/pwm/Kconfig ++++ b/drivers/pwm/Kconfig +@@ -171,6 +171,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 c368599d36c0..937d212bb02a 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_IMX1) += pwm-imx1.o +diff --git a/drivers/pwm/pwm-fttmr010.c b/drivers/pwm/pwm-fttmr010.c +new file mode 100644 +index 000000000000..4c929a25e27c +--- /dev/null ++++ b/drivers/pwm/pwm-fttmr010.c +@@ -0,0 +1,441 @@ ++// 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> ++ ++#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; ++}; ++ ++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); ++ u32 cr; ++ ++ 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; ++ default: ++ return -ERANGE; ++ } ++ ++ writel(cr, priv->base + TIMER_CR); ++ 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); ++ u32 cr; ++ ++ 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; ++ default: ++ return; ++ } ++ ++ writel(cr, priv->base + TIMER_CR); ++ 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; ++ default: ++ return -ERANGE; ++ } ++ ++ 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"); +-- +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..b2057d247 --- /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,488 @@ +From ed13e7eb07e6cb8bdc1eb4563de30b469a9beb26 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 | 70 +++++++++++++++++++++++++-- + drivers/i2c/i2c-core-smbus.c | 22 +++++++-- + 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, 214 insertions(+), 16 deletions(-) + +diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c +index 9e43508d4567..8a480d269542 100644 +--- a/drivers/i2c/i2c-core-base.c ++++ b/drivers/i2c/i2c-core-base.c +@@ -1299,6 +1299,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; +@@ -1381,6 +1400,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: +@@ -1601,6 +1623,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)); +@@ -1950,7 +1974,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)) +@@ -1963,6 +1989,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 +@@ -1999,6 +2044,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); +@@ -2017,6 +2065,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; + + if (!adap->algo->master_xfer) { +@@ -2040,12 +2089,25 @@ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) + * one (discarding status on the second message) or errno + * (discarding status on the first one). + */ +- ret = __i2c_lock_bus_helper(adap); +- if (ret) +- return ret; ++ /* ++ * 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) { ++ ret = __i2c_lock_bus_helper(adap); ++ if (ret) ++ return ret; ++ } + + 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; + } +diff --git a/drivers/i2c/i2c-core-smbus.c b/drivers/i2c/i2c-core-smbus.c +index 3ac426a8ab5a..f7bf95101e34 100644 +--- a/drivers/i2c/i2c-core-smbus.c ++++ b/drivers/i2c/i2c-core-smbus.c +@@ -526,15 +526,29 @@ 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; + +- res = __i2c_lock_bus_helper(adapter); +- if (res) +- return res; ++ /* ++ * 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) { ++ res = __i2c_lock_bus_helper(adapter); ++ if (res) ++ return res; ++ } + + 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 603252fa1284..d3f55243e701 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); +@@ -441,6 +532,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 c5a977320f82..47f8763d6ed2 100644 +--- a/include/linux/i2c-mux.h ++++ b/include/linux/i2c-mux.h +@@ -27,6 +27,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 1308126fc384..3ee92c6a442d 100644 +--- a/include/linux/i2c.h ++++ b/include/linux/i2c.h +@@ -699,6 +699,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) + +@@ -998,4 +1005,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/0042-Add-bus-timeout-ms-and-retries-device-tree-propertie.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0042-Add-bus-timeout-ms-and-retries-device-tree-propertie.patch new file mode 100644 index 000000000..25f494652 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0042-Add-bus-timeout-ms-and-retries-device-tree-propertie.patch @@ -0,0 +1,105 @@ +From c55d6bb49cfbd39a7c0848076c84166def65936e Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Thu, 7 Mar 2019 15:17:40 -0800 +Subject: [PATCH] Add bus-timeout-ms and #retries device tree properties + +BMC uses I2C bus 7 as a PMBus channel to communicate with PSUs, +also ME uses this bus as SMLink to control PSUs so this bus is +managed by multi-masters. In this use case, some arbitration errors +are expected so we need to add retry logic. And PMBus subsystem +uses I2C bus in kernel internally so retry logic should be +supported in kernel level. + +To support the use case, this commit adds 'bus-timeout-ms' and +'#retries' device tree properties to set the bus specific +parameters at kernel boot time without using any additional ioctls +from user space. + +This patch would not be accepted by I2C maintainer in linux +upstream because he doesn't like adding these legacy properties +into device tree, so keep it only in downstream. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + Documentation/devicetree/bindings/i2c/i2c-aspeed.txt | 3 +++ + Documentation/devicetree/bindings/i2c/i2c.txt | 6 ++++++ + drivers/i2c/busses/i2c-aspeed.c | 1 - + drivers/i2c/i2c-core-base.c | 12 ++++++++++-- + 4 files changed, 19 insertions(+), 3 deletions(-) + +diff --git a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +index 8fbd8633a387..7da7e813b2b0 100644 +--- a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt ++++ b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +@@ -16,6 +16,9 @@ Optional Properties: + - bus-frequency : frequency of the bus clock in Hz defaults to 100 kHz when not + specified + - multi-master : states that there is another master active on this bus. ++- bus-timeout-ms: bus timeout in milliseconds defaults to 1 second when not ++ specified. ++- #retries : Number of retries for master transfer. + + Example: + +diff --git a/Documentation/devicetree/bindings/i2c/i2c.txt b/Documentation/devicetree/bindings/i2c/i2c.txt +index 44efafdfd7f5..e382931cf3d6 100644 +--- a/Documentation/devicetree/bindings/i2c/i2c.txt ++++ b/Documentation/devicetree/bindings/i2c/i2c.txt +@@ -80,6 +80,12 @@ wants to support one of the below features, it should adapt the bindings below. + Names of map programmable addresses. + It can contain any map needing another address than default one. + ++- bus-timeout-ms ++ Bus timeout in milliseconds. ++ ++- #retries ++ Number of retries for master transfer. ++ + Binding may contain optional "interrupts" property, describing interrupts + used by the device. I2C core will assign "irq" interrupt (or the very first + interrupt if not using interrupt names) as primary interrupt for the slave. +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index 434b753def3b..99bd30953531 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -1014,7 +1014,6 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + spin_lock_init(&bus->lock); + init_completion(&bus->cmd_complete); + bus->adap.owner = THIS_MODULE; +- bus->adap.retries = 0; + bus->adap.algo = &aspeed_i2c_algo; + bus->adap.dev.parent = &pdev->dev; + bus->adap.dev.of_node = pdev->dev.of_node; +diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c +index 8a480d269542..69b798a2d31e 100644 +--- a/drivers/i2c/i2c-core-base.c ++++ b/drivers/i2c/i2c-core-base.c +@@ -1320,6 +1320,7 @@ static void i2c_adapter_hold_timer_callback(struct timer_list *t) + + static int i2c_register_adapter(struct i2c_adapter *adap) + { ++ u32 bus_timeout_ms = 0; + int res = -EINVAL; + + /* Can't register until after driver model init */ +@@ -1347,8 +1348,15 @@ static int i2c_register_adapter(struct i2c_adapter *adap) + INIT_LIST_HEAD(&adap->userspace_clients); + + /* Set default timeout to 1 second if not already set */ +- if (adap->timeout == 0) +- adap->timeout = HZ; ++ if (adap->timeout == 0) { ++ device_property_read_u32(&adap->dev, "bus-timeout-ms", ++ &bus_timeout_ms); ++ adap->timeout = bus_timeout_ms ? ++ msecs_to_jiffies(bus_timeout_ms) : HZ; ++ } ++ ++ /* Set retries count if it has the property setting */ ++ device_property_read_u32(&adap->dev, "#retries", &adap->retries); + + /* register soft irqs for Host Notify */ + res = i2c_setup_host_notify_irq_domain(adap); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0043-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-BT.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0043-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-BT.patch new file mode 100644 index 000000000..035945cf3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0043-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-BT.patch @@ -0,0 +1,140 @@ +From c4e7b89ee20dc28e10fae7eb0859e4fa9d3e8643 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Wed, 13 Mar 2019 15:04:16 -0700 +Subject: [PATCH] char: ipmi: Add clock control logic into Aspeed LPC BT driver + +If LPC BT driver is registered ahead of lpc-ctrl module, LPC BT +block will be enabled without heart beating of LCLK until lpc-ctrl +enables the LCLK. This issue causes improper handling on host +interrupts when the host sends interrupt in that time frame. Then +kernel eventually forcibly disables the interrupt with dumping +stack and printing a 'nobody cared this irq' message out. + +To prevent this issue, all LPC sub-nodes should enable LCLK +individually so this patch adds clock control logic into the LPC +BT driver. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + .../bindings/ipmi/aspeed,ast2400-ibt-bmc.txt | 3 +++ + arch/arm/boot/dts/aspeed-g4.dtsi | 1 + + arch/arm/boot/dts/aspeed-g5.dtsi | 1 + + drivers/char/ipmi/bt-bmc.c | 24 +++++++++++++++++++++- + 4 files changed, 28 insertions(+), 1 deletion(-) + +diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt +index 028268fd99ee..d13887d60f19 100644 +--- a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt ++++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt +@@ -10,6 +10,8 @@ Required properties: + "aspeed,ast2400-ibt-bmc" + "aspeed,ast2500-ibt-bmc" + - reg: physical address and size of the registers ++- clocks: contains a phandle to the syscon node describing the clocks. ++ There should then be one cell representing the clock to use. + + Optional properties: + +@@ -22,4 +24,5 @@ Example: + compatible = "aspeed,ast2400-ibt-bmc"; + reg = <0x1e789140 0x18>; + interrupts = <8>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + }; +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index 618abeab539b..c2388bb7c678 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -337,6 +337,7 @@ + ibt: ibt@c0 { + compatible = "aspeed,ast2400-ibt-bmc"; + reg = <0xc0 0x18>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + interrupts = <8>; + status = "disabled"; + }; +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 88ac8e08b6ae..88b318fd287b 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -470,6 +470,7 @@ + ibt: ibt@c0 { + compatible = "aspeed,ast2500-ibt-bmc"; + reg = <0xc0 0x18>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + interrupts = <8>; + status = "disabled"; + }; +diff --git a/drivers/char/ipmi/bt-bmc.c b/drivers/char/ipmi/bt-bmc.c +index 40b9927c072c..a4ec9d1743d7 100644 +--- a/drivers/char/ipmi/bt-bmc.c ++++ b/drivers/char/ipmi/bt-bmc.c +@@ -5,6 +5,7 @@ + + #include <linux/atomic.h> + #include <linux/bt-bmc.h> ++#include <linux/clk.h> + #include <linux/errno.h> + #include <linux/interrupt.h> + #include <linux/io.h> +@@ -60,6 +61,7 @@ struct bt_bmc { + struct device dev; + struct miscdevice miscdev; + struct regmap *map; ++ struct clk *clk; + int offset; + int irq; + wait_queue_head_t queue; +@@ -467,6 +469,19 @@ static int bt_bmc_probe(struct platform_device *pdev) + mutex_init(&bt_bmc->mutex); + init_waitqueue_head(&bt_bmc->queue); + ++ bt_bmc->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(bt_bmc->clk)) { ++ rc = PTR_ERR(bt_bmc->clk); ++ if (rc != -EPROBE_DEFER) ++ dev_err(dev, "couldn't get clock\n"); ++ return rc; ++ } ++ rc = clk_prepare_enable(bt_bmc->clk); ++ if (rc) { ++ dev_err(dev, "couldn't enable clock\n"); ++ return rc; ++ } ++ + bt_bmc->miscdev.minor = MISC_DYNAMIC_MINOR, + bt_bmc->miscdev.name = DEVICE_NAME, + bt_bmc->miscdev.fops = &bt_bmc_fops, +@@ -474,7 +489,7 @@ static int bt_bmc_probe(struct platform_device *pdev) + rc = misc_register(&bt_bmc->miscdev); + if (rc) { + dev_err(dev, "Unable to register misc device\n"); +- return rc; ++ goto err; + } + + bt_bmc_config_irq(bt_bmc, pdev); +@@ -498,6 +513,11 @@ static int bt_bmc_probe(struct platform_device *pdev) + clr_b_busy(bt_bmc); + + return 0; ++ ++err: ++ clk_disable_unprepare(bt_bmc->clk); ++ ++ return rc; + } + + static int bt_bmc_remove(struct platform_device *pdev) +@@ -507,6 +527,8 @@ static int bt_bmc_remove(struct platform_device *pdev) + misc_deregister(&bt_bmc->miscdev); + if (!bt_bmc->irq) + del_timer_sync(&bt_bmc->poll_timer); ++ clk_disable_unprepare(bt_bmc->clk); ++ + return 0; + } + +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0044-misc-Add-clock-control-logic-into-Aspeed-LPC-SNOOP-d.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0044-misc-Add-clock-control-logic-into-Aspeed-LPC-SNOOP-d.patch new file mode 100644 index 000000000..c335fb3ec --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0044-misc-Add-clock-control-logic-into-Aspeed-LPC-SNOOP-d.patch @@ -0,0 +1,125 @@ +From a5bbf0f5943d3a6be6e58a4b78d6d18694ceb859 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Wed, 13 Mar 2019 15:27:48 -0700 +Subject: [PATCH] misc: Add clock control logic into Aspeed LPC SNOOP driver + +If LPC SNOOP driver is registered ahead of lpc-ctrl module, LPC +SNOOP block will be enabled without heart beating of LCLK until +lpc-ctrl enables the LCLK. This issue causes improper handling on +host interrupts when the host sends interrupt in that time frame. +Then kernel eventually forcibly disables the interrupt with +dumping stack and printing a 'nobody cared this irq' message out. + +To prevent this issue, all LPC sub-nodes should enable LCLK +individually so this patch adds clock control logic into the LPC +SNOOP driver. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + arch/arm/boot/dts/aspeed-g4.dtsi | 1 + + arch/arm/boot/dts/aspeed-g5.dtsi | 1 + + drivers/soc/aspeed/aspeed-lpc-snoop.c | 30 +++++++++++++++++++++++++++--- + 3 files changed, 29 insertions(+), 3 deletions(-) + +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index c2388bb7c678..70aca75d65e0 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -320,6 +320,7 @@ + compatible = "aspeed,ast2400-lpc-snoop"; + reg = <0x0 0x80>; + interrupts = <8>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + status = "disabled"; + }; + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 88b318fd287b..f4a5d06f1000 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -453,6 +453,7 @@ + compatible = "aspeed,ast2500-lpc-snoop"; + reg = <0x0 0x80>; + interrupts = <8>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + status = "disabled"; + }; + +diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c +index 48f7ac238861..96ea52db25be 100644 +--- a/drivers/soc/aspeed/aspeed-lpc-snoop.c ++++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c +@@ -11,6 +11,7 @@ + */ + + #include <linux/bitops.h> ++#include <linux/clk.h> + #include <linux/interrupt.h> + #include <linux/fs.h> + #include <linux/kfifo.h> +@@ -67,6 +68,7 @@ struct aspeed_lpc_snoop_channel { + struct aspeed_lpc_snoop { + struct regmap *regmap; + int irq; ++ struct clk *clk; + struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; + }; + +@@ -282,22 +284,42 @@ static int aspeed_lpc_snoop_probe(struct platform_device *pdev) + return -ENODEV; + } + ++ lpc_snoop->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(lpc_snoop->clk)) { ++ rc = PTR_ERR(lpc_snoop->clk); ++ if (rc != -EPROBE_DEFER) ++ dev_err(dev, "couldn't get clock\n"); ++ return rc; ++ } ++ rc = clk_prepare_enable(lpc_snoop->clk); ++ if (rc) { ++ dev_err(dev, "couldn't enable clock\n"); ++ return rc; ++ } ++ + rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); + if (rc) +- return rc; ++ goto err; + + rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port); + if (rc) +- return rc; ++ goto err; + + /* Configuration of 2nd snoop channel port is optional */ + if (of_property_read_u32_index(dev->of_node, "snoop-ports", + 1, &port) == 0) { + rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port); +- if (rc) ++ if (rc) { + aspeed_lpc_disable_snoop(lpc_snoop, 0); ++ goto err; ++ } + } + ++ return 0; ++ ++err: ++ clk_disable_unprepare(lpc_snoop->clk); ++ + return rc; + } + +@@ -309,6 +331,8 @@ static int aspeed_lpc_snoop_remove(struct platform_device *pdev) + aspeed_lpc_disable_snoop(lpc_snoop, 0); + aspeed_lpc_disable_snoop(lpc_snoop, 1); + ++ clk_disable_unprepare(lpc_snoop->clk); ++ + return 0; + } + +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0045-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-KC.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0045-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-KC.patch new file mode 100644 index 000000000..499662ac3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0045-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-KC.patch @@ -0,0 +1,235 @@ +From 27675470115548612cee9153903aaffffb68177b Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Wed, 13 Mar 2019 15:36:34 -0700 +Subject: [PATCH] char: ipmi: Add clock control logic into Aspeed LPC KCS + driver + +If LPC KCS driver is registered ahead of lpc-ctrl module, LPC KCS +block will be enabled without heart beating of LCLK until lpc-ctrl +enables the LCLK. This issue causes improper handling on host +interrupts when the host sends interrupt in that time frame. Then +kernel eventually forcibly disables the interrupt with dumping +stack and printing a 'nobody cared this irq' message out. + +To prevent this issue, all LPC sub-nodes should enable LCLK +individually so this patch adds clock control logic into the LPC +KCS driver. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + .../devicetree/bindings/ipmi/aspeed-kcs-bmc.txt | 3 ++ + arch/arm/boot/dts/aspeed-g4.dtsi | 35 ++++++++++++++++++++ + arch/arm/boot/dts/aspeed-g5.dtsi | 6 +++- + drivers/char/ipmi/kcs_bmc_aspeed.c | 37 ++++++++++++++++++---- + 4 files changed, 73 insertions(+), 8 deletions(-) + +diff --git a/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt b/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt +index d98a9bf45d6c..3453eb0bf8f2 100644 +--- a/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt ++++ b/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt +@@ -9,6 +9,8 @@ Required properties: + "aspeed,ast2400-kcs-bmc" + "aspeed,ast2500-kcs-bmc" + - interrupts : interrupt generated by the controller ++- clocks: contains a phandle to the syscon node describing the clocks. ++ There should then be one cell representing the clock to use. + - kcs_chan : The LPC channel number in the controller + - kcs_addr : The host CPU IO map address + +@@ -19,6 +21,7 @@ Example: + compatible = "aspeed,ast2500-kcs-bmc"; + reg = <0x0 0x80>; + interrupts = <8>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + kcs_chan = <3>; + kcs_addr = <0xCA2>; + status = "okay"; +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index 1c00828da913..f04006f4cd27 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -298,6 +298,33 @@ + lpc_bmc: lpc-bmc@0 { + compatible = "aspeed,ast2400-lpc-bmc"; + reg = <0x0 0x80>; ++ reg-io-width = <4>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x80>; ++ ++ kcs1: kcs1@0 { ++ compatible = "aspeed,ast2400-kcs-bmc"; ++ interrupts = <8>; ++ kcs_chan = <1>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; ++ kcs2: kcs2@0 { ++ compatible = "aspeed,ast2400-kcs-bmc"; ++ interrupts = <8>; ++ kcs_chan = <2>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; ++ kcs3: kcs3@0 { ++ compatible = "aspeed,ast2400-kcs-bmc"; ++ interrupts = <8>; ++ kcs_chan = <3>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; + }; + + lpc_host: lpc-host@80 { +@@ -309,6 +336,14 @@ + #size-cells = <1>; + ranges = <0x0 0x80 0x1e0>; + ++ kcs4: kcs4@0 { ++ compatible = "aspeed,ast2400-kcs-bmc"; ++ interrupts = <8>; ++ kcs_chan = <4>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; ++ status = "disabled"; ++ }; ++ + lpc_ctrl: lpc-ctrl@0 { + compatible = "aspeed,ast2400-lpc-ctrl"; + reg = <0x0 0x80>; +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index f2292bdfef91..d46b9ffc79de 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -135,7 +135,7 @@ + }; + + vic: interrupt-controller@1e6c0080 { +- compatible = "aspeed,ast2400-vic"; ++ compatible = "aspeed,ast2500-vic"; + interrupt-controller; + #interrupt-cells = <1>; + valid-sources = <0xfefff7ff 0x0807ffff>; +@@ -410,18 +410,21 @@ + compatible = "aspeed,ast2500-kcs-bmc"; + interrupts = <8>; + kcs_chan = <1>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + status = "disabled"; + }; + kcs2: kcs2@0 { + compatible = "aspeed,ast2500-kcs-bmc"; + interrupts = <8>; + kcs_chan = <2>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + status = "disabled"; + }; + kcs3: kcs3@0 { + compatible = "aspeed,ast2500-kcs-bmc"; + interrupts = <8>; + kcs_chan = <3>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + status = "disabled"; + }; + }; +@@ -439,6 +442,7 @@ + compatible = "aspeed,ast2500-kcs-bmc"; + interrupts = <8>; + kcs_chan = <4>; ++ clocks = <&syscon ASPEED_CLK_GATE_LCLK>; + status = "disabled"; + }; + +diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c +index 3c955946e647..bd1912dc5a21 100644 +--- a/drivers/char/ipmi/kcs_bmc_aspeed.c ++++ b/drivers/char/ipmi/kcs_bmc_aspeed.c +@@ -1,11 +1,10 @@ + // SPDX-License-Identifier: GPL-2.0 +-/* +- * Copyright (c) 2015-2018, Intel Corporation. +- */ ++// Copyright (c) 2015-2019, Intel Corporation. + + #define pr_fmt(fmt) "aspeed-kcs-bmc: " fmt + + #include <linux/atomic.h> ++#include <linux/clk.h> + #include <linux/errno.h> + #include <linux/interrupt.h> + #include <linux/io.h> +@@ -63,6 +62,7 @@ + + struct aspeed_kcs_bmc { + struct regmap *map; ++ struct clk *clk; + }; + + +@@ -264,36 +264,59 @@ static int aspeed_kcs_probe(struct platform_device *pdev) + return -ENODEV; + } + ++ priv->clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(priv->clk)) { ++ rc = PTR_ERR(priv->clk); ++ if (rc != -EPROBE_DEFER) ++ dev_err(dev, "couldn't get clock\n"); ++ return rc; ++ } ++ rc = clk_prepare_enable(priv->clk); ++ if (rc) { ++ dev_err(dev, "couldn't enable clock\n"); ++ return rc; ++ } ++ + kcs_bmc->ioreg = ast_kcs_bmc_ioregs[chan - 1]; + kcs_bmc->io_inputb = aspeed_kcs_inb; + kcs_bmc->io_outputb = aspeed_kcs_outb; + + dev_set_drvdata(dev, kcs_bmc); + +- aspeed_kcs_set_address(kcs_bmc, addr); +- aspeed_kcs_enable_channel(kcs_bmc, true); + rc = aspeed_kcs_config_irq(kcs_bmc, pdev); + if (rc) +- return rc; ++ goto err; + + rc = misc_register(&kcs_bmc->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); +- return rc; ++ goto err; + } + ++ aspeed_kcs_set_address(kcs_bmc, addr); ++ aspeed_kcs_enable_channel(kcs_bmc, true); ++ + pr_info("channel=%u addr=0x%x idr=0x%x odr=0x%x str=0x%x\n", + chan, addr, + kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str); + + return 0; ++ ++err: ++ aspeed_kcs_enable_channel(kcs_bmc, false); ++ clk_disable_unprepare(priv->clk); ++ ++ return rc; + } + + static int aspeed_kcs_remove(struct platform_device *pdev) + { + struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev); ++ struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc); + + misc_deregister(&kcs_bmc->miscdev); ++ aspeed_kcs_enable_channel(kcs_bmc, false); ++ clk_disable_unprepare(priv->clk); + + return 0; + } +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0047-misc-Block-error-printing-on-probe-defer-case-in-Asp.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0047-misc-Block-error-printing-on-probe-defer-case-in-Asp.patch new file mode 100644 index 000000000..c3d1f4e8d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0047-misc-Block-error-printing-on-probe-defer-case-in-Asp.patch @@ -0,0 +1,43 @@ +From 108b39883e73f822b8f03e0d3fe3818b85b29b41 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Wed, 13 Mar 2019 15:57:08 -0700 +Subject: [PATCH] misc: Block error printing on probe defer case in Aspeed LPC + ctrl + +This commit adds a checking code when it gets -EPROBE_DEFER while +getting a clock resource. In this case it doesn't need to print +out an error message because the probing will be re-visited. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/soc/aspeed/aspeed-lpc-ctrl.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/drivers/soc/aspeed/aspeed-lpc-ctrl.c b/drivers/soc/aspeed/aspeed-lpc-ctrl.c +index 01ed21e8bfee..ae08419834a3 100644 +--- a/drivers/soc/aspeed/aspeed-lpc-ctrl.c ++++ b/drivers/soc/aspeed/aspeed-lpc-ctrl.c +@@ -252,8 +252,10 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) + + lpc_ctrl->clk = devm_clk_get(dev, NULL); + if (IS_ERR(lpc_ctrl->clk)) { +- dev_err(dev, "couldn't get clock\n"); +- return PTR_ERR(lpc_ctrl->clk); ++ rc = PTR_ERR(lpc_ctrl->clk); ++ if (rc != -EPROBE_DEFER) ++ dev_err(dev, "couldn't get clock\n"); ++ return rc; + } + rc = clk_prepare_enable(lpc_ctrl->clk); + if (rc) { +@@ -275,6 +277,7 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) + + err: + clk_disable_unprepare(lpc_ctrl->clk); ++ + return rc; + } + +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0049-Suppress-excessive-HID-gadget-error-logs.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0049-Suppress-excessive-HID-gadget-error-logs.patch new file mode 100644 index 000000000..d0f98b9c1 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0049-Suppress-excessive-HID-gadget-error-logs.patch @@ -0,0 +1,43 @@ +From 7dd0a7c62e5885bb726ef2bd5007e79a50932c38 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Mon, 18 Mar 2019 14:06:36 -0700 +Subject: [PATCH] Suppress excessive HID gadget error logs + +HID events can be sent even when the host disconnects the HID +device according to the current graphic mode. For an example, if +KVM mouse events are sent when the host is in text mode, queueing +of end point messages will be dropped with this message: + +configfs-gadget gadget: usb_ep_queue error on int endpoint -108 + +This case is very usual case in BMC since BMC can control power +status of the host, so this commit suppress the error printing outs +with making HID gadget driver drop events quietly in the case. + +This should be a downstream only customization. Do not upstream it. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/usb/gadget/function/f_hid.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c +index f3816a5c861e..3a94584a9dbc 100644 +--- a/drivers/usb/gadget/function/f_hid.c ++++ b/drivers/usb/gadget/function/f_hid.c +@@ -395,8 +395,10 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, + + status = usb_ep_queue(hidg->in_ep, req, GFP_ATOMIC); + if (status < 0) { +- ERROR(hidg->func.config->cdev, +- "usb_ep_queue error on int endpoint %zd\n", status); ++ if (status != -ESHUTDOWN) ++ ERROR(hidg->func.config->cdev, ++ "usb_ep_queue error on int endpoint %zd\n", ++ status); + goto release_write_pending; + } else { + status = count; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0051-Add-AST2500-JTAG-device.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0051-Add-AST2500-JTAG-device.patch new file mode 100644 index 000000000..d9379f565 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0051-Add-AST2500-JTAG-device.patch @@ -0,0 +1,35 @@ +From 278740d23c1eac792c64608af4bfa3fae640965f Mon Sep 17 00:00:00 2001 +From: "Hunt, Bryan" <bryan.hunt@intel.com> +Date: Mon, 6 May 2019 10:02:14 -0700 +Subject: [PATCH] Add AST2500d JTAG driver + +Adding aspeed jtag device + +Signed-off-by: Hunt, Bryan <bryan.hunt@intel.com> +--- + arch/arm/boot/dts/aspeed-g5.dtsi | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index ed0b7217f55f..89a9febf6f14 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -389,6 +389,15 @@ + pinctrl-0 = <&pinctrl_espi_default>; + }; + ++ jtag: jtag@1e6e4000 { ++ compatible = "aspeed,ast2500-jtag"; ++ reg = <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>; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0052-drivers-jtag-Add-JTAG-core-driver.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0052-drivers-jtag-Add-JTAG-core-driver.patch new file mode 100644 index 000000000..3047746ca --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0052-drivers-jtag-Add-JTAG-core-driver.patch @@ -0,0 +1,920 @@ +From 520416545a47cffe66815f31bc1465eab8864554 Mon Sep 17 00:00:00 2001 +From: "Corona, Ernesto" <ernesto.corona@intel.com> +Date: Fri, 7 Jun 2019 07:37:39 -0800 +Subject: [PATCH v29 1/6] drivers: jtag: Add JTAG core driver + +JTAG class driver provide infrastructure to support hardware/software +JTAG platform drivers. It provide user layer API interface for flashing +and debugging external devices which equipped with JTAG interface +using standard transactions. + +Driver exposes set of IOCTL to user space for: +- XFER: + SIR (Scan Instruction Register, IEEE 1149.1 Data Register scan); + SDR (Scan Data Register, IEEE 1149.1 Instruction Register scan); +- GIOCSTATUS read the current TAPC state of the JTAG controller +- SIOCSTATE Forces the JTAG TAPC to go into a particular state. +- SIOCFREQ/GIOCFREQ for setting and reading JTAG frequency. +- IOCBITBANG for low level control of JTAG signals. + +Driver core provides set of internal APIs for allocation and +registration: +- jtag_register; +- jtag_unregister; +- jtag_alloc; +- jtag_free; + +Platform driver on registration with jtag-core creates the next +entry in dev folder: +/dev/jtagX + +Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com> +Signed-off-by: Jiri Pirko <jiri@mellanox.com> +Signed-off-by: Corona, Ernesto <ernesto.corona@intel.com> +Acked-by: Philippe Ombredanne <pombredanne@nexb.com> +Cc: Vadim Pasternak <vadimp@mellanox.com> +Cc: Masahiro Yamada <yamada.masahiro@socionext.com> +Cc: Paul Burton <paul.burton@mips.com> +Cc: Thomas Gleixner <tglx@linutronix.de> +Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> +Cc: Arnd Bergmann <arnd@arndb.de> +Cc: Boris Brezillon <bbrezillon@kernel.org> +Cc: Randy Dunlap <rdunlap@infradead.org> +Cc: Johan Hovold <johan@kernel.org> +Cc: Jens Axboe <axboe@kernel.dk> +Cc: Joel Stanley <joel@jms.id.au> +Cc: Palmer Dabbelt <palmer@sifive.com> +Cc: Kees Cook <keescook@chromium.org> +Cc: Steven A Filary <steven.a.filary@intel.com> +Cc: Bryan Hunt <bryan.hunt@intel.com> +--- +v28->v29 +Comments pointed by Steven Filary <steven.a.filary@intel.com> +- Expand bitbang function to accept multiples bitbang operations within a + single JTAG_IOCBITBANG call. It will receive a buffer with TDI and TMS + values and it is expected that driver fills TDO fields with its + corresponding output value for every transaction. +- Always setup JTAG controller to master mode but disable JTAG output when + the driver is not in use to allow other HW to own the JTAG bus. Remove SCU + register accesses. This register controls the JTAG controller mode + (master/slave). +- Fix static analysis issues +- Add support for multichain. Set tap state and xfer operations now include + two tap state arguments: current state and end state. + +v27->v28 +Comments pointed by Steven Filary <steven.a.filary@intel.com> +- Replace JTAG_IOCRUNTEST with JTAG_SIOCSTATE adding support for all TAPC + end states in SW mode using a lookup table to navigate across states. +- Add support for simultaneous READ/WRITE transfers(JTAG_READ_WRITE_XFER). +- Support for switching JTAG controller mode between slave and master + mode. +- Setup JTAG controller mode to master only when the driver is opened, + letting + other HW to own the JTAG bus when it isn't in use. +- Include JTAG bit bang IOCTL for low level JTAG control usage + (JTAG_IOCBITBANG). + +v24->v25 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- set values to enums in jtag.h + +v23->v24 +Notifications from kbuild test robot <lkp@intel.com> +- Add include types.h header to jtag.h +- remove unecessary jtag_release + +v22->v23 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- remove restriction of allocated JTAG devs- +- add validation fo idle values +- remove unnecessary blank line +- change retcode for xfer +- remove unecessary jtag_release callback +- remove unecessary defined fron jtag.h +- align in one line define JTAG_IOCRUNTEST + +v21->v22 +Comments pointed by Andy Shevchenko <andy.shevchenko@gmail.com> +- Fix 0x0f -> 0x0F in ioctl-number.txt +- Add description to #define MAX_JTAG_NAME_LEN +- Remove unnecessary entry *dev from struct jtag +- Remove redundant parens +- Described mandatory callbacks and removed unnecessary +- Set JTAG_MAX_XFER_DATA_LEN to power of 2 +- rework driver alloc/register to devm_ variant +- increasing line length up to 84 in order to improve readability. + +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- fix spell in ABI doccumentation + +v20->v21 + Comments pointed by Randy Dunlap <rdunlap@infradead.org> + - Fix JTAG dirver help in Kconfig + +v19->v20 +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- Fix JTAG dirver help in Kconfig + +Notifications from kbuild test robot <lkp@intel.com> +- fix incompatible type casts + +v18->v19 +Comments pointed by Julia Cartwright <juliac@eso.teric.us> +- Fix memory leak on jtag_alloc exit + +v17->v18 +Comments pointed by Julia Cartwright <juliac@eso.teric.us> +- Change to return -EOPNOTSUPP in case of error in JTAG_GIOCFREQ +- Add ops callbacks check to jtag_alloc +- Add err check for copy_to_user +- Move the kfree() above the if (err) in JTAG_IOCXFER +- remove unnecessary check for error after put_user +- add padding to struct jtag_xfer + +v16->v17 +Comments pointed by Julia Cartwright <juliac@eso.teric.us> +- Fix memory allocation on jtag alloc +- Move out unnecessary form lock on jtag open +- Rework jtag register behavior + +v15->v16 +Comments pointed by Florian Fainelli <f.fainelli@gmail.com> +- move check jtag->ops->* in ioctl before get_user() +- change error type -EINVAL --> -EBUSY on open already opened jtag +- remove unnecessary ARCH_DMA_MINALIGN flag from kzalloc +- remove define ARCH_DMA_MINALIGN + +v14->v15 +v13->v14 +Comments pointed by Philippe Ombredanne <pombredanne@nexb.com> +- Change style of head block comment from /**/ to // + +v12->v13 +Comments pointed by Philippe Ombredanne <pombredanne@nexb.com> +- Change jtag.c licence type to + SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + and reorder line with license in description + +v11->v12 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- Change jtag.h licence type to + SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + and reorder line with license in description + +Comments pointed by Chip Bilbrey <chip@bilbrey.org> +- Remove Apeed reference from uapi jtag.h header +- Remove access mode from xfer and idle transactions +- Add new ioctl JTAG_SIOCMODE for set hw mode +- Add single open per device blocking + +v10->v11 +Notifications from kbuild test robot <lkp@intel.com> +- Add include types.h header to jtag.h +- fix incompatible type of xfer callback +- remove rdundant class defination +- Fix return order in case of xfer error + +V9->v10 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- remove unnecessary alignment for pirv data +- move jtag_copy_to_user and jtag_copy_from_user code just to ioctl +- move int jtag_run_test_idle_op and jtag_xfer_op code + just to ioctl +- change return error codes to more applicable +- add missing error checks +- fix error check order in ioctl +- remove unnecessary blank lines +- add param validation to ioctl +- remove compat_ioctl +- remove only one open per JTAG port blocking. + User will care about this. +- Fix idr memory leak on jtag_exit +- change cdev device type to misc + +V8->v9 +Comments pointed by Arnd Bergmann <arnd@arndb.de> +- use get_user() instead of __get_user(). +- change jtag->open type from int to atomic_t +- remove spinlock on jtg_open +- remove mutex on jtag_register +- add unregister_chrdev_region on jtag_init err +- add unregister_chrdev_region on jtag_exit +- remove unnecessary pointer casts +- add *data parameter to xfer function prototype + +v7->v8 +Comments pointed by Moritz Fischer <moritz.fischer@ettus.com> +- Fix misspelling s/friver/driver + +v6->v7 +Notifications from kbuild test robot <lkp@intel.com> +- Remove include asm/types.h from jtag.h +- Add include <linux/types.h> to jtag.c + +v5->v6 +v4->v5 + +v3->v4 +Comments pointed by Arnd Bergmann <arnd@arndb.de> +- change transaction pointer tdio type to __u64 +- change internal status type from enum to __u32 +- reorder jtag_xfer members to avoid the implied padding +- add __packed attribute to jtag_xfer and jtag_run_test_idle + +v2->v3 +Notifications from kbuild test robot <lkp@intel.com> +- Change include path to <linux/types.h> in jtag.h + +v1->v2 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- Change license type from GPLv2/BSD to GPLv2 +- Change type of variables which crossed user/kernel to __type +- Remove "default n" from Kconfig + +Comments pointed by Andrew Lunn <andrew@lunn.ch> +- Change list_add_tail in jtag_unregister to list_del + +Comments pointed by Neil Armstrong <narmstrong@baylibre.com> +- Add SPDX-License-Identifier instead of license text + +Comments pointed by Arnd Bergmann <arnd@arndb.de> +- Change __copy_to_user to memdup_user +- Change __put_user to put_user +- Change type of variables to __type for compatible 32 and 64-bit systems +- Add check for maximum xfer data size +- Change lookup data mechanism to get jtag data from inode +- Add .compat_ioctl to file ops +- Add mem alignment for jtag priv data + +Comments pointed by Tobias Klauser <tklauser@distanz.ch> +- Change function names to avoid match with variable types +- Fix description for jtag_ru_test_idle in uapi jtag.h +- Fix misprints IDEL/IDLE, trough/through +--- + drivers/Kconfig | 1 + + drivers/Makefile | 1 + + drivers/jtag/Kconfig | 17 +++ + drivers/jtag/Makefile | 1 + + drivers/jtag/jtag.c | 321 ++++++++++++++++++++++++++++++++++++++++++++++ + include/linux/jtag.h | 47 +++++++ + include/uapi/linux/jtag.h | 214 +++++++++++++++++++++++++++++++ + 7 files changed, 602 insertions(+) + create mode 100644 drivers/jtag/Kconfig + create mode 100644 drivers/jtag/Makefile + create mode 100644 drivers/jtag/jtag.c + create mode 100644 include/linux/jtag.h + create mode 100644 include/uapi/linux/jtag.h + +diff --git a/drivers/Kconfig b/drivers/Kconfig +index 339ac306ded6..fb970bbe6b03 100644 +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -234,4 +234,5 @@ source "drivers/counter/Kconfig" + + source "drivers/peci/Kconfig" + ++source "drivers/jtag/Kconfig" + endmenu +diff --git a/drivers/Makefile b/drivers/Makefile +index 18d91cd5ac0f..b4d21c963923 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -189,3 +189,4 @@ obj-$(CONFIG_GNSS) += gnss/ + obj-$(CONFIG_INTERCONNECT) += interconnect/ + obj-$(CONFIG_COUNTER) += counter/ + 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..47771fcd3c5b +--- /dev/null ++++ b/drivers/jtag/Kconfig +@@ -0,0 +1,17 @@ ++menuconfig JTAG ++ tristate "JTAG support" ++ help ++ This provides basic core functionality support for JTAG class devices. ++ Hardware that is equipped with a JTAG microcontroller can be ++ supported by using this driver's interfaces. ++ This driver exposes a set of IOCTLs to the user space for ++ the following commands: ++ SDR: Performs an IEEE 1149.1 Data Register scan ++ SIR: Performs an IEEE 1149.1 Instruction Register scan. ++ RUNTEST: Forces the IEEE 1149.1 bus to a run state for a specified ++ number of clocks or a specified time period. ++ ++ 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. +diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile +new file mode 100644 +index 000000000000..af374939a9e6 +--- /dev/null ++++ b/drivers/jtag/Makefile +@@ -0,0 +1 @@ ++obj-$(CONFIG_JTAG) += jtag.o +diff --git a/drivers/jtag/jtag.c b/drivers/jtag/jtag.c +new file mode 100644 +index 000000000000..39a4d88a9c21 +--- /dev/null ++++ b/drivers/jtag/jtag.c +@@ -0,0 +1,321 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// Copyright (c) 2018 Mellanox Technologies. All rights reserved. ++// Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com> ++// Copyright (c) 2019 Intel Corporation ++ ++#include <linux/cdev.h> ++#include <linux/device.h> ++#include <linux/jtag.h> ++#include <linux/kernel.h> ++#include <linux/list.h> ++#include <linux/miscdevice.h> ++#include <linux/module.h> ++#include <linux/rtnetlink.h> ++#include <linux/spinlock.h> ++#include <linux/types.h> ++#include <uapi/linux/jtag.h> ++ ++struct jtag { ++ struct miscdevice miscdev; ++ const struct jtag_ops *ops; ++ int id; ++ unsigned long priv[0]; ++}; ++ ++static DEFINE_IDA(jtag_ida); ++ ++void *jtag_priv(struct jtag *jtag) ++{ ++ return jtag->priv; ++} ++EXPORT_SYMBOL_GPL(jtag_priv); ++ ++static long jtag_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ struct jtag *jtag = file->private_data; ++ struct jtag_tap_state tapstate; ++ struct jtag_xfer xfer; ++ struct bitbang_packet bitbang; ++ struct tck_bitbang *bitbang_data; ++ struct jtag_mode mode; ++ u8 *xfer_data; ++ u32 data_size; ++ u32 value; ++ int err; ++ ++ if (!arg) ++ return -EINVAL; ++ ++ switch (cmd) { ++ case JTAG_GIOCFREQ: ++ if (!jtag->ops->freq_get) ++ return -EOPNOTSUPP; ++ ++ err = jtag->ops->freq_get(jtag, &value); ++ if (err) ++ break; ++ ++ if (put_user(value, (__u32 __user *)arg)) ++ err = -EFAULT; ++ break; ++ ++ case JTAG_SIOCFREQ: ++ if (!jtag->ops->freq_set) ++ return -EOPNOTSUPP; ++ ++ if (get_user(value, (__u32 __user *)arg)) ++ return -EFAULT; ++ if (value == 0) ++ return -EINVAL; ++ ++ err = jtag->ops->freq_set(jtag, value); ++ break; ++ ++ case JTAG_SIOCSTATE: ++ if (copy_from_user(&tapstate, (const void __user *)arg, ++ sizeof(struct jtag_tap_state))) ++ return -EFAULT; ++ ++ if (tapstate.from > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ if (tapstate.endstate > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ if (tapstate.reset > JTAG_FORCE_RESET) ++ return -EINVAL; ++ ++ err = jtag->ops->status_set(jtag, &tapstate); ++ break; ++ ++ case JTAG_IOCXFER: ++ if (copy_from_user(&xfer, (const void __user *)arg, ++ sizeof(struct jtag_xfer))) ++ return -EFAULT; ++ ++ if (xfer.length >= JTAG_MAX_XFER_DATA_LEN) ++ return -EINVAL; ++ ++ if (xfer.type > JTAG_SDR_XFER) ++ return -EINVAL; ++ ++ if (xfer.direction > JTAG_READ_WRITE_XFER) ++ return -EINVAL; ++ ++ if (xfer.from > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ if (xfer.endstate > JTAG_STATE_CURRENT) ++ return -EINVAL; ++ ++ data_size = DIV_ROUND_UP(xfer.length, BITS_PER_BYTE); ++ xfer_data = memdup_user(u64_to_user_ptr(xfer.tdio), data_size); ++ if (IS_ERR(xfer_data)) ++ return -EFAULT; ++ ++ err = jtag->ops->xfer(jtag, &xfer, xfer_data); ++ if (err) { ++ kfree(xfer_data); ++ return err; ++ } ++ ++ err = copy_to_user(u64_to_user_ptr(xfer.tdio), ++ (void *)xfer_data, data_size); ++ kfree(xfer_data); ++ if (err) ++ return -EFAULT; ++ ++ if (copy_to_user((void __user *)arg, (void *)&xfer, ++ sizeof(struct jtag_xfer))) ++ return -EFAULT; ++ break; ++ ++ case JTAG_GIOCSTATUS: ++ err = jtag->ops->status_get(jtag, &value); ++ if (err) ++ break; ++ ++ err = put_user(value, (__u32 __user *)arg); ++ break; ++ case JTAG_IOCBITBANG: ++ if (copy_from_user(&bitbang, (const void __user *)arg, ++ sizeof(struct bitbang_packet))) ++ return -EFAULT; ++ ++ if (bitbang.length >= JTAG_MAX_XFER_DATA_LEN) ++ return -EINVAL; ++ ++ data_size = bitbang.length * sizeof(struct tck_bitbang); ++ bitbang_data = memdup_user((void __user *)bitbang.data, ++ data_size); ++ if (IS_ERR(bitbang_data)) ++ return -EFAULT; ++ ++ err = jtag->ops->bitbang(jtag, &bitbang, bitbang_data); ++ if (err) { ++ kfree(bitbang_data); ++ return err; ++ } ++ err = copy_to_user((void __user *)bitbang.data, ++ (void *)bitbang_data, data_size); ++ kfree(bitbang_data); ++ if (err) ++ return -EFAULT; ++ break; ++ case JTAG_SIOCMODE: ++ if (!jtag->ops->mode_set) ++ return -EOPNOTSUPP; ++ ++ if (copy_from_user(&mode, (const void __user *)arg, ++ sizeof(struct jtag_mode))) ++ return -EFAULT; ++ ++ err = jtag->ops->mode_set(jtag, &mode); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ return err; ++} ++ ++static int jtag_open(struct inode *inode, struct file *file) ++{ ++ struct jtag *jtag = container_of(file->private_data, ++ struct jtag, ++ miscdev); ++ ++ file->private_data = jtag; ++ if (jtag->ops->enable(jtag)) ++ return -EBUSY; ++ return nonseekable_open(inode, file); ++} ++ ++static int jtag_release(struct inode *inode, struct file *file) ++{ ++ struct jtag *jtag = file->private_data; ++ ++ if (jtag->ops->disable(jtag)) ++ return -EBUSY; ++ return 0; ++} ++ ++static const struct file_operations jtag_fops = { ++ .owner = THIS_MODULE, ++ .open = jtag_open, ++ .llseek = noop_llseek, ++ .unlocked_ioctl = jtag_ioctl, ++ .release = jtag_release, ++}; ++ ++struct jtag *jtag_alloc(struct device *host, size_t priv_size, ++ const struct jtag_ops *ops) ++{ ++ struct jtag *jtag; ++ ++ if (!host) ++ return NULL; ++ ++ if (!ops) ++ return NULL; ++ ++ if (!ops->status_set || !ops->status_get || !ops->xfer) ++ return NULL; ++ ++ jtag = kzalloc(sizeof(*jtag) + priv_size, GFP_KERNEL); ++ if (!jtag) ++ return NULL; ++ ++ jtag->ops = ops; ++ jtag->miscdev.parent = host; ++ ++ return jtag; ++} ++EXPORT_SYMBOL_GPL(jtag_alloc); ++ ++void jtag_free(struct jtag *jtag) ++{ ++ kfree(jtag); ++} ++EXPORT_SYMBOL_GPL(jtag_free); ++ ++static int jtag_register(struct jtag *jtag) ++{ ++ struct device *dev = jtag->miscdev.parent; ++ int err; ++ int id; ++ ++ if (!dev) ++ return -ENODEV; ++ ++ id = ida_simple_get(&jtag_ida, 0, 0, GFP_KERNEL); ++ if (id < 0) ++ return id; ++ ++ jtag->id = id; ++ ++ jtag->miscdev.fops = &jtag_fops; ++ jtag->miscdev.minor = MISC_DYNAMIC_MINOR; ++ jtag->miscdev.name = kasprintf(GFP_KERNEL, "jtag%d", id); ++ if (!jtag->miscdev.name) { ++ err = -ENOMEM; ++ goto err_jtag_alloc; ++ } ++ ++ err = misc_register(&jtag->miscdev); ++ if (err) { ++ dev_err(jtag->miscdev.parent, "Unable to register device\n"); ++ goto err_jtag_name; ++ } ++ return 0; ++ ++err_jtag_name: ++ kfree(jtag->miscdev.name); ++err_jtag_alloc: ++ ida_simple_remove(&jtag_ida, id); ++ return err; ++} ++ ++static void jtag_unregister(struct jtag *jtag) ++{ ++ misc_deregister(&jtag->miscdev); ++ kfree(jtag->miscdev.name); ++ ida_simple_remove(&jtag_ida, jtag->id); ++} ++ ++static void devm_jtag_unregister(struct device *dev, void *res) ++{ ++ jtag_unregister(*(struct jtag **)res); ++} ++ ++int devm_jtag_register(struct device *dev, struct jtag *jtag) ++{ ++ struct jtag **ptr; ++ int ret; ++ ++ ptr = devres_alloc(devm_jtag_unregister, sizeof(struct jtag *), ++ GFP_KERNEL); ++ if (!ptr) ++ return -ENOMEM; ++ ++ ret = jtag_register(jtag); ++ if (!ret) { ++ *ptr = jtag; ++ devres_add(dev, ptr); ++ } else { ++ devres_free(ptr); ++ } ++ return ret; ++} ++EXPORT_SYMBOL_GPL(devm_jtag_register); ++ ++static void __exit jtag_exit(void) ++{ ++ ida_destroy(&jtag_ida); ++} ++ ++module_exit(jtag_exit); ++ ++MODULE_AUTHOR("Oleksandr Shamray <oleksandrs@mellanox.com>"); ++MODULE_DESCRIPTION("Generic jtag support"); ++MODULE_LICENSE("GPL v2"); +diff --git a/include/linux/jtag.h b/include/linux/jtag.h +new file mode 100644 +index 000000000000..fab12dc4fc5e +--- /dev/null ++++ b/include/linux/jtag.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (c) 2018 Mellanox Technologies. All rights reserved. */ ++/* Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com> */ ++/* Copyright (c) 2019 Intel Corporation */ ++ ++#ifndef __LINUX_JTAG_H ++#define __LINUX_JTAG_H ++ ++#include <linux/types.h> ++#include <uapi/linux/jtag.h> ++ ++#define JTAG_MAX_XFER_DATA_LEN 65535 ++ ++struct jtag; ++/** ++ * struct jtag_ops - callbacks for JTAG control functions: ++ * ++ * @freq_get: get frequency function. Filled by dev driver ++ * @freq_set: set frequency function. Filled by dev driver ++ * @status_get: get JTAG TAPC state function. Mandatory, Filled by dev driver ++ * @status_set: set JTAG TAPC state function. Mandatory, Filled by dev driver ++ * @xfer: send JTAG xfer function. Mandatory func. Filled by dev driver ++ * @mode_set: set specific work mode for JTAG. Filled by dev driver ++ * @bitbang: set low level bitbang operations. Filled by dev driver ++ * @enable: enables JTAG interface in master mode. Filled by dev driver ++ * @disable: disables JTAG interface master mode. Filled by dev driver ++ */ ++struct jtag_ops { ++ int (*freq_get)(struct jtag *jtag, u32 *freq); ++ int (*freq_set)(struct jtag *jtag, u32 freq); ++ int (*status_get)(struct jtag *jtag, u32 *state); ++ int (*status_set)(struct jtag *jtag, struct jtag_tap_state *endst); ++ int (*xfer)(struct jtag *jtag, struct jtag_xfer *xfer, u8 *xfer_data); ++ int (*mode_set)(struct jtag *jtag, struct jtag_mode *jtag_mode); ++ int (*bitbang)(struct jtag *jtag, struct bitbang_packet *bitbang, ++ struct tck_bitbang *bitbang_data); ++ int (*enable)(struct jtag *jtag); ++ int (*disable)(struct jtag *jtag); ++}; ++ ++void *jtag_priv(struct jtag *jtag); ++int devm_jtag_register(struct device *dev, struct jtag *jtag); ++struct jtag *jtag_alloc(struct device *host, size_t priv_size, ++ const struct jtag_ops *ops); ++void jtag_free(struct jtag *jtag); ++ ++#endif /* __LINUX_JTAG_H */ +diff --git a/include/uapi/linux/jtag.h b/include/uapi/linux/jtag.h +new file mode 100644 +index 000000000000..315e59577a17 +--- /dev/null ++++ b/include/uapi/linux/jtag.h +@@ -0,0 +1,214 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (c) 2018 Mellanox Technologies. All rights reserved. */ ++/* Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com> */ ++/* Copyright (c) 2019 Intel Corporation */ ++ ++#ifndef __UAPI_LINUX_JTAG_H ++#define __UAPI_LINUX_JTAG_H ++ ++/* ++ * JTAG_XFER_MODE: JTAG transfer mode. Used to set JTAG controller transfer mode ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_MODE 0 ++/* ++ * JTAG_CONTROL_MODE: JTAG controller mode. Used to set JTAG controller mode ++ * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_CONTROL_MODE 1 ++/* ++ * JTAG_MASTER_OUTPUT_DISABLE: JTAG master mode output disable, it is used to ++ * enable other devices to own the JTAG bus. ++ * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_MASTER_OUTPUT_DISABLE 0 ++/* ++ * JTAG_MASTER_MODE: JTAG master mode. Used to set JTAG controller master mode ++ * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_MASTER_MODE 1 ++/* ++ * JTAG_XFER_HW_MODE: JTAG hardware mode. Used to set HW drived or bitbang ++ * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_HW_MODE 1 ++/* ++ * JTAG_XFER_SW_MODE: JTAG software mode. Used to set SW drived or bitbang ++ * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE ++ */ ++#define JTAG_XFER_SW_MODE 0 ++ ++/** ++ * enum jtag_tapstate: ++ * ++ * @JTAG_STATE_TLRESET: JTAG state machine Test Logic Reset state ++ * @JTAG_STATE_IDLE: JTAG state machine IDLE state ++ * @JTAG_STATE_SELECTDR: JTAG state machine SELECT_DR state ++ * @JTAG_STATE_CAPTUREDR: JTAG state machine CAPTURE_DR state ++ * @JTAG_STATE_SHIFTDR: JTAG state machine SHIFT_DR state ++ * @JTAG_STATE_EXIT1DR: JTAG state machine EXIT-1 DR state ++ * @JTAG_STATE_PAUSEDR: JTAG state machine PAUSE_DR state ++ * @JTAG_STATE_EXIT2DR: JTAG state machine EXIT-2 DR state ++ * @JTAG_STATE_UPDATEDR: JTAG state machine UPDATE DR state ++ * @JTAG_STATE_SELECTIR: JTAG state machine SELECT_IR state ++ * @JTAG_STATE_CAPTUREIR: JTAG state machine CAPTURE_IR state ++ * @JTAG_STATE_SHIFTIR: JTAG state machine SHIFT_IR state ++ * @JTAG_STATE_EXIT1IR: JTAG state machine EXIT-1 IR state ++ * @JTAG_STATE_PAUSEIR: JTAG state machine PAUSE_IR state ++ * @JTAG_STATE_EXIT2IR: JTAG state machine EXIT-2 IR state ++ * @JTAG_STATE_UPDATEIR: JTAG state machine UPDATE IR state ++ * @JTAG_STATE_CURRENT: JTAG current state, saved by driver ++ */ ++enum jtag_tapstate { ++ JTAG_STATE_TLRESET, ++ JTAG_STATE_IDLE, ++ JTAG_STATE_SELECTDR, ++ JTAG_STATE_CAPTUREDR, ++ JTAG_STATE_SHIFTDR, ++ JTAG_STATE_EXIT1DR, ++ JTAG_STATE_PAUSEDR, ++ JTAG_STATE_EXIT2DR, ++ JTAG_STATE_UPDATEDR, ++ JTAG_STATE_SELECTIR, ++ JTAG_STATE_CAPTUREIR, ++ JTAG_STATE_SHIFTIR, ++ JTAG_STATE_EXIT1IR, ++ JTAG_STATE_PAUSEIR, ++ JTAG_STATE_EXIT2IR, ++ JTAG_STATE_UPDATEIR, ++ JTAG_STATE_CURRENT ++}; ++ ++/** ++ * enum jtag_reset: ++ * ++ * @JTAG_NO_RESET: JTAG run TAP from current state ++ * @JTAG_FORCE_RESET: JTAG force TAP to reset state ++ */ ++enum jtag_reset { ++ JTAG_NO_RESET = 0, ++ JTAG_FORCE_RESET = 1, ++}; ++ ++/** ++ * enum jtag_xfer_type: ++ * ++ * @JTAG_SIR_XFER: SIR transfer ++ * @JTAG_SDR_XFER: SDR transfer ++ */ ++enum jtag_xfer_type { ++ JTAG_SIR_XFER = 0, ++ JTAG_SDR_XFER = 1, ++}; ++ ++/** ++ * enum jtag_xfer_direction: ++ * ++ * @JTAG_READ_XFER: read transfer ++ * @JTAG_WRITE_XFER: write transfer ++ * @JTAG_READ_WRITE_XFER: read & write transfer ++ */ ++enum jtag_xfer_direction { ++ JTAG_READ_XFER = 1, ++ JTAG_WRITE_XFER = 2, ++ JTAG_READ_WRITE_XFER = 3, ++}; ++ ++/** ++ * struct jtag_tap_state - forces JTAG state machine to go into a TAPC ++ * state ++ * ++ * @reset: 0 - run IDLE/PAUSE from current state ++ * 1 - go through TEST_LOGIC/RESET state before IDLE/PAUSE ++ * @end: completion flag ++ * @tck: clock counter ++ * ++ * Structure provide interface to JTAG device for JTAG set state execution. ++ */ ++struct jtag_tap_state { ++ __u8 reset; ++ __u8 from; ++ __u8 endstate; ++ __u8 tck; ++}; ++ ++/** ++ * struct jtag_xfer - jtag xfer: ++ * ++ * @type: transfer type ++ * @direction: xfer direction ++ * @from: xfer current state ++ * @endstate: xfer end state ++ * @padding: xfer padding ++ * @length: xfer bits length ++ * @tdio : xfer data array ++ * ++ * Structure provide interface to JTAG device for JTAG SDR/SIR xfer execution. ++ */ ++struct jtag_xfer { ++ __u8 type; ++ __u8 direction; ++ __u8 from; ++ __u8 endstate; ++ __u8 padding; ++ __u32 length; ++ __u64 tdio; ++}; ++ ++/** ++ * struct bitbang_packet - jtag bitbang array packet: ++ * ++ * @data: JTAG Bitbang struct array pointer(input/output) ++ * @length: array size (input) ++ * ++ * Structure provide interface to JTAG device for JTAG bitbang bundle execution ++ */ ++struct bitbang_packet { ++ struct tck_bitbang *data; ++ __u32 length; ++} __attribute__((__packed__)); ++ ++/** ++ * struct jtag_bitbang - jtag bitbang: ++ * ++ * @tms: JTAG TMS ++ * @tdi: JTAG TDI (input) ++ * @tdo: JTAG TDO (output) ++ * ++ * Structure provide interface to JTAG device for JTAG bitbang execution. ++ */ ++struct tck_bitbang { ++ __u8 tms; ++ __u8 tdi; ++ __u8 tdo; ++} __attribute__((__packed__)); ++ ++/** ++ * struct jtag_mode - jtag mode: ++ * ++ * @feature: 0 - JTAG feature setting selector for JTAG controller HW/SW ++ * 1 - JTAG feature setting selector for controller bus master ++ * mode output (enable / disable). ++ * @mode: (0 - SW / 1 - HW) for JTAG_XFER_MODE feature(0) ++ * (0 - output disable / 1 - output enable) for JTAG_CONTROL_MODE ++ * feature(1) ++ * ++ * Structure provide configuration modes to JTAG device. ++ */ ++struct jtag_mode { ++ __u32 feature; ++ __u32 mode; ++}; ++ ++/* ioctl interface */ ++#define __JTAG_IOCTL_MAGIC 0xb2 ++ ++#define JTAG_SIOCSTATE _IOW(__JTAG_IOCTL_MAGIC, 0, struct jtag_tap_state) ++#define JTAG_SIOCFREQ _IOW(__JTAG_IOCTL_MAGIC, 1, unsigned int) ++#define JTAG_GIOCFREQ _IOR(__JTAG_IOCTL_MAGIC, 2, unsigned int) ++#define JTAG_IOCXFER _IOWR(__JTAG_IOCTL_MAGIC, 3, struct jtag_xfer) ++#define JTAG_GIOCSTATUS _IOWR(__JTAG_IOCTL_MAGIC, 4, enum jtag_tapstate) ++#define JTAG_SIOCMODE _IOW(__JTAG_IOCTL_MAGIC, 5, unsigned int) ++#define JTAG_IOCBITBANG _IOW(__JTAG_IOCTL_MAGIC, 6, unsigned int) ++ ++#endif /* __UAPI_LINUX_JTAG_H */ +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0053-Add-Aspeed-SoC-24xx-and-25xx-families-JTAG.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0053-Add-Aspeed-SoC-24xx-and-25xx-families-JTAG.patch new file mode 100644 index 000000000..94722d6c4 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0053-Add-Aspeed-SoC-24xx-and-25xx-families-JTAG.patch @@ -0,0 +1,1294 @@ +From 817a43d1b1e197e7eff43492599469bbc23bf0fd Mon Sep 17 00:00:00 2001 +From: "Corona, Ernesto" <ernesto.corona@intel.com> +Date: Mon, 3 Jun 2019 08:22:09 -0800 +Subject: [PATCH v29 2/6] Add Aspeed SoC 24xx and 25xx families JTAG master + driver + +Driver adds support of Aspeed 2500/2400 series SOC JTAG master controller. + +Driver implements the following jtag ops: +- freq_get; +- freq_set; +- status_get; +- status_set +- xfer; +- mode_set; +- bitbang; +- enable; +- disable; + +It has been tested on Mellanox system with BMC equipped with +Aspeed 2520 SoC for programming CPLD devices. + +It has also been tested on Intel system using Aspeed 25xx SoC +for JTAG communication. + +Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com> +Signed-off-by: Jiri Pirko <jiri@mellanox.com> +Signed-off-by: Corona, Ernesto <ernesto.corona@intel.com> +Acked-by: Arnd Bergmann <arnd@arndb.de> +Acked-by: Philippe Ombredanne <pombredanne@nexb.com> +Acked-by: Joel Stanley <joel@jms.id.au> +Cc: Vadim Pasternak <vadimp@mellanox.com> +Cc: Andrew Jeffery <andrew@aj.id.au> +Cc: Steven A Filary <steven.a.filary@intel.com> +Cc: Bryan Hunt <bryan.hunt@intel.com> +--- +v28->v29 +Comments pointed by Steven Filary <steven.a.filary@intel.com> +- Expand bitbang function to accept multiples bitbang operations within a + single JTAG_IOCBITBANG call. It will receive a buffer with TDI and TMS + values and it is expected that driver fills TDO fields with its + corresponding output value for every transaction. +- Always setup JTAG controller to master mode but disable JTAG output when + the driver is not in use to allow other HW to own the JTAG bus. Remove SCU + register accesses. This register controls the JTAG controller mode + (master/slave). +- Encansulate dev_dgb message into DEBUG_JTAG macros to improve driver's JTAG + performace. +- Add support for multichain. Set tap state and xfer operations now include + two tap state arguments: current state and end state. + +v27->v28 +Comments pointed by Steven Filary <steven.a.filary@intel.com> +- Replace JTAG_IOCRUNTEST with JTAG_SIOCSTATE adding support for all TAPC + end states in SW mode using a lookup table to navigate across states. +- Add support for simultaneous READ/WRITE transfers(JTAG_READ_WRITE_XFER). +- Support for switching JTAG controller mode between slave and master + mode. +- Setup JTAG controller mode to master only when the driver is opened, + letting other HW to own the JTAG bus when it isn't in use. +- Include JTAG bit bang IOCTL for low level JTAG control usage + (JTAG_IOCBITBANG). +- Add debug traces. +- Add support for register polling (default) due it is 3 times faster than + interrupt mode. Define USE_INTERRUPTS macro to enable interrupt usage. +- Remove unnecessary delays for aspeed_jtag_status_set function. It makes + SW mode 4 times faster. +- Clean data buffer on aspeed_jtag_xfer_sw before tdo writes to avoid data + output corruption for read operations in SW mode. +- Correct register settings for HW mode transfer operations. +- Propagate ret codes all the way from low level functions up to + JTAG_IOCXFER call. +- Support for partitioned transfers. Single JTAG transfer through + multiples JTAG_IOCXFER calls. Now end transmission(scan_end) also + evaluates transfer end state. + +v26->v27 +Changes made by Oleksandr Shamray <oleksandrs@mellamnox.com> +- change aspeed_jtag_sw_delay to udelay function in bit-bang operation + +v25->v26 +v24->v25 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- reduced debug printouts + +v23->v24 +v22->v23 +v21->v22 +Comments pointed by Andy Shevchenko <andy.shevchenko@gmail.com> +- rearrange ASPEED register defines +- simplified JTAG divider calculation formula +- change delay function in bit-bang operation +- add helper functions for TAP states switching +- remove unnecessary comments +- remove redundant debug messages +- make dines for repetative register bit sets +- fixed indentation +- change checks from negative to positive +- add error check for clk_prepare_enable +- rework driver alloc/register to devm_ variant +- Increasing line length up to 85 in order to improve readability + +v20->v21 +v19->v20 +Notifications from kbuild test robot <lkp@intel.com> +- add static declaration to 'aspeed_jtag_init' and + 'aspeed_jtag_deinit' functions + +v18->v19 +v17->v18 +v16->v17 +v15->v16 +Comments pointed by Joel Stanley <joel.stan@gmail.com> +- Add reset_control on Jtag init/deinit + +v14->v15 +Comments pointed by Joel Stanley <joel.stan@gmail.com> +- Add ARCH_ASPEED || COMPILE_TEST to Kconfig +- remove unused offset variable +- remove "aspeed_jtag" from dev_err and dev_dbg messages +- change clk_prepare_enable initialisation order + +v13->v14 +Comments pointed by Philippe Ombredanne <pombredanne@nexb.com> +- Change style of head block comment from /**/ to // + +v12->v13 +Comments pointed by Philippe Ombredanne <pombredanne@nexb.com> +- Change jtag-aspeed.c licence type to + SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + and reorder line with license- add reset descriptions in bndings file + in description +Comments pointed by Kun Yi <kunyi@google.com> +- Changed capability check for aspeed,ast2400-jtag/ast200-jtag + +v11->v12 +Comments pointed by Chip Bilbrey <chip@bilbrey.org> +- Remove access mode from xfer and idle transactions +- Add new ioctl JTAG_SIOCMODE for set hw mode + +v10->v11 +v9->v10 +V8->v9 +Comments pointed by Arnd Bergmann <arnd@arndb.de> +- add *data parameter to xfer function prototype + +v7->v8 +Comments pointed by Joel Stanley <joel.stan@gmail.com> +- aspeed_jtag_init replace goto to return; +- change input variables type from __u32 to u32 + in functios freq_get, freq_set, status_get +- change sm_ variables type from char to u8 +- in jatg_init add disable clocks on error case +- remove release_mem_region on error case +- remove devm_free_irq on jtag_deinit +- Fix misspelling Disabe/Disable +- Change compatible string to ast2400 and ast2000 + +v6->v7 +Notifications from kbuild test robot <lkp@intel.com> +- Add include <linux/types.h> to jtag-asapeed.c + +v5->v6 +v4->v5 +Comments pointed by Arnd Bergmann <arnd@arndb.de> +- Added HAS_IOMEM dependence in Kconfig to avoid + "undefined reference to `devm_ioremap_resource'" error, + because in some arch this not supported + +v3->v4 +Comments pointed by Arnd Bergmann <arnd@arndb.de> +- change transaction pointer tdio type to __u64 +- change internal status type from enum to __u32 + +v2->v3 + +v1->v2 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- change license type from GPLv2/BSD to GPLv2 + +Comments pointed by Neil Armstrong <narmstrong@baylibre.com> +- Add clk_prepare_enable/clk_disable_unprepare in clock init/deinit +- Change .compatible to soc-specific compatible names + aspeed,aspeed4000-jtag/aspeed5000-jtag +- Added dt-bindings + +Comments pointed by Arnd Bergmann <arnd@arndb.de> +- Reorder functions and removed the forward declarations +- Add static const qualifier to state machine states transitions +- Change .compatible to soc-specific compatible names + aspeed,aspeed4000-jtag/aspeed5000-jtag +- Add dt-bindings + +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- Change module name jtag-aspeed in description in Kconfig + +Comments pointed by kbuild test robot <lkp@intel.com> +- Remove invalid include <asm/mach-types.h> +- add resource_size instead of calculation +--- + drivers/jtag/Kconfig | 14 + + drivers/jtag/Makefile | 1 + + drivers/jtag/jtag-aspeed.c | 1050 ++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 1065 insertions(+) + create mode 100644 drivers/jtag/jtag-aspeed.c + +diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig +index 47771fc..0cc163f 100644 +--- a/drivers/jtag/Kconfig ++++ b/drivers/jtag/Kconfig +@@ -15,3 +15,17 @@ menuconfig JTAG + + To compile this driver as a module, choose M here: the module will + be called jtag. ++ ++menuconfig JTAG_ASPEED ++ tristate "Aspeed SoC JTAG controller support" ++ depends on JTAG && 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 +index af37493..04a855e 100644 +--- a/drivers/jtag/Makefile ++++ b/drivers/jtag/Makefile +@@ -1 +1,2 @@ + obj-$(CONFIG_JTAG) += jtag.o ++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 0000000..1d41a66 +--- /dev/null ++++ b/drivers/jtag/jtag-aspeed.c +@@ -0,0 +1,1050 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2018 Mellanox Technologies. All rights reserved. ++// Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com> ++// Copyright (c) 2019 Intel Corporation ++ ++#include <linux/clk.h> ++#include <linux/device.h> ++#include <linux/interrupt.h> ++#include <linux/jtag.h> ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/of_address.h> ++#include <linux/platform_device.h> ++#include <linux/reset.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++#include <linux/delay.h> ++#include <uapi/linux/jtag.h> ++ ++#define ASPEED_SCU_RESET_JTAG BIT(22) ++ ++#define ASPEED_JTAG_DATA 0x00 ++#define ASPEED_JTAG_INST 0x04 ++#define ASPEED_JTAG_CTRL 0x08 ++#define ASPEED_JTAG_ISR 0x0C ++#define ASPEED_JTAG_SW 0x10 ++#define ASPEED_JTAG_TCK 0x14 ++#define ASPEED_JTAG_EC 0x18 ++ ++#define ASPEED_JTAG_DATA_MSB 0x01 ++#define ASPEED_JTAG_DATA_CHUNK_SIZE 0x20 ++ ++/* ASPEED_JTAG_CTRL: Engine Control */ ++#define ASPEED_JTAG_CTL_ENG_EN BIT(31) ++#define ASPEED_JTAG_CTL_ENG_OUT_EN BIT(30) ++#define ASPEED_JTAG_CTL_FORCE_TMS BIT(29) ++#define ASPEED_JTAG_CTL_IR_UPDATE BIT(26) ++#define ASPEED_JTAG_CTL_INST_LEN(x) ((x) << 20) ++#define ASPEED_JTAG_CTL_LASPEED_INST BIT(17) ++#define ASPEED_JTAG_CTL_INST_EN BIT(16) ++#define ASPEED_JTAG_CTL_DR_UPDATE BIT(10) ++#define ASPEED_JTAG_CTL_DATA_LEN(x) ((x) << 4) ++#define ASPEED_JTAG_CTL_LASPEED_DATA BIT(1) ++#define ASPEED_JTAG_CTL_DATA_EN BIT(0) ++ ++/* ASPEED_JTAG_ISR : Interrupt status and enable */ ++#define ASPEED_JTAG_ISR_INST_PAUSE BIT(19) ++#define ASPEED_JTAG_ISR_INST_COMPLETE BIT(18) ++#define ASPEED_JTAG_ISR_DATA_PAUSE BIT(17) ++#define ASPEED_JTAG_ISR_DATA_COMPLETE BIT(16) ++#define ASPEED_JTAG_ISR_INST_PAUSE_EN BIT(3) ++#define ASPEED_JTAG_ISR_INST_COMPLETE_EN BIT(2) ++#define ASPEED_JTAG_ISR_DATA_PAUSE_EN BIT(1) ++#define ASPEED_JTAG_ISR_DATA_COMPLETE_EN BIT(0) ++#define ASPEED_JTAG_ISR_INT_EN_MASK GENMASK(3, 0) ++#define ASPEED_JTAG_ISR_INT_MASK GENMASK(19, 16) ++ ++/* ASPEED_JTAG_SW : Software Mode and Status */ ++#define ASPEED_JTAG_SW_MODE_EN BIT(19) ++#define ASPEED_JTAG_SW_MODE_TCK BIT(18) ++#define ASPEED_JTAG_SW_MODE_TMS BIT(17) ++#define ASPEED_JTAG_SW_MODE_TDIO BIT(16) ++ ++/* ASPEED_JTAG_TCK : TCK Control */ ++#define ASPEED_JTAG_TCK_DIVISOR_MASK GENMASK(10, 0) ++#define ASPEED_JTAG_TCK_GET_DIV(x) ((x) & ASPEED_JTAG_TCK_DIVISOR_MASK) ++ ++/* ASPEED_JTAG_EC : Controller set for go to IDLE */ ++#define ASPEED_JTAG_EC_GO_IDLE BIT(0) ++ ++#define ASPEED_JTAG_IOUT_LEN(len) \ ++ (ASPEED_JTAG_CTL_ENG_EN | \ ++ ASPEED_JTAG_CTL_ENG_OUT_EN | \ ++ ASPEED_JTAG_CTL_INST_LEN(len)) ++ ++#define ASPEED_JTAG_DOUT_LEN(len) \ ++ (ASPEED_JTAG_CTL_ENG_EN | \ ++ ASPEED_JTAG_CTL_ENG_OUT_EN | \ ++ ASPEED_JTAG_CTL_DATA_LEN(len)) ++ ++#define ASPEED_JTAG_SW_TDIO (ASPEED_JTAG_SW_MODE_EN | ASPEED_JTAG_SW_MODE_TDIO) ++ ++#define ASPEED_JTAG_GET_TDI(direction, byte) \ ++ (((direction) & JTAG_WRITE_XFER) ? byte : UINT_MAX) ++ ++#define ASPEED_JTAG_TCK_WAIT 10 ++#define ASPEED_JTAG_RESET_CNTR 10 ++#define WAIT_ITERATIONS 75 ++ ++/*#define USE_INTERRUPTS*/ ++/*#define DEBUG_JTAG*/ ++ ++static const char * const regnames[] = { ++ [ASPEED_JTAG_DATA] = "ASPEED_JTAG_DATA", ++ [ASPEED_JTAG_INST] = "ASPEED_JTAG_INST", ++ [ASPEED_JTAG_CTRL] = "ASPEED_JTAG_CTRL", ++ [ASPEED_JTAG_ISR] = "ASPEED_JTAG_ISR", ++ [ASPEED_JTAG_SW] = "ASPEED_JTAG_SW", ++ [ASPEED_JTAG_TCK] = "ASPEED_JTAG_TCK", ++ [ASPEED_JTAG_EC] = "ASPEED_JTAG_EC", ++}; ++ ++#define ASPEED_JTAG_NAME "jtag-aspeed" ++ ++struct aspeed_jtag { ++ void __iomem *reg_base; ++ struct device *dev; ++ struct clk *pclk; ++ enum jtag_tapstate status; ++ int irq; ++ struct reset_control *rst; ++ u32 flag; ++ wait_queue_head_t jtag_wq; ++ u32 mode; ++}; ++ ++/* ++ * 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; ++}; ++ ++/* ++ * 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*/ ++/* TLR */{{0x00, 0}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x02, 4}, {0x0a, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0a, 5}, {0x2a, 6}, {0x1a, 5}, {0x06, 3}, {0x06, 4}, {0x06, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x16, 5}, {0x16, 6}, {0x56, 7}, {0x36, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* RTI */{{0x07, 3}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SelDR*/{{0x03, 2}, {0x03, 3}, {0x00, 0}, {0x00, 1}, {0x00, 2}, {0x02, 2}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x02, 3}, {0x0a, 4}, {0x06, 3}, {0x01, 1}, {0x01, 2}, {0x01, 3}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* CapDR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x00, 0}, {0x00, 1}, {0x01, 1}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x00, 0}, {0x01, 1}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex1DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x02, 3}, {0x00, 0}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x00, 1}, {0x02, 2}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* PDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x01, 2}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x00, 0}, {0x01, 1}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex2DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x00, 1}, {0x02, 2}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x02, 3}, {0x00, 0}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* UpdDR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x00, 0}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SelIR*/{{0x01, 1}, {0x01, 2}, {0x05, 3}, {0x05, 4}, {0x05, 5}, {0x15, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x15, 6}, {0x55, 7}, {0x35, 6}, {0x00, 0}, {0x00, 1}, {0x00, 2}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x02, 2}, {0x02, 3}, {0x0a, 4}, {0x06, 3} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* CapIR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x00, 0}, {0x00, 1}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* SIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x00, 0}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex1IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x02, 3}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x00, 0}, {0x00, 1}, {0x02, 2}, {0x01, 1} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* PIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x01, 2}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x05, 3}, {0x00, 0}, {0x01, 1}, {0x03, 2} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* Ex2IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x00, 1}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x02, 2}, {0x02, 3}, {0x00, 0}, {0x01, 1} }, ++ ++/* TLR RTI SelDR CapDR SDR Ex1DR*/ ++/* UpdIR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, ++/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ ++ {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, ++/* Ex1IR PIR Ex2IR UpdIR*/ ++ {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x00, 0} }, ++}; ++ ++#ifdef DEBUG_JTAG ++static char *end_status_str[] = { ++ "tlr", "idle", "selDR", "capDR", "sDR", "ex1DR", "pDR", "ex2DR", ++ "updDR", "selIR", "capIR", "sIR", "ex1IR", "pIR", "ex2IR", "updIR" ++}; ++#endif ++ ++static u32 aspeed_jtag_read(struct aspeed_jtag *aspeed_jtag, u32 reg) ++{ ++ u32 val = readl(aspeed_jtag->reg_base + reg); ++ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "read:%s val = 0x%08x\n", regnames[reg], val); ++#endif ++ return val; ++} ++ ++static void ++aspeed_jtag_write(struct aspeed_jtag *aspeed_jtag, u32 val, u32 reg) ++{ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "write:%s val = 0x%08x\n", ++ regnames[reg], val); ++#endif ++ writel(val, aspeed_jtag->reg_base + reg); ++} ++ ++static int aspeed_jtag_freq_set(struct jtag *jtag, u32 freq) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ unsigned long apb_frq; ++ u32 tck_val; ++ u16 div; ++ ++ apb_frq = clk_get_rate(aspeed_jtag->pclk); ++ if (!apb_frq) ++ return -ENOTSUPP; ++ ++ div = (apb_frq - 1) / freq; ++ tck_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK); ++ aspeed_jtag_write(aspeed_jtag, ++ (tck_val & ~ASPEED_JTAG_TCK_DIVISOR_MASK) | div, ++ ASPEED_JTAG_TCK); ++ return 0; ++} ++ ++static int aspeed_jtag_freq_get(struct jtag *jtag, u32 *frq) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ u32 pclk; ++ u32 tck; ++ ++ pclk = clk_get_rate(aspeed_jtag->pclk); ++ tck = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK); ++ *frq = pclk / (ASPEED_JTAG_TCK_GET_DIV(tck) + 1); ++ ++ return 0; ++} ++ ++static inline void aspeed_jtag_output_disable(struct aspeed_jtag *aspeed_jtag) ++{ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); ++} ++ ++static inline void aspeed_jtag_master(struct aspeed_jtag *aspeed_jtag) ++{ ++ aspeed_jtag_write(aspeed_jtag, (ASPEED_JTAG_CTL_ENG_EN | ++ ASPEED_JTAG_CTL_ENG_OUT_EN), ++ ASPEED_JTAG_CTRL); ++ ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | ++ ASPEED_JTAG_SW_MODE_TDIO, ++ ASPEED_JTAG_SW); ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_INST_PAUSE | ++ ASPEED_JTAG_ISR_INST_COMPLETE | ++ ASPEED_JTAG_ISR_DATA_PAUSE | ++ ASPEED_JTAG_ISR_DATA_COMPLETE | ++ ASPEED_JTAG_ISR_INST_PAUSE_EN | ++ ASPEED_JTAG_ISR_INST_COMPLETE_EN | ++ ASPEED_JTAG_ISR_DATA_PAUSE_EN | ++ ASPEED_JTAG_ISR_DATA_COMPLETE_EN, ++ ASPEED_JTAG_ISR); /* Enable Interrupt */ ++} ++ ++static int aspeed_jtag_mode_set(struct jtag *jtag, struct jtag_mode *jtag_mode) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ switch (jtag_mode->feature) { ++ case JTAG_XFER_MODE: ++ aspeed_jtag->mode = jtag_mode->mode; ++ break; ++ case JTAG_CONTROL_MODE: ++ if (jtag_mode->mode == JTAG_MASTER_OUTPUT_DISABLE) ++ aspeed_jtag_output_disable(aspeed_jtag); ++ else if (jtag_mode->mode == JTAG_MASTER_MODE) ++ aspeed_jtag_master(aspeed_jtag); ++ break; ++ default: ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static char aspeed_jtag_tck_cycle(struct aspeed_jtag *aspeed_jtag, ++ u8 tms, u8 tdi) ++{ ++ char tdo = 0; ++ ++ /* TCK = 0 */ ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | ++ (tms * ASPEED_JTAG_SW_MODE_TMS) | ++ (tdi * ASPEED_JTAG_SW_MODE_TDIO), ASPEED_JTAG_SW); ++ ++ aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); ++ ++ /* TCK = 1 */ ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | ++ ASPEED_JTAG_SW_MODE_TCK | ++ (tms * ASPEED_JTAG_SW_MODE_TMS) | ++ (tdi * ASPEED_JTAG_SW_MODE_TDIO), ASPEED_JTAG_SW); ++ ++ if (aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW) & ++ ASPEED_JTAG_SW_MODE_TDIO) ++ tdo = 1; ++ ++ return tdo; ++} ++ ++static int aspeed_jtag_bitbang(struct jtag *jtag, ++ struct bitbang_packet *bitbang, ++ struct tck_bitbang *bitbang_data) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ int i = 0; ++ ++ for (i = 0; i < bitbang->length; i++) { ++ bitbang_data[i].tdo = ++ aspeed_jtag_tck_cycle(aspeed_jtag, bitbang_data[i].tms, ++ bitbang_data[i].tdi); ++ } ++ return 0; ++} ++ ++static int aspeed_jtag_wait_instruction_pause(struct aspeed_jtag *aspeed_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(aspeed_jtag->jtag_wq, ++ aspeed_jtag->flag & ++ ASPEED_JTAG_ISR_INST_PAUSE); ++ aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_PAUSE; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & ASPEED_JTAG_ISR_INST_PAUSE) == 0) { ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); ++#endif ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(aspeed_jtag->dev, ++ "aspeed_jtag driver timed out waiting for instruction pause complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_INST_PAUSE | ++ (status & 0xf), ++ ASPEED_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static int ++aspeed_jtag_wait_instruction_complete(struct aspeed_jtag *aspeed_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(aspeed_jtag->jtag_wq, ++ aspeed_jtag->flag & ++ ASPEED_JTAG_ISR_INST_COMPLETE); ++ aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_COMPLETE; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & ASPEED_JTAG_ISR_INST_COMPLETE) == 0) { ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); ++#endif ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(aspeed_jtag->dev, ++ "aspeed_jtag driver timed out waiting for instruction complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_INST_COMPLETE | ++ (status & 0xf), ++ ASPEED_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static int ++aspeed_jtag_wait_data_pause_complete(struct aspeed_jtag *aspeed_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(aspeed_jtag->jtag_wq, ++ aspeed_jtag->flag & ++ ASPEED_JTAG_ISR_DATA_PAUSE); ++ aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_PAUSE; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & ASPEED_JTAG_ISR_DATA_PAUSE) == 0) { ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); ++#endif ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(aspeed_jtag->dev, ++ "aspeed_jtag driver timed out waiting for data pause complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_DATA_PAUSE | ++ (status & 0xf), ASPEED_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static int aspeed_jtag_wait_data_complete(struct aspeed_jtag *aspeed_jtag) ++{ ++ int res = 0; ++#ifdef USE_INTERRUPTS ++ res = wait_event_interruptible(aspeed_jtag->jtag_wq, ++ aspeed_jtag->flag & ++ ASPEED_JTAG_ISR_DATA_COMPLETE); ++ aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_COMPLETE; ++#else ++ u32 status = 0; ++ u32 iterations = 0; ++ ++ while ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); ++#endif ++ iterations++; ++ if (iterations > WAIT_ITERATIONS) { ++ dev_err(aspeed_jtag->dev, ++ "ast_jtag driver timed out waiting for data complete\n"); ++ res = -EFAULT; ++ break; ++ } ++ if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { ++ if (iterations % 25 == 0) ++ usleep_range(1, 5); ++ else ++ udelay(1); ++ } ++ } ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_ISR_DATA_COMPLETE | (status & 0xf), ++ ASPEED_JTAG_ISR); ++#endif ++ return res; ++} ++ ++static void aspeed_jtag_set_tap_state(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_tapstate from_state, ++ enum jtag_tapstate end_state) ++{ ++ int i = 0; ++ enum jtag_tapstate from, to; ++ ++ from = from_state; ++ to = end_state; ++ ++ if (from == JTAG_STATE_CURRENT) ++ from = aspeed_jtag->status; ++ ++ for (i = 0; i < _tms_cycle_lookup[from][to].count; i++) ++ aspeed_jtag_tck_cycle(aspeed_jtag, ++ ((_tms_cycle_lookup[from][to].tmsbits >> i) & 0x1), 0); ++ aspeed_jtag->status = end_state; ++} ++ ++static void aspeed_jtag_set_tap_state_sw(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ /* SW mode from curent tap state -> to end_state */ ++ if (tapstate->reset) { ++ int i = 0; ++ ++ for (i = 0; i < ASPEED_JTAG_RESET_CNTR; i++) ++ aspeed_jtag_tck_cycle(aspeed_jtag, 1, 0); ++ aspeed_jtag->status = JTAG_STATE_TLRESET; ++ } ++ ++ aspeed_jtag_set_tap_state(aspeed_jtag, tapstate->from, ++ tapstate->endstate); ++} ++ ++static int aspeed_jtag_status_set(struct jtag *jtag, ++ struct jtag_tap_state *tapstate) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "Set TAP state: %s\n", ++ end_status_str[tapstate->endstate]); ++#endif ++ ++ if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { ++ aspeed_jtag_set_tap_state_sw(aspeed_jtag, tapstate); ++ return 0; ++ } ++ ++ /* x TMS high + 1 TMS low */ ++ if (tapstate->reset) { ++ /* Disable sw mode */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ mdelay(1); ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_CTL_ENG_EN | ++ ASPEED_JTAG_CTL_ENG_OUT_EN | ++ ASPEED_JTAG_CTL_FORCE_TMS, ASPEED_JTAG_CTRL); ++ mdelay(1); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_SW_TDIO, ASPEED_JTAG_SW); ++ aspeed_jtag->status = JTAG_STATE_TLRESET; ++ } ++ ++ return 0; ++} ++ ++static void aspeed_jtag_xfer_sw(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_xfer *xfer, u32 *data) ++{ ++ unsigned long remain_xfer = xfer->length; ++ unsigned long shift_bits = 0; ++ unsigned long index = 0; ++ unsigned long tdi; ++ char tdo; ++ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "SW JTAG SHIFT %s, length = %d\n", ++ (xfer->type == JTAG_SIR_XFER) ? "IR" : "DR", xfer->length); ++#endif ++ ++ if (xfer->type == JTAG_SIR_XFER) ++ aspeed_jtag_set_tap_state(aspeed_jtag, xfer->from, ++ JTAG_STATE_SHIFTIR); ++ else ++ aspeed_jtag_set_tap_state(aspeed_jtag, xfer->from, ++ JTAG_STATE_SHIFTDR); ++ ++ tdi = ASPEED_JTAG_GET_TDI(xfer->direction, data[index]); ++ data[index] = 0; ++ while (remain_xfer > 1) { ++ tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 0, ++ tdi & ASPEED_JTAG_DATA_MSB); ++ data[index] |= tdo << (shift_bits % ++ ASPEED_JTAG_DATA_CHUNK_SIZE); ++ tdi >>= 1; ++ shift_bits++; ++ remain_xfer--; ++ ++ if (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE == 0) { ++ tdo = 0; ++ index++; ++ tdi = ASPEED_JTAG_GET_TDI(xfer->direction, data[index]); ++ data[index] = 0; ++ } ++ } ++ ++ if ((xfer->endstate == (xfer->type == JTAG_SIR_XFER ? ++ JTAG_STATE_SHIFTIR : JTAG_STATE_SHIFTDR))) { ++ /* Stay in Shift IR/DR*/ ++ tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 0, ++ tdi & ASPEED_JTAG_DATA_MSB); ++ data[index] |= tdo << (shift_bits % ++ ASPEED_JTAG_DATA_CHUNK_SIZE); ++ } else { ++ /* Goto end state */ ++ tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 1, ++ tdi & ASPEED_JTAG_DATA_MSB); ++ data[index] |= tdo << (shift_bits % ++ ASPEED_JTAG_DATA_CHUNK_SIZE); ++ aspeed_jtag->status = (xfer->type == JTAG_SIR_XFER) ? ++ JTAG_STATE_EXIT1IR : JTAG_STATE_EXIT1DR; ++ aspeed_jtag_set_tap_state(aspeed_jtag, aspeed_jtag->status, ++ xfer->endstate); ++ } ++} ++ ++static int aspeed_jtag_xfer_push_data(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, u32 bits_len) ++{ ++ int res = 0; ++ ++ if (type == JTAG_SIR_XFER) { ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_IOUT_LEN(bits_len), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_IOUT_LEN(bits_len) | ++ ASPEED_JTAG_CTL_INST_EN, ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_wait_instruction_pause(aspeed_jtag); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len), ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len) | ++ ASPEED_JTAG_CTL_DATA_EN, ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_wait_data_pause_complete(aspeed_jtag); ++ } ++ return res; ++} ++ ++static int aspeed_jtag_xfer_push_data_last(struct aspeed_jtag *aspeed_jtag, ++ enum jtag_xfer_type type, ++ u32 shift_bits) ++{ ++ int res = 0; ++ ++ if (type == JTAG_SIR_XFER) { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_IOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_INST, ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_IOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_INST | ++ ASPEED_JTAG_CTL_INST_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_wait_instruction_complete(aspeed_jtag); ++ } else { ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_DOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_DATA, ++ ASPEED_JTAG_CTRL); ++ aspeed_jtag_write(aspeed_jtag, ++ ASPEED_JTAG_DOUT_LEN(shift_bits) | ++ ASPEED_JTAG_CTL_LASPEED_DATA | ++ ASPEED_JTAG_CTL_DATA_EN, ++ ASPEED_JTAG_CTRL); ++ res = aspeed_jtag_wait_data_complete(aspeed_jtag); ++ } ++ return res; ++} ++ ++static int aspeed_jtag_xfer_hw(struct aspeed_jtag *aspeed_jtag, ++ struct jtag_xfer *xfer, u32 *data) ++{ ++ unsigned long remain_xfer = xfer->length; ++ unsigned long index = 0; ++ char shift_bits; ++ u32 data_reg; ++ u32 scan_end; ++ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, "HW JTAG SHIFT %s, length = %d\n", ++ (xfer->type == JTAG_SIR_XFER) ? "IR" : "DR", xfer->length); ++#endif ++ data_reg = xfer->type == JTAG_SIR_XFER ? ++ ASPEED_JTAG_INST : ASPEED_JTAG_DATA; ++ if (xfer->endstate == JTAG_STATE_SHIFTIR || ++ xfer->endstate == JTAG_STATE_SHIFTDR || ++ xfer->endstate == JTAG_STATE_PAUSEIR || ++ xfer->endstate == JTAG_STATE_PAUSEDR) { ++ scan_end = 0; ++ } else { ++ scan_end = 1; ++ } ++ ++ while (remain_xfer) { ++ if (xfer->direction & JTAG_WRITE_XFER) ++ aspeed_jtag_write(aspeed_jtag, data[index], data_reg); ++ else ++ aspeed_jtag_write(aspeed_jtag, 0, data_reg); ++ ++ if (remain_xfer > ASPEED_JTAG_DATA_CHUNK_SIZE) { ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, ++ "Chunk len=%d chunk_size=%d remain_xfer=%lu\n", ++ xfer->length, ASPEED_JTAG_DATA_CHUNK_SIZE, ++ remain_xfer); ++#endif ++ shift_bits = ASPEED_JTAG_DATA_CHUNK_SIZE; ++ ++ /* ++ * Transmit bytes that were not equals to column length ++ * and after the transfer go to Pause IR/DR. ++ */ ++ if (aspeed_jtag_xfer_push_data(aspeed_jtag, xfer->type, ++ shift_bits) != 0) { ++ return -EFAULT; ++ } ++ } else { ++ /* ++ * Read bytes equals to column length ++ */ ++ shift_bits = remain_xfer; ++ if (scan_end) { ++ /* ++ * If this data is the end of the transmission ++ * send remaining bits and go to endstate ++ */ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, ++ "Last len=%d chunk_size=%d remain_xfer=%lu\n", ++ xfer->length, ++ ASPEED_JTAG_DATA_CHUNK_SIZE, ++ remain_xfer); ++#endif ++ if (aspeed_jtag_xfer_push_data_last( ++ aspeed_jtag, ++ xfer->type, ++ shift_bits) != 0) { ++ return -EFAULT; ++ } ++ } else { ++ /* ++ * If transmission is waiting for additional ++ * data send remaining bits and then go to ++ * Pause IR/DR. ++ */ ++#ifdef DEBUG_JTAG ++ dev_dbg(aspeed_jtag->dev, ++ "Tail len=%d chunk_size=%d remain_xfer=%lu\n", ++ xfer->length, ++ ASPEED_JTAG_DATA_CHUNK_SIZE, ++ remain_xfer); ++#endif ++ if (aspeed_jtag_xfer_push_data(aspeed_jtag, ++ xfer->type, ++ shift_bits) ++ != 0) { ++ return -EFAULT; ++ } ++ } ++ } ++ ++ if (xfer->direction & JTAG_READ_XFER) { ++ if (shift_bits < ASPEED_JTAG_DATA_CHUNK_SIZE) { ++ data[index] = aspeed_jtag_read(aspeed_jtag, ++ data_reg); ++ ++ data[index] >>= ASPEED_JTAG_DATA_CHUNK_SIZE - ++ shift_bits; ++ } else { ++ data[index] = aspeed_jtag_read(aspeed_jtag, ++ data_reg); ++ } ++ } ++ ++ remain_xfer = remain_xfer - shift_bits; ++ index++; ++ } ++ return 0; ++} ++ ++static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer, ++ u8 *xfer_data) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { ++ /* SW mode */ ++ aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_TDIO, ++ ASPEED_JTAG_SW); ++ ++ aspeed_jtag_xfer_sw(aspeed_jtag, xfer, (u32 *)xfer_data); ++ } else { ++ /* HW mode */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); ++ if (aspeed_jtag_xfer_hw(aspeed_jtag, xfer, ++ (u32 *)xfer_data) != 0) ++ return -EFAULT; ++ } ++ ++ aspeed_jtag->status = xfer->endstate; ++ return 0; ++} ++ ++static int aspeed_jtag_status_get(struct jtag *jtag, u32 *status) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ *status = aspeed_jtag->status; ++ return 0; ++} ++ ++#ifdef USE_INTERRUPTS ++static irqreturn_t aspeed_jtag_interrupt(s32 this_irq, void *dev_id) ++{ ++ struct aspeed_jtag *aspeed_jtag = dev_id; ++ irqreturn_t ret = IRQ_HANDLED; ++ u32 status; ++ ++ status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); ++ ++ if (status & ASPEED_JTAG_ISR_INT_MASK) { ++ aspeed_jtag_write(aspeed_jtag, ++ (status & ASPEED_JTAG_ISR_INT_MASK) ++ | (status & ASPEED_JTAG_ISR_INT_EN_MASK), ++ ASPEED_JTAG_ISR); ++ aspeed_jtag->flag |= status & ASPEED_JTAG_ISR_INT_MASK; ++ } ++ ++ if (aspeed_jtag->flag) { ++ wake_up_interruptible(&aspeed_jtag->jtag_wq); ++ ret = IRQ_HANDLED; ++ } else { ++ dev_err(aspeed_jtag->dev, "irq status:%x\n", ++ status); ++ ret = IRQ_NONE; ++ } ++ return ret; ++} ++#endif ++ ++static int aspeed_jtag_enable(struct jtag *jtag) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ aspeed_jtag_master(aspeed_jtag); ++ return 0; ++} ++ ++static int aspeed_jtag_disable(struct jtag *jtag) ++{ ++ struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); ++ ++ aspeed_jtag_output_disable(aspeed_jtag); ++ return 0; ++} ++ ++static int aspeed_jtag_init(struct platform_device *pdev, ++ struct aspeed_jtag *aspeed_jtag) ++{ ++ struct resource *res; ++#ifdef USE_INTERRUPTS ++ int err; ++#endif ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ aspeed_jtag->reg_base = devm_ioremap_resource(aspeed_jtag->dev, res); ++ if (IS_ERR(aspeed_jtag->reg_base)) ++ return -ENOMEM; ++ ++ aspeed_jtag->pclk = devm_clk_get(aspeed_jtag->dev, NULL); ++ if (IS_ERR(aspeed_jtag->pclk)) { ++ dev_err(aspeed_jtag->dev, "devm_clk_get failed\n"); ++ return PTR_ERR(aspeed_jtag->pclk); ++ } ++ ++#ifdef USE_INTERRUPTS ++ aspeed_jtag->irq = platform_get_irq(pdev, 0); ++ if (aspeed_jtag->irq < 0) { ++ dev_err(aspeed_jtag->dev, "no irq specified\n"); ++ return -ENOENT; ++ } ++#endif ++ ++ if (clk_prepare_enable(aspeed_jtag->pclk)) { ++ dev_err(aspeed_jtag->dev, "no irq specified\n"); ++ return -ENOENT; ++ } ++ ++ aspeed_jtag->rst = devm_reset_control_get_shared(&pdev->dev, NULL); ++ if (IS_ERR(aspeed_jtag->rst)) { ++ dev_err(aspeed_jtag->dev, ++ "missing or invalid reset controller device tree entry"); ++ return PTR_ERR(aspeed_jtag->rst); ++ } ++ reset_control_deassert(aspeed_jtag->rst); ++ ++#ifdef USE_INTERRUPTS ++ err = devm_request_irq(aspeed_jtag->dev, aspeed_jtag->irq, ++ aspeed_jtag_interrupt, 0, ++ "aspeed-jtag", aspeed_jtag); ++ if (err) { ++ dev_err(aspeed_jtag->dev, "unable to get IRQ"); ++ clk_disable_unprepare(aspeed_jtag->pclk); ++ return err; ++ } ++#endif ++ ++ aspeed_jtag_output_disable(aspeed_jtag); ++ ++ aspeed_jtag->flag = 0; ++ aspeed_jtag->mode = 0; ++ init_waitqueue_head(&aspeed_jtag->jtag_wq); ++ return 0; ++} ++ ++static int aspeed_jtag_deinit(struct platform_device *pdev, ++ struct aspeed_jtag *aspeed_jtag) ++{ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_ISR); ++ /* Disable clock */ ++ aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); ++ reset_control_assert(aspeed_jtag->rst); ++ clk_disable_unprepare(aspeed_jtag->pclk); ++ return 0; ++} ++ ++static const struct jtag_ops aspeed_jtag_ops = { ++ .freq_get = aspeed_jtag_freq_get, ++ .freq_set = aspeed_jtag_freq_set, ++ .status_get = aspeed_jtag_status_get, ++ .status_set = aspeed_jtag_status_set, ++ .xfer = aspeed_jtag_xfer, ++ .mode_set = aspeed_jtag_mode_set, ++ .bitbang = aspeed_jtag_bitbang, ++ .enable = aspeed_jtag_enable, ++ .disable = aspeed_jtag_disable ++}; ++ ++static int aspeed_jtag_probe(struct platform_device *pdev) ++{ ++ struct aspeed_jtag *aspeed_jtag; ++ struct jtag *jtag; ++ int err; ++ ++ jtag = jtag_alloc(&pdev->dev, sizeof(*aspeed_jtag), &aspeed_jtag_ops); ++ if (!jtag) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, jtag); ++ aspeed_jtag = jtag_priv(jtag); ++ aspeed_jtag->dev = &pdev->dev; ++ ++ /* Initialize device*/ ++ err = aspeed_jtag_init(pdev, aspeed_jtag); ++ if (err) ++ goto err_jtag_init; ++ ++ /* Initialize JTAG core structure*/ ++ err = devm_jtag_register(aspeed_jtag->dev, jtag); ++ if (err) ++ goto err_jtag_register; ++ ++ return 0; ++ ++err_jtag_register: ++ aspeed_jtag_deinit(pdev, aspeed_jtag); ++err_jtag_init: ++ jtag_free(jtag); ++ return err; ++} ++ ++static int aspeed_jtag_remove(struct platform_device *pdev) ++{ ++ struct jtag *jtag = platform_get_drvdata(pdev); ++ ++ aspeed_jtag_deinit(pdev, jtag_priv(jtag)); ++ return 0; ++} ++ ++static const struct of_device_id aspeed_jtag_of_match[] = { ++ { .compatible = "aspeed,ast2400-jtag", }, ++ { .compatible = "aspeed,ast2500-jtag", }, ++ {} ++}; ++ ++static struct platform_driver aspeed_jtag_driver = { ++ .probe = aspeed_jtag_probe, ++ .remove = aspeed_jtag_remove, ++ .driver = { ++ .name = ASPEED_JTAG_NAME, ++ .of_match_table = aspeed_jtag_of_match, ++ }, ++}; ++module_platform_driver(aspeed_jtag_driver); ++ ++MODULE_AUTHOR("Oleksandr Shamray <oleksandrs@mellanox.com>"); ++MODULE_DESCRIPTION("ASPEED JTAG driver"); ++MODULE_LICENSE("GPL v2"); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0054-Documentation-jtag-Add-bindings-for-Aspeed-SoC.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0054-Documentation-jtag-Add-bindings-for-Aspeed-SoC.patch new file mode 100644 index 000000000..f17bdcd68 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0054-Documentation-jtag-Add-bindings-for-Aspeed-SoC.patch @@ -0,0 +1,108 @@ +From 2a22feac440070b7feaf0a6fe7e7e555d57ca19b Mon Sep 17 00:00:00 2001 +From: "Corona, Ernesto" <ernesto.corona@intel.com> +Date: Wed, 10 Mar 2019 11:45:04 -0800 +Subject: [PATCH v29 3/6] Documentation: jtag: Add bindings for Aspeed SoC + 24xx and 25xx families JTAG master driver + +It has been tested on Mellanox system with BMC equipped with +Aspeed 2520 SoC for programming CPLD devices. + +It also has been tested on Intel systems with BMC equipped with +Aspeed 25x0 SoC for JTAG board communication. + +Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com> +Signed-off-by: Jiri Pirko <jiri@mellanox.com> +Signed-off-by: Corona, Ernesto <ernesto.corona@intel.com> +Acked-by: Rob Herring <robh@kernel.org> +Cc: Mark Rutland <mark.rutland@arm.com> +Cc: Joel Stanley <joel@jms.id.au> +Cc: Andrew Jeffery <andrew@aj.id.au> +Cc: Steven A Filary <steven.a.filary@intel.com> +Cc: Bryan Hunt <bryan.hunt@intel.com> +--- +v28->v29 +v27->v28 +v26->v27 +v25->v26 +v24->v25 +v23->v24 +v22->v23 +v21->v22 +v20->v21 +v19->v20 +v18->v19 + +v17->v18 +v16->v17 +v15->v16 +Comments pointed by Joel Stanley <joel.stan@gmail.com> +- change clocks = <&clk_apb> to proper clocks = <&syscon ASPEED_CLK_APB> +- add reset descriptions in bindings file + +v14->v15 +v13->v14 +v12->v13 +v11->v12 +v10->v11 +v9->v10 +v8->v9 +v7->v8 +Comments pointed by pointed by Joel Stanley <joel.stan@gmail.com> +- Change compatible string to ast2400 and ast2000 + +V6->v7 +Comments pointed by Tobias Klauser <tklauser@distanz.ch> + - Fix spell "Doccumentation" -> "Documentation" + +v5->v6 +Comments pointed by Tobias Klauser <tklauser@distanz.ch> +- Small nit: s/documentation/Documentation/ + +v4->v5 + +V3->v4 +Comments pointed by Rob Herring <robh@kernel.org> +- delete unnecessary "status" and "reg-shift" descriptions in + bindings file + +v2->v3 +Comments pointed by Rob Herring <robh@kernel.org> +- split Aspeed jtag driver and binding to separate patches +- delete unnecessary "status" and "reg-shift" descriptions in + bindings file +--- + .../devicetree/bindings/jtag/aspeed-jtag.txt | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + create mode 100644 Documentation/devicetree/bindings/jtag/aspeed-jtag.txt + +diff --git a/Documentation/devicetree/bindings/jtag/aspeed-jtag.txt b/Documentation/devicetree/bindings/jtag/aspeed-jtag.txt +new file mode 100644 +index 0000000..7c36eb6 +--- /dev/null ++++ b/Documentation/devicetree/bindings/jtag/aspeed-jtag.txt +@@ -0,0 +1,22 @@ ++Aspeed JTAG driver for ast2400 and ast2500 SoC ++ ++Required properties: ++- compatible: Should be one of ++ - "aspeed,ast2400-jtag" ++ - "aspeed,ast2500-jtag" ++- reg contains the offset and length of the JTAG memory ++ region ++- clocks root clock of bus, should reference the APB ++ clock in the second cell ++- resets phandle to reset controller with the reset number in ++ the second cell ++- interrupts should contain JTAG controller interrupt ++ ++Example: ++jtag: jtag@1e6e4000 { ++ compatible = "aspeed,ast2500-jtag"; ++ reg = <0x1e6e4000 0x1c>; ++ clocks = <&syscon ASPEED_CLK_APB>; ++ resets = <&syscon ASPEED_RESET_JTAG_MASTER>; ++ interrupts = <43>; ++}; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0055-Documentation-jtag-Add-ABI-documentation.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0055-Documentation-jtag-Add-ABI-documentation.patch new file mode 100644 index 000000000..1e4142035 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0055-Documentation-jtag-Add-ABI-documentation.patch @@ -0,0 +1,310 @@ +From d4c55bd4eeb6d9290025fa353d1787f18bca6ade Mon Sep 17 00:00:00 2001 +From: "Corona, Ernesto" <ernesto.corona@intel.com> +Date: Sun, 10 Mar 2019 11:47:40 -0800 +Subject: [PATCH v29 4/6] Documentation: jtag: Add ABI documentation + +Added document that describe the ABI for JTAG class driver + +Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com> +Signed-off-by: Corona, Ernesto <ernesto.corona@intel.com> +Acked-by: Arnd Bergmann <arnd@arndb.de> +Cc: Jonathan Corbet <corbet@lwn.net> +Cc: Jiri Pirko <jiri@mellanox.com> +Cc: Vadim Pasternak <vadimp@mellanox.com> +Cc: Steven A Filary <steven.a.filary@intel.com> +Cc: Bryan Hunt <bryan.hunt@intel.com> +--- +v28->v29 +Comments pointed by Steven Filary <steven.a.filary@intel.com> +- Expand bitbang function to accept multiples bitbang operations within a + single JTAG_IOCBITBANG call. It will receive a buffer with TDI and TMS + values and it is expected that driver fills TDO fields with its + corresponding output value for every transaction. + +v27->v28 +Comments pointed by Steven Filary <steven.a.filary@intel.com> +- Replace JTAG_IOCRUNTEST with JTAG_SIOCSTATE adding support for all TAPC + end states in SW mode using a lookup table to navigate across states. +- Add support for simultaneous READ/WRITE transfers(JTAG_READ_WRITE_XFER). +- Support for switching JTAG controller mode between slave and master + mode. +- Setup JTAG controller mode to master only when the driver is opened, + letting other HW to own the JTAG bus when it isn't in use. +- Include JTAG bit bang IOCTL for low level JTAG control usage + (JTAG_IOCBITBANG). + +v26->v27 +v25->v26 +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- fix spell in ABI documentation + +v24->v25 +Comments pointed by Greg KH <gregkh@linuxfoundation.org> +- Fixed documentation according to new open() behavior + +v23->v24 +v22->v23 +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- fix spell in ABI doccumentation + +v21->v22 +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- fix spell in ABI doccumentation + +v20->v21 +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- Fix JTAG dirver help in Kconfig + +v19->v20 +Comments pointed by Randy Dunlap <rdunlap@infradead.org> +- Fix JTAG doccumentation + +v18->v19 +Pavel Machek <pavel@ucw.cz> +- Added JTAG doccumentation to Documentation/jtag + +v17->v18 +v16->v17 +v15->v16 +v14->v15 +v13->v14 +v12->v13 +v11->v12 Tobias Klauser <tklauser@distanz.ch> +Comments pointed by +- rename /Documentation/ABI/testing/jatg-dev -> jtag-dev +- Typo: s/interfase/interface +v10->v11 +v9->v10 +Fixes added by Oleksandr: +- change jtag-cdev to jtag-dev in documentation +- update KernelVersion and Date in jtag-dev documentation; +v8->v9 +v7->v8 +v6->v7 +Comments pointed by Pavel Machek <pavel@ucw.cz> +- Added jtag-cdev documentation to Documentation/ABI/testing folder +--- + Documentation/ABI/testing/jtag-dev | 23 ++++++ + Documentation/jtag/overview | 27 +++++++ + Documentation/jtag/transactions | 145 +++++++++++++++++++++++++++++++++++++ + 3 files changed, 195 insertions(+) + create mode 100644 Documentation/ABI/testing/jtag-dev + create mode 100644 Documentation/jtag/overview + create mode 100644 Documentation/jtag/transactions + +diff --git a/Documentation/ABI/testing/jtag-dev b/Documentation/ABI/testing/jtag-dev +new file mode 100644 +index 000000000000..423baab18761 +--- /dev/null ++++ b/Documentation/ABI/testing/jtag-dev +@@ -0,0 +1,23 @@ ++What: /dev/jtag[0-9]+ ++Date: July 2018 ++KernelVersion: 4.20 ++Contact: oleksandrs@mellanox.com ++Description: ++ The misc device files /dev/jtag* are the interface ++ between JTAG master interface and userspace. ++ ++ The ioctl(2)-based ABI is defined and documented in ++ [include/uapi]<linux/jtag.h>. ++ ++ The following file operations are supported: ++ ++ open(2) ++ Opens and allocates file descriptor. ++ ++ ioctl(2) ++ Initiate various actions. ++ See the inline documentation in [include/uapi]<linux/jtag.h> ++ for descriptions of all ioctls. ++ ++Users: ++ userspace tools which wants to access to JTAG bus +diff --git a/Documentation/jtag/overview b/Documentation/jtag/overview +new file mode 100644 +index 000000000000..6a5ec335e313 +--- /dev/null ++++ b/Documentation/jtag/overview +@@ -0,0 +1,27 @@ ++Linux kernel JTAG support ++========================= ++ ++JTAG is an industry standard for verifying hardware. JTAG provides access to ++many logic signals of a complex integrated circuit, including the device pins. ++ ++A JTAG interface is a special interface added to a chip. ++Depending on the version of JTAG, two, four, or five pins are added. ++ ++The connector pins are: ++ TDI (Test Data In) ++ TDO (Test Data Out) ++ TCK (Test Clock) ++ TMS (Test Mode Select) ++ TRST (Test Reset) optional ++ ++JTAG interface is designed to have two parts - basic core driver and ++hardware specific driver. The basic driver introduces a general interface ++which is not dependent of specific hardware. It provides communication ++between user space and hardware specific driver. ++Each JTAG device is represented as a char device from (jtag0, jtag1, ...). ++Access to a JTAG device is performed through IOCTL calls. ++ ++Call flow example: ++User: open -> /dev/jatgX -> JTAG core driver -> JTAG hardware specific driver ++User: ioctl -> /dev/jtagX -> JTAG core driver -> JTAG hardware specific driver ++User: close -> /dev/jatgX -> JTAG core driver -> JTAG hardware specific driver +diff --git a/Documentation/jtag/transactions b/Documentation/jtag/transactions +new file mode 100644 +index 000000000000..f5d4a1ded6cf +--- /dev/null ++++ b/Documentation/jtag/transactions +@@ -0,0 +1,145 @@ ++The JTAG API ++============= ++ ++JTAG master devices can be accessed through a character misc-device. ++Each JTAG master interface can be accessed by using /dev/jtagN. ++ ++JTAG system calls set: ++- SIR (Scan Instruction Register, IEEE 1149.1 Instruction Register scan); ++- SDR (Scan Data Register, IEEE 1149.1 Data Register scan); ++- RUNTEST (Forces the IEEE 1149.1 bus to a run state for a specified ++number of clocks. ++ ++open(), close() ++------- ++open() opens JTAG device. ++ ++Open/Close device: ++- jtag_fd = open("/dev/jtag0", O_RDWR); ++- close(jtag_fd); ++ ++ioctl() ++------- ++All access operations to JTAG devices are performed through ioctl interface. ++The IOCTL interface supports these requests: ++ JTAG_SIOCSTATE - Force JTAG state machine to go into a TAPC state ++ JTAG_SIOCFREQ - Set JTAG TCK frequency ++ JTAG_GIOCFREQ - Get JTAG TCK frequency ++ JTAG_IOCXFER - send/receive JTAG data Xfer ++ JTAG_GIOCSTATUS - get current JTAG TAP state ++ JTAG_SIOCMODE - set JTAG mode flags. ++ JTAG_IOCBITBANG - JTAG bitbang low level control. ++ ++JTAG_SIOCFREQ, JTAG_GIOCFREQ ++------ ++Set/Get JTAG clock speed: ++ ++ unsigned int jtag_fd; ++ ioctl(jtag_fd, JTAG_SIOCFREQ, &frq); ++ ioctl(jtag_fd, JTAG_GIOCFREQ, &frq); ++ ++JTAG_SIOCSTATE ++------ ++Force JTAG state machine to go into a TAPC state ++ ++struct jtag_tap_state { ++ __u8 reset; ++ __u8 from; ++ __u8 endstate; ++ __u8 tck; ++}; ++ ++reset: ++ JTAG_NO_RESET - go through selected endstate from current state ++ JTAG_FORCE_RESET - go through TEST_LOGIC/RESET state before selected endstate ++endstate: completion flag ++tck: clock counter ++ ++Example: ++ struct jtag_tap_state tap_state; ++ ++ tap_state.from = JTAG_STATE_TLRESET; ++ tap_state.endstate = JTAG_STATE_IDLE; ++ tap_state.reset = 0; ++ tap_state.tck = data_p->tck; ++ usleep(25 * 1000); ++ ioctl(jtag_fd, JTAG_SIOCSTATE, &tap_state); ++ ++JTAG_GIOCSTATUS ++------ ++Get JTAG TAPC machine state ++ ++ unsigned int jtag_fd; ++ jtag_tapstate tapstate; ++ ioctl(jtag_fd, JTAG_GIOCSTATUS, &tapstate); ++ ++JTAG_IOCXFER ++------ ++Send SDR/SIR transaction ++ ++struct jtag_xfer { ++ __u8 type; ++ __u8 direction; ++ __u8 from; ++ __u8 endstate; ++ __u8 padding; ++ __u32 length; ++ __u64 tdio; ++}; ++ ++type: transfer type - JTAG_SIR_XFER/JTAG_SDR_XFER ++direction: xfer direction - JTAG_READ_XFER/JTAG_WRITE_XFER/JTAG_READ_WRITE_XFER ++length: xfer data length in bits ++tdio : xfer data array ++from: xfer from state can be current JTAG tap state saved by the driver ++ JTAG_STATE_CURRENT or in a multichain environment any state listed in ++ jtag_tapstate struct saved by your multichain controller software. ++endstate: xfer end state after transaction finish ++ can be: any state listed in jtag_tapstate struct ++ ++Example: ++ struct jtag_xfer xfer; ++ static char buf[64]; ++ static unsigned int buf_len = 0; ++ [...] ++ xfer.type = JTAG_SDR_XFER; ++ xfer.tdio = (__u64)buf; ++ xfer.length = buf_len; ++ xfer.from = JTAG_STATE_TLRESET; ++ xfer.endstate = JTAG_STATE_IDLE; ++ ++ if (is_read) ++ xfer.direction = JTAG_READ_XFER; ++ else if (is_write) ++ xfer.direction = JTAG_WRITE_XFER; ++ else ++ xfer.direction = JTAG_READ_WRITE_XFER; ++ ++ ioctl(jtag_fd, JTAG_IOCXFER, &xfer); ++ ++JTAG_SIOCMODE ++------ ++If hardware driver can support different running modes you can change it. ++ ++Example: ++ struct jtag_mode mode; ++ mode.feature = JTAG_XFER_MODE; ++ mode.mode = JTAG_XFER_HW_MODE; ++ ioctl(jtag_fd, JTAG_SIOCMODE, &mode); ++ ++JTAG_IOCBITBANG ++------ ++JTAG Bitbang low level operation. ++ ++Example: ++ struct bitbang_packet bitbang; ++ struct tck_bitbang bitbang_data[2]; ++ bitbang_data[0].tms = 0; ++ bitbang_data[0].tdi = 1; ++ bitbang_data[1].tms = 0; ++ bitbang_data[1].tdi = 1; ++ bitbang.data = bitbang_data; ++ bitbang.length = 2; ++ ioctl(jtag_fd, JTAG_IOCBITBANG, &bitbang); ++ tdo0 = bitbang_data[0].tdo; ++ tdo1 = bitbang_data[1].tdo; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0056-Documentation-jtag-Add-JTAG-core-driver-ioctl-number.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0056-Documentation-jtag-Add-JTAG-core-driver-ioctl-number.patch new file mode 100644 index 000000000..a7dccc4b6 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0056-Documentation-jtag-Add-JTAG-core-driver-ioctl-number.patch @@ -0,0 +1,57 @@ +From ba0c35ae070cffcb384fc76e23a38e00142b128d Mon Sep 17 00:00:00 2001 +From: "Corona, Ernesto" <ernesto.corona@intel.com> +Date: Wed, 10 Mar 2019 11:48:18 -0800 +Subject: [PATCH v29 5/6] Documentation jtag: Add JTAG core driver ioctl number + +JTAG class driver provide infrastructure to support hardware/software +JTAG platform drivers. It provide user layer API interface for flashing +and debugging external devices which equipped with JTAG interface +using standard transactions. + +Driver exposes set of IOCTL to user space for: +- XFER: + SIR (Scan Instruction Register, IEEE 1149.1 Data Register scan); + SDR (Scan Data Register, IEEE 1149.1 Instruction Register scan); +- GIOCSTATUS read the current TAPC state of the JTAG controller +- SIOCSTATE Forces the JTAG TAPC to go into a particular state. +- SIOCFREQ/GIOCFREQ for setting and reading JTAG frequency. +- IOCBITBANG for low level control of JTAG signals. + +Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com> +Signed-off-by: Corona, Ernesto <ernesto.corona@intel.com> +Acked-by: Philippe Ombredanne <pombredanne@nexb.com> +Cc: Jiri Pirko <jiri@mellanox.com> +Cc: Vadim Pasternak <vadimp@mellanox.com> +Cc: Jonathan Corbet <corbet@lwn.net> +Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> +Cc: Gustavo Pimentel <gustavo.pimentel@synopsys.com> +Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> +Cc: Kishon Vijay Abraham I <kishon@ti.com> +Cc: Darrick J. Wong <darrick.wong@oracle.com> +Cc: Bryant G. Ly <bryantly@linux.vnet.ibm.com> +Cc: Eric Sandeen <sandeen@redhat.com> +Cc: Randy Dunlap <rdunlap@infradead.org> +Cc: Tomohiro Kusumi <kusumi.tomohiro@gmail.com> +Cc: Arnd Bergmann <arnd@arndb.de> +Cc: Steven A Filary <steven.a.filary@intel.com> +Cc: Bryan Hunt <bryan.hunt@intel.com> +--- + Documentation/ioctl/ioctl-number.txt | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt +index c955814..f732118 100644 +--- a/Documentation/ioctl/ioctl-number.txt ++++ b/Documentation/ioctl/ioctl-number.txt +@@ -323,6 +323,8 @@ Code Seq#(hex) Include File Comments + 0xB0 all RATIO devices in development: + <mailto:vgo@ratio.de> + 0xB1 00-1F PPPoX <mailto:mostrows@styx.uwaterloo.ca> ++0xB2 00-0F linux/jtag.h JTAG driver ++ <mailto:oleksandrs@mellanox.com> + 0xB3 00 linux/mmc/ioctl.h + 0xB4 00-0F linux/gpio.h <mailto:linux-gpio@vger.kernel.org> + 0xB5 00-0F uapi/linux/rpmsg.h <mailto:linux-remoteproc@vger.kernel.org> +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0057-drivers-jtag-Add-JTAG-core-driver-Maintainers.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0057-drivers-jtag-Add-JTAG-core-driver-Maintainers.patch new file mode 100644 index 000000000..47a532590 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0057-drivers-jtag-Add-JTAG-core-driver-Maintainers.patch @@ -0,0 +1,50 @@ +From 8598b57423967f5851484533a1b09a04bafa235b Mon Sep 17 00:00:00 2001 +From: "Corona, Ernesto" <ernesto.corona@intel.com> +Date: Sun, 10 Mar 2019 11:49:37 -0800 +Subject: [PATCH v29 6/6] drivers: jtag: Add JTAG core driver Maintainers + +JTAG class driver provide infrastructure to support hardware/software +JTAG platform drivers. It provide user layer API interface for flashing +and debugging external devices which equipped with JTAG interface +using standard transactions. + +Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com> +Signed-off-by: Corona, Ernesto <ernesto.corona@intel.com> +Acked-by: Arnd Bergmann <arnd@arndb.de> +Cc: Jiri Pirko <jiri@mellanox.com> +Cc: Vadim Pasternak <vadimp@mellanox.com> +Cc: Mauro Carvalho Chehab <mchehab+samsung@kernel.org> +Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> +Cc: David S. Miller <davem@davemloft.net> +Cc: Nicolas Ferre <nicolas.ferre@microchip.com> +Cc: Steven A Filary <steven.a.filary@intel.com> +Cc: Bryan Hunt <bryan.hunt@intel.com> +--- + MAINTAINERS | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index e0925f221e25..2cd0a46f9a1a 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -8521,6 +8521,17 @@ L: linux-serial@vger.kernel.org + S: Orphan + F: drivers/tty/serial/jsm/ + ++JTAG SUBSYSTEM ++M: Oleksandr Shamray <oleksandrs@mellanox.com> ++M: Vadim Pasternak <vadimp@mellanox.com> ++M Ernesto Corona <ernesto.corona@intel.com> ++S: Maintained ++F: include/linux/jtag.h ++F: include/uapi/linux/jtag.h ++F: drivers/jtag/ ++F: Documentation/devicetree/bindings/jtag/ ++F: Documentation/ABI/testing/jtag-dev ++ + K10TEMP HARDWARE MONITORING DRIVER + M: Clemens Ladisch <clemens@ladisch.de> + L: linux-hwmon@vger.kernel.org +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0058-i2c-aspeed-add-general-call-support.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0058-i2c-aspeed-add-general-call-support.patch new file mode 100644 index 000000000..7d5e006d1 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0058-i2c-aspeed-add-general-call-support.patch @@ -0,0 +1,180 @@ +From f5b6d42c1710a4c1314bc0160f904aa01f501e96 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Wed, 1 May 2019 13:27:34 -0700 +Subject: [PATCH] i2c: aspeed: add general call support + +This commit adds general call support into Aspeed I2C driver. +This is downstream only customization so it should not go into +upstream. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + .../devicetree/bindings/i2c/i2c-aspeed.txt | 1 + + drivers/i2c/busses/i2c-aspeed.c | 39 ++++++++++++++++++++++ + drivers/i2c/i2c-slave-mqueue.c | 4 ++- + include/linux/i2c.h | 1 + + 4 files changed, 44 insertions(+), 1 deletion(-) + +diff --git a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +index 7da7e813b2b0..724ee9f35c10 100644 +--- a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt ++++ b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +@@ -16,6 +16,7 @@ Optional Properties: + - bus-frequency : frequency of the bus clock in Hz defaults to 100 kHz when not + specified + - multi-master : states that there is another master active on this bus. ++- general-call : enables general call receiving. + - bus-timeout-ms: bus timeout in milliseconds defaults to 1 second when not + specified. + - #retries : Number of retries for master transfer. +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index 99bd30953531..8f26060bd685 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -47,6 +47,7 @@ + #define ASPEED_I2CD_SDA_DRIVE_1T_EN BIT(8) + #define ASPEED_I2CD_M_SDA_DRIVE_1T_EN BIT(7) + #define ASPEED_I2CD_M_HIGH_SPEED_EN BIT(6) ++#define ASPEED_I2CD_GCALL_EN BIT(2) + #define ASPEED_I2CD_SLAVE_EN BIT(1) + #define ASPEED_I2CD_MASTER_EN BIT(0) + +@@ -71,6 +72,7 @@ + */ + #define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT BIT(14) + #define ASPEED_I2CD_INTR_BUS_RECOVER_DONE BIT(13) ++#define ASPEED_I2CD_INTR_GCALL_ADDR BIT(8) + #define ASPEED_I2CD_INTR_SLAVE_MATCH BIT(7) + #define ASPEED_I2CD_INTR_SCL_TIMEOUT BIT(6) + #define ASPEED_I2CD_INTR_ABNORMAL BIT(5) +@@ -130,6 +132,8 @@ enum aspeed_i2c_slave_state { + ASPEED_I2C_SLAVE_READ_PROCESSED, + ASPEED_I2C_SLAVE_WRITE_REQUESTED, + ASPEED_I2C_SLAVE_WRITE_RECEIVED, ++ ASPEED_I2C_SLAVE_GCALL_START, ++ ASPEED_I2C_SLAVE_GCALL_REQUESTED, + ASPEED_I2C_SLAVE_STOP, + }; + +@@ -160,6 +164,8 @@ struct aspeed_i2c_bus { + #if IS_ENABLED(CONFIG_I2C_SLAVE) + struct i2c_client *slave; + enum aspeed_i2c_slave_state slave_state; ++ /* General call */ ++ bool general_call; + #endif /* CONFIG_I2C_SLAVE */ + }; + +@@ -266,6 +272,12 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + bus->slave_state = ASPEED_I2C_SLAVE_START; + } + ++ /* General call was requested, restart state machine. */ ++ if (irq_status & ASPEED_I2CD_INTR_GCALL_ADDR) { ++ irq_handled |= ASPEED_I2CD_INTR_GCALL_ADDR; ++ bus->slave_state = ASPEED_I2C_SLAVE_GCALL_START; ++ } ++ + /* Slave is not currently active, irq was for someone else. */ + if (bus->slave_state == ASPEED_I2C_SLAVE_INACTIVE) + return irq_handled; +@@ -284,6 +296,21 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + else + bus->slave_state = + ASPEED_I2C_SLAVE_WRITE_REQUESTED; ++ } else if (bus->slave_state == ASPEED_I2C_SLAVE_GCALL_START) { ++ /* ++ * I2C spec defines the second byte meaning like below. ++ * 0x06 : Reset and write programmable part of slave ++ * address by hardware. ++ * 0x04 : Write programmable part of slave address by ++ * hardware. ++ * 0x00 : No allowed. ++ * ++ * But in OpenBMC, we are going to use this ++ * 'General call' feature for IPMB message broadcasting ++ * so it delivers all data as is without any specific ++ * handling of the second byte. ++ */ ++ bus->slave_state = ASPEED_I2C_SLAVE_GCALL_REQUESTED; + } + irq_handled |= ASPEED_I2CD_INTR_RX_DONE; + } +@@ -326,11 +353,16 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + case ASPEED_I2C_SLAVE_WRITE_RECEIVED: + i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value); + break; ++ case ASPEED_I2C_SLAVE_GCALL_REQUESTED: ++ bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED; ++ i2c_slave_event(slave, I2C_SLAVE_GCALL_REQUESTED, &value); ++ 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: ++ case ASPEED_I2C_SLAVE_GCALL_START: + /* Slave was just started. Waiting for the next event. */; + break; + default: +@@ -738,6 +770,8 @@ static void __aspeed_i2c_reg_slave(struct aspeed_i2c_bus *bus, u16 slave_addr) + /* Turn on slave mode. */ + func_ctrl_reg_val = readl(bus->base + ASPEED_I2C_FUN_CTRL_REG); + func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN; ++ if (bus->general_call) ++ func_ctrl_reg_val |= ASPEED_I2CD_GCALL_EN; + writel(func_ctrl_reg_val, bus->base + ASPEED_I2C_FUN_CTRL_REG); + } + +@@ -776,6 +810,8 @@ static int aspeed_i2c_unreg_slave(struct i2c_client *client) + /* Turn off slave mode. */ + func_ctrl_reg_val = readl(bus->base + ASPEED_I2C_FUN_CTRL_REG); + func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN; ++ if (bus->general_call) ++ func_ctrl_reg_val &= ~ASPEED_I2CD_GCALL_EN; + writel(func_ctrl_reg_val, bus->base + ASPEED_I2C_FUN_CTRL_REG); + + bus->slave = NULL; +@@ -920,6 +956,9 @@ static int aspeed_i2c_init(struct aspeed_i2c_bus *bus, + bus->base + ASPEED_I2C_FUN_CTRL_REG); + + #if IS_ENABLED(CONFIG_I2C_SLAVE) ++ if (of_property_read_bool(pdev->dev.of_node, "general-call")) ++ bus->general_call = true; ++ + /* If slave has already been registered, re-enable it. */ + if (bus->slave) + __aspeed_i2c_reg_slave(bus, bus->slave->addr); +diff --git a/drivers/i2c/i2c-slave-mqueue.c b/drivers/i2c/i2c-slave-mqueue.c +index 2c7a6038409c..1d4db584b393 100644 +--- a/drivers/i2c/i2c-slave-mqueue.c ++++ b/drivers/i2c/i2c-slave-mqueue.c +@@ -56,10 +56,12 @@ static int i2c_slave_mqueue_callback(struct i2c_client *client, + + switch (event) { + case I2C_SLAVE_WRITE_REQUESTED: ++ case I2C_SLAVE_GCALL_REQUESTED: + mq->truncated = 0; + + msg->len = 1; +- msg->buf[0] = client->addr << 1; ++ msg->buf[0] = event == I2C_SLAVE_GCALL_REQUESTED ? ++ 0 : client->addr << 1; + break; + + case I2C_SLAVE_WRITE_RECEIVED: +diff --git a/include/linux/i2c.h b/include/linux/i2c.h +index 3ee92c6a442d..dfdccb2acb24 100644 +--- a/include/linux/i2c.h ++++ b/include/linux/i2c.h +@@ -359,6 +359,7 @@ enum i2c_slave_event { + I2C_SLAVE_WRITE_REQUESTED, + I2C_SLAVE_READ_PROCESSED, + I2C_SLAVE_WRITE_RECEIVED, ++ I2C_SLAVE_GCALL_REQUESTED, + I2C_SLAVE_STOP, + }; + +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0065-i2c-aspeed-fix-master-pending-state-handling.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0065-i2c-aspeed-fix-master-pending-state-handling.patch new file mode 100644 index 000000000..003a96c8d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0065-i2c-aspeed-fix-master-pending-state-handling.patch @@ -0,0 +1,55 @@ +From 4a05d2506e7cb7fb3ad323a16861f09279b4da39 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Tue, 11 Jun 2019 14:59:53 -0700 +Subject: [PATCH] i2c: aspeed: fix master pending state handling + +In case of a master pending state, it should not trigger the master +command because this H/W is sharing the same byte buffer for slave +and master operation, so this commit fixes the issue with making +the master command triggering happens when the state goes to active +state. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/i2c/busses/i2c-aspeed.c | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index 8f26060bd685..d8143c24d3a7 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -383,18 +383,19 @@ 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 the idle state. + */ +- if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) ++ if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) { + bus->master_state = ASPEED_I2C_MASTER_PENDING; ++ return; ++ } + #endif /* CONFIG_I2C_SLAVE */ + ++ bus->master_state = ASPEED_I2C_MASTER_START; + bus->buf_index = 0; + + if (msg->flags & I2C_M_RD) { +@@ -479,7 +480,7 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE) + goto out_no_complete; + +- bus->master_state = ASPEED_I2C_MASTER_START; ++ aspeed_i2c_do_start(bus); + } + #endif /* CONFIG_I2C_SLAVE */ + +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0066-i2c-aspeed-add-buffer-mode-transfer-support.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0066-i2c-aspeed-add-buffer-mode-transfer-support.patch new file mode 100644 index 000000000..9b96b400b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0066-i2c-aspeed-add-buffer-mode-transfer-support.patch @@ -0,0 +1,886 @@ +From 36128aacdc8b642b9ee080e493abbc00de345f1d Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Tue, 11 Jun 2019 15:07:08 -0700 +Subject: [PATCH] i2c: aspeed: add buffer mode transfer support + +Byte mode currently this driver uses makes lots of interrupt call +which isn't good for performance and it makes the driver very +timing sensitive. To improve performance of the driver, this commit +adds buffer mode transfer support which uses I2C SRAM buffer +instead of using a single byte buffer. + +AST2400: +It has 2 KBytes (256 Bytes x 8 pages) of I2C SRAM buffer pool from +0x1e78a800 to 0x1e78afff that can be used for all busses with +buffer pool manipulation. To simplify implementation for supporting +both AST2400 and AST2500, it assigns each 128 Bytes per bus without +using buffer pool manipulation so total 1792 Bytes of I2C SRAM +buffer will be used. + +AST2500: +It has 16 Bytes of individual I2C SRAM buffer per each bus and its +range is from 0x1e78a200 to 0x1e78a2df, so it doesn't have 'buffer +page selection' bit field in the Function control register, and +neither 'base address pointer' bit field in the Pool buffer control +register it has. To simplify implementation for supporting both +AST2400 and AST2500, it writes zeros on those register bit fields +but it's okay because it does nothing in AST2500. + +It provides buffer based master and slave data transfer. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + .../devicetree/bindings/i2c/i2c-aspeed.txt | 40 ++- + arch/arm/boot/dts/aspeed-g4.dtsi | 47 ++-- + arch/arm/boot/dts/aspeed-g5.dtsi | 47 ++-- + drivers/i2c/busses/i2c-aspeed.c | 294 ++++++++++++++++++--- + 4 files changed, 349 insertions(+), 79 deletions(-) + +diff --git a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +index 724ee9f35c10..e1a0ae7a8c08 100644 +--- a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt ++++ b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +@@ -3,7 +3,10 @@ Device tree configuration for the I2C busses on the AST24XX and AST25XX SoCs. + Required Properties: + - #address-cells : should be 1 + - #size-cells : should be 0 +-- reg : address offset and range of bus ++- reg : Address offset and range of bus registers. ++ An additional SRAM buffer address offset and range is ++ optional in case of enabling I2C dedicated SRAM for ++ buffer mode transfer support. + - compatible : should be "aspeed,ast2400-i2c-bus" + or "aspeed,ast2500-i2c-bus" + - clocks : root clock of bus, should reference the APB +@@ -29,12 +32,21 @@ i2c { + #size-cells = <1>; + ranges = <0 0x1e78a000 0x1000>; + +- i2c_ic: interrupt-controller@0 { +- #interrupt-cells = <1>; +- compatible = "aspeed,ast2400-i2c-ic"; ++ i2c_gr: i2c-global-regs@0 { ++ compatible = "aspeed,ast2500-i2c-gr", "syscon"; + reg = <0x0 0x40>; +- interrupts = <12>; +- interrupt-controller; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x40>; ++ ++ i2c_ic: interrupt-controller@0 { ++ #interrupt-cells = <1>; ++ compatible = "aspeed,ast2500-i2c-ic"; ++ reg = <0x0 0x4>; ++ interrupts = <12>; ++ interrupt-controller; ++ }; + }; + + i2c0: i2c-bus@40 { +@@ -42,11 +54,25 @@ i2c { + #size-cells = <0>; + #interrupt-cells = <1>; + reg = <0x40 0x40>; +- compatible = "aspeed,ast2400-i2c-bus"; ++ compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; + bus-frequency = <100000>; + interrupts = <0>; + interrupt-parent = <&i2c_ic>; + }; ++ ++ /* buffer mode transfer enabled */ ++ i2c1: i2c-bus@80 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ #interrupt-cells = <1>; ++ reg = <0x80 0x40>, <0x210 0x10>; ++ compatible = "aspeed,ast2500-i2c-bus"; ++ clocks = <&syscon ASPEED_CLK_APB>; ++ resets = <&syscon ASPEED_RESET_I2C>; ++ bus-frequency = <100000>; ++ interrupts = <1>; ++ interrupt-parent = <&i2c_ic>; ++ }; + }; +diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi +index 47a5029f5bdb..052b1b6b4dc7 100644 +--- a/arch/arm/boot/dts/aspeed-g4.dtsi ++++ b/arch/arm/boot/dts/aspeed-g4.dtsi +@@ -469,12 +469,21 @@ + }; + + &i2c { +- i2c_ic: interrupt-controller@0 { +- #interrupt-cells = <1>; +- compatible = "aspeed,ast2400-i2c-ic"; ++ i2c_gr: i2c-global-regs@0 { ++ compatible = "aspeed,ast2400-i2c-gr", "syscon"; + reg = <0x0 0x40>; +- interrupts = <12>; +- interrupt-controller; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x40>; ++ ++ i2c_ic: interrupt-controller@0 { ++ #interrupt-cells = <1>; ++ compatible = "aspeed,ast2400-i2c-ic"; ++ reg = <0x0 0x4>; ++ interrupts = <12>; ++ interrupt-controller; ++ }; + }; + + i2c0: i2c-bus@40 { +@@ -482,7 +491,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x40 0x40>; ++ reg = <0x40 0x40>, <0x800 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -498,7 +507,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x80 0x40>; ++ reg = <0x80 0x40>, <0x880 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -514,7 +523,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0xc0 0x40>; ++ reg = <0xc0 0x40>, <0x900 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -531,7 +540,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x100 0x40>; ++ reg = <0x100 0x40>, <0x980 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -548,7 +557,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x140 0x40>; ++ reg = <0x140 0x40>, <0xa00 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -565,7 +574,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x180 0x40>; ++ reg = <0x180 0x40>, <0xa80 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -582,7 +591,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x1c0 0x40>; ++ reg = <0x1c0 0x40>, <0xb00 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -599,7 +608,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x300 0x40>; ++ reg = <0x300 0x40>, <0xb80 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -616,7 +625,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x340 0x40>; ++ reg = <0x340 0x40>, <0xc00 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -633,7 +642,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x380 0x40>; ++ reg = <0x380 0x40>, <0xc80 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -650,7 +659,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x3c0 0x40>; ++ reg = <0x3c0 0x40>, <0xd00 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -667,7 +676,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x400 0x40>; ++ reg = <0x400 0x40>, <0xd80 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -684,7 +693,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x440 0x40>; ++ reg = <0x440 0x40>, <0xe00 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -701,7 +710,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x480 0x40>; ++ reg = <0x480 0x40>, <0xe80 0x80>; + compatible = "aspeed,ast2400-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi +index 89a9febf6f14..2d2173d598e4 100644 +--- a/arch/arm/boot/dts/aspeed-g5.dtsi ++++ b/arch/arm/boot/dts/aspeed-g5.dtsi +@@ -586,12 +586,21 @@ + }; + + &i2c { +- i2c_ic: interrupt-controller@0 { +- #interrupt-cells = <1>; +- compatible = "aspeed,ast2500-i2c-ic"; ++ i2c_gr: i2c-global-regs@0 { ++ compatible = "aspeed,ast2500-i2c-gr", "syscon"; + reg = <0x0 0x40>; +- interrupts = <12>; +- interrupt-controller; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges = <0x0 0x0 0x40>; ++ ++ i2c_ic: interrupt-controller@0 { ++ #interrupt-cells = <1>; ++ compatible = "aspeed,ast2500-i2c-ic"; ++ reg = <0x0 0x4>; ++ interrupts = <12>; ++ interrupt-controller; ++ }; + }; + + i2c0: i2c-bus@40 { +@@ -599,7 +608,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x40 0x40>; ++ reg = <0x40 0x40>, <0x200 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -615,7 +624,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x80 0x40>; ++ reg = <0x80 0x40>, <0x210 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -631,7 +640,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0xc0 0x40>; ++ reg = <0xc0 0x40>, <0x220 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -648,7 +657,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x100 0x40>; ++ reg = <0x100 0x40>, <0x230 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -665,7 +674,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x140 0x40>; ++ reg = <0x140 0x40>, <0x240 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -682,7 +691,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x180 0x40>; ++ reg = <0x180 0x40>, <0x250 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -699,7 +708,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x1c0 0x40>; ++ reg = <0x1c0 0x40>, <0x260 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -716,7 +725,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x300 0x40>; ++ reg = <0x300 0x40>, <0x270 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -733,7 +742,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x340 0x40>; ++ reg = <0x340 0x40>, <0x280 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -750,7 +759,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x380 0x40>; ++ reg = <0x380 0x40>, <0x290 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -767,7 +776,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x3c0 0x40>; ++ reg = <0x3c0 0x40>, <0x2a0 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -784,7 +793,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x400 0x40>; ++ reg = <0x400 0x40>, <0x2b0 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -801,7 +810,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x440 0x40>; ++ reg = <0x440 0x40>, <0x2c0 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +@@ -818,7 +827,7 @@ + #size-cells = <0>; + #interrupt-cells = <1>; + +- reg = <0x480 0x40>; ++ reg = <0x480 0x40>, <0x2d0 0x10>; + compatible = "aspeed,ast2500-i2c-bus"; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_I2C>; +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index d8143c24d3a7..b721fb1d6d4c 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -7,6 +7,7 @@ + * Copyright 2017 Google, Inc. + */ + ++#include <linux/bitfield.h> + #include <linux/clk.h> + #include <linux/completion.h> + #include <linux/err.h> +@@ -19,15 +20,24 @@ + #include <linux/irqchip/chained_irq.h> + #include <linux/irqdomain.h> + #include <linux/kernel.h> ++#include <linux/mfd/syscon.h> + #include <linux/module.h> + #include <linux/of_address.h> + #include <linux/of_irq.h> + #include <linux/of_platform.h> + #include <linux/platform_device.h> ++#include <linux/regmap.h> + #include <linux/reset.h> + #include <linux/slab.h> + +-/* I2C Register */ ++/* I2C Global Registers */ ++/* 0x00 : I2CG Interrupt Status Register */ ++/* 0x08 : I2CG Interrupt Target Assignment */ ++/* 0x0c : I2CG Global Control Register (AST2500) */ ++#define ASPEED_I2CG_GLOBAL_CTRL_REG 0x0c ++#define ASPEED_I2CG_SRAM_BUFFER_EN BIT(0) ++ ++/* I2C Bus Registers */ + #define ASPEED_I2C_FUN_CTRL_REG 0x00 + #define ASPEED_I2C_AC_TIMING_REG1 0x04 + #define ASPEED_I2C_AC_TIMING_REG2 0x08 +@@ -35,14 +45,12 @@ + #define ASPEED_I2C_INTR_STS_REG 0x10 + #define ASPEED_I2C_CMD_REG 0x14 + #define ASPEED_I2C_DEV_ADDR_REG 0x18 ++#define ASPEED_I2C_BUF_CTRL_REG 0x1c + #define ASPEED_I2C_BYTE_BUF_REG 0x20 + +-/* Global Register Definition */ +-/* 0x00 : I2C Interrupt Status Register */ +-/* 0x08 : I2C Interrupt Target Assignment */ +- + /* Device Register Definition */ + /* 0x00 : I2CD Function Control Register */ ++#define ASPEED_I2CD_BUFFER_PAGE_SEL_MASK GENMASK(22, 20) + #define ASPEED_I2CD_MULTI_MASTER_DIS BIT(15) + #define ASPEED_I2CD_SDA_DRIVE_1T_EN BIT(8) + #define ASPEED_I2CD_M_SDA_DRIVE_1T_EN BIT(7) +@@ -104,6 +112,8 @@ + #define ASPEED_I2CD_BUS_RECOVER_CMD BIT(11) + + /* Command Bit */ ++#define ASPEED_I2CD_RX_BUFF_ENABLE BIT(7) ++#define ASPEED_I2CD_TX_BUFF_ENABLE BIT(6) + #define ASPEED_I2CD_M_STOP_CMD BIT(5) + #define ASPEED_I2CD_M_S_RX_CMD_LAST BIT(4) + #define ASPEED_I2CD_M_RX_CMD BIT(3) +@@ -114,6 +124,13 @@ + /* 0x18 : I2CD Slave Device Address Register */ + #define ASPEED_I2CD_DEV_ADDR_MASK GENMASK(6, 0) + ++/* 0x1c : I2CD Buffer Control Register */ ++/* Use 8-bits or 6-bits wide bit fileds to support both AST2400 and AST2500 */ ++#define ASPEED_I2CD_BUF_RX_COUNT_MASK GENMASK(31, 24) ++#define ASPEED_I2CD_BUF_RX_SIZE_MASK GENMASK(23, 16) ++#define ASPEED_I2CD_BUF_TX_COUNT_MASK GENMASK(15, 8) ++#define ASPEED_I2CD_BUF_OFFSET_MASK GENMASK(5, 0) ++ + enum aspeed_i2c_master_state { + ASPEED_I2C_MASTER_INACTIVE, + ASPEED_I2C_MASTER_PENDING, +@@ -161,6 +178,11 @@ struct aspeed_i2c_bus { + int master_xfer_result; + /* Multi-master */ + bool multi_master; ++ /* Buffer mode */ ++ void __iomem *buf_base; ++ size_t buf_size; ++ u8 buf_offset; ++ u8 buf_page; + #if IS_ENABLED(CONFIG_I2C_SLAVE) + struct i2c_client *slave; + enum aspeed_i2c_slave_state slave_state; +@@ -259,6 +281,7 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + { + u32 command, irq_handled = 0; + struct i2c_client *slave = bus->slave; ++ int i, len; + u8 value; + + if (!slave) +@@ -287,7 +310,12 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + + /* Slave was sent something. */ + if (irq_status & ASPEED_I2CD_INTR_RX_DONE) { +- value = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8; ++ if (bus->buf_base && ++ bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED && ++ !(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP)) ++ value = readb(bus->buf_base); ++ else ++ value = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8; + /* Handle address frame. */ + if (bus->slave_state == ASPEED_I2C_SLAVE_START) { + if (value & 0x1) +@@ -317,6 +345,20 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + + /* Slave was asked to stop. */ + if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) { ++ if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED && ++ irq_status & ASPEED_I2CD_INTR_RX_DONE) { ++ if (bus->buf_base) { ++ len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK, ++ readl(bus->base + ++ ASPEED_I2C_BUF_CTRL_REG)); ++ for (i = 0; i < len; i++) { ++ value = readb(bus->buf_base + i); ++ i2c_slave_event(slave, ++ I2C_SLAVE_WRITE_RECEIVED, ++ &value); ++ } ++ } ++ } + irq_handled |= ASPEED_I2CD_INTR_NORMAL_STOP; + bus->slave_state = ASPEED_I2C_SLAVE_STOP; + } +@@ -349,9 +391,36 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + case ASPEED_I2C_SLAVE_WRITE_REQUESTED: + bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED; + i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value); ++ if (bus->buf_base) { ++ writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, ++ bus->buf_size - 1) | ++ FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, ++ bus->buf_offset), ++ bus->base + ASPEED_I2C_BUF_CTRL_REG); ++ writel(ASPEED_I2CD_RX_BUFF_ENABLE, ++ bus->base + ASPEED_I2C_CMD_REG); ++ } + break; + case ASPEED_I2C_SLAVE_WRITE_RECEIVED: + i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value); ++ if (bus->buf_base) { ++ len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK, ++ readl(bus->base + ++ ASPEED_I2C_BUF_CTRL_REG)); ++ for (i = 1; i < len; i++) { ++ value = readb(bus->buf_base + i); ++ i2c_slave_event(slave, ++ I2C_SLAVE_WRITE_RECEIVED, ++ &value); ++ } ++ writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, ++ bus->buf_size - 1) | ++ FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, ++ bus->buf_offset), ++ bus->base + ASPEED_I2C_BUF_CTRL_REG); ++ writel(ASPEED_I2CD_RX_BUFF_ENABLE, ++ bus->base + ASPEED_I2C_CMD_REG); ++ } + break; + case ASPEED_I2C_SLAVE_GCALL_REQUESTED: + bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED; +@@ -382,6 +451,8 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus) + u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD; + struct i2c_msg *msg = &bus->msgs[bus->msgs_index]; + u8 slave_addr = i2c_8bit_addr_from_msg(msg); ++ u8 wbuf[4]; ++ int len; + + #if IS_ENABLED(CONFIG_I2C_SLAVE) + /* +@@ -400,12 +471,66 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus) + + if (msg->flags & I2C_M_RD) { + command |= ASPEED_I2CD_M_RX_CMD; +- /* Need to let the hardware know to NACK after RX. */ +- if (msg->len == 1 && !(msg->flags & I2C_M_RECV_LEN)) +- command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ ++ if (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { ++ command |= ASPEED_I2CD_RX_BUFF_ENABLE; ++ ++ if (msg->len > bus->buf_size) { ++ len = bus->buf_size; ++ } else { ++ len = msg->len; ++ command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ } ++ ++ writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, ++ len - 1) | ++ FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, ++ bus->buf_offset), ++ bus->base + ASPEED_I2C_BUF_CTRL_REG); ++ } else { ++ /* Need to let the hardware know to NACK after RX. */ ++ if (msg->len == 1 && !(msg->flags & I2C_M_RECV_LEN)) ++ command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ } ++ } else { ++ if (bus->buf_base) { ++ int i; ++ ++ command |= ASPEED_I2CD_TX_BUFF_ENABLE; ++ ++ if (msg->len + 1 > bus->buf_size) ++ len = bus->buf_size; ++ else ++ len = msg->len + 1; ++ ++ /* ++ * Yeah, it looks clumsy but byte writings on a remapped ++ * I2C SRAM cause corruptions so use this way to make ++ * dword writings. ++ */ ++ wbuf[0] = slave_addr; ++ for (i = 1; i < len; i++) { ++ wbuf[i % 4] = msg->buf[i - 1]; ++ if (i % 4 == 3) ++ writel(*(u32 *)wbuf, ++ bus->buf_base + i - 3); ++ } ++ if (--i % 4 != 3) ++ writel(*(u32 *)wbuf, ++ bus->buf_base + i - (i % 4)); ++ ++ bus->buf_index = len - 1; ++ ++ writel(FIELD_PREP(ASPEED_I2CD_BUF_TX_COUNT_MASK, ++ len - 1) | ++ FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, ++ bus->buf_offset), ++ bus->base + ASPEED_I2C_BUF_CTRL_REG); ++ } + } + +- writel(slave_addr, bus->base + ASPEED_I2C_BYTE_BUF_REG); ++ if (!(command & ASPEED_I2CD_TX_BUFF_ENABLE)) ++ writel(slave_addr, bus->base + ASPEED_I2C_BYTE_BUF_REG); + writel(command, bus->base + ASPEED_I2C_CMD_REG); + } + +@@ -445,7 +570,7 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + u32 irq_handled = 0, command = 0; + struct i2c_msg *msg; + u8 recv_byte; +- int ret; ++ int ret, len; + + if (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE) { + bus->master_state = ASPEED_I2C_MASTER_INACTIVE; +@@ -558,11 +683,43 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + /* fall through */ + case ASPEED_I2C_MASTER_TX_FIRST: + if (bus->buf_index < msg->len) { ++ command = ASPEED_I2CD_M_TX_CMD; ++ ++ if (bus->buf_base) { ++ u8 wbuf[4]; ++ int i; ++ ++ command |= ASPEED_I2CD_TX_BUFF_ENABLE; ++ ++ if (msg->len - bus->buf_index > bus->buf_size) ++ len = bus->buf_size; ++ else ++ len = msg->len - bus->buf_index; ++ ++ for (i = 0; i < len; i++) { ++ wbuf[i % 4] = msg->buf[bus->buf_index ++ + i]; ++ if (i % 4 == 3) ++ writel(*(u32 *)wbuf, ++ bus->buf_base + i - 3); ++ } ++ if (--i % 4 != 3) ++ writel(*(u32 *)wbuf, ++ bus->buf_base + i - (i % 4)); ++ ++ bus->buf_index += len; ++ ++ writel(FIELD_PREP(ASPEED_I2CD_BUF_TX_COUNT_MASK, ++ len - 1) | ++ FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, ++ bus->buf_offset), ++ bus->base + ASPEED_I2C_BUF_CTRL_REG); ++ } else { ++ writel(msg->buf[bus->buf_index++], ++ bus->base + ASPEED_I2C_BYTE_BUF_REG); ++ } ++ writel(command, bus->base + ASPEED_I2C_CMD_REG); + bus->master_state = ASPEED_I2C_MASTER_TX; +- writel(msg->buf[bus->buf_index++], +- bus->base + ASPEED_I2C_BYTE_BUF_REG); +- writel(ASPEED_I2CD_M_TX_CMD, +- bus->base + ASPEED_I2C_CMD_REG); + } else { + aspeed_i2c_next_msg_or_stop(bus); + } +@@ -579,25 +736,56 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + } + irq_handled |= ASPEED_I2CD_INTR_RX_DONE; + +- recv_byte = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8; +- msg->buf[bus->buf_index++] = recv_byte; +- +- if (msg->flags & I2C_M_RECV_LEN) { +- if (unlikely(recv_byte > I2C_SMBUS_BLOCK_MAX)) { +- bus->cmd_err = -EPROTO; +- aspeed_i2c_do_stop(bus); +- goto out_no_complete; ++ if (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { ++ len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK, ++ readl(bus->base + ++ ASPEED_I2C_BUF_CTRL_REG)); ++ memcpy_fromio(msg->buf + bus->buf_index, ++ bus->buf_base, len); ++ bus->buf_index += len; ++ } else { ++ recv_byte = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) ++ >> 8; ++ msg->buf[bus->buf_index++] = recv_byte; ++ ++ if (msg->flags & I2C_M_RECV_LEN) { ++ if (unlikely(recv_byte > I2C_SMBUS_BLOCK_MAX)) { ++ bus->cmd_err = -EPROTO; ++ aspeed_i2c_do_stop(bus); ++ goto out_no_complete; ++ } ++ msg->len = recv_byte + ++ ((msg->flags & I2C_CLIENT_PEC) ? ++ 2 : 1); ++ msg->flags &= ~I2C_M_RECV_LEN; + } +- msg->len = recv_byte + +- ((msg->flags & I2C_CLIENT_PEC) ? 2 : 1); +- msg->flags &= ~I2C_M_RECV_LEN; + } + + if (bus->buf_index < msg->len) { +- bus->master_state = ASPEED_I2C_MASTER_RX; + command = ASPEED_I2CD_M_RX_CMD; +- if (bus->buf_index + 1 == msg->len) +- command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ bus->master_state = ASPEED_I2C_MASTER_RX; ++ if (bus->buf_base) { ++ command |= ASPEED_I2CD_RX_BUFF_ENABLE; ++ ++ if (msg->len - bus->buf_index > ++ bus->buf_size) { ++ len = bus->buf_size; ++ } else { ++ len = msg->len - bus->buf_index; ++ command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ } ++ ++ writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, ++ len - 1) | ++ FIELD_PREP(ASPEED_I2CD_BUF_TX_COUNT_MASK, ++ 0) | ++ FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, ++ bus->buf_offset), ++ bus->base + ASPEED_I2C_BUF_CTRL_REG); ++ } else { ++ if (bus->buf_index + 1 == msg->len) ++ command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ } + writel(command, bus->base + ASPEED_I2C_CMD_REG); + } else { + aspeed_i2c_next_msg_or_stop(bus); +@@ -947,6 +1135,9 @@ static int aspeed_i2c_init(struct aspeed_i2c_bus *bus, + if (ret < 0) + return ret; + ++ fun_ctrl_reg |= FIELD_PREP(ASPEED_I2CD_BUFFER_PAGE_SEL_MASK, ++ bus->buf_page); ++ + if (of_property_read_bool(pdev->dev.of_node, "multi-master")) + bus->multi_master = true; + else +@@ -1007,16 +1198,15 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + { + const struct of_device_id *match; + struct aspeed_i2c_bus *bus; ++ bool sram_enabled = true; + struct clk *parent_clk; +- struct resource *res; + int irq, ret; + + bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); + if (!bus) + return -ENOMEM; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- bus->base = devm_ioremap_resource(&pdev->dev, res); ++ bus->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bus->base)) + return PTR_ERR(bus->base); + +@@ -1050,6 +1240,42 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + bus->get_clk_reg_val = (u32 (*)(struct device *, u32)) + match->data; + ++ /* Enable I2C SRAM in case of AST2500 */ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2500-i2c-bus")) { ++ struct regmap *gr_regmap = syscon_regmap_lookup_by_compatible( ++ "aspeed,ast2500-i2c-gr"); ++ if (IS_ERR(gr_regmap)) ++ ret = PTR_ERR(gr_regmap); ++ else ++ ret = regmap_update_bits(gr_regmap, ++ ASPEED_I2CG_GLOBAL_CTRL_REG, ++ ASPEED_I2CG_SRAM_BUFFER_EN, ++ ASPEED_I2CG_SRAM_BUFFER_EN); ++ ++ if (ret) ++ sram_enabled = false; ++ } ++ ++ if (sram_enabled) { ++ struct resource *res = platform_get_resource(pdev, ++ IORESOURCE_MEM, 1); ++ ++ bus->buf_base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(bus->buf_base) || resource_size(res) < 2) { ++ bus->buf_base = NULL; ++ } else { ++ bus->buf_size = resource_size(res); ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2400-i2c-bus")) { ++ bus->buf_page = ((res->start >> 8) & ++ GENMASK(3, 0)) - 8; ++ bus->buf_offset = (res->start >> 2) & ++ ASPEED_I2CD_BUF_OFFSET_MASK; ++ } ++ } ++ } ++ + /* Initialize the I2C adapter */ + spin_lock_init(&bus->lock); + init_completion(&bus->cmd_complete); +@@ -1085,8 +1311,8 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + + platform_set_drvdata(pdev, bus); + +- dev_info(bus->dev, "i2c bus %d registered, irq %d\n", +- bus->adap.nr, irq); ++ dev_info(bus->dev, "i2c bus %d registered (%s mode), irq %d\n", ++ bus->adap.nr, bus->buf_base ? "buffer" : "byte", irq); + + return 0; + } +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0067-i2c-aspeed-add-DMA-mode-transfer-support.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0067-i2c-aspeed-add-DMA-mode-transfer-support.patch new file mode 100644 index 000000000..a051dbc4d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0067-i2c-aspeed-add-DMA-mode-transfer-support.patch @@ -0,0 +1,442 @@ +From 7defe387ea07af3d48feb4daec78d54a284105f1 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Tue, 18 Jun 2019 08:47:50 -0700 +Subject: [PATCH] i2c: aspeed: add DMA mode transfer support + +This commit adds DMA mode transfer support. + +Only AST2500 supports DMA mode under some limitations: +I2C is sharing the DMA H/W with UHCI host controller and MCTP +controller. Since those controllers operate with DMA mode only, I2C +has to use buffer mode or byte mode instead if one of those +controllers is enabled. Also make sure that if SD/eMMC or Port80 +snoop uses DMA mode instead of PIO or FIFO respectively, I2C can't +use DMA mode. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + .../devicetree/bindings/i2c/i2c-aspeed.txt | 25 +++ + drivers/i2c/busses/i2c-aspeed.c | 231 +++++++++++++++++++-- + 2 files changed, 241 insertions(+), 15 deletions(-) + +diff --git a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +index e1a0ae7a8c08..97142516399a 100644 +--- a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt ++++ b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt +@@ -19,6 +19,16 @@ Optional Properties: + - bus-frequency : frequency of the bus clock in Hz defaults to 100 kHz when not + specified + - multi-master : states that there is another master active on this bus. ++- aspeed,dma-buf-size : size of DMA buffer (from 2 to 4095 in case of AST2500). ++ Only AST2500 supports DMA mode under some limitations: ++ I2C is sharing the DMA H/W with UHCI host controller ++ and MCTP controller. Since those controllers operate ++ with DMA mode only, I2C has to use buffer mode or byte ++ mode instead if one of those controllers is enabled. ++ Also make sure that if SD/eMMC or Port80 snoop uses ++ DMA mode instead of PIO or FIFO respectively, I2C ++ can't use DMA mode. IF both DMA and buffer modes are ++ enabled, DMA mode will be selected. + - general-call : enables general call receiving. + - bus-timeout-ms: bus timeout in milliseconds defaults to 1 second when not + specified. +@@ -75,4 +85,19 @@ i2c { + interrupts = <1>; + interrupt-parent = <&i2c_ic>; + }; ++ ++ /* DMA mode transfer enabled */ ++ i2c2: i2c-bus@c0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ #interrupt-cells = <1>; ++ reg = <0xc0 0x40>; ++ aspeed,dma-buf-size = <4095>; ++ compatible = "aspeed,ast2500-i2c-bus"; ++ clocks = <&syscon ASPEED_CLK_APB>; ++ resets = <&syscon ASPEED_RESET_I2C>; ++ bus-frequency = <100000>; ++ interrupts = <2>; ++ interrupt-parent = <&i2c_ic>; ++ }; + }; +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index b721fb1d6d4c..127bc69952ca 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -10,6 +10,8 @@ + #include <linux/bitfield.h> + #include <linux/clk.h> + #include <linux/completion.h> ++#include <linux/dma-mapping.h> ++#include <linux/dmapool.h> + #include <linux/err.h> + #include <linux/errno.h> + #include <linux/i2c.h> +@@ -47,6 +49,8 @@ + #define ASPEED_I2C_DEV_ADDR_REG 0x18 + #define ASPEED_I2C_BUF_CTRL_REG 0x1c + #define ASPEED_I2C_BYTE_BUF_REG 0x20 ++#define ASPEED_I2C_DMA_ADDR_REG 0x24 ++#define ASPEED_I2C_DMA_LEN_REG 0x28 + + /* Device Register Definition */ + /* 0x00 : I2CD Function Control Register */ +@@ -112,6 +116,8 @@ + #define ASPEED_I2CD_BUS_RECOVER_CMD BIT(11) + + /* Command Bit */ ++#define ASPEED_I2CD_RX_DMA_ENABLE BIT(9) ++#define ASPEED_I2CD_TX_DMA_ENABLE BIT(8) + #define ASPEED_I2CD_RX_BUFF_ENABLE BIT(7) + #define ASPEED_I2CD_TX_BUFF_ENABLE BIT(6) + #define ASPEED_I2CD_M_STOP_CMD BIT(5) +@@ -131,6 +137,14 @@ + #define ASPEED_I2CD_BUF_TX_COUNT_MASK GENMASK(15, 8) + #define ASPEED_I2CD_BUF_OFFSET_MASK GENMASK(5, 0) + ++/* 0x24 : I2CD DMA Mode Buffer Address Register */ ++#define ASPEED_I2CD_DMA_ADDR_MASK GENMASK(31, 2) ++#define ASPEED_I2CD_DMA_ALIGN 4 ++ ++/* 0x28 : I2CD DMA Transfer Length Register */ ++#define ASPEED_I2CD_DMA_LEN_SHIFT 0 ++#define ASPEED_I2CD_DMA_LEN_MASK GENMASK(11, 0) ++ + enum aspeed_i2c_master_state { + ASPEED_I2C_MASTER_INACTIVE, + ASPEED_I2C_MASTER_PENDING, +@@ -183,6 +197,12 @@ struct aspeed_i2c_bus { + size_t buf_size; + u8 buf_offset; + u8 buf_page; ++ /* DMA mode */ ++ struct dma_pool *dma_pool; ++ dma_addr_t dma_handle; ++ u8 *dma_buf; ++ size_t dma_buf_size; ++ size_t dma_len; + #if IS_ENABLED(CONFIG_I2C_SLAVE) + struct i2c_client *slave; + enum aspeed_i2c_slave_state slave_state; +@@ -310,9 +330,13 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + + /* Slave was sent something. */ + if (irq_status & ASPEED_I2CD_INTR_RX_DONE) { +- if (bus->buf_base && ++ if (bus->dma_buf && + bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED && + !(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP)) ++ value = bus->dma_buf[0]; ++ else if (bus->buf_base && ++ bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED && ++ !(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP)) + value = readb(bus->buf_base); + else + value = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8; +@@ -347,7 +371,18 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) { + if (bus->slave_state == ASPEED_I2C_SLAVE_WRITE_RECEIVED && + irq_status & ASPEED_I2CD_INTR_RX_DONE) { +- if (bus->buf_base) { ++ if (bus->dma_buf) { ++ len = bus->dma_buf_size - ++ FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK, ++ readl(bus->base + ++ ASPEED_I2C_DMA_LEN_REG)); ++ for (i = 0; i < len; i++) { ++ value = bus->dma_buf[i]; ++ i2c_slave_event(slave, ++ I2C_SLAVE_WRITE_RECEIVED, ++ &value); ++ } ++ } else if (bus->buf_base) { + len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK, + readl(bus->base + + ASPEED_I2C_BUF_CTRL_REG)); +@@ -391,7 +426,15 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + case ASPEED_I2C_SLAVE_WRITE_REQUESTED: + bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED; + i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value); +- if (bus->buf_base) { ++ if (bus->dma_buf) { ++ writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK, ++ bus->base + ASPEED_I2C_DMA_ADDR_REG); ++ writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, ++ bus->dma_buf_size), ++ bus->base + ASPEED_I2C_DMA_LEN_REG); ++ writel(ASPEED_I2CD_RX_DMA_ENABLE, ++ bus->base + ASPEED_I2C_CMD_REG); ++ } else if (bus->buf_base) { + writel(FIELD_PREP(ASPEED_I2CD_BUF_RX_SIZE_MASK, + bus->buf_size - 1) | + FIELD_PREP(ASPEED_I2CD_BUF_OFFSET_MASK, +@@ -403,7 +446,25 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + break; + case ASPEED_I2C_SLAVE_WRITE_RECEIVED: + i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value); +- if (bus->buf_base) { ++ if (bus->dma_buf) { ++ len = bus->dma_buf_size - ++ FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK, ++ readl(bus->base + ++ ASPEED_I2C_DMA_LEN_REG)); ++ for (i = 1; i < len; i++) { ++ value = bus->dma_buf[i]; ++ i2c_slave_event(slave, ++ I2C_SLAVE_WRITE_RECEIVED, ++ &value); ++ } ++ writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK, ++ bus->base + ASPEED_I2C_DMA_ADDR_REG); ++ writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, ++ bus->dma_buf_size), ++ bus->base + ASPEED_I2C_DMA_LEN_REG); ++ writel(ASPEED_I2CD_RX_DMA_ENABLE, ++ bus->base + ASPEED_I2C_CMD_REG); ++ } else if (bus->buf_base) { + len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK, + readl(bus->base + + ASPEED_I2C_BUF_CTRL_REG)); +@@ -472,7 +533,23 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus) + if (msg->flags & I2C_M_RD) { + command |= ASPEED_I2CD_M_RX_CMD; + +- if (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { ++ if (bus->dma_buf && !(msg->flags & I2C_M_RECV_LEN)) { ++ command |= ASPEED_I2CD_RX_DMA_ENABLE; ++ ++ if (msg->len > bus->dma_buf_size) { ++ len = bus->dma_buf_size; ++ } else { ++ len = msg->len; ++ command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ } ++ ++ writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK, ++ bus->base + ASPEED_I2C_DMA_ADDR_REG); ++ writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, ++ len), ++ bus->base + ASPEED_I2C_DMA_LEN_REG); ++ bus->dma_len = len; ++ } else if (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { + command |= ASPEED_I2CD_RX_BUFF_ENABLE; + + if (msg->len > bus->buf_size) { +@@ -493,7 +570,26 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus) + command |= ASPEED_I2CD_M_S_RX_CMD_LAST; + } + } else { +- if (bus->buf_base) { ++ if (bus->dma_buf) { ++ command |= ASPEED_I2CD_TX_DMA_ENABLE; ++ ++ if (msg->len + 1 > bus->dma_buf_size) ++ len = bus->dma_buf_size; ++ else ++ len = msg->len + 1; ++ ++ bus->dma_buf[0] = slave_addr; ++ memcpy(bus->dma_buf + 1, msg->buf, len); ++ ++ bus->buf_index = len - 1; ++ ++ writel(bus->dma_handle & ASPEED_I2CD_DMA_ADDR_MASK, ++ bus->base + ASPEED_I2C_DMA_ADDR_REG); ++ writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, ++ len), ++ bus->base + ASPEED_I2C_DMA_LEN_REG); ++ bus->dma_len = len; ++ } else if (bus->buf_base) { + int i; + + command |= ASPEED_I2CD_TX_BUFF_ENABLE; +@@ -529,7 +625,8 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus) + } + } + +- if (!(command & ASPEED_I2CD_TX_BUFF_ENABLE)) ++ if (!(command & (ASPEED_I2CD_TX_BUFF_ENABLE | ++ ASPEED_I2CD_TX_DMA_ENABLE))) + writel(slave_addr, bus->base + ASPEED_I2C_BYTE_BUF_REG); + writel(command, bus->base + ASPEED_I2C_CMD_REG); + } +@@ -685,7 +782,28 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + if (bus->buf_index < msg->len) { + command = ASPEED_I2CD_M_TX_CMD; + +- if (bus->buf_base) { ++ if (bus->dma_buf) { ++ command |= ASPEED_I2CD_TX_DMA_ENABLE; ++ ++ if (msg->len - bus->buf_index > ++ bus->dma_buf_size) ++ len = bus->dma_buf_size; ++ else ++ len = msg->len - bus->buf_index; ++ ++ memcpy(bus->dma_buf, msg->buf + bus->buf_index, ++ len); ++ ++ bus->buf_index += len; ++ ++ writel(bus->dma_handle & ++ ASPEED_I2CD_DMA_ADDR_MASK, ++ bus->base + ASPEED_I2C_DMA_ADDR_REG); ++ writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, ++ len), ++ bus->base + ASPEED_I2C_DMA_LEN_REG); ++ bus->dma_len = len; ++ } else if (bus->buf_base) { + u8 wbuf[4]; + int i; + +@@ -736,7 +854,15 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + } + irq_handled |= ASPEED_I2CD_INTR_RX_DONE; + +- if (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { ++ if (bus->dma_buf && !(msg->flags & I2C_M_RECV_LEN)) { ++ len = bus->dma_len - ++ FIELD_GET(ASPEED_I2CD_DMA_LEN_MASK, ++ readl(bus->base + ++ ASPEED_I2C_DMA_LEN_REG)); ++ ++ memcpy(msg->buf + bus->buf_index, bus->dma_buf, len); ++ bus->buf_index += len; ++ } else if (bus->buf_base && !(msg->flags & I2C_M_RECV_LEN)) { + len = FIELD_GET(ASPEED_I2CD_BUF_RX_COUNT_MASK, + readl(bus->base + + ASPEED_I2C_BUF_CTRL_REG)); +@@ -764,7 +890,25 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + if (bus->buf_index < msg->len) { + command = ASPEED_I2CD_M_RX_CMD; + bus->master_state = ASPEED_I2C_MASTER_RX; +- if (bus->buf_base) { ++ if (bus->dma_buf) { ++ command |= ASPEED_I2CD_RX_DMA_ENABLE; ++ ++ if (msg->len - bus->buf_index > ++ bus->dma_buf_size) { ++ len = bus->dma_buf_size; ++ } else { ++ len = msg->len - bus->buf_index; ++ command |= ASPEED_I2CD_M_S_RX_CMD_LAST; ++ } ++ ++ writel(bus->dma_handle & ++ ASPEED_I2CD_DMA_ADDR_MASK, ++ bus->base + ASPEED_I2C_DMA_ADDR_REG); ++ writel(FIELD_PREP(ASPEED_I2CD_DMA_LEN_MASK, ++ len), ++ bus->base + ASPEED_I2C_DMA_LEN_REG); ++ bus->dma_len = len; ++ } else if (bus->buf_base) { + command |= ASPEED_I2CD_RX_BUFF_ENABLE; + + if (msg->len - bus->buf_index > +@@ -1257,7 +1401,51 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + sram_enabled = false; + } + +- if (sram_enabled) { ++ /* ++ * Only AST2500 supports DMA mode under some limitations: ++ * I2C is sharing the DMA H/W with UHCI host controller and MCTP ++ * controller. Since those controllers operate with DMA mode only, I2C ++ * has to use buffer mode or byte mode instead if one of those ++ * controllers is enabled. Also make sure that if SD/eMMC or Port80 ++ * snoop uses DMA mode instead of PIO or FIFO respectively, I2C can't ++ * use DMA mode. ++ */ ++ if (sram_enabled && !IS_ENABLED(CONFIG_USB_UHCI_ASPEED) && ++ of_device_is_compatible(pdev->dev.of_node, ++ "aspeed,ast2500-i2c-bus")) { ++ u32 dma_len_max = ASPEED_I2CD_DMA_LEN_MASK >> ++ ASPEED_I2CD_DMA_LEN_SHIFT; ++ ++ ret = device_property_read_u32(&pdev->dev, ++ "aspeed,dma-buf-size", ++ &bus->dma_buf_size); ++ if (!ret && bus->dma_buf_size > dma_len_max) ++ bus->dma_buf_size = dma_len_max; ++ } ++ ++ if (bus->dma_buf_size) { ++ if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) { ++ dev_warn(&pdev->dev, "No suitable DMA available\n"); ++ } else { ++ bus->dma_pool = dma_pool_create("i2c-aspeed", ++ &pdev->dev, ++ bus->dma_buf_size, ++ ASPEED_I2CD_DMA_ALIGN, ++ 0); ++ if (bus->dma_pool) ++ bus->dma_buf = dma_pool_alloc(bus->dma_pool, ++ GFP_KERNEL, ++ &bus->dma_handle); ++ ++ if (!bus->dma_buf) { ++ dev_warn(&pdev->dev, ++ "Cannot allocate DMA buffer\n"); ++ dma_pool_destroy(bus->dma_pool); ++ } ++ } ++ } ++ ++ if (!bus->dma_buf && sram_enabled) { + struct resource *res = platform_get_resource(pdev, + IORESOURCE_MEM, 1); + +@@ -1297,24 +1485,33 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + */ + ret = aspeed_i2c_init(bus, pdev); + if (ret < 0) +- return ret; ++ goto out_free_dma_buf; + + irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + ret = devm_request_irq(&pdev->dev, irq, aspeed_i2c_bus_irq, + 0, dev_name(&pdev->dev), bus); + if (ret < 0) +- return ret; ++ goto out_free_dma_buf; + + ret = i2c_add_adapter(&bus->adap); + if (ret < 0) +- return ret; ++ goto out_free_dma_buf; + + platform_set_drvdata(pdev, bus); + + dev_info(bus->dev, "i2c bus %d registered (%s mode), irq %d\n", +- bus->adap.nr, bus->buf_base ? "buffer" : "byte", irq); ++ bus->adap.nr, bus->dma_buf ? "dma" : ++ bus->buf_base ? "buffer" : "byte", ++ irq); + + return 0; ++ ++out_free_dma_buf: ++ if (bus->dma_buf) ++ dma_pool_free(bus->dma_pool, bus->dma_buf, bus->dma_handle); ++ dma_pool_destroy(bus->dma_pool); ++ ++ return ret; + } + + static int aspeed_i2c_remove_bus(struct platform_device *pdev) +@@ -1332,6 +1529,10 @@ static int aspeed_i2c_remove_bus(struct platform_device *pdev) + + reset_control_assert(bus->rst); + ++ if (bus->dma_buf) ++ dma_pool_free(bus->dma_pool, bus->dma_buf, bus->dma_handle); ++ dma_pool_destroy(bus->dma_pool); ++ + i2c_del_adapter(&bus->adap); + + return 0; +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0068-i2c-aspeed-add-H-W-timeout-support.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0068-i2c-aspeed-add-H-W-timeout-support.patch new file mode 100644 index 000000000..f4dfd6cfa --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0068-i2c-aspeed-add-H-W-timeout-support.patch @@ -0,0 +1,181 @@ +From 9a43b47fb794fd195912c6956783b021a46307f8 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Thu, 11 Jul 2019 13:53:34 -0700 +Subject: [PATCH] i2c: aspeed: add H/W timeout support + +This commit adds I2C H/W timeout support. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/i2c/busses/i2c-aspeed.c | 79 +++++++++++++++++++++++++++++++++++++---- + 1 file changed, 73 insertions(+), 6 deletions(-) + +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index 0ed9a27850e6..ecb5793036cc 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -71,10 +71,14 @@ + #define ASPEED_I2CD_TIME_SCL_HIGH_MASK GENMASK(19, 16) + #define ASPEED_I2CD_TIME_SCL_LOW_SHIFT 12 + #define ASPEED_I2CD_TIME_SCL_LOW_MASK GENMASK(15, 12) ++#define ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_SHIFT 8 ++#define ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK GENMASK(9, 8) + #define ASPEED_I2CD_TIME_BASE_DIVISOR_MASK GENMASK(3, 0) + #define ASPEED_I2CD_TIME_SCL_REG_MAX GENMASK(3, 0) ++ + /* 0x08 : I2CD Clock and AC Timing Control Register #2 */ +-#define ASPEED_NO_TIMEOUT_CTRL 0 ++#define ASPEED_I2CD_TIMEOUT_CYCLES_SHIFT 0 ++#define ASPEED_I2CD_TIMEOUT_CYCLES_MASK GENMASK(4, 0) + + /* 0x0c : I2CD Interrupt Control Register & + * 0x10 : I2CD Interrupt Status Register +@@ -82,6 +86,7 @@ + * These share bit definitions, so use the same values for the enable & + * status bits. + */ ++#define ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT BIT(15) + #define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT BIT(14) + #define ASPEED_I2CD_INTR_BUS_RECOVER_DONE BIT(13) + #define ASPEED_I2CD_INTR_GCALL_ADDR BIT(8) +@@ -98,8 +103,11 @@ + ASPEED_I2CD_INTR_SCL_TIMEOUT | \ + ASPEED_I2CD_INTR_ABNORMAL | \ + ASPEED_I2CD_INTR_ARBIT_LOSS) ++#define ASPEED_I2CD_INTR_SLAVE_ERRORS \ ++ ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT + #define ASPEED_I2CD_INTR_ALL \ +- (ASPEED_I2CD_INTR_SDA_DL_TIMEOUT | \ ++ (ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT | \ ++ ASPEED_I2CD_INTR_SDA_DL_TIMEOUT | \ + ASPEED_I2CD_INTR_BUS_RECOVER_DONE | \ + ASPEED_I2CD_INTR_SCL_TIMEOUT | \ + ASPEED_I2CD_INTR_ABNORMAL | \ +@@ -180,6 +188,7 @@ struct aspeed_i2c_bus { + u32 divisor); + unsigned long parent_clk_frequency; + u32 bus_frequency; ++ u32 hw_timeout_ms; + /* Transaction state. */ + enum aspeed_i2c_master_state master_state; + struct i2c_msg *msgs; +@@ -297,6 +306,14 @@ static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus) + } + + #if IS_ENABLED(CONFIG_I2C_SLAVE) ++static int aspeed_i2c_check_slave_error(u32 irq_status) ++{ ++ if (irq_status & ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT) ++ return -EIO; ++ ++ return 0; ++} ++ + static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + { + u32 command, irq_handled = 0; +@@ -307,6 +324,14 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + if (!slave) + return 0; + ++ if (aspeed_i2c_check_slave_error(irq_status)) { ++ dev_dbg(bus->dev, "received slave error interrupt: 0x%08x\n", ++ irq_status); ++ irq_handled |= (irq_status & ASPEED_I2CD_INTR_SLAVE_ERRORS); ++ bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE; ++ return irq_handled; ++ } ++ + command = readl(bus->base + ASPEED_I2C_CMD_REG); + + /* Slave was requested, restart state machine. */ +@@ -649,7 +674,7 @@ static void aspeed_i2c_next_msg_or_stop(struct aspeed_i2c_bus *bus) + } + } + +-static int aspeed_i2c_is_irq_error(u32 irq_status) ++static int aspeed_i2c_check_master_error(u32 irq_status) + { + if (irq_status & ASPEED_I2CD_INTR_ARBIT_LOSS) + return -EAGAIN; +@@ -680,9 +705,9 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + * should clear the command queue effectively taking us back to the + * INACTIVE state. + */ +- ret = aspeed_i2c_is_irq_error(irq_status); ++ ret = aspeed_i2c_check_master_error(irq_status); + if (ret) { +- dev_dbg(bus->dev, "received error interrupt: 0x%08x\n", ++ dev_dbg(bus->dev, "received master error interrupt: 0x%08x\n", + irq_status); + irq_handled |= (irq_status & ASPEED_I2CD_INTR_MASTER_ERRORS); + if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) { +@@ -1251,6 +1276,7 @@ static u32 aspeed_i2c_25xx_get_clk_reg_val(struct device *dev, u32 divisor) + /* precondition: bus.lock has been acquired. */ + static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus) + { ++ u32 timeout_base_divisor, timeout_tick_us, timeout_cycles; + u32 divisor, clk_reg_val; + + divisor = DIV_ROUND_UP(bus->parent_clk_frequency, bus->bus_frequency); +@@ -1259,8 +1285,46 @@ static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus) + ASPEED_I2CD_TIME_THDSTA_MASK | + ASPEED_I2CD_TIME_TACST_MASK); + clk_reg_val |= bus->get_clk_reg_val(bus->dev, divisor); ++ ++ if (bus->hw_timeout_ms) { ++ u8 div_max = ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK >> ++ ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_SHIFT; ++ u8 cycles_max = ASPEED_I2CD_TIMEOUT_CYCLES_MASK >> ++ ASPEED_I2CD_TIMEOUT_CYCLES_SHIFT; ++ ++ timeout_base_divisor = 0; ++ ++ do { ++ timeout_tick_us = 1000 * (16384 << ++ (timeout_base_divisor << 1)) / ++ (bus->parent_clk_frequency / 1000); ++ ++ if (timeout_base_divisor == div_max || ++ timeout_tick_us * ASPEED_I2CD_TIMEOUT_CYCLES_MASK >= ++ bus->hw_timeout_ms * 1000) ++ break; ++ } while (timeout_base_divisor++ < div_max); ++ ++ if (timeout_tick_us) { ++ timeout_cycles = DIV_ROUND_UP(bus->hw_timeout_ms * 1000, ++ timeout_tick_us); ++ if (timeout_cycles == 0) ++ timeout_cycles = 1; ++ else if (timeout_cycles > cycles_max) ++ timeout_cycles = cycles_max; ++ } else { ++ timeout_cycles = 0; ++ } ++ } else { ++ timeout_base_divisor = 0; ++ timeout_cycles = 0; ++ } ++ ++ clk_reg_val |= FIELD_PREP(ASPEED_I2CD_TIME_TIMEOUT_BASE_DIVISOR_MASK, ++ timeout_base_divisor); ++ + writel(clk_reg_val, bus->base + ASPEED_I2C_AC_TIMING_REG1); +- writel(ASPEED_NO_TIMEOUT_CTRL, bus->base + ASPEED_I2C_AC_TIMING_REG2); ++ writel(timeout_cycles, bus->base + ASPEED_I2C_AC_TIMING_REG2); + + return 0; + } +@@ -1464,6 +1528,9 @@ static int aspeed_i2c_probe_bus(struct platform_device *pdev) + } + } + ++ device_property_read_u32(&pdev->dev, "aspeed,hw-timeout-ms", ++ &bus->hw_timeout_ms); ++ + /* Initialize the I2C adapter */ + spin_lock_init(&bus->lock); + init_completion(&bus->cmd_complete); +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0069-i2c-aspeed-add-SLAVE_ADDR_RECEIVED_PENDING-interrupt.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0069-i2c-aspeed-add-SLAVE_ADDR_RECEIVED_PENDING-interrupt.patch new file mode 100644 index 000000000..dc38c81f3 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0069-i2c-aspeed-add-SLAVE_ADDR_RECEIVED_PENDING-interrupt.patch @@ -0,0 +1,50 @@ +From 085bde1e91d85435c44a752bd59d38cf31465518 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Thu, 11 Jul 2019 14:04:39 -0700 +Subject: [PATCH] i2c: aspeed: add SLAVE_ADDR_RECEIVED_PENDING interrupt + handling + +If a peer master sends messages too quickly before it processes +previous slave DMA data handling, this indicator will be set. It's +just a indicator and driver can't recover this case so just ignore +it. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/i2c/busses/i2c-aspeed.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c +index d41f377de9dc..46de9a01a0eb 100644 +--- a/drivers/i2c/busses/i2c-aspeed.c ++++ b/drivers/i2c/busses/i2c-aspeed.c +@@ -86,6 +86,7 @@ + * These share bit definitions, so use the same values for the enable & + * status bits. + */ ++#define ASPEED_I2CD_INTR_SLAVE_ADDR_RECEIVED_PENDING BIT(30) + #define ASPEED_I2CD_INTR_SLAVE_INACTIVE_TIMEOUT BIT(15) + #define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT BIT(14) + #define ASPEED_I2CD_INTR_BUS_RECOVER_DONE BIT(13) +@@ -353,6 +354,18 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status) + dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n", + irq_status, command); + ++ /* ++ * If a peer master sends messages too quickly before it processes ++ * previous slave DMA data handling, this indicator will be set. It's ++ * just a indicator and driver can't recover this case so just ignore ++ * it. ++ */ ++ if (unlikely(irq_status & ++ ASPEED_I2CD_INTR_SLAVE_ADDR_RECEIVED_PENDING)) { ++ dev_dbg(bus->dev, "A slave addr match interrupt is pending.\n"); ++ irq_handled |= ASPEED_I2CD_INTR_SLAVE_ADDR_RECEIVED_PENDING; ++ } ++ + /* Slave was sent something. */ + if (irq_status & ASPEED_I2CD_INTR_RX_DONE) { + if (bus->dma_buf && +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0070-gpio-aspeed-temporary-fix-for-gpiochip-range-setting.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0070-gpio-aspeed-temporary-fix-for-gpiochip-range-setting.patch new file mode 100644 index 000000000..4af5be95d --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0070-gpio-aspeed-temporary-fix-for-gpiochip-range-setting.patch @@ -0,0 +1,44 @@ +From 9195eef3fec7022ca52ac9791c19de2362ed042e Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Fri, 19 Jul 2019 12:54:38 -0700 +Subject: [PATCH] gpio: aspeed: temporary fix for gpiochip range setting + +Since we are still using fixed indices for gpio line numbers for sysfs +interface, this commit set the gpiochip range as fixed temporariliy +til we replace all index based gpio uses with name based uses. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/gpio/gpio-aspeed.c | 2 +- + drivers/gpio/sgpio-aspeed.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c +index e426452a28f7..d4aaf7fa8e4b 100644 +--- a/drivers/gpio/gpio-aspeed.c ++++ b/drivers/gpio/gpio-aspeed.c +@@ -1189,7 +1189,7 @@ static int __init aspeed_gpio_probe(struct platform_device *pdev) + gpio->chip.set = aspeed_gpio_set; + gpio->chip.set_config = aspeed_gpio_set_config; + gpio->chip.label = dev_name(&pdev->dev); +- gpio->chip.base = -1; ++ gpio->chip.base = 0; + gpio->chip.irq.need_valid_mask = true; + + /* Allocate a cache of the output registers */ +diff --git a/drivers/gpio/sgpio-aspeed.c b/drivers/gpio/sgpio-aspeed.c +index 6fb402a3f74d..2f4c0aab0bf2 100644 +--- a/drivers/gpio/sgpio-aspeed.c ++++ b/drivers/gpio/sgpio-aspeed.c +@@ -675,7 +675,7 @@ static int __init aspeed_sgpio_probe(struct platform_device *pdev) + gpio->chip.set = aspeed_sgpio_set; + gpio->chip.set_config = aspeed_sgpio_set_config; + gpio->chip.label = dev_name(&pdev->dev); +- gpio->chip.base = -1; ++ gpio->chip.base = gpio->config->nr_pgpios; + gpio->chip.irq.need_valid_mask = true; + + /* Allocate a cache of the output registers */ +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0071-peci-add-a-temporary-workaround.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0071-peci-add-a-temporary-workaround.patch new file mode 100644 index 000000000..e32ec54ac --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0071-peci-add-a-temporary-workaround.patch @@ -0,0 +1,101 @@ +From 3394fabbd17ad7263feeb0f4ae593056237f0647 Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Tue, 30 Jul 2019 13:01:58 -0700 +Subject: [PATCH] peci: add a temporary workaround + +To cover a PECI issue, this commit makes PECI driver block all PECI +commands when PLTRST_N signal is 0. + +Also, it adds 'use_wa' module parameter for platforms that don't have +the PLTRST_N gpio input so that the WA can be disabled through the +module parameter. + +This is a temporary workaround. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/peci/busses/peci-aspeed.c | 11 +++++++++++ + drivers/peci/peci-core.c | 11 +++++++++++ + include/linux/peci.h | 1 + + 3 files changed, 23 insertions(+) + +diff --git a/drivers/peci/busses/peci-aspeed.c b/drivers/peci/busses/peci-aspeed.c +index 8a0dd40730cc..76394ab32854 100644 +--- a/drivers/peci/busses/peci-aspeed.c ++++ b/drivers/peci/busses/peci-aspeed.c +@@ -10,6 +10,7 @@ + #include <linux/jiffies.h> + #include <linux/module.h> + #include <linux/of.h> ++#include <linux/of_gpio.h> + #include <linux/peci.h> + #include <linux/platform_device.h> + #include <linux/reset.h> +@@ -445,6 +446,16 @@ static int aspeed_peci_probe(struct platform_device *pdev) + if (ret) + goto err_put_adapter_dev; + ++ priv->adapter->pltrst_pin = of_get_gpio(pdev->dev.of_node, 0); ++ if (gpio_is_valid(priv->adapter->pltrst_pin)) { ++ ret = devm_gpio_request(&pdev->dev, priv->adapter->pltrst_pin, ++ "peci-aspeed"); ++ if (ret < 0) { ++ priv->adapter->pltrst_pin = -1; ++ dev_err(&pdev->dev, "error requesting pltrst gpio\n"); ++ } ++ } ++ + ret = peci_add_adapter(priv->adapter); + if (ret) + goto err_put_adapter_dev; +diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c +index b99ba788a032..2e3b9a0c83e9 100644 +--- a/drivers/peci/peci-core.c ++++ b/drivers/peci/peci-core.c +@@ -5,6 +5,7 @@ + #include <linux/crc8.h> + #include <linux/delay.h> + #include <linux/mm.h> ++#include <linux/gpio.h> + #include <linux/module.h> + #include <linux/of_device.h> + #include <linux/peci.h> +@@ -190,6 +191,11 @@ static int peci_aw_fcs(struct peci_xfer_msg *msg, int len, u8 *aw_fcs) + return 0; + } + ++/* Temporary WA */ ++static bool use_wa __read_mostly = true; ++module_param_named(use_wa, use_wa, bool, 0644); ++MODULE_PARM_DESC(use_wa, "flag for enabling of WA"); ++ + static int __peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg, + bool do_retry, bool has_aw_fcs) + { +@@ -197,6 +203,11 @@ static int __peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg, + u8 aw_fcs; + int ret; + ++ /* Temporary WA */ ++ if (use_wa && gpio_is_valid(adapter->pltrst_pin) && ++ gpio_get_value(adapter->pltrst_pin) == 0) ++ return -EAGAIN; ++ + /* + * In case if adapter uses DMA, check at here whether tx and rx buffers + * are DMA capable or not. +diff --git a/include/linux/peci.h b/include/linux/peci.h +index 6fc424dc2a73..e589cb258a2a 100644 +--- a/include/linux/peci.h ++++ b/include/linux/peci.h +@@ -44,6 +44,7 @@ struct peci_adapter { + struct peci_xfer_msg *msg); + u32 cmd_mask; + bool use_dma; ++ int pltrst_pin; + }; + + static inline struct peci_adapter *to_peci_adapter(void *d) +-- +2.7.4 + diff --git a/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0072-pmbus-add-fault-and-beep-attributes.patch b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0072-pmbus-add-fault-and-beep-attributes.patch new file mode 100644 index 000000000..40a9272e0 --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed/0072-pmbus-add-fault-and-beep-attributes.patch @@ -0,0 +1,88 @@ +From e360a6c2a3f15bfc8900c7262c56f9bcd5e0f16e Mon Sep 17 00:00:00 2001 +From: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +Date: Thu, 8 Aug 2019 10:38:00 -0700 +Subject: [PATCH] pmbus: add 'fault' and 'beep' attributes + +This commit adds two more attirbutes to reflect MFR_SPECIFIC bit in +the STATUS_WORD and 'Unit Off For Insufficient Input Voltage' bit in +the STATUS_INPUT into 'fault' and 'beep' attributes respectively. + +The attributes will be enumerated as 'inX_fault' and 'inX_beep' in +a 'vin' group. + +Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com> +--- + drivers/hwmon/pmbus/pmbus.h | 1 + + drivers/hwmon/pmbus/pmbus_core.c | 30 ++++++++++++++++++++++++++++++ + 2 files changed, 31 insertions(+) + +diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h +index d198af3a92b6..09e6fe01c304 100644 +--- a/drivers/hwmon/pmbus/pmbus.h ++++ b/drivers/hwmon/pmbus/pmbus.h +@@ -303,6 +303,7 @@ enum pmbus_fan_mode { percent = 0, rpm }; + #define PB_PIN_OP_WARNING BIT(0) + #define PB_IIN_OC_WARNING BIT(1) + #define PB_IIN_OC_FAULT BIT(2) ++#define PB_UNIT_OFF_FOR_INSUF_VIN BIT(3) + + /* + * STATUS_TEMPERATURE +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +index 898d7378f4f8..85295a45c3ba 100644 +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -1163,6 +1163,8 @@ struct pmbus_limit_attr { + struct pmbus_sensor_attr { + u16 reg; /* sensor register */ + u16 gbit; /* generic status bit */ ++ u16 gfbit; /* generic fault status bit */ ++ u16 sbbit; /* beep status bit */ + u8 nlimit; /* # of limit registers */ + enum pmbus_sensor_classes class;/* sensor class */ + const char *label; /* sensor label */ +@@ -1264,6 +1266,32 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client, + return ret; + } + } ++ /* ++ * Add fault attribute if there is a generic fault bit, and if ++ * the generic status register (word or byte, depending on which global ++ * bit is set) for this page is accessible. ++ */ ++ if (attr->gfbit) { ++ upper = !!(attr->gfbit & 0xff00); /* need to check STATUS_WORD */ ++ if ((!upper || (upper && data->has_status_word)) && ++ pmbus_check_status_register(client, page)) { ++ ret = pmbus_add_boolean(data, name, "fault", index, ++ NULL, NULL, ++ PB_STATUS_BASE + page, ++ attr->gfbit); ++ if (ret) ++ return ret; ++ } ++ } ++ /* Add beep attribute if there is a beep status bit. */ ++ if (attr->sbbit) { ++ ret = pmbus_add_boolean(data, name, "beep", index, ++ NULL, NULL, ++ attr->sbase + page, ++ attr->sbbit); ++ if (ret) ++ return ret; ++ } + return 0; + } + +@@ -1435,6 +1463,8 @@ static const struct pmbus_sensor_attr voltage_attributes[] = { + .gbit = PB_STATUS_VIN_UV, + .limit = vin_limit_attrs, + .nlimit = ARRAY_SIZE(vin_limit_attrs), ++ .gfbit = PB_STATUS_WORD_MFR, ++ .sbbit = PB_UNIT_OFF_FOR_INSUF_VIN, + }, { + .reg = PMBUS_VIRT_READ_VMON, + .class = PSC_VOLTAGE_IN, +-- +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..8ff89685b --- /dev/null +++ b/meta-openbmc-mods/meta-common/recipes-kernel/linux/linux-aspeed_%.bbappend @@ -0,0 +1,51 @@ +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://0020-misc-aspeed-add-lpc-mbox-driver.patch \ + file://0021-Initial-Port-of-Aspeed-LPC-SIO-driver.patch \ + file://0022-Add-AST2500-eSPI-driver.patch \ + file://0026-Add-support-for-new-PECI-commands.patch \ + file://0028-Add-AST2500-JTAG-driver.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-aspeed-Swap-the-mac-nodes-numbering.patch \ + file://0035-Implement-a-memory-driver-share-memory.patch \ + file://0039-Add-Aspeed-PWM-driver-which-uses-FTTMR010-timer-IP.patch \ + file://0040-i2c-Add-mux-hold-unhold-msg-types.patch \ + file://0042-Add-bus-timeout-ms-and-retries-device-tree-propertie.patch \ + file://0043-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-BT.patch \ + file://0044-misc-Add-clock-control-logic-into-Aspeed-LPC-SNOOP-d.patch \ + file://0045-char-ipmi-Add-clock-control-logic-into-Aspeed-LPC-KC.patch \ + file://0047-misc-Block-error-printing-on-probe-defer-case-in-Asp.patch \ + file://0049-Suppress-excessive-HID-gadget-error-logs.patch \ + file://0051-Add-AST2500-JTAG-device.patch \ + file://0052-drivers-jtag-Add-JTAG-core-driver.patch \ + file://0053-Add-Aspeed-SoC-24xx-and-25xx-families-JTAG.patch \ + file://0054-Documentation-jtag-Add-bindings-for-Aspeed-SoC.patch \ + file://0055-Documentation-jtag-Add-ABI-documentation.patch \ + file://0056-Documentation-jtag-Add-JTAG-core-driver-ioctl-number.patch \ + file://0057-drivers-jtag-Add-JTAG-core-driver-Maintainers.patch \ + file://0058-i2c-aspeed-add-general-call-support.patch \ + file://0065-i2c-aspeed-fix-master-pending-state-handling.patch \ + file://0066-i2c-aspeed-add-buffer-mode-transfer-support.patch \ + file://0067-i2c-aspeed-add-DMA-mode-transfer-support.patch \ + file://0001-set-idle-disconnect-to-true-in-all-cases.patch \ + file://0068-i2c-aspeed-add-H-W-timeout-support.patch \ + file://0069-i2c-aspeed-add-SLAVE_ADDR_RECEIVED_PENDING-interrupt.patch \ + file://0070-gpio-aspeed-temporary-fix-for-gpiochip-range-setting.patch \ + file://0071-peci-add-a-temporary-workaround.patch \ + file://0072-pmbus-add-fault-and-beep-attributes.patch \ + " |