Re: [PATCH linux ipmi for BMC v2] ipmi: add an Aspeed KCS IPMI BMC driver

From: Corey Minyard
Date: Wed Dec 20 2017 - 20:12:40 EST


On 12/20/2017 06:59 PM, Wang, Haiyue wrote:


On 2017-12-21 04:11, Corey Minyard wrote:
On 12/14/2017 10:34 PM, Haiyue Wang wrote:
This patch adds a simple device driver to expose the KCS interface on
Aspeed SOCs (AST2400 and AST2500) as a character device. Such SOCs are
commonly used as BMCs (BaseBoard Management Controllers) and this driver
implements the BMC side of the KCS interface.

The KCS (Keyboard Controller Style) interface is used to perform in-band
IPMI communication between a host and its BMC.

The device name defaults to '/dev/ipmi-kcsX', X=1,2,3,4.

Looking through this, I have no objections. I didn't do an analysis of the state machine. Have you tested it through the various error states? You'd have to hack the IPMI driver on the host side to be able to do that, but it's not too hard.

Not hack the IPMI driver, but work well with it in so many stress cycle test and long time running well.

That is probably not enough. Under normal conditions you will never see an abort, you generally have to force it. That's a lot of code that is essentially untested.


My only comment is that it might be nice to have consistent ioctl numbers for the operations on a BMC (at least ATN would be common between KCS and BT). But that's not a big deal.

In KCS, the ATN has the set/clear requirement, BT only has set requirement. Or you mean change '1' to ' _IOW(__KCS_BMC_IOCTL_MAGIC, 0, unsigned long)' as BT ?


I mean "discuss with the BT driver maintainers so that the BT and KCS interfaces work essentially the same."Â From a user point of view, it would be nice to not have to know if it's KCS or BT for the most part.

-corey

#define __BT_BMC_IOCTL_MAGICÂÂÂ 0xb1
#define BT_BMC_IOCTL_SMS_ATNÂÂÂ _IO(__BT_BMC_IOCTL_MAGIC, 0x00)

#define __KCS_BMC_IOCTL_MAGICÂÂÂÂÂ 'K'
#define KCS_BMC_IOCTL_SMS_ATNÂÂÂÂÂ _IOW(__KCS_BMC_IOCTL_MAGIC, 1, unsigned long)
#define KCS_BMC_IOCTL_FORCE_ABORTÂ _IO(__KCS_BMC_IOCTL_MAGIC, 2)
Anyone from the openbmc group want to comment on this?

-corey

Signed-off-by: Haiyue Wang <haiyue.wang@xxxxxxxxxxxxxxx>
---
 .../devicetree/bindings/mfd/aspeed-lpc.txt | 31 +-
 drivers/char/ipmi/Kconfig | 9 +
 drivers/char/ipmi/Makefile | 1 +
 drivers/char/ipmi/kcs-bmc.c | 759 +++++++++++++++++++++
 include/uapi/linux/kcs-bmc.h | 13 +
 5 files changed, 810 insertions(+), 3 deletions(-)
 create mode 100644 drivers/char/ipmi/kcs-bmc.c
 create mode 100644 include/uapi/linux/kcs-bmc.h

diff --git a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
index 514d82c..e682000 100644
--- a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
+++ b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
@@ -62,12 +62,24 @@ BMC Node
 --------
  - compatible: One of:
-ÂÂÂÂÂÂÂ "aspeed,ast2400-lpc-bmc"
-ÂÂÂÂÂÂÂ "aspeed,ast2500-lpc-bmc"
+ÂÂÂÂÂÂÂ "aspeed,ast2400-lpc-bmc", "simple-mfd", "syscon"
+ÂÂÂÂÂÂÂ "aspeed,ast2500-lpc-bmc", "simple-mfd", "syscon"
  - reg: contains the physical address and length values of the
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ H8S/2168-compatible LPC controller memory region
 +BMC KCS Node
+------------
+
+- compatible:ÂÂÂ One of:
+ÂÂÂÂÂÂÂ "aspeed,ast2400-kcs-bmc"
+ÂÂÂÂÂÂÂ "aspeed,ast2500-kcs-bmc"
+
+- kcs_chan: The LPC channel number
+
+- kcs_addr: The host CPU IO map address
+
+
 Host Node
 ---------
 @@ -94,8 +106,21 @@ lpc: lpc@1e789000 {
ÂÂÂÂÂ ranges = <0x0 0x1e789000 0x1000>;
 Â lpc_bmc: lpc-bmc@0 {
-ÂÂÂÂÂÂÂ compatible = "aspeed,ast2500-lpc-bmc";
+ÂÂÂÂÂÂÂ compatible = "aspeed,ast2500-lpc-bmc", "simple-mfd", "syscon";
ÂÂÂÂÂÂÂÂÂ reg = <0x0 0x80>;
+ÂÂÂÂÂÂÂ reg-io-width = <4>;
+
+ÂÂÂÂÂÂÂ #address-cells = <1>;
+ÂÂÂÂÂÂÂ #size-cells = <1>;
+ÂÂÂÂÂÂÂ ranges = <0x0 0x0 0x80>;
+
+ÂÂÂÂÂÂÂ kcs3: kcs3@0 {
+ÂÂÂÂÂÂÂÂÂÂÂ compatible = "aspeed,ast2500-kcs-bmc";
+ÂÂÂÂÂÂÂÂÂÂÂ interrupts = <8>;
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_chan = <3>;
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_addr = <0xCA2>;
+ÂÂÂÂÂÂÂÂÂÂÂ status = "okay";
+ÂÂÂÂÂÂÂ };
ÂÂÂÂÂ };
 Â lpc_host: lpc-host@80 {
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
index 3544abc..3a9d29b 100644
--- a/drivers/char/ipmi/Kconfig
+++ b/drivers/char/ipmi/Kconfig
@@ -104,3 +104,12 @@ config ASPEED_BT_IPMI_BMC
ÂÂÂÂÂÂÂ Provides a driver for the BT (Block Transfer) IPMI interface
ÂÂÂÂÂÂÂ found on Aspeed SOCs (AST2400 and AST2500). The driver
ÂÂÂÂÂÂÂ implements the BMC side of the BT interface.
+
+config ASPEED_KCS_IPMI_BMC
+ÂÂÂ depends on ARCH_ASPEED || COMPILE_TEST
+ÂÂÂÂÂÂÂ depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
+ÂÂÂ tristate "KCS IPMI bmc driver"
+ÂÂÂ help
+ÂÂÂÂÂ Provides a driver for the KCS (Keyboard Controller Style) IPMI
+ÂÂÂÂÂ interface found on Aspeed SOCs (AST2400 and AST2500). The driver
+ÂÂÂÂÂ implements the BMC side of the KCS interface.
\ No newline at end of file
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
index 33b899f..f217bae 100644
--- a/drivers/char/ipmi/Makefile
+++ b/drivers/char/ipmi/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
 obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
 obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
 obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += bt-bmc.o
+obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs-bmc.o
\ No newline at end of file
diff --git a/drivers/char/ipmi/kcs-bmc.c b/drivers/char/ipmi/kcs-bmc.c
new file mode 100644
index 0000000..256fb00
--- /dev/null
+++ b/drivers/char/ipmi/kcs-bmc.c
@@ -0,0 +1,759 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2015-2017, Intel Corporation.
+
+#include <linux/atomic.h>
+#include <linux/slab.h>
+#include <linux/kcs-bmc.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.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/timer.h>
+
+#define KCS_MSG_BUFSIZÂÂÂÂÂ 1024
+#define KCS_CHANNEL_MAXÂÂÂÂ 4
+
+/*
+ * This is a BMC device used to communicate to the host
+ */
+#define DEVICE_NAMEÂÂÂÂ "ipmi-kcs-host"
+
+
+/* Different Phases of the KCS Module */
+#define KCS_PHASE_IDLEÂÂÂÂÂÂÂÂÂ 0x00
+#define KCS_PHASE_WRITEÂÂÂÂÂÂÂÂ 0x01
+#define KCS_PHASE_WRITE_ENDÂÂÂÂ 0x02
+#define KCS_PHASE_READÂÂÂÂÂÂÂÂÂ 0x03
+#define KCS_PHASE_ABORTÂÂÂÂÂÂÂÂ 0x04
+#define KCS_PHASE_ERRORÂÂÂÂÂÂÂÂ 0x05
+
+/* Abort Phase */
+#define ABORT_PHASE_ERROR1ÂÂÂÂÂ 0x01
+#define ABORT_PHASE_ERROR2ÂÂÂÂÂ 0x02
+
+/* KCS Command Control codes. */
+#define KCS_GET_STATUSÂÂÂÂÂÂÂÂÂ 0x60
+#define KCS_ABORTÂÂÂÂÂÂÂÂÂÂÂÂÂÂ 0x60
+#define KCS_WRITE_STARTÂÂÂÂÂÂÂÂ 0x61
+#define KCS_WRITE_ENDÂÂÂÂÂÂÂÂÂÂ 0x62
+#define KCS_READ_BYTEÂÂÂÂÂÂÂÂÂÂ 0x68
+
+/* Status bits.:
+ * - IDLE_STATE. Interface is idle. System software should not be expecting
+ *ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ nor sending any data.
+ * - READ_STATE. BMC is transferring a packet to system software. System
+ *ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ software should be in the "Read Message" state.
+ * - WRITE_STATE. BMC is receiving a packet from system software. System
+ *ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ software should be writing a command to the BMC.
+ * - ERROR_STATE. BMC has detected a protocol violation at the interface level,
+ *ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ or the transfer has been aborted. System software can either
+ *ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ use the "Get_Status" control code to request the nature of
+ *ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ the error, or it can just retry the command.
+ */
+#define KCS_IDLE_STATEÂÂÂÂÂÂÂÂÂÂ 0
+#define KCS_READ_STATEÂÂÂÂÂÂÂÂÂÂ 1
+#define KCS_WRITE_STATEÂÂÂÂÂÂÂÂÂ 2
+#define KCS_ERROR_STATEÂÂÂÂÂÂÂÂÂ 3
+
+/* KCS Error Codes */
+#define KCS_NO_ERRORÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ 0x00
+#define KCS_ABORTED_BY_COMMANDÂÂÂÂÂ 0x01
+#define KCS_ILLEGAL_CONTROL_CODEÂÂÂ 0x02
+#define KCS_LENGTH_ERRORÂÂÂÂÂÂÂÂÂÂÂ 0x06
+#define KCS_UNSPECIFIED_ERRORÂÂÂÂÂÂ 0xFF
+
+
+#define KCS_ZERO_DATAÂÂÂÂÂÂÂÂÂÂ 0
+
+/* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
+#define KCS_STR_STATE(state)ÂÂÂÂÂÂÂ (state << 6)
+#define KCS_STR_STATE_MASKÂÂÂÂÂÂÂÂÂ KCS_STR_STATE(0x3)
+#define KCS_STR_CMD_DATÂÂÂÂÂÂÂÂÂÂÂÂ BIT(3)
+#define KCS_STR_ATNÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ BIT(2)
+#define KCS_STR_IBFÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ BIT(1)
+#define KCS_STR_OBFÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ BIT(0)
+
+
+/***************************** LPC Register ****************************/
+/* mapped to lpc-bmc@0 IO space */
+#define LPC_HICR0ÂÂÂÂÂÂÂÂÂÂÂ 0x000
+#defineÂÂÂÂ LPC_HICR0_LPC3EÂÂÂÂÂÂÂÂÂ BIT(7)
+#defineÂÂÂÂ LPC_HICR0_LPC2EÂÂÂÂÂÂÂÂÂ BIT(6)
+#defineÂÂÂÂ LPC_HICR0_LPC1EÂÂÂÂÂÂÂÂÂ BIT(5)
+#define LPC_HICR2ÂÂÂÂÂÂÂÂÂÂÂ 0x008
+#defineÂÂÂÂ LPC_HICR2_IBFIF3ÂÂÂÂÂÂÂÂ BIT(3)
+#defineÂÂÂÂ LPC_HICR2_IBFIF2ÂÂÂÂÂÂÂÂ BIT(2)
+#defineÂÂÂÂ LPC_HICR2_IBFIF1ÂÂÂÂÂÂÂÂ BIT(1)
+#define LPC_HICR4ÂÂÂÂÂÂÂÂÂÂÂ 0x010
+#defineÂÂÂÂ LPC_HICR4_LADR12ASÂÂÂÂÂÂ BIT(7)
+#defineÂÂÂÂ LPC_HICR4_KCSENBLÂÂÂÂÂÂÂ BIT(2)
+#define LPC_LADR3HÂÂÂÂÂÂÂÂÂÂ 0x014
+#define LPC_LADR3LÂÂÂÂÂÂÂÂÂÂ 0x018
+#define LPC_LADR12HÂÂÂÂÂÂÂÂÂ 0x01C
+#define LPC_LADR12LÂÂÂÂÂÂÂÂÂ 0x020
+#define LPC_IDR1ÂÂÂÂÂÂÂÂÂÂÂÂ 0x024
+#define LPC_IDR2ÂÂÂÂÂÂÂÂÂÂÂÂ 0x028
+#define LPC_IDR3ÂÂÂÂÂÂÂÂÂÂÂÂ 0x02C
+#define LPC_ODR1ÂÂÂÂÂÂÂÂÂÂÂÂ 0x030
+#define LPC_ODR2ÂÂÂÂÂÂÂÂÂÂÂÂ 0x034
+#define LPC_ODR3ÂÂÂÂÂÂÂÂÂÂÂÂ 0x038
+#define LPC_STR1ÂÂÂÂÂÂÂÂÂÂÂÂ 0x03C
+#define LPC_STR2ÂÂÂÂÂÂÂÂÂÂÂÂ 0x040
+#define LPC_STR3ÂÂÂÂÂÂÂÂÂÂÂÂ 0x044
+
+/* mapped to lpc-host@80 IO space */
+#define LPC_HICRBÂÂÂÂÂÂÂÂÂÂÂ 0x080
+#defineÂÂÂÂ LPC_HICRB_IBFIF4ÂÂÂÂÂÂÂÂ BIT(1)
+#defineÂÂÂÂ LPC_HICRB_LPC4EÂÂÂÂÂÂÂÂÂ BIT(0)
+#define LPC_LADR4ÂÂÂÂÂÂÂÂÂÂÂ 0x090
+#define LPC_IDR4ÂÂÂÂÂÂÂÂÂÂÂÂ 0x094
+#define LPC_ODR4ÂÂÂÂÂÂÂÂÂÂÂÂ 0x098
+#define LPC_STR4ÂÂÂÂÂÂÂÂÂÂÂÂ 0x09C
+
+
+/* IPMI 2.0 - 9.5, KCS Interface Registers */
+struct kcs_ioreg {
+ÂÂÂ u32 idr; /* Input Data Register */
+ÂÂÂ u32 odr; /* Output Data Register */
+ÂÂÂ u32 str; /* Status Register */
+};
+
+static const struct kcs_ioreg kcs_channel_ioregs[KCS_CHANNEL_MAX] = {
+ÂÂÂ { .idr = LPC_IDR1, .odr = LPC_ODR1, .str = LPC_STR1 },
+ÂÂÂ { .idr = LPC_IDR2, .odr = LPC_ODR2, .str = LPC_STR2 },
+ÂÂÂ { .idr = LPC_IDR3, .odr = LPC_ODR3, .str = LPC_STR3 },
+ÂÂÂ { .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 },
+};
+
+struct kcs_bmc {
+ÂÂÂ struct regmap *map;
+ÂÂÂ intÂÂÂÂÂÂÂÂÂÂÂ irq;
+ÂÂÂ spinlock_tÂÂÂÂ lock;
+
+ÂÂÂ u32 chan;
+ÂÂÂ int running;
+
+ÂÂÂ u32 idr;
+ÂÂÂ u32 odr;
+ÂÂÂ u32 str;
+
+ÂÂÂ int kcs_phase;
+ÂÂÂ u8Â abort_phase;
+ÂÂÂ u8Â kcs_error;
+
+ÂÂÂ wait_queue_head_t queue;
+ int data_in_avail;
+ int data_in_idx;
+ÂÂÂ u8Â *data_in;
+
+ int data_out_idx;
+ int data_out_len;
+ÂÂÂ u8Â *data_out;
+
+ÂÂÂ struct miscdevice miscdev;
+ÂÂÂ char name[16];
+};
+
+static u8 kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
+{
+ÂÂÂ u32 val = 0;
+ÂÂÂ int rc;
+
+ÂÂÂ rc = regmap_read(kcs_bmc->map, reg, &val);
+ÂÂÂ WARN(rc != 0, "regmap_read failed: %d\n", rc);
+
+ÂÂÂ return rc == 0 ? (u8) val : 0;
+}
+
+static void kcs_outb(struct kcs_bmc *kcs_bmc, u8 data, u32 reg)
+{
+ÂÂÂ int rc;
+
+ÂÂÂ rc = regmap_write(kcs_bmc->map, reg, data);
+ÂÂÂ WARN(rc != 0, "regmap_write failed: %d\n", rc);
+}
+
+static void kcs_set_state(struct kcs_bmc *kcs_bmc, u8 state)
+{
+ÂÂÂ int rc;
+
+ÂÂÂ rc = regmap_update_bits(kcs_bmc->map, kcs_bmc->str,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ KCS_STR_STATE_MASK,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ KCS_STR_STATE(state));
+ÂÂÂ WARN(rc != 0, "KCS_STR_STATE failed: %d\n", rc);
+}
+
+static void kcs_set_atn(struct kcs_bmc *kcs_bmc, unsigned long set)
+{
+ÂÂÂ int rc;
+
+ÂÂÂ rc = regmap_update_bits(kcs_bmc->map, kcs_bmc->str,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ KCS_STR_ATN,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ set != 0 ? KCS_STR_ATN : 0);
+ÂÂÂ WARN(rc != 0, "KCS_STR_ATN failed: %d\n", rc);
+}
+
+/*********************************************************************
+ * AST_usrGuide_KCS.pdf
+ * 2. Background:
+ *ÂÂ we note D for Data, and C for Cmd/Status, default rules are
+ *ÂÂÂÂ A. KCS1 / KCS2 ( D / C:X / X+4 )
+ *ÂÂÂÂÂÂÂ D / C : CA0h / CA4h
+ *ÂÂÂÂÂÂÂ D / C : CA8h / CACh
+ *ÂÂÂÂ B. KCS3 ( D / C:XX2h / XX3h )
+ *ÂÂÂÂÂÂÂ D / C : CA2h / CA3h
+ *ÂÂÂÂÂÂÂ D / C : CB2h / CB3h
+ *ÂÂÂÂ C. KCS4
+ *ÂÂÂÂÂÂÂ D / C : CA4h / CA5h
+ *********************************************************************/
+void kcs_set_addr(struct kcs_bmc *kcs_bmc, u16 addr)
+{
+ÂÂÂ switch (kcs_bmc->chan) {
+ÂÂÂ case 1:
+ÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR4,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR4_LADR12AS,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ 0);
+ÂÂÂÂÂÂÂ regmap_write(kcs_bmc->map, LPC_LADR12H, addr >> 8);
+ÂÂÂÂÂÂÂ regmap_write(kcs_bmc->map, LPC_LADR12L, addr & 0xFF);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case 2:
+ÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR4,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR4_LADR12AS,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR4_LADR12AS);
+ÂÂÂÂÂÂÂ regmap_write(kcs_bmc->map, LPC_LADR12H, addr >> 8);
+ÂÂÂÂÂÂÂ regmap_write(kcs_bmc->map, LPC_LADR12L, addr & 0xFF);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case 3:
+ÂÂÂÂÂÂÂ regmap_write(kcs_bmc->map, LPC_LADR3H, addr >> 8);
+ÂÂÂÂÂÂÂ regmap_write(kcs_bmc->map, LPC_LADR3L, addr & 0xFF);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case 4:
+ÂÂÂÂÂÂÂ regmap_write(kcs_bmc->map, LPC_LADR4,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ ((addr + 1) << 16) | (addr));
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ default:
+ÂÂÂÂÂÂÂ break;
+ÂÂÂ }
+}
+
+static void kcs_enable_channel(struct kcs_bmc *kcs_bmc, int enable)
+{
+ÂÂÂ switch (kcs_bmc->chan) {
+ÂÂÂ case 1:
+ÂÂÂÂÂÂÂ if (enable) {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR2,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR2_IBFIF1, LPC_HICR2_IBFIF1);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR0,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR0_LPC1E, LPC_HICR0_LPC1E);
+ÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR0,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR0_LPC1E, 0);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR2,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR2_IBFIF1, 0);
+ÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case 2:
+ÂÂÂÂÂÂÂ if (enable) {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR2,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR2_IBFIF2, LPC_HICR2_IBFIF2);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR0,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR0_LPC2E, LPC_HICR0_LPC2E);
+ÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR0,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR0_LPC2E, 0);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR2,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR2_IBFIF2, 0);
+ÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case 3:
+ÂÂÂÂÂÂÂ if (enable) {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR2,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR2_IBFIF3, LPC_HICR2_IBFIF3);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR0,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR0_LPC3E, LPC_HICR0_LPC3E);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR4,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR4_KCSENBL, LPC_HICR4_KCSENBL);
+ÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR0,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR0_LPC3E, 0);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR4,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR4_KCSENBL, 0);
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICR2,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICR2_IBFIF3, 0);
+ÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case 4:
+ÂÂÂÂÂÂÂ if (enable) {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICRB,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E);
+ÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂ regmap_update_bits(kcs_bmc->map, LPC_HICRB,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ 0);
+ÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ default:
+ÂÂÂÂÂÂÂ break;
+ÂÂÂ }
+}
+
+static void kcs_rx_data(struct kcs_bmc *kcs_bmc)
+{
+ÂÂÂ u8 data;
+
+ÂÂÂ switch (kcs_bmc->kcs_phase) {
+ÂÂÂ case KCS_PHASE_WRITE:
+ÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_WRITE_STATE);
+
+ÂÂÂÂÂÂÂ /* set OBF before reading data */
+ÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+
+ÂÂÂÂÂÂÂ if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ)
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ kcs_inb(kcs_bmc, kcs_bmc->idr);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_PHASE_WRITE_END:
+ÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_READ_STATE);
+
+ÂÂÂÂÂÂÂ if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ)
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ kcs_inb(kcs_bmc, kcs_bmc->idr);
+
+ÂÂÂÂÂÂÂ kcs_bmc->kcs_phase = KCS_PHASE_READ;
+ÂÂÂÂÂÂÂ if (kcs_bmc->running) {
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->data_in_avail = 1;
+ÂÂÂÂÂÂÂÂÂÂÂ wake_up_interruptible(&kcs_bmc->queue);
+ÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_PHASE_READ:
+ÂÂÂÂÂÂÂ if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_IDLE_STATE);
+
+ÂÂÂÂÂÂÂ data = kcs_inb(kcs_bmc, kcs_bmc->idr);
+ÂÂÂÂÂÂÂ if (data != KCS_READ_BYTE) {
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_ERROR_STATE);
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+ÂÂÂÂÂÂÂÂÂÂÂ break;
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->kcs_phase = KCS_PHASE_IDLE;
+ÂÂÂÂÂÂÂÂÂÂÂ break;
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, kcs_bmc->data_out[kcs_bmc->data_out_idx++],
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->odr);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_PHASE_ABORT:
+ÂÂÂÂÂÂÂ switch (kcs_bmc->abort_phase) {
+ÂÂÂÂÂÂÂ case ABORT_PHASE_ERROR1:
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_READ_STATE);
+
+ÂÂÂÂÂÂÂÂÂÂÂ /* Read the Dummy byte */
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_inb(kcs_bmc, kcs_bmc->idr);
+
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, kcs_bmc->kcs_error, kcs_bmc->odr);
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->abort_phase = ABORT_PHASE_ERROR2;
+ÂÂÂÂÂÂÂÂÂÂÂ break;
+
+ÂÂÂÂÂÂÂ case ABORT_PHASE_ERROR2:
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_IDLE_STATE);
+
+ÂÂÂÂÂÂÂÂÂÂÂ /* Read the Dummy byte */
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_inb(kcs_bmc, kcs_bmc->idr);
+
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->kcs_phase = KCS_PHASE_IDLE;
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->abort_phase = 0;
+ÂÂÂÂÂÂÂÂÂÂÂ break;
+
+ÂÂÂÂÂÂÂ default:
+ÂÂÂÂÂÂÂÂÂÂÂ break;
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_PHASE_ERROR:
+ÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_ERROR_STATE);
+
+ÂÂÂÂÂÂÂ /* Read the Dummy byte */
+ÂÂÂÂÂÂÂ kcs_inb(kcs_bmc, kcs_bmc->idr);
+
+ÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ default:
+ÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_ERROR_STATE);
+
+ÂÂÂÂÂÂÂ /* Read the Dummy byte */
+ÂÂÂÂÂÂÂ kcs_inb(kcs_bmc, kcs_bmc->idr);
+
+ÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+ÂÂÂÂÂÂÂ break;
+ÂÂÂ }
+}
+
+static void kcs_rx_cmd(struct kcs_bmc *kcs_bmc)
+{
+ÂÂÂ u8 cmd;
+
+ÂÂÂ kcs_set_state(kcs_bmc, KCS_WRITE_STATE);
+
+ÂÂÂ /* Dummy data to generate OBF */
+ÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+
+ÂÂÂ cmd = kcs_inb(kcs_bmc, kcs_bmc->idr);
+ÂÂÂ switch (cmd) {
+ÂÂÂ case KCS_WRITE_START:
+ÂÂÂÂÂÂÂ kcs_bmc->data_in_avail = 0;
+ÂÂÂÂÂÂÂ kcs_bmc->data_in_idxÂÂ = 0;
+ÂÂÂÂÂÂÂ kcs_bmc->kcs_phaseÂÂÂÂ = KCS_PHASE_WRITE;
+ÂÂÂÂÂÂÂ kcs_bmc->kcs_errorÂÂÂÂ = KCS_NO_ERROR;
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_WRITE_END:
+ÂÂÂÂÂÂÂ kcs_bmc->kcs_phase = KCS_PHASE_WRITE_END;
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_ABORT:
+ÂÂÂÂÂÂÂ if (kcs_bmc->kcs_error == KCS_NO_ERROR)
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->kcs_error = KCS_ABORTED_BY_COMMAND;
+
+ÂÂÂÂÂÂÂ kcs_bmc->kcs_phaseÂÂ = KCS_PHASE_ABORT;
+ÂÂÂÂÂÂÂ kcs_bmc->abort_phase = ABORT_PHASE_ERROR1;
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ default:
+ÂÂÂÂÂÂÂ kcs_bmc->kcs_error = KCS_ILLEGAL_CONTROL_CODE;
+ÂÂÂÂÂÂÂ kcs_set_state(kcs_bmc, KCS_ERROR_STATE);
+ÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, kcs_bmc->kcs_error, kcs_bmc->odr);
+ÂÂÂÂÂÂÂ kcs_bmc->kcs_phase = KCS_PHASE_ERROR;
+ÂÂÂÂÂÂÂ break;
+ÂÂÂ }
+}
+
+/*
+ * Whenever the BMC is reset (from power-on or a hard reset), the State Bits
+ * are initialized to "11 - Error State". Doing so allows SMS to detect that
+ * the BMC has been reset and that any message in process has been terminated
+ * by the BMC.
+ */
+static void kcs_force_abort(struct kcs_bmc *kcs_bmc)
+{
+ÂÂÂ unsigned long flags;
+
+ÂÂÂ spin_lock_irqsave(&kcs_bmc->lock, flags);
+ÂÂÂ kcs_set_state(kcs_bmc, KCS_ERROR_STATE);
+
+ÂÂÂ /* Read the Dummy byte */
+ÂÂÂ kcs_inb(kcs_bmc, kcs_bmc->idr);
+
+ÂÂÂ kcs_outb(kcs_bmc, KCS_ZERO_DATA, kcs_bmc->odr);
+ÂÂÂ kcs_bmc->kcs_phase = KCS_PHASE_ERROR;
+ÂÂÂ spin_unlock_irqrestore(&kcs_bmc->lock, flags);
+}
+
+static irqreturn_t kcs_bmc_irq(int irq, void *arg)
+{
+ÂÂÂ int rc;
+ÂÂÂ u32 sts;
+ÂÂÂ struct kcs_bmc *kcs_bmc = arg;
+
+ÂÂÂ rc = regmap_read(kcs_bmc->map, kcs_bmc->str, &sts);
+ÂÂÂ if (rc)
+ÂÂÂÂÂÂÂ return IRQ_NONE;
+
+ÂÂÂ sts &= (KCS_STR_IBF | KCS_STR_CMD_DAT);
+
+ÂÂÂ switch (sts) {
+ÂÂÂ case KCS_STR_IBF | KCS_STR_CMD_DAT:
+ÂÂÂÂÂÂÂ kcs_rx_cmd(kcs_bmc);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_STR_IBF:
+ÂÂÂÂÂÂÂ kcs_rx_data(kcs_bmc);
+
+ÂÂÂ default:
+ÂÂÂÂÂÂÂ return IRQ_NONE;
+ÂÂÂ }
+
+ÂÂÂ return IRQ_HANDLED;
+}
+
+static int kcs_bmc_config_irq(struct kcs_bmc *kcs_bmc,
+ÂÂÂÂÂÂÂÂÂÂÂ struct platform_device *pdev)
+{
+ÂÂÂ struct device *dev = &pdev->dev;
+ÂÂÂ int rc;
+
+ÂÂÂ kcs_bmc->irq = platform_get_irq(pdev, 0);
+ÂÂÂ if (!kcs_bmc->irq)
+ÂÂÂÂÂÂÂ return -ENODEV;
+
+ÂÂÂ rc = devm_request_irq(dev, kcs_bmc->irq, kcs_bmc_irq, IRQF_SHARED,
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->name, kcs_bmc);
+ÂÂÂ if (rc < 0) {
+ÂÂÂÂÂÂÂ dev_warn(dev, "Unable to request IRQ %d\n", kcs_bmc->irq);
+ÂÂÂÂÂÂÂ kcs_bmc->irq = 0;
+ÂÂÂÂÂÂÂ return rc;
+ÂÂÂ }
+
+ÂÂÂ return rc;
+}
+
+
+static inline struct kcs_bmc *file_kcs_bmc(struct file *filp)
+{
+ÂÂÂ return container_of(filp->private_data, struct kcs_bmc, miscdev);
+}
+
+static int kcs_bmc_open(struct inode *inode, struct file *filp)
+{
+ÂÂÂ unsigned long flags;
+ÂÂÂ struct kcs_bmc *kcs_bmc = file_kcs_bmc(filp);
+
+ÂÂÂ if (kcs_bmc->running)
+ÂÂÂÂÂÂÂ return -EBUSY;
+
+ÂÂÂ spin_lock_irqsave(&kcs_bmc->lock, flags);
+ÂÂÂ kcs_bmc->kcs_phaseÂÂÂÂ = KCS_PHASE_IDLE;
+ÂÂÂ kcs_bmc->runningÂÂÂÂÂÂ = 1;
+ÂÂÂ kcs_bmc->data_in_avail = 0;
+ÂÂÂ spin_unlock_irqrestore(&kcs_bmc->lock, flags);
+
+ÂÂÂ return 0;
+}
+
+static unsigned int kcs_bmc_poll(struct file *filp, poll_table *wait)
+{
+ÂÂÂ unsigned int mask = 0;
+ÂÂÂ struct kcs_bmc *kcs_bmc = file_kcs_bmc(filp);
+
+ÂÂÂ poll_wait(filp, &kcs_bmc->queue, wait);
+
+ÂÂÂ if (kcs_bmc->data_in_avail)
+ÂÂÂÂÂÂÂ mask |= POLLIN;
+
+ÂÂÂ if (kcs_bmc->kcs_phase == KCS_PHASE_READ)
+ÂÂÂÂÂÂÂ mask |= POLLOUT;
+
+ÂÂÂ return mask;
+}
+
+static ssize_t kcs_bmc_read(struct file *filp, char *buf,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ size_t count, loff_t *offset)
+{
+ÂÂÂ int rv;
+ÂÂÂ struct kcs_bmc *kcs_bmc = file_kcs_bmc(filp);
+
+ÂÂÂ rv = wait_event_interruptible(kcs_bmc->queue,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->data_in_avail != 0);
+ÂÂÂ if (rv < 0)
+ÂÂÂÂÂÂÂ return -ERESTARTSYS;
+
+ÂÂÂ kcs_bmc->data_in_avail = 0;
+
+ÂÂÂ if (count > kcs_bmc->data_in_idx)
+ÂÂÂÂÂÂÂ count = kcs_bmc->data_in_idx;
+
+ÂÂÂ if (copy_to_user(buf, kcs_bmc->data_in, count))
+ÂÂÂÂÂÂÂ return -EFAULT;
+
+ÂÂÂ return count;
+}
+
+static ssize_t kcs_bmc_write(struct file *filp, const char *buf,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ size_t count, loff_t *offset)
+{
+ÂÂÂ unsigned long flags;
+ÂÂÂ struct kcs_bmc *kcs_bmc = file_kcs_bmc(filp);
+
+ÂÂÂ if (count < 1 || count > KCS_MSG_BUFSIZ)
+ÂÂÂÂÂÂÂ return -EINVAL;
+
+ÂÂÂ if (copy_from_user(kcs_bmc->data_out, buf, count))
+ÂÂÂÂÂÂÂ return -EFAULT;
+
+ÂÂÂ spin_lock_irqsave(&kcs_bmc->lock, flags);
+ÂÂÂ if (kcs_bmc->kcs_phase == KCS_PHASE_READ) {
+ÂÂÂÂÂÂÂ kcs_bmc->data_out_idx = 1;
+ÂÂÂÂÂÂÂ kcs_bmc->data_out_len = count;
+ÂÂÂÂÂÂÂ kcs_outb(kcs_bmc, kcs_bmc->data_out[0], kcs_bmc->odr);
+ÂÂÂ }
+ÂÂÂ spin_unlock_irqrestore(&kcs_bmc->lock, flags);
+
+ÂÂÂ return count;
+}
+
+static long kcs_bmc_ioctl(struct file *filp, unsigned int cmd,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long arg)
+{
+ÂÂÂ long ret = 0;
+ÂÂÂ struct kcs_bmc *kcs_bmc = file_kcs_bmc(filp);
+
+ÂÂÂ switch (cmd) {
+ÂÂÂ case KCS_BMC_IOCTL_SMS_ATN:
+ÂÂÂÂÂÂÂ kcs_set_atn(kcs_bmc, arg);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ case KCS_BMC_IOCTL_FORCE_ABORT:
+ÂÂÂÂÂÂÂ kcs_force_abort(kcs_bmc);
+ÂÂÂÂÂÂÂ break;
+
+ÂÂÂ default:
+ÂÂÂÂÂÂÂ ret = -EINVAL;
+ÂÂÂÂÂÂÂ break;
+ÂÂÂ }
+
+ÂÂÂ return ret;
+}
+
+static int kcs_bmc_release(struct inode *inode, struct file *filp)
+{
+ÂÂÂ unsigned long flags;
+ÂÂÂ struct kcs_bmc *kcs_bmc = file_kcs_bmc(filp);
+
+ÂÂÂ spin_lock_irqsave(&kcs_bmc->lock, flags);
+ÂÂÂ kcs_bmc->running = 0;
+ÂÂÂ spin_unlock_irqrestore(&kcs_bmc->lock, flags);
+
+ÂÂÂ return 0;
+}
+
+static const struct file_operations kcs_bmc_fops = {
+ÂÂÂ .ownerÂÂÂÂÂÂÂÂÂ = THIS_MODULE,
+ÂÂÂ .openÂÂÂÂÂÂÂÂÂÂ = kcs_bmc_open,
+ÂÂÂ .readÂÂÂÂÂÂÂÂÂÂ = kcs_bmc_read,
+ÂÂÂ .writeÂÂÂÂÂÂÂÂÂ = kcs_bmc_write,
+ÂÂÂ .releaseÂÂÂÂÂÂÂ = kcs_bmc_release,
+ÂÂÂ .pollÂÂÂÂÂÂÂÂÂÂ = kcs_bmc_poll,
+ÂÂÂ .unlocked_ioctl = kcs_bmc_ioctl,
+};
+
+static int kcs_bmc_probe(struct platform_device *pdev)
+{
+ÂÂÂ struct kcs_bmc *kcs_bmc;
+ÂÂÂ struct device *dev;
+ÂÂÂ const struct kcs_ioreg *ioreg;
+ÂÂÂ u32 chan, addr;
+ÂÂÂ int rc;
+
+ÂÂÂ dev = &pdev->dev;
+
+ÂÂÂ kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc), GFP_KERNEL);
+ÂÂÂ if (!kcs_bmc)
+ÂÂÂÂÂÂÂ return -ENOMEM;
+
+ÂÂÂ rc = of_property_read_u32(dev->of_node, "kcs_chan", &chan);
+ÂÂÂ if ((rc != 0) || (chan == 0 || chan > KCS_CHANNEL_MAX)) {
+ÂÂÂÂÂÂÂ dev_err(dev, "no valid 'kcs_chan' configured\n");
+ÂÂÂÂÂÂÂ return -ENODEV;
+ÂÂÂ }
+
+ÂÂÂ rc = of_property_read_u32(dev->of_node, "kcs_addr", &addr);
+ÂÂÂ if (rc) {
+ÂÂÂÂÂÂÂ dev_err(dev, "no valid 'kcs_addr' configured\n");
+ÂÂÂÂÂÂÂ return -ENODEV;
+ÂÂÂ }
+
+ÂÂÂ kcs_bmc->map = syscon_node_to_regmap(dev->parent->of_node);
+ÂÂÂ if (IS_ERR(kcs_bmc->map)) {
+ÂÂÂÂÂÂÂ dev_err(dev, "Couldn't get regmap\n");
+ÂÂÂÂÂÂÂ return -ENODEV;
+ÂÂÂ }
+
+ÂÂÂ spin_lock_init(&kcs_bmc->lock);
+ÂÂÂ kcs_bmc->chan = chan;
+
+ÂÂÂ ioreg = &kcs_channel_ioregs[chan - 1];
+ kcs_bmc->idr = ioreg->idr;
+ kcs_bmc->odr = ioreg->odr;
+ kcs_bmc->str = ioreg->str;
+
+ÂÂÂ init_waitqueue_head(&kcs_bmc->queue);
+ kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+ÂÂÂ kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+ÂÂÂ if (kcs_bmc->data_in == NULL || kcs_bmc->data_out == NULL) {
+ÂÂÂÂÂÂÂ dev_err(dev, "Failed to allocate data buffers\n");
+ÂÂÂÂÂÂÂ return -ENOMEM;
+ÂÂÂ }
+
+ÂÂÂ snprintf(kcs_bmc->name, sizeof(kcs_bmc->name), "ipmi-kcs%u", chan);
+ÂÂÂ kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
+ÂÂÂ kcs_bmc->miscdev.name = kcs_bmc->name;
+ÂÂÂ kcs_bmc->miscdev.fops = &kcs_bmc_fops;
+ÂÂÂ rc = misc_register(&kcs_bmc->miscdev);
+ÂÂÂ if (rc) {
+ÂÂÂÂÂÂÂ dev_err(dev, "Unable to register device\n");
+ÂÂÂÂÂÂÂ return rc;
+ÂÂÂ }
+
+ÂÂÂ kcs_set_addr(kcs_bmc, addr);
+ÂÂÂ kcs_enable_channel(kcs_bmc, 1);
+
+ÂÂÂ rc = kcs_bmc_config_irq(kcs_bmc, pdev);
+ÂÂÂ if (rc) {
+ÂÂÂÂÂÂÂ dev_err(dev, "Failed to configure IRQ\n");
+ÂÂÂÂÂÂÂ misc_deregister(&kcs_bmc->miscdev);
+ÂÂÂÂÂÂÂ return rc;
+ÂÂÂ }
+
+ÂÂÂ dev_set_drvdata(&pdev->dev, kcs_bmc);
+
+ÂÂÂ dev_info(dev, "addr=0x%x, idr=0x%x, odr=0x%x, str=0x%x\n",
+ÂÂÂÂÂÂÂÂÂÂÂ addr,
+ÂÂÂÂÂÂÂÂÂÂÂ kcs_bmc->idr, kcs_bmc->odr, kcs_bmc->str);
+
+ÂÂÂ return 0;
+}
+
+static int kcs_bmc_remove(struct platform_device *pdev)
+{
+ÂÂÂ struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
+
+ÂÂÂ misc_deregister(&kcs_bmc->miscdev);
+
+ÂÂÂ return 0;
+}
+
+static const struct of_device_id kcs_bmc_match[] = {
+ÂÂÂ { .compatible = "aspeed,ast2400-kcs-bmc" },
+ÂÂÂ { .compatible = "aspeed,ast2500-kcs-bmc" },
+ÂÂÂ { },
+};
+
+static struct platform_driver kcs_bmc_driver = {
+ÂÂÂ .driver = {
+ÂÂÂÂÂÂÂ .nameÂÂÂÂÂÂÂÂÂÂ = DEVICE_NAME,
+ÂÂÂÂÂÂÂ .of_match_table = kcs_bmc_match,
+ÂÂÂ },
+ÂÂÂ .probe = kcs_bmc_probe,
+ÂÂÂ .remove = kcs_bmc_remove,
+};
+
+module_platform_driver(kcs_bmc_driver);
+
+MODULE_DEVICE_TABLE(of, kcs_bmc_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Haiyue Wang <haiyue.wang@xxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Linux device interface to the IPMI KCS interface");
diff --git a/include/uapi/linux/kcs-bmc.h b/include/uapi/linux/kcs-bmc.h
new file mode 100644
index 0000000..f0c3ff60
--- /dev/null
+++ b/include/uapi/linux/kcs-bmc.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2015-2017, Intel Corporation.
+
+#ifndef _UAPI_LINUX_KCS_BMC_H
+#define _UAPI_LINUX_KCS_BMC_H
+
+#include <linux/ioctl.h>
+
+#define __KCS_BMC_IOCTL_MAGICÂÂÂÂÂ 'K'
+#define KCS_BMC_IOCTL_SMS_ATN _IOW(__KCS_BMC_IOCTL_MAGIC, 1, unsigned long)
+#define KCS_BMC_IOCTL_FORCE_ABORTÂ _IO(__KCS_BMC_IOCTL_MAGIC, 2)
+
+#endif /* _UAPI_LINUX_KCS_BMC_H */