Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Ingenic SoCs drivers"
depends on MIPS || COMPILE_TEST
config INGENIC_CGU_COMMON
bool
config INGENIC_CGU_JZ4740
bool "Ingenic JZ4740 CGU driver"
default MACH_JZ4740
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic JZ4740
and compatible SoCs.
If building for a JZ4740 SoC, you want to say Y here.
config INGENIC_CGU_JZ4755
bool "Ingenic JZ4755 CGU driver"
default MACH_JZ4755
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic JZ4755
and compatible SoCs.
If building for a JZ4755 SoC, you want to say Y here.
config INGENIC_CGU_JZ4725B
bool "Ingenic JZ4725B CGU driver"
default MACH_JZ4725B
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic JZ4725B
and compatible SoCs.
If building for a JZ4725B SoC, you want to say Y here.
config INGENIC_CGU_JZ4760
bool "Ingenic JZ4760 CGU driver"
default MACH_JZ4760
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic JZ4760
and compatible SoCs.
If building for a JZ4760 SoC, you want to say Y here.
config INGENIC_CGU_JZ4770
bool "Ingenic JZ4770 CGU driver"
default MACH_JZ4770
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic JZ4770
and compatible SoCs.
If building for a JZ4770 SoC, you want to say Y here.
config INGENIC_CGU_JZ4780
bool "Ingenic JZ4780 CGU driver"
default MACH_JZ4780
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic JZ4780
and compatible SoCs.
If building for a JZ4780 SoC, you want to say Y here.
config INGENIC_CGU_X1000
bool "Ingenic X1000 CGU driver"
default MACH_X1000
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic X1000
and compatible SoCs.
If building for a X1000 SoC, you want to say Y here.
config INGENIC_CGU_X1830
bool "Ingenic X1830 CGU driver"
default MACH_X1830
select INGENIC_CGU_COMMON
help
Support the clocks provided by the CGU hardware on Ingenic X1830
and compatible SoCs.
If building for a X1830 SoC, you want to say Y here.
config INGENIC_TCU_CLK
bool "Ingenic JZ47xx TCU clocks driver"
default MACH_INGENIC
select MFD_SYSCON
help
Support the clocks of the Timer/Counter Unit (TCU) of the Ingenic
JZ47xx SoCs.
endmenu
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_INGENIC_CGU_COMMON) += cgu.o pm.o
obj-$(CONFIG_INGENIC_CGU_JZ4740) += jz4740-cgu.o
obj-$(CONFIG_INGENIC_CGU_JZ4755) += jz4755-cgu.o
obj-$(CONFIG_INGENIC_CGU_JZ4725B) += jz4725b-cgu.o
obj-$(CONFIG_INGENIC_CGU_JZ4760) += jz4760-cgu.o
obj-$(CONFIG_INGENIC_CGU_JZ4770) += jz4770-cgu.o
obj-$(CONFIG_INGENIC_CGU_JZ4780) += jz4780-cgu.o
obj-$(CONFIG_INGENIC_CGU_X1000) += x1000-cgu.o
obj-$(CONFIG_INGENIC_CGU_X1830) += x1830-cgu.o
obj-$(CONFIG_INGENIC_TCU_CLK) += tcu.o
+853
View File
@@ -0,0 +1,853 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Ingenic SoC CGU driver
*
* Copyright (c) 2013-2015 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/math64.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
#include "cgu.h"
#define MHZ (1000 * 1000)
static inline const struct ingenic_cgu_clk_info *
to_clk_info(struct ingenic_clk *clk)
{
return &clk->cgu->clock_info[clk->idx];
}
/**
* ingenic_cgu_gate_get() - get the value of clock gate register bit
* @cgu: reference to the CGU whose registers should be read
* @info: info struct describing the gate bit
*
* Retrieves the state of the clock gate bit described by info. The
* caller must hold cgu->lock.
*
* Return: true if the gate bit is set, else false.
*/
static inline bool
ingenic_cgu_gate_get(struct ingenic_cgu *cgu,
const struct ingenic_cgu_gate_info *info)
{
return !!(readl(cgu->base + info->reg) & BIT(info->bit))
^ info->clear_to_gate;
}
/**
* ingenic_cgu_gate_set() - set the value of clock gate register bit
* @cgu: reference to the CGU whose registers should be modified
* @info: info struct describing the gate bit
* @val: non-zero to gate a clock, otherwise zero
*
* Sets the given gate bit in order to gate or ungate a clock.
*
* The caller must hold cgu->lock.
*/
static inline void
ingenic_cgu_gate_set(struct ingenic_cgu *cgu,
const struct ingenic_cgu_gate_info *info, bool val)
{
u32 clkgr = readl(cgu->base + info->reg);
if (val ^ info->clear_to_gate)
clkgr |= BIT(info->bit);
else
clkgr &= ~BIT(info->bit);
writel(clkgr, cgu->base + info->reg);
}
/*
* PLL operations
*/
static unsigned long
ingenic_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
const struct ingenic_cgu_pll_info *pll_info;
unsigned m, n, od, od_enc = 0;
bool bypass;
u32 ctl;
BUG_ON(clk_info->type != CGU_CLK_PLL);
pll_info = &clk_info->pll;
ctl = readl(cgu->base + pll_info->reg);
m = (ctl >> pll_info->m_shift) & GENMASK(pll_info->m_bits - 1, 0);
m += pll_info->m_offset;
n = (ctl >> pll_info->n_shift) & GENMASK(pll_info->n_bits - 1, 0);
n += pll_info->n_offset;
if (pll_info->od_bits > 0) {
od_enc = ctl >> pll_info->od_shift;
od_enc &= GENMASK(pll_info->od_bits - 1, 0);
}
if (pll_info->bypass_bit >= 0) {
ctl = readl(cgu->base + pll_info->bypass_reg);
bypass = !!(ctl & BIT(pll_info->bypass_bit));
if (bypass)
return parent_rate;
}
for (od = 0; od < pll_info->od_max; od++)
if (pll_info->od_encoding[od] == od_enc)
break;
/* if od_max = 0, od_bits should be 0 and od is fixed to 1. */
if (pll_info->od_max == 0)
BUG_ON(pll_info->od_bits != 0);
else
BUG_ON(od == pll_info->od_max);
od++;
return div_u64((u64)parent_rate * m * pll_info->rate_multiplier,
n * od);
}
static void
ingenic_pll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
unsigned long rate, unsigned long parent_rate,
unsigned int *pm, unsigned int *pn, unsigned int *pod)
{
unsigned int m, n, od = 1;
/*
* The frequency after the input divider must be between 10 and 50 MHz.
* The highest divider yields the best resolution.
*/
n = parent_rate / (10 * MHZ);
n = min_t(unsigned int, n, 1 << pll_info->n_bits);
n = max_t(unsigned int, n, pll_info->n_offset);
m = (rate / MHZ) * od * n / (parent_rate / MHZ);
m = min_t(unsigned int, m, 1 << pll_info->m_bits);
m = max_t(unsigned int, m, pll_info->m_offset);
*pm = m;
*pn = n;
*pod = od;
}
static unsigned long
ingenic_pll_calc(const struct ingenic_cgu_clk_info *clk_info,
unsigned long rate, unsigned long parent_rate,
unsigned int *pm, unsigned int *pn, unsigned int *pod)
{
const struct ingenic_cgu_pll_info *pll_info = &clk_info->pll;
unsigned int m, n, od;
if (pll_info->calc_m_n_od)
(*pll_info->calc_m_n_od)(pll_info, rate, parent_rate, &m, &n, &od);
else
ingenic_pll_calc_m_n_od(pll_info, rate, parent_rate, &m, &n, &od);
if (pm)
*pm = m;
if (pn)
*pn = n;
if (pod)
*pod = od;
return div_u64((u64)parent_rate * m * pll_info->rate_multiplier,
n * od);
}
static int ingenic_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
req->rate = ingenic_pll_calc(clk_info, req->rate, req->best_parent_rate,
NULL, NULL, NULL);
return 0;
}
static inline int ingenic_pll_check_stable(struct ingenic_cgu *cgu,
const struct ingenic_cgu_pll_info *pll_info)
{
u32 ctl;
if (pll_info->stable_bit < 0)
return 0;
return readl_poll_timeout(cgu->base + pll_info->reg, ctl,
ctl & BIT(pll_info->stable_bit),
0, 100 * USEC_PER_MSEC);
}
static int
ingenic_pll_set_rate(struct clk_hw *hw, unsigned long req_rate,
unsigned long parent_rate)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
const struct ingenic_cgu_pll_info *pll_info = &clk_info->pll;
unsigned long rate, flags;
unsigned int m, n, od;
int ret = 0;
u32 ctl;
rate = ingenic_pll_calc(clk_info, req_rate, parent_rate,
&m, &n, &od);
if (rate != req_rate)
pr_info("ingenic-cgu: request '%s' rate %luHz, actual %luHz\n",
clk_info->name, req_rate, rate);
spin_lock_irqsave(&cgu->lock, flags);
ctl = readl(cgu->base + pll_info->reg);
ctl &= ~(GENMASK(pll_info->m_bits - 1, 0) << pll_info->m_shift);
ctl |= (m - pll_info->m_offset) << pll_info->m_shift;
ctl &= ~(GENMASK(pll_info->n_bits - 1, 0) << pll_info->n_shift);
ctl |= (n - pll_info->n_offset) << pll_info->n_shift;
if (pll_info->od_bits > 0) {
ctl &= ~(GENMASK(pll_info->od_bits - 1, 0) << pll_info->od_shift);
ctl |= pll_info->od_encoding[od - 1] << pll_info->od_shift;
}
writel(ctl, cgu->base + pll_info->reg);
if (pll_info->set_rate_hook)
pll_info->set_rate_hook(pll_info, rate, parent_rate);
/* If the PLL is enabled, verify that it's stable */
if (pll_info->enable_bit >= 0 && (ctl & BIT(pll_info->enable_bit)))
ret = ingenic_pll_check_stable(cgu, pll_info);
spin_unlock_irqrestore(&cgu->lock, flags);
return ret;
}
static int ingenic_pll_enable(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
const struct ingenic_cgu_pll_info *pll_info = &clk_info->pll;
unsigned long flags;
int ret;
u32 ctl;
if (pll_info->enable_bit < 0)
return 0;
spin_lock_irqsave(&cgu->lock, flags);
if (pll_info->bypass_bit >= 0) {
ctl = readl(cgu->base + pll_info->bypass_reg);
ctl &= ~BIT(pll_info->bypass_bit);
writel(ctl, cgu->base + pll_info->bypass_reg);
}
ctl = readl(cgu->base + pll_info->reg);
ctl |= BIT(pll_info->enable_bit);
writel(ctl, cgu->base + pll_info->reg);
ret = ingenic_pll_check_stable(cgu, pll_info);
spin_unlock_irqrestore(&cgu->lock, flags);
return ret;
}
static void ingenic_pll_disable(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
const struct ingenic_cgu_pll_info *pll_info = &clk_info->pll;
unsigned long flags;
u32 ctl;
if (pll_info->enable_bit < 0)
return;
spin_lock_irqsave(&cgu->lock, flags);
ctl = readl(cgu->base + pll_info->reg);
ctl &= ~BIT(pll_info->enable_bit);
writel(ctl, cgu->base + pll_info->reg);
spin_unlock_irqrestore(&cgu->lock, flags);
}
static int ingenic_pll_is_enabled(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
const struct ingenic_cgu_pll_info *pll_info = &clk_info->pll;
u32 ctl;
if (pll_info->enable_bit < 0)
return true;
ctl = readl(cgu->base + pll_info->reg);
return !!(ctl & BIT(pll_info->enable_bit));
}
static const struct clk_ops ingenic_pll_ops = {
.recalc_rate = ingenic_pll_recalc_rate,
.determine_rate = ingenic_pll_determine_rate,
.set_rate = ingenic_pll_set_rate,
.enable = ingenic_pll_enable,
.disable = ingenic_pll_disable,
.is_enabled = ingenic_pll_is_enabled,
};
/*
* Operations for all non-PLL clocks
*/
static u8 ingenic_clk_get_parent(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
u32 reg;
u8 i, hw_idx, idx = 0;
if (clk_info->type & CGU_CLK_MUX) {
reg = readl(cgu->base + clk_info->mux.reg);
hw_idx = (reg >> clk_info->mux.shift) &
GENMASK(clk_info->mux.bits - 1, 0);
/*
* Convert the hardware index to the parent index by skipping
* over any -1's in the parents array.
*/
for (i = 0; i < hw_idx; i++) {
if (clk_info->parents[i] != -1)
idx++;
}
}
return idx;
}
static int ingenic_clk_set_parent(struct clk_hw *hw, u8 idx)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
unsigned long flags;
u8 curr_idx, hw_idx, num_poss;
u32 reg, mask;
if (clk_info->type & CGU_CLK_MUX) {
/*
* Convert the parent index to the hardware index by adding
* 1 for any -1 in the parents array preceding the given
* index. That is, we want the index of idx'th entry in
* clk_info->parents which does not equal -1.
*/
hw_idx = curr_idx = 0;
num_poss = 1 << clk_info->mux.bits;
for (; hw_idx < num_poss; hw_idx++) {
if (clk_info->parents[hw_idx] == -1)
continue;
if (curr_idx == idx)
break;
curr_idx++;
}
/* idx should always be a valid parent */
BUG_ON(curr_idx != idx);
mask = GENMASK(clk_info->mux.bits - 1, 0);
mask <<= clk_info->mux.shift;
spin_lock_irqsave(&cgu->lock, flags);
/* write the register */
reg = readl(cgu->base + clk_info->mux.reg);
reg &= ~mask;
reg |= hw_idx << clk_info->mux.shift;
writel(reg, cgu->base + clk_info->mux.reg);
spin_unlock_irqrestore(&cgu->lock, flags);
return 0;
}
return idx ? -EINVAL : 0;
}
static unsigned long
ingenic_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
unsigned long rate = parent_rate;
u32 div_reg, div;
u8 parent;
if (clk_info->type & CGU_CLK_DIV) {
parent = ingenic_clk_get_parent(hw);
if (!(clk_info->div.bypass_mask & BIT(parent))) {
div_reg = readl(cgu->base + clk_info->div.reg);
div = (div_reg >> clk_info->div.shift) &
GENMASK(clk_info->div.bits - 1, 0);
if (clk_info->div.div_table)
div = clk_info->div.div_table[div];
else
div = (div + 1) * clk_info->div.div;
rate /= div;
}
} else if (clk_info->type & CGU_CLK_FIXDIV) {
rate /= clk_info->fixdiv.div;
}
return rate;
}
static unsigned int
ingenic_clk_calc_hw_div(const struct ingenic_cgu_clk_info *clk_info,
unsigned int div)
{
unsigned int i, best_i = 0, best = (unsigned int)-1;
for (i = 0; i < (1 << clk_info->div.bits)
&& clk_info->div.div_table[i]; i++) {
if (clk_info->div.div_table[i] >= div &&
clk_info->div.div_table[i] < best) {
best = clk_info->div.div_table[i];
best_i = i;
if (div == best)
break;
}
}
return best_i;
}
static unsigned
ingenic_clk_calc_div(struct clk_hw *hw,
const struct ingenic_cgu_clk_info *clk_info,
unsigned long parent_rate, unsigned long req_rate)
{
unsigned int div, hw_div;
u8 parent;
parent = ingenic_clk_get_parent(hw);
if (clk_info->div.bypass_mask & BIT(parent))
return 1;
/* calculate the divide */
div = DIV_ROUND_UP(parent_rate, req_rate);
if (clk_info->div.div_table) {
hw_div = ingenic_clk_calc_hw_div(clk_info, div);
return clk_info->div.div_table[hw_div];
}
/* Impose hardware constraints */
div = clamp_t(unsigned int, div, clk_info->div.div,
clk_info->div.div << clk_info->div.bits);
/*
* If the divider value itself must be divided before being written to
* the divider register, we must ensure we don't have any bits set that
* would be lost as a result of doing so.
*/
div = DIV_ROUND_UP(div, clk_info->div.div);
div *= clk_info->div.div;
return div;
}
static int ingenic_clk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
unsigned int div = 1;
if (clk_info->type & CGU_CLK_DIV)
div = ingenic_clk_calc_div(hw, clk_info, req->best_parent_rate,
req->rate);
else if (clk_info->type & CGU_CLK_FIXDIV)
div = clk_info->fixdiv.div;
else if (clk_hw_can_set_rate_parent(hw))
req->best_parent_rate = req->rate;
req->rate = DIV_ROUND_UP(req->best_parent_rate, div);
return 0;
}
static inline int ingenic_clk_check_stable(struct ingenic_cgu *cgu,
const struct ingenic_cgu_clk_info *clk_info)
{
u32 reg;
return readl_poll_timeout(cgu->base + clk_info->div.reg, reg,
!(reg & BIT(clk_info->div.busy_bit)),
0, 100 * USEC_PER_MSEC);
}
static int
ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
unsigned long parent_rate)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
unsigned long rate, flags;
unsigned int hw_div, div;
u32 reg, mask;
int ret = 0;
if (clk_info->type & CGU_CLK_DIV) {
div = ingenic_clk_calc_div(hw, clk_info, parent_rate, req_rate);
rate = DIV_ROUND_UP(parent_rate, div);
if (rate != req_rate)
return -EINVAL;
if (clk_info->div.div_table)
hw_div = ingenic_clk_calc_hw_div(clk_info, div);
else
hw_div = ((div / clk_info->div.div) - 1);
spin_lock_irqsave(&cgu->lock, flags);
reg = readl(cgu->base + clk_info->div.reg);
/* update the divide */
mask = GENMASK(clk_info->div.bits - 1, 0);
reg &= ~(mask << clk_info->div.shift);
reg |= hw_div << clk_info->div.shift;
/* clear the stop bit */
if (clk_info->div.stop_bit != -1)
reg &= ~BIT(clk_info->div.stop_bit);
/* set the change enable bit */
if (clk_info->div.ce_bit != -1)
reg |= BIT(clk_info->div.ce_bit);
/* update the hardware */
writel(reg, cgu->base + clk_info->div.reg);
/* wait for the change to take effect */
if (clk_info->div.busy_bit != -1)
ret = ingenic_clk_check_stable(cgu, clk_info);
spin_unlock_irqrestore(&cgu->lock, flags);
return ret;
}
return -EINVAL;
}
static int ingenic_clk_enable(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
unsigned long flags;
if (clk_info->type & CGU_CLK_GATE) {
/* ungate the clock */
spin_lock_irqsave(&cgu->lock, flags);
ingenic_cgu_gate_set(cgu, &clk_info->gate, false);
spin_unlock_irqrestore(&cgu->lock, flags);
if (clk_info->gate.delay_us)
udelay(clk_info->gate.delay_us);
}
return 0;
}
static void ingenic_clk_disable(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
unsigned long flags;
if (clk_info->type & CGU_CLK_GATE) {
/* gate the clock */
spin_lock_irqsave(&cgu->lock, flags);
ingenic_cgu_gate_set(cgu, &clk_info->gate, true);
spin_unlock_irqrestore(&cgu->lock, flags);
}
}
static int ingenic_clk_is_enabled(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
int enabled = 1;
if (clk_info->type & CGU_CLK_GATE)
enabled = !ingenic_cgu_gate_get(cgu, &clk_info->gate);
return enabled;
}
static const struct clk_ops ingenic_clk_ops = {
.get_parent = ingenic_clk_get_parent,
.set_parent = ingenic_clk_set_parent,
.recalc_rate = ingenic_clk_recalc_rate,
.determine_rate = ingenic_clk_determine_rate,
.set_rate = ingenic_clk_set_rate,
.enable = ingenic_clk_enable,
.disable = ingenic_clk_disable,
.is_enabled = ingenic_clk_is_enabled,
};
/*
* Setup functions.
*/
static int ingenic_register_clock(struct ingenic_cgu *cgu, unsigned idx)
{
const struct ingenic_cgu_clk_info *clk_info = &cgu->clock_info[idx];
struct clk_init_data clk_init;
struct ingenic_clk *ingenic_clk = NULL;
struct clk *clk, *parent;
const char *parent_names[4];
unsigned caps, i, num_possible;
int err = -EINVAL;
BUILD_BUG_ON(ARRAY_SIZE(clk_info->parents) > ARRAY_SIZE(parent_names));
if (clk_info->type == CGU_CLK_EXT) {
clk = of_clk_get_by_name(cgu->np, clk_info->name);
if (IS_ERR(clk)) {
pr_err("%s: no external clock '%s' provided\n",
__func__, clk_info->name);
err = -ENODEV;
goto out;
}
err = clk_register_clkdev(clk, clk_info->name, NULL);
if (err) {
clk_put(clk);
goto out;
}
cgu->clocks.clks[idx] = clk;
return 0;
}
if (!clk_info->type) {
pr_err("%s: no clock type specified for '%s'\n", __func__,
clk_info->name);
goto out;
}
ingenic_clk = kzalloc_obj(*ingenic_clk);
if (!ingenic_clk) {
err = -ENOMEM;
goto out;
}
ingenic_clk->hw.init = &clk_init;
ingenic_clk->cgu = cgu;
ingenic_clk->idx = idx;
clk_init.name = clk_info->name;
clk_init.flags = clk_info->flags;
clk_init.parent_names = parent_names;
caps = clk_info->type;
if (caps & CGU_CLK_DIV) {
caps &= ~CGU_CLK_DIV;
} else if (!(caps & CGU_CLK_CUSTOM)) {
/* pass rate changes to the parent clock */
clk_init.flags |= CLK_SET_RATE_PARENT;
}
if (caps & (CGU_CLK_MUX | CGU_CLK_CUSTOM)) {
clk_init.num_parents = 0;
if (caps & CGU_CLK_MUX)
num_possible = 1 << clk_info->mux.bits;
else
num_possible = ARRAY_SIZE(clk_info->parents);
for (i = 0; i < num_possible; i++) {
if (clk_info->parents[i] == -1)
continue;
parent = cgu->clocks.clks[clk_info->parents[i]];
parent_names[clk_init.num_parents] =
__clk_get_name(parent);
clk_init.num_parents++;
}
BUG_ON(!clk_init.num_parents);
BUG_ON(clk_init.num_parents > ARRAY_SIZE(parent_names));
} else {
BUG_ON(clk_info->parents[0] == -1);
clk_init.num_parents = 1;
parent = cgu->clocks.clks[clk_info->parents[0]];
parent_names[0] = __clk_get_name(parent);
}
if (caps & CGU_CLK_CUSTOM) {
clk_init.ops = clk_info->custom.clk_ops;
caps &= ~CGU_CLK_CUSTOM;
if (caps) {
pr_err("%s: custom clock may not be combined with type 0x%x\n",
__func__, caps);
goto out;
}
} else if (caps & CGU_CLK_PLL) {
clk_init.ops = &ingenic_pll_ops;
caps &= ~CGU_CLK_PLL;
if (caps) {
pr_err("%s: PLL may not be combined with type 0x%x\n",
__func__, caps);
goto out;
}
} else {
clk_init.ops = &ingenic_clk_ops;
}
/* nothing to do for gates or fixed dividers */
caps &= ~(CGU_CLK_GATE | CGU_CLK_FIXDIV);
if (caps & CGU_CLK_MUX) {
if (!(caps & CGU_CLK_MUX_GLITCHFREE))
clk_init.flags |= CLK_SET_PARENT_GATE;
caps &= ~(CGU_CLK_MUX | CGU_CLK_MUX_GLITCHFREE);
}
if (caps) {
pr_err("%s: unknown clock type 0x%x\n", __func__, caps);
goto out;
}
clk = clk_register(NULL, &ingenic_clk->hw);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock '%s'\n", __func__,
clk_info->name);
err = PTR_ERR(clk);
goto out;
}
err = clk_register_clkdev(clk, clk_info->name, NULL);
if (err)
goto out;
cgu->clocks.clks[idx] = clk;
out:
if (err)
kfree(ingenic_clk);
return err;
}
struct ingenic_cgu *
ingenic_cgu_new(const struct ingenic_cgu_clk_info *clock_info,
unsigned num_clocks, struct device_node *np)
{
struct ingenic_cgu *cgu;
cgu = kzalloc_obj(*cgu);
if (!cgu)
goto err_out;
cgu->base = of_iomap(np, 0);
if (!cgu->base) {
pr_err("%s: failed to map CGU registers\n", __func__);
goto err_out_free;
}
cgu->np = np;
cgu->clock_info = clock_info;
cgu->clocks.clk_num = num_clocks;
spin_lock_init(&cgu->lock);
return cgu;
err_out_free:
kfree(cgu);
err_out:
return NULL;
}
int ingenic_cgu_register_clocks(struct ingenic_cgu *cgu)
{
unsigned i;
int err;
cgu->clocks.clks = kzalloc_objs(struct clk *, cgu->clocks.clk_num);
if (!cgu->clocks.clks) {
err = -ENOMEM;
goto err_out;
}
for (i = 0; i < cgu->clocks.clk_num; i++) {
err = ingenic_register_clock(cgu, i);
if (err)
goto err_out_unregister;
}
err = of_clk_add_provider(cgu->np, of_clk_src_onecell_get,
&cgu->clocks);
if (err)
goto err_out_unregister;
return 0;
err_out_unregister:
for (i = 0; i < cgu->clocks.clk_num; i++) {
if (!cgu->clocks.clks[i])
continue;
if (cgu->clock_info[i].type & CGU_CLK_EXT)
clk_put(cgu->clocks.clks[i]);
else
clk_unregister(cgu->clocks.clks[i]);
}
kfree(cgu->clocks.clks);
err_out:
return err;
}
+246
View File
@@ -0,0 +1,246 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Ingenic SoC CGU driver
*
* Copyright (c) 2013-2015 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
*/
#ifndef __DRIVERS_CLK_INGENIC_CGU_H__
#define __DRIVERS_CLK_INGENIC_CGU_H__
#include <linux/bitops.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/spinlock.h>
/**
* struct ingenic_cgu_pll_info - information about a PLL
* @reg: the offset of the PLL's control register within the CGU
* @rate_multiplier: the multiplier needed by pll rate calculation
* @m_shift: the number of bits to shift the multiplier value by (ie. the
* index of the lowest bit of the multiplier value in the PLL's
* control register)
* @m_bits: the size of the multiplier field in bits
* @m_offset: the multiplier value which encodes to 0 in the PLL's control
* register
* @n_shift: the number of bits to shift the divider value by (ie. the
* index of the lowest bit of the divider value in the PLL's
* control register)
* @n_bits: the size of the divider field in bits
* @n_offset: the divider value which encodes to 0 in the PLL's control
* register
* @od_shift: the number of bits to shift the post-VCO divider value by (ie.
* the index of the lowest bit of the post-VCO divider value in
* the PLL's control register)
* @od_bits: the size of the post-VCO divider field in bits, or 0 if no
* OD field exists (then the OD is fixed to 1)
* @od_max: the maximum post-VCO divider value
* @od_encoding: a pointer to an array mapping post-VCO divider values to
* their encoded values in the PLL control register, or -1 for
* unsupported values
* @bypass_reg: the offset of the bypass control register within the CGU
* @bypass_bit: the index of the bypass bit in the PLL control register, or
* -1 if there is no bypass bit
* @enable_bit: the index of the enable bit in the PLL control register, or
* -1 if there is no enable bit (ie, the PLL is always on)
* @stable_bit: the index of the stable bit in the PLL control register, or
* -1 if there is no stable bit
* @set_rate_hook: hook called immediately after updating the CGU register,
* before releasing the spinlock
*/
struct ingenic_cgu_pll_info {
unsigned reg;
unsigned rate_multiplier;
const s8 *od_encoding;
u8 m_shift, m_bits, m_offset;
u8 n_shift, n_bits, n_offset;
u8 od_shift, od_bits, od_max;
unsigned bypass_reg;
s8 bypass_bit;
s8 enable_bit;
s8 stable_bit;
void (*calc_m_n_od)(const struct ingenic_cgu_pll_info *pll_info,
unsigned long rate, unsigned long parent_rate,
unsigned int *m, unsigned int *n, unsigned int *od);
void (*set_rate_hook)(const struct ingenic_cgu_pll_info *pll_info,
unsigned long rate, unsigned long parent_rate);
};
/**
* struct ingenic_cgu_mux_info - information about a clock mux
* @reg: offset of the mux control register within the CGU
* @shift: number of bits to shift the mux value by (ie. the index of
* the lowest bit of the mux value within its control register)
* @bits: the size of the mux value in bits
*/
struct ingenic_cgu_mux_info {
unsigned reg;
u8 shift;
u8 bits;
};
/**
* struct ingenic_cgu_div_info - information about a divider
* @reg: offset of the divider control register within the CGU
* @shift: number of bits to left shift the divide value by (ie. the index of
* the lowest bit of the divide value within its control register)
* @div: number to divide the divider value by (i.e. if the
* effective divider value is the value written to the register
* multiplied by some constant)
* @bits: the size of the divide value in bits
* @ce_bit: the index of the change enable bit within reg, or -1 if there
* isn't one
* @busy_bit: the index of the busy bit within reg, or -1 if there isn't one
* @stop_bit: the index of the stop bit within reg, or -1 if there isn't one
* @bypass_mask: mask of parent clocks for which the divider does not apply
* @div_table: optional table to map the value read from the register to the
* actual divider value
*/
struct ingenic_cgu_div_info {
unsigned reg;
u8 shift;
u8 div;
u8 bits;
s8 ce_bit;
s8 busy_bit;
s8 stop_bit;
u8 bypass_mask;
const u8 *div_table;
};
/**
* struct ingenic_cgu_fixdiv_info - information about a fixed divider
* @div: the divider applied to the parent clock
*/
struct ingenic_cgu_fixdiv_info {
unsigned div;
};
/**
* struct ingenic_cgu_gate_info - information about a clock gate
* @reg: offset of the gate control register within the CGU
* @bit: offset of the bit in the register that controls the gate
* @clear_to_gate: if set, the clock is gated when the bit is cleared
* @delay_us: delay in microseconds after which the clock is considered stable
*/
struct ingenic_cgu_gate_info {
unsigned reg;
u8 bit;
bool clear_to_gate;
u16 delay_us;
};
/**
* struct ingenic_cgu_custom_info - information about a custom (SoC) clock
* @clk_ops: custom clock operation callbacks
*/
struct ingenic_cgu_custom_info {
const struct clk_ops *clk_ops;
};
/**
* struct ingenic_cgu_clk_info - information about a clock
* @name: name of the clock
* @type: a bitmask formed from CGU_CLK_* values
* @flags: common clock flags to set on this clock
* @parents: an array of the indices of potential parents of this clock
* within the clock_info array of the CGU, or -1 in entries
* which correspond to no valid parent
* @pll: information valid if type includes CGU_CLK_PLL
* @gate: information valid if type includes CGU_CLK_GATE
* @mux: information valid if type includes CGU_CLK_MUX
* @div: information valid if type includes CGU_CLK_DIV
* @fixdiv: information valid if type includes CGU_CLK_FIXDIV
* @custom: information valid if type includes CGU_CLK_CUSTOM
*/
struct ingenic_cgu_clk_info {
const char *name;
enum {
CGU_CLK_NONE = 0,
CGU_CLK_EXT = BIT(0),
CGU_CLK_PLL = BIT(1),
CGU_CLK_GATE = BIT(2),
CGU_CLK_MUX = BIT(3),
CGU_CLK_MUX_GLITCHFREE = BIT(4),
CGU_CLK_DIV = BIT(5),
CGU_CLK_FIXDIV = BIT(6),
CGU_CLK_CUSTOM = BIT(7),
} type;
unsigned long flags;
int parents[4];
union {
struct ingenic_cgu_pll_info pll;
struct {
struct ingenic_cgu_gate_info gate;
struct ingenic_cgu_mux_info mux;
struct ingenic_cgu_div_info div;
struct ingenic_cgu_fixdiv_info fixdiv;
};
struct ingenic_cgu_custom_info custom;
};
};
/**
* struct ingenic_cgu - data about the CGU
* @np: the device tree node that caused the CGU to be probed
* @base: the ioremap'ed base address of the CGU registers
* @clock_info: an array containing information about implemented clocks
* @clocks: used to provide clocks to DT, allows lookup of struct clk*
* @lock: lock to be held whilst manipulating CGU registers
*/
struct ingenic_cgu {
struct device_node *np;
void __iomem *base;
const struct ingenic_cgu_clk_info *clock_info;
struct clk_onecell_data clocks;
spinlock_t lock;
};
/**
* struct ingenic_clk - private data for a clock
* @hw: see Documentation/driver-api/clk.rst
* @cgu: a pointer to the CGU data
* @idx: the index of this clock in cgu->clock_info
*/
struct ingenic_clk {
struct clk_hw hw;
struct ingenic_cgu *cgu;
unsigned idx;
};
#define to_ingenic_clk(_hw) container_of(_hw, struct ingenic_clk, hw)
/**
* ingenic_cgu_new() - create a new CGU instance
* @clock_info: an array of clock information structures describing the clocks
* which are implemented by the CGU
* @num_clocks: the number of entries in clock_info
* @np: the device tree node which causes this CGU to be probed
*
* Return: a pointer to the CGU instance if initialisation is successful,
* otherwise NULL.
*/
struct ingenic_cgu *
ingenic_cgu_new(const struct ingenic_cgu_clk_info *clock_info,
unsigned num_clocks, struct device_node *np);
/**
* ingenic_cgu_register_clocks() - Registers the clocks
* @cgu: pointer to cgu data
*
* Register the clocks described by the CGU with the common clock framework.
*
* Return: 0 on success or -errno if unsuccessful.
*/
int ingenic_cgu_register_clocks(struct ingenic_cgu *cgu);
#endif /* __DRIVERS_CLK_INGENIC_CGU_H__ */
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Ingenic JZ4725B SoC CGU driver
*
* Copyright (C) 2018 Paul Cercueil
* Author: Paul Cercueil <paul@crapouillou.net>
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <dt-bindings/clock/ingenic,jz4725b-cgu.h>
#include "cgu.h"
#include "pm.h"
/* CGU register offsets */
#define CGU_REG_CPCCR 0x00
#define CGU_REG_LCR 0x04
#define CGU_REG_CPPCR 0x10
#define CGU_REG_CLKGR 0x20
#define CGU_REG_OPCR 0x24
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LPCDR 0x64
#define CGU_REG_MSCCDR 0x68
#define CGU_REG_SSICDR 0x74
#define CGU_REG_CIMCDR 0x78
/* bits within the LCR register */
#define LCR_SLEEP BIT(0)
static struct ingenic_cgu *cgu;
static const s8 pll_od_encoding[4] = {
0x0, 0x1, -1, 0x3,
};
static const u8 jz4725b_cgu_cpccr_div_table[] = {
1, 2, 3, 4, 6, 8,
};
static const u8 jz4725b_cgu_pll_half_div_table[] = {
2, 1,
};
static const struct ingenic_cgu_clk_info jz4725b_cgu_clocks[] = {
/* External clocks */
[JZ4725B_CLK_EXT] = { "ext", CGU_CLK_EXT },
[JZ4725B_CLK_OSC32K] = { "osc32k", CGU_CLK_EXT },
[JZ4725B_CLK_PLL] = {
"pll", CGU_CLK_PLL,
.parents = { JZ4725B_CLK_EXT, -1, -1, -1 },
.pll = {
.reg = CGU_REG_CPPCR,
.rate_multiplier = 1,
.m_shift = 23,
.m_bits = 9,
.m_offset = 2,
.n_shift = 18,
.n_bits = 5,
.n_offset = 2,
.od_shift = 16,
.od_bits = 2,
.od_max = 4,
.od_encoding = pll_od_encoding,
.stable_bit = 10,
.bypass_reg = CGU_REG_CPPCR,
.bypass_bit = 9,
.enable_bit = 8,
},
},
/* Muxes & dividers */
[JZ4725B_CLK_PLL_HALF] = {
"pll half", CGU_CLK_DIV,
.parents = { JZ4725B_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 21, 1, 1, -1, -1, -1, 0,
jz4725b_cgu_pll_half_div_table,
},
},
[JZ4725B_CLK_CCLK] = {
"cclk", CGU_CLK_DIV,
/*
* Disabling the CPU clock or any parent clocks will hang the
* system; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4725B_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1, 0,
jz4725b_cgu_cpccr_div_table,
},
},
[JZ4725B_CLK_HCLK] = {
"hclk", CGU_CLK_DIV,
.parents = { JZ4725B_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1, 0,
jz4725b_cgu_cpccr_div_table,
},
},
[JZ4725B_CLK_PCLK] = {
"pclk", CGU_CLK_DIV,
.parents = { JZ4725B_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 8, 1, 4, 22, -1, -1, 0,
jz4725b_cgu_cpccr_div_table,
},
},
[JZ4725B_CLK_MCLK] = {
"mclk", CGU_CLK_DIV,
/*
* Disabling MCLK or its parents will render DRAM
* inaccessible; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4725B_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 12, 1, 4, 22, -1, -1, 0,
jz4725b_cgu_cpccr_div_table,
},
},
[JZ4725B_CLK_IPU] = {
"ipu", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4725B_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 16, 1, 4, 22, -1, -1, 0,
jz4725b_cgu_cpccr_div_table,
},
.gate = { CGU_REG_CLKGR, 13 },
},
[JZ4725B_CLK_LCD] = {
"lcd", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4725B_CLK_PLL_HALF, -1, -1, -1 },
.div = { CGU_REG_LPCDR, 0, 1, 11, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 9 },
},
[JZ4725B_CLK_I2S] = {
"i2s", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4725B_CLK_EXT, JZ4725B_CLK_PLL_HALF, -1, -1 },
.mux = { CGU_REG_CPCCR, 31, 1 },
.div = { CGU_REG_I2SCDR, 0, 1, 9, -1, -1, -1 },
},
[JZ4725B_CLK_SPI] = {
"spi", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4725B_CLK_EXT, JZ4725B_CLK_PLL, -1, -1 },
.mux = { CGU_REG_SSICDR, 31, 1 },
.div = { CGU_REG_SSICDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 4 },
},
[JZ4725B_CLK_MMC_MUX] = {
"mmc_mux", CGU_CLK_DIV,
.parents = { JZ4725B_CLK_PLL_HALF, -1, -1, -1 },
.div = { CGU_REG_MSCCDR, 0, 1, 5, -1, -1, -1 },
},
[JZ4725B_CLK_UDC] = {
"udc", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4725B_CLK_EXT, JZ4725B_CLK_PLL_HALF, -1, -1 },
.mux = { CGU_REG_CPCCR, 29, 1 },
.div = { CGU_REG_CPCCR, 23, 1, 6, -1, -1, -1 },
},
/* Gate-only clocks */
[JZ4725B_CLK_UART] = {
"uart", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 0 },
},
[JZ4725B_CLK_DMA] = {
"dma", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 12 },
},
[JZ4725B_CLK_ADC] = {
"adc", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 7 },
},
[JZ4725B_CLK_I2C] = {
"i2c", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 3 },
},
[JZ4725B_CLK_AIC] = {
"aic", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 5 },
},
[JZ4725B_CLK_MMC0] = {
"mmc0", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_MMC_MUX, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 6 },
},
[JZ4725B_CLK_MMC1] = {
"mmc1", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_MMC_MUX, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 16 },
},
[JZ4725B_CLK_BCH] = {
"bch", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_MCLK/* not sure */, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 11 },
},
[JZ4725B_CLK_TCU] = {
"tcu", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_EXT/* not sure */, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 1 },
},
[JZ4725B_CLK_EXT512] = {
"ext/512", CGU_CLK_FIXDIV,
.parents = { JZ4725B_CLK_EXT },
/* Doc calls it EXT512, but it seems to be /256... */
.fixdiv = { 256 },
},
[JZ4725B_CLK_RTC] = {
"rtc", CGU_CLK_MUX,
.parents = { JZ4725B_CLK_EXT512, JZ4725B_CLK_OSC32K, -1, -1 },
.mux = { CGU_REG_OPCR, 2, 1},
},
[JZ4725B_CLK_UDC_PHY] = {
"udc_phy", CGU_CLK_GATE,
.parents = { JZ4725B_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_OPCR, 6, true },
},
};
static void __init jz4725b_cgu_init(struct device_node *np)
{
int retval;
cgu = ingenic_cgu_new(jz4725b_cgu_clocks,
ARRAY_SIZE(jz4725b_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval)
pr_err("%s: failed to register CGU Clocks\n", __func__);
ingenic_cgu_register_syscore(cgu);
}
CLK_OF_DECLARE_DRIVER(jz4725b_cgu, "ingenic,jz4725b-cgu", jz4725b_cgu_init);
@@ -0,0 +1,271 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Ingenic JZ4740 SoC CGU driver
*
* Copyright (c) 2015 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <dt-bindings/clock/ingenic,jz4740-cgu.h>
#include "cgu.h"
#include "pm.h"
/* CGU register offsets */
#define CGU_REG_CPCCR 0x00
#define CGU_REG_LCR 0x04
#define CGU_REG_CPPCR 0x10
#define CGU_REG_CLKGR 0x20
#define CGU_REG_SCR 0x24
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LPCDR 0x64
#define CGU_REG_MSCCDR 0x68
#define CGU_REG_UHCCDR 0x6c
#define CGU_REG_SSICDR 0x74
/* bits within a PLL control register */
#define PLLCTL_M_SHIFT 23
#define PLLCTL_M_MASK (0x1ff << PLLCTL_M_SHIFT)
#define PLLCTL_N_SHIFT 18
#define PLLCTL_N_MASK (0x1f << PLLCTL_N_SHIFT)
#define PLLCTL_OD_SHIFT 16
#define PLLCTL_OD_MASK (0x3 << PLLCTL_OD_SHIFT)
#define PLLCTL_STABLE (1 << 10)
#define PLLCTL_BYPASS (1 << 9)
#define PLLCTL_ENABLE (1 << 8)
/* bits within the LCR register */
#define LCR_SLEEP (1 << 0)
/* bits within the CLKGR register */
#define CLKGR_UDC (1 << 11)
static struct ingenic_cgu *cgu;
static const s8 pll_od_encoding[4] = {
0x0, 0x1, -1, 0x3,
};
static const u8 jz4740_cgu_cpccr_div_table[] = {
1, 2, 3, 4, 6, 8, 12, 16, 24, 32,
};
static const u8 jz4740_cgu_pll_half_div_table[] = {
2, 1,
};
static const struct ingenic_cgu_clk_info jz4740_cgu_clocks[] = {
/* External clocks */
[JZ4740_CLK_EXT] = { "ext", CGU_CLK_EXT },
[JZ4740_CLK_RTC] = { "rtc", CGU_CLK_EXT },
[JZ4740_CLK_PLL] = {
"pll", CGU_CLK_PLL,
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
.pll = {
.reg = CGU_REG_CPPCR,
.rate_multiplier = 1,
.m_shift = 23,
.m_bits = 9,
.m_offset = 2,
.n_shift = 18,
.n_bits = 5,
.n_offset = 2,
.od_shift = 16,
.od_bits = 2,
.od_max = 4,
.od_encoding = pll_od_encoding,
.stable_bit = 10,
.bypass_reg = CGU_REG_CPPCR,
.bypass_bit = 9,
.enable_bit = 8,
},
},
/* Muxes & dividers */
[JZ4740_CLK_PLL_HALF] = {
"pll half", CGU_CLK_DIV,
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 21, 1, 1, -1, -1, -1, 0,
jz4740_cgu_pll_half_div_table,
},
},
[JZ4740_CLK_CCLK] = {
"cclk", CGU_CLK_DIV,
/*
* Disabling the CPU clock or any parent clocks will hang the
* system; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1, 0,
jz4740_cgu_cpccr_div_table,
},
},
[JZ4740_CLK_HCLK] = {
"hclk", CGU_CLK_DIV,
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1, 0,
jz4740_cgu_cpccr_div_table,
},
},
[JZ4740_CLK_PCLK] = {
"pclk", CGU_CLK_DIV,
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 8, 1, 4, 22, -1, -1, 0,
jz4740_cgu_cpccr_div_table,
},
},
[JZ4740_CLK_MCLK] = {
"mclk", CGU_CLK_DIV,
/*
* Disabling MCLK or its parents will render DRAM
* inaccessible; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4740_CLK_PLL, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 12, 1, 4, 22, -1, -1, 0,
jz4740_cgu_cpccr_div_table,
},
},
[JZ4740_CLK_LCD] = {
"lcd", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
.div = {
CGU_REG_CPCCR, 16, 1, 5, 22, -1, -1, 0,
jz4740_cgu_cpccr_div_table,
},
.gate = { CGU_REG_CLKGR, 10 },
},
[JZ4740_CLK_LCD_PCLK] = {
"lcd_pclk", CGU_CLK_DIV,
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
.div = { CGU_REG_LPCDR, 0, 1, 11, -1, -1, -1 },
},
[JZ4740_CLK_I2S] = {
"i2s", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL_HALF, -1, -1 },
.mux = { CGU_REG_CPCCR, 31, 1 },
.div = { CGU_REG_I2SCDR, 0, 1, 9, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 6 },
},
[JZ4740_CLK_SPI] = {
"spi", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL, -1, -1 },
.mux = { CGU_REG_SSICDR, 31, 1 },
.div = { CGU_REG_SSICDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 4 },
},
[JZ4740_CLK_MMC] = {
"mmc", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
.div = { CGU_REG_MSCCDR, 0, 1, 5, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 7 },
},
[JZ4740_CLK_UHC] = {
"uhc", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
.div = { CGU_REG_UHCCDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 14 },
},
[JZ4740_CLK_UDC] = {
"udc", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL_HALF, -1, -1 },
.mux = { CGU_REG_CPCCR, 29, 1 },
.div = { CGU_REG_CPCCR, 23, 1, 6, -1, -1, -1 },
.gate = { CGU_REG_SCR, 6, true },
},
/* Gate-only clocks */
[JZ4740_CLK_UART0] = {
"uart0", CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 0 },
},
[JZ4740_CLK_UART1] = {
"uart1", CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 15 },
},
[JZ4740_CLK_DMA] = {
"dma", CGU_CLK_GATE,
.parents = { JZ4740_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 12 },
},
[JZ4740_CLK_IPU] = {
"ipu", CGU_CLK_GATE,
.parents = { JZ4740_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 13 },
},
[JZ4740_CLK_ADC] = {
"adc", CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 8 },
},
[JZ4740_CLK_I2C] = {
"i2c", CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 3 },
},
[JZ4740_CLK_AIC] = {
"aic", CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 5 },
},
[JZ4740_CLK_TCU] = {
"tcu", CGU_CLK_GATE,
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 1 },
},
};
static void __init jz4740_cgu_init(struct device_node *np)
{
int retval;
cgu = ingenic_cgu_new(jz4740_cgu_clocks,
ARRAY_SIZE(jz4740_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval)
pr_err("%s: failed to register CGU Clocks\n", __func__);
ingenic_cgu_register_syscore(cgu);
}
CLK_OF_DECLARE_DRIVER(jz4740_cgu, "ingenic,jz4740-cgu", jz4740_cgu_init);
@@ -0,0 +1,346 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Ingenic JZ4755 SoC CGU driver
* Heavily based on JZ4725b CGU driver
*
* Copyright (C) 2022 Siarhei Volkau
* Author: Siarhei Volkau <lis8215@gmail.com>
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <dt-bindings/clock/ingenic,jz4755-cgu.h>
#include "cgu.h"
#include "pm.h"
/* CGU register offsets */
#define CGU_REG_CPCCR 0x00
#define CGU_REG_CPPCR 0x10
#define CGU_REG_CLKGR 0x20
#define CGU_REG_OPCR 0x24
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LPCDR 0x64
#define CGU_REG_MSCCDR 0x68
#define CGU_REG_SSICDR 0x74
#define CGU_REG_CIMCDR 0x7C
static struct ingenic_cgu *cgu;
static const s8 pll_od_encoding[4] = {
0x0, 0x1, -1, 0x3,
};
static const u8 jz4755_cgu_cpccr_div_table[] = {
1, 2, 3, 4, 6, 8,
};
static const u8 jz4755_cgu_pll_half_div_table[] = {
2, 1,
};
static const struct ingenic_cgu_clk_info jz4755_cgu_clocks[] = {
/* External clocks */
[JZ4755_CLK_EXT] = { "ext", CGU_CLK_EXT },
[JZ4755_CLK_OSC32K] = { "osc32k", CGU_CLK_EXT },
[JZ4755_CLK_PLL] = {
"pll", CGU_CLK_PLL,
.parents = { JZ4755_CLK_EXT, },
.pll = {
.reg = CGU_REG_CPPCR,
.rate_multiplier = 1,
.m_shift = 23,
.m_bits = 9,
.m_offset = 2,
.n_shift = 18,
.n_bits = 5,
.n_offset = 2,
.od_shift = 16,
.od_bits = 2,
.od_max = 4,
.od_encoding = pll_od_encoding,
.stable_bit = 10,
.bypass_reg = CGU_REG_CPPCR,
.bypass_bit = 9,
.enable_bit = 8,
},
},
/* Muxes & dividers */
[JZ4755_CLK_PLL_HALF] = {
"pll half", CGU_CLK_DIV,
.parents = { JZ4755_CLK_PLL, },
.div = {
CGU_REG_CPCCR, 21, 1, 1, -1, -1, -1, 0,
jz4755_cgu_pll_half_div_table,
},
},
[JZ4755_CLK_EXT_HALF] = {
"ext half", CGU_CLK_DIV,
.parents = { JZ4755_CLK_EXT, },
.div = {
CGU_REG_CPCCR, 30, 1, 1, -1, -1, -1, 0,
NULL,
},
},
[JZ4755_CLK_CCLK] = {
"cclk", CGU_CLK_DIV,
.parents = { JZ4755_CLK_PLL, },
.div = {
CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1, 0,
jz4755_cgu_cpccr_div_table,
},
},
[JZ4755_CLK_H0CLK] = {
"hclk", CGU_CLK_DIV,
.parents = { JZ4755_CLK_PLL, },
.div = {
CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1, 0,
jz4755_cgu_cpccr_div_table,
},
},
[JZ4755_CLK_PCLK] = {
"pclk", CGU_CLK_DIV,
.parents = { JZ4755_CLK_PLL, },
.div = {
CGU_REG_CPCCR, 8, 1, 4, 22, -1, -1, 0,
jz4755_cgu_cpccr_div_table,
},
},
[JZ4755_CLK_MCLK] = {
"mclk", CGU_CLK_DIV,
.parents = { JZ4755_CLK_PLL, },
.div = {
CGU_REG_CPCCR, 12, 1, 4, 22, -1, -1, 0,
jz4755_cgu_cpccr_div_table,
},
},
[JZ4755_CLK_H1CLK] = {
"h1clk", CGU_CLK_DIV,
.parents = { JZ4755_CLK_PLL, },
.div = {
CGU_REG_CPCCR, 16, 1, 4, 22, -1, -1, 0,
jz4755_cgu_cpccr_div_table,
},
},
[JZ4755_CLK_UDC] = {
"udc", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, JZ4755_CLK_PLL_HALF, },
.mux = { CGU_REG_CPCCR, 29, 1 },
.div = { CGU_REG_CPCCR, 23, 1, 6, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 10 },
},
[JZ4755_CLK_LCD] = {
"lcd", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4755_CLK_PLL_HALF, },
.div = { CGU_REG_LPCDR, 0, 1, 11, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 9 },
},
[JZ4755_CLK_MMC] = {
"mmc", CGU_CLK_DIV,
.parents = { JZ4755_CLK_PLL_HALF, },
.div = { CGU_REG_MSCCDR, 0, 1, 5, -1, -1, -1 },
},
[JZ4755_CLK_I2S] = {
"i2s", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4755_CLK_EXT_HALF, JZ4755_CLK_PLL_HALF, },
.mux = { CGU_REG_CPCCR, 31, 1 },
.div = { CGU_REG_I2SCDR, 0, 1, 9, -1, -1, -1 },
},
[JZ4755_CLK_SPI] = {
"spi", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4755_CLK_PLL_HALF, },
.div = { CGU_REG_SSICDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 4 },
},
[JZ4755_CLK_TVE] = {
"tve", CGU_CLK_MUX | CGU_CLK_GATE,
.parents = { JZ4755_CLK_LCD, JZ4755_CLK_EXT, },
.mux = { CGU_REG_LPCDR, 31, 1 },
.gate = { CGU_REG_CLKGR, 18 },
},
[JZ4755_CLK_RTC] = {
"rtc", CGU_CLK_MUX | CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT512, JZ4755_CLK_OSC32K, },
.mux = { CGU_REG_OPCR, 2, 1},
.gate = { CGU_REG_CLKGR, 2 },
},
[JZ4755_CLK_CIM] = {
"cim", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4755_CLK_PLL_HALF, },
.div = { CGU_REG_CIMCDR, 0, 1, 8, -1, -1, -1 },
.gate = { CGU_REG_CLKGR, 8 },
},
/* Gate-only clocks */
[JZ4755_CLK_UART0] = {
"uart0", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, },
.gate = { CGU_REG_CLKGR, 0 },
},
[JZ4755_CLK_UART1] = {
"uart1", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, },
.gate = { CGU_REG_CLKGR, 14 },
},
[JZ4755_CLK_UART2] = {
"uart2", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, },
.gate = { CGU_REG_CLKGR, 15 },
},
[JZ4755_CLK_ADC] = {
"adc", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, },
.gate = { CGU_REG_CLKGR, 7 },
},
[JZ4755_CLK_AIC] = {
"aic", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, },
.gate = { CGU_REG_CLKGR, 5 },
},
[JZ4755_CLK_I2C] = {
"i2c", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, },
.gate = { CGU_REG_CLKGR, 3 },
},
[JZ4755_CLK_BCH] = {
"bch", CGU_CLK_GATE,
.parents = { JZ4755_CLK_H1CLK, },
.gate = { CGU_REG_CLKGR, 11 },
},
[JZ4755_CLK_TCU] = {
"tcu", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT, },
.gate = { CGU_REG_CLKGR, 1 },
},
[JZ4755_CLK_DMA] = {
"dma", CGU_CLK_GATE,
.parents = { JZ4755_CLK_PCLK, },
.gate = { CGU_REG_CLKGR, 12 },
},
[JZ4755_CLK_MMC0] = {
"mmc0", CGU_CLK_GATE,
.parents = { JZ4755_CLK_MMC, },
.gate = { CGU_REG_CLKGR, 6 },
},
[JZ4755_CLK_MMC1] = {
"mmc1", CGU_CLK_GATE,
.parents = { JZ4755_CLK_MMC, },
.gate = { CGU_REG_CLKGR, 16 },
},
[JZ4755_CLK_AUX_CPU] = {
"aux_cpu", CGU_CLK_GATE,
.parents = { JZ4755_CLK_H1CLK, },
.gate = { CGU_REG_CLKGR, 24 },
},
[JZ4755_CLK_AHB1] = {
"ahb1", CGU_CLK_GATE,
.parents = { JZ4755_CLK_H1CLK, },
.gate = { CGU_REG_CLKGR, 23 },
},
[JZ4755_CLK_IDCT] = {
"idct", CGU_CLK_GATE,
.parents = { JZ4755_CLK_H1CLK, },
.gate = { CGU_REG_CLKGR, 22 },
},
[JZ4755_CLK_DB] = {
"db", CGU_CLK_GATE,
.parents = { JZ4755_CLK_H1CLK, },
.gate = { CGU_REG_CLKGR, 21 },
},
[JZ4755_CLK_ME] = {
"me", CGU_CLK_GATE,
.parents = { JZ4755_CLK_H1CLK, },
.gate = { CGU_REG_CLKGR, 20 },
},
[JZ4755_CLK_MC] = {
"mc", CGU_CLK_GATE,
.parents = { JZ4755_CLK_H1CLK, },
.gate = { CGU_REG_CLKGR, 19 },
},
[JZ4755_CLK_TSSI] = {
"tssi", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF/* not sure */, },
.gate = { CGU_REG_CLKGR, 17 },
},
[JZ4755_CLK_IPU] = {
"ipu", CGU_CLK_GATE,
.parents = { JZ4755_CLK_PLL_HALF/* not sure */, },
.gate = { CGU_REG_CLKGR, 13 },
},
[JZ4755_CLK_EXT512] = {
"ext/512", CGU_CLK_FIXDIV,
.parents = { JZ4755_CLK_EXT, },
.fixdiv = { 512 },
},
[JZ4755_CLK_UDC_PHY] = {
"udc_phy", CGU_CLK_GATE,
.parents = { JZ4755_CLK_EXT_HALF, },
.gate = { CGU_REG_OPCR, 6, true },
},
};
static void __init jz4755_cgu_init(struct device_node *np)
{
int retval;
cgu = ingenic_cgu_new(jz4755_cgu_clocks,
ARRAY_SIZE(jz4755_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval)
pr_err("%s: failed to register CGU Clocks\n", __func__);
ingenic_cgu_register_syscore(cgu);
}
/*
* CGU has some children devices, this is useful for probing children devices
* in the case where the device node is compatible with "simple-mfd".
*/
CLK_OF_DECLARE_DRIVER(jz4755_cgu, "ingenic,jz4755-cgu", jz4755_cgu_init);
@@ -0,0 +1,446 @@
// SPDX-License-Identifier: GPL-2.0
/*
* JZ4760 SoC CGU driver
* Copyright 2018, Paul Cercueil <paul@crapouillou.net>
*/
#include <linux/bitops.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <dt-bindings/clock/ingenic,jz4760-cgu.h>
#include "cgu.h"
#include "pm.h"
#define MHZ (1000 * 1000)
/*
* CPM registers offset address definition
*/
#define CGU_REG_CPCCR 0x00
#define CGU_REG_LCR 0x04
#define CGU_REG_CPPCR0 0x10
#define CGU_REG_CLKGR0 0x20
#define CGU_REG_OPCR 0x24
#define CGU_REG_CLKGR1 0x28
#define CGU_REG_CPPCR1 0x30
#define CGU_REG_USBPCR 0x3c
#define CGU_REG_USBCDR 0x50
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LPCDR 0x64
#define CGU_REG_MSCCDR 0x68
#define CGU_REG_UHCCDR 0x6c
#define CGU_REG_SSICDR 0x74
#define CGU_REG_CIMCDR 0x7c
#define CGU_REG_GPSCDR 0x80
#define CGU_REG_PCMCDR 0x84
#define CGU_REG_GPUCDR 0x88
static const s8 pll_od_encoding[8] = {
0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3,
};
static const u8 jz4760_cgu_cpccr_div_table[] = {
1, 2, 3, 4, 6, 8,
};
static const u8 jz4760_cgu_pll_half_div_table[] = {
2, 1,
};
static void
jz4760_cgu_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
unsigned long rate, unsigned long parent_rate,
unsigned int *pm, unsigned int *pn, unsigned int *pod)
{
unsigned int m, n, od, m_max = (1 << pll_info->m_bits) - 1;
/* The frequency after the N divider must be between 1 and 50 MHz. */
n = parent_rate / (1 * MHZ);
/* The N divider must be >= 2. */
n = clamp_val(n, 2, 1 << pll_info->n_bits);
rate /= MHZ;
parent_rate /= MHZ;
for (m = m_max; m >= m_max && n >= 2; n--) {
m = rate * n / parent_rate;
od = m & 1;
m <<= od;
}
*pm = m;
*pn = n + 1;
*pod = 1 << od;
}
static const struct ingenic_cgu_clk_info jz4760_cgu_clocks[] = {
/* External clocks */
[JZ4760_CLK_EXT] = { "ext", CGU_CLK_EXT },
[JZ4760_CLK_OSC32K] = { "osc32k", CGU_CLK_EXT },
/* PLLs */
[JZ4760_CLK_PLL0] = {
"pll0", CGU_CLK_PLL,
.parents = { JZ4760_CLK_EXT },
.pll = {
.reg = CGU_REG_CPPCR0,
.rate_multiplier = 1,
.m_shift = 23,
.m_bits = 8,
.m_offset = 0,
.n_shift = 18,
.n_bits = 4,
.n_offset = 0,
.od_shift = 16,
.od_bits = 2,
.od_max = 8,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_CPPCR0,
.bypass_bit = 9,
.enable_bit = 8,
.stable_bit = 10,
.calc_m_n_od = jz4760_cgu_calc_m_n_od,
},
},
[JZ4760_CLK_PLL1] = {
/* TODO: PLL1 can depend on PLL0 */
"pll1", CGU_CLK_PLL,
.parents = { JZ4760_CLK_EXT },
.pll = {
.reg = CGU_REG_CPPCR1,
.rate_multiplier = 1,
.m_shift = 23,
.m_bits = 8,
.m_offset = 0,
.n_shift = 18,
.n_bits = 4,
.n_offset = 0,
.od_shift = 16,
.od_bits = 2,
.od_max = 8,
.od_encoding = pll_od_encoding,
.bypass_bit = -1,
.enable_bit = 7,
.stable_bit = 6,
.calc_m_n_od = jz4760_cgu_calc_m_n_od,
},
},
/* Main clocks */
[JZ4760_CLK_CCLK] = {
"cclk", CGU_CLK_DIV,
/*
* Disabling the CPU clock or any parent clocks will hang the
* system; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4760_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1, 0,
jz4760_cgu_cpccr_div_table,
},
},
[JZ4760_CLK_HCLK] = {
"hclk", CGU_CLK_DIV,
.parents = { JZ4760_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1, 0,
jz4760_cgu_cpccr_div_table,
},
},
[JZ4760_CLK_SCLK] = {
"sclk", CGU_CLK_DIV,
.parents = { JZ4760_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 24, 1, 4, 22, -1, -1, 0,
jz4760_cgu_cpccr_div_table,
},
},
[JZ4760_CLK_H2CLK] = {
"h2clk", CGU_CLK_DIV,
.parents = { JZ4760_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 16, 1, 4, 22, -1, -1, 0,
jz4760_cgu_cpccr_div_table,
},
},
[JZ4760_CLK_MCLK] = {
"mclk", CGU_CLK_DIV,
/*
* Disabling MCLK or its parents will render DRAM
* inaccessible; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4760_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 12, 1, 4, 22, -1, -1, 0,
jz4760_cgu_cpccr_div_table,
},
},
[JZ4760_CLK_PCLK] = {
"pclk", CGU_CLK_DIV,
.parents = { JZ4760_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 8, 1, 4, 22, -1, -1, 0,
jz4760_cgu_cpccr_div_table,
},
},
/* Divided clocks */
[JZ4760_CLK_PLL0_HALF] = {
"pll0_half", CGU_CLK_DIV,
.parents = { JZ4760_CLK_PLL0 },
.div = {
CGU_REG_CPCCR, 21, 1, 1, 22, -1, -1, 0,
jz4760_cgu_pll_half_div_table,
},
},
/* Those divided clocks can connect to PLL0 or PLL1 */
[JZ4760_CLK_UHC] = {
"uhc", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4760_CLK_PLL0_HALF, JZ4760_CLK_PLL1, },
.mux = { CGU_REG_UHCCDR, 31, 1 },
.div = { CGU_REG_UHCCDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 24 },
},
[JZ4760_CLK_GPU] = {
"gpu", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4760_CLK_PLL0_HALF, JZ4760_CLK_PLL1, },
.mux = { CGU_REG_GPUCDR, 31, 1 },
.div = { CGU_REG_GPUCDR, 0, 1, 3, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 9 },
},
[JZ4760_CLK_LPCLK_DIV] = {
"lpclk_div", CGU_CLK_DIV | CGU_CLK_MUX,
.parents = { JZ4760_CLK_PLL0_HALF, JZ4760_CLK_PLL1, },
.mux = { CGU_REG_LPCDR, 29, 1 },
.div = { CGU_REG_LPCDR, 0, 1, 11, -1, -1, -1 },
},
[JZ4760_CLK_TVE] = {
"tve", CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4760_CLK_LPCLK_DIV, JZ4760_CLK_EXT, },
.mux = { CGU_REG_LPCDR, 31, 1 },
.gate = { CGU_REG_CLKGR0, 27 },
},
[JZ4760_CLK_LPCLK] = {
"lpclk", CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4760_CLK_LPCLK_DIV, JZ4760_CLK_TVE, },
.mux = { CGU_REG_LPCDR, 30, 1 },
.gate = { CGU_REG_CLKGR0, 28 },
},
[JZ4760_CLK_GPS] = {
"gps", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4760_CLK_PLL0_HALF, JZ4760_CLK_PLL1, },
.mux = { CGU_REG_GPSCDR, 31, 1 },
.div = { CGU_REG_GPSCDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 22 },
},
/* Those divided clocks can connect to EXT, PLL0 or PLL1 */
[JZ4760_CLK_PCM] = {
"pcm", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4760_CLK_EXT, -1,
JZ4760_CLK_PLL0_HALF, JZ4760_CLK_PLL1 },
.mux = { CGU_REG_PCMCDR, 30, 2 },
.div = { CGU_REG_PCMCDR, 0, 1, 9, -1, -1, -1, BIT(0) },
.gate = { CGU_REG_CLKGR1, 8 },
},
[JZ4760_CLK_I2S] = {
"i2s", CGU_CLK_DIV | CGU_CLK_MUX,
.parents = { JZ4760_CLK_EXT, -1,
JZ4760_CLK_PLL0_HALF, JZ4760_CLK_PLL1 },
.mux = { CGU_REG_I2SCDR, 30, 2 },
.div = { CGU_REG_I2SCDR, 0, 1, 9, -1, -1, -1, BIT(0) },
},
[JZ4760_CLK_OTG] = {
"usb", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4760_CLK_EXT, -1,
JZ4760_CLK_PLL0_HALF, JZ4760_CLK_PLL1 },
.mux = { CGU_REG_USBCDR, 30, 2 },
.div = { CGU_REG_USBCDR, 0, 1, 8, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 2 },
},
/* Those divided clocks can connect to EXT or PLL0 */
[JZ4760_CLK_MMC_MUX] = {
"mmc_mux", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4760_CLK_EXT, JZ4760_CLK_PLL0_HALF, },
.mux = { CGU_REG_MSCCDR, 31, 1 },
.div = { CGU_REG_MSCCDR, 0, 1, 6, -1, -1, -1, BIT(0) },
},
[JZ4760_CLK_SSI_MUX] = {
"ssi_mux", CGU_CLK_DIV | CGU_CLK_MUX,
.parents = { JZ4760_CLK_EXT, JZ4760_CLK_PLL0_HALF, },
.mux = { CGU_REG_SSICDR, 31, 1 },
.div = { CGU_REG_SSICDR, 0, 1, 6, -1, -1, -1, BIT(0) },
},
/* These divided clock can connect to PLL0 only */
[JZ4760_CLK_CIM] = {
"cim", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4760_CLK_PLL0_HALF },
.div = { CGU_REG_CIMCDR, 0, 1, 8, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 26 },
},
/* Gate-only clocks */
[JZ4760_CLK_SSI0] = {
"ssi0", CGU_CLK_GATE,
.parents = { JZ4760_CLK_SSI_MUX, },
.gate = { CGU_REG_CLKGR0, 4 },
},
[JZ4760_CLK_SSI1] = {
"ssi1", CGU_CLK_GATE,
.parents = { JZ4760_CLK_SSI_MUX, },
.gate = { CGU_REG_CLKGR0, 19 },
},
[JZ4760_CLK_SSI2] = {
"ssi2", CGU_CLK_GATE,
.parents = { JZ4760_CLK_SSI_MUX, },
.gate = { CGU_REG_CLKGR0, 20 },
},
[JZ4760_CLK_DMA] = {
"dma", CGU_CLK_GATE,
.parents = { JZ4760_CLK_H2CLK, },
.gate = { CGU_REG_CLKGR0, 21 },
},
[JZ4760_CLK_MDMA] = {
"mdma", CGU_CLK_GATE,
.parents = { JZ4760_CLK_HCLK, },
.gate = { CGU_REG_CLKGR0, 25 },
},
[JZ4760_CLK_BDMA] = {
"bdma", CGU_CLK_GATE,
.parents = { JZ4760_CLK_HCLK, },
.gate = { CGU_REG_CLKGR1, 0 },
},
[JZ4760_CLK_I2C0] = {
"i2c0", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 5 },
},
[JZ4760_CLK_I2C1] = {
"i2c1", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 6 },
},
[JZ4760_CLK_UART0] = {
"uart0", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 15 },
},
[JZ4760_CLK_UART1] = {
"uart1", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 16 },
},
[JZ4760_CLK_UART2] = {
"uart2", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 17 },
},
[JZ4760_CLK_UART3] = {
"uart3", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 18 },
},
[JZ4760_CLK_IPU] = {
"ipu", CGU_CLK_GATE,
.parents = { JZ4760_CLK_HCLK, },
.gate = { CGU_REG_CLKGR0, 29 },
},
[JZ4760_CLK_ADC] = {
"adc", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 14 },
},
[JZ4760_CLK_AIC] = {
"aic", CGU_CLK_GATE,
.parents = { JZ4760_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 8 },
},
[JZ4760_CLK_VPU] = {
"vpu", CGU_CLK_GATE,
.parents = { JZ4760_CLK_HCLK, },
.gate = { CGU_REG_LCR, 30, false, 150 },
},
[JZ4760_CLK_MMC0] = {
"mmc0", CGU_CLK_GATE,
.parents = { JZ4760_CLK_MMC_MUX, },
.gate = { CGU_REG_CLKGR0, 3 },
},
[JZ4760_CLK_MMC1] = {
"mmc1", CGU_CLK_GATE,
.parents = { JZ4760_CLK_MMC_MUX, },
.gate = { CGU_REG_CLKGR0, 11 },
},
[JZ4760_CLK_MMC2] = {
"mmc2", CGU_CLK_GATE,
.parents = { JZ4760_CLK_MMC_MUX, },
.gate = { CGU_REG_CLKGR0, 12 },
},
[JZ4760_CLK_UHC_PHY] = {
"uhc_phy", CGU_CLK_GATE,
.parents = { JZ4760_CLK_UHC, },
.gate = { CGU_REG_OPCR, 5 },
},
[JZ4760_CLK_OTG_PHY] = {
"usb_phy", CGU_CLK_GATE,
.parents = { JZ4760_CLK_OTG },
.gate = { CGU_REG_OPCR, 7, true, 50 },
},
/* Custom clocks */
[JZ4760_CLK_EXT512] = {
"ext/512", CGU_CLK_FIXDIV,
.parents = { JZ4760_CLK_EXT },
.fixdiv = { 512 },
},
[JZ4760_CLK_RTC] = {
"rtc", CGU_CLK_MUX,
.parents = { JZ4760_CLK_EXT512, JZ4760_CLK_OSC32K, },
.mux = { CGU_REG_OPCR, 2, 1},
},
};
static void __init jz4760_cgu_init(struct device_node *np)
{
struct ingenic_cgu *cgu;
int retval;
cgu = ingenic_cgu_new(jz4760_cgu_clocks,
ARRAY_SIZE(jz4760_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval)
pr_err("%s: failed to register CGU Clocks\n", __func__);
ingenic_cgu_register_syscore(cgu);
}
/* We only probe via devicetree, no need for a platform driver */
CLK_OF_DECLARE_DRIVER(jz4760_cgu, "ingenic,jz4760-cgu", jz4760_cgu_init);
/* JZ4760B has some small differences, but we don't implement them. */
CLK_OF_DECLARE_DRIVER(jz4760b_cgu, "ingenic,jz4760b-cgu", jz4760_cgu_init);
@@ -0,0 +1,463 @@
// SPDX-License-Identifier: GPL-2.0
/*
* JZ4770 SoC CGU driver
* Copyright 2018, Paul Cercueil <paul@crapouillou.net>
*/
#include <linux/bitops.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <dt-bindings/clock/ingenic,jz4770-cgu.h>
#include "cgu.h"
#include "pm.h"
/*
* CPM registers offset address definition
*/
#define CGU_REG_CPCCR 0x00
#define CGU_REG_LCR 0x04
#define CGU_REG_CPPCR0 0x10
#define CGU_REG_CLKGR0 0x20
#define CGU_REG_OPCR 0x24
#define CGU_REG_CLKGR1 0x28
#define CGU_REG_CPPCR1 0x30
#define CGU_REG_USBPCR1 0x48
#define CGU_REG_USBCDR 0x50
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LPCDR 0x64
#define CGU_REG_MSC0CDR 0x68
#define CGU_REG_UHCCDR 0x6c
#define CGU_REG_SSICDR 0x74
#define CGU_REG_CIMCDR 0x7c
#define CGU_REG_GPSCDR 0x80
#define CGU_REG_PCMCDR 0x84
#define CGU_REG_GPUCDR 0x88
#define CGU_REG_MSC1CDR 0xA4
#define CGU_REG_MSC2CDR 0xA8
#define CGU_REG_BCHCDR 0xAC
/* bits within the OPCR register */
#define OPCR_SPENDH BIT(5) /* UHC PHY suspend */
/* bits within the USBPCR1 register */
#define USBPCR1_UHC_POWER BIT(5) /* UHC PHY power down */
static struct ingenic_cgu *cgu;
static int jz4770_uhc_phy_enable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr1 = cgu->base + CGU_REG_USBPCR1;
writel(readl(reg_opcr) & ~OPCR_SPENDH, reg_opcr);
writel(readl(reg_usbpcr1) | USBPCR1_UHC_POWER, reg_usbpcr1);
return 0;
}
static void jz4770_uhc_phy_disable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr1 = cgu->base + CGU_REG_USBPCR1;
writel(readl(reg_usbpcr1) & ~USBPCR1_UHC_POWER, reg_usbpcr1);
writel(readl(reg_opcr) | OPCR_SPENDH, reg_opcr);
}
static int jz4770_uhc_phy_is_enabled(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr1 = cgu->base + CGU_REG_USBPCR1;
return !(readl(reg_opcr) & OPCR_SPENDH) &&
(readl(reg_usbpcr1) & USBPCR1_UHC_POWER);
}
static const struct clk_ops jz4770_uhc_phy_ops = {
.enable = jz4770_uhc_phy_enable,
.disable = jz4770_uhc_phy_disable,
.is_enabled = jz4770_uhc_phy_is_enabled,
};
static const s8 pll_od_encoding[8] = {
0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3,
};
static const u8 jz4770_cgu_cpccr_div_table[] = {
1, 2, 3, 4, 6, 8, 12,
};
static const struct ingenic_cgu_clk_info jz4770_cgu_clocks[] = {
/* External clocks */
[JZ4770_CLK_EXT] = { "ext", CGU_CLK_EXT },
[JZ4770_CLK_OSC32K] = { "osc32k", CGU_CLK_EXT },
/* PLLs */
[JZ4770_CLK_PLL0] = {
"pll0", CGU_CLK_PLL,
.parents = { JZ4770_CLK_EXT },
.pll = {
.reg = CGU_REG_CPPCR0,
.rate_multiplier = 1,
.m_shift = 24,
.m_bits = 7,
.m_offset = 1,
.n_shift = 18,
.n_bits = 5,
.n_offset = 1,
.od_shift = 16,
.od_bits = 2,
.od_max = 8,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_CPPCR0,
.bypass_bit = 9,
.enable_bit = 8,
.stable_bit = 10,
},
},
[JZ4770_CLK_PLL1] = {
/* TODO: PLL1 can depend on PLL0 */
"pll1", CGU_CLK_PLL,
.parents = { JZ4770_CLK_EXT },
.pll = {
.reg = CGU_REG_CPPCR1,
.rate_multiplier = 1,
.m_shift = 24,
.m_bits = 7,
.m_offset = 1,
.n_shift = 18,
.n_bits = 5,
.n_offset = 1,
.od_shift = 16,
.od_bits = 2,
.od_max = 8,
.od_encoding = pll_od_encoding,
.bypass_bit = -1,
.enable_bit = 7,
.stable_bit = 6,
},
},
/* Main clocks */
[JZ4770_CLK_CCLK] = {
"cclk", CGU_CLK_DIV,
/*
* Disabling the CPU clock or any parent clocks will hang the
* system; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4770_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1, 0,
jz4770_cgu_cpccr_div_table,
},
},
[JZ4770_CLK_H0CLK] = {
"h0clk", CGU_CLK_DIV,
.parents = { JZ4770_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1, 0,
jz4770_cgu_cpccr_div_table,
},
},
[JZ4770_CLK_H1CLK] = {
"h1clk", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4770_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 24, 1, 4, 22, -1, -1, 0,
jz4770_cgu_cpccr_div_table,
},
.gate = { CGU_REG_CLKGR1, 7 },
},
[JZ4770_CLK_H2CLK] = {
"h2clk", CGU_CLK_DIV,
.parents = { JZ4770_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 16, 1, 4, 22, -1, -1, 0,
jz4770_cgu_cpccr_div_table,
},
},
[JZ4770_CLK_C1CLK] = {
"c1clk", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4770_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 12, 1, 4, 22, -1, -1, 0,
jz4770_cgu_cpccr_div_table,
},
.gate = { CGU_REG_OPCR, 31, true }, // disable CCLK stop on idle
},
[JZ4770_CLK_PCLK] = {
"pclk", CGU_CLK_DIV,
.parents = { JZ4770_CLK_PLL0, },
.div = {
CGU_REG_CPCCR, 8, 1, 4, 22, -1, -1, 0,
jz4770_cgu_cpccr_div_table,
},
},
/* Those divided clocks can connect to PLL0 or PLL1 */
[JZ4770_CLK_MMC0_MUX] = {
"mmc0_mux", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_MSC0CDR, 30, 1 },
.div = { CGU_REG_MSC0CDR, 0, 1, 7, -1, -1, 31 },
.gate = { CGU_REG_MSC0CDR, 31 },
},
[JZ4770_CLK_MMC1_MUX] = {
"mmc1_mux", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_MSC1CDR, 30, 1 },
.div = { CGU_REG_MSC1CDR, 0, 1, 7, -1, -1, 31 },
.gate = { CGU_REG_MSC1CDR, 31 },
},
[JZ4770_CLK_MMC2_MUX] = {
"mmc2_mux", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_MSC2CDR, 30, 1 },
.div = { CGU_REG_MSC2CDR, 0, 1, 7, -1, -1, 31 },
.gate = { CGU_REG_MSC2CDR, 31 },
},
[JZ4770_CLK_CIM] = {
"cim", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_CIMCDR, 31, 1 },
.div = { CGU_REG_CIMCDR, 0, 1, 8, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 26 },
},
[JZ4770_CLK_UHC] = {
"uhc", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_UHCCDR, 29, 1 },
.div = { CGU_REG_UHCCDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 24 },
},
[JZ4770_CLK_GPU] = {
"gpu", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, -1 },
.mux = { CGU_REG_GPUCDR, 31, 1 },
.div = { CGU_REG_GPUCDR, 0, 1, 3, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 9 },
},
[JZ4770_CLK_BCH] = {
"bch", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_BCHCDR, 31, 1 },
.div = { CGU_REG_BCHCDR, 0, 1, 3, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 1 },
},
[JZ4770_CLK_LPCLK_MUX] = {
"lpclk", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_LPCDR, 29, 1 },
.div = { CGU_REG_LPCDR, 0, 1, 11, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 28 },
},
[JZ4770_CLK_GPS] = {
"gps", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_PLL0, JZ4770_CLK_PLL1, },
.mux = { CGU_REG_GPSCDR, 31, 1 },
.div = { CGU_REG_GPSCDR, 0, 1, 4, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 22 },
},
/* Those divided clocks can connect to EXT, PLL0 or PLL1 */
[JZ4770_CLK_SSI_MUX] = {
"ssi_mux", CGU_CLK_DIV | CGU_CLK_MUX,
.parents = { JZ4770_CLK_EXT, -1,
JZ4770_CLK_PLL0, JZ4770_CLK_PLL1 },
.mux = { CGU_REG_SSICDR, 30, 2 },
.div = { CGU_REG_SSICDR, 0, 1, 6, -1, -1, -1 },
},
[JZ4770_CLK_PCM_MUX] = {
"pcm_mux", CGU_CLK_DIV | CGU_CLK_MUX,
.parents = { JZ4770_CLK_EXT, -1,
JZ4770_CLK_PLL0, JZ4770_CLK_PLL1 },
.mux = { CGU_REG_PCMCDR, 30, 2 },
.div = { CGU_REG_PCMCDR, 0, 1, 9, -1, -1, -1 },
},
[JZ4770_CLK_I2S] = {
"i2s", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_EXT, -1,
JZ4770_CLK_PLL0, JZ4770_CLK_PLL1 },
.mux = { CGU_REG_I2SCDR, 30, 2 },
.div = { CGU_REG_I2SCDR, 0, 1, 9, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 13 },
},
[JZ4770_CLK_OTG] = {
"usb", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { JZ4770_CLK_EXT, -1,
JZ4770_CLK_PLL0, JZ4770_CLK_PLL1 },
.mux = { CGU_REG_USBCDR, 30, 2 },
.div = { CGU_REG_USBCDR, 0, 1, 8, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 2 },
},
/* Gate-only clocks */
[JZ4770_CLK_SSI0] = {
"ssi0", CGU_CLK_GATE,
.parents = { JZ4770_CLK_SSI_MUX, },
.gate = { CGU_REG_CLKGR0, 4 },
},
[JZ4770_CLK_SSI1] = {
"ssi1", CGU_CLK_GATE,
.parents = { JZ4770_CLK_SSI_MUX, },
.gate = { CGU_REG_CLKGR0, 19 },
},
[JZ4770_CLK_SSI2] = {
"ssi2", CGU_CLK_GATE,
.parents = { JZ4770_CLK_SSI_MUX, },
.gate = { CGU_REG_CLKGR0, 20 },
},
[JZ4770_CLK_PCM0] = {
"pcm0", CGU_CLK_GATE,
.parents = { JZ4770_CLK_PCM_MUX, },
.gate = { CGU_REG_CLKGR1, 8 },
},
[JZ4770_CLK_PCM1] = {
"pcm1", CGU_CLK_GATE,
.parents = { JZ4770_CLK_PCM_MUX, },
.gate = { CGU_REG_CLKGR1, 10 },
},
[JZ4770_CLK_DMA] = {
"dma", CGU_CLK_GATE,
.parents = { JZ4770_CLK_H2CLK, },
.gate = { CGU_REG_CLKGR0, 21 },
},
[JZ4770_CLK_BDMA] = {
"bdma", CGU_CLK_GATE,
.parents = { JZ4770_CLK_H2CLK, },
.gate = { CGU_REG_CLKGR1, 0 },
},
[JZ4770_CLK_I2C0] = {
"i2c0", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 5 },
},
[JZ4770_CLK_I2C1] = {
"i2c1", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 6 },
},
[JZ4770_CLK_I2C2] = {
"i2c2", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR1, 15 },
},
[JZ4770_CLK_UART0] = {
"uart0", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 15 },
},
[JZ4770_CLK_UART1] = {
"uart1", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 16 },
},
[JZ4770_CLK_UART2] = {
"uart2", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 17 },
},
[JZ4770_CLK_UART3] = {
"uart3", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 18 },
},
[JZ4770_CLK_IPU] = {
"ipu", CGU_CLK_GATE,
.parents = { JZ4770_CLK_H0CLK, },
.gate = { CGU_REG_CLKGR0, 29 },
},
[JZ4770_CLK_ADC] = {
"adc", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 14 },
},
[JZ4770_CLK_AIC] = {
"aic", CGU_CLK_GATE,
.parents = { JZ4770_CLK_EXT, },
.gate = { CGU_REG_CLKGR0, 8 },
},
[JZ4770_CLK_AUX] = {
"aux", CGU_CLK_GATE,
.parents = { JZ4770_CLK_C1CLK, },
.gate = { CGU_REG_CLKGR1, 14 },
},
[JZ4770_CLK_VPU] = {
"vpu", CGU_CLK_GATE,
.parents = { JZ4770_CLK_H1CLK, },
.gate = { CGU_REG_LCR, 30, false, 150 },
},
[JZ4770_CLK_MMC0] = {
"mmc0", CGU_CLK_GATE,
.parents = { JZ4770_CLK_MMC0_MUX, },
.gate = { CGU_REG_CLKGR0, 3 },
},
[JZ4770_CLK_MMC1] = {
"mmc1", CGU_CLK_GATE,
.parents = { JZ4770_CLK_MMC1_MUX, },
.gate = { CGU_REG_CLKGR0, 11 },
},
[JZ4770_CLK_MMC2] = {
"mmc2", CGU_CLK_GATE,
.parents = { JZ4770_CLK_MMC2_MUX, },
.gate = { CGU_REG_CLKGR0, 12 },
},
[JZ4770_CLK_OTG_PHY] = {
"usb_phy", CGU_CLK_GATE,
.parents = { JZ4770_CLK_OTG },
.gate = { CGU_REG_OPCR, 7, true, 50 },
},
/* Custom clocks */
[JZ4770_CLK_UHC_PHY] = {
"uhc_phy", CGU_CLK_CUSTOM,
.parents = { JZ4770_CLK_UHC, -1, -1, -1 },
.custom = { &jz4770_uhc_phy_ops },
},
[JZ4770_CLK_EXT512] = {
"ext/512", CGU_CLK_FIXDIV,
.parents = { JZ4770_CLK_EXT },
.fixdiv = { 512 },
},
[JZ4770_CLK_RTC] = {
"rtc", CGU_CLK_MUX,
.parents = { JZ4770_CLK_EXT512, JZ4770_CLK_OSC32K, },
.mux = { CGU_REG_OPCR, 2, 1},
},
};
static void __init jz4770_cgu_init(struct device_node *np)
{
int retval;
cgu = ingenic_cgu_new(jz4770_cgu_clocks,
ARRAY_SIZE(jz4770_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval)
pr_err("%s: failed to register CGU Clocks\n", __func__);
ingenic_cgu_register_syscore(cgu);
}
/* We only probe via devicetree, no need for a platform driver */
CLK_OF_DECLARE_DRIVER(jz4770_cgu, "ingenic,jz4770-cgu", jz4770_cgu_init);
@@ -0,0 +1,808 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Ingenic JZ4780 SoC CGU driver
*
* Copyright (c) 2013-2015 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
* Copyright (c) 2020 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include <dt-bindings/clock/ingenic,jz4780-cgu.h>
#include "cgu.h"
#include "pm.h"
/* CGU register offsets */
#define CGU_REG_CLOCKCONTROL 0x00
#define CGU_REG_LCR 0x04
#define CGU_REG_APLL 0x10
#define CGU_REG_MPLL 0x14
#define CGU_REG_EPLL 0x18
#define CGU_REG_VPLL 0x1c
#define CGU_REG_CLKGR0 0x20
#define CGU_REG_OPCR 0x24
#define CGU_REG_CLKGR1 0x28
#define CGU_REG_DDRCDR 0x2c
#define CGU_REG_VPUCDR 0x30
#define CGU_REG_USBPCR 0x3c
#define CGU_REG_USBRDT 0x40
#define CGU_REG_USBVBFIL 0x44
#define CGU_REG_USBPCR1 0x48
#define CGU_REG_LP0CDR 0x54
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LP1CDR 0x64
#define CGU_REG_MSC0CDR 0x68
#define CGU_REG_UHCCDR 0x6c
#define CGU_REG_SSICDR 0x74
#define CGU_REG_CIMCDR 0x7c
#define CGU_REG_PCMCDR 0x84
#define CGU_REG_GPUCDR 0x88
#define CGU_REG_HDMICDR 0x8c
#define CGU_REG_MSC1CDR 0xa4
#define CGU_REG_MSC2CDR 0xa8
#define CGU_REG_BCHCDR 0xac
#define CGU_REG_CLOCKSTATUS 0xd4
/* bits within the OPCR register */
#define OPCR_SPENDN0 BIT(7)
#define OPCR_SPENDN1 BIT(6)
/* bits within the USBPCR register */
#define USBPCR_USB_MODE BIT(31)
#define USBPCR_IDPULLUP_MASK (0x3 << 28)
#define USBPCR_COMMONONN BIT(25)
#define USBPCR_VBUSVLDEXT BIT(24)
#define USBPCR_VBUSVLDEXTSEL BIT(23)
#define USBPCR_POR BIT(22)
#define USBPCR_SIDDQ BIT(21)
#define USBPCR_OTG_DISABLE BIT(20)
#define USBPCR_COMPDISTUNE_MASK (0x7 << 17)
#define USBPCR_OTGTUNE_MASK (0x7 << 14)
#define USBPCR_SQRXTUNE_MASK (0x7 << 11)
#define USBPCR_TXFSLSTUNE_MASK (0xf << 7)
#define USBPCR_TXPREEMPHTUNE BIT(6)
#define USBPCR_TXHSXVTUNE_MASK (0x3 << 4)
#define USBPCR_TXVREFTUNE_MASK 0xf
/* bits within the USBPCR1 register */
#define USBPCR1_REFCLKSEL_SHIFT 26
#define USBPCR1_REFCLKSEL_MASK (0x3 << USBPCR1_REFCLKSEL_SHIFT)
#define USBPCR1_REFCLKSEL_CORE (0x2 << USBPCR1_REFCLKSEL_SHIFT)
#define USBPCR1_REFCLKDIV_SHIFT 24
#define USBPCR1_REFCLKDIV_MASK (0x3 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_REFCLKDIV_19_2 (0x3 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_REFCLKDIV_48 (0x2 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_REFCLKDIV_24 (0x1 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_REFCLKDIV_12 (0x0 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_USB_SEL BIT(28)
#define USBPCR1_WORD_IF0 BIT(19)
#define USBPCR1_WORD_IF1 BIT(18)
/* bits within the USBRDT register */
#define USBRDT_VBFIL_LD_EN BIT(25)
#define USBRDT_USBRDT_MASK 0x7fffff
/* bits within the USBVBFIL register */
#define USBVBFIL_IDDIGFIL_SHIFT 16
#define USBVBFIL_IDDIGFIL_MASK (0xffff << USBVBFIL_IDDIGFIL_SHIFT)
#define USBVBFIL_USBVBFIL_MASK (0xffff)
/* bits within the LCR register */
#define LCR_PD_SCPU BIT(31)
#define LCR_SCPUS BIT(27)
/* bits within the CLKGR1 register */
#define CLKGR1_CORE1 BIT(15)
static struct ingenic_cgu *cgu;
static unsigned long jz4780_otg_phy_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u32 usbpcr1;
unsigned refclk_div;
usbpcr1 = readl(cgu->base + CGU_REG_USBPCR1);
refclk_div = usbpcr1 & USBPCR1_REFCLKDIV_MASK;
switch (refclk_div) {
case USBPCR1_REFCLKDIV_12:
return 12000000;
case USBPCR1_REFCLKDIV_24:
return 24000000;
case USBPCR1_REFCLKDIV_48:
return 48000000;
case USBPCR1_REFCLKDIV_19_2:
return 19200000;
}
return parent_rate;
}
static int jz4780_otg_phy_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
if (req->rate < 15600000)
req->rate = 12000000;
else if (req->rate < 21600000)
req->rate = 19200000;
else if (req->rate < 36000000)
req->rate = 24000000;
else
req->rate = 48000000;
return 0;
}
static int jz4780_otg_phy_set_rate(struct clk_hw *hw, unsigned long req_rate,
unsigned long parent_rate)
{
unsigned long flags;
u32 usbpcr1, div_bits;
switch (req_rate) {
case 12000000:
div_bits = USBPCR1_REFCLKDIV_12;
break;
case 19200000:
div_bits = USBPCR1_REFCLKDIV_19_2;
break;
case 24000000:
div_bits = USBPCR1_REFCLKDIV_24;
break;
case 48000000:
div_bits = USBPCR1_REFCLKDIV_48;
break;
default:
return -EINVAL;
}
spin_lock_irqsave(&cgu->lock, flags);
usbpcr1 = readl(cgu->base + CGU_REG_USBPCR1);
usbpcr1 &= ~USBPCR1_REFCLKDIV_MASK;
usbpcr1 |= div_bits;
writel(usbpcr1, cgu->base + CGU_REG_USBPCR1);
spin_unlock_irqrestore(&cgu->lock, flags);
return 0;
}
static int jz4780_otg_phy_enable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
writel(readl(reg_opcr) | OPCR_SPENDN0, reg_opcr);
writel(readl(reg_usbpcr) & ~USBPCR_OTG_DISABLE & ~USBPCR_SIDDQ, reg_usbpcr);
return 0;
}
static void jz4780_otg_phy_disable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
writel(readl(reg_opcr) & ~OPCR_SPENDN0, reg_opcr);
writel(readl(reg_usbpcr) | USBPCR_OTG_DISABLE | USBPCR_SIDDQ, reg_usbpcr);
}
static int jz4780_otg_phy_is_enabled(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
return (readl(reg_opcr) & OPCR_SPENDN0) &&
!(readl(reg_usbpcr) & USBPCR_SIDDQ) &&
!(readl(reg_usbpcr) & USBPCR_OTG_DISABLE);
}
static const struct clk_ops jz4780_otg_phy_ops = {
.recalc_rate = jz4780_otg_phy_recalc_rate,
.determine_rate = jz4780_otg_phy_determine_rate,
.set_rate = jz4780_otg_phy_set_rate,
.enable = jz4780_otg_phy_enable,
.disable = jz4780_otg_phy_disable,
.is_enabled = jz4780_otg_phy_is_enabled,
};
static int jz4780_core1_enable(struct clk_hw *hw)
{
struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
struct ingenic_cgu *cgu = ingenic_clk->cgu;
const unsigned int timeout = 5000;
unsigned long flags;
int retval;
u32 lcr, clkgr1;
spin_lock_irqsave(&cgu->lock, flags);
lcr = readl(cgu->base + CGU_REG_LCR);
lcr &= ~LCR_PD_SCPU;
writel(lcr, cgu->base + CGU_REG_LCR);
clkgr1 = readl(cgu->base + CGU_REG_CLKGR1);
clkgr1 &= ~CLKGR1_CORE1;
writel(clkgr1, cgu->base + CGU_REG_CLKGR1);
spin_unlock_irqrestore(&cgu->lock, flags);
/* wait for the CPU to be powered up */
retval = readl_poll_timeout(cgu->base + CGU_REG_LCR, lcr,
!(lcr & LCR_SCPUS), 10, timeout);
if (retval == -ETIMEDOUT) {
pr_err("%s: Wait for power up core1 timeout\n", __func__);
return retval;
}
return 0;
}
static const struct clk_ops jz4780_core1_ops = {
.enable = jz4780_core1_enable,
};
static const s8 pll_od_encoding[16] = {
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
};
static const struct ingenic_cgu_clk_info jz4780_cgu_clocks[] = {
/* External clocks */
[JZ4780_CLK_EXCLK] = { "ext", CGU_CLK_EXT },
[JZ4780_CLK_RTCLK] = { "rtc", CGU_CLK_EXT },
/* PLLs */
#define DEF_PLL(name) { \
.reg = CGU_REG_ ## name, \
.rate_multiplier = 1, \
.m_shift = 19, \
.m_bits = 13, \
.m_offset = 1, \
.n_shift = 13, \
.n_bits = 6, \
.n_offset = 1, \
.od_shift = 9, \
.od_bits = 4, \
.od_max = 16, \
.od_encoding = pll_od_encoding, \
.stable_bit = 6, \
.bypass_reg = CGU_REG_ ## name, \
.bypass_bit = 1, \
.enable_bit = 0, \
}
[JZ4780_CLK_APLL] = {
"apll", CGU_CLK_PLL,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.pll = DEF_PLL(APLL),
},
[JZ4780_CLK_MPLL] = {
"mpll", CGU_CLK_PLL,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.pll = DEF_PLL(MPLL),
},
[JZ4780_CLK_EPLL] = {
"epll", CGU_CLK_PLL,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.pll = DEF_PLL(EPLL),
},
[JZ4780_CLK_VPLL] = {
"vpll", CGU_CLK_PLL,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.pll = DEF_PLL(VPLL),
},
#undef DEF_PLL
/* Custom (SoC-specific) OTG PHY */
[JZ4780_CLK_OTGPHY] = {
"otg_phy", CGU_CLK_CUSTOM,
.parents = { -1, -1, JZ4780_CLK_EXCLK, -1 },
.custom = { &jz4780_otg_phy_ops },
},
/* Muxes & dividers */
[JZ4780_CLK_SCLKA] = {
"sclk_a", CGU_CLK_MUX,
.parents = { -1, JZ4780_CLK_APLL, JZ4780_CLK_EXCLK,
JZ4780_CLK_RTCLK },
.mux = { CGU_REG_CLOCKCONTROL, 30, 2 },
},
[JZ4780_CLK_CPUMUX] = {
"cpumux", CGU_CLK_MUX,
.parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_EPLL },
.mux = { CGU_REG_CLOCKCONTROL, 28, 2 },
},
[JZ4780_CLK_CPU] = {
"cpu", CGU_CLK_DIV,
/*
* Disabling the CPU clock or any parent clocks will hang the
* system; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4780_CLK_CPUMUX, -1, -1, -1 },
.div = { CGU_REG_CLOCKCONTROL, 0, 1, 4, 22, -1, -1 },
},
[JZ4780_CLK_L2CACHE] = {
"l2cache", CGU_CLK_DIV,
/*
* The L2 cache clock is critical if caches are enabled and
* disabling it or any parent clocks will hang the system.
*/
.flags = CLK_IS_CRITICAL,
.parents = { JZ4780_CLK_CPUMUX, -1, -1, -1 },
.div = { CGU_REG_CLOCKCONTROL, 4, 1, 4, -1, -1, -1 },
},
[JZ4780_CLK_AHB0] = {
"ahb0", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_EPLL },
.mux = { CGU_REG_CLOCKCONTROL, 26, 2 },
.div = { CGU_REG_CLOCKCONTROL, 8, 1, 4, 21, -1, -1 },
},
[JZ4780_CLK_AHB2PMUX] = {
"ahb2_apb_mux", CGU_CLK_MUX,
.parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_RTCLK },
.mux = { CGU_REG_CLOCKCONTROL, 24, 2 },
},
[JZ4780_CLK_AHB2] = {
"ahb2", CGU_CLK_DIV,
.parents = { JZ4780_CLK_AHB2PMUX, -1, -1, -1 },
.div = { CGU_REG_CLOCKCONTROL, 12, 1, 4, 20, -1, -1 },
},
[JZ4780_CLK_PCLK] = {
"pclk", CGU_CLK_DIV,
.parents = { JZ4780_CLK_AHB2PMUX, -1, -1, -1 },
.div = { CGU_REG_CLOCKCONTROL, 16, 1, 4, 20, -1, -1 },
},
[JZ4780_CLK_DDR] = {
"ddr", CGU_CLK_MUX | CGU_CLK_DIV,
/*
* Disabling DDR clock or its parents will render DRAM
* inaccessible; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1 },
.mux = { CGU_REG_DDRCDR, 30, 2 },
.div = { CGU_REG_DDRCDR, 0, 1, 4, 29, 28, 27 },
},
[JZ4780_CLK_VPU] = {
"vpu", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_EPLL, -1 },
.mux = { CGU_REG_VPUCDR, 30, 2 },
.div = { CGU_REG_VPUCDR, 0, 1, 4, 29, 28, 27 },
.gate = { CGU_REG_CLKGR1, 2 },
},
[JZ4780_CLK_I2SPLL] = {
"i2s_pll", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_EPLL, -1, -1 },
.mux = { CGU_REG_I2SCDR, 30, 1 },
.div = { CGU_REG_I2SCDR, 0, 1, 8, 29, 28, 27 },
},
[JZ4780_CLK_I2S] = {
"i2s", CGU_CLK_MUX,
.parents = { JZ4780_CLK_EXCLK, JZ4780_CLK_I2SPLL, -1, -1 },
.mux = { CGU_REG_I2SCDR, 31, 1 },
},
[JZ4780_CLK_LCD0PIXCLK] = {
"lcd0pixclk", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_VPLL, -1 },
.mux = { CGU_REG_LP0CDR, 30, 2 },
.div = { CGU_REG_LP0CDR, 0, 1, 8, 28, 27, 26 },
},
[JZ4780_CLK_LCD1PIXCLK] = {
"lcd1pixclk", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_VPLL, -1 },
.mux = { CGU_REG_LP1CDR, 30, 2 },
.div = { CGU_REG_LP1CDR, 0, 1, 8, 28, 27, 26 },
},
[JZ4780_CLK_MSCMUX] = {
"msc_mux", CGU_CLK_MUX,
.parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1 },
.mux = { CGU_REG_MSC0CDR, 30, 2 },
},
[JZ4780_CLK_MSC0] = {
"msc0", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4780_CLK_MSCMUX, -1, -1, -1 },
.div = { CGU_REG_MSC0CDR, 0, 2, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 3 },
},
[JZ4780_CLK_MSC1] = {
"msc1", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4780_CLK_MSCMUX, -1, -1, -1 },
.div = { CGU_REG_MSC1CDR, 0, 2, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 11 },
},
[JZ4780_CLK_MSC2] = {
"msc2", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4780_CLK_MSCMUX, -1, -1, -1 },
.div = { CGU_REG_MSC2CDR, 0, 2, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 12 },
},
[JZ4780_CLK_UHC] = {
"uhc", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_EPLL, JZ4780_CLK_OTGPHY },
.mux = { CGU_REG_UHCCDR, 30, 2 },
.div = { CGU_REG_UHCCDR, 0, 1, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 24 },
},
[JZ4780_CLK_SSIPLL] = {
"ssi_pll", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1, -1 },
.mux = { CGU_REG_SSICDR, 30, 1 },
.div = { CGU_REG_SSICDR, 0, 1, 8, 29, 28, 27 },
},
[JZ4780_CLK_SSI] = {
"ssi", CGU_CLK_MUX,
.parents = { JZ4780_CLK_EXCLK, JZ4780_CLK_SSIPLL, -1, -1 },
.mux = { CGU_REG_SSICDR, 31, 1 },
},
[JZ4780_CLK_CIMMCLK] = {
"cim_mclk", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1, -1 },
.mux = { CGU_REG_CIMCDR, 31, 1 },
.div = { CGU_REG_CIMCDR, 0, 1, 8, 30, 29, 28 },
},
[JZ4780_CLK_PCMPLL] = {
"pcm_pll", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_EPLL, JZ4780_CLK_VPLL },
.mux = { CGU_REG_PCMCDR, 29, 2 },
.div = { CGU_REG_PCMCDR, 0, 1, 8, 28, 27, 26 },
},
[JZ4780_CLK_PCM] = {
"pcm", CGU_CLK_MUX | CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, JZ4780_CLK_PCMPLL, -1, -1 },
.mux = { CGU_REG_PCMCDR, 31, 1 },
.gate = { CGU_REG_CLKGR1, 3 },
},
[JZ4780_CLK_GPU] = {
"gpu", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_EPLL },
.mux = { CGU_REG_GPUCDR, 30, 2 },
.div = { CGU_REG_GPUCDR, 0, 1, 4, 29, 28, 27 },
.gate = { CGU_REG_CLKGR1, 4 },
},
[JZ4780_CLK_HDMI] = {
"hdmi", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_VPLL, -1 },
.mux = { CGU_REG_HDMICDR, 30, 2 },
.div = { CGU_REG_HDMICDR, 0, 1, 8, 29, 28, 26 },
.gate = { CGU_REG_CLKGR1, 9 },
},
[JZ4780_CLK_BCH] = {
"bch", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
JZ4780_CLK_EPLL },
.mux = { CGU_REG_BCHCDR, 30, 2 },
.div = { CGU_REG_BCHCDR, 0, 1, 4, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 1 },
},
[JZ4780_CLK_EXCLK_DIV512] = {
"exclk_div512", CGU_CLK_FIXDIV,
.parents = { JZ4780_CLK_EXCLK },
.fixdiv = { 512 },
},
[JZ4780_CLK_RTC] = {
"rtc_ercs", CGU_CLK_MUX | CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK_DIV512, JZ4780_CLK_RTCLK },
.mux = { CGU_REG_OPCR, 2, 1},
},
/* Gate-only clocks */
[JZ4780_CLK_NEMC] = {
"nemc", CGU_CLK_GATE,
.parents = { JZ4780_CLK_AHB2, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 0 },
},
[JZ4780_CLK_OTG0] = {
"otg0", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 2 },
},
[JZ4780_CLK_SSI0] = {
"ssi0", CGU_CLK_GATE,
.parents = { JZ4780_CLK_SSI, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 4 },
},
[JZ4780_CLK_SMB0] = {
"smb0", CGU_CLK_GATE,
.parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 5 },
},
[JZ4780_CLK_SMB1] = {
"smb1", CGU_CLK_GATE,
.parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 6 },
},
[JZ4780_CLK_SCC] = {
"scc", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 7 },
},
[JZ4780_CLK_AIC] = {
"aic", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 8 },
},
[JZ4780_CLK_TSSI0] = {
"tssi0", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 9 },
},
[JZ4780_CLK_OWI] = {
"owi", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 10 },
},
[JZ4780_CLK_KBC] = {
"kbc", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 13 },
},
[JZ4780_CLK_SADC] = {
"sadc", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 14 },
},
[JZ4780_CLK_UART0] = {
"uart0", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 15 },
},
[JZ4780_CLK_UART1] = {
"uart1", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 16 },
},
[JZ4780_CLK_UART2] = {
"uart2", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 17 },
},
[JZ4780_CLK_UART3] = {
"uart3", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 18 },
},
[JZ4780_CLK_SSI1] = {
"ssi1", CGU_CLK_GATE,
.parents = { JZ4780_CLK_SSI, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 19 },
},
[JZ4780_CLK_SSI2] = {
"ssi2", CGU_CLK_GATE,
.parents = { JZ4780_CLK_SSI, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 20 },
},
[JZ4780_CLK_PDMA] = {
"pdma", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 21 },
},
[JZ4780_CLK_GPS] = {
"gps", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 22 },
},
[JZ4780_CLK_MAC] = {
"mac", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 23 },
},
[JZ4780_CLK_SMB2] = {
"smb2", CGU_CLK_GATE,
.parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 24 },
},
[JZ4780_CLK_CIM] = {
"cim", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 26 },
},
[JZ4780_CLK_LCD] = {
"lcd", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 28 },
},
[JZ4780_CLK_TVE] = {
"tve", CGU_CLK_GATE,
.parents = { JZ4780_CLK_LCD, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 27 },
},
[JZ4780_CLK_IPU] = {
"ipu", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 29 },
},
[JZ4780_CLK_DDR0] = {
"ddr0", CGU_CLK_GATE,
.parents = { JZ4780_CLK_DDR, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 30 },
},
[JZ4780_CLK_DDR1] = {
"ddr1", CGU_CLK_GATE,
.parents = { JZ4780_CLK_DDR, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 31 },
},
[JZ4780_CLK_SMB3] = {
"smb3", CGU_CLK_GATE,
.parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 0 },
},
[JZ4780_CLK_TSSI1] = {
"tssi1", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 1 },
},
[JZ4780_CLK_COMPRESS] = {
"compress", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 5 },
},
[JZ4780_CLK_AIC1] = {
"aic1", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 6 },
},
[JZ4780_CLK_GPVLC] = {
"gpvlc", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 7 },
},
[JZ4780_CLK_OTG1] = {
"otg1", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 8 },
},
[JZ4780_CLK_UART4] = {
"uart4", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 10 },
},
[JZ4780_CLK_AHBMON] = {
"ahb_mon", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 11 },
},
[JZ4780_CLK_SMB4] = {
"smb4", CGU_CLK_GATE,
.parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 12 },
},
[JZ4780_CLK_DES] = {
"des", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 13 },
},
[JZ4780_CLK_X2D] = {
"x2d", CGU_CLK_GATE,
.parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 14 },
},
[JZ4780_CLK_CORE1] = {
"core1", CGU_CLK_CUSTOM,
.parents = { JZ4780_CLK_CPU, -1, -1, -1 },
.custom = { &jz4780_core1_ops },
},
};
static void __init jz4780_cgu_init(struct device_node *np)
{
int retval;
cgu = ingenic_cgu_new(jz4780_cgu_clocks,
ARRAY_SIZE(jz4780_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval) {
pr_err("%s: failed to register CGU Clocks\n", __func__);
return;
}
ingenic_cgu_register_syscore(cgu);
}
CLK_OF_DECLARE_DRIVER(jz4780_cgu, "ingenic,jz4780-cgu", jz4780_cgu_init);
+49
View File
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
*/
#include "cgu.h"
#include "pm.h"
#include <linux/io.h>
#include <linux/syscore_ops.h>
#define CGU_REG_LCR 0x04
#define LCR_LOW_POWER_MODE BIT(0)
static void __iomem * __maybe_unused ingenic_cgu_base;
static int __maybe_unused ingenic_cgu_pm_suspend(void *data)
{
u32 val = readl(ingenic_cgu_base + CGU_REG_LCR);
writel(val | LCR_LOW_POWER_MODE, ingenic_cgu_base + CGU_REG_LCR);
return 0;
}
static void __maybe_unused ingenic_cgu_pm_resume(void *data)
{
u32 val = readl(ingenic_cgu_base + CGU_REG_LCR);
writel(val & ~LCR_LOW_POWER_MODE, ingenic_cgu_base + CGU_REG_LCR);
}
static const struct syscore_ops __maybe_unused ingenic_cgu_pm_ops = {
.suspend = ingenic_cgu_pm_suspend,
.resume = ingenic_cgu_pm_resume,
};
static struct syscore __maybe_unused ingenic_cgu_pm = {
.ops = &ingenic_cgu_pm_ops,
};
void ingenic_cgu_register_syscore(struct ingenic_cgu *cgu)
{
if (IS_ENABLED(CONFIG_PM_SLEEP)) {
ingenic_cgu_base = cgu->base;
register_syscore(&ingenic_cgu_pm);
}
}
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
*/
#ifndef DRIVERS_CLK_INGENIC_PM_H
#define DRIVERS_CLK_INGENIC_PM_H
struct ingenic_cgu;
void ingenic_cgu_register_syscore(struct ingenic_cgu *cgu);
#endif /* DRIVERS_CLK_INGENIC_PM_H */
+499
View File
@@ -0,0 +1,499 @@
// SPDX-License-Identifier: GPL-2.0
/*
* JZ47xx SoCs TCU clocks driver
* Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clockchips.h>
#include <linux/mfd/ingenic-tcu.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/syscore_ops.h>
#include <dt-bindings/clock/ingenic,tcu.h>
/* 8 channels max + watchdog + OST */
#define TCU_CLK_COUNT 10
#undef pr_fmt
#define pr_fmt(fmt) "ingenic-tcu-clk: " fmt
enum tcu_clk_parent {
TCU_PARENT_PCLK,
TCU_PARENT_RTC,
TCU_PARENT_EXT,
};
struct ingenic_soc_info {
unsigned int num_channels;
bool has_ost;
bool has_tcu_clk;
bool allow_missing_tcu_clk;
};
struct ingenic_tcu_clk_info {
struct clk_init_data init_data;
u8 gate_bit;
u8 tcsr_reg;
};
struct ingenic_tcu_clk {
struct clk_hw hw;
unsigned int idx;
struct ingenic_tcu *tcu;
const struct ingenic_tcu_clk_info *info;
};
struct ingenic_tcu {
const struct ingenic_soc_info *soc_info;
struct regmap *map;
struct clk *clk;
struct clk_hw_onecell_data *clocks;
};
static struct ingenic_tcu *ingenic_tcu;
static inline struct ingenic_tcu_clk *to_tcu_clk(struct clk_hw *hw)
{
return container_of(hw, struct ingenic_tcu_clk, hw);
}
static int ingenic_tcu_enable(struct clk_hw *hw)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
struct ingenic_tcu *tcu = tcu_clk->tcu;
regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit));
return 0;
}
static void ingenic_tcu_disable(struct clk_hw *hw)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
struct ingenic_tcu *tcu = tcu_clk->tcu;
regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
}
static int ingenic_tcu_is_enabled(struct clk_hw *hw)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
unsigned int value;
regmap_read(tcu_clk->tcu->map, TCU_REG_TSR, &value);
return !(value & BIT(info->gate_bit));
}
static bool ingenic_tcu_enable_regs(struct clk_hw *hw)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
struct ingenic_tcu *tcu = tcu_clk->tcu;
bool enabled = false;
/*
* According to the programming manual, a timer channel's registers can
* only be accessed when the channel's stop bit is clear.
*/
enabled = !!ingenic_tcu_is_enabled(hw);
regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit));
return enabled;
}
static void ingenic_tcu_disable_regs(struct clk_hw *hw)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
struct ingenic_tcu *tcu = tcu_clk->tcu;
regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
}
static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
unsigned int val = 0;
int ret;
ret = regmap_read(tcu_clk->tcu->map, info->tcsr_reg, &val);
WARN_ONCE(ret < 0, "Unable to read TCSR %d", tcu_clk->idx);
return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
}
static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
bool was_enabled;
int ret;
was_enabled = ingenic_tcu_enable_regs(hw);
ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
WARN_ONCE(ret < 0, "Unable to update TCSR %d", tcu_clk->idx);
if (!was_enabled)
ingenic_tcu_disable_regs(hw);
return 0;
}
static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
unsigned int prescale;
int ret;
ret = regmap_read(tcu_clk->tcu->map, info->tcsr_reg, &prescale);
WARN_ONCE(ret < 0, "Unable to read TCSR %d", tcu_clk->idx);
prescale = (prescale & TCU_TCSR_PRESCALE_MASK) >> TCU_TCSR_PRESCALE_LSB;
return parent_rate >> (prescale * 2);
}
static u8 ingenic_tcu_get_prescale(unsigned long rate, unsigned long req_rate)
{
u8 prescale;
for (prescale = 0; prescale < 5; prescale++)
if ((rate >> (prescale * 2)) <= req_rate)
return prescale;
return 5; /* /1024 divider */
}
static int ingenic_tcu_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
unsigned long rate = req->best_parent_rate;
u8 prescale;
if (req->rate > rate) {
req->rate = rate;
return 0;
}
prescale = ingenic_tcu_get_prescale(rate, req->rate);
req->rate = rate >> (prescale * 2);
return 0;
}
static int ingenic_tcu_set_rate(struct clk_hw *hw, unsigned long req_rate,
unsigned long parent_rate)
{
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
bool was_enabled;
int ret;
was_enabled = ingenic_tcu_enable_regs(hw);
ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
TCU_TCSR_PRESCALE_MASK,
prescale << TCU_TCSR_PRESCALE_LSB);
WARN_ONCE(ret < 0, "Unable to update TCSR %d", tcu_clk->idx);
if (!was_enabled)
ingenic_tcu_disable_regs(hw);
return 0;
}
static const struct clk_ops ingenic_tcu_clk_ops = {
.get_parent = ingenic_tcu_get_parent,
.set_parent = ingenic_tcu_set_parent,
.recalc_rate = ingenic_tcu_recalc_rate,
.determine_rate = ingenic_tcu_determine_rate,
.set_rate = ingenic_tcu_set_rate,
.enable = ingenic_tcu_enable,
.disable = ingenic_tcu_disable,
.is_enabled = ingenic_tcu_is_enabled,
};
static const char * const ingenic_tcu_timer_parents[] = {
[TCU_PARENT_PCLK] = "pclk",
[TCU_PARENT_RTC] = "rtc",
[TCU_PARENT_EXT] = "ext",
};
#define DEF_TIMER(_name, _gate_bit, _tcsr) \
{ \
.init_data = { \
.name = _name, \
.parent_names = ingenic_tcu_timer_parents, \
.num_parents = ARRAY_SIZE(ingenic_tcu_timer_parents),\
.ops = &ingenic_tcu_clk_ops, \
.flags = CLK_SET_RATE_UNGATE, \
}, \
.gate_bit = _gate_bit, \
.tcsr_reg = _tcsr, \
}
static const struct ingenic_tcu_clk_info ingenic_tcu_clk_info[] = {
[TCU_CLK_TIMER0] = DEF_TIMER("timer0", 0, TCU_REG_TCSRc(0)),
[TCU_CLK_TIMER1] = DEF_TIMER("timer1", 1, TCU_REG_TCSRc(1)),
[TCU_CLK_TIMER2] = DEF_TIMER("timer2", 2, TCU_REG_TCSRc(2)),
[TCU_CLK_TIMER3] = DEF_TIMER("timer3", 3, TCU_REG_TCSRc(3)),
[TCU_CLK_TIMER4] = DEF_TIMER("timer4", 4, TCU_REG_TCSRc(4)),
[TCU_CLK_TIMER5] = DEF_TIMER("timer5", 5, TCU_REG_TCSRc(5)),
[TCU_CLK_TIMER6] = DEF_TIMER("timer6", 6, TCU_REG_TCSRc(6)),
[TCU_CLK_TIMER7] = DEF_TIMER("timer7", 7, TCU_REG_TCSRc(7)),
};
static const struct ingenic_tcu_clk_info ingenic_tcu_watchdog_clk_info =
DEF_TIMER("wdt", 16, TCU_REG_WDT_TCSR);
static const struct ingenic_tcu_clk_info ingenic_tcu_ost_clk_info =
DEF_TIMER("ost", 15, TCU_REG_OST_TCSR);
#undef DEF_TIMER
static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
unsigned int idx, enum tcu_clk_parent parent,
const struct ingenic_tcu_clk_info *info,
struct clk_hw_onecell_data *clocks)
{
struct ingenic_tcu_clk *tcu_clk;
int err;
tcu_clk = kzalloc_obj(*tcu_clk);
if (!tcu_clk)
return -ENOMEM;
tcu_clk->hw.init = &info->init_data;
tcu_clk->idx = idx;
tcu_clk->info = info;
tcu_clk->tcu = tcu;
/* Reset channel and clock divider, set default parent */
ingenic_tcu_enable_regs(&tcu_clk->hw);
regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
ingenic_tcu_disable_regs(&tcu_clk->hw);
err = clk_hw_register(NULL, &tcu_clk->hw);
if (err) {
kfree(tcu_clk);
return err;
}
clocks->hws[idx] = &tcu_clk->hw;
return 0;
}
static const struct ingenic_soc_info jz4740_soc_info = {
.num_channels = 8,
.has_ost = false,
.has_tcu_clk = true,
};
static const struct ingenic_soc_info jz4725b_soc_info = {
.num_channels = 6,
.has_ost = true,
.has_tcu_clk = true,
};
static const struct ingenic_soc_info jz4770_soc_info = {
.num_channels = 8,
.has_ost = true,
.has_tcu_clk = false,
};
static const struct ingenic_soc_info x1000_soc_info = {
.num_channels = 8,
.has_ost = false, /* X1000 has OST, but it not belong TCU */
.has_tcu_clk = true,
.allow_missing_tcu_clk = true,
};
static const struct of_device_id __maybe_unused ingenic_tcu_of_match[] __initconst = {
{ .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, },
{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
{ .compatible = "ingenic,jz4760-tcu", .data = &jz4770_soc_info, },
{ .compatible = "ingenic,jz4770-tcu", .data = &jz4770_soc_info, },
{ .compatible = "ingenic,x1000-tcu", .data = &x1000_soc_info, },
{ /* sentinel */ }
};
static int __init ingenic_tcu_probe(struct device_node *np)
{
const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
struct ingenic_tcu *tcu;
struct regmap *map;
unsigned int i;
int ret;
map = device_node_to_regmap(np);
if (IS_ERR(map))
return PTR_ERR(map);
tcu = kzalloc_obj(*tcu);
if (!tcu)
return -ENOMEM;
tcu->map = map;
tcu->soc_info = id->data;
if (tcu->soc_info->has_tcu_clk) {
tcu->clk = of_clk_get_by_name(np, "tcu");
if (IS_ERR(tcu->clk)) {
ret = PTR_ERR(tcu->clk);
/*
* Old device trees for some SoCs did not include the
* TCU clock because this driver (incorrectly) didn't
* use it. In this case we complain loudly and attempt
* to continue without the clock, which might work if
* booting with workarounds like "clk_ignore_unused".
*/
if (tcu->soc_info->allow_missing_tcu_clk && ret == -EINVAL) {
pr_warn("TCU clock missing from device tree, please update your device tree\n");
tcu->clk = NULL;
} else {
pr_crit("Cannot get TCU clock from device tree\n");
goto err_free_tcu;
}
} else {
ret = clk_prepare_enable(tcu->clk);
if (ret) {
pr_crit("Unable to enable TCU clock\n");
goto err_put_clk;
}
}
}
tcu->clocks = kzalloc_flex(*tcu->clocks, hws, TCU_CLK_COUNT);
if (!tcu->clocks) {
ret = -ENOMEM;
goto err_clk_disable;
}
tcu->clocks->num = TCU_CLK_COUNT;
for (i = 0; i < tcu->soc_info->num_channels; i++) {
ret = ingenic_tcu_register_clock(tcu, i, TCU_PARENT_EXT,
&ingenic_tcu_clk_info[i],
tcu->clocks);
if (ret) {
pr_crit("cannot register clock %d\n", i);
goto err_unregister_timer_clocks;
}
}
/*
* We set EXT as the default parent clock for all the TCU clocks
* except for the watchdog one, where we set the RTC clock as the
* parent. Since the EXT and PCLK are much faster than the RTC clock,
* the watchdog would kick after a maximum time of 5s, and we might
* want a slower kicking time.
*/
ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, TCU_PARENT_RTC,
&ingenic_tcu_watchdog_clk_info,
tcu->clocks);
if (ret) {
pr_crit("cannot register watchdog clock\n");
goto err_unregister_timer_clocks;
}
if (tcu->soc_info->has_ost) {
ret = ingenic_tcu_register_clock(tcu, TCU_CLK_OST,
TCU_PARENT_EXT,
&ingenic_tcu_ost_clk_info,
tcu->clocks);
if (ret) {
pr_crit("cannot register ost clock\n");
goto err_unregister_watchdog_clock;
}
}
ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, tcu->clocks);
if (ret) {
pr_crit("cannot add OF clock provider\n");
goto err_unregister_ost_clock;
}
ingenic_tcu = tcu;
return 0;
err_unregister_ost_clock:
if (tcu->soc_info->has_ost)
clk_hw_unregister(tcu->clocks->hws[i + 1]);
err_unregister_watchdog_clock:
clk_hw_unregister(tcu->clocks->hws[i]);
err_unregister_timer_clocks:
for (i = 0; i < tcu->clocks->num; i++)
if (tcu->clocks->hws[i])
clk_hw_unregister(tcu->clocks->hws[i]);
kfree(tcu->clocks);
err_clk_disable:
if (tcu->clk)
clk_disable_unprepare(tcu->clk);
err_put_clk:
if (tcu->clk)
clk_put(tcu->clk);
err_free_tcu:
kfree(tcu);
return ret;
}
static int __maybe_unused tcu_pm_suspend(void *data)
{
struct ingenic_tcu *tcu = ingenic_tcu;
if (tcu->clk)
clk_disable(tcu->clk);
return 0;
}
static void __maybe_unused tcu_pm_resume(void *data)
{
struct ingenic_tcu *tcu = ingenic_tcu;
if (tcu->clk)
clk_enable(tcu->clk);
}
static const struct syscore_ops __maybe_unused tcu_pm_ops = {
.suspend = tcu_pm_suspend,
.resume = tcu_pm_resume,
};
static struct syscore __maybe_unused tcu_pm = {
.ops = &tcu_pm_ops,
};
static void __init ingenic_tcu_init(struct device_node *np)
{
int ret = ingenic_tcu_probe(np);
if (ret)
pr_crit("Failed to initialize TCU clocks: %d\n", ret);
if (IS_ENABLED(CONFIG_PM_SLEEP))
register_syscore(&tcu_pm);
}
CLK_OF_DECLARE_DRIVER(jz4740_cgu, "ingenic,jz4740-tcu", ingenic_tcu_init);
CLK_OF_DECLARE_DRIVER(jz4725b_cgu, "ingenic,jz4725b-tcu", ingenic_tcu_init);
CLK_OF_DECLARE_DRIVER(jz4760_cgu, "ingenic,jz4760-tcu", ingenic_tcu_init);
CLK_OF_DECLARE_DRIVER(jz4770_cgu, "ingenic,jz4770-tcu", ingenic_tcu_init);
CLK_OF_DECLARE_DRIVER(x1000_cgu, "ingenic,x1000-tcu", ingenic_tcu_init);
+565
View File
@@ -0,0 +1,565 @@
// SPDX-License-Identifier: GPL-2.0
/*
* X1000 SoC CGU driver
* Copyright (c) 2019 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/rational.h>
#include <dt-bindings/clock/ingenic,x1000-cgu.h>
#include "cgu.h"
#include "pm.h"
/* CGU register offsets */
#define CGU_REG_CPCCR 0x00
#define CGU_REG_APLL 0x10
#define CGU_REG_MPLL 0x14
#define CGU_REG_CLKGR 0x20
#define CGU_REG_OPCR 0x24
#define CGU_REG_DDRCDR 0x2c
#define CGU_REG_USBPCR 0x3c
#define CGU_REG_USBPCR1 0x48
#define CGU_REG_USBCDR 0x50
#define CGU_REG_MACCDR 0x54
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LPCDR 0x64
#define CGU_REG_MSC0CDR 0x68
#define CGU_REG_I2SCDR1 0x70
#define CGU_REG_SSICDR 0x74
#define CGU_REG_CIMCDR 0x7c
#define CGU_REG_PCMCDR 0x84
#define CGU_REG_MSC1CDR 0xa4
#define CGU_REG_CMP_INTR 0xb0
#define CGU_REG_CMP_INTRE 0xb4
#define CGU_REG_DRCG 0xd0
#define CGU_REG_CPCSR 0xd4
#define CGU_REG_PCMCDR1 0xe0
#define CGU_REG_MACPHYC 0xe8
/* bits within the OPCR register */
#define OPCR_SPENDN0 BIT(7)
#define OPCR_SPENDN1 BIT(6)
/* bits within the USBPCR register */
#define USBPCR_SIDDQ BIT(21)
#define USBPCR_OTG_DISABLE BIT(20)
/* bits within the USBPCR1 register */
#define USBPCR1_REFCLKSEL_SHIFT 26
#define USBPCR1_REFCLKSEL_MASK (0x3 << USBPCR1_REFCLKSEL_SHIFT)
#define USBPCR1_REFCLKSEL_CORE (0x2 << USBPCR1_REFCLKSEL_SHIFT)
#define USBPCR1_REFCLKDIV_SHIFT 24
#define USBPCR1_REFCLKDIV_MASK (0x3 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_REFCLKDIV_48 (0x2 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_REFCLKDIV_24 (0x1 << USBPCR1_REFCLKDIV_SHIFT)
#define USBPCR1_REFCLKDIV_12 (0x0 << USBPCR1_REFCLKDIV_SHIFT)
static struct ingenic_cgu *cgu;
static unsigned long x1000_otg_phy_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u32 usbpcr1;
unsigned refclk_div;
usbpcr1 = readl(cgu->base + CGU_REG_USBPCR1);
refclk_div = usbpcr1 & USBPCR1_REFCLKDIV_MASK;
switch (refclk_div) {
case USBPCR1_REFCLKDIV_12:
return 12000000;
case USBPCR1_REFCLKDIV_24:
return 24000000;
case USBPCR1_REFCLKDIV_48:
return 48000000;
}
return parent_rate;
}
static int x1000_otg_phy_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
if (req->rate < 18000000)
req->rate = 12000000;
else if (req->rate < 36000000)
req->rate = 24000000;
else
req->rate = 48000000;
return 0;
}
static int x1000_otg_phy_set_rate(struct clk_hw *hw, unsigned long req_rate,
unsigned long parent_rate)
{
unsigned long flags;
u32 usbpcr1, div_bits;
switch (req_rate) {
case 12000000:
div_bits = USBPCR1_REFCLKDIV_12;
break;
case 24000000:
div_bits = USBPCR1_REFCLKDIV_24;
break;
case 48000000:
div_bits = USBPCR1_REFCLKDIV_48;
break;
default:
return -EINVAL;
}
spin_lock_irqsave(&cgu->lock, flags);
usbpcr1 = readl(cgu->base + CGU_REG_USBPCR1);
usbpcr1 &= ~USBPCR1_REFCLKDIV_MASK;
usbpcr1 |= div_bits;
writel(usbpcr1, cgu->base + CGU_REG_USBPCR1);
spin_unlock_irqrestore(&cgu->lock, flags);
return 0;
}
static int x1000_usb_phy_enable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
writel(readl(reg_opcr) | OPCR_SPENDN0, reg_opcr);
writel(readl(reg_usbpcr) & ~USBPCR_OTG_DISABLE & ~USBPCR_SIDDQ, reg_usbpcr);
return 0;
}
static void x1000_usb_phy_disable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
writel(readl(reg_opcr) & ~OPCR_SPENDN0, reg_opcr);
writel(readl(reg_usbpcr) | USBPCR_OTG_DISABLE | USBPCR_SIDDQ, reg_usbpcr);
}
static int x1000_usb_phy_is_enabled(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
return (readl(reg_opcr) & OPCR_SPENDN0) &&
!(readl(reg_usbpcr) & USBPCR_SIDDQ) &&
!(readl(reg_usbpcr) & USBPCR_OTG_DISABLE);
}
static const struct clk_ops x1000_otg_phy_ops = {
.recalc_rate = x1000_otg_phy_recalc_rate,
.determine_rate = x1000_otg_phy_determine_rate,
.set_rate = x1000_otg_phy_set_rate,
.enable = x1000_usb_phy_enable,
.disable = x1000_usb_phy_disable,
.is_enabled = x1000_usb_phy_is_enabled,
};
static void
x1000_i2spll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
unsigned long rate, unsigned long parent_rate,
unsigned int *pm, unsigned int *pn, unsigned int *pod)
{
const unsigned long m_max = GENMASK(pll_info->m_bits - 1, 0);
const unsigned long n_max = GENMASK(pll_info->n_bits - 1, 0);
unsigned long m, n;
rational_best_approximation(rate, parent_rate, m_max, n_max, &m, &n);
/* n should not be less than 2*m */
if (n < 2 * m)
n = 2 * m;
*pm = m;
*pn = n;
*pod = 1;
}
static void
x1000_i2spll_set_rate_hook(const struct ingenic_cgu_pll_info *pll_info,
unsigned long rate, unsigned long parent_rate)
{
/*
* Writing 0 causes I2SCDR1.I2SDIV_D to be automatically recalculated
* based on the current value of I2SCDR.I2SDIV_N, which is needed for
* the divider to function correctly.
*/
writel(0, cgu->base + CGU_REG_I2SCDR1);
}
static const s8 pll_od_encoding[8] = {
0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3,
};
static const struct ingenic_cgu_clk_info x1000_cgu_clocks[] = {
/* External clocks */
[X1000_CLK_EXCLK] = { "ext", CGU_CLK_EXT },
[X1000_CLK_RTCLK] = { "rtc", CGU_CLK_EXT },
/* PLLs */
[X1000_CLK_APLL] = {
"apll", CGU_CLK_PLL,
.parents = { X1000_CLK_EXCLK },
.pll = {
.reg = CGU_REG_APLL,
.rate_multiplier = 1,
.m_shift = 24,
.m_bits = 7,
.m_offset = 1,
.n_shift = 18,
.n_bits = 5,
.n_offset = 1,
.od_shift = 16,
.od_bits = 2,
.od_max = 8,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_APLL,
.bypass_bit = 9,
.enable_bit = 8,
.stable_bit = 10,
},
},
[X1000_CLK_MPLL] = {
"mpll", CGU_CLK_PLL,
.parents = { X1000_CLK_EXCLK },
.pll = {
.reg = CGU_REG_MPLL,
.rate_multiplier = 1,
.m_shift = 24,
.m_bits = 7,
.m_offset = 1,
.n_shift = 18,
.n_bits = 5,
.n_offset = 1,
.od_shift = 16,
.od_bits = 2,
.od_max = 8,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_MPLL,
.bypass_bit = 6,
.enable_bit = 7,
.stable_bit = 0,
},
},
/* Custom (SoC-specific) OTG PHY */
[X1000_CLK_OTGPHY] = {
"otg_phy", CGU_CLK_CUSTOM,
.parents = { -1, -1, X1000_CLK_EXCLK, -1 },
.custom = { &x1000_otg_phy_ops },
},
/* Muxes & dividers */
[X1000_CLK_SCLKA] = {
"sclk_a", CGU_CLK_MUX,
.parents = { -1, X1000_CLK_EXCLK, X1000_CLK_APLL, -1 },
.mux = { CGU_REG_CPCCR, 30, 2 },
},
[X1000_CLK_CPUMUX] = {
"cpu_mux", CGU_CLK_MUX,
.parents = { -1, X1000_CLK_SCLKA, X1000_CLK_MPLL, -1 },
.mux = { CGU_REG_CPCCR, 28, 2 },
},
[X1000_CLK_CPU] = {
"cpu", CGU_CLK_DIV | CGU_CLK_GATE,
/*
* Disabling the CPU clock or any parent clocks will hang the
* system; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { X1000_CLK_CPUMUX },
.div = { CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1 },
.gate = { CGU_REG_CLKGR, 30 },
},
[X1000_CLK_L2CACHE] = {
"l2cache", CGU_CLK_DIV,
/*
* The L2 cache clock is critical if caches are enabled and
* disabling it or any parent clocks will hang the system.
*/
.flags = CLK_IS_CRITICAL,
.parents = { X1000_CLK_CPUMUX },
.div = { CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1 },
},
[X1000_CLK_AHB0] = {
"ahb0", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { -1, X1000_CLK_SCLKA, X1000_CLK_MPLL, -1 },
.mux = { CGU_REG_CPCCR, 26, 2 },
.div = { CGU_REG_CPCCR, 8, 1, 4, 21, -1, -1 },
},
[X1000_CLK_AHB2PMUX] = {
"ahb2_apb_mux", CGU_CLK_MUX,
.parents = { -1, X1000_CLK_SCLKA, X1000_CLK_MPLL, -1 },
.mux = { CGU_REG_CPCCR, 24, 2 },
},
[X1000_CLK_AHB2] = {
"ahb2", CGU_CLK_DIV,
.parents = { X1000_CLK_AHB2PMUX },
.div = { CGU_REG_CPCCR, 12, 1, 4, 20, -1, -1 },
},
[X1000_CLK_PCLK] = {
"pclk", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1000_CLK_AHB2PMUX },
.div = { CGU_REG_CPCCR, 16, 1, 4, 20, -1, -1 },
.gate = { CGU_REG_CLKGR, 28 },
},
[X1000_CLK_DDR] = {
"ddr", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
/*
* Disabling DDR clock or its parents will render DRAM
* inaccessible; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { -1, X1000_CLK_SCLKA, X1000_CLK_MPLL, -1 },
.mux = { CGU_REG_DDRCDR, 30, 2 },
.div = { CGU_REG_DDRCDR, 0, 1, 4, 29, 28, 27 },
.gate = { CGU_REG_CLKGR, 31 },
},
[X1000_CLK_MAC] = {
"mac", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL },
.mux = { CGU_REG_MACCDR, 31, 1 },
.div = { CGU_REG_MACCDR, 0, 1, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR, 25 },
},
[X1000_CLK_I2SPLLMUX] = {
"i2s_pll_mux", CGU_CLK_MUX,
.parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL },
.mux = { CGU_REG_I2SCDR, 31, 1 },
},
[X1000_CLK_I2SPLL] = {
"i2s_pll", CGU_CLK_PLL,
.parents = { X1000_CLK_I2SPLLMUX },
.pll = {
.reg = CGU_REG_I2SCDR,
.rate_multiplier = 1,
.m_shift = 13,
.m_bits = 9,
.n_shift = 0,
.n_bits = 13,
.calc_m_n_od = x1000_i2spll_calc_m_n_od,
.set_rate_hook = x1000_i2spll_set_rate_hook,
},
},
[X1000_CLK_I2S] = {
"i2s", CGU_CLK_MUX,
.parents = { X1000_CLK_EXCLK, -1, -1, X1000_CLK_I2SPLL },
/*
* NOTE: the mux is at bit 30; bit 29 enables the M/N divider.
* Therefore, the divider is disabled when EXCLK is selected.
*/
.mux = { CGU_REG_I2SCDR, 29, 2 },
},
[X1000_CLK_LCD] = {
"lcd", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL },
.mux = { CGU_REG_LPCDR, 31, 1 },
.div = { CGU_REG_LPCDR, 0, 1, 8, 28, 27, 26 },
.gate = { CGU_REG_CLKGR, 23 },
},
[X1000_CLK_MSCMUX] = {
"msc_mux", CGU_CLK_MUX,
.parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL },
.mux = { CGU_REG_MSC0CDR, 31, 1 },
},
[X1000_CLK_MSC0] = {
"msc0", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1000_CLK_MSCMUX },
.div = { CGU_REG_MSC0CDR, 0, 2, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR, 4 },
},
[X1000_CLK_MSC1] = {
"msc1", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1000_CLK_MSCMUX, -1, -1, -1 },
.div = { CGU_REG_MSC1CDR, 0, 2, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR, 5 },
},
[X1000_CLK_OTG] = {
"otg", CGU_CLK_DIV | CGU_CLK_GATE | CGU_CLK_MUX,
.parents = { X1000_CLK_EXCLK, -1, X1000_CLK_APLL, X1000_CLK_MPLL },
.mux = { CGU_REG_USBCDR, 30, 2 },
.div = { CGU_REG_USBCDR, 0, 1, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR, 3 },
},
[X1000_CLK_SSIPLL] = {
"ssi_pll", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL },
.mux = { CGU_REG_SSICDR, 31, 1 },
.div = { CGU_REG_SSICDR, 0, 1, 8, 29, 28, 27 },
},
[X1000_CLK_SSIPLL_DIV2] = {
"ssi_pll_div2", CGU_CLK_FIXDIV,
.parents = { X1000_CLK_SSIPLL },
.fixdiv = { 2 },
},
[X1000_CLK_SSIMUX] = {
"ssi_mux", CGU_CLK_MUX,
.parents = { X1000_CLK_EXCLK, X1000_CLK_SSIPLL_DIV2 },
.mux = { CGU_REG_SSICDR, 30, 1 },
},
[X1000_CLK_EXCLK_DIV512] = {
"exclk_div512", CGU_CLK_FIXDIV,
.parents = { X1000_CLK_EXCLK },
.fixdiv = { 512 },
},
[X1000_CLK_RTC] = {
"rtc_ercs", CGU_CLK_MUX | CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK_DIV512, X1000_CLK_RTCLK },
.mux = { CGU_REG_OPCR, 2, 1},
.gate = { CGU_REG_CLKGR, 27 },
},
/* Gate-only clocks */
[X1000_CLK_EMC] = {
"emc", CGU_CLK_GATE,
.parents = { X1000_CLK_AHB2 },
.gate = { CGU_REG_CLKGR, 0 },
},
[X1000_CLK_EFUSE] = {
"efuse", CGU_CLK_GATE,
.parents = { X1000_CLK_AHB2 },
.gate = { CGU_REG_CLKGR, 1 },
},
[X1000_CLK_SFC] = {
"sfc", CGU_CLK_GATE,
.parents = { X1000_CLK_SSIPLL },
.gate = { CGU_REG_CLKGR, 2 },
},
[X1000_CLK_I2C0] = {
"i2c0", CGU_CLK_GATE,
.parents = { X1000_CLK_PCLK },
.gate = { CGU_REG_CLKGR, 7 },
},
[X1000_CLK_I2C1] = {
"i2c1", CGU_CLK_GATE,
.parents = { X1000_CLK_PCLK },
.gate = { CGU_REG_CLKGR, 8 },
},
[X1000_CLK_I2C2] = {
"i2c2", CGU_CLK_GATE,
.parents = { X1000_CLK_PCLK },
.gate = { CGU_REG_CLKGR, 9 },
},
[X1000_CLK_AIC] = {
"aic", CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK },
.gate = { CGU_REG_CLKGR, 11 },
},
[X1000_CLK_UART0] = {
"uart0", CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK },
.gate = { CGU_REG_CLKGR, 14 },
},
[X1000_CLK_UART1] = {
"uart1", CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK},
.gate = { CGU_REG_CLKGR, 15 },
},
[X1000_CLK_UART2] = {
"uart2", CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK },
.gate = { CGU_REG_CLKGR, 16 },
},
[X1000_CLK_TCU] = {
"tcu", CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK },
.gate = { CGU_REG_CLKGR, 18 },
},
[X1000_CLK_SSI] = {
"ssi", CGU_CLK_GATE,
.parents = { X1000_CLK_SSIMUX },
.gate = { CGU_REG_CLKGR, 19 },
},
[X1000_CLK_OST] = {
"ost", CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK },
.gate = { CGU_REG_CLKGR, 20 },
},
[X1000_CLK_PDMA] = {
"pdma", CGU_CLK_GATE,
.parents = { X1000_CLK_EXCLK },
.gate = { CGU_REG_CLKGR, 21 },
},
};
static void __init x1000_cgu_init(struct device_node *np)
{
int retval;
cgu = ingenic_cgu_new(x1000_cgu_clocks,
ARRAY_SIZE(x1000_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval) {
pr_err("%s: failed to register CGU Clocks\n", __func__);
return;
}
ingenic_cgu_register_syscore(cgu);
}
/*
* CGU has some children devices, this is useful for probing children devices
* in the case where the device node is compatible with "simple-mfd".
*/
CLK_OF_DECLARE_DRIVER(x1000_cgu, "ingenic,x1000-cgu", x1000_cgu_init);
+472
View File
@@ -0,0 +1,472 @@
// SPDX-License-Identifier: GPL-2.0
/*
* X1830 SoC CGU driver
* Copyright (c) 2019 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <dt-bindings/clock/ingenic,x1830-cgu.h>
#include "cgu.h"
#include "pm.h"
/* CGU register offsets */
#define CGU_REG_CPCCR 0x00
#define CGU_REG_CPPCR 0x0c
#define CGU_REG_APLL 0x10
#define CGU_REG_MPLL 0x14
#define CGU_REG_CLKGR0 0x20
#define CGU_REG_OPCR 0x24
#define CGU_REG_CLKGR1 0x28
#define CGU_REG_DDRCDR 0x2c
#define CGU_REG_USBPCR 0x3c
#define CGU_REG_USBRDT 0x40
#define CGU_REG_USBVBFIL 0x44
#define CGU_REG_USBPCR1 0x48
#define CGU_REG_MACCDR 0x54
#define CGU_REG_EPLL 0x58
#define CGU_REG_I2SCDR 0x60
#define CGU_REG_LPCDR 0x64
#define CGU_REG_MSC0CDR 0x68
#define CGU_REG_I2SCDR1 0x70
#define CGU_REG_SSICDR 0x74
#define CGU_REG_CIMCDR 0x7c
#define CGU_REG_MSC1CDR 0xa4
#define CGU_REG_CMP_INTR 0xb0
#define CGU_REG_CMP_INTRE 0xb4
#define CGU_REG_DRCG 0xd0
#define CGU_REG_CPCSR 0xd4
#define CGU_REG_VPLL 0xe0
#define CGU_REG_MACPHYC 0xe8
/* bits within the OPCR register */
#define OPCR_GATE_USBPHYCLK BIT(23)
#define OPCR_SPENDN0 BIT(7)
#define OPCR_SPENDN1 BIT(6)
/* bits within the USBPCR register */
#define USBPCR_SIDDQ BIT(21)
#define USBPCR_OTG_DISABLE BIT(20)
static struct ingenic_cgu *cgu;
static int x1830_usb_phy_enable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
writel((readl(reg_opcr) | OPCR_SPENDN0) & ~OPCR_GATE_USBPHYCLK, reg_opcr);
writel(readl(reg_usbpcr) & ~USBPCR_OTG_DISABLE & ~USBPCR_SIDDQ, reg_usbpcr);
return 0;
}
static void x1830_usb_phy_disable(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
writel((readl(reg_opcr) & ~OPCR_SPENDN0) | OPCR_GATE_USBPHYCLK, reg_opcr);
writel(readl(reg_usbpcr) | USBPCR_OTG_DISABLE | USBPCR_SIDDQ, reg_usbpcr);
}
static int x1830_usb_phy_is_enabled(struct clk_hw *hw)
{
void __iomem *reg_opcr = cgu->base + CGU_REG_OPCR;
void __iomem *reg_usbpcr = cgu->base + CGU_REG_USBPCR;
return (readl(reg_opcr) & OPCR_SPENDN0) &&
!(readl(reg_usbpcr) & USBPCR_SIDDQ) &&
!(readl(reg_usbpcr) & USBPCR_OTG_DISABLE);
}
static const struct clk_ops x1830_otg_phy_ops = {
.enable = x1830_usb_phy_enable,
.disable = x1830_usb_phy_disable,
.is_enabled = x1830_usb_phy_is_enabled,
};
static const s8 pll_od_encoding[64] = {
0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3,
-1, -1, -1, -1, -1, -1, -1, 0x4,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 0x5,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, 0x6,
};
static const struct ingenic_cgu_clk_info x1830_cgu_clocks[] = {
/* External clocks */
[X1830_CLK_EXCLK] = { "ext", CGU_CLK_EXT },
[X1830_CLK_RTCLK] = { "rtc", CGU_CLK_EXT },
/* PLLs */
[X1830_CLK_APLL] = {
"apll", CGU_CLK_PLL,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.pll = {
.reg = CGU_REG_APLL,
.rate_multiplier = 2,
.m_shift = 20,
.m_bits = 9,
.m_offset = 1,
.n_shift = 14,
.n_bits = 6,
.n_offset = 1,
.od_shift = 11,
.od_bits = 3,
.od_max = 64,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_CPPCR,
.bypass_bit = 30,
.enable_bit = 0,
.stable_bit = 3,
},
},
[X1830_CLK_MPLL] = {
"mpll", CGU_CLK_PLL,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.pll = {
.reg = CGU_REG_MPLL,
.rate_multiplier = 2,
.m_shift = 20,
.m_bits = 9,
.m_offset = 1,
.n_shift = 14,
.n_bits = 6,
.n_offset = 1,
.od_shift = 11,
.od_bits = 3,
.od_max = 64,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_CPPCR,
.bypass_bit = 28,
.enable_bit = 0,
.stable_bit = 3,
},
},
[X1830_CLK_EPLL] = {
"epll", CGU_CLK_PLL,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.pll = {
.reg = CGU_REG_EPLL,
.rate_multiplier = 2,
.m_shift = 20,
.m_bits = 9,
.m_offset = 1,
.n_shift = 14,
.n_bits = 6,
.n_offset = 1,
.od_shift = 11,
.od_bits = 3,
.od_max = 64,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_CPPCR,
.bypass_bit = 24,
.enable_bit = 0,
.stable_bit = 3,
},
},
[X1830_CLK_VPLL] = {
"vpll", CGU_CLK_PLL,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.pll = {
.reg = CGU_REG_VPLL,
.rate_multiplier = 2,
.m_shift = 20,
.m_bits = 9,
.m_offset = 1,
.n_shift = 14,
.n_bits = 6,
.n_offset = 1,
.od_shift = 11,
.od_bits = 3,
.od_max = 64,
.od_encoding = pll_od_encoding,
.bypass_reg = CGU_REG_CPPCR,
.bypass_bit = 26,
.enable_bit = 0,
.stable_bit = 3,
},
},
/* Custom (SoC-specific) OTG PHY */
[X1830_CLK_OTGPHY] = {
"otg_phy", CGU_CLK_CUSTOM,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.custom = { &x1830_otg_phy_ops },
},
/* Muxes & dividers */
[X1830_CLK_SCLKA] = {
"sclk_a", CGU_CLK_MUX,
.parents = { -1, X1830_CLK_EXCLK, X1830_CLK_APLL, -1 },
.mux = { CGU_REG_CPCCR, 30, 2 },
},
[X1830_CLK_CPUMUX] = {
"cpu_mux", CGU_CLK_MUX,
.parents = { -1, X1830_CLK_SCLKA, X1830_CLK_MPLL, -1 },
.mux = { CGU_REG_CPCCR, 28, 2 },
},
[X1830_CLK_CPU] = {
"cpu", CGU_CLK_DIV | CGU_CLK_GATE,
.flags = CLK_IS_CRITICAL,
.parents = { X1830_CLK_CPUMUX, -1, -1, -1 },
.div = { CGU_REG_CPCCR, 0, 1, 4, 22, -1, -1 },
.gate = { CGU_REG_CLKGR1, 15 },
},
[X1830_CLK_L2CACHE] = {
"l2cache", CGU_CLK_DIV,
/*
* The L2 cache clock is critical if caches are enabled and
* disabling it or any parent clocks will hang the system.
*/
.flags = CLK_IS_CRITICAL,
.parents = { X1830_CLK_CPUMUX, -1, -1, -1 },
.div = { CGU_REG_CPCCR, 4, 1, 4, 22, -1, -1 },
},
[X1830_CLK_AHB0] = {
"ahb0", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { -1, X1830_CLK_SCLKA, X1830_CLK_MPLL, -1 },
.mux = { CGU_REG_CPCCR, 26, 2 },
.div = { CGU_REG_CPCCR, 8, 1, 4, 21, -1, -1 },
},
[X1830_CLK_AHB2PMUX] = {
"ahb2_apb_mux", CGU_CLK_MUX,
.parents = { -1, X1830_CLK_SCLKA, X1830_CLK_MPLL, -1 },
.mux = { CGU_REG_CPCCR, 24, 2 },
},
[X1830_CLK_AHB2] = {
"ahb2", CGU_CLK_DIV,
.parents = { X1830_CLK_AHB2PMUX, -1, -1, -1 },
.div = { CGU_REG_CPCCR, 12, 1, 4, 20, -1, -1 },
},
[X1830_CLK_PCLK] = {
"pclk", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1830_CLK_AHB2PMUX, -1, -1, -1 },
.div = { CGU_REG_CPCCR, 16, 1, 4, 20, -1, -1 },
.gate = { CGU_REG_CLKGR1, 14 },
},
[X1830_CLK_DDR] = {
"ddr", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
/*
* Disabling DDR clock or its parents will render DRAM
* inaccessible; mark it critical.
*/
.flags = CLK_IS_CRITICAL,
.parents = { -1, X1830_CLK_SCLKA, X1830_CLK_MPLL, -1 },
.mux = { CGU_REG_DDRCDR, 30, 2 },
.div = { CGU_REG_DDRCDR, 0, 1, 4, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 31 },
},
[X1830_CLK_MAC] = {
"mac", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1830_CLK_SCLKA, X1830_CLK_MPLL,
X1830_CLK_VPLL, X1830_CLK_EPLL },
.mux = { CGU_REG_MACCDR, 30, 2 },
.div = { CGU_REG_MACCDR, 0, 1, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR1, 4 },
},
[X1830_CLK_LCD] = {
"lcd", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1830_CLK_SCLKA, X1830_CLK_MPLL,
X1830_CLK_VPLL, X1830_CLK_EPLL },
.mux = { CGU_REG_LPCDR, 30, 2 },
.div = { CGU_REG_LPCDR, 0, 1, 8, 28, 27, 26 },
.gate = { CGU_REG_CLKGR1, 9 },
},
[X1830_CLK_MSCMUX] = {
"msc_mux", CGU_CLK_MUX,
.parents = { X1830_CLK_SCLKA, X1830_CLK_MPLL,
X1830_CLK_VPLL, X1830_CLK_EPLL },
.mux = { CGU_REG_MSC0CDR, 30, 2 },
},
[X1830_CLK_MSC0] = {
"msc0", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1830_CLK_MSCMUX, -1, -1, -1 },
.div = { CGU_REG_MSC0CDR, 0, 2, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 4 },
},
[X1830_CLK_MSC1] = {
"msc1", CGU_CLK_DIV | CGU_CLK_GATE,
.parents = { X1830_CLK_MSCMUX, -1, -1, -1 },
.div = { CGU_REG_MSC1CDR, 0, 2, 8, 29, 28, 27 },
.gate = { CGU_REG_CLKGR0, 5 },
},
[X1830_CLK_SSIPLL] = {
"ssi_pll", CGU_CLK_MUX | CGU_CLK_DIV,
.parents = { X1830_CLK_SCLKA, X1830_CLK_MPLL,
X1830_CLK_VPLL, X1830_CLK_EPLL },
.mux = { CGU_REG_SSICDR, 30, 2 },
.div = { CGU_REG_SSICDR, 0, 1, 8, 28, 27, 26 },
},
[X1830_CLK_SSIPLL_DIV2] = {
"ssi_pll_div2", CGU_CLK_FIXDIV,
.parents = { X1830_CLK_SSIPLL },
.fixdiv = { 2 },
},
[X1830_CLK_SSIMUX] = {
"ssi_mux", CGU_CLK_MUX,
.parents = { X1830_CLK_EXCLK, X1830_CLK_SSIPLL_DIV2, -1, -1 },
.mux = { CGU_REG_SSICDR, 29, 1 },
},
[X1830_CLK_EXCLK_DIV512] = {
"exclk_div512", CGU_CLK_FIXDIV,
.parents = { X1830_CLK_EXCLK },
.fixdiv = { 512 },
},
[X1830_CLK_RTC] = {
"rtc_ercs", CGU_CLK_MUX | CGU_CLK_GATE,
.parents = { X1830_CLK_EXCLK_DIV512, X1830_CLK_RTCLK },
.mux = { CGU_REG_OPCR, 2, 1},
.gate = { CGU_REG_CLKGR0, 29 },
},
/* Gate-only clocks */
[X1830_CLK_EMC] = {
"emc", CGU_CLK_GATE,
.parents = { X1830_CLK_AHB2, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 0 },
},
[X1830_CLK_EFUSE] = {
"efuse", CGU_CLK_GATE,
.parents = { X1830_CLK_AHB2, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 1 },
},
[X1830_CLK_OTG] = {
"otg", CGU_CLK_GATE,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 3 },
},
[X1830_CLK_SSI0] = {
"ssi0", CGU_CLK_GATE,
.parents = { X1830_CLK_SSIMUX, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 6 },
},
[X1830_CLK_SMB0] = {
"smb0", CGU_CLK_GATE,
.parents = { X1830_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 7 },
},
[X1830_CLK_SMB1] = {
"smb1", CGU_CLK_GATE,
.parents = { X1830_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 8 },
},
[X1830_CLK_SMB2] = {
"smb2", CGU_CLK_GATE,
.parents = { X1830_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 9 },
},
[X1830_CLK_UART0] = {
"uart0", CGU_CLK_GATE,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 14 },
},
[X1830_CLK_UART1] = {
"uart1", CGU_CLK_GATE,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 15 },
},
[X1830_CLK_SSI1] = {
"ssi1", CGU_CLK_GATE,
.parents = { X1830_CLK_SSIMUX, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 19 },
},
[X1830_CLK_SFC] = {
"sfc", CGU_CLK_GATE,
.parents = { X1830_CLK_SSIPLL, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 20 },
},
[X1830_CLK_PDMA] = {
"pdma", CGU_CLK_GATE,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 21 },
},
[X1830_CLK_TCU] = {
"tcu", CGU_CLK_GATE,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR0, 30 },
},
[X1830_CLK_DTRNG] = {
"dtrng", CGU_CLK_GATE,
.parents = { X1830_CLK_PCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 1 },
},
[X1830_CLK_OST] = {
"ost", CGU_CLK_GATE,
.parents = { X1830_CLK_EXCLK, -1, -1, -1 },
.gate = { CGU_REG_CLKGR1, 11 },
},
};
static void __init x1830_cgu_init(struct device_node *np)
{
int retval;
cgu = ingenic_cgu_new(x1830_cgu_clocks,
ARRAY_SIZE(x1830_cgu_clocks), np);
if (!cgu) {
pr_err("%s: failed to initialise CGU\n", __func__);
return;
}
retval = ingenic_cgu_register_clocks(cgu);
if (retval) {
pr_err("%s: failed to register CGU Clocks\n", __func__);
return;
}
ingenic_cgu_register_syscore(cgu);
}
/*
* CGU has some children devices, this is useful for probing children devices
* in the case where the device node is compatible with "simple-mfd".
*/
CLK_OF_DECLARE_DRIVER(x1830_cgu, "ingenic,x1830-cgu", x1830_cgu_init);