Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753916AbdGJMZV (ORCPT ); Mon, 10 Jul 2017 08:25:21 -0400 Received: from mout.gmx.net ([212.227.15.19]:63330 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752509AbdGJMZT (ORCPT ); Mon, 10 Jul 2017 08:25:19 -0400 From: Ingo van Lil To: linux-kernel@vger.kernel.org Cc: Alexandre Belloni , Boris Brezillon , Nicolas Ferre , linux-clk@vger.kernel.org, Ingo van Lil Subject: [PATCH] clk: at91: utmi: Support configurable multipliers Date: Mon, 10 Jul 2017 14:24:09 +0200 Message-Id: <20170710122409.3017-1-inguin@gmx.de> X-Mailer: git-send-email 2.9.4 X-Provags-ID: V03:K0:qwyHlpV2hfZKDoTDedADZ7PXyGgVzkFcF63nsE7B7GcLZPW3gBU 0X68MWquzZyEdrQj3ZyPgMzXajDR/Ho2//ULH50oqfDEeA35dtOLa5z2eSnvcQQyBEe+QrZ J/fRxFUeRCu1zak+gD3ChWlGOKdTG9crDO4U8zrMTcH71l3rvGDVQTOihijSC1yR9Fetfkf yacZJQnF80/669qg/IJUw== X-UI-Out-Filterresults: notjunk:1;V01:K0:Kx6N959JBR8=:VmLnUqXxVTeAmnsvd1qrfk AUn94kA17NAHBtaiz+44y340W2+3Hyk5t+rkUbs+7LzWsHtkpbUxkEocG32iiEpakvO27qPyv qSL9QtGBcLMLBeXGgTu2Y/RJirb+EOdtya531ulJBsMITdRcAM04CJutLrC8v1goLZhAaQsga J09EYDow120uwRf73ErSPeAI2g+dvbbpRrtcajPj7oaY+dCqpvwbA3jrCzN7UhCMx3sWm/uVK X9t6OtGj8sb/9Iov7RebBF/PHAPwXVo1/45HomTx7txrBlvaEV2DBRSrzB2ZyKxIqEpiEmLLW na/7fyhQlRaa4YnOcPVg4P79Ml3BqHgCjdgkw0JotLOOsombdck7fXueCV6H5g7VqOqyH3yGL l/v+rq0aL/0z+7v6m1L8ERn7ebjPzWYNWrVCJqAOY5u/fhf01B4hcUcpyvB354xbPHR2VDi/G X6OYmmOw1D5i499dyhoJTunLFx6iAHFOdGilCco3SkuMFaxJXHNA+CicvTJgMnjMmZGNTqi07 jMFYkOL3GN5nPyWWKmU7XzXXQ4ekUXmBAqVQvnA0lgDt5t9TgZAZmrvYGW9EJMAJ1shiGXebN fm1i19NWmKJDT8Yg7+XhkNXvx0RmyQ3ocEUe4Kc6OIY9dRhdYTJXaRhmn3iJvIMd9gQMCFGax aPzv/v/pFULmVwBSxNpyOnGYt8/pdn3BbDAQHPZ9Dm/ugrIaldNMRorHj/Eo8UA0580CqZiIx u1r6g+Mh5fHy/DDtsPjqFo/Psp7uG3dJLCgqBecg8B9LOtxh6cywEvTXaeA= Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 5729 Lines: 196 The AT91 UTMI clock is a PLL generating the 480 MHz USB clock. Originally, this PLL always had a fixed x40 multiplier, thus requiring a 12 MHz input clock. Recent SOCs (sama5d2 and sama5d3) have a special function register for configuring this multiplier, allowing to use different main clock frequencies. Signed-off-by: Ingo van Lil --- drivers/clk/at91/clk-utmi.c | 105 +++++++++++++++++++++++++++++++++++++++++-- include/soc/at91/atmel-sfr.h | 2 + 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c index aadabd9..d41c38b 100644 --- a/drivers/clk/at91/clk-utmi.c +++ b/drivers/clk/at91/clk-utmi.c @@ -14,14 +14,29 @@ #include #include #include +#include #include "pmc.h" +/* default multiplier for SOCs that do not allow configuration via SFR */ #define UTMI_FIXED_MUL 40 +/* supported multiplier settings for SOCs that allow configuration via SFR */ +struct utmi_multipliers { + const char *sfr_compatible_name; + u8 multipliers[4]; +}; + +static const struct utmi_multipliers utmi_multipliers[] = { + { "atmel,sama5d2-sfr", { 40, 30, 20, 40 } }, + { "atmel,sama5d3-sfr", { 40, 30, 20, 10 } }, +}; + struct clk_utmi { struct clk_hw hw; struct regmap *regmap; + struct regmap *sfr_regmap; + const u8 *multipliers; }; #define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw) @@ -66,8 +81,67 @@ static void clk_utmi_unprepare(struct clk_hw *hw) static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { - /* UTMI clk is a fixed clk multiplier */ - return parent_rate * UTMI_FIXED_MUL; + struct clk_utmi *utmi = to_clk_utmi(hw); + u8 mul = UTMI_FIXED_MUL; + + if (utmi->sfr_regmap && utmi->multipliers) { + u32 regval; + regmap_read(utmi->sfr_regmap, AT91_SFR_UTMICKTRIM, ®val); + mul = utmi->multipliers[regval & AT91_UTMICKTRIM_FREQ_MASK]; + } + + return parent_rate * mul; +} + +static long clk_utmi_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct clk_utmi *utmi = to_clk_utmi(hw); + unsigned long bestrate = 0; + int bestdiff = -1; + int i; + + if (!utmi->sfr_regmap || !utmi->multipliers) + return *parent_rate * UTMI_FIXED_MUL; + + for (i = 0; i < ARRAY_SIZE(utmi_multipliers); i++) { + unsigned long tmprate = *parent_rate * utmi->multipliers[i]; + int tmpdiff; + + if (tmprate < rate) + continue; + + tmpdiff = tmprate - rate; + if (bestdiff < 0 || bestdiff > tmpdiff) { + bestrate = tmprate; + bestdiff = tmpdiff; + } + + if (!bestdiff) + break; + } + + return bestrate; +} + +static int clk_utmi_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_utmi *utmi = to_clk_utmi(hw); + int i; + + if (!utmi->sfr_regmap || !utmi->multipliers) + return rate == parent_rate * UTMI_FIXED_MUL ? 0 : -EINVAL; + + for (i = 0; i < ARRAY_SIZE(utmi_multipliers); i++) { + if (rate == parent_rate * utmi->multipliers[i]) { + regmap_update_bits(utmi->sfr_regmap, AT91_SFR_UTMICKTRIM, + AT91_UTMICKTRIM_FREQ_MASK, i); + return 0; + } + } + + return -EINVAL; } static const struct clk_ops utmi_ops = { @@ -75,10 +149,13 @@ static const struct clk_ops utmi_ops = { .unprepare = clk_utmi_unprepare, .is_prepared = clk_utmi_is_prepared, .recalc_rate = clk_utmi_recalc_rate, + .round_rate = clk_utmi_round_rate, + .set_rate = clk_utmi_set_rate, }; static struct clk_hw * __init at91_clk_register_utmi(struct regmap *regmap, + struct regmap *sfr_regmap, const u8 *multipliers, const char *name, const char *parent_name) { struct clk_utmi *utmi; @@ -98,6 +175,8 @@ at91_clk_register_utmi(struct regmap *regmap, utmi->hw.init = &init; utmi->regmap = regmap; + utmi->sfr_regmap = sfr_regmap; + utmi->multipliers = multipliers; hw = &utmi->hw; ret = clk_hw_register(NULL, &utmi->hw); @@ -115,6 +194,9 @@ static void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np) const char *parent_name; const char *name = np->name; struct regmap *regmap; + struct regmap *sfr_regmap; + const u8 *multipliers = NULL; + size_t i; parent_name = of_clk_get_parent_name(np, 0); @@ -124,7 +206,24 @@ static void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np) if (IS_ERR(regmap)) return; - hw = at91_clk_register_utmi(regmap, name, parent_name); + for (i = 0; i < ARRAY_SIZE(utmi_multipliers); i++) { + sfr_regmap = syscon_regmap_lookup_by_compatible( + utmi_multipliers[i].sfr_compatible_name); + if (!IS_ERR(sfr_regmap)) { + pr_debug("clk-utmi: found sfr node: %s\n", + utmi_multipliers[i].sfr_compatible_name); + multipliers = utmi_multipliers[i].multipliers; + break; + } + } + + if (IS_ERR(sfr_regmap)) { + pr_debug("clk-utmi: failed to find sfr node\n"); + sfr_regmap = NULL; + } + + hw = at91_clk_register_utmi(regmap, sfr_regmap, multipliers, + name, parent_name); if (IS_ERR(hw)) return; diff --git a/include/soc/at91/atmel-sfr.h b/include/soc/at91/atmel-sfr.h index 506ea8f..763948e 100644 --- a/include/soc/at91/atmel-sfr.h +++ b/include/soc/at91/atmel-sfr.h @@ -17,6 +17,7 @@ /* 0x08 ~ 0x0c: Reserved */ #define AT91_SFR_OHCIICR 0x10 /* OHCI INT Configuration Register */ #define AT91_SFR_OHCIISR 0x14 /* OHCI INT Status Register */ +#define AT91_SFR_UTMICKTRIM 0x30 /* UTMI Clock Trimming Register */ #define AT91_SFR_I2SCLKSEL 0x90 /* I2SC Register */ /* Field definitions */ @@ -28,5 +29,6 @@ AT91_OHCIICR_SUSPEND_B | \ AT91_OHCIICR_SUSPEND_C) +#define AT91_UTMICKTRIM_FREQ_MASK 0x03 #endif /* _LINUX_MFD_SYSCON_ATMEL_SFR_H */ -- 2.9.4