All of lore.kernel.org
 help / color / mirror / Atom feed
From: manabian@gmail.com (Joachim Eastwood)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 1/2] reset: add driver for lpc18xx rgu
Date: Tue, 28 Apr 2015 00:30:42 +0200	[thread overview]
Message-ID: <1430173843-11648-2-git-send-email-manabian@gmail.com> (raw)
In-Reply-To: <1430173843-11648-1-git-send-email-manabian@gmail.com>

Signed-off-by: Joachim Eastwood <manabian@gmail.com>
---
 drivers/reset/Makefile        |   1 +
 drivers/reset/reset-lpc18xx.c | 260 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 261 insertions(+)
 create mode 100644 drivers/reset/reset-lpc18xx.c

diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 157d421f755b..1d41feeb2dce 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -1,4 +1,5 @@
 obj-$(CONFIG_RESET_CONTROLLER) += core.o
+obj-$(CONFIG_ARCH_LPC18XX) += reset-lpc18xx.o
 obj-$(CONFIG_ARCH_SOCFPGA) += reset-socfpga.o
 obj-$(CONFIG_ARCH_BERLIN) += reset-berlin.o
 obj-$(CONFIG_ARCH_SUNXI) += reset-sunxi.o
diff --git a/drivers/reset/reset-lpc18xx.c b/drivers/reset/reset-lpc18xx.c
new file mode 100644
index 000000000000..9564d78ab9d2
--- /dev/null
+++ b/drivers/reset/reset-lpc18xx.c
@@ -0,0 +1,260 @@
+/*
+ * Reset driver for NXP LPC18xx/43xx Reset Generation Unit (RGU).
+ *
+ * Copyright (C) 2014 Joachim Eastwood <manabian@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/system_misc.h>
+
+/* LPC18xx RGU registers */
+#define LPC18XX_RGU_CTRL0		0x100
+#define LPC18XX_RGU_CTRL1		0x104
+#define LPC18XX_RGU_ACTIVE_STATUS0	0x150
+#define LPC18XX_RGU_ACTIVE_STATUS1	0x154
+
+#define LPC18XX_RGU_RESETS_PER_REG	32
+
+/* Internal reset outputs */
+#define LPC18XX_RGU_CORE_RST	0
+#define LPC18XX_RGU_M0SUB_RST	12
+#define LPC18XX_RGU_M0APP_RST	56
+
+struct lpc18xx_rgu_data {
+	struct reset_controller_dev rcdev;
+	struct clk *clk_delay;
+	struct clk *clk_reg;
+	void __iomem *base;
+	spinlock_t lock;
+	u32 delay_us;
+};
+
+#define to_rgu_data(rcdev) container_of(rcdev, struct lpc18xx_rgu_data, rcdev)
+
+static void __iomem *rgu_base;
+
+static int lpc18xx_rgu_restart(struct notifier_block *this, unsigned long mode,
+			       void *cmd)
+{
+	writel(BIT(LPC18XX_RGU_CORE_RST), rgu_base + LPC18XX_RGU_CTRL0);
+	mdelay(2000);
+
+	pr_emerg("%s: unable to restart system\n", __func__);
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block lpc18xx_rgu_restart_nb = {
+	.notifier_call = lpc18xx_rgu_restart,
+	.priority = 192,
+};
+
+/*
+ * The LPC18xx RGU has mostly self-deasserting resets except for the
+ * two reset lines going to the internal Cortex-M0 cores.
+ *
+ * To prevent the M0 cores from accidentally getting deasserting the
+ * status register must be check and bits in control register set to
+ * preserve the state.
+ */
+static void lpc18xx_rgu_setclear_reset(struct reset_controller_dev *rcdev,
+				       unsigned long id, bool set)
+{
+	struct lpc18xx_rgu_data *rc = to_rgu_data(rcdev);
+	u32 stat_offset = LPC18XX_RGU_ACTIVE_STATUS0;
+	u32 ctrl_offset = LPC18XX_RGU_CTRL0;
+	unsigned long flags;
+	u32 stat, rst_bit;
+
+	stat_offset += (id / LPC18XX_RGU_RESETS_PER_REG) * sizeof(u32);
+	ctrl_offset += (id / LPC18XX_RGU_RESETS_PER_REG) * sizeof(u32);
+	rst_bit = 1 << (id % LPC18XX_RGU_RESETS_PER_REG);
+
+	spin_lock_irqsave(&rc->lock, flags);
+	stat = ~readl(rc->base + stat_offset);
+	if (set)
+		writel(stat | rst_bit, rc->base + ctrl_offset);
+	else
+		writel(stat & ~rst_bit, rc->base + ctrl_offset);
+	spin_unlock_irqrestore(&rc->lock, flags);
+}
+
+static int lpc18xx_rgu_reset(struct reset_controller_dev *rcdev,
+			     unsigned long id)
+{
+	struct lpc18xx_rgu_data *rc = to_rgu_data(rcdev);
+
+	lpc18xx_rgu_setclear_reset(rcdev, id, true);
+	udelay(rc->delay_us);
+
+	return 0;
+}
+
+static int lpc18xx_rgu_assert(struct reset_controller_dev *rcdev,
+			      unsigned long id)
+{
+	return lpc18xx_rgu_reset(rcdev, id);
+}
+
+/* Only M0 cores require explicit reset deassert */
+static int lpc18xx_rgu_deassert(struct reset_controller_dev *rcdev,
+				unsigned long id)
+{
+	switch (id) {
+	case LPC18XX_RGU_M0SUB_RST:
+	case LPC18XX_RGU_M0APP_RST:
+		lpc18xx_rgu_setclear_reset(rcdev, id, false);
+	}
+
+	return 0;
+}
+
+static int lpc18xx_rgu_status(struct reset_controller_dev *rcdev,
+			      unsigned long id)
+{
+	struct lpc18xx_rgu_data *rc = to_rgu_data(rcdev);
+	u32 bit, offset = LPC18XX_RGU_ACTIVE_STATUS0;
+
+	offset += (id / LPC18XX_RGU_RESETS_PER_REG) * sizeof(u32);
+	bit = 1 << (id % LPC18XX_RGU_RESETS_PER_REG);
+
+	return !(readl(rc->base + offset) & bit);
+}
+
+static struct reset_control_ops lpc18xx_rgu_ops = {
+	.reset		= lpc18xx_rgu_reset,
+	.assert		= lpc18xx_rgu_assert,
+	.deassert	= lpc18xx_rgu_deassert,
+	.status		= lpc18xx_rgu_status,
+};
+
+static int lpc18xx_rgu_probe(struct platform_device *pdev)
+{
+	struct lpc18xx_rgu_data *rc;
+	struct resource *res;
+	u32 fcclk, firc;
+	int ret;
+
+	rc = devm_kzalloc(&pdev->dev, sizeof(*rc), GFP_KERNEL);
+	if (!rc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	rc->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rc->base))
+		return PTR_ERR(rc->base);
+
+	rc->clk_reg = devm_clk_get(&pdev->dev, "reg");
+	if (IS_ERR(rc->clk_reg)) {
+		dev_err(&pdev->dev, "reg clock not found\n");
+		return PTR_ERR(rc->clk_reg);
+	}
+
+	rc->clk_delay = devm_clk_get(&pdev->dev, "delay");
+	if (IS_ERR(rc->clk_delay)) {
+		dev_err(&pdev->dev, "delay clock not found\n");
+		return PTR_ERR(rc->clk_delay);
+	}
+
+	ret = clk_prepare_enable(rc->clk_reg);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to enable reg clock\n");
+		return ret;
+	}
+
+	ret = clk_prepare_enable(rc->clk_delay);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to enable delay clock\n");
+		goto dis_clk_reg;
+	}
+
+	fcclk = clk_get_rate(rc->clk_reg) / USEC_PER_SEC;
+	firc = clk_get_rate(rc->clk_delay) / USEC_PER_SEC;
+	if (fcclk == 0 || firc == 0)
+		rc->delay_us = 2;
+	else
+		rc->delay_us = DIV_ROUND_UP(fcclk, firc * firc);
+
+	spin_lock_init(&rc->lock);
+
+	rc->rcdev.owner = THIS_MODULE;
+	rc->rcdev.nr_resets = 64;
+	rc->rcdev.ops = &lpc18xx_rgu_ops;
+	rc->rcdev.of_node = pdev->dev.of_node;
+
+	platform_set_drvdata(pdev, rc);
+
+	ret = reset_controller_register(&rc->rcdev);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to register device\n");
+		goto dis_clks;
+	}
+
+	rgu_base = rc->base;
+	ret = register_restart_handler(&lpc18xx_rgu_restart_nb);
+	if (ret)
+		dev_warn(&pdev->dev, "failed to register restart handler\n");
+
+	return 0;
+
+dis_clks:
+	clk_disable_unprepare(rc->clk_delay);
+dis_clk_reg:
+	clk_disable_unprepare(rc->clk_reg);
+
+	return ret;
+}
+
+static int lpc18xx_rgu_remove(struct platform_device *pdev)
+{
+	struct lpc18xx_rgu_data *rc = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = unregister_restart_handler(&lpc18xx_rgu_restart_nb);
+	if (ret)
+		dev_warn(&pdev->dev, "failed to unregister restart handler\n");
+
+	reset_controller_unregister(&rc->rcdev);
+
+	clk_disable_unprepare(rc->clk_delay);
+	clk_disable_unprepare(rc->clk_reg);
+
+	return 0;
+}
+
+static const struct of_device_id lpc18xx_rgu_match[] = {
+	{ .compatible = "nxp,lpc1850-rgu" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, lpc18xx_rgu_match);
+
+static struct platform_driver lpc18xx_rgu_driver = {
+	.probe	= lpc18xx_rgu_probe,
+	.remove	= lpc18xx_rgu_remove,
+	.driver	= {
+		.name		= "lpc18xx-reset",
+		.of_match_table	= lpc18xx_rgu_match,
+	},
+};
+module_platform_driver(lpc18xx_rgu_driver);
+
+MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
+MODULE_DESCRIPTION("Reset driver for LPC18xx/43xx RGU");
+MODULE_LICENSE("GPL v2");
-- 
1.8.0

  reply	other threads:[~2015-04-27 22:30 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-04-27 22:30 [PATCH 0/2] reset driver for NXP LPC18xx family Joachim Eastwood
2015-04-27 22:30 ` Joachim Eastwood [this message]
2015-05-04  8:00   ` [PATCH 1/2] reset: add driver for lpc18xx rgu Philipp Zabel
2015-05-04 21:29     ` Joachim Eastwood
2015-04-27 22:30 ` [PATCH 2/2] doc: dt: add documentation for lpc1850-rgu reset driver Joachim Eastwood
2015-05-04  8:51   ` Philipp Zabel
2015-05-04 21:41     ` Joachim Eastwood

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1430173843-11648-2-git-send-email-manabian@gmail.com \
    --to=manabian@gmail.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.