[RFC 1/2] spi: aspeed: ASPEED 24XX/25XX SPI1 disable/passthrough/master mode control

Kun Yi kunyi at google.com
Sat Oct 22 11:45:57 AEDT 2016


Simple driver that exposes userspace sysfs to control SPI1 mode. It also
optionally override initial SPI mode if given "start_mode" parameter in
the device tree.

Signed-off-by: Kun Yi <kunyi at google.com>
---
 drivers/misc/Kconfig           |   6 ++
 drivers/misc/Makefile          |   1 +
 drivers/misc/aspeed-spi-mode.c | 191 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 198 insertions(+)
 create mode 100644 drivers/misc/aspeed-spi-mode.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 4617ddc..ea9b713 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -809,6 +809,12 @@ config ASPEED_BT_IPMI_HOST
 	help
 	  Support for the Aspeed BT ipmi host.
 
+config SPI_MODECTRL_ASPEED
+        tristate "Support for SPI1 host flash mode change on Aspeed 24xx/25xx"
+        ---help---
+          Support for the Aspeed 24xx/25xx sysfs control toggle between
+	  disabled/master/pass-through modes.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 724861b..0cce3d6 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -58,3 +58,4 @@ obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
 obj-$(CONFIG_PANEL)             += panel.o
 obj-$(CONFIG_ASPEED_BT_IPMI_HOST)	+= bt-host.o
+obj-$(CONFIG_SPI_MODECTRL_ASPEED)	+= aspeed-spi-mode.o
diff --git a/drivers/misc/aspeed-spi-mode.c b/drivers/misc/aspeed-spi-mode.c
new file mode 100644
index 0000000..20f7754
--- /dev/null
+++ b/drivers/misc/aspeed-spi-mode.c
@@ -0,0 +1,191 @@
+/*
+ * ASPEED host flash SPI1 mode controller driver.
+ *
+ * Copyright (c) 2016 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_platform.h>
+
+struct aspeed_spi_mode_controller {
+	struct mutex mutex;			/* controller access mutex */
+	void __iomem *hwstrap_regs;		/* controller registers */
+	void __iomem *hwstrap_reset;		/* scu reset registers */
+};
+
+/* SPI mode definitions. MODE_DEBUG is reserved on AST2500. */
+#define SPI_MODE_DISABLED	0
+#define SPI_MODE_MASTER		1
+#define SPI_MODE_DEBUG		2
+#define SPI_MODE_PASSTHROUGH	3
+/* SPI mode control bits in HW strap register */
+#define SPI_MODE_OFFSET		(12)
+#define SPI_MODE_MASK		(BIT(12) | BIT(13))
+/* Extract SPI mode from register value */
+#define SPI_MODE(reg)		((reg & SPI_MODE_MASK) >> SPI_MODE_OFFSET)
+
+static int aspeed_get_spi_mode(struct aspeed_spi_mode_controller *controller)
+{
+	u32 reg;
+
+	mutex_lock(&controller->mutex);
+	reg = readl(controller->hwstrap_regs);
+	mutex_unlock(&controller->mutex);
+
+	return SPI_MODE(reg);
+}
+
+/*
+ * Set SPI1 interface to given mode.
+ * Note SCU70 reg bits cannot be set to 0. Need to write to SCU7C in order to
+ * clear bits in SCU70.
+ */
+static int aspeed_set_spi_mode(struct aspeed_spi_mode_controller *controller,
+			       unsigned int mode)
+{
+
+	if (mode != SPI_MODE_DISABLED &&
+	    mode != SPI_MODE_MASTER &&
+	    mode != SPI_MODE_PASSTHROUGH) {
+		return -EPERM;
+	}
+
+	void __iomem *regs;
+	void __iomem *regs_reset;
+	u32 val;
+
+	regs = controller->hwstrap_regs;
+	regs_reset = controller->hwstrap_reset;
+	mutex_lock(&controller->mutex);
+	val = readl(regs);
+
+	if (SPI_MODE(val) == mode)
+		goto finish;
+
+	/* Reset bit 13:12 and set to new value */
+	writel(SPI_MODE_MASK, regs_reset);
+	val &= ~SPI_MODE_MASK;
+	val |= (mode << SPI_MODE_OFFSET);
+	writel(val, regs);
+
+	pr_debug("HW strap register set to 0x%x\n", readl(regs));
+
+finish:
+	mutex_unlock(&controller->mutex);
+	return 0;
+}
+
+static ssize_t aspeed_spi_mode_show(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct aspeed_spi_mode_controller *controller;
+
+	controller = dev_get_drvdata(dev);
+	return scnprintf(buf, PAGE_SIZE, "%d\n",
+			 aspeed_get_spi_mode(controller));
+}
+
+static ssize_t aspeed_spi_mode_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct aspeed_spi_mode_controller *controller = dev_get_drvdata(dev);
+	unsigned long mode;
+	int err;
+
+	err = kstrtoul(buf, 10, &mode);
+	if (err)
+		return err;
+
+	err = aspeed_set_spi_mode(controller, mode);
+
+	if (err) {
+		dev_err(dev, "Support disabled(0) master(1) pass-through(3)");
+		return err;
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR(spi_mode, S_IRUSR | S_IWUSR, aspeed_spi_mode_show,
+		   aspeed_spi_mode_store);
+
+
+static const struct of_device_id aspeed_spi_mode_match[] = {
+	{ .compatible = "aspeed,aspeed_spi_mode" },
+	{},
+}
+MODULE_DEVICE_TABLE(of, aspeed_spi_mode_match);
+
+static int aspeed_spi_mode_probe(struct platform_device *pdev)
+{
+	struct aspeed_spi_mode_controller *controller;
+	struct resource *r;
+	void __iomem *regs, *regs_reset;
+	u32 mode;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(&pdev->dev, r);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	regs_reset = devm_ioremap_resource(&pdev->dev, r);
+	if (IS_ERR(regs_reset))
+		return PTR_ERR(regs_reset);
+
+	controller = devm_kzalloc(&pdev->dev, sizeof(*controller), GFP_KERNEL);
+	if (!controller)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, controller);
+	controller->hwstrap_regs = regs;
+	controller->hwstrap_reset = regs_reset;
+	mutex_init(&controller->mutex);
+
+	if (!of_property_read_u32(pdev->dev.of_node, "start_mode", &mode)) {
+		int err;
+
+		err = aspeed_set_spi_mode(controller, mode);
+		if (!err)
+			dev_info(&pdev->dev, "Set SPI mode to %d\n", mode);
+	}
+
+	if (device_create_file(&pdev->dev, &dev_attr_spi_mode) != 0)
+		dev_err(&pdev->dev, "Sysfs attribute creation failed");
+
+	dev_info(&pdev->dev, "ASPEED spi mode controller initialized");
+
+	return 0;
+}
+
+static int aspeed_spi_mode_remove(struct platform_device *pdev)
+{
+	device_remove_file(&pdev->dev, &dev_attr_spi_mode);
+	return 0;
+}
+
+static struct platform_driver aspeed_spi_mode_driver = {
+	.probe = aspeed_spi_mode_probe,
+	.remove = aspeed_spi_mode_remove,
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.of_match_table = aspeed_spi_mode_match,
+	}
+};
+
+module_platform_driver(aspeed_spi_mode_driver);
+
+MODULE_AUTHOR("Kun Yi <kunyi at google.com>");
+MODULE_DESCRIPTION("ASPEED SPI mode controller");
+MODULE_LICENSE("GPL v2");
-- 
2.8.0.rc3.226.g39d4020



More information about the openbmc mailing list