| From 6681dd7150f3651b5a40e2852a19b2828f412acc Mon Sep 17 00:00:00 2001 |
| From: Stanley Chu <yschu@nuvoton.com> |
| Date: Mon, 29 Apr 2024 14:46:55 +0800 |
| Subject: [PATCH] soc: nuvoton: Add mmbi driver (#401) |
| |
| Support mmbi over espi transport between BMC and Host. |
| |
| refs: |
| https://github.com/Nuvoton-Israel/linux/commit/0957afc87b4acaf8f2b8f36dd8256b196492f877 |
| |
| Signed-off-by: Stanley Chu <yschu@nuvoton.com> |
| --- |
| drivers/soc/nuvoton/Kconfig | 8 + |
| drivers/soc/nuvoton/Makefile | 1 + |
| drivers/soc/nuvoton/npcm-espi-mmbi.c | 1327 ++++++++++++++++++++++++++ |
| include/dt-bindings/mmbi/protocols.h | 11 + |
| 4 files changed, 1347 insertions(+) |
| create mode 100644 drivers/soc/nuvoton/npcm-espi-mmbi.c |
| create mode 100644 include/dt-bindings/mmbi/protocols.h |
| |
| diff --git a/drivers/soc/nuvoton/Kconfig b/drivers/soc/nuvoton/Kconfig |
| index 20f144d9aaa6..2e882378f819 100644 |
| --- a/drivers/soc/nuvoton/Kconfig |
| +++ b/drivers/soc/nuvoton/Kconfig |
| @@ -24,6 +24,14 @@ config NPCM_ESPI_VWGPIO |
| to handle the master-to-slave gpio event. |
| Select Y here if you want to use eSPI virtual wire gpio. |
| |
| +config NPCM_ESPI_MMBI |
| + tristate "NPCM MMBI Driver" |
| + depends on ARCH_NPCM || COMPILE_TEST |
| + help |
| + Provides a driver to implement the mmbi protocol that |
| + handle the data exchange between BMC and host over the |
| + eSPI interface. |
| + |
| endmenu |
| |
| endif |
| diff --git a/drivers/soc/nuvoton/Makefile b/drivers/soc/nuvoton/Makefile |
| index 483460e6f399..14c4cef19be8 100644 |
| --- a/drivers/soc/nuvoton/Makefile |
| +++ b/drivers/soc/nuvoton/Makefile |
| @@ -1,4 +1,5 @@ |
| # SPDX-License-Identifier: GPL-2.0-only |
| obj-$(CONFIG_NPCM_MBOX_CERBERUS) += npcm-cerberus.o |
| obj-$(CONFIG_NPCM_ESPI_VWGPIO) += npcm-espi-vwgpio.o |
| +obj-$(CONFIG_NPCM_ESPI_MMBI) += npcm-espi-mmbi.o |
| |
| diff --git a/drivers/soc/nuvoton/npcm-espi-mmbi.c b/drivers/soc/nuvoton/npcm-espi-mmbi.c |
| new file mode 100644 |
| index 000000000000..c3f322599a2d |
| --- /dev/null |
| +++ b/drivers/soc/nuvoton/npcm-espi-mmbi.c |
| @@ -0,0 +1,1327 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright (c) 2021 Intel Corporation. |
| + * Copyright (c) 2024 Nuvoton Technology corporation. |
| + */ |
| + |
| +#include <linux/bitfield.h> |
| +#include <linux/crc8.h> |
| +#include <linux/dma-mapping.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_address.h> |
| +#include <linux/of_device.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/poll.h> |
| +#include <linux/regmap.h> |
| +#include <dt-bindings/mmbi/protocols.h> |
| + |
| +#define DEVICE_NAME "mmbi" |
| +#define MAX_NO_OF_SUPPORTED_CHANNELS 1 |
| +#define MAX_NO_OF_SUPPORTED_PROTOCOLS 5 |
| + |
| +#define POLLING_INTERVAL_MS 50 |
| +#define POLLING_TIMEOUT_MS 1000 |
| +#define NPCM_ESPI_VWEVSM2 0x108 |
| +#define WIRE(x) BIT(x) |
| +#define HW_WIRE(x) (BIT(x) << 24) |
| +#define NPCM_MAX_WIN_SIZE 4096 |
| +#define SHM_SMC_STS 0x0 |
| +#define SHM_ACC BIT(6) |
| +#define SHM_SMC_CTL 0x1 |
| +#define SHM_ACC_IE BIT(5) |
| +#define SHM_WIN_SIZE 0x7 |
| +#define SHM_WIN_BASE1 0x20 |
| + |
| +/* 20 Bits for H2B/B2H Write/Read Pointers */ |
| +#define H2B_WRITE_POINTER_MASK GENMASK(19, 0) |
| +#define B2H_READ_POINTER_MASK GENMASK(19, 0) |
| +#define MMBI_HDR_LENGTH_MASK GENMASK(23, 0) |
| +#define MMBI_HDR_TYPE_MASK GENMASK(31, 24) |
| +#define HOST_RESET_REQUEST_BIT BIT(31) |
| +#define HOST_READY_BIT BIT(31) |
| +#define ESPI_SCI_STATUS_BIT BIT(16) |
| + |
| +#define GET_H2B_WRITE_POINTER(x) ((x) & H2B_WRITE_POINTER_MASK) |
| +#define GET_B2H_READ_POINTER(x) ((x) & B2H_READ_POINTER_MASK) |
| +#define GET_HOST_RESET_REQ_BIT(x) ((x) & HOST_RESET_REQUEST_BIT) |
| +#define GET_HOST_READY_BIT(x) ((x) & HOST_READY_BIT) |
| +#define HOST_READ_SCI_STATUS_BIT(x) ((x) & ESPI_SCI_STATUS_BIT) |
| + |
| +#define MMBI_CRC8_POLYNOMIAL 0x07 |
| +DECLARE_CRC8_TABLE(mmbi_crc8_table); |
| + |
| +typedef u8 protocol_type; |
| + |
| +struct host_rop { |
| + unsigned int |
| + b2h_wp : 20; /* Offset where BMC can write next data in B2H */ |
| + unsigned int reserved1 : 11; |
| + unsigned int b_rdy : 1; /* BMC ready bit */ |
| + unsigned int h2b_rp : 20; /* Offset till where bmc read data in H2B */ |
| + unsigned int reserved2 : 11; |
| + unsigned int b_rst : 1; /* BMC reset request bit */ |
| +}; |
| + |
| +struct host_rwp { |
| + unsigned int |
| + h2b_wp : 20; /* Offset where HOST can write next data in H2B */ |
| + unsigned int reserved1 : 11; |
| + unsigned int h_rdy : 1; /* Host ready bit */ |
| + unsigned int b2h_rp : 20; /* Offset till where host read data in B2H */ |
| + unsigned int reserved2 : 11; |
| + unsigned int h_rst : 1; /* host reset request bit */ |
| +}; |
| + |
| +struct buffer_type_desc { |
| + u32 host_rop_p; |
| + u32 host_rwp_p; |
| + u8 msg_protocol_type; |
| + u8 host_int_type; |
| + u16 global_sys_interrupt; |
| + u8 bmc_int_type; |
| + u32 bmc_int_a; |
| + u8 bmc_int_v; |
| +} __packed; |
| + |
| +struct mmbi_cap_desc { |
| + u8 signature[6]; |
| + u8 version; |
| + u8 instance_num; |
| + u32 nex_inst_base_addr; |
| + u32 b2h_ba; /* B2H buffer base offset */ |
| + u32 h2b_ba; /* H2B buffer base offset */ |
| + u16 b2h_d; /* Multiple of 16 Bytes (Max 1MB) */ |
| + u16 h2b_d; /* multiples of 16 bytes (Max 1MB) */ |
| + u8 buffer_type; /* Type of buffer in B2H/H2B */ |
| + u8 reserved1[7]; |
| + struct buffer_type_desc bt_desc; /* 18 bytes */ |
| + u8 reserved2[13]; |
| + u8 crc8; /* CRC-8-CCITT of the whole data structure (bytes 0 to 62) */ |
| +} __packed; |
| + |
| +struct mmbi_header { |
| + u32 data; |
| +}; |
| + |
| +struct npcm_mmbi_protocol { |
| + struct miscdevice miscdev; |
| + struct npcm_mmbi_channel *chan_ref; |
| + protocol_type type; |
| + |
| + bool data_available; |
| + /* |
| + * If user space application is opened for read, then only process |
| + * the data and copy to userspace. Otherwise, discard the command and |
| + * process the remaining commands (can be different protocol type) |
| + */ |
| + bool process_data; |
| + wait_queue_head_t queue; |
| +}; |
| + |
| +struct npcm_mmbi_channel { |
| + struct npcm_mmbi_protocol protocol[MAX_NO_OF_SUPPORTED_PROTOCOLS]; |
| + struct npcm_espi_mmbi *priv; |
| + |
| + u8 chan_num; |
| + u8 supported_protocols[MAX_NO_OF_SUPPORTED_PROTOCOLS]; |
| + u32 b2h_cb_size; |
| + u32 h2b_cb_size; |
| + u8 *desc_vmem; |
| + u8 *hrop_vmem; |
| + u8 *b2h_cb_vmem; |
| + u8 *hrwp_vmem; |
| + u8 *h2b_cb_vmem; |
| + u32 cur_h2b_wp; |
| + bool enabled; |
| +}; |
| + |
| +struct npcm_espi_mmbi { |
| + struct device *dev; |
| + void __iomem *regs; |
| + struct regmap *pmap; |
| + int irq; |
| + dma_addr_t mmbi_phys_addr; |
| + resource_size_t mmbi_size; |
| + u8 __iomem *shm_vaddr; |
| + struct delayed_work polling_work; |
| + struct workqueue_struct *wq; |
| + struct mutex lock; |
| + |
| + struct npcm_mmbi_channel chan[MAX_NO_OF_SUPPORTED_CHANNELS]; |
| + unsigned long poll_timeout; |
| + u32 poll_interval; |
| +}; |
| + |
| +struct npcm_mmbi_get_empty_space { |
| + u32 length; |
| +}; |
| + |
| +struct npcm_mmbi_get_config { |
| + bool h_rdy; |
| + u32 h2b_wp; |
| + u32 b2h_rp; |
| + u32 h2b_rp; |
| + u32 b2h_wp; |
| +}; |
| + |
| +#define __NPCM_MMBI_CTRL_IOCTL_MAGIC 0xBB |
| +/* |
| + * This IOCTL is meant to read empty space in B2H buffer |
| + * in a specific channel |
| + */ |
| +#define NPCM_MMBI_CTRL_IOCTL_GET_B2H_EMPTY_SPACE \ |
| + _IOWR(__NPCM_MMBI_CTRL_IOCTL_MAGIC, 0x00, \ |
| + struct npcm_mmbi_get_empty_space) |
| + |
| +/* This IOCTL to send BMC reset request */ |
| +#define NPCM_MMBI_CTRL_IOCTL_SEND_RESET_REQUEST \ |
| + _IOW(__NPCM_MMBI_CTRL_IOCTL_MAGIC, 0x01, int) |
| + |
| +/* This IOCTL is to Get Config in HROP and HRWP */ |
| +#define NPCM_MMBI_CTRL_IOCTL_GET_CONFIG \ |
| + _IOWR(__NPCM_MMBI_CTRL_IOCTL_MAGIC, 0x02, \ |
| + struct npcm_mmbi_get_config) |
| + |
| +static inline void npcm_mmbi_enable_interrupt(struct npcm_espi_mmbi *priv, |
| + bool enable) |
| +{ |
| + u8 val; |
| + |
| + val = readb(priv->regs + SHM_SMC_CTL); |
| + if (enable) |
| + val |= SHM_ACC_IE; |
| + else |
| + val &= ~SHM_ACC_IE; |
| + writeb(val, priv->regs + SHM_SMC_CTL); |
| +} |
| + |
| +static void raise_sci_interrupt(struct npcm_mmbi_channel *channel) |
| +{ |
| + struct regmap *espi_regmap = channel->priv->pmap; |
| + int retry; |
| + u32 val; |
| + |
| + dev_dbg(channel->priv->dev, "Raising SCI interrupt...\n"); |
| + regmap_set_bits(espi_regmap, NPCM_ESPI_VWEVSM2, WIRE(0)); |
| + regmap_clear_bits(espi_regmap, NPCM_ESPI_VWEVSM2, WIRE(0)); |
| + |
| + retry = 30; |
| + while (retry) { |
| + if (regmap_read(channel->priv->pmap, NPCM_ESPI_VWEVSM2, |
| + &val)) { |
| + dev_err(channel->priv->dev, "Unable to read VWEVSM2\n"); |
| + break; |
| + } |
| + |
| + if (HOST_READ_SCI_STATUS_BIT(val) == 0) |
| + break; |
| + |
| + retry--; |
| + udelay(1); |
| + } |
| + dev_dbg(channel->priv->dev, |
| + "Host SCI handler not invoked(VWEVSM2: 0x%0x), so retry(%d) after 1us...\n", |
| + val, retry); |
| + regmap_set_bits(espi_regmap, NPCM_ESPI_VWEVSM2, WIRE(0)); |
| +} |
| + |
| +static int read_host_rwp_val(struct npcm_mmbi_channel *channel, u32 offset, |
| + u32 *val) |
| +{ |
| + *val = readl(channel->hrwp_vmem + offset); |
| + |
| + return 0; |
| +} |
| + |
| +static int get_b2h_avail_buf_len(struct npcm_mmbi_channel *channel, |
| + ssize_t *avail_buf_len) |
| +{ |
| + struct host_rop hrop; |
| + u32 b2h_rp, h_rwp1; |
| + |
| + if (read_host_rwp_val(channel, 4, &h_rwp1)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return -EAGAIN; |
| + } |
| + |
| + b2h_rp = GET_B2H_READ_POINTER(h_rwp1); |
| + dev_dbg(channel->priv->dev, "MMBI HRWP - b2h_rp: 0x%0x\n", b2h_rp); |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + dev_dbg(channel->priv->dev, "HROP - b2h_wp: 0x%0x, h2b_rp: 0x%0x", |
| + hrop.b2h_wp, hrop.h2b_rp); |
| + |
| + if (hrop.b2h_wp >= b2h_rp) |
| + *avail_buf_len = channel->b2h_cb_size - hrop.b2h_wp + b2h_rp; |
| + else |
| + *avail_buf_len = b2h_rp - hrop.b2h_wp; |
| + |
| + return 0; |
| +} |
| + |
| +/* |
| + * data_length = multi-protocl packet length |
| + * unread_data_len = filled length in circular buffer |
| + */ |
| +static int get_mmbi_header(struct npcm_mmbi_channel *channel, |
| + u32 *data_length, u8 *type, u32 *unread_data_len) |
| +{ |
| + u32 h2b_wp, b2h_rp, h_rwp0, h_rwp1; |
| + struct mmbi_header header; |
| + struct host_rop hrop; |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + dev_dbg(channel->priv->dev, |
| + "MMBI HROP - b2h_wp: 0x%0x, h2b_rp: 0x%0x\n", hrop.b2h_wp, |
| + hrop.h2b_rp); |
| + |
| + if (read_host_rwp_val(channel, 0, &h_rwp0)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return -EAGAIN; |
| + } |
| + if (read_host_rwp_val(channel, 4, &h_rwp1)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return -EAGAIN; |
| + } |
| + h2b_wp = GET_H2B_WRITE_POINTER(h_rwp0); |
| + b2h_rp = GET_B2H_READ_POINTER(h_rwp1); |
| + dev_dbg(channel->priv->dev, "MMBI HRWP - h2b_wp: 0x%0x, b2h_rp: 0x%0x\n", h2b_wp, b2h_rp); |
| + |
| + if (h2b_wp >= hrop.h2b_rp) |
| + *unread_data_len = h2b_wp - hrop.h2b_rp; |
| + else |
| + *unread_data_len = channel->h2b_cb_size - hrop.h2b_rp + h2b_wp; |
| + |
| + if (*unread_data_len < sizeof(struct mmbi_header)) { |
| + dev_dbg(channel->priv->dev, "No data to read(%d -%d)\n", h2b_wp, |
| + hrop.h2b_rp); |
| + return -EAGAIN; |
| + } |
| + |
| + dev_dbg(channel->priv->dev, "READ MMBI header from: 0x%p\n", |
| + (channel->h2b_cb_vmem + hrop.h2b_rp)); |
| + |
| + /* Extract MMBI protocol - protocol type and length */ |
| + if ((hrop.h2b_rp + sizeof(header)) <= channel->h2b_cb_size) { |
| + memcpy(&header, channel->h2b_cb_vmem + hrop.h2b_rp, |
| + sizeof(header)); |
| + } else { |
| + ssize_t chunk_len = channel->h2b_cb_size - hrop.h2b_rp; |
| + |
| + memcpy(&header, channel->h2b_cb_vmem + hrop.h2b_rp, chunk_len); |
| + memcpy(((u8 *)&header) + chunk_len, channel->h2b_cb_vmem, |
| + sizeof(header) - chunk_len); |
| + } |
| + |
| + *data_length = FIELD_GET(MMBI_HDR_LENGTH_MASK, header.data); |
| + *type = FIELD_GET(MMBI_HDR_TYPE_MASK, header.data); |
| + |
| + return 0; |
| +} |
| + |
| +static void raise_missing_sci(struct npcm_mmbi_channel *channel) |
| +{ |
| + struct host_rop hrop; |
| + u32 h_rwp0, h_rwp1, b2h_rp; |
| + |
| + /* Rise SCI only if Host is READY (h_rdy is 1). */ |
| + if (read_host_rwp_val(channel, 0, &h_rwp0)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return; |
| + } |
| + if (!GET_HOST_READY_BIT(h_rwp0)) { |
| + /* Host is not ready, no point in raising the SCI */ |
| + return; |
| + } |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + if (read_host_rwp_val(channel, 4, &h_rwp1)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return; |
| + } |
| + b2h_rp = GET_B2H_READ_POINTER(h_rwp1); |
| + |
| + if (hrop.b2h_wp == b2h_rp) { |
| + /* |
| + * Host has read all outstanding SCI data, |
| + * Do not raise another SCI. |
| + */ |
| + return; |
| + } |
| + |
| + dev_dbg(channel->priv->dev, |
| + "Host not read the data yet, so rising SCI interrupt again...\n"); |
| + raise_sci_interrupt(channel); |
| +} |
| + |
| +static void update_host_rop(struct npcm_mmbi_channel *channel, |
| + unsigned int w_len, unsigned int r_len) |
| +{ |
| + struct host_rop hrop; |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + dev_dbg(channel->priv->dev, |
| + "MMBI HROP - b2h_wp: 0x%0x, h2b_rp: 0x%0x\n", hrop.b2h_wp, |
| + hrop.h2b_rp); |
| + |
| + /* Advance the B2H CB offset for next write */ |
| + if ((hrop.b2h_wp + w_len) <= channel->b2h_cb_size) |
| + hrop.b2h_wp += w_len; |
| + else |
| + hrop.b2h_wp = hrop.b2h_wp + w_len - channel->b2h_cb_size; |
| + |
| + /* Advance the H2B CB offset till where BMC read data */ |
| + if ((hrop.h2b_rp + r_len) <= channel->h2b_cb_size) |
| + hrop.h2b_rp += r_len; |
| + else |
| + hrop.h2b_rp = hrop.h2b_rp + r_len - channel->h2b_cb_size; |
| + |
| + /* |
| + * Clear BMC reset request state its set: |
| + * Set BMC reset request bit to 0 |
| + * Set BMC ready bit to 1 |
| + */ |
| + if (hrop.b_rst) { |
| + dev_dbg(channel->priv->dev, |
| + "Clearing BMC reset request state\n"); |
| + hrop.b_rst = 0; |
| + hrop.b_rdy = 1; |
| + } |
| + |
| + dev_dbg(channel->priv->dev, |
| + "Updating HROP - b2h_wp: 0x%0x, h2b_rp: 0x%0x\n", |
| + hrop.b2h_wp, hrop.h2b_rp); |
| + memcpy(channel->hrop_vmem, &hrop, sizeof(hrop)); |
| + |
| + /* |
| + * Raise SCI interrupt only if B2H buffer is updated |
| + * Don't raise SCI, after BMC read the H2B buffer |
| + */ |
| + if (w_len != 0) |
| + raise_sci_interrupt(channel); |
| +} |
| + |
| +static int send_bmc_reset_request(struct npcm_mmbi_channel *channel) |
| +{ |
| + struct host_rop hrop; |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + /* |
| + * Send MMBI buffer reset request: First BMC should clear its own |
| + * pointers, set Reset bit and reset BMC ready bit. |
| + * B2H Write pointer - must be set to zero |
| + * H2B read pointer - must be set to zero |
| + * BMC ready bit - Set to 0 |
| + * BMC reset bit - Set to 1 |
| + */ |
| + hrop.b2h_wp = 0; |
| + hrop.h2b_rp = 0; |
| + hrop.b_rdy = 0; |
| + hrop.b_rst = 1; |
| + |
| + dev_info(channel->priv->dev, |
| + "Send BMC reset request on MMBI channel(%d)\n", |
| + channel->chan_num); |
| + |
| + memcpy(channel->hrop_vmem, &hrop, sizeof(hrop)); |
| + |
| + /* Raise SCI interrupt */ |
| + raise_sci_interrupt(channel); |
| + |
| + return 0; |
| +} |
| + |
| +static bool check_host_reset_request(struct npcm_mmbi_channel *channel) |
| +{ |
| + struct host_rop hrop; |
| + u32 h_rwp1; |
| + |
| + if (read_host_rwp_val(channel, 4, &h_rwp1)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return false; |
| + } |
| + |
| + /* If its not host reset request, just discard */ |
| + if (!GET_HOST_RESET_REQ_BIT(h_rwp1)) |
| + return false; |
| + |
| + /* Host requested for MMBI buffer reset */ |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + /* |
| + * When host request for reset MMBI buffer: |
| + * B2H Write pointer - must be set to zero |
| + * H2B read pointer - must be set to zero |
| + * BMC ready bit - No change (Set to 1) |
| + * BMC reset bit - No change (Set to 0) |
| + */ |
| + hrop.b2h_wp = 0; |
| + hrop.h2b_rp = 0; |
| + channel->cur_h2b_wp = 0; |
| + |
| + dev_info(channel->priv->dev, |
| + "Handle Host reset request on MMBI channel(%d)\n", |
| + channel->chan_num); |
| + |
| + memcpy(channel->hrop_vmem, &hrop, sizeof(hrop)); |
| + |
| + return true; |
| +} |
| + |
| +void wake_up_device(struct npcm_mmbi_channel *channel) |
| +{ |
| + u32 req_data_len, unread_data_len; |
| + u8 type; |
| + int i; |
| + |
| + if (0 != |
| + get_mmbi_header(channel, &req_data_len, &type, &unread_data_len)) { |
| + /* Bail out as we can't read header */ |
| + return; |
| + } |
| + dev_dbg(channel->priv->dev, "%s: Length: 0x%0x, Protocol Type: %d\n", |
| + __func__, req_data_len, type); |
| + |
| + for (i = 0; channel->supported_protocols[i] != 0; i++) { |
| + if (type == channel->supported_protocols[i]) { |
| + /* |
| + * MMBI supports multiple protocols on each channel |
| + * If userspace application is not opened the device |
| + * for read /write the data, discard the data and |
| + * advance the HROP for processing next command. |
| + */ |
| + if (channel->protocol[i].process_data) { |
| + channel->protocol[i].data_available = true; |
| + wake_up(&channel->protocol[i].queue); |
| + } else { |
| + /* Discard data and advance the hrop */ |
| + update_host_rop(channel, 0, |
| + req_data_len + |
| + sizeof(struct mmbi_header)); |
| + } |
| + /* |
| + * Raise the missing SCI's by checking pointer for host |
| + * read acknowledgment. This will work around the Missing |
| + * SCI bug on host side. |
| + */ |
| + dev_warn(channel->priv->dev, |
| + "%s: Check and raise missing SCI\n", __func__); |
| + raise_missing_sci(channel); |
| + } |
| + } |
| +} |
| + |
| +static struct npcm_mmbi_protocol *file_npcm_espi_mmbi(struct file *file) |
| +{ |
| + return container_of(file->private_data, struct npcm_mmbi_protocol, |
| + miscdev); |
| +} |
| + |
| +static int mmbi_open(struct inode *inode, struct file *filp) |
| +{ |
| + return 0; |
| +} |
| + |
| +static int mmbi_release(struct inode *inode, struct file *filp) |
| +{ |
| + return 0; |
| +} |
| + |
| +static unsigned int mmbi_poll(struct file *filp, poll_table *wait) |
| +{ |
| + struct npcm_mmbi_protocol *protocol = file_npcm_espi_mmbi(filp); |
| + |
| + poll_wait(filp, &protocol->queue, wait); |
| + |
| + return protocol->data_available ? POLLIN : 0; |
| +} |
| + |
| +static ssize_t mmbi_read(struct file *filp, char *buff, size_t count, |
| + loff_t *offp) |
| +{ |
| + struct npcm_mmbi_protocol *protocol = file_npcm_espi_mmbi(filp); |
| + struct npcm_mmbi_channel *channel = protocol->chan_ref; |
| + struct npcm_espi_mmbi *priv = channel->priv; |
| + struct host_rop hrop; |
| + ssize_t rd_offset, rd_len; |
| + ssize_t ret; |
| + u32 unread_data_len, req_data_len; |
| + u8 type; |
| + |
| + protocol->process_data = true; |
| + |
| + if (!protocol->data_available && (filp->f_flags & O_NONBLOCK)) { |
| + dev_dbg(priv->dev, "%s: Non blocking file\n", __func__); |
| + /* |
| + * Work around: The lack of response might be cause by missing SCI |
| + * (host didn't consume the last message), check the buffer state |
| + * and retry if it's needed |
| + */ |
| + raise_missing_sci(channel); |
| + return -EAGAIN; |
| + } |
| + dev_dbg(priv->dev, "%s: count:%ld, Type: %d\n", __func__, count, |
| + protocol->type); |
| + |
| + ret = wait_event_interruptible(protocol->queue, |
| + protocol->data_available); |
| + |
| + mutex_lock(&priv->lock); |
| + if (ret == -ERESTARTSYS) { |
| + ret = -EINTR; |
| + goto err_out; |
| + } |
| + |
| + if (*offp >= channel->h2b_cb_size) { |
| + ret = -EINVAL; |
| + goto err_out; |
| + } |
| + |
| + if (*offp + count > channel->h2b_cb_size) |
| + count = channel->h2b_cb_size - *offp; |
| + |
| + ret = get_mmbi_header(channel, &req_data_len, &type, &unread_data_len); |
| + if (ret != 0) { |
| + /* Bail out as we can't read header. */ |
| + goto err_out; |
| + } |
| + dev_dbg(priv->dev, |
| + "%s: Length: %d, Protocol Type: %d, Unread data: %d\n", |
| + __func__, req_data_len, type, unread_data_len); |
| + |
| + if (req_data_len > count) { |
| + dev_err(priv->dev, "Data exceeding user space limit: %ld\n", count); |
| + ret = -EFAULT; |
| + /* Discard data and advance the hrop */ |
| + update_host_rop(channel, 0, req_data_len + sizeof(struct mmbi_header)); |
| + goto err_out; |
| + } |
| + |
| + /* Check is data belongs to this device, if not wake_up corresponding device. */ |
| + if (type != protocol->type) { |
| + dev_err(priv->dev, "type != protocol->type\n"); |
| + ret = -EFAULT; |
| + goto err_out; |
| + } |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + if ((hrop.h2b_rp + sizeof(struct mmbi_header)) <= |
| + channel->h2b_cb_size) { |
| + rd_offset = hrop.h2b_rp + sizeof(struct mmbi_header); |
| + } else { |
| + rd_offset = hrop.h2b_rp + sizeof(struct mmbi_header) - |
| + channel->h2b_cb_size; |
| + } |
| + rd_len = req_data_len; |
| + |
| + /* Extract data and copy to user space application */ |
| + dev_dbg(priv->dev, "READ MMBI Data from: offset 0x%lx and length: %ld\n", |
| + rd_offset, rd_len); |
| + if (unread_data_len < sizeof(struct mmbi_header) + rd_len) { |
| + dev_err(priv->dev, "Invalid H2B buffer (Read msg length: %ld)\n", |
| + rd_len); |
| + ret = -EFAULT; |
| + goto err_out; |
| + } |
| + |
| + if ((channel->h2b_cb_size - rd_offset) >= rd_len) { |
| + if (copy_to_user(buff, channel->h2b_cb_vmem + rd_offset, |
| + rd_len)) { |
| + dev_err(priv->dev, |
| + "Failed to copy data to user space\n"); |
| + ret = -EFAULT; |
| + goto err_out; |
| + } |
| + print_hex_dump_debug("mmbi H2B:", DUMP_PREFIX_NONE, 16, 1, |
| + channel->h2b_cb_vmem + rd_offset, |
| + rd_len, false); |
| + rd_offset += rd_len; |
| + } else { |
| + ssize_t chunk_len; |
| + |
| + chunk_len = channel->h2b_cb_size - rd_offset; |
| + if (copy_to_user(buff, channel->h2b_cb_vmem + rd_offset, |
| + chunk_len)) { |
| + dev_err(priv->dev, |
| + "Failed to copy data to user space\n"); |
| + ret = -EFAULT; |
| + goto err_out; |
| + } |
| + print_hex_dump_debug("mmbi H2B-1:", DUMP_PREFIX_NONE, 16, 1, |
| + channel->h2b_cb_vmem + rd_offset, |
| + chunk_len, false); |
| + rd_offset = 0; |
| + if (copy_to_user(buff + chunk_len, |
| + channel->h2b_cb_vmem + rd_offset, |
| + rd_len - chunk_len)) { |
| + dev_err(priv->dev, |
| + "Failed to copy data to user space\n"); |
| + ret = -EFAULT; |
| + goto err_out; |
| + } |
| + print_hex_dump_debug("mmbi H2B-2:", DUMP_PREFIX_NONE, 16, 1, |
| + channel->h2b_cb_vmem + rd_offset, |
| + rd_len - chunk_len, false); |
| + rd_offset += (rd_len - chunk_len); |
| + } |
| + *offp += rd_len; |
| + ret = rd_len; |
| + |
| + update_host_rop(channel, 0, rd_len + sizeof(struct mmbi_header)); |
| + |
| + dev_dbg(priv->dev, "%s: Return length: %ld\n", __func__, ret); |
| +err_out: |
| + mutex_unlock(&priv->lock); |
| + /* |
| + * Raise the missing SCI's by checking pointer for host |
| + * read acknowledgment. This will work around the Missing |
| + * SCI bug on host side. |
| + */ |
| + dev_warn(priv->dev, "%s: Check and raise missing SCI\n", __func__); |
| + raise_missing_sci(channel); |
| + |
| + protocol->data_available = false; |
| + |
| + wake_up_device(channel); |
| + |
| + return ret; |
| +} |
| + |
| +static ssize_t mmbi_write(struct file *filp, const char *buffer, size_t len, |
| + loff_t *offp) |
| +{ |
| + struct npcm_mmbi_protocol *protocol = file_npcm_espi_mmbi(filp); |
| + struct npcm_mmbi_channel *channel = protocol->chan_ref; |
| + struct npcm_espi_mmbi *priv = channel->priv; |
| + struct mmbi_header header; |
| + struct host_rop hrop; |
| + ssize_t wt_offset; |
| + ssize_t avail_buf_len; |
| + ssize_t chunk_len; |
| + ssize_t end_offset; |
| + u32 h_rwp0; |
| + |
| + dev_dbg(priv->dev, "%s: length:%ld , type: %d\n", __func__, len, |
| + protocol->type); |
| + |
| + if (read_host_rwp_val(channel, 0, &h_rwp0)) { |
| + dev_err(priv->dev, "Failed to read Host RWP\n"); |
| + return -EAGAIN; |
| + } |
| + |
| + /* If Host READY bit is not set, Just discard the write. */ |
| + if (!GET_HOST_READY_BIT(h_rwp0)) { |
| + dev_dbg(channel->priv->dev, |
| + "Host not ready, discarding request...\n"); |
| + return -EAGAIN; |
| + } |
| + |
| + if (get_b2h_avail_buf_len(channel, &avail_buf_len)) { |
| + dev_dbg(priv->dev, "Failed to B2H empty buffer len\n"); |
| + return -EAGAIN; |
| + } |
| + |
| + dev_dbg(priv->dev, "B2H buffer empty space: %ld\n", avail_buf_len); |
| + |
| + /* Empty space should be more than write request data size */ |
| + if (len + sizeof(header) > avail_buf_len) { |
| + dev_err(priv->dev, "Not enough space(%ld) in B2H buffer\n", |
| + avail_buf_len); |
| + return -ENOSPC; |
| + } |
| + |
| + mutex_lock(&priv->lock); |
| + /* Fill multi-protocol header */ |
| + header.data = ((protocol->type << 24) + len); |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + wt_offset = hrop.b2h_wp; |
| + end_offset = channel->b2h_cb_size; |
| + |
| + if ((end_offset - wt_offset) >= sizeof(header)) { |
| + memcpy(channel->b2h_cb_vmem + wt_offset, &header, |
| + sizeof(header)); |
| + wt_offset += sizeof(header); |
| + } else { |
| + chunk_len = end_offset - wt_offset; |
| + memcpy(channel->b2h_cb_vmem + wt_offset, &header, chunk_len); |
| + memcpy(channel->b2h_cb_vmem, &header + chunk_len, |
| + (sizeof(header) - chunk_len)); |
| + wt_offset = (sizeof(header) - chunk_len); |
| + } |
| + |
| + /* Write the data */ |
| + if ((end_offset - wt_offset) >= len) { |
| + if (copy_from_user(&channel->b2h_cb_vmem[wt_offset], buffer, |
| + len)) { |
| + mutex_unlock(&priv->lock); |
| + return -EFAULT; |
| + } |
| + print_hex_dump_debug("mmbi B2H:", DUMP_PREFIX_NONE, 16, 1, |
| + channel->b2h_cb_vmem + wt_offset, |
| + len, false); |
| + wt_offset += len; |
| + } else { |
| + chunk_len = end_offset - wt_offset; |
| + if (copy_from_user(&channel->b2h_cb_vmem[wt_offset], buffer, |
| + chunk_len)) { |
| + mutex_unlock(&priv->lock); |
| + return -EFAULT; |
| + } |
| + print_hex_dump_debug("mmbi B2H-1:", DUMP_PREFIX_NONE, 16, 1, |
| + channel->b2h_cb_vmem + wt_offset, |
| + chunk_len, false); |
| + wt_offset = 0; |
| + if (copy_from_user(&channel->b2h_cb_vmem[wt_offset], |
| + buffer + chunk_len, len - chunk_len)) { |
| + mutex_unlock(&priv->lock); |
| + return -EFAULT; |
| + } |
| + print_hex_dump_debug("mmbi B2H-2:", DUMP_PREFIX_NONE, 16, 1, |
| + channel->b2h_cb_vmem + wt_offset, |
| + len - chunk_len, false); |
| + wt_offset += len - chunk_len; |
| + } |
| + |
| + *offp += len; |
| + |
| + update_host_rop(channel, len + sizeof(struct mmbi_header), 0); |
| + mutex_unlock(&priv->lock); |
| + |
| + return len; |
| +} |
| + |
| +static int get_mmbi_config(struct npcm_mmbi_channel *channel, void __user *userbuf) |
| +{ |
| + bool h_ready; |
| + struct host_rop hrop; |
| + struct npcm_mmbi_get_config get_conf; |
| + u32 h2b_wptr, b2h_rptr, h_rwp0, h_rwp1; |
| + |
| + if (read_host_rwp_val(channel, 0, &h_rwp0)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return -EAGAIN; |
| + } |
| + if (read_host_rwp_val(channel, 4, &h_rwp1)) { |
| + dev_err(channel->priv->dev, "Failed to read Host RWP\n"); |
| + return -EAGAIN; |
| + } |
| + |
| + h2b_wptr = GET_H2B_WRITE_POINTER(h_rwp0); |
| + b2h_rptr = GET_B2H_READ_POINTER(h_rwp1); |
| + |
| + h_ready = GET_HOST_READY_BIT(h_rwp0) ? true : false; |
| + |
| + memcpy(&hrop, channel->hrop_vmem, sizeof(struct host_rop)); |
| + |
| + get_conf.h_rdy = h_ready; |
| + get_conf.h2b_wp = h2b_wptr; |
| + get_conf.b2h_rp = b2h_rptr; |
| + get_conf.h2b_rp = hrop.h2b_rp; |
| + get_conf.b2h_wp = hrop.b2h_wp; |
| + |
| + if (copy_to_user(userbuf, &get_conf, sizeof(get_conf))) { |
| + dev_err(channel->priv->dev, "copy to user failed\n"); |
| + return -EFAULT; |
| + } |
| + return 0; |
| +} |
| + |
| +static int get_b2h_empty_space(struct npcm_mmbi_channel *channel, |
| + void __user *userbuf) |
| +{ |
| + struct npcm_mmbi_get_empty_space empty_space; |
| + ssize_t avail_buf_len; |
| + |
| + if (get_b2h_avail_buf_len(channel, &avail_buf_len)) { |
| + dev_dbg(channel->priv->dev, "Failed to B2H empty buffer len\n"); |
| + return -EAGAIN; |
| + } |
| + |
| + dev_dbg(channel->priv->dev, "B2H buffer empty space: %ld\n", |
| + avail_buf_len); |
| + |
| + empty_space.length = avail_buf_len; |
| + |
| + if (copy_to_user(userbuf, &empty_space, sizeof(empty_space))) { |
| + dev_err(channel->priv->dev, "copy to user failed\n"); |
| + return -EFAULT; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static long mmbi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| +{ |
| + struct npcm_mmbi_protocol *protocol = file_npcm_espi_mmbi(filp); |
| + struct npcm_mmbi_channel *channel = protocol->chan_ref; |
| + void __user *userbuf = (void __user *)arg; |
| + int ret; |
| + |
| + switch (cmd) { |
| + case NPCM_MMBI_CTRL_IOCTL_GET_B2H_EMPTY_SPACE: |
| + ret = get_b2h_empty_space(channel, userbuf); |
| + break; |
| + |
| + case NPCM_MMBI_CTRL_IOCTL_SEND_RESET_REQUEST: |
| + ret = send_bmc_reset_request(channel); |
| + break; |
| + |
| + case NPCM_MMBI_CTRL_IOCTL_GET_CONFIG: |
| + ret = get_mmbi_config(channel, userbuf); |
| + break; |
| + |
| + default: |
| + dev_err(channel->priv->dev, "Command not found\n"); |
| + ret = -ENOTTY; |
| + } |
| + |
| + return ret; |
| +} |
| + |
| +static const struct file_operations npcm_espi_mmbi_fops = { |
| + .owner = THIS_MODULE, |
| + .open = mmbi_open, |
| + .release = mmbi_release, |
| + .read = mmbi_read, |
| + .write = mmbi_write, |
| + .unlocked_ioctl = mmbi_ioctl, |
| + .poll = mmbi_poll |
| +}; |
| + |
| +static char *get_protocol_suffix(protocol_type type) |
| +{ |
| + switch (type) { |
| + case MMBI_PROTOCOL_IPMI: |
| + return "ipmi"; |
| + case MMBI_PROTOCOL_SEAMLESS: |
| + return "seamless"; |
| + case MMBI_PROTOCOL_RAS_OFFLOAD: |
| + return "ras_offload"; |
| + case MMBI_PROTOCOL_MCTP: |
| + return "mctp"; |
| + case MMBI_PROTOCOL_NODE_MANAGER: |
| + return "nm"; |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +static void mmbi_desc_init(struct npcm_mmbi_channel *channel, |
| + struct mmbi_cap_desc *desc) |
| +{ |
| + struct mmbi_cap_desc ch_desc; |
| + u32 b2h_size = channel->hrwp_vmem - channel->desc_vmem; |
| + |
| + memset(&ch_desc, 0, sizeof(ch_desc)); |
| + |
| + /* Per MMBI protoco spec, Set it to "$MMBI$" */ |
| + strncpy(ch_desc.signature, "$MMBI$", sizeof(ch_desc.signature)); |
| + ch_desc.version = 1; |
| + ch_desc.instance_num = channel->chan_num; |
| + /* |
| + * TODO: Add multi-channel support. Handcoded H2B start offset |
| + * to 0x8000 as we support single channel today. |
| + */ |
| + ch_desc.nex_inst_base_addr = 0; |
| + ch_desc.b2h_ba = sizeof(struct mmbi_cap_desc) + sizeof(struct host_rop); |
| + ch_desc.h2b_ba = b2h_size + sizeof(struct host_rwp); |
| + ch_desc.b2h_d = b2h_size / 16; |
| + ch_desc.h2b_d = b2h_size / 16; /* 32KB = 0x800 * 16 */ |
| + |
| + ch_desc.buffer_type = 0x01; /* VMSCB */ |
| + ch_desc.bt_desc.host_rop_p = sizeof(struct mmbi_cap_desc); |
| + ch_desc.bt_desc.host_rwp_p = b2h_size; |
| + ch_desc.bt_desc.msg_protocol_type = 0x01; /* Multiple protocol type */ |
| + ch_desc.bt_desc.host_int_type = |
| + 0x01; /* SCI Triggered through eSPI VW */ |
| + ch_desc.bt_desc.global_sys_interrupt = 0x00; /* Not used */ |
| + ch_desc.bt_desc.bmc_int_type = 0x00; /* HW Interrupt */ |
| + ch_desc.bt_desc.bmc_int_a = 0x00; /* Not used, set to zero */ |
| + ch_desc.bt_desc.bmc_int_v = 0x00; /* Not used, set to zero */ |
| + |
| + ch_desc.crc8 = crc8(mmbi_crc8_table, (u8 *)&ch_desc, |
| + (size_t)(sizeof(ch_desc) - 1), 0); |
| + memcpy(desc, &ch_desc, sizeof(ch_desc)); |
| +} |
| + |
| +static int mmbi_channel_init(struct npcm_espi_mmbi *priv, u8 idx) |
| +{ |
| + struct device *dev = priv->dev; |
| + int rc; |
| + u8 i; |
| + u8 *h2b_vaddr, *b2h_vaddr; |
| + struct mmbi_cap_desc ch_desc; |
| + struct host_rop hrop; |
| + struct device_node *node; |
| + int no_of_protocols_enabled; |
| + u8 mmbi_supported_protocols[MAX_NO_OF_SUPPORTED_PROTOCOLS]; |
| + |
| + u32 b2h_size = (priv->mmbi_size / 2); |
| + u32 h2b_size = (priv->mmbi_size / 2); |
| + |
| + b2h_vaddr = priv->shm_vaddr; |
| + h2b_vaddr = priv->shm_vaddr + (priv->mmbi_size / 2); |
| + |
| + memset(&priv->chan[idx], 0, sizeof(struct npcm_mmbi_channel)); |
| + priv->chan[idx].chan_num = idx; |
| + |
| + priv->chan[idx].desc_vmem = b2h_vaddr; |
| + priv->chan[idx].hrop_vmem = b2h_vaddr + sizeof(struct mmbi_cap_desc); |
| + priv->chan[idx].b2h_cb_vmem = b2h_vaddr + sizeof(struct mmbi_cap_desc) + |
| + sizeof(struct host_rop); |
| + priv->chan[idx].b2h_cb_size = b2h_size - sizeof(struct mmbi_cap_desc) - |
| + sizeof(struct host_rop); |
| + /* Set BMC ready bit */ |
| + memcpy(&hrop, priv->chan[idx].hrop_vmem, sizeof(hrop)); |
| + hrop.b_rdy = 1; |
| + memcpy(priv->chan[idx].hrop_vmem, &hrop, sizeof(hrop)); |
| + |
| + priv->chan[idx].hrwp_vmem = h2b_vaddr; |
| + priv->chan[idx].h2b_cb_vmem = h2b_vaddr + sizeof(struct host_rwp); |
| + priv->chan[idx].h2b_cb_size = h2b_size - sizeof(struct host_rwp); |
| + |
| + dev_dbg(priv->dev, |
| + "B2H mapped addr - desc: 0x%0lx, hrop: 0x%0lx, b2h_cb: 0x%0lx\n", |
| + (size_t)priv->chan[idx].desc_vmem, |
| + (size_t)priv->chan[idx].hrop_vmem, |
| + (size_t)priv->chan[idx].b2h_cb_vmem); |
| + dev_dbg(priv->dev, "H2B mapped addr - hrwp: 0x%0lx, h2b_cb: 0x%0lx\n", |
| + (size_t)priv->chan[idx].hrwp_vmem, |
| + (size_t)priv->chan[idx].h2b_cb_vmem); |
| + |
| + dev_dbg(priv->dev, "B2H buffer size: 0x%0lx\n", |
| + (size_t)priv->chan[idx].b2h_cb_size); |
| + dev_dbg(priv->dev, "H2B buffer size: 0x%0lx\n", |
| + (size_t)priv->chan[idx].h2b_cb_size); |
| + |
| + /* Initialize the MMBI channel descriptor */ |
| + mmbi_desc_init(&priv->chan[idx], &ch_desc); |
| + memcpy(priv->chan[idx].desc_vmem, &ch_desc, sizeof(ch_desc)); |
| + |
| + priv->chan[idx].enabled = true; |
| + node = of_get_child_by_name(priv->dev->of_node, "instance"); |
| + if (!node) { |
| + dev_err(priv->dev, "mmbi protocol : no instance found\n"); |
| + goto err_destroy_channel; |
| + } |
| + no_of_protocols_enabled = of_property_count_u8_elems(node, "protocols"); |
| + if (no_of_protocols_enabled <= 0 || no_of_protocols_enabled > |
| + MAX_NO_OF_SUPPORTED_PROTOCOLS){ |
| + dev_err(dev, "No supported mmbi protocol\n"); |
| + of_node_put(node); |
| + goto err_destroy_channel; |
| + } |
| + memset(mmbi_supported_protocols, 0, sizeof(mmbi_supported_protocols)); |
| + rc = of_property_read_u8_array(node, "protocols", mmbi_supported_protocols, |
| + no_of_protocols_enabled); |
| + if (!rc) { |
| + memset(&priv->chan[idx].supported_protocols, 0, |
| + sizeof(priv->chan[idx].supported_protocols)); |
| + memcpy(&priv->chan[idx].supported_protocols, mmbi_supported_protocols, |
| + sizeof(mmbi_supported_protocols)); |
| + } |
| + of_node_put(node); |
| + |
| + for (i = 0; i < no_of_protocols_enabled; i++) { |
| + char *dev_name; |
| + u8 proto_type; |
| + |
| + proto_type = priv->chan[idx].supported_protocols[i]; |
| + dev_name = get_protocol_suffix(proto_type); |
| + if (!dev_name) { |
| + dev_err(dev, |
| + "Unable to get MMBI protocol suffix name\n"); |
| + goto err_destroy_channel; |
| + } |
| + priv->chan[idx].protocol[i].type = proto_type; |
| + priv->chan[idx].protocol[i].miscdev.name = |
| + devm_kasprintf(dev, GFP_KERNEL, "%s_%d_%s", DEVICE_NAME, |
| + idx, dev_name); |
| + priv->chan[idx].protocol[i].miscdev.minor = MISC_DYNAMIC_MINOR; |
| + priv->chan[idx].protocol[i].miscdev.fops = |
| + &npcm_espi_mmbi_fops; |
| + priv->chan[idx].protocol[i].miscdev.parent = dev; |
| + rc = misc_register(&priv->chan[idx].protocol[i].miscdev); |
| + if (rc) { |
| + dev_err(dev, "Unable to register device\n"); |
| + goto err_destroy_channel; |
| + } |
| + |
| + /* Hold the back reference of channel */ |
| + priv->chan[idx].protocol[i].chan_ref = &priv->chan[idx]; |
| + |
| + priv->chan[idx].protocol[i].data_available = false; |
| + priv->chan[idx].protocol[i].process_data = false; |
| + init_waitqueue_head(&priv->chan[idx].protocol[i].queue); |
| + } |
| + |
| + priv->chan[idx].priv = priv; |
| + |
| + /* |
| + * When BMC goes for reset while host is in OS, SRAM memory will be |
| + * remapped and the content in memory will be lost. This include |
| + * host ready state which will block memory write transactions. |
| + * Ideally this reset has to be done while mapping memory(u-boot). |
| + * Since channel initialization (including descriptor) done at kernel, |
| + * So added channel reset also during driver load. Future, when staged |
| + * commands processing(IPMI commands for BIOS-BMC communication) is |
| + * enabled, this check should be moved to u-boot. |
| + */ |
| + if (send_bmc_reset_request(&priv->chan[idx])) |
| + dev_info(dev, "MMBI channel(%d) reset failed\n", idx); |
| + |
| + dev_info(dev, "MMBI Channel(%d) initialized successfully\n", idx); |
| + |
| + return 0; |
| + |
| +err_destroy_channel: |
| + if (b2h_vaddr) |
| + memunmap(b2h_vaddr); |
| + |
| + if (h2b_vaddr) |
| + memunmap(h2b_vaddr); |
| + |
| + priv->chan[idx].enabled = false; |
| + return -ENOMEM; |
| +} |
| + |
| +static void npcm_mmbi_poll_work(struct work_struct *work) |
| +{ |
| + struct npcm_espi_mmbi *priv = container_of(to_delayed_work(work), |
| + struct npcm_espi_mmbi, polling_work); |
| + struct npcm_mmbi_channel *channel = &priv->chan[0]; |
| + u32 h2b_wp, h_rwp0; |
| + bool h2b_changed = false; |
| + |
| + mutex_lock(&priv->lock); |
| + /* Clear Access status */ |
| + writeb(SHM_ACC, priv->regs + SHM_SMC_STS); |
| + |
| + if (check_host_reset_request(channel)) { |
| + h2b_changed = true; |
| + goto out; |
| + } |
| + |
| + if (read_host_rwp_val(channel, 0, &h_rwp0)) { |
| + dev_dbg(channel->priv->dev, "Failed to read Host RWP0\n"); |
| + goto out; |
| + } |
| + h2b_wp = GET_H2B_WRITE_POINTER(h_rwp0); |
| + if (h2b_wp != channel->cur_h2b_wp) { |
| + dev_dbg(priv->dev, "current h2b_wp 0x%x, new h2b_wp 0x%x\n", |
| + channel->cur_h2b_wp, h2b_wp); |
| + channel->cur_h2b_wp = h2b_wp; |
| + h2b_changed = true; |
| + wake_up_device(channel); |
| + } |
| +out: |
| + mutex_unlock(&priv->lock); |
| + if (!priv->poll_timeout) |
| + goto poll_again; |
| + |
| + if (h2b_changed || time_after(jiffies, priv->poll_timeout)) { |
| + dev_dbg(priv->dev, "End of polling work, h2b_changed %d\n", h2b_changed); |
| + npcm_mmbi_enable_interrupt(priv, true); |
| + return; |
| + } |
| + |
| +poll_again: |
| + queue_delayed_work(priv->wq, &priv->polling_work, |
| + msecs_to_jiffies(priv->poll_interval)); |
| +} |
| + |
| +static irqreturn_t npcm_espi_mmbi_irq(int irq, void *arg) |
| +{ |
| + struct npcm_espi_mmbi *priv = arg; |
| + u8 status; |
| + |
| + npcm_mmbi_enable_interrupt(priv, false); |
| + /* Clear Access status */ |
| + status = readb(priv->regs + SHM_SMC_STS); |
| + writeb(SHM_ACC, priv->regs + SHM_SMC_STS); |
| + |
| + dev_dbg(priv->dev, "MMBI IRQ Status: 0x%x\n", status); |
| + priv->poll_timeout = jiffies + msecs_to_jiffies(POLLING_TIMEOUT_MS); |
| + queue_delayed_work(priv->wq, &priv->polling_work, |
| + msecs_to_jiffies(priv->poll_interval)); |
| + |
| + return IRQ_HANDLED; |
| +} |
| + |
| +static int npcm_mmbi_setup_window(struct npcm_espi_mmbi *priv, struct resource *res) |
| +{ |
| + resource_size_t size = resource_size(res); |
| + resource_size_t phys_addr = res->start; |
| + int size_sel; |
| + u8 reg_val; |
| + |
| + if (size > NPCM_MAX_WIN_SIZE) |
| + size = NPCM_MAX_WIN_SIZE; |
| + size_sel = ilog2(size); |
| + reg_val = readb(priv->regs + SHM_WIN_SIZE) & 0xF0; |
| + reg_val |= size_sel & 0x0F; |
| + /* Map to access window 1 */ |
| + writel(phys_addr, priv->regs + SHM_WIN_BASE1); |
| + writeb(reg_val, priv->regs + SHM_WIN_SIZE); |
| + |
| + return 0; |
| +} |
| + |
| +static int npcm_espi_mmbi_probe(struct platform_device *pdev) |
| +{ |
| + struct npcm_espi_mmbi *priv; |
| + struct device_node *node; |
| + struct resource resm; |
| + int rc, i; |
| + u32 val; |
| + |
| + dev_dbg(&pdev->dev, "MMBI: Probing MMBI devices...\n"); |
| + |
| + priv = devm_kzalloc(&pdev->dev, sizeof(struct npcm_espi_mmbi), |
| + GFP_KERNEL); |
| + if (!priv) |
| + return -ENOMEM; |
| + |
| + priv->dev = &pdev->dev; |
| + priv->regs = devm_platform_ioremap_resource(pdev, 0); |
| + if (IS_ERR(priv->regs)) { |
| + dev_err(priv->dev, "MMBI: Failed to get regmap!\n"); |
| + return PTR_ERR(priv->regs); |
| + } |
| + |
| + /* ESPI register map */ |
| + priv->pmap = syscon_regmap_lookup_by_phandle(priv->dev->of_node, |
| + "nuvoton,espi"); |
| + if (IS_ERR(priv->pmap)) { |
| + dev_err(priv->dev, "MMBI: Failed to find ESPI regmap\n"); |
| + return PTR_ERR(priv->pmap); |
| + } |
| + |
| + /* Set SW control for SCI# VW(System Event Index 6 wire 0) */ |
| + regmap_clear_bits(priv->pmap, NPCM_ESPI_VWEVSM2, HW_WIRE(0)); |
| + |
| + priv->wq = alloc_workqueue("%s", 0, 0, dev_name(priv->dev)); |
| + if (!priv->wq) { |
| + dev_err(priv->dev, "MMBI: Failed to allocate workqueue\n"); |
| + return -ENOMEM; |
| + } |
| + /* If memory-region is described in device tree then store */ |
| + node = of_parse_phandle(priv->dev->of_node, "memory-region", 0); |
| + if (node) { |
| + rc = of_address_to_resource(node, 0, &resm); |
| + of_node_put(node); |
| + if (!rc) { |
| + priv->mmbi_size = resource_size(&resm); |
| + priv->mmbi_phys_addr = resm.start; |
| + priv->shm_vaddr = devm_ioremap_resource_wc(priv->dev, &resm); |
| + if (IS_ERR(priv->shm_vaddr)) { |
| + dev_err(priv->dev, "device mem io remap failed\n"); |
| + return PTR_ERR(priv->shm_vaddr); |
| + } |
| + memset_io(priv->shm_vaddr, 0, priv->mmbi_size); |
| + npcm_mmbi_setup_window(priv, &resm); |
| + } else { |
| + dev_err(priv->dev, "No memory region\n"); |
| + return -EINVAL; |
| + } |
| + } else { |
| + dev_dbg(priv->dev, |
| + "No DTS config, assign default MMBI Address\n"); |
| + return -EINVAL; |
| + } |
| + dev_dbg(priv->dev, "MMBI: SramAddr:0x%llx, Size: 0x%llx\n", |
| + priv->mmbi_phys_addr, priv->mmbi_size); |
| + |
| + crc8_populate_msb(mmbi_crc8_table, MMBI_CRC8_POLYNOMIAL); |
| + |
| + dev_set_drvdata(priv->dev, priv); |
| + |
| + for (i = 0; i < MAX_NO_OF_SUPPORTED_CHANNELS; i++) { |
| + rc = mmbi_channel_init(priv, i); |
| + if (rc) { |
| + dev_err(priv->dev, "MMBI: Channel(%d) init failed\n", |
| + i); |
| + return rc; |
| + } |
| + } |
| + |
| + mutex_init(&priv->lock); |
| + INIT_DELAYED_WORK(&priv->polling_work, npcm_mmbi_poll_work); |
| + |
| + if (!of_property_read_u32(priv->dev->of_node, "poll-interval-ms", &val)) |
| + priv->poll_interval = val > POLLING_TIMEOUT_MS ? POLLING_TIMEOUT_MS : val; |
| + else |
| + priv->poll_interval = POLLING_INTERVAL_MS; |
| + dev_dbg(priv->dev, "poll_interval=%dms\n", priv->poll_interval); |
| + if (of_property_read_bool(priv->dev->of_node, "use-interrupt")) { |
| + dev_info(priv->dev, "use interrupt\n"); |
| + /* Enable IRQ */ |
| + priv->irq = platform_get_irq(pdev, 0); |
| + if (priv->irq < 0) { |
| + dev_err(priv->dev, "MMBI: No irq specified\n"); |
| + return priv->irq; |
| + } |
| + |
| + rc = devm_request_irq(priv->dev, priv->irq, npcm_espi_mmbi_irq, |
| + IRQF_SHARED, dev_name(priv->dev), priv); |
| + if (rc) { |
| + dev_err(priv->dev, "MMBI: Unable to get IRQ\n"); |
| + return rc; |
| + } |
| + npcm_mmbi_enable_interrupt(priv, true); |
| + } else { |
| + dev_info(priv->dev, "use polling, interval=%u ms\n", priv->poll_interval); |
| + priv->poll_timeout = 0; |
| + queue_delayed_work(priv->wq, &priv->polling_work, |
| + msecs_to_jiffies(priv->poll_interval)); |
| + } |
| + dev_info(priv->dev, "MMBI: npcm MMBI driver loaded successfully\n"); |
| + |
| + return 0; |
| +} |
| + |
| +static int npcm_espi_mmbi_remove(struct platform_device *pdev) |
| +{ |
| + struct npcm_espi_mmbi *priv = dev_get_drvdata(&pdev->dev); |
| + int i, j; |
| + |
| + dev_dbg(priv->dev, "MMBI: Removing MMBI device\n"); |
| + |
| + if (priv->wq) |
| + destroy_workqueue(priv->wq); |
| + for (i = 0; i < MAX_NO_OF_SUPPORTED_CHANNELS; i++) { |
| + if (!priv->chan[i].enabled) |
| + continue; |
| + for (j = 0; priv->chan[i].supported_protocols[j] != 0; j++) |
| + misc_deregister(&priv->chan[i].protocol[j].miscdev); |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static const struct of_device_id npcm_espi_mmbi_match[] = { |
| + { .compatible = "nuvoton,npcm845-espi-mmbi" }, |
| + {} |
| +}; |
| +MODULE_DEVICE_TABLE(of, npcm_espi_mmbi_match); |
| + |
| +static struct platform_driver npcm_espi_mmbi_driver = { |
| + .driver = { |
| + .name = DEVICE_NAME, |
| + .of_match_table = npcm_espi_mmbi_match, |
| + }, |
| + .probe = npcm_espi_mmbi_probe, |
| + .remove = npcm_espi_mmbi_remove, |
| +}; |
| +module_platform_driver(npcm_espi_mmbi_driver); |
| + |
| +MODULE_AUTHOR("AppaRao Puli <apparao.puli@intel.com>"); |
| +MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>"); |
| +MODULE_AUTHOR("Stanley Chu <yschu@nuvoton.com>"); |
| +MODULE_AUTHOR("Ban Feng <kcfeng0@nuvoton.com>"); |
| +MODULE_DESCRIPTION("Nuvoton NPCM eSPI MMBI Driver"); |
| +MODULE_LICENSE("GPL v2"); |
| diff --git a/include/dt-bindings/mmbi/protocols.h b/include/dt-bindings/mmbi/protocols.h |
| new file mode 100644 |
| index 000000000000..9cbf9ca50e36 |
| --- /dev/null |
| +++ b/include/dt-bindings/mmbi/protocols.h |
| @@ -0,0 +1,11 @@ |
| +#ifndef _DT_BINDINGS_MMBI_PROTOCOLS_H_ |
| +#define _DT_BINDINGS_MMBI_PROTOCOLS_H_ |
| + |
| +//This definitions are as per MMBI specification. |
| +#define MMBI_PROTOCOL_IPMI 1 |
| +#define MMBI_PROTOCOL_SEAMLESS 2 |
| +#define MMBI_PROTOCOL_RAS_OFFLOAD 3 |
| +#define MMBI_PROTOCOL_MCTP 4 |
| +#define MMBI_PROTOCOL_NODE_MANAGER 5 |
| + |
| +#endif |
| -- |
| 2.34.1 |
| |