mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
6.1 KiB
286 lines
6.1 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Copyright (C) 2013 Boris BREZILLON <[email protected]> |
|
*/ |
|
|
|
#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); |
|
}
|
|
|