From patchwork Thu Jul 6 10:24:22 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Neil Armstrong X-Patchwork-Id: 9828017 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 10B7F602BD for ; Thu, 6 Jul 2017 10:40:34 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 00FB128628 for ; Thu, 6 Jul 2017 10:40:34 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id E90722861F; Thu, 6 Jul 2017 10:40:33 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 29C8928628 for ; Thu, 6 Jul 2017 10:40:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=EZ695LUHQLHGdP0svj1+PhnGXTu2Ss4bbPZEAp1wQm0=; b=IqicqhOsrBdo0UDeJtSt7cLmDC SJpNskvGmVd8cfxrISB9Wx/WLdf6plFGj5a8yqqkfOpR2g8dLaD+I/rVhz59KDtBLXUA7OgYlYC+H X2wNkczBNtYx2a2iE3q2KCo4Uc3ouTfranj1A917LyasfMNNEVyA8+wcdBqILaZp14zj0HwK96mEl VqykuKGO75aPwFpQIPY+RyUbrx32xQxAhTZtJxLL+JiHkztxNt4lDf8N3PusabrDA1MsnFjN1dl2G 9Uo0BSIJYrZ8gmpuN5upVDx55xGuc/00jZGJAOYk7quvVfCKDgHhyg4ZK0VM2MgdE1FLn2sTpFBtj 4pmraMag==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1dT4Cy-0004QO-3Z; Thu, 06 Jul 2017 10:40:28 +0000 Received: from mail-wr0-x231.google.com ([2a00:1450:400c:c0c::231]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1dT3xz-00036s-Hd for linux-amlogic@lists.infradead.org; Thu, 06 Jul 2017 10:25:04 +0000 Received: by mail-wr0-x231.google.com with SMTP id c11so22536803wrc.3 for ; Thu, 06 Jul 2017 03:24:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=lLksE+SV2dXZGVbB63+x+D4fXmtPPyZdYZCkQeZ+Vqo=; b=wpan01mP19cx3Xky5ALZs1d5KkjsYqGaZ6m8/PTPR7tSVodPoHeIPe2g00JVeF4mpD 7Y7oyO4dYRDVVYn+f6LmVGt42bAxy4EVbubCCDLkjyOr4ewxoc+EVtfgUGEzZvKTLKr2 xLcSmVoOtWuYiJu7NGg0gS3OGuZ5weEL3hgoDgJvW6bhmS+M1YFLS13YQSMSTPHsqD4r Dzw5FLu4+HL8ClYnth1pJHKHSzGFJvm2SRtZ2hNj7b3epczY7aKZeOCqGxeCaVCnOy60 JjWmby01mdUh9B1fkSGQAObhsLF6BJVnY2p/CP0tJh0SVFH3rMX1/gdZH7XGDAj02fUn q1Tw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=lLksE+SV2dXZGVbB63+x+D4fXmtPPyZdYZCkQeZ+Vqo=; b=imDvYr4GwJraaO/FsrUPcUpdRIT/pbiB6dMmEAX61a928MwknBH6AQHzYeLR87iEyg iimiyNTA5t57XnO3tFZ1LFrwM9hpi8jWZlAVIP2IuIX4ov2Y9gWmN53I3lsdcOF/HvmM cnQ/eQLaWXqAenXRV+5CuagjvEWG29g6WuSC7Ss/Kt/IBTYahR7q5crzE/aherdEYKkj 76AgL8duJ3v11LbHbMpivLiZRN1cZ8eaHFK6/rKHzXNdRjPAjAT0IsWENtcJattwzHOv HsRGzFew6d3DsxPGfvN1HttYfZzznJHKdo3vccPPncg4P/Wo4DzBq1kjbnchCm4DpjQR rxEA== X-Gm-Message-State: AKS2vOwicB0V60U2ke7rLcQQIMWOv3kc4LrfcXYwLka949lsrK3IgLfA ze8YLtE8KM0PYh5G X-Received: by 10.223.157.4 with SMTP id k4mr45529300wre.43.1499336677518; Thu, 06 Jul 2017 03:24:37 -0700 (PDT) Received: from localhost.localdomain ([90.63.244.31]) by smtp.gmail.com with ESMTPSA id w197sm31936286wme.20.2017.07.06.03.24.35 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 06 Jul 2017 03:24:35 -0700 (PDT) From: Neil Armstrong To: jbrunet@baylibre.com, narmstrong@baylibre.com Subject: [PATCH 2/3] clk: meson: gxbb-aoclk: Add CEC 32k clock Date: Thu, 6 Jul 2017 12:24:22 +0200 Message-Id: <1499336663-23875-3-git-send-email-narmstrong@baylibre.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1499336663-23875-1-git-send-email-narmstrong@baylibre.com> References: <1499336663-23875-1-git-send-email-narmstrong@baylibre.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170706_032459_897980_EFB76438 X-CRM114-Status: GOOD ( 21.13 ) X-BeenThere: linux-amlogic@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-amlogic@lists.infradead.org, linux-clk@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org MIME-Version: 1.0 Sender: "linux-amlogic" Errors-To: linux-amlogic-bounces+patchwork-linux-amlogic=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP The CEC 32K AO Clock is a dual divider with dual counter to provide a more precise 32768Hz clock for the CEC subsystem from the external xtal. The AO clocks management registers are spread among the AO register space, so this patch also adds management of these registers mappings then uses them for the CEC 32K AO clock management. Signed-off-by: Neil Armstrong --- drivers/clk/meson/Makefile | 2 +- drivers/clk/meson/gxbb-aoclk-32k.c | 188 +++++++++++++++++++++++++++++++++++++ drivers/clk/meson/gxbb-aoclk.c | 59 ++++++++++-- drivers/clk/meson/gxbb-aoclk.h | 23 +++++ 4 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 drivers/clk/meson/gxbb-aoclk-32k.c create mode 100644 drivers/clk/meson/gxbb-aoclk.h diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index 83b6d9d..e315057 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o -obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o +obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o gxbb-aoclk-32k.o diff --git a/drivers/clk/meson/gxbb-aoclk-32k.c b/drivers/clk/meson/gxbb-aoclk-32k.c new file mode 100644 index 0000000..c37c76a --- /dev/null +++ b/drivers/clk/meson/gxbb-aoclk-32k.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Neil Armstrong + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include "gxbb-aoclk.h" + +/* + * The AO Domain embeds a dual/divider to generate a more precise + * 32,768KHz clock for low-power suspend mode and CEC. + */ + +#define AO_RTC_ALT_CLK_CNTL0 0x0 +#define AO_RTC_ALT_CLK_CNTL1 0x4 +#define AO_CRT_CLK_CNTL1 0x0 +#define AO_RTI_PWR_CNTL_REG0 0x4 + +#define CLK_CNTL0_N1_MASK GENMASK(11, 0) +#define CLK_CNTL0_N2_MASK GENMASK(23, 12) +#define CLK_CNTL0_DUALDIV_EN BIT(28) +#define CLK_CNTL0_OUT_GATE_EN BIT(30) +#define CLK_CNTL0_IN_GATE_EN BIT(31) + +#define CLK_CNTL1_M1_MASK GENMASK(11, 0) +#define CLK_CNTL1_M2_MASK GENMASK(23, 12) +#define CLK_CNTL1_BYPASS_EN BIT(24) +#define CLK_CNTL1_SELECT_OSC BIT(27) + +#define PWR_CNTL_ALT_32K_SEL GENMASK(13, 10) + +struct cec_32k_freq_table { + unsigned long parent_rate; + unsigned long target_rate; + bool dualdiv; + unsigned int n1; + unsigned int n2; + unsigned int m1; + unsigned int m2; +}; + +static const struct cec_32k_freq_table aoclk_cec_32k_table[] = { + [0] = { + .parent_rate = 24000000, + .target_rate = 32768, + .dualdiv = true, + .n1 = 733, + .n2 = 732, + .m1 = 8, + .m2 = 11, + }, +}; + +/* + * If CLK_CNTL0_DUALDIV_EN == 0 + * - will use N1 divider only + * If CLK_CNTL0_DUALDIV_EN == 1 + * - hold M1 cycles of N1 divider then changes to N2 + * - hold M2 cycles of N2 divider then changes to N1 + * Then we can get more accurate division. + */ +static unsigned long aoclk_cec_32k_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw); + unsigned long n1; + u32 reg0, reg1; + + reg0 = readl_relaxed(cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL0); + reg1 = readl_relaxed(cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL1); + + if (reg1 & CLK_CNTL1_BYPASS_EN) + return parent_rate; + + if (reg0 & CLK_CNTL0_DUALDIV_EN) { + unsigned long n2, m1, m2, f1, f2, p1, p2; + + n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1; + n2 = FIELD_GET(CLK_CNTL0_N2_MASK, reg0) + 1; + + m1 = FIELD_GET(CLK_CNTL1_M1_MASK, reg1) + 1; + m2 = FIELD_GET(CLK_CNTL1_M2_MASK, reg1) + 1; + + f1 = DIV_ROUND_CLOSEST(parent_rate, n1); + f2 = DIV_ROUND_CLOSEST(parent_rate, n2); + + p1 = DIV_ROUND_CLOSEST(100000000 * m1, f1 * (m1 + m2)); + p2 = DIV_ROUND_CLOSEST(100000000 * m2, f2 * (m1 + m2)); + + return DIV_ROUND_UP(100000000, p1 + p2); + } + + n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1; + + return DIV_ROUND_CLOSEST(parent_rate, n1); +} + +static const struct cec_32k_freq_table *find_cec_32k_freq(unsigned long rate, + unsigned long prate) +{ + int i; + + for (i = 0 ; i < ARRAY_SIZE(aoclk_cec_32k_table) ; ++i) + if (aoclk_cec_32k_table[i].parent_rate == prate && + aoclk_cec_32k_table[i].target_rate == rate) + return &aoclk_cec_32k_table[i]; + + return NULL; +} + +static long aoclk_cec_32k_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate, + *prate); + + /* If invalid return first one */ + if (!freq) + return freq[0].target_rate; + + return freq->target_rate; +} + +static int aoclk_cec_32k_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate, + parent_rate); + struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw); + u32 reg = 0; + + if (!freq) + return -EINVAL; + + /* Disable clock */ + reg = readl(cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL0); + reg &= ~(CLK_CNTL0_IN_GATE_EN | CLK_CNTL0_OUT_GATE_EN); + writel(reg, cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL0); + + if (freq->dualdiv) + reg = CLK_CNTL0_DUALDIV_EN | + FIELD_PREP(CLK_CNTL0_N1_MASK, freq->n1 - 1) | + FIELD_PREP(CLK_CNTL0_N2_MASK, freq->n2 - 1); + else + reg = FIELD_PREP(CLK_CNTL0_N1_MASK, freq->n1 - 1); + + writel_relaxed(reg, cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL0); + + if (freq->dualdiv) + reg = FIELD_PREP(CLK_CNTL1_M1_MASK, freq->m1 - 1) | + FIELD_PREP(CLK_CNTL1_M2_MASK, freq->m2 - 1); + else + reg = FIELD_PREP(CLK_CNTL1_M1_MASK, freq->m1 - 1); + + writel_relaxed(reg, cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL1); + + /* Enable clock */ + reg = readl(cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL0); + reg |= CLK_CNTL0_IN_GATE_EN; + writel(reg, cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL0); + + udelay(200); + + reg |= CLK_CNTL0_OUT_GATE_EN; + writel(reg, cec_32k->rtc_base + AO_RTC_ALT_CLK_CNTL0); + + reg = readl(cec_32k->crt_base + AO_CRT_CLK_CNTL1); + reg |= CLK_CNTL1_SELECT_OSC; /* select cts_rtc_oscin_clk */ + writel(reg, cec_32k->crt_base + AO_CRT_CLK_CNTL1); + + /* Select 32k from XTAL */ + regmap_write_bits(cec_32k->pwr_regmap, + AO_RTI_PWR_CNTL_REG0, + PWR_CNTL_ALT_32K_SEL, + FIELD_PREP(PWR_CNTL_ALT_32K_SEL, 4)); + + return 0; +} + +const struct clk_ops meson_aoclk_cec_32k_ops = { + .recalc_rate = aoclk_cec_32k_recalc_rate, + .round_rate = aoclk_cec_32k_round_rate, + .set_rate = aoclk_cec_32k_set_rate, +}; diff --git a/drivers/clk/meson/gxbb-aoclk.c b/drivers/clk/meson/gxbb-aoclk.c index b45c5fb..e47f021 100644 --- a/drivers/clk/meson/gxbb-aoclk.c +++ b/drivers/clk/meson/gxbb-aoclk.c @@ -56,9 +56,13 @@ #include #include #include +#include +#include #include +#include #include #include +#include "gxbb-aoclk.h" static DEFINE_SPINLOCK(gxbb_aoclk_lock); @@ -104,6 +108,17 @@ static int gxbb_aoclk_do_reset(struct reset_controller_dev *rcdev, GXBB_AO_GATE(uart2, 5); GXBB_AO_GATE(ir_blaster, 6); +static struct aoclk_cec_32k cec_32k_ao = { + .lock = &gxbb_aoclk_lock, + .hw.init = &(struct clk_init_data) { + .name = "cec_32k_ao", + .ops = &meson_aoclk_cec_32k_ops, + .parent_names = (const char *[]){ "xtal" }, + .num_parents = 1, + .flags = CLK_IGNORE_UNUSED, + }, +}; + static unsigned int gxbb_aoclk_reset[] = { [RESET_AO_REMOTE] = 16, [RESET_AO_I2C_MASTER] = 18, @@ -130,28 +145,52 @@ static int gxbb_aoclk_do_reset(struct reset_controller_dev *rcdev, [CLKID_AO_UART1] = &uart1_ao.hw, [CLKID_AO_UART2] = &uart2_ao.hw, [CLKID_AO_IR_BLASTER] = &ir_blaster_ao.hw, + [CLKID_AO_CEC_32K] = &cec_32k_ao.hw, }, - .num = ARRAY_SIZE(gxbb_aoclk_gate), + .num = 7, }; static int gxbb_aoclkc_probe(struct platform_device *pdev) { - struct resource *res; + struct gxbb_aoclk_reset_controller *rstc; + struct device *dev = &pdev->dev; + struct regmap *regmap_pwr; + void __iomem *base_crt; + void __iomem *base_rtc; void __iomem *base; + struct resource *res; int ret, clkid; - struct device *dev = &pdev->dev; - struct gxbb_aoclk_reset_controller *rstc; rstc = devm_kzalloc(dev, sizeof(*rstc), GFP_KERNEL); if (!rstc) return -ENOMEM; /* Generic clocks */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aoclk"); base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) return PTR_ERR(base); + /* CRT base */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aocrt"); + base_crt = devm_ioremap_resource(dev, res); + if (IS_ERR(base_crt)) + return PTR_ERR(base_crt); + + /* RTC base */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aortc"); + base_rtc = devm_ioremap_resource(dev, res); + if (IS_ERR(base_rtc)) + return PTR_ERR(base_rtc); + + /* PWR regmap */ + regmap_pwr = syscon_regmap_lookup_by_phandle(dev->of_node, + "amlogic,pwr-ctrl"); + if (IS_ERR(regmap_pwr)) { + dev_err(dev, "failed to get PWR regmap\n"); + return -ENODEV; + } + /* Reset Controller */ rstc->base = base; rstc->data = gxbb_aoclk_reset; @@ -163,7 +202,7 @@ static int gxbb_aoclkc_probe(struct platform_device *pdev) /* * Populate base address and register all clks */ - for (clkid = 0; clkid < gxbb_aoclk_onecell_data.num; clkid++) { + for (clkid = 0; clkid < ARRAY_SIZE(gxbb_aoclk_gate); clkid++) { gxbb_aoclk_gate[clkid]->reg = base; ret = devm_clk_hw_register(dev, @@ -172,6 +211,14 @@ static int gxbb_aoclkc_probe(struct platform_device *pdev) return ret; } + /* Specific clocks */ + cec_32k_ao.crt_base = base_crt; + cec_32k_ao.rtc_base = base_rtc; + cec_32k_ao.pwr_regmap = regmap_pwr; + ret = devm_clk_hw_register(dev, &cec_32k_ao.hw); + if (ret) + return ret; + return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, &gxbb_aoclk_onecell_data); } diff --git a/drivers/clk/meson/gxbb-aoclk.h b/drivers/clk/meson/gxbb-aoclk.h new file mode 100644 index 0000000..5925a6b --- /dev/null +++ b/drivers/clk/meson/gxbb-aoclk.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017 BayLibre, SAS + * Author: Neil Armstrong + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __GXBB_AOCLKC_H +#define __GXBB_AOCLKC_H + +struct aoclk_cec_32k { + struct clk_hw hw; + void __iomem *crt_base; + void __iomem *rtc_base; + struct regmap *pwr_regmap; + spinlock_t *lock; +}; + +#define to_aoclk_cec_32k(_hw) container_of(_hw, struct aoclk_cec_32k, hw) + +extern const struct clk_ops meson_aoclk_cec_32k_ops; + +#endif /* __GXBB_AOCLKC_H */