// SPDX-License-Identifier: GPL-2.0 /* * FPGA Freeze Bridge Controller * * Copyright (C) 2016 Altera Corporation. All rights reserved. */ #include <linux/delay.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/of_device.h> #include <linux/module.h> #include <linux/fpga/fpga-bridge.h> #define FREEZE_CSR_STATUS_OFFSET 0 #define FREEZE_CSR_CTRL_OFFSET 4 #define FREEZE_CSR_ILLEGAL_REQ_OFFSET 8 #define FREEZE_CSR_REG_VERSION 12 #define FREEZE_CSR_SUPPORTED_VERSION 2 #define FREEZE_CSR_OFFICIAL_VERSION 0xad000003 #define FREEZE_CSR_STATUS_FREEZE_REQ_DONE BIT(0) #define FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE BIT(1) #define FREEZE_CSR_CTRL_FREEZE_REQ BIT(0) #define FREEZE_CSR_CTRL_RESET_REQ BIT(1) #define FREEZE_CSR_CTRL_UNFREEZE_REQ BIT(2) #define FREEZE_BRIDGE_NAME "freeze" struct altera_freeze_br_data { struct device *dev; void __iomem *base_addr; bool enable; }; /* * Poll status until status bit is set or we have a timeout. */ static int altera_freeze_br_req_ack(struct altera_freeze_br_data *priv, u32 timeout, u32 req_ack) { struct device *dev = priv->dev; void __iomem *csr_illegal_req_addr = priv->base_addr + FREEZE_CSR_ILLEGAL_REQ_OFFSET; u32 status, illegal, ctrl; int ret = -ETIMEDOUT; do { illegal = readl(csr_illegal_req_addr); if (illegal) { dev_err(dev, "illegal request detected 0x%x", illegal); writel(1, csr_illegal_req_addr); illegal = readl(csr_illegal_req_addr); if (illegal) dev_err(dev, "illegal request not cleared 0x%x", illegal); ret = -EINVAL; break; } status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); dev_dbg(dev, "%s %x %x\n", __func__, status, req_ack); status &= req_ack; if (status) { ctrl = readl(priv->base_addr + FREEZE_CSR_CTRL_OFFSET); dev_dbg(dev, "%s request %x acknowledged %x %x\n", __func__, req_ack, status, ctrl); ret = 0; break; } udelay(1); } while (timeout--); if (ret == -ETIMEDOUT) dev_err(dev, "%s timeout waiting for 0x%x\n", __func__, req_ack); return ret; } static int altera_freeze_br_do_freeze(struct altera_freeze_br_data *priv, u32 timeout) { struct device *dev = priv->dev; void __iomem *csr_ctrl_addr = priv->base_addr + FREEZE_CSR_CTRL_OFFSET; u32 status; int ret; status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); if (status & FREEZE_CSR_STATUS_FREEZE_REQ_DONE) { dev_dbg(dev, "%s bridge already disabled %d\n", __func__, status); return 0; } else if (!(status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE)) { dev_err(dev, "%s bridge not enabled %d\n", __func__, status); return -EINVAL; } writel(FREEZE_CSR_CTRL_FREEZE_REQ, csr_ctrl_addr); ret = altera_freeze_br_req_ack(priv, timeout, FREEZE_CSR_STATUS_FREEZE_REQ_DONE); if (ret) writel(0, csr_ctrl_addr); else writel(FREEZE_CSR_CTRL_RESET_REQ, csr_ctrl_addr); return ret; } static int altera_freeze_br_do_unfreeze(struct altera_freeze_br_data *priv, u32 timeout) { struct device *dev = priv->dev; void __iomem *csr_ctrl_addr = priv->base_addr + FREEZE_CSR_CTRL_OFFSET; u32 status; int ret; writel(0, csr_ctrl_addr); status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) { dev_dbg(dev, "%s bridge already enabled %d\n", __func__, status); return 0; } else if (!(status & FREEZE_CSR_STATUS_FREEZE_REQ_DONE)) { dev_err(dev, "%s bridge not frozen %d\n", __func__, status); return -EINVAL; } writel(FREEZE_CSR_CTRL_UNFREEZE_REQ, csr_ctrl_addr); ret = altera_freeze_br_req_ack(priv, timeout, FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE); status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); writel(0, csr_ctrl_addr); return ret; } /* * enable = 1 : allow traffic through the bridge * enable = 0 : disable traffic through the bridge */ static int altera_freeze_br_enable_set(struct fpga_bridge *bridge, bool enable) { struct altera_freeze_br_data *priv = bridge->priv; struct fpga_image_info *info = bridge->info; u32 timeout = 0; int ret; if (enable) { if (info) timeout = info->enable_timeout_us; ret = altera_freeze_br_do_unfreeze(bridge->priv, timeout); } else { if (info) timeout = info->disable_timeout_us; ret = altera_freeze_br_do_freeze(bridge->priv, timeout); } if (!ret) priv->enable = enable; return ret; } static int altera_freeze_br_enable_show(struct fpga_bridge *bridge) { struct altera_freeze_br_data *priv = bridge->priv; return priv->enable; } static const struct fpga_bridge_ops altera_freeze_br_br_ops = { .enable_set = altera_freeze_br_enable_set, .enable_show = altera_freeze_br_enable_show, }; static const struct of_device_id altera_freeze_br_of_match[] = { { .compatible = "altr,freeze-bridge-controller", }, {}, }; MODULE_DEVICE_TABLE(of, altera_freeze_br_of_match); static int altera_freeze_br_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; void __iomem *base_addr; struct altera_freeze_br_data *priv; struct fpga_bridge *br; struct resource *res; u32 status, revision; if (!np) return -ENODEV; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base_addr = devm_ioremap_resource(dev, res); if (IS_ERR(base_addr)) return PTR_ERR(base_addr); revision = readl(base_addr + FREEZE_CSR_REG_VERSION); if ((revision != FREEZE_CSR_SUPPORTED_VERSION) && (revision != FREEZE_CSR_OFFICIAL_VERSION)) { dev_err(dev, "%s unexpected revision 0x%x != 0x%x != 0x%x\n", __func__, revision, FREEZE_CSR_SUPPORTED_VERSION, FREEZE_CSR_OFFICIAL_VERSION); return -EINVAL; } priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; status = readl(base_addr + FREEZE_CSR_STATUS_OFFSET); if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) priv->enable = 1; priv->base_addr = base_addr; br = devm_fpga_bridge_create(dev, FREEZE_BRIDGE_NAME, &altera_freeze_br_br_ops, priv); if (!br) return -ENOMEM; platform_set_drvdata(pdev, br); return fpga_bridge_register(br); } static int altera_freeze_br_remove(struct platform_device *pdev) { struct fpga_bridge *br = platform_get_drvdata(pdev); fpga_bridge_unregister(br); return 0; } static struct platform_driver altera_freeze_br_driver = { .probe = altera_freeze_br_probe, .remove = altera_freeze_br_remove, .driver = { .name = "altera_freeze_br", .of_match_table = of_match_ptr(altera_freeze_br_of_match), }, }; module_platform_driver(altera_freeze_br_driver); MODULE_DESCRIPTION("Altera Freeze Bridge"); MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); MODULE_LICENSE("GPL v2");