mirror of
https://git.hardenedbsd.org/hardenedbsd/HardenedBSD.git
synced 2024-11-22 11:14:18 +01:00
qcom_clk: add the qualcomm clock nodes for the IPQ4018
These clock nodes are used by the IPQ4018/IPQ4019 and derivatives. They're also used by other 32 and 64 bit qualcomm parts; so it's best to put these nodes here in a single qcom_clk driver and add to it as we grow new Qualcomm SoC support. Tested: * IPQ4018, boot Differential Revision: https://reviews.freebsd.org/D33665
This commit is contained in:
parent
7d4394cbe7
commit
e34a491b35
@ -8,6 +8,14 @@ arm/qualcomm/qcom_gcc_ipq4018.c optional qcom_gcc_ipq4018
|
||||
arm/qualcomm/qcom_gcc_ipq4018_reset.c optional qcom_gcc_ipq4018
|
||||
arm/qualcomm/qcom_gcc_ipq4018_clock.c optional qcom_gcc_ipq4018
|
||||
|
||||
dev/qcom_clk/qcom_clk_fepll.c optional qcom_gcc_ipq4018
|
||||
dev/qcom_clk/qcom_clk_fdiv.c optional qcom_gcc_ipq4018
|
||||
dev/qcom_clk/qcom_clk_apssdiv.c optional qcom_gcc_ipq4018
|
||||
dev/qcom_clk/qcom_clk_freqtbl.c optional qcom_gcc_ipq4018
|
||||
dev/qcom_clk/qcom_clk_rcg2.c optional qcom_gcc_ipq4018
|
||||
dev/qcom_clk/qcom_clk_branch2.c optional qcom_gcc_ipq4018
|
||||
dev/qcom_clk/qcom_clk_ro_div.c optional qcom_gcc_ipq4018
|
||||
|
||||
dev/qcom_tlmm/qcom_tlmm_debug.c optional qcom_tlmm_ipq4018
|
||||
dev/qcom_tlmm/qcom_tlmm_ipq4018.c optional qcom_tlmm_ipq4018
|
||||
dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.c optional qcom_tlmm_ipq4018
|
||||
|
287
sys/dev/qcom_clk/qcom_clk_apssdiv.c
Normal file
287
sys/dev/qcom_clk/qcom_clk_apssdiv.c
Normal file
@ -0,0 +1,287 @@
|
||||
/*-
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/rman.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/extres/clk/clk.h>
|
||||
#include <dev/extres/clk/clk_div.h>
|
||||
#include <dev/extres/clk/clk_fixed.h>
|
||||
#include <dev/extres/clk/clk_mux.h>
|
||||
|
||||
#include "qcom_clk_freqtbl.h"
|
||||
#include "qcom_clk_apssdiv.h"
|
||||
|
||||
#include "clkdev_if.h"
|
||||
|
||||
/*
|
||||
* This is a combination gate, divisor/PLL configuration
|
||||
* for the APSS CPU clock.
|
||||
*/
|
||||
|
||||
#if 0
|
||||
#define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
|
||||
#else
|
||||
#define DPRINTF(dev, msg...)
|
||||
#endif
|
||||
|
||||
struct qcom_clk_apssdiv_sc {
|
||||
struct clknode *clknode;
|
||||
uint32_t div_offset;
|
||||
uint32_t div_width;
|
||||
uint32_t div_shift;
|
||||
uint32_t enable_offset;
|
||||
uint32_t enable_shift;
|
||||
const struct qcom_clk_freq_tbl *freq_tbl;
|
||||
};
|
||||
|
||||
static uint64_t
|
||||
qcom_clk_apssdiv_calc_rate(struct clknode *clk, uint64_t freq, uint32_t cdiv)
|
||||
{
|
||||
uint32_t pre_div;
|
||||
|
||||
/*
|
||||
* The divisor isn't a linear map with a linear pre-divisor.
|
||||
*/
|
||||
if (cdiv > 10) {
|
||||
pre_div = (cdiv + 1) * 2;
|
||||
} else {
|
||||
pre_div = cdiv + 12;
|
||||
}
|
||||
/*
|
||||
* Multiplier is a fixed "2" here.
|
||||
*/
|
||||
return (freq * 2L) / pre_div;
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_apssdiv_recalc(struct clknode *clk, uint64_t *freq)
|
||||
{
|
||||
struct qcom_clk_apssdiv_sc *sc;
|
||||
uint32_t reg, cdiv;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
if (freq == NULL || *freq == 0) {
|
||||
printf("%s: called; NULL or 0 frequency\n", __func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®);
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
cdiv = (reg >> sc->div_shift) & ((1U << sc->div_width) - 1);
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: called; cdiv=0x%x, freq=%llu\n", __func__, cdiv, *freq);
|
||||
|
||||
*freq = qcom_clk_apssdiv_calc_rate(clk, *freq, cdiv);
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: called; freq is %llu\n", __func__, *freq);
|
||||
return (0);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static bool
|
||||
qcom_clk_apssdiv_get_gate_locked(struct qcom_clk_apssdiv_sc *sc)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
if (sc->enable_offset == 0)
|
||||
return (false);
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
|
||||
®);
|
||||
|
||||
return (!! (reg & (1U << sc->enable_shift)));
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
qcom_clk_apssdiv_init(struct clknode *clk, device_t dev)
|
||||
{
|
||||
struct qcom_clk_apssdiv_sc *sc;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
/*
|
||||
* There's only a single parent here for an fixed divisor,
|
||||
* so just set it to 0; the caller doesn't need to supply it.
|
||||
*
|
||||
* Note that the freqtbl entries have an upstream clock,
|
||||
* but the APSS div/gate only has a single upstream and we
|
||||
* don't program anything else specific in here.
|
||||
*/
|
||||
clknode_init_parent_idx(clk, 0);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_apssdiv_set_gate(struct clknode *clk, bool enable)
|
||||
{
|
||||
struct qcom_clk_apssdiv_sc *sc;
|
||||
uint32_t reg;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
if (sc->enable_offset == 0) {
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: called; enable=%d\n", __func__, enable);
|
||||
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
|
||||
®);
|
||||
if (enable) {
|
||||
reg |= (1U << sc->enable_shift);
|
||||
} else {
|
||||
reg &= ~(1U << sc->enable_shift);
|
||||
}
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->enable_offset,
|
||||
reg);
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set frequency
|
||||
*
|
||||
* fin - the parent frequency, if exists
|
||||
* fout - starts as the requested frequency, ends with the configured
|
||||
* or dry-run frequency
|
||||
* Flags - CLK_SET_DRYRUN, CLK_SET_ROUND_UP, CLK_SET_ROUND_DOWN
|
||||
* retval - 0, ERANGE
|
||||
*/
|
||||
static int
|
||||
qcom_clk_apssdiv_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
|
||||
int flags, int *stop)
|
||||
{
|
||||
const struct qcom_clk_freq_tbl *f;
|
||||
struct qcom_clk_apssdiv_sc *sc;
|
||||
uint64_t f_freq;
|
||||
uint32_t reg;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
/* There are no further PLLs to set in this chain */
|
||||
*stop = 1;
|
||||
|
||||
/* Search the table for a suitable frequency */
|
||||
f = qcom_clk_freq_tbl_lookup(sc->freq_tbl, *fout);
|
||||
if (f == NULL) {
|
||||
return (ERANGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate what the resultant frequency would be based on the
|
||||
* parent PLL.
|
||||
*/
|
||||
f_freq = qcom_clk_apssdiv_calc_rate(clk, fin, f->pre_div);
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: dryrun: %d, fin=%llu fout=%llu f_freq=%llu pre_div=%u"
|
||||
" target_freq=%llu\n",
|
||||
__func__,
|
||||
!! (flags & CLK_SET_DRYRUN),
|
||||
fin, *fout, f_freq, f->pre_div, f->freq);
|
||||
|
||||
if (flags & CLK_SET_DRYRUN) {
|
||||
*fout = f_freq;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Program in the new pre-divisor.
|
||||
*/
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®);
|
||||
reg &= ~(((1U << sc->div_width) - 1) << sc->div_shift);
|
||||
reg |= (f->pre_div << sc->div_shift);
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->div_offset, reg);
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
|
||||
/*
|
||||
* The linux driver notes there's no status/completion bit to poll.
|
||||
* So sleep for a bit and hope that's enough time for it to
|
||||
* settle.
|
||||
*/
|
||||
DELAY(1);
|
||||
|
||||
*fout = f_freq;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static clknode_method_t qcom_clk_apssdiv_methods[] = {
|
||||
/* Device interface */
|
||||
CLKNODEMETHOD(clknode_init, qcom_clk_apssdiv_init),
|
||||
CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_apssdiv_recalc),
|
||||
CLKNODEMETHOD(clknode_set_gate, qcom_clk_apssdiv_set_gate),
|
||||
CLKNODEMETHOD(clknode_set_freq, qcom_clk_apssdiv_set_freq),
|
||||
CLKNODEMETHOD_END
|
||||
};
|
||||
|
||||
DEFINE_CLASS_1(qcom_clk_apssdiv, qcom_clk_apssdiv_class,
|
||||
qcom_clk_apssdiv_methods, sizeof(struct qcom_clk_apssdiv_sc),
|
||||
clknode_class);
|
||||
|
||||
int
|
||||
qcom_clk_apssdiv_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_apssdiv_def *clkdef)
|
||||
{
|
||||
struct clknode *clk;
|
||||
struct qcom_clk_apssdiv_sc *sc;
|
||||
|
||||
clk = clknode_create(clkdom, &qcom_clk_apssdiv_class, &clkdef->clkdef);
|
||||
if (clk == NULL)
|
||||
return (1);
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
sc->clknode = clk;
|
||||
|
||||
sc->div_offset = clkdef->div_offset;
|
||||
sc->div_width = clkdef->div_width;
|
||||
sc->div_shift = clkdef->div_shift;
|
||||
sc->freq_tbl = clkdef->freq_tbl;
|
||||
sc->enable_offset = clkdef->enable_offset;
|
||||
sc->enable_shift = clkdef->enable_shift;
|
||||
|
||||
clknode_register(clkdom, clk);
|
||||
|
||||
return (0);
|
||||
}
|
48
sys/dev/qcom_clk/qcom_clk_apssdiv.h
Normal file
48
sys/dev/qcom_clk/qcom_clk_apssdiv.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_APSS_H__
|
||||
#define __QCOM_CLK_APSS_H__
|
||||
|
||||
#include "qcom_clk_freqtbl.h"
|
||||
|
||||
struct qcom_clk_apssdiv_def {
|
||||
struct clknode_init_def clkdef;
|
||||
uint32_t div_offset;
|
||||
uint32_t div_width;
|
||||
uint32_t div_shift;
|
||||
uint32_t enable_offset;
|
||||
uint32_t enable_shift;
|
||||
const struct qcom_clk_freq_tbl *freq_tbl;
|
||||
};
|
||||
|
||||
extern int qcom_clk_apssdiv_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_apssdiv_def *clkdef);
|
||||
|
||||
#endif /* __QCOM_CLK_APSS_H__ */
|
290
sys/dev/qcom_clk/qcom_clk_branch2.c
Normal file
290
sys/dev/qcom_clk/qcom_clk_branch2.c
Normal file
@ -0,0 +1,290 @@
|
||||
/*-
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/rman.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/extres/clk/clk.h>
|
||||
#include <dev/extres/clk/clk_div.h>
|
||||
#include <dev/extres/clk/clk_fixed.h>
|
||||
#include <dev/extres/clk/clk_mux.h>
|
||||
|
||||
#include "qcom_clk_branch2.h"
|
||||
#include "qcom_clk_branch2_reg.h"
|
||||
|
||||
#include "clkdev_if.h"
|
||||
|
||||
/*
|
||||
* This is a combination gate/status and dynamic hardware clock gating with
|
||||
* voting.
|
||||
*/
|
||||
|
||||
#if 0
|
||||
#define DPRINTF(dev, msg...) device_printf(dev, msg);
|
||||
#else
|
||||
#define DPRINTF(dev, msg...)
|
||||
#endif
|
||||
|
||||
struct qcom_clk_branch2_sc {
|
||||
struct clknode *clknode;
|
||||
uint32_t flags;
|
||||
uint32_t enable_offset;
|
||||
uint32_t enable_shift;
|
||||
uint32_t hwcg_reg;
|
||||
uint32_t hwcg_bit;
|
||||
uint32_t halt_reg;
|
||||
uint32_t halt_check_type;
|
||||
bool halt_check_voted;
|
||||
};
|
||||
#if 0
|
||||
static bool
|
||||
qcom_clk_branch2_get_gate_locked(struct qcom_clk_branch2_sc *sc)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
|
||||
®);
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: offset=0x%x, reg=0x%x\n", __func__,
|
||||
sc->enable_offset, reg);
|
||||
|
||||
return (!! (reg & (1U << sc->enable_shift)));
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
qcom_clk_branch2_init(struct clknode *clk, device_t dev)
|
||||
{
|
||||
|
||||
clknode_init_parent_idx(clk, 0);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static bool
|
||||
qcom_clk_branch2_in_hwcg_mode_locked(struct qcom_clk_branch2_sc *sc)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
if (sc->hwcg_reg == 0)
|
||||
return (false);
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->hwcg_reg,
|
||||
®);
|
||||
|
||||
return (!! (reg & (1U << sc->hwcg_bit)));
|
||||
}
|
||||
|
||||
static bool
|
||||
qcom_clk_branch2_check_halt_locked(struct qcom_clk_branch2_sc *sc, bool enable)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->halt_reg, ®);
|
||||
|
||||
if (enable) {
|
||||
/*
|
||||
* The upstream Linux code is .. unfortunate.
|
||||
*
|
||||
* Here it says "return true if BRANCH_CLK_OFF is not set,
|
||||
* or if the status field = FSM_STATUS_ON AND
|
||||
* the clk_off field is 0.
|
||||
*
|
||||
* Which .. is weird, because I can't currently see
|
||||
* how we'd ever need to check FSM_STATUS_ON - the only
|
||||
* valid check for the FSM status also requires clk_off=0.
|
||||
*/
|
||||
return !! ((reg & QCOM_CLK_BRANCH2_CLK_OFF) == 0);
|
||||
} else {
|
||||
return !! (reg & QCOM_CLK_BRANCH2_CLK_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the given type/voted flag match what is configured.
|
||||
*/
|
||||
static bool
|
||||
qcom_clk_branch2_halt_check_type(struct qcom_clk_branch2_sc *sc,
|
||||
uint32_t type, bool voted)
|
||||
{
|
||||
return ((sc->halt_check_type == type) &&
|
||||
(sc->halt_check_voted == voted));
|
||||
}
|
||||
|
||||
static bool
|
||||
qcom_clk_branch2_wait_locked(struct qcom_clk_branch2_sc *sc, bool enable)
|
||||
{
|
||||
|
||||
if (qcom_clk_branch2_halt_check_type(sc,
|
||||
QCOM_CLK_BRANCH2_BRANCH_HALT_SKIP, false))
|
||||
return (true);
|
||||
if (qcom_clk_branch2_in_hwcg_mode_locked(sc))
|
||||
return (true);
|
||||
|
||||
if ((qcom_clk_branch2_halt_check_type(sc,
|
||||
QCOM_CLK_BRANCH2_BRANCH_HALT_DELAY, false)) ||
|
||||
(enable == false && sc->halt_check_voted)) {
|
||||
DELAY(10);
|
||||
return (true);
|
||||
}
|
||||
|
||||
if ((qcom_clk_branch2_halt_check_type(sc,
|
||||
QCOM_CLK_BRANCH2_BRANCH_HALT_INVERTED, false)) ||
|
||||
(qcom_clk_branch2_halt_check_type(sc,
|
||||
QCOM_CLK_BRANCH2_BRANCH_HALT, false)) ||
|
||||
(enable && sc->halt_check_voted)) {
|
||||
int count;
|
||||
|
||||
for (count = 0; count < 200; count++) {
|
||||
if (qcom_clk_branch2_check_halt_locked(sc, enable))
|
||||
return (true);
|
||||
DELAY(1);
|
||||
}
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: enable stuck (%d)!\n", __func__, enable);
|
||||
return (false);
|
||||
}
|
||||
|
||||
/* Default */
|
||||
return (true);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_branch2_set_gate(struct clknode *clk, bool enable)
|
||||
{
|
||||
struct qcom_clk_branch2_sc *sc;
|
||||
uint32_t reg;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode), "%s: called\n", __func__);
|
||||
|
||||
if (sc->enable_offset == 0) {
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: no enable_offset", __func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: called; enable=%d\n", __func__, enable);
|
||||
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
|
||||
®);
|
||||
if (enable) {
|
||||
reg |= (1U << sc->enable_shift);
|
||||
} else {
|
||||
reg &= ~(1U << sc->enable_shift);
|
||||
}
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->enable_offset,
|
||||
reg);
|
||||
|
||||
/*
|
||||
* Now wait for the clock branch to update!
|
||||
*/
|
||||
if (! qcom_clk_branch2_wait_locked(sc, enable)) {
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: failed to wait!\n", __func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_branch2_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
|
||||
int flags, int *stop)
|
||||
{
|
||||
struct qcom_clk_branch2_sc *sc;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
/* We only support what our parent clock is currently set as */
|
||||
*fout = fin;
|
||||
|
||||
/* .. and stop here if we don't have SET_RATE_PARENT */
|
||||
if (sc->flags & QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT)
|
||||
*stop = 0;
|
||||
else
|
||||
*stop = 1;
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
static clknode_method_t qcom_clk_branch2_methods[] = {
|
||||
/* Device interface */
|
||||
CLKNODEMETHOD(clknode_init, qcom_clk_branch2_init),
|
||||
CLKNODEMETHOD(clknode_set_gate, qcom_clk_branch2_set_gate),
|
||||
CLKNODEMETHOD(clknode_set_freq, qcom_clk_branch2_set_freq),
|
||||
CLKNODEMETHOD_END
|
||||
};
|
||||
|
||||
DEFINE_CLASS_1(qcom_clk_branch2, qcom_clk_branch2_class,
|
||||
qcom_clk_branch2_methods, sizeof(struct qcom_clk_branch2_sc),
|
||||
clknode_class);
|
||||
|
||||
int
|
||||
qcom_clk_branch2_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_branch2_def *clkdef)
|
||||
{
|
||||
struct clknode *clk;
|
||||
struct qcom_clk_branch2_sc *sc;
|
||||
|
||||
if (clkdef->flags & QCOM_CLK_BRANCH2_FLAGS_CRITICAL)
|
||||
clkdef->clkdef.flags |= CLK_NODE_CANNOT_STOP;
|
||||
|
||||
clk = clknode_create(clkdom, &qcom_clk_branch2_class,
|
||||
&clkdef->clkdef);
|
||||
if (clk == NULL)
|
||||
return (1);
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
sc->clknode = clk;
|
||||
|
||||
sc->enable_offset = clkdef->enable_offset;
|
||||
sc->enable_shift = clkdef->enable_shift;
|
||||
sc->halt_reg = clkdef->halt_reg;
|
||||
sc->hwcg_reg = clkdef->hwcg_reg;
|
||||
sc->hwcg_bit = clkdef->hwcg_bit;
|
||||
sc->halt_check_type = clkdef->halt_check_type;
|
||||
sc->halt_check_voted = clkdef->halt_check_voted;
|
||||
sc->flags = clkdef->flags;
|
||||
|
||||
clknode_register(clkdom, clk);
|
||||
|
||||
return (0);
|
||||
}
|
70
sys/dev/qcom_clk/qcom_clk_branch2.h
Normal file
70
sys/dev/qcom_clk/qcom_clk_branch2.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_BRANCH2_H__
|
||||
#define __QCOM_CLK_BRANCH2_H__
|
||||
|
||||
#include "qcom_clk_freqtbl.h"
|
||||
|
||||
/* halt is 1 */
|
||||
#define QCOM_CLK_BRANCH2_BRANCH_HALT 0
|
||||
|
||||
/* halt is inverted (ie, 0) */
|
||||
#define QCOM_CLK_BRANCH2_BRANCH_HALT_INVERTED 1
|
||||
|
||||
/* Don't check the bit, just delay */
|
||||
#define QCOM_CLK_BRANCH2_BRANCH_HALT_DELAY 2
|
||||
|
||||
/* Don't check the halt bit at all */
|
||||
#define QCOM_CLK_BRANCH2_BRANCH_HALT_SKIP 3
|
||||
|
||||
/* Flags */
|
||||
#define QCOM_CLK_BRANCH2_FLAGS_CRITICAL 0x1
|
||||
#define QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT 0x2
|
||||
|
||||
struct qcom_clk_branch2_def {
|
||||
struct clknode_init_def clkdef;
|
||||
|
||||
uint32_t flags;
|
||||
|
||||
uint32_t enable_offset; /* enable register*/
|
||||
uint32_t enable_shift; /* enable bit shift */
|
||||
|
||||
uint32_t hwcg_reg; /* hw clock gate register */
|
||||
uint32_t hwcg_bit;
|
||||
uint32_t halt_reg; /* halt register */
|
||||
|
||||
uint32_t halt_check_type;
|
||||
bool halt_check_voted; /* whether to delay when waiting */
|
||||
};
|
||||
|
||||
extern int qcom_clk_branch2_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_branch2_def *clkdef);
|
||||
|
||||
#endif /* __QCOM_CLK_BRANCH2_H__ */
|
39
sys/dev/qcom_clk/qcom_clk_branch2_reg.h
Normal file
39
sys/dev/qcom_clk/qcom_clk_branch2_reg.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_BRANCH2_REG_H__
|
||||
#define __QCOM_CLK_BRANCH2_REG_H__
|
||||
|
||||
#define QCOM_CLK_BRANCH2_CLK_OFF (1U << 31)
|
||||
#define QCOM_CLK_BRANCH2_NOC_FSM_STATUS_SHIFT 28
|
||||
#define QCOM_CLK_BRANCH2_NOC_FSM_STATUS_MASK 0x7
|
||||
#define QCOM_CLK_BRANCH2_NOC_FSM_STATUS_ON \
|
||||
(0x2 << QCOM_CLK_BRANCH2_NOC_FSM_STATUS_SHIFT)
|
||||
|
||||
#endif /* __QCOM_CLK_BRANCH2_REG_H__ */
|
115
sys/dev/qcom_clk/qcom_clk_fdiv.c
Normal file
115
sys/dev/qcom_clk/qcom_clk_fdiv.c
Normal file
@ -0,0 +1,115 @@
|
||||
/*-
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/rman.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/extres/clk/clk.h>
|
||||
#include <dev/extres/clk/clk_div.h>
|
||||
#include <dev/extres/clk/clk_fixed.h>
|
||||
#include <dev/extres/clk/clk_mux.h>
|
||||
|
||||
#include "qcom_clk_fdiv.h"
|
||||
|
||||
#include "clkdev_if.h"
|
||||
|
||||
/*
|
||||
* This is a fixed divisor node. It represents some divisor
|
||||
* that is setup by the boot environment and we don't have
|
||||
* any need for the driver to go and fiddle with.
|
||||
*
|
||||
* It likely should just live in the extres/clk code.
|
||||
*/
|
||||
|
||||
struct qcom_clk_fdiv_sc {
|
||||
struct clknode *clknode;
|
||||
uint32_t divisor;
|
||||
};
|
||||
|
||||
static int
|
||||
qcom_clk_fdiv_recalc(struct clknode *clk, uint64_t *freq)
|
||||
{
|
||||
struct qcom_clk_fdiv_sc *sc;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
if (freq == NULL || *freq == 0) {
|
||||
printf("%s: called; NULL or 0 frequency\n", __func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
*freq = *freq / sc->divisor;
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_fdiv_init(struct clknode *clk, device_t dev)
|
||||
{
|
||||
/*
|
||||
* There's only a single parent here for an fixed divisor,
|
||||
* so just set it to 0; the caller doesn't need to supply it.
|
||||
*/
|
||||
clknode_init_parent_idx(clk, 0);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
static clknode_method_t qcom_clk_fdiv_methods[] = {
|
||||
/* Device interface */
|
||||
CLKNODEMETHOD(clknode_init, qcom_clk_fdiv_init),
|
||||
CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_fdiv_recalc),
|
||||
CLKNODEMETHOD_END
|
||||
};
|
||||
|
||||
DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_fdiv_class, qcom_clk_fdiv_methods,
|
||||
sizeof(struct qcom_clk_fdiv_sc), clknode_class);
|
||||
|
||||
int
|
||||
qcom_clk_fdiv_register(struct clkdom *clkdom, struct qcom_clk_fdiv_def *clkdef)
|
||||
{
|
||||
struct clknode *clk;
|
||||
struct qcom_clk_fdiv_sc *sc;
|
||||
|
||||
clk = clknode_create(clkdom, &qcom_clk_fdiv_class, &clkdef->clkdef);
|
||||
if (clk == NULL)
|
||||
return (1);
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
sc->clknode = clk;
|
||||
|
||||
sc->divisor = clkdef->divisor;
|
||||
|
||||
clknode_register(clkdom, clk);
|
||||
|
||||
return (0);
|
||||
}
|
41
sys/dev/qcom_clk/qcom_clk_fdiv.h
Normal file
41
sys/dev/qcom_clk/qcom_clk_fdiv.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_FDIV_H__
|
||||
#define __QCOM_CLK_FDIV_H__
|
||||
|
||||
struct qcom_clk_fdiv_def {
|
||||
struct clknode_init_def clkdef;
|
||||
uint32_t divisor; /* Fixed divisor */
|
||||
};
|
||||
|
||||
extern int qcom_clk_fdiv_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_fdiv_def *clkdef);
|
||||
|
||||
#endif /* __QCOM_CLK_FDIV_H__ */
|
153
sys/dev/qcom_clk/qcom_clk_fepll.c
Normal file
153
sys/dev/qcom_clk/qcom_clk_fepll.c
Normal file
@ -0,0 +1,153 @@
|
||||
/*-
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/rman.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/extres/clk/clk.h>
|
||||
#include <dev/extres/clk/clk_div.h>
|
||||
#include <dev/extres/clk/clk_fixed.h>
|
||||
#include <dev/extres/clk/clk_mux.h>
|
||||
|
||||
#include "qcom_clk_fepll.h"
|
||||
|
||||
#include "clkdev_if.h"
|
||||
|
||||
#if 0
|
||||
#define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
|
||||
#else
|
||||
#define DPRINTF(dev, msg...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This is the top-level PLL clock on the IPQ4018/IPQ4019.
|
||||
* It's a fixed PLL clock that feeds a bunch of divisors into
|
||||
* downstrem FEPLL* and DDR clocks.
|
||||
*
|
||||
* Now, on Linux the clock code creates multiple instances of this
|
||||
* with an inbuilt divisor. Here instead there'll be a single
|
||||
* instance of the FEPLL, and then normal divisors will feed into
|
||||
* the multiple PLL nodes.
|
||||
*/
|
||||
|
||||
struct qcom_clk_fepll_sc {
|
||||
struct clknode *clknode;
|
||||
uint32_t offset;
|
||||
uint32_t fdbkdiv_shift; /* FDBKDIV base */
|
||||
uint32_t fdbkdiv_width; /* FDBKDIV width */
|
||||
uint32_t refclkdiv_shift; /* REFCLKDIV base */
|
||||
uint32_t refclkdiv_width; /* REFCLKDIV width */
|
||||
};
|
||||
|
||||
static int
|
||||
qcom_clk_fepll_recalc(struct clknode *clk, uint64_t *freq)
|
||||
{
|
||||
struct qcom_clk_fepll_sc *sc;
|
||||
uint64_t vco, parent_rate;
|
||||
uint32_t reg, fdbkdiv, refclkdiv;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
if (freq == NULL || *freq == 0) {
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: called; NULL or 0 frequency\n",
|
||||
__func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
parent_rate = *freq;
|
||||
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->offset, ®);
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
|
||||
fdbkdiv = (reg >> sc->fdbkdiv_shift) &
|
||||
((1U << sc->fdbkdiv_width) - 1);
|
||||
refclkdiv = (reg >> sc->refclkdiv_shift) &
|
||||
((1U << sc->refclkdiv_width) - 1);
|
||||
|
||||
vco = parent_rate / refclkdiv;
|
||||
vco = vco * 2;
|
||||
vco = vco * fdbkdiv;
|
||||
|
||||
*freq = vco;
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_fepll_init(struct clknode *clk, device_t dev)
|
||||
{
|
||||
|
||||
/*
|
||||
* There's only a single parent here for an FEPLL, so just set it
|
||||
* to 0; the caller doesn't need to supply it.
|
||||
*/
|
||||
clknode_init_parent_idx(clk, 0);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static clknode_method_t qcom_clk_fepll_methods[] = {
|
||||
/* Device interface */
|
||||
CLKNODEMETHOD(clknode_init, qcom_clk_fepll_init),
|
||||
CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_fepll_recalc),
|
||||
CLKNODEMETHOD_END
|
||||
};
|
||||
|
||||
DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_fepll_class, qcom_clk_fepll_methods,
|
||||
sizeof(struct qcom_clk_fepll_sc), clknode_class);
|
||||
|
||||
int
|
||||
qcom_clk_fepll_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_fepll_def *clkdef)
|
||||
{
|
||||
struct clknode *clk;
|
||||
struct qcom_clk_fepll_sc *sc;
|
||||
|
||||
clk = clknode_create(clkdom, &qcom_clk_fepll_class, &clkdef->clkdef);
|
||||
if (clk == NULL)
|
||||
return (1);
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
sc->clknode = clk;
|
||||
|
||||
sc->offset = clkdef->offset;
|
||||
sc->fdbkdiv_shift = clkdef->fdbkdiv_shift;
|
||||
sc->fdbkdiv_width = clkdef->fdbkdiv_width;
|
||||
sc->refclkdiv_shift = clkdef->refclkdiv_shift;
|
||||
sc->refclkdiv_width = clkdef->refclkdiv_width;
|
||||
|
||||
clknode_register(clkdom, clk);
|
||||
|
||||
return (0);
|
||||
}
|
45
sys/dev/qcom_clk/qcom_clk_fepll.h
Normal file
45
sys/dev/qcom_clk/qcom_clk_fepll.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_FEPLL_H__
|
||||
#define __QCOM_CLK_FEPLL_H__
|
||||
|
||||
struct qcom_clk_fepll_def {
|
||||
struct clknode_init_def clkdef;
|
||||
uint32_t offset; /* Register offset */
|
||||
uint32_t fdbkdiv_shift; /* FDBKDIV base */
|
||||
uint32_t fdbkdiv_width; /* FDBKDIV width */
|
||||
uint32_t refclkdiv_shift; /* REFCLKDIV base */
|
||||
uint32_t refclkdiv_width; /* REFCLKDIV width */
|
||||
};
|
||||
|
||||
extern int qcom_clk_fepll_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_fepll_def *clkdef);
|
||||
|
||||
#endif /* __QCOM_CLK_FEPLL_H__ */
|
56
sys/dev/qcom_clk/qcom_clk_freqtbl.c
Normal file
56
sys/dev/qcom_clk/qcom_clk_freqtbl.c
Normal file
@ -0,0 +1,56 @@
|
||||
/*-
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/rman.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include "qcom_clk_freqtbl.h"
|
||||
|
||||
/*
|
||||
* Walk the list of frequencies and return the highest frequency supported.
|
||||
*/
|
||||
const struct qcom_clk_freq_tbl *
|
||||
qcom_clk_freq_tbl_lookup(const struct qcom_clk_freq_tbl *tbl, uint64_t freq)
|
||||
{
|
||||
const struct qcom_clk_freq_tbl *t;
|
||||
|
||||
if (tbl == NULL)
|
||||
return (NULL);
|
||||
|
||||
for (t = tbl; t->freq !=0; t++) {
|
||||
if (freq <= t->freq)
|
||||
return (t);
|
||||
}
|
||||
|
||||
return (NULL);
|
||||
}
|
44
sys/dev/qcom_clk/qcom_clk_freqtbl.h
Normal file
44
sys/dev/qcom_clk/qcom_clk_freqtbl.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_FREQTBL_H__
|
||||
#define __QCOM_CLK_FREQTBL_H__
|
||||
|
||||
struct qcom_clk_freq_tbl {
|
||||
uint64_t freq;
|
||||
const char *parent;
|
||||
uint32_t pre_div;
|
||||
uint32_t m;
|
||||
uint32_t n;
|
||||
};
|
||||
|
||||
const struct qcom_clk_freq_tbl * qcom_clk_freq_tbl_lookup(
|
||||
const struct qcom_clk_freq_tbl *tbl, uint64_t freq);
|
||||
|
||||
#endif /* __QCOM_CLK_FREQTBL_H__ */
|
660
sys/dev/qcom_clk/qcom_clk_rcg2.c
Normal file
660
sys/dev/qcom_clk/qcom_clk_rcg2.c
Normal file
@ -0,0 +1,660 @@
|
||||
/*-
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/rman.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/extres/clk/clk.h>
|
||||
#include <dev/extres/clk/clk_div.h>
|
||||
#include <dev/extres/clk/clk_fixed.h>
|
||||
#include <dev/extres/clk/clk_mux.h>
|
||||
|
||||
#include "qcom_clk_freqtbl.h"
|
||||
#include "qcom_clk_rcg2.h"
|
||||
#include "qcom_clk_rcg2_reg.h"
|
||||
|
||||
#include "clkdev_if.h"
|
||||
|
||||
#if 0
|
||||
#define DPRINTF(dev, msg...) device_printf(dev, msg);
|
||||
#else
|
||||
#define DPRINTF(dev, msg...)
|
||||
#endif
|
||||
|
||||
#define QCOM_CLK_RCG2_CFG_OFFSET(sc) \
|
||||
((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_CFG_REG)
|
||||
#define QCOM_CLK_RCG2_CMD_REGISTER(sc) \
|
||||
((sc)->cmd_rcgr + QCOM_CLK_RCG2_CMD_REG)
|
||||
#define QCOM_CLK_RCG2_M_OFFSET(sc) \
|
||||
((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_M_REG)
|
||||
#define QCOM_CLK_RCG2_N_OFFSET(sc) \
|
||||
((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_N_REG)
|
||||
#define QCOM_CLK_RCG2_D_OFFSET(sc) \
|
||||
((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_D_REG)
|
||||
|
||||
struct qcom_clk_rcg2_sc {
|
||||
struct clknode *clknode;
|
||||
uint32_t cmd_rcgr;
|
||||
uint32_t hid_width;
|
||||
uint32_t mnd_width;
|
||||
int32_t safe_src_idx;
|
||||
uint32_t cfg_offset;
|
||||
int safe_pre_parent_idx;
|
||||
uint32_t flags;
|
||||
const struct qcom_clk_freq_tbl *freq_tbl;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Finish a clock update.
|
||||
*
|
||||
* This instructs the configuration to take effect.
|
||||
*/
|
||||
static bool
|
||||
qcom_clk_rcg2_update_config_locked(struct qcom_clk_rcg2_sc *sc)
|
||||
{
|
||||
uint32_t reg, count;
|
||||
|
||||
/*
|
||||
* Send "update" to the controller.
|
||||
*/
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
|
||||
reg |= QCOM_CLK_RCG2_CMD_UPDATE;
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CMD_REGISTER(sc), reg);
|
||||
wmb();
|
||||
|
||||
/*
|
||||
* Poll for completion of update.
|
||||
*/
|
||||
for (count = 0; count < 1000; count++) {
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
|
||||
if ((reg & QCOM_CLK_RCG2_CMD_UPDATE) == 0) {
|
||||
return (true);
|
||||
}
|
||||
DELAY(10);
|
||||
rmb();
|
||||
}
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
|
||||
DPRINTF(clknode_get_device(sc->clknode), "%s: failed; reg=0x%08x\n",
|
||||
__func__, reg);
|
||||
return (false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the output frequency given an input frequency and the m/n:d
|
||||
* configuration.
|
||||
*/
|
||||
static uint64_t
|
||||
qcom_clk_rcg2_calc_rate(uint64_t rate, uint32_t mode, uint32_t m, uint32_t n,
|
||||
uint32_t hid_div)
|
||||
{
|
||||
if (hid_div != 0) {
|
||||
rate = rate * 2;
|
||||
rate = rate / (hid_div + 1);
|
||||
}
|
||||
|
||||
/* Note: assume n is not 0 here; bad things happen if it is */
|
||||
|
||||
if (mode != 0) {
|
||||
rate = (rate * m) / n;
|
||||
}
|
||||
|
||||
return (rate);
|
||||
}
|
||||
|
||||
/*
|
||||
* The inverse of calc_rate() - calculate the required input frequency
|
||||
* given the desired output freqency and m/n:d configuration.
|
||||
*/
|
||||
static uint64_t
|
||||
qcom_clk_rcg2_calc_input_freq(uint64_t freq, uint32_t m, uint32_t n,
|
||||
uint32_t hid_div)
|
||||
{
|
||||
if (hid_div != 0) {
|
||||
freq = freq / 2;
|
||||
freq = freq * (hid_div + 1);
|
||||
}
|
||||
|
||||
if (n != 0) {
|
||||
freq = (freq * n) / m;
|
||||
}
|
||||
|
||||
return (freq);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_rcg2_recalc(struct clknode *clk, uint64_t *freq)
|
||||
{
|
||||
struct qcom_clk_rcg2_sc *sc;
|
||||
uint32_t cfg, m = 0, n = 0, hid_div = 0;
|
||||
uint32_t mode = 0, mask;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
/* Read the MODE, CFG, M and N parameters */
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CFG_OFFSET(sc),
|
||||
&cfg);
|
||||
if (sc->mnd_width != 0) {
|
||||
mask = (1U << sc->mnd_width) - 1;
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_M_OFFSET(sc), &m);
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_N_OFFSET(sc), &n);
|
||||
m = m & mask;
|
||||
n = ~ n;
|
||||
n = n & mask;
|
||||
n = n + m;
|
||||
mode = (cfg & QCOM_CLK_RCG2_CFG_MODE_MASK)
|
||||
>> QCOM_CLK_RCG2_CFG_MODE_SHIFT;
|
||||
}
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
|
||||
/* Fetch the divisor */
|
||||
mask = (1U << sc->hid_width) - 1;
|
||||
hid_div = (cfg >> QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT) & mask;
|
||||
|
||||
/* Calculate the rate based on the parent rate and config */
|
||||
*freq = qcom_clk_rcg2_calc_rate(*freq, mode, m, n, hid_div);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* configure the mn:d divisor, pre-divisor, and parent.
|
||||
*/
|
||||
static void
|
||||
qcom_clk_rcg2_set_config_locked(struct qcom_clk_rcg2_sc *sc,
|
||||
const struct qcom_clk_freq_tbl *f, int parent_idx)
|
||||
{
|
||||
uint32_t mask, reg;
|
||||
|
||||
/* If we have MN:D, then update it */
|
||||
if (sc->mnd_width != 0 && f->n != 0) {
|
||||
mask = (1U << sc->mnd_width) - 1;
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_M_OFFSET(sc), ®);
|
||||
reg &= ~mask;
|
||||
reg |= (f->m & mask);
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_M_OFFSET(sc), reg);
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_N_OFFSET(sc), ®);
|
||||
reg &= ~mask;
|
||||
reg |= ((~(f->n - f->m)) & mask);
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_N_OFFSET(sc), reg);
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_D_OFFSET(sc), ®);
|
||||
reg &= ~mask;
|
||||
reg |= ((~f->n) & mask);
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_D_OFFSET(sc), reg);
|
||||
}
|
||||
|
||||
mask = (1U << sc->hid_width) - 1;
|
||||
/*
|
||||
* Mask out register fields we're going to modify along with
|
||||
* the pre-divisor.
|
||||
*/
|
||||
mask |= QCOM_CLK_RCG2_CFG_SRC_SEL_MASK
|
||||
| QCOM_CLK_RCG2_CFG_MODE_MASK
|
||||
| QCOM_CLK_RCG2_CFG_HW_CLK_CTRL_MASK;
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
|
||||
reg &= ~mask;
|
||||
|
||||
/* Configure pre-divisor */
|
||||
reg = reg | ((f->pre_div) << QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT);
|
||||
|
||||
/* Configure parent clock */
|
||||
reg = reg | (((parent_idx << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
|
||||
& QCOM_CLK_RCG2_CFG_SRC_SEL_MASK));
|
||||
|
||||
/* Configure dual-edge if needed */
|
||||
if (sc->mnd_width != 0 && f->n != 0 && (f->m != f->n))
|
||||
reg |= QCOM_CLK_RCG2_CFG_MODE_DUAL_EDGE;
|
||||
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CFG_OFFSET(sc), reg);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_rcg2_init(struct clknode *clk, device_t dev)
|
||||
{
|
||||
struct qcom_clk_rcg2_sc *sc;
|
||||
uint32_t reg;
|
||||
uint32_t idx;
|
||||
bool enabled;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
/*
|
||||
* Read the mux setting to set the right parent.
|
||||
* Whilst here, read the config to get whether we're enabled
|
||||
* or not.
|
||||
*/
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
/* check if rcg2 root clock is enabled */
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
|
||||
if (reg & QCOM_CLK_RCG2_CMD_ROOT_OFF)
|
||||
enabled = false;
|
||||
else
|
||||
enabled = true;
|
||||
/* mux settings */
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
|
||||
idx = (reg & QCOM_CLK_RCG2_CFG_SRC_SEL_MASK)
|
||||
>> QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT;
|
||||
DPRINTF(clknode_get_device(sc->clknode), "%s: mux index %u\n",
|
||||
__func__, idx);
|
||||
clknode_init_parent_idx(clk, idx);
|
||||
|
||||
/*
|
||||
* If we could be sure our parent clocks existed here in the tree,
|
||||
* we could calculate our current frequency by fetching the parent
|
||||
* frequency and then do our divider math. Unfortunately that
|
||||
* currently isn't the case.
|
||||
*/
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_rcg2_set_gate(struct clknode *clk, bool enable)
|
||||
{
|
||||
|
||||
/*
|
||||
* For now this isn't supported; there's some support for
|
||||
* "shared" rcg2 nodes in the Qualcomm/upstream Linux trees but
|
||||
* it's not currently needed for the supported platforms.
|
||||
*/
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Program the parent index.
|
||||
*
|
||||
* This doesn't do the update. It also must be called with the device
|
||||
* lock held.
|
||||
*/
|
||||
static void
|
||||
qcom_clk_rcg2_set_parent_index_locked(struct qcom_clk_rcg2_sc *sc,
|
||||
uint32_t index)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
|
||||
reg = reg & ~QCOM_CLK_RCG2_CFG_SRC_SEL_MASK;
|
||||
reg = reg | (((index << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
|
||||
& QCOM_CLK_RCG2_CFG_SRC_SEL_MASK));
|
||||
CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
|
||||
QCOM_CLK_RCG2_CFG_OFFSET(sc),
|
||||
reg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set frequency
|
||||
*
|
||||
* fin - the parent frequency, if exists
|
||||
* fout - starts as the requested frequency, ends with the configured
|
||||
* or dry-run frequency
|
||||
* Flags - CLK_SET_DRYRUN, CLK_SET_ROUND_UP, CLK_SET_ROUND_DOWN
|
||||
* retval - 0, ERANGE
|
||||
*/
|
||||
static int
|
||||
qcom_clk_rcg2_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
|
||||
int flags, int *stop)
|
||||
{
|
||||
struct qcom_clk_rcg2_sc *sc;
|
||||
const struct qcom_clk_freq_tbl *f;
|
||||
const char **parent_names;
|
||||
uint64_t p_freq, p_clk_freq;
|
||||
int parent_cnt;
|
||||
struct clknode *p_clk;
|
||||
int i;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
/*
|
||||
* Find a suitable frequency in the frequency table.
|
||||
*
|
||||
* TODO: should pay attention to ROUND_UP / ROUND_DOWN and add
|
||||
* a freqtbl method to handle both accordingly.
|
||||
*/
|
||||
f = qcom_clk_freq_tbl_lookup(sc->freq_tbl, *fout);
|
||||
if (f == NULL) {
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: no suitable freqtbl entry found for freq %llu\n",
|
||||
__func__,
|
||||
*fout);
|
||||
return (ERANGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the parent index for the given parent clock.
|
||||
* Abort if we can't actually find it.
|
||||
*
|
||||
* XXX TODO: this should be a clk API call!
|
||||
*/
|
||||
parent_cnt = clknode_get_parents_num(clk);
|
||||
parent_names = clknode_get_parent_names(clk);
|
||||
for (i = 0; i < parent_cnt; i++) {
|
||||
if (parent_names[i] == NULL)
|
||||
continue;
|
||||
if (strcmp(parent_names[i], f->parent) == 0)
|
||||
break;
|
||||
}
|
||||
if (i >= parent_cnt) {
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: couldn't find suitable parent?\n",
|
||||
__func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we aren't setting the parent clock, then we need
|
||||
* to just program the new parent clock in and update.
|
||||
* (or for DRYRUN just skip that and return the new
|
||||
* frequency.)
|
||||
*/
|
||||
if ((sc->flags & QCOM_CLK_RCG2_FLAGS_SET_RATE_PARENT) == 0) {
|
||||
if (flags & CLK_SET_DRYRUN) {
|
||||
*fout = f->freq;
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (sc->safe_pre_parent_idx > -1) {
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: setting to safe parent idx %d\n",
|
||||
__func__,
|
||||
sc->safe_pre_parent_idx);
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
qcom_clk_rcg2_set_parent_index_locked(sc,
|
||||
sc->safe_pre_parent_idx);
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: safe parent: updating config\n", __func__);
|
||||
if (! qcom_clk_rcg2_update_config_locked(sc)) {
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: error updating config\n",
|
||||
__func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: safe parent: done\n", __func__);
|
||||
clknode_set_parent_by_idx(sc->clknode,
|
||||
sc->safe_pre_parent_idx);
|
||||
}
|
||||
/* Program parent index, then schedule update */
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
qcom_clk_rcg2_set_parent_index_locked(sc, i);
|
||||
if (! qcom_clk_rcg2_update_config_locked(sc)) {
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: couldn't program in parent idx %u!\n",
|
||||
__func__, i);
|
||||
return (ENXIO);
|
||||
}
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
clknode_set_parent_by_idx(sc->clknode, i);
|
||||
*fout = f->freq;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we /are/ setting the parent clock, then we need
|
||||
* to determine what frequency we need the parent to
|
||||
* be, and then reconfigure the parent to the new
|
||||
* frequency, and then change our parent.
|
||||
*
|
||||
* (Again, if we're doing DRYRUN, just skip that
|
||||
* and return the new frequency.)
|
||||
*/
|
||||
p_clk = clknode_find_by_name(f->parent);
|
||||
if (p_clk == NULL) {
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: couldn't find parent clk (%s)\n",
|
||||
__func__, f->parent);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate required frequency from said parent clock to
|
||||
* meet the needs of our target clock.
|
||||
*/
|
||||
p_freq = qcom_clk_rcg2_calc_input_freq(f->freq, f->m, f->n,
|
||||
f->pre_div);
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: request %llu, parent %s freq %llu, parent freq %llu\n",
|
||||
__func__,
|
||||
*fout,
|
||||
f->parent,
|
||||
f->freq,
|
||||
p_freq);
|
||||
|
||||
/*
|
||||
* To ensure glitch-free operation on some clocks, set it to
|
||||
* a safe parent before programming our divisor and the parent
|
||||
* clock configuration. Then once it's done, flip the parent
|
||||
* to the new parent.
|
||||
*
|
||||
* If we're doing a dry-run then we don't need to re-parent the
|
||||
* clock just yet!
|
||||
*/
|
||||
if (((flags & CLK_SET_DRYRUN) == 0) &&
|
||||
(sc->safe_pre_parent_idx > -1)) {
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: setting to safe parent idx %d\n",
|
||||
__func__,
|
||||
sc->safe_pre_parent_idx);
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
qcom_clk_rcg2_set_parent_index_locked(sc,
|
||||
sc->safe_pre_parent_idx);
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: safe parent: updating config\n", __func__);
|
||||
if (! qcom_clk_rcg2_update_config_locked(sc)) {
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: error updating config\n",
|
||||
__func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: safe parent: done\n", __func__);
|
||||
clknode_set_parent_by_idx(sc->clknode,
|
||||
sc->safe_pre_parent_idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the parent frequency before we change our mux and divisor
|
||||
* configuration.
|
||||
*/
|
||||
if (clknode_get_freq(p_clk, &p_clk_freq) != 0) {
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: couldn't get freq for parent clock %s\n",
|
||||
__func__,
|
||||
f->parent);
|
||||
return (ENXIO);
|
||||
}
|
||||
if (p_clk_freq != p_freq) {
|
||||
uint64_t n_freq;
|
||||
int rv;
|
||||
|
||||
/*
|
||||
* If we're doing a dryrun then call test_freq() not set_freq().
|
||||
* That way we get the frequency back that we would be set to.
|
||||
*
|
||||
* If we're not doing a dry run then set the frequency, then
|
||||
* call get_freq to get what it was set to.
|
||||
*/
|
||||
if (flags & CLK_SET_DRYRUN) {
|
||||
n_freq = p_freq;
|
||||
rv = clknode_test_freq(p_clk, n_freq, flags, 0,
|
||||
&p_freq);
|
||||
} else {
|
||||
rv = clknode_set_freq(p_clk, p_freq, flags, 0);
|
||||
}
|
||||
|
||||
if (rv != 0) {
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: couldn't set parent clock %s frequency to "
|
||||
"%llu\n",
|
||||
__func__,
|
||||
f->parent,
|
||||
p_freq);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
/* Frequency was set, fetch what it was set to */
|
||||
if ((flags & CLK_SET_DRYRUN) == 0) {
|
||||
rv = clknode_get_freq(p_clk, &p_freq);
|
||||
if (rv != 0) {
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: couldn't get parent frequency",
|
||||
__func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: requsted freq=%llu, target freq=%llu,"
|
||||
" parent choice=%s, parent_freq=%llu\n",
|
||||
__func__,
|
||||
*fout,
|
||||
f->freq,
|
||||
f->parent,
|
||||
p_freq);
|
||||
|
||||
/*
|
||||
* Set the parent node, the parent programming and the divisor
|
||||
* config. Because they're done together, we don't go via
|
||||
* a mux method on this node.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Program the divisor and parent.
|
||||
*/
|
||||
if ((flags & CLK_SET_DRYRUN) == 0) {
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
qcom_clk_rcg2_set_config_locked(sc, f, i);
|
||||
if (! qcom_clk_rcg2_update_config_locked(sc)) {
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
device_printf(clknode_get_device(sc->clknode),
|
||||
"%s: couldn't program in divisor, help!\n",
|
||||
__func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
clknode_set_parent_by_idx(sc->clknode, i);
|
||||
}
|
||||
|
||||
/*
|
||||
* p_freq is now the frequency that the parent /is/ set to.
|
||||
* (Or would be set to for a dry run.)
|
||||
*
|
||||
* Calculate what the eventual frequency would be, we'll want
|
||||
* this to return when we're done - and again, if it's a dryrun,
|
||||
* don't set anything up. This doesn't rely on the register
|
||||
* contents.
|
||||
*/
|
||||
*fout = qcom_clk_rcg2_calc_rate(p_freq, (f->n == 0 ? 0 : 1),
|
||||
f->m, f->n, f->pre_div);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static clknode_method_t qcom_clk_rcg2_methods[] = {
|
||||
/* Device interface */
|
||||
CLKNODEMETHOD(clknode_init, qcom_clk_rcg2_init),
|
||||
CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_rcg2_recalc),
|
||||
CLKNODEMETHOD(clknode_set_gate, qcom_clk_rcg2_set_gate),
|
||||
CLKNODEMETHOD(clknode_set_freq, qcom_clk_rcg2_set_freq),
|
||||
CLKNODEMETHOD_END
|
||||
};
|
||||
|
||||
DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_rcg2_class, qcom_clk_rcg2_methods,
|
||||
sizeof(struct qcom_clk_rcg2_sc), clknode_class);
|
||||
|
||||
int
|
||||
qcom_clk_rcg2_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_rcg2_def *clkdef)
|
||||
{
|
||||
struct clknode *clk;
|
||||
struct qcom_clk_rcg2_sc *sc;
|
||||
|
||||
/*
|
||||
* Right now the rcg2 code isn't supporting turning off the clock
|
||||
* or limiting it to the lowest parent clock. But, do set the
|
||||
* flags appropriately.
|
||||
*/
|
||||
if (clkdef->flags & QCOM_CLK_RCG2_FLAGS_CRITICAL)
|
||||
clkdef->clkdef.flags |= CLK_NODE_CANNOT_STOP;
|
||||
|
||||
clk = clknode_create(clkdom, &qcom_clk_rcg2_class, &clkdef->clkdef);
|
||||
if (clk == NULL)
|
||||
return (1);
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
sc->clknode = clk;
|
||||
|
||||
sc->cmd_rcgr = clkdef->cmd_rcgr;
|
||||
sc->hid_width = clkdef->hid_width;
|
||||
sc->mnd_width = clkdef->mnd_width;
|
||||
sc->safe_src_idx = clkdef->safe_src_idx;
|
||||
sc->safe_pre_parent_idx = clkdef->safe_pre_parent_idx;
|
||||
sc->cfg_offset = clkdef->cfg_offset;
|
||||
sc->flags = clkdef->flags;
|
||||
sc->freq_tbl = clkdef->freq_tbl;
|
||||
|
||||
clknode_register(clkdom, clk);
|
||||
|
||||
return (0);
|
||||
}
|
61
sys/dev/qcom_clk/qcom_clk_rcg2.h
Normal file
61
sys/dev/qcom_clk/qcom_clk_rcg2.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_RCG2_H__
|
||||
#define __QCOM_CLK_RCG2_H__
|
||||
|
||||
#include "qcom_clk_freqtbl.h"
|
||||
|
||||
/* Flags */
|
||||
/* Set the rate on the parent clock, not just ours */
|
||||
#define QCOM_CLK_RCG2_FLAGS_SET_RATE_PARENT 0x1
|
||||
/* Must not stop this clock/gate! */
|
||||
#define QCOM_CLK_RCG2_FLAGS_CRITICAL 0x2
|
||||
|
||||
/* prediv to hw mapping */
|
||||
#define QCOM_CLK_FREQTBL_PREDIV_RCG2(prediv) (2*(prediv)-1)
|
||||
|
||||
struct qcom_clk_rcg2_def {
|
||||
struct clknode_init_def clkdef;
|
||||
uint32_t cmd_rcgr; /* rcg2 register start */
|
||||
uint32_t hid_width; /* pre-divisor width */
|
||||
uint32_t mnd_width; /* mn:d divisor width */
|
||||
int32_t safe_src_idx; /* safe parent when disabling a shared
|
||||
* rcg2 */
|
||||
uint32_t cfg_offset; /* cfg offset after cmd_rcgr */
|
||||
int32_t safe_pre_parent_idx; /* safe parent before switching
|
||||
* parent mux */
|
||||
uint32_t flags;
|
||||
const struct qcom_clk_freq_tbl *freq_tbl;
|
||||
};
|
||||
|
||||
extern int qcom_clk_rcg2_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_rcg2_def *clkdef);
|
||||
|
||||
#endif /* __QCOM_CLK_RCG2_H__ */
|
58
sys/dev/qcom_clk/qcom_clk_rcg2_reg.h
Normal file
58
sys/dev/qcom_clk/qcom_clk_rcg2_reg.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_RCG2_REG_H__
|
||||
#define __QCOM_CLK_RCG2_REG_H__
|
||||
|
||||
#define QCOM_CLK_RCG2_CMD_REG 0x0
|
||||
#define QCOM_CLK_RCG2_CMD_UPDATE (1U << 0)
|
||||
#define QCOM_CLK_RCG2_CMD_ROOT_EN (1U << 1)
|
||||
#define QCOM_CLK_RCG2_CMD_DIRTY_CFG (1U << 4)
|
||||
#define QCOM_CLK_RCG2_CMD_DIRTY_N (1U << 5)
|
||||
#define QCOM_CLK_RCG2_CMD_DIRTY_M (1U << 6)
|
||||
#define QCOM_CLK_RCG2_CMD_DIRTY_D (1U << 7)
|
||||
#define QCOM_CLK_RCG2_CMD_ROOT_OFF (1U << 31)
|
||||
|
||||
#define QCOM_CLK_RCG2_CFG_REG 0x4
|
||||
#define QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT 0
|
||||
#define QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT 8
|
||||
#define QCOM_CLK_RCG2_CFG_SRC_SEL_MASK \
|
||||
(0x7 << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
|
||||
#define QCOM_CLK_RCG2_CFG_MODE_SHIFT 12
|
||||
#define QCOM_CLK_RCG2_CFG_MODE_MASK \
|
||||
(0x3 << QCOM_CLK_RCG2_CFG_MODE_SHIFT)
|
||||
#define QCOM_CLK_RCG2_CFG_MODE_DUAL_EDGE \
|
||||
(0x2 << QCOM_CLK_RCG2_CFG_MODE_SHIFT)
|
||||
#define QCOM_CLK_RCG2_CFG_HW_CLK_CTRL_MASK (1U << 20)
|
||||
|
||||
#define QCOM_CLK_RCG2_M_REG 0x8
|
||||
#define QCOM_CLK_RCG2_N_REG 0xc
|
||||
#define QCOM_CLK_RCG2_D_REG 0x10
|
||||
|
||||
#endif /* __QCOM_CLK_RCG2_REG_H__ */
|
153
sys/dev/qcom_clk/qcom_clk_ro_div.c
Normal file
153
sys/dev/qcom_clk/qcom_clk_ro_div.c
Normal file
@ -0,0 +1,153 @@
|
||||
/*-
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/rman.h>
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/extres/clk/clk.h>
|
||||
#include <dev/extres/clk/clk_div.h>
|
||||
#include <dev/extres/clk/clk_fixed.h>
|
||||
#include <dev/extres/clk/clk_mux.h>
|
||||
|
||||
#include "qcom_clk_ro_div.h"
|
||||
|
||||
#include "clkdev_if.h"
|
||||
|
||||
#if 0
|
||||
#define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
|
||||
#else
|
||||
#define DPRINTF(dev, msg...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This is a read-only divisor table node.
|
||||
* It represents some divisor that is setup by the boot environment
|
||||
* and we don't have any need for the driver to go and fiddle with.
|
||||
*
|
||||
* It likely should just live in the extres/clk code.
|
||||
*/
|
||||
|
||||
struct qcom_clk_ro_div_sc {
|
||||
struct clknode *clknode;
|
||||
uint32_t offset;
|
||||
uint32_t shift;
|
||||
uint32_t width;
|
||||
struct qcom_clk_ro_div_tbl *div_tbl;
|
||||
};
|
||||
|
||||
static int
|
||||
qcom_clk_ro_div_recalc(struct clknode *clk, uint64_t *freq)
|
||||
{
|
||||
struct qcom_clk_ro_div_sc *sc;
|
||||
uint32_t reg, idx, div = 1;
|
||||
int i;
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
|
||||
if (freq == NULL || *freq == 0) {
|
||||
printf("%s: called; NULL or 0 frequency\n", __func__);
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
|
||||
CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->offset, ®);
|
||||
CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
|
||||
|
||||
idx = (reg >> sc->shift) & ((1U << sc->width) - 1);
|
||||
|
||||
for (i = 0; (sc->div_tbl[i].div != 0); i++) {
|
||||
if (idx == sc->div_tbl[i].val) {
|
||||
div = sc->div_tbl[i].div;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DPRINTF(clknode_get_device(sc->clknode),
|
||||
"%s: freq=%llu, idx=%u, div=%u, out_freq=%llu\n",
|
||||
__func__,
|
||||
*freq,
|
||||
idx,
|
||||
div,
|
||||
*freq / div);
|
||||
|
||||
*freq = *freq / div;
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
qcom_clk_ro_div_init(struct clknode *clk, device_t dev)
|
||||
{
|
||||
|
||||
/*
|
||||
* There's only a single parent here for this divisor,
|
||||
* so just set it to 0; the caller doesn't need to supply it.
|
||||
*/
|
||||
clknode_init_parent_idx(clk, 0);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static clknode_method_t qcom_clk_ro_div_methods[] = {
|
||||
/* Device interface */
|
||||
CLKNODEMETHOD(clknode_init, qcom_clk_ro_div_init),
|
||||
CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_ro_div_recalc),
|
||||
CLKNODEMETHOD_END
|
||||
};
|
||||
|
||||
DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_ro_div_class,
|
||||
qcom_clk_ro_div_methods, sizeof(struct qcom_clk_ro_div_sc),
|
||||
clknode_class);
|
||||
|
||||
int
|
||||
qcom_clk_ro_div_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_ro_div_def *clkdef)
|
||||
{
|
||||
struct clknode *clk;
|
||||
struct qcom_clk_ro_div_sc *sc;
|
||||
|
||||
clk = clknode_create(clkdom, &qcom_clk_ro_div_class,
|
||||
&clkdef->clkdef);
|
||||
if (clk == NULL)
|
||||
return (1);
|
||||
|
||||
sc = clknode_get_softc(clk);
|
||||
sc->clknode = clk;
|
||||
sc->offset = clkdef->offset;
|
||||
sc->shift = clkdef->shift;
|
||||
sc->width = clkdef->width;
|
||||
sc->div_tbl = clkdef->div_tbl;
|
||||
|
||||
clknode_register(clkdom, clk);
|
||||
|
||||
return (0);
|
||||
}
|
49
sys/dev/qcom_clk/qcom_clk_ro_div.h
Normal file
49
sys/dev/qcom_clk/qcom_clk_ro_div.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
*
|
||||
* Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef __QCOM_CLK_RO_DIV_H__
|
||||
#define __QCOM_CLK_RO_DIV_H__
|
||||
|
||||
struct qcom_clk_ro_div_tbl {
|
||||
uint32_t val;
|
||||
uint32_t div;
|
||||
};
|
||||
|
||||
struct qcom_clk_ro_div_def {
|
||||
struct clknode_init_def clkdef;
|
||||
uint32_t offset; /* register offset */
|
||||
uint32_t shift; /* field shift */
|
||||
uint32_t width; /* field width */
|
||||
struct qcom_clk_ro_div_tbl *div_tbl;
|
||||
};
|
||||
|
||||
extern int qcom_clk_ro_div_register(struct clkdom *clkdom,
|
||||
struct qcom_clk_ro_div_def *clkdef);
|
||||
|
||||
#endif /* __QCOM_CLK_RO_DIV_H__ */
|
Loading…
Reference in New Issue
Block a user