forked from Minki/linux
59e85335dd
The audio divider needs a specific clock divider driver. With am mpll parent clock, which is able to provide a fairly precise rate, the generic divider tends to select low value of the divider. In such case the quality of the clock is very poor. For the same final rate, maximizing the audio clock divider value and selecting the corresponding mpll rate gives better results. This is what this driver aims to acheive. So far, so good. Cc: Hendrik v. Raven <hendrik@consetetur.de> Acked-by: Michael Turquette <mturquette@baylibre.com> Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
156 lines
3.8 KiB
C
156 lines
3.8 KiB
C
/*
|
|
* Copyright (c) 2015 Endless Mobile, Inc.
|
|
* Author: Carlo Caione <carlo@endlessm.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef __CLKC_H
|
|
#define __CLKC_H
|
|
|
|
#define PMASK(width) GENMASK(width - 1, 0)
|
|
#define SETPMASK(width, shift) GENMASK(shift + width - 1, shift)
|
|
#define CLRPMASK(width, shift) (~SETPMASK(width, shift))
|
|
|
|
#define PARM_GET(width, shift, reg) \
|
|
(((reg) & SETPMASK(width, shift)) >> (shift))
|
|
#define PARM_SET(width, shift, reg, val) \
|
|
(((reg) & CLRPMASK(width, shift)) | ((val) << (shift)))
|
|
|
|
#define MESON_PARM_APPLICABLE(p) (!!((p)->width))
|
|
|
|
struct parm {
|
|
u16 reg_off;
|
|
u8 shift;
|
|
u8 width;
|
|
};
|
|
|
|
struct pll_rate_table {
|
|
unsigned long rate;
|
|
u16 m;
|
|
u16 n;
|
|
u16 od;
|
|
u16 od2;
|
|
u16 frac;
|
|
};
|
|
|
|
#define PLL_RATE(_r, _m, _n, _od) \
|
|
{ \
|
|
.rate = (_r), \
|
|
.m = (_m), \
|
|
.n = (_n), \
|
|
.od = (_od), \
|
|
} \
|
|
|
|
#define PLL_FRAC_RATE(_r, _m, _n, _od, _od2, _frac) \
|
|
{ \
|
|
.rate = (_r), \
|
|
.m = (_m), \
|
|
.n = (_n), \
|
|
.od = (_od), \
|
|
.od2 = (_od2), \
|
|
.frac = (_frac), \
|
|
} \
|
|
|
|
struct pll_params_table {
|
|
unsigned int reg_off;
|
|
unsigned int value;
|
|
};
|
|
|
|
#define PLL_PARAM(_reg, _val) \
|
|
{ \
|
|
.reg_off = (_reg), \
|
|
.value = (_val), \
|
|
}
|
|
|
|
struct pll_setup_params {
|
|
struct pll_params_table *params_table;
|
|
unsigned int params_count;
|
|
/* Workaround for GP0, do not reset before configuring */
|
|
bool no_init_reset;
|
|
/* Workaround for GP0, unreset right before checking for lock */
|
|
bool clear_reset_for_lock;
|
|
/* Workaround for GXL GP0, reset in the lock checking loop */
|
|
bool reset_lock_loop;
|
|
};
|
|
|
|
struct meson_clk_pll {
|
|
struct clk_hw hw;
|
|
void __iomem *base;
|
|
struct parm m;
|
|
struct parm n;
|
|
struct parm frac;
|
|
struct parm od;
|
|
struct parm od2;
|
|
const struct pll_setup_params params;
|
|
const struct pll_rate_table *rate_table;
|
|
unsigned int rate_count;
|
|
spinlock_t *lock;
|
|
};
|
|
|
|
#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)
|
|
|
|
struct meson_clk_cpu {
|
|
struct clk_hw hw;
|
|
void __iomem *base;
|
|
u16 reg_off;
|
|
struct notifier_block clk_nb;
|
|
const struct clk_div_table *div_table;
|
|
};
|
|
|
|
int meson_clk_cpu_notifier_cb(struct notifier_block *nb, unsigned long event,
|
|
void *data);
|
|
|
|
struct meson_clk_mpll {
|
|
struct clk_hw hw;
|
|
void __iomem *base;
|
|
struct parm sdm;
|
|
struct parm sdm_en;
|
|
struct parm n2;
|
|
struct parm en;
|
|
spinlock_t *lock;
|
|
};
|
|
|
|
struct meson_clk_audio_divider {
|
|
struct clk_hw hw;
|
|
void __iomem *base;
|
|
struct parm div;
|
|
u8 flags;
|
|
spinlock_t *lock;
|
|
};
|
|
|
|
#define MESON_GATE(_name, _reg, _bit) \
|
|
struct clk_gate _name = { \
|
|
.reg = (void __iomem *) _reg, \
|
|
.bit_idx = (_bit), \
|
|
.lock = &clk_lock, \
|
|
.hw.init = &(struct clk_init_data) { \
|
|
.name = #_name, \
|
|
.ops = &clk_gate_ops, \
|
|
.parent_names = (const char *[]){ "clk81" }, \
|
|
.num_parents = 1, \
|
|
.flags = (CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED), \
|
|
}, \
|
|
};
|
|
|
|
/* clk_ops */
|
|
extern const struct clk_ops meson_clk_pll_ro_ops;
|
|
extern const struct clk_ops meson_clk_pll_ops;
|
|
extern const struct clk_ops meson_clk_cpu_ops;
|
|
extern const struct clk_ops meson_clk_mpll_ro_ops;
|
|
extern const struct clk_ops meson_clk_mpll_ops;
|
|
extern const struct clk_ops meson_clk_audio_divider_ro_ops;
|
|
extern const struct clk_ops meson_clk_audio_divider_ops;
|
|
|
|
#endif /* __CLKC_H */
|