From patchwork Tue May 31 07:26:38 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jean-Francois Moine X-Patchwork-Id: 9143805 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 9BD6960761 for ; Tue, 31 May 2016 08:39:27 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9032627BF0 for ; Tue, 31 May 2016 08:39:27 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 84D4727D17; Tue, 31 May 2016 08:39:27 +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=-6.9 required=2.0 tests=BAYES_00,FREEMAIL_FROM, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DE37627BF0 for ; Tue, 31 May 2016 08:39:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1162077AbcEaIj0 (ORCPT ); Tue, 31 May 2016 04:39:26 -0400 Received: from smtp5-g21.free.fr ([212.27.42.5]:59642 "EHLO smtp5-g21.free.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1162056AbcEaIjX (ORCPT ); Tue, 31 May 2016 04:39:23 -0400 Received: from localhost (unknown [82.245.201.222]) by smtp5-g21.free.fr (Postfix) with ESMTP id B61475FFCC; Tue, 31 May 2016 10:41:34 +0200 (CEST) X-Mailbox-Line: From bbf3cefa47323660ead5ebdafe63f382fea06f39 Mon Sep 17 00:00:00 2001 Message-Id: In-Reply-To: References: From: Jean-Francois Moine Date: Tue, 31 May 2016 09:26:38 +0200 Subject: [PATCH RFC 1/2] clk: sunxi-ng: Add the A83T and A80 PLL clocks To: Emilio Lopez , Maxime Ripard , Chen-Yu Tsai Cc: Stephen Boyd , linux-arm-kernel@lists.infradead.org, linux-clk@vger.kernel.org, linux-sunxi@googlegroups.com Sender: linux-clk-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-clk@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The A83T and A80 SoCs have unique settings of their PLL clocks. Signed-off-by: Jean-Francois Moine --- drivers/clk/sunxi-ng/ccu_ndmp.c | 247 ++++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_ndmp.h | 45 ++++++++ 2 files changed, 292 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_ndmp.c create mode 100644 drivers/clk/sunxi-ng/ccu_ndmp.h diff --git a/drivers/clk/sunxi-ng/ccu_ndmp.c b/drivers/clk/sunxi-ng/ccu_ndmp.c new file mode 100644 index 0000000..079b155 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_ndmp.c @@ -0,0 +1,247 @@ +/* + * PLL clocks of sun8iw6 (A83T) and sun9iw1 (A80) + * + * Copyright (c) 2016 Jean-Francois Moine + * + * 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. + * + * The clock rates are computed as: + * rate = parent_rate / d1 * n / d2 / m >> p + */ + +#include +#include +#include + +#include "ccu_gate.h" +#include "ccu_ndmp.h" + +static void ccu_ndmp_disable(struct clk_hw *hw) +{ + struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw); + + return ccu_gate_helper_disable(&ndmp->common, ndmp->enable); +} + +static int ccu_ndmp_enable(struct clk_hw *hw) +{ + struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw); + + return ccu_gate_helper_enable(&ndmp->common, ndmp->enable); +} + +static int ccu_ndmp_is_enabled(struct clk_hw *hw) +{ + struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw); + + return ccu_gate_helper_is_enabled(&ndmp->common, ndmp->enable); +} + +static unsigned long ccu_ndmp_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw); + int n, d1, d2, m, p; + unsigned long rate; + u32 reg; + + reg = readl(ndmp->common.base + ndmp->common.reg); + + rate = parent_rate; + + if (ndmp->d1.shift) { + d1 = reg >> ndmp->d1.shift; + d1 &= (1 << ndmp->d1.width) - 1; + rate /= (d1 + 1); + } + + n = reg >> ndmp->n.shift; + n &= (1 << ndmp->n.width) - 1; + if (!(ndmp->common.features & CCU_FEATURE_N0)) + n++; + rate *= n; + + if (ndmp->d2.shift) { + d2 = reg >> ndmp->d2.shift; + d2 &= (1 << ndmp->d2.width) - 1; + rate /= (d2 + 1); + } + + if (ndmp->m.shift) { + m = reg >> ndmp->m.shift; + m &= (1 << ndmp->m.width) - 1; + rate /= (m + 1); + } + + if (ndmp->p.shift) { + p = reg >> ndmp->p.shift; + p &= (1 << ndmp->p.width) - 1; + rate >>= p; + } + + return rate; +} + +/* d1 and d2 may be only 1 or 2 */ +static int ccu_ndmp_get_fact(struct ccu_ndmp *ndmp, + unsigned long rate, unsigned long prate, + int *p_n, int *p_d1, int *p_d2, int *p_m, int *p_p) +{ + int n, d1, d2, m, p, d; + unsigned long t; + + /* m implies only n, d1, d2 and m (pll-audio) */ + /* Setting d1=1 and d2=2 keeps n and m small enough + * with error < 5/10000 */ + /* As only 2 rates are used, this could be simplified: + * 22579200Hz => n = 32, m = 17 + * 24576000Hz => n = 43, m = 21 + */ + if (ndmp->m.shift) { + long unsigned int lun, lum; + + d1 = 0 + 1; + d2 = 1 + 1; + t = prate / 2; + rational_best_approximation(rate, t, + 1 << ndmp->n.width, + 1 << ndmp->m.width, + &lun, &lum); + if (lum == 0) + return -EINVAL; + n = lun; + m = lum; + p = 0; + + /* no d1 implies n alone (pll-cxcpux) */ + } else if (!ndmp->d1.shift) { + d1 = d2 = 0 + 1; + n = rate / prate; + m = 1; + p = 0; + + /* p implies only n, d1 and p (pll-videox) */ + } else if (ndmp->m.shift) { + d2 = 0 + 1; + d = 2 + ndmp->p.width; + n = rate / (prate / (1 << d)); + if (n < 12) { + n *= 2; + d++; + } + while (n >= 12 * 2 && !(n & 1)) { + n /= 2; + if (--d == 0) + break; + } + if (d <= 1) { + d1 = d + 1; + p = 0; + } else { + d1 = 1 + 1; + p = d - 1; + } + m = 1; + + /* only n, d1 and d2 (other plls) */ + } else { + t = prate / 4; + n = rate / t; + if (n < 12) { + n *= 4; + d1 = d2 = 0 + 1; + } else if (n >= 12 * 2 && !(n & 1)) { + if (n >= 12 * 4 && !(n % 4)) { + n /= 4; + d1 = d2 = 0 + 1; + } else { + n /= 2; + d1 = 0 + 1; + d2 = 1 + 1; + } + } else { + d1 = d2 = 1 + 1; + } + if (n > (1 << ndmp->n.width)) + return -EINVAL; + m = 1; + p = 0; + } + + if (n < 12 || n > (1 << ndmp->n.width)) + return -EINVAL; + + *p_n = n; + *p_d1 = d1; + *p_d2 = d2; + *p_m = m; + *p_p = p; + + return 0; +} + +static long ccu_ndmp_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw); + int n, d1, d2, m, p, ret; + + ret = ccu_ndmp_get_fact(ndmp, rate, *parent_rate, + &n, &d1, &d2, &m, &p); + if (!ret) + return 0; + + return *parent_rate / d1 * n / d2 / m >> p; +} + +static int ccu_ndmp_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw); + unsigned long flags; + int n, d1, d2, m, p, ret; + u32 reg; + + ret = ccu_ndmp_get_fact(ndmp, rate, parent_rate, + &n, &d1, &d2, &m, &p); + if (!ret) + return ret; + if (!(ndmp->common.features & CCU_FEATURE_N0)) + n--; + + spin_lock_irqsave(ndmp->common.lock, flags); + + reg = readl(ndmp->common.base + ndmp->common.reg) & + ~((((1 << ndmp->n.width) - 1) << ndmp->n.shift) | + (((1 << ndmp->d1.width) - 1) << ndmp->d1.shift) | + (((1 << ndmp->d2.width) - 1) << ndmp->d2.shift) | + (((1 << ndmp->m.width) - 1) << ndmp->m.shift) | + (((1 << ndmp->p.width) - 1) << ndmp->p.shift)); + + writel(reg | (n << ndmp->n.shift) | + ((d1 - 1) << ndmp->d1.shift) | + ((d2 - 1) << ndmp->d2.shift) | + ((m - 1) << ndmp->m.shift) | + (p << ndmp->p.shift), + ndmp->common.base + ndmp->common.reg); + + spin_unlock_irqrestore(ndmp->common.lock, flags); + + WARN_ON(readl_relaxed_poll_timeout(ndmp->common.base + ndmp->reg_lock, + reg, !(reg & ndmp->lock), 50, 500)); + + return 0; +} + +const struct clk_ops ccu_ndmp_ops = { + .disable = ccu_ndmp_disable, + .enable = ccu_ndmp_enable, + .is_enabled = ccu_ndmp_is_enabled, + + .recalc_rate = ccu_ndmp_recalc_rate, + .round_rate = ccu_ndmp_round_rate, + .set_rate = ccu_ndmp_set_rate, +}; diff --git a/drivers/clk/sunxi-ng/ccu_ndmp.h b/drivers/clk/sunxi-ng/ccu_ndmp.h new file mode 100644 index 0000000..bb47127 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_ndmp.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016 Jean-Francois Moine + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CCU_NDMP_H_ +#define _CCU_NDMP_H_ + +#include + +#include "ccu_factor.h" +#include "ccu_common.h" + +struct ccu_ndmp { + u32 enable; + u32 lock; + int reg_lock; + + struct ccu_factor n; + struct ccu_factor d1; + struct ccu_factor d2; + struct ccu_factor m; + struct ccu_factor p; + + struct ccu_common common; +}; + +static inline struct ccu_ndmp *hw_to_ccu_ndmp(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_ndmp, common); +} + +extern const struct clk_ops ccu_ndmp_ops; + +#endif /* _CCU_NDMP_H_ */