| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> |
| */ |
| |
| #include <linux/clk-provider.h> |
| #include <linux/clkdev.h> |
| #include <linux/clk/at91_pmc.h> |
| #include <linux/of.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/regmap.h> |
| #include <soc/at91/atmel-sfr.h> |
| |
| #include "pmc.h" |
| |
| /* |
| * The purpose of this clock is to generate a 480 MHz signal. A different |
| * rate can't be configured. |
| */ |
| #define UTMI_RATE 480000000 |
| |
| struct clk_utmi { |
| struct clk_hw hw; |
| struct regmap *regmap_pmc; |
| struct regmap *regmap_sfr; |
| struct at91_clk_pms pms; |
| }; |
| |
| #define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw) |
| |
| static inline bool clk_utmi_ready(struct regmap *regmap) |
| { |
| unsigned int status; |
| |
| regmap_read(regmap, AT91_PMC_SR, &status); |
| |
| return status & AT91_PMC_LOCKU; |
| } |
| |
| static int clk_utmi_prepare(struct clk_hw *hw) |
| { |
| struct clk_hw *hw_parent; |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| unsigned int uckr = AT91_PMC_UPLLEN | AT91_PMC_UPLLCOUNT | |
| AT91_PMC_BIASEN; |
| unsigned int utmi_ref_clk_freq; |
| unsigned long parent_rate; |
| |
| /* |
| * If mainck rate is different from 12 MHz, we have to configure the |
| * FREQ field of the SFR_UTMICKTRIM register to generate properly |
| * the utmi clock. |
| */ |
| hw_parent = clk_hw_get_parent(hw); |
| parent_rate = clk_hw_get_rate(hw_parent); |
| |
| switch (parent_rate) { |
| case 12000000: |
| utmi_ref_clk_freq = 0; |
| break; |
| case 16000000: |
| utmi_ref_clk_freq = 1; |
| break; |
| case 24000000: |
| utmi_ref_clk_freq = 2; |
| break; |
| /* |
| * Not supported on SAMA5D2 but it's not an issue since MAINCK |
| * maximum value is 24 MHz. |
| */ |
| case 48000000: |
| utmi_ref_clk_freq = 3; |
| break; |
| default: |
| pr_err("UTMICK: unsupported mainck rate\n"); |
| return -EINVAL; |
| } |
| |
| if (utmi->regmap_sfr) { |
| regmap_update_bits(utmi->regmap_sfr, AT91_SFR_UTMICKTRIM, |
| AT91_UTMICKTRIM_FREQ, utmi_ref_clk_freq); |
| } else if (utmi_ref_clk_freq) { |
| pr_err("UTMICK: sfr node required\n"); |
| return -EINVAL; |
| } |
| |
| regmap_update_bits(utmi->regmap_pmc, AT91_CKGR_UCKR, uckr, uckr); |
| |
| while (!clk_utmi_ready(utmi->regmap_pmc)) |
| cpu_relax(); |
| |
| return 0; |
| } |
| |
| static int clk_utmi_is_prepared(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| |
| return clk_utmi_ready(utmi->regmap_pmc); |
| } |
| |
| static void clk_utmi_unprepare(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| |
| regmap_update_bits(utmi->regmap_pmc, AT91_CKGR_UCKR, |
| AT91_PMC_UPLLEN, 0); |
| } |
| |
| static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| /* UTMI clk rate is fixed. */ |
| return UTMI_RATE; |
| } |
| |
| static int clk_utmi_save_context(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| |
| utmi->pms.status = clk_utmi_is_prepared(hw); |
| |
| return 0; |
| } |
| |
| static void clk_utmi_restore_context(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| |
| if (utmi->pms.status) |
| clk_utmi_prepare(hw); |
| } |
| |
| static const struct clk_ops utmi_ops = { |
| .prepare = clk_utmi_prepare, |
| .unprepare = clk_utmi_unprepare, |
| .is_prepared = clk_utmi_is_prepared, |
| .recalc_rate = clk_utmi_recalc_rate, |
| .save_context = clk_utmi_save_context, |
| .restore_context = clk_utmi_restore_context, |
| }; |
| |
| static struct clk_hw * __init |
| at91_clk_register_utmi_internal(struct regmap *regmap_pmc, |
| struct regmap *regmap_sfr, |
| const char *name, const char *parent_name, |
| const struct clk_ops *ops, unsigned long flags) |
| { |
| struct clk_utmi *utmi; |
| struct clk_hw *hw; |
| struct clk_init_data init; |
| int ret; |
| |
| utmi = kzalloc(sizeof(*utmi), GFP_KERNEL); |
| if (!utmi) |
| return ERR_PTR(-ENOMEM); |
| |
| init.name = name; |
| init.ops = ops; |
| init.parent_names = parent_name ? &parent_name : NULL; |
| init.num_parents = parent_name ? 1 : 0; |
| init.flags = flags; |
| |
| utmi->hw.init = &init; |
| utmi->regmap_pmc = regmap_pmc; |
| utmi->regmap_sfr = regmap_sfr; |
| |
| hw = &utmi->hw; |
| ret = clk_hw_register(NULL, &utmi->hw); |
| if (ret) { |
| kfree(utmi); |
| hw = ERR_PTR(ret); |
| } |
| |
| return hw; |
| } |
| |
| struct clk_hw * __init |
| at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr, |
| const char *name, const char *parent_name) |
| { |
| return at91_clk_register_utmi_internal(regmap_pmc, regmap_sfr, name, |
| parent_name, &utmi_ops, CLK_SET_RATE_GATE); |
| } |
| |
| static int clk_utmi_sama7g5_prepare(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| struct clk_hw *hw_parent; |
| unsigned long parent_rate; |
| unsigned int val; |
| |
| hw_parent = clk_hw_get_parent(hw); |
| parent_rate = clk_hw_get_rate(hw_parent); |
| |
| switch (parent_rate) { |
| case 16000000: |
| val = 0; |
| break; |
| case 20000000: |
| val = 2; |
| break; |
| case 24000000: |
| val = 3; |
| break; |
| case 32000000: |
| val = 5; |
| break; |
| default: |
| pr_err("UTMICK: unsupported main_xtal rate\n"); |
| return -EINVAL; |
| } |
| |
| regmap_write(utmi->regmap_pmc, AT91_PMC_XTALF, val); |
| |
| return 0; |
| |
| } |
| |
| static int clk_utmi_sama7g5_is_prepared(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| struct clk_hw *hw_parent; |
| unsigned long parent_rate; |
| unsigned int val; |
| |
| hw_parent = clk_hw_get_parent(hw); |
| parent_rate = clk_hw_get_rate(hw_parent); |
| |
| regmap_read(utmi->regmap_pmc, AT91_PMC_XTALF, &val); |
| switch (val & 0x7) { |
| case 0: |
| if (parent_rate == 16000000) |
| return 1; |
| break; |
| case 2: |
| if (parent_rate == 20000000) |
| return 1; |
| break; |
| case 3: |
| if (parent_rate == 24000000) |
| return 1; |
| break; |
| case 5: |
| if (parent_rate == 32000000) |
| return 1; |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int clk_utmi_sama7g5_save_context(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| |
| utmi->pms.status = clk_utmi_sama7g5_is_prepared(hw); |
| |
| return 0; |
| } |
| |
| static void clk_utmi_sama7g5_restore_context(struct clk_hw *hw) |
| { |
| struct clk_utmi *utmi = to_clk_utmi(hw); |
| |
| if (utmi->pms.status) |
| clk_utmi_sama7g5_prepare(hw); |
| } |
| |
| static const struct clk_ops sama7g5_utmi_ops = { |
| .prepare = clk_utmi_sama7g5_prepare, |
| .is_prepared = clk_utmi_sama7g5_is_prepared, |
| .recalc_rate = clk_utmi_recalc_rate, |
| .save_context = clk_utmi_sama7g5_save_context, |
| .restore_context = clk_utmi_sama7g5_restore_context, |
| }; |
| |
| struct clk_hw * __init |
| at91_clk_sama7g5_register_utmi(struct regmap *regmap_pmc, const char *name, |
| const char *parent_name) |
| { |
| return at91_clk_register_utmi_internal(regmap_pmc, NULL, name, |
| parent_name, &sama7g5_utmi_ops, 0); |
| } |