From patchwork Tue Jun 7 20:41:46 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Maxime Ripard X-Patchwork-Id: 9162549 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 84CD060777 for ; Tue, 7 Jun 2016 20:44:19 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7558228369 for ; Tue, 7 Jun 2016 20:44:19 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6A3652836F; Tue, 7 Jun 2016 20:44:19 +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,RCVD_IN_DNSWL_HI autolearn=unavailable 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 C230528369 for ; Tue, 7 Jun 2016 20:44:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1422732AbcFGUne (ORCPT ); Tue, 7 Jun 2016 16:43:34 -0400 Received: from down.free-electrons.com ([37.187.137.238]:52990 "EHLO mail.free-electrons.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1752688AbcFGUmW (ORCPT ); Tue, 7 Jun 2016 16:42:22 -0400 Received: by mail.free-electrons.com (Postfix, from userid 110) id 17A38B2; Tue, 7 Jun 2016 22:42:20 +0200 (CEST) Received: from localhost (LFbn-1-2034-31.w90-76.abo.wanadoo.fr [90.76.103.31]) by mail.free-electrons.com (Postfix) with ESMTPSA id DC07047B; Tue, 7 Jun 2016 22:42:05 +0200 (CEST) From: Maxime Ripard To: Mike Turquette , Stephen Boyd , Chen-Yu Tsai Cc: linux-clk@vger.kernel.org, Hans de Goede , Andre Przywara , Rob Herring , Vishnu Patekar , linux-arm-kernel@lists.infradead.org, Boris Brezillon , Jean-Francois Moine , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Maxime Ripard Subject: [PATCH v2 07/15] clk: sunxi-ng: Add phase clock support Date: Tue, 7 Jun 2016 22:41:46 +0200 Message-Id: <20160607204154.31967-8-maxime.ripard@free-electrons.com> X-Mailer: git-send-email 2.8.3 In-Reply-To: <20160607204154.31967-1-maxime.ripard@free-electrons.com> References: <20160607204154.31967-1-maxime.ripard@free-electrons.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 Add support for the clocks in the CCU that introduce a phase shift from their parent clock. Signed-off-by: Maxime Ripard --- drivers/clk/sunxi-ng/Makefile | 1 + drivers/clk/sunxi-ng/ccu_phase.c | 126 +++++++++++++++++++++++++++++++++++++++ drivers/clk/sunxi-ng/ccu_phase.h | 50 ++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 drivers/clk/sunxi-ng/ccu_phase.c create mode 100644 drivers/clk/sunxi-ng/ccu_phase.h diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 99133df3f7ab..c47cbd309d59 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile @@ -6,3 +6,4 @@ obj-y += ccu_frac.o obj-y += ccu_fixed_factor.o obj-y += ccu_gate.o obj-y += ccu_mux.o +obj-y += ccu_phase.o diff --git a/drivers/clk/sunxi-ng/ccu_phase.c b/drivers/clk/sunxi-ng/ccu_phase.c new file mode 100644 index 000000000000..400c58ad72fd --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_phase.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * 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 +#include + +#include "ccu_phase.h" + +static int ccu_phase_get_phase(struct clk_hw *hw) +{ + struct ccu_phase *phase = hw_to_ccu_phase(hw); + struct clk_hw *parent, *grandparent; + unsigned int parent_rate, grandparent_rate; + u16 step, parent_div; + u32 reg; + u8 delay; + + reg = readl(phase->common.base + phase->common.reg); + delay = (reg >> phase->shift); + delay &= (1 << phase->width) - 1; + + if (!delay) + return 180; + + /* Get our parent clock, it's the one that can adjust its rate */ + parent = clk_hw_get_parent(hw); + if (!parent) + return -EINVAL; + + /* And its rate */ + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + return -EINVAL; + + /* Now, get our parent's parent (most likely some PLL) */ + grandparent = clk_hw_get_parent(parent); + if (!grandparent) + return -EINVAL; + + /* And its rate */ + grandparent_rate = clk_hw_get_rate(grandparent); + if (!grandparent_rate) + return -EINVAL; + + /* Get our parent clock divider */ + parent_div = grandparent_rate / parent_rate; + + step = DIV_ROUND_CLOSEST(360, parent_div); + return delay * step; +} + +static int ccu_phase_set_phase(struct clk_hw *hw, int degrees) +{ + struct ccu_phase *phase = hw_to_ccu_phase(hw); + struct clk_hw *parent, *grandparent; + unsigned int parent_rate, grandparent_rate; + unsigned long flags; + u32 reg; + u8 delay; + + /* Get our parent clock, it's the one that can adjust its rate */ + parent = clk_hw_get_parent(hw); + if (!parent) + return -EINVAL; + + /* And its rate */ + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + return -EINVAL; + + /* Now, get our parent's parent (most likely some PLL) */ + grandparent = clk_hw_get_parent(parent); + if (!grandparent) + return -EINVAL; + + /* And its rate */ + grandparent_rate = clk_hw_get_rate(grandparent); + if (!grandparent_rate) + return -EINVAL; + + if (degrees != 180) { + u16 step, parent_div; + + /* Get our parent divider */ + parent_div = grandparent_rate / parent_rate; + + /* + * We can only outphase the clocks by multiple of the + * PLL's period. + * + * Since our parent clock is only a divider, and the + * formula to get the outphasing in degrees is deg = + * 360 * delta / period + * + * If we simplify this formula, we can see that the + * only thing that we're concerned about is the number + * of period we want to outphase our clock from, and + * the divider set by our parent clock. + */ + step = DIV_ROUND_CLOSEST(360, parent_div); + delay = DIV_ROUND_CLOSEST(degrees, step); + } else { + delay = 0; + } + + spin_lock_irqsave(phase->common.lock, flags); + reg = readl(phase->common.base + phase->common.reg); + reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift); + writel(reg | (delay << phase->shift), + phase->common.base + phase->common.reg); + spin_unlock_irqrestore(phase->common.lock, flags); + + return 0; +} + +const struct clk_ops ccu_phase_ops = { + .get_phase = ccu_phase_get_phase, + .set_phase = ccu_phase_set_phase, +}; diff --git a/drivers/clk/sunxi-ng/ccu_phase.h b/drivers/clk/sunxi-ng/ccu_phase.h new file mode 100644 index 000000000000..e28b4e58a819 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_phase.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 Maxime Ripard. All rights reserved. + * + * 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_PHASE_H_ +#define _CCU_PHASE_H_ + +#include + +#include "ccu_common.h" + +struct ccu_phase { + u8 shift; + u8 width; + + struct ccu_common common; +}; + +#define SUNXI_CCU_PHASE(_struct, _name, _parent, _reg, _shift, _width, _flags) \ + struct ccu_phase _struct = { \ + .shift = _shift, \ + .width = _width, \ + .common = { \ + .reg = _reg, \ + .hw.init = SUNXI_HW_INIT(_name, \ + _parent, \ + &ccu_phase_ops, \ + _flags), \ + } \ + } + +static inline struct ccu_phase *hw_to_ccu_phase(struct clk_hw *hw) +{ + struct ccu_common *common = hw_to_ccu_common(hw); + + return container_of(common, struct ccu_phase, common); +} + +extern const struct clk_ops ccu_phase_ops; + +#endif /* _CCU_PHASE_H_ */