| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * MAX10 BMC Platform Management Component Interface (PMCI) based |
| * interface. |
| * |
| * Copyright (C) 2020-2023 Intel Corporation. |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/device.h> |
| #include <linux/dfl.h> |
| #include <linux/mfd/core.h> |
| #include <linux/mfd/intel-m10-bmc.h> |
| #include <linux/minmax.h> |
| #include <linux/module.h> |
| #include <linux/regmap.h> |
| |
| struct m10bmc_pmci_device { |
| void __iomem *base; |
| struct intel_m10bmc m10bmc; |
| struct mutex flash_mutex; /* protects flash_busy and serializes flash read/read */ |
| bool flash_busy; |
| }; |
| |
| /* |
| * Intel FGPA indirect register access via hardware controller/bridge. |
| */ |
| #define INDIRECT_CMD_OFF 0 |
| #define INDIRECT_CMD_CLR 0 |
| #define INDIRECT_CMD_RD BIT(0) |
| #define INDIRECT_CMD_WR BIT(1) |
| #define INDIRECT_CMD_ACK BIT(2) |
| |
| #define INDIRECT_ADDR_OFF 0x4 |
| #define INDIRECT_RD_OFF 0x8 |
| #define INDIRECT_WR_OFF 0xc |
| |
| #define INDIRECT_INT_US 1 |
| #define INDIRECT_TIMEOUT_US 10000 |
| |
| struct indirect_ctx { |
| void __iomem *base; |
| struct device *dev; |
| }; |
| |
| static int indirect_clear_cmd(struct indirect_ctx *ctx) |
| { |
| unsigned int cmd; |
| int ret; |
| |
| writel(INDIRECT_CMD_CLR, ctx->base + INDIRECT_CMD_OFF); |
| |
| ret = readl_poll_timeout(ctx->base + INDIRECT_CMD_OFF, cmd, |
| cmd == INDIRECT_CMD_CLR, |
| INDIRECT_INT_US, INDIRECT_TIMEOUT_US); |
| if (ret) |
| dev_err(ctx->dev, "timed out waiting clear cmd (residual cmd=0x%x)\n", cmd); |
| |
| return ret; |
| } |
| |
| static int indirect_reg_read(void *context, unsigned int reg, unsigned int *val) |
| { |
| struct indirect_ctx *ctx = context; |
| unsigned int cmd, ack, tmpval; |
| int ret, ret2; |
| |
| cmd = readl(ctx->base + INDIRECT_CMD_OFF); |
| if (cmd != INDIRECT_CMD_CLR) |
| dev_warn(ctx->dev, "residual cmd 0x%x on read entry\n", cmd); |
| |
| writel(reg, ctx->base + INDIRECT_ADDR_OFF); |
| writel(INDIRECT_CMD_RD, ctx->base + INDIRECT_CMD_OFF); |
| |
| ret = readl_poll_timeout(ctx->base + INDIRECT_CMD_OFF, ack, |
| (ack & INDIRECT_CMD_ACK) == INDIRECT_CMD_ACK, |
| INDIRECT_INT_US, INDIRECT_TIMEOUT_US); |
| if (ret) |
| dev_err(ctx->dev, "read timed out on reg 0x%x ack 0x%x\n", reg, ack); |
| else |
| tmpval = readl(ctx->base + INDIRECT_RD_OFF); |
| |
| ret2 = indirect_clear_cmd(ctx); |
| |
| if (ret) |
| return ret; |
| if (ret2) |
| return ret2; |
| |
| *val = tmpval; |
| return 0; |
| } |
| |
| static int indirect_reg_write(void *context, unsigned int reg, unsigned int val) |
| { |
| struct indirect_ctx *ctx = context; |
| unsigned int cmd, ack; |
| int ret, ret2; |
| |
| cmd = readl(ctx->base + INDIRECT_CMD_OFF); |
| if (cmd != INDIRECT_CMD_CLR) |
| dev_warn(ctx->dev, "residual cmd 0x%x on write entry\n", cmd); |
| |
| writel(val, ctx->base + INDIRECT_WR_OFF); |
| writel(reg, ctx->base + INDIRECT_ADDR_OFF); |
| writel(INDIRECT_CMD_WR, ctx->base + INDIRECT_CMD_OFF); |
| |
| ret = readl_poll_timeout(ctx->base + INDIRECT_CMD_OFF, ack, |
| (ack & INDIRECT_CMD_ACK) == INDIRECT_CMD_ACK, |
| INDIRECT_INT_US, INDIRECT_TIMEOUT_US); |
| if (ret) |
| dev_err(ctx->dev, "write timed out on reg 0x%x ack 0x%x\n", reg, ack); |
| |
| ret2 = indirect_clear_cmd(ctx); |
| |
| if (ret) |
| return ret; |
| return ret2; |
| } |
| |
| static void pmci_write_fifo(void __iomem *base, const u32 *buf, size_t count) |
| { |
| while (count--) |
| writel(*buf++, base); |
| } |
| |
| static void pmci_read_fifo(void __iomem *base, u32 *buf, size_t count) |
| { |
| while (count--) |
| *buf++ = readl(base); |
| } |
| |
| static u32 pmci_get_write_space(struct m10bmc_pmci_device *pmci) |
| { |
| u32 val; |
| int ret; |
| |
| ret = read_poll_timeout(readl, val, |
| FIELD_GET(M10BMC_N6000_FLASH_FIFO_SPACE, val) == |
| M10BMC_N6000_FIFO_MAX_WORDS, |
| M10BMC_FLASH_INT_US, M10BMC_FLASH_TIMEOUT_US, |
| false, pmci->base + M10BMC_N6000_FLASH_CTRL); |
| if (ret == -ETIMEDOUT) |
| return 0; |
| |
| return FIELD_GET(M10BMC_N6000_FLASH_FIFO_SPACE, val) * M10BMC_N6000_FIFO_WORD_SIZE; |
| } |
| |
| static int pmci_flash_bulk_write(struct intel_m10bmc *m10bmc, const u8 *buf, u32 size) |
| { |
| struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc); |
| u32 blk_size, offset = 0, write_count; |
| |
| while (size) { |
| blk_size = min(pmci_get_write_space(pmci), size); |
| if (blk_size == 0) { |
| dev_err(m10bmc->dev, "get FIFO available size fail\n"); |
| return -EIO; |
| } |
| |
| if (size < M10BMC_N6000_FIFO_WORD_SIZE) |
| break; |
| |
| write_count = blk_size / M10BMC_N6000_FIFO_WORD_SIZE; |
| pmci_write_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO, |
| (u32 *)(buf + offset), write_count); |
| |
| size -= blk_size; |
| offset += blk_size; |
| } |
| |
| /* Handle remainder (less than M10BMC_N6000_FIFO_WORD_SIZE bytes) */ |
| if (size) { |
| u32 tmp = 0; |
| |
| memcpy(&tmp, buf + offset, size); |
| pmci_write_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO, &tmp, 1); |
| } |
| |
| return 0; |
| } |
| |
| static int pmci_flash_bulk_read(struct intel_m10bmc *m10bmc, u8 *buf, u32 addr, u32 size) |
| { |
| struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc); |
| u32 blk_size, offset = 0, val, full_read_count, read_count; |
| int ret; |
| |
| while (size) { |
| blk_size = min_t(u32, size, M10BMC_N6000_READ_BLOCK_SIZE); |
| full_read_count = blk_size / M10BMC_N6000_FIFO_WORD_SIZE; |
| |
| read_count = full_read_count; |
| if (full_read_count * M10BMC_N6000_FIFO_WORD_SIZE < blk_size) |
| read_count++; |
| |
| writel(addr + offset, pmci->base + M10BMC_N6000_FLASH_ADDR); |
| writel(FIELD_PREP(M10BMC_N6000_FLASH_READ_COUNT, read_count) | |
| M10BMC_N6000_FLASH_RD_MODE, |
| pmci->base + M10BMC_N6000_FLASH_CTRL); |
| |
| ret = readl_poll_timeout((pmci->base + M10BMC_N6000_FLASH_CTRL), val, |
| !(val & M10BMC_N6000_FLASH_BUSY), |
| M10BMC_FLASH_INT_US, M10BMC_FLASH_TIMEOUT_US); |
| if (ret) { |
| dev_err(m10bmc->dev, "read timed out on reading flash 0x%xn", val); |
| return ret; |
| } |
| |
| pmci_read_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO, |
| (u32 *)(buf + offset), full_read_count); |
| |
| size -= blk_size; |
| offset += blk_size; |
| |
| if (full_read_count < read_count) |
| break; |
| |
| writel(0, pmci->base + M10BMC_N6000_FLASH_CTRL); |
| } |
| |
| /* Handle remainder (less than M10BMC_N6000_FIFO_WORD_SIZE bytes) */ |
| if (size) { |
| u32 tmp; |
| |
| pmci_read_fifo(pmci->base + M10BMC_N6000_FLASH_FIFO, &tmp, 1); |
| memcpy(buf + offset, &tmp, size); |
| |
| writel(0, pmci->base + M10BMC_N6000_FLASH_CTRL); |
| } |
| |
| return 0; |
| } |
| |
| static int m10bmc_pmci_set_flash_host_mux(struct intel_m10bmc *m10bmc, bool request) |
| { |
| u32 ctrl; |
| int ret; |
| |
| ret = regmap_update_bits(m10bmc->regmap, M10BMC_N6000_FLASH_MUX_CTRL, |
| M10BMC_N6000_FLASH_HOST_REQUEST, |
| FIELD_PREP(M10BMC_N6000_FLASH_HOST_REQUEST, request)); |
| if (ret) |
| return ret; |
| |
| return regmap_read_poll_timeout(m10bmc->regmap, |
| M10BMC_N6000_FLASH_MUX_CTRL, ctrl, |
| request ? |
| (get_flash_mux(ctrl) == M10BMC_N6000_FLASH_MUX_HOST) : |
| (get_flash_mux(ctrl) != M10BMC_N6000_FLASH_MUX_HOST), |
| M10BMC_FLASH_INT_US, M10BMC_FLASH_TIMEOUT_US); |
| } |
| |
| static int m10bmc_pmci_flash_read(struct intel_m10bmc *m10bmc, u8 *buf, u32 addr, u32 size) |
| { |
| struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc); |
| int ret, ret2; |
| |
| mutex_lock(&pmci->flash_mutex); |
| if (pmci->flash_busy) { |
| ret = -EBUSY; |
| goto unlock; |
| } |
| |
| ret = m10bmc_pmci_set_flash_host_mux(m10bmc, true); |
| if (ret) |
| goto mux_fail; |
| |
| ret = pmci_flash_bulk_read(m10bmc, buf, addr, size); |
| |
| mux_fail: |
| ret2 = m10bmc_pmci_set_flash_host_mux(m10bmc, false); |
| |
| unlock: |
| mutex_unlock(&pmci->flash_mutex); |
| if (ret) |
| return ret; |
| return ret2; |
| } |
| |
| static int m10bmc_pmci_flash_write(struct intel_m10bmc *m10bmc, const u8 *buf, u32 offset, u32 size) |
| { |
| struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc); |
| int ret; |
| |
| mutex_lock(&pmci->flash_mutex); |
| WARN_ON_ONCE(!pmci->flash_busy); |
| /* On write, firmware manages flash MUX */ |
| ret = pmci_flash_bulk_write(m10bmc, buf + offset, size); |
| mutex_unlock(&pmci->flash_mutex); |
| |
| return ret; |
| } |
| |
| static int m10bmc_pmci_flash_lock(struct intel_m10bmc *m10bmc) |
| { |
| struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc); |
| int ret = 0; |
| |
| mutex_lock(&pmci->flash_mutex); |
| if (pmci->flash_busy) { |
| ret = -EBUSY; |
| goto unlock; |
| } |
| |
| pmci->flash_busy = true; |
| |
| unlock: |
| mutex_unlock(&pmci->flash_mutex); |
| return ret; |
| } |
| |
| static void m10bmc_pmci_flash_unlock(struct intel_m10bmc *m10bmc) |
| { |
| struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc); |
| |
| mutex_lock(&pmci->flash_mutex); |
| WARN_ON_ONCE(!pmci->flash_busy); |
| pmci->flash_busy = false; |
| mutex_unlock(&pmci->flash_mutex); |
| } |
| |
| static const struct intel_m10bmc_flash_bulk_ops m10bmc_pmci_flash_bulk_ops = { |
| .read = m10bmc_pmci_flash_read, |
| .write = m10bmc_pmci_flash_write, |
| .lock_write = m10bmc_pmci_flash_lock, |
| .unlock_write = m10bmc_pmci_flash_unlock, |
| }; |
| |
| static const struct regmap_range m10bmc_pmci_regmap_range[] = { |
| regmap_reg_range(M10BMC_N6000_SYS_BASE, M10BMC_N6000_SYS_END), |
| }; |
| |
| static const struct regmap_access_table m10bmc_pmci_access_table = { |
| .yes_ranges = m10bmc_pmci_regmap_range, |
| .n_yes_ranges = ARRAY_SIZE(m10bmc_pmci_regmap_range), |
| }; |
| |
| static struct regmap_config m10bmc_pmci_regmap_config = { |
| .reg_bits = 32, |
| .reg_stride = 4, |
| .val_bits = 32, |
| .wr_table = &m10bmc_pmci_access_table, |
| .rd_table = &m10bmc_pmci_access_table, |
| .reg_read = &indirect_reg_read, |
| .reg_write = &indirect_reg_write, |
| .max_register = M10BMC_N6000_SYS_END, |
| }; |
| |
| static struct mfd_cell m10bmc_pmci_n6000_bmc_subdevs[] = { |
| { .name = "n6000bmc-hwmon" }, |
| { .name = "n6000bmc-sec-update" }, |
| }; |
| |
| static const struct m10bmc_csr_map m10bmc_n6000_csr_map = { |
| .base = M10BMC_N6000_SYS_BASE, |
| .build_version = M10BMC_N6000_BUILD_VER, |
| .fw_version = NIOS2_N6000_FW_VERSION, |
| .mac_low = M10BMC_N6000_MAC_LOW, |
| .mac_high = M10BMC_N6000_MAC_HIGH, |
| .doorbell = M10BMC_N6000_DOORBELL, |
| .auth_result = M10BMC_N6000_AUTH_RESULT, |
| .bmc_prog_addr = M10BMC_N6000_BMC_PROG_ADDR, |
| .bmc_reh_addr = M10BMC_N6000_BMC_REH_ADDR, |
| .bmc_magic = M10BMC_N6000_BMC_PROG_MAGIC, |
| .sr_prog_addr = M10BMC_N6000_SR_PROG_ADDR, |
| .sr_reh_addr = M10BMC_N6000_SR_REH_ADDR, |
| .sr_magic = M10BMC_N6000_SR_PROG_MAGIC, |
| .pr_prog_addr = M10BMC_N6000_PR_PROG_ADDR, |
| .pr_reh_addr = M10BMC_N6000_PR_REH_ADDR, |
| .pr_magic = M10BMC_N6000_PR_PROG_MAGIC, |
| .rsu_update_counter = M10BMC_N6000_STAGING_FLASH_COUNT, |
| .staging_size = M10BMC_STAGING_SIZE, |
| }; |
| |
| static const struct intel_m10bmc_platform_info m10bmc_pmci_n6000 = { |
| .cells = m10bmc_pmci_n6000_bmc_subdevs, |
| .n_cells = ARRAY_SIZE(m10bmc_pmci_n6000_bmc_subdevs), |
| .csr_map = &m10bmc_n6000_csr_map, |
| }; |
| |
| static int m10bmc_pmci_probe(struct dfl_device *ddev) |
| { |
| struct device *dev = &ddev->dev; |
| struct m10bmc_pmci_device *pmci; |
| struct indirect_ctx *ctx; |
| int ret; |
| |
| pmci = devm_kzalloc(dev, sizeof(*pmci), GFP_KERNEL); |
| if (!pmci) |
| return -ENOMEM; |
| |
| pmci->m10bmc.flash_bulk_ops = &m10bmc_pmci_flash_bulk_ops; |
| pmci->m10bmc.dev = dev; |
| |
| pmci->base = devm_ioremap_resource(dev, &ddev->mmio_res); |
| if (IS_ERR(pmci->base)) |
| return PTR_ERR(pmci->base); |
| |
| ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| mutex_init(&pmci->flash_mutex); |
| |
| ctx->base = pmci->base + M10BMC_N6000_INDIRECT_BASE; |
| ctx->dev = dev; |
| indirect_clear_cmd(ctx); |
| pmci->m10bmc.regmap = devm_regmap_init(dev, NULL, ctx, &m10bmc_pmci_regmap_config); |
| |
| if (IS_ERR(pmci->m10bmc.regmap)) { |
| ret = PTR_ERR(pmci->m10bmc.regmap); |
| goto destroy_mutex; |
| } |
| |
| ret = m10bmc_dev_init(&pmci->m10bmc, &m10bmc_pmci_n6000); |
| if (ret) |
| goto destroy_mutex; |
| return 0; |
| |
| destroy_mutex: |
| mutex_destroy(&pmci->flash_mutex); |
| return ret; |
| } |
| |
| static void m10bmc_pmci_remove(struct dfl_device *ddev) |
| { |
| struct intel_m10bmc *m10bmc = dev_get_drvdata(&ddev->dev); |
| struct m10bmc_pmci_device *pmci = container_of(m10bmc, struct m10bmc_pmci_device, m10bmc); |
| |
| mutex_destroy(&pmci->flash_mutex); |
| } |
| |
| #define FME_FEATURE_ID_M10BMC_PMCI 0x12 |
| |
| static const struct dfl_device_id m10bmc_pmci_ids[] = { |
| { FME_ID, FME_FEATURE_ID_M10BMC_PMCI }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(dfl, m10bmc_pmci_ids); |
| |
| static struct dfl_driver m10bmc_pmci_driver = { |
| .drv = { |
| .name = "intel-m10-bmc", |
| .dev_groups = m10bmc_dev_groups, |
| }, |
| .id_table = m10bmc_pmci_ids, |
| .probe = m10bmc_pmci_probe, |
| .remove = m10bmc_pmci_remove, |
| }; |
| |
| module_dfl_driver(m10bmc_pmci_driver); |
| |
| MODULE_DESCRIPTION("MAX10 BMC PMCI-based interface"); |
| MODULE_AUTHOR("Intel Corporation"); |
| MODULE_LICENSE("GPL"); |
| MODULE_IMPORT_NS(INTEL_M10_BMC_CORE); |