From patchwork Mon Apr 27 22:30:42 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joachim Eastwood X-Patchwork-Id: 6283791 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id EC0DDBEEE1 for ; Mon, 27 Apr 2015 22:34:10 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id BE5C920259 for ; Mon, 27 Apr 2015 22:34:09 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id ADF0C20270 for ; Mon, 27 Apr 2015 22:34:08 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YmrZK-0000S0-Ml; Mon, 27 Apr 2015 22:32:02 +0000 Received: from merlin.infradead.org ([2001:4978:20e::2]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1YmrYs-0000DS-Pt for linux-arm-kernel@bombadil.infradead.org; Mon, 27 Apr 2015 22:31:34 +0000 Received: from mail-lb0-x22d.google.com ([2a00:1450:4010:c04::22d]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1YmrYo-00074i-KU for linux-arm-kernel@lists.infradead.org; Mon, 27 Apr 2015 22:31:31 +0000 Received: by lbbuc2 with SMTP id uc2so93456433lbb.2 for ; Mon, 27 Apr 2015 15:31:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=j7o9+REgsI44tELBYmnVlErBjvVxrM4HZTQcTgC4+Io=; b=RvO+7U2j2pCW4SZHx5tCZcgRWrzG/igBMlJVoOTHukTAPp4c8bz8MtsBFURJ00hest 3t9d5lusD/j0fDfwltCn7kXj3MsaksRDg6+dheieBGdkGmnHH3iDa4Z6w7lyJtrVJCnU pA5WRDXRgkmAnvbiCrad8DkRCIkfP5/ITUT9eRuly+qMuPHqZr2qJ1EvOfzT2ID9V6Op gyIqi1sGkivx+nzizCEA+Kmsqa3RaNEnbXzxBGCGjpRVgMotIHLYXU8t6M/vMd0LmWMN VaQQwHSG9l9eQb040srq3N13+DGVs7M+GuZtmkvi9sHxIIvmNwPoQ3iQ9+/2IenY4jaa h0Dw== X-Received: by 10.152.87.70 with SMTP id v6mr11686432laz.30.1430173865277; Mon, 27 Apr 2015 15:31:05 -0700 (PDT) Received: from localhost.localdomain ([90.149.48.183]) by mx.google.com with ESMTPSA id yq18sm5086204lbb.47.2015.04.27.15.31.04 (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Mon, 27 Apr 2015 15:31:04 -0700 (PDT) From: Joachim Eastwood To: p.zabel@pengutronix.de, 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 Message-Id: <1430173843-11648-2-git-send-email-manabian@gmail.com> X-Mailer: git-send-email 1.8.0 In-Reply-To: <1430173843-11648-1-git-send-email-manabian@gmail.com> References: <1430173843-11648-1-git-send-email-manabian@gmail.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150427_183131_166611_B78926F6 X-CRM114-Status: GOOD ( 21.70 ) X-Spam-Score: -2.7 (--) Cc: Joachim Eastwood , ezequiel@vanguardiasur.com.ar, arnd@arndb.de, ariel.dalessandro@gmail.com X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.1 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, T_DKIM_INVALID, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Joachim Eastwood --- 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* 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 "); +MODULE_DESCRIPTION("Reset driver for LPC18xx/43xx RGU"); +MODULE_LICENSE("GPL v2");