diff options
author | Neil Armstrong <narmstrong@baylibre.com> | 2018-11-06 09:57:34 -0500 |
---|---|---|
committer | Neil Armstrong <narmstrong@baylibre.com> | 2018-11-23 09:11:56 -0500 |
commit | 72dbb8c94d0d8a44d1a006fd487e755e2de48dec (patch) | |
tree | 7e3bb995ee0532c09ac00feb9527b7890cad0f02 | |
parent | 8e1dd17c8b0e3f8c66ed2a3f88a440d36135e589 (diff) |
clk: meson: Add vid_pll divider driver
Add support the VID_PLL fully programmable divider used right after the
HDMI PLL clock source. It is used to achieve complex fractional division
with a programmble bitfield.
Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
Acked-by: Jerome Brunet <jbrunet@baylibre.com>
Link: http://lkml.kernel.org/r/1541516257-16157-2-git-send-email-narmstrong@baylibre.com
-rw-r--r-- | drivers/clk/meson/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/meson/clkc.h | 6 | ||||
-rw-r--r-- | drivers/clk/meson/vid-pll-div.c | 91 |
3 files changed, 98 insertions, 1 deletions
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index 72ec8c40d848..0234767f6cfc 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile | |||
@@ -2,7 +2,7 @@ | |||
2 | # Makefile for Meson specific clk | 2 | # Makefile for Meson specific clk |
3 | # | 3 | # |
4 | 4 | ||
5 | obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-mpll.o clk-phase.o | 5 | obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-mpll.o clk-phase.o vid-pll-div.o |
6 | obj-$(CONFIG_COMMON_CLK_AMLOGIC_AUDIO) += clk-triphase.o sclk-div.o | 6 | obj-$(CONFIG_COMMON_CLK_AMLOGIC_AUDIO) += clk-triphase.o sclk-div.o |
7 | obj-$(CONFIG_COMMON_CLK_MESON_AO) += meson-aoclk.o | 7 | obj-$(CONFIG_COMMON_CLK_MESON_AO) += meson-aoclk.o |
8 | obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o | 8 | obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o |
diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h index 6b96d55c047d..91666055c75a 100644 --- a/drivers/clk/meson/clkc.h +++ b/drivers/clk/meson/clkc.h | |||
@@ -90,6 +90,11 @@ struct meson_clk_phase_data { | |||
90 | int meson_clk_degrees_from_val(unsigned int val, unsigned int width); | 90 | int meson_clk_degrees_from_val(unsigned int val, unsigned int width); |
91 | unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width); | 91 | unsigned int meson_clk_degrees_to_val(int degrees, unsigned int width); |
92 | 92 | ||
93 | struct meson_vid_pll_div_data { | ||
94 | struct parm val; | ||
95 | struct parm sel; | ||
96 | }; | ||
97 | |||
93 | #define MESON_GATE(_name, _reg, _bit) \ | 98 | #define MESON_GATE(_name, _reg, _bit) \ |
94 | struct clk_regmap _name = { \ | 99 | struct clk_regmap _name = { \ |
95 | .data = &(struct clk_regmap_gate_data){ \ | 100 | .data = &(struct clk_regmap_gate_data){ \ |
@@ -112,5 +117,6 @@ extern const struct clk_ops meson_clk_cpu_ops; | |||
112 | extern const struct clk_ops meson_clk_mpll_ro_ops; | 117 | extern const struct clk_ops meson_clk_mpll_ro_ops; |
113 | extern const struct clk_ops meson_clk_mpll_ops; | 118 | extern const struct clk_ops meson_clk_mpll_ops; |
114 | extern const struct clk_ops meson_clk_phase_ops; | 119 | extern const struct clk_ops meson_clk_phase_ops; |
120 | extern const struct clk_ops meson_vid_pll_div_ro_ops; | ||
115 | 121 | ||
116 | #endif /* __CLKC_H */ | 122 | #endif /* __CLKC_H */ |
diff --git a/drivers/clk/meson/vid-pll-div.c b/drivers/clk/meson/vid-pll-div.c new file mode 100644 index 000000000000..b3370ea7beac --- /dev/null +++ b/drivers/clk/meson/vid-pll-div.c | |||
@@ -0,0 +1,91 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright (c) 2018 BayLibre, SAS. | ||
4 | * Author: Neil Armstrong <narmstrong@baylibre.com> | ||
5 | */ | ||
6 | |||
7 | #include <linux/clk-provider.h> | ||
8 | #include "clkc.h" | ||
9 | |||
10 | static inline struct meson_vid_pll_div_data * | ||
11 | meson_vid_pll_div_data(struct clk_regmap *clk) | ||
12 | { | ||
13 | return (struct meson_vid_pll_div_data *)clk->data; | ||
14 | } | ||
15 | |||
16 | /* | ||
17 | * This vid_pll divided is a fully programmable fractionnal divider to | ||
18 | * achieve complex video clock rates. | ||
19 | * | ||
20 | * Here are provided the commonly used fraction values provided by Amlogic. | ||
21 | */ | ||
22 | |||
23 | struct vid_pll_div { | ||
24 | unsigned int shift_val; | ||
25 | unsigned int shift_sel; | ||
26 | unsigned int divider; | ||
27 | unsigned int multiplier; | ||
28 | }; | ||
29 | |||
30 | #define VID_PLL_DIV(_val, _sel, _ft, _fb) \ | ||
31 | { \ | ||
32 | .shift_val = (_val), \ | ||
33 | .shift_sel = (_sel), \ | ||
34 | .divider = (_ft), \ | ||
35 | .multiplier = (_fb), \ | ||
36 | } | ||
37 | |||
38 | static const struct vid_pll_div vid_pll_div_table[] = { | ||
39 | VID_PLL_DIV(0x0aaa, 0, 2, 1), /* 2/1 => /2 */ | ||
40 | VID_PLL_DIV(0x5294, 2, 5, 2), /* 5/2 => /2.5 */ | ||
41 | VID_PLL_DIV(0x0db6, 0, 3, 1), /* 3/1 => /3 */ | ||
42 | VID_PLL_DIV(0x36cc, 1, 7, 2), /* 7/2 => /3.5 */ | ||
43 | VID_PLL_DIV(0x6666, 2, 15, 4), /* 15/4 => /3.75 */ | ||
44 | VID_PLL_DIV(0x0ccc, 0, 4, 1), /* 4/1 => /4 */ | ||
45 | VID_PLL_DIV(0x739c, 2, 5, 1), /* 5/1 => /5 */ | ||
46 | VID_PLL_DIV(0x0e38, 0, 6, 1), /* 6/1 => /6 */ | ||
47 | VID_PLL_DIV(0x0000, 3, 25, 4), /* 25/4 => /6.25 */ | ||
48 | VID_PLL_DIV(0x3c78, 1, 7, 1), /* 7/1 => /7 */ | ||
49 | VID_PLL_DIV(0x78f0, 2, 15, 2), /* 15/2 => /7.5 */ | ||
50 | VID_PLL_DIV(0x0fc0, 0, 12, 1), /* 12/1 => /12 */ | ||
51 | VID_PLL_DIV(0x3f80, 1, 14, 1), /* 14/1 => /14 */ | ||
52 | VID_PLL_DIV(0x7f80, 2, 15, 1), /* 15/1 => /15 */ | ||
53 | }; | ||
54 | |||
55 | #define to_meson_vid_pll_div(_hw) \ | ||
56 | container_of(_hw, struct meson_vid_pll_div, hw) | ||
57 | |||
58 | const struct vid_pll_div *_get_table_val(unsigned int shift_val, | ||
59 | unsigned int shift_sel) | ||
60 | { | ||
61 | int i; | ||
62 | |||
63 | for (i = 0 ; i < ARRAY_SIZE(vid_pll_div_table) ; ++i) { | ||
64 | if (vid_pll_div_table[i].shift_val == shift_val && | ||
65 | vid_pll_div_table[i].shift_sel == shift_sel) | ||
66 | return &vid_pll_div_table[i]; | ||
67 | } | ||
68 | |||
69 | return NULL; | ||
70 | } | ||
71 | |||
72 | static unsigned long meson_vid_pll_div_recalc_rate(struct clk_hw *hw, | ||
73 | unsigned long parent_rate) | ||
74 | { | ||
75 | struct clk_regmap *clk = to_clk_regmap(hw); | ||
76 | struct meson_vid_pll_div_data *pll_div = meson_vid_pll_div_data(clk); | ||
77 | const struct vid_pll_div *div; | ||
78 | |||
79 | div = _get_table_val(meson_parm_read(clk->map, &pll_div->val), | ||
80 | meson_parm_read(clk->map, &pll_div->sel)); | ||
81 | if (!div || !div->divider) { | ||
82 | pr_info("%s: Invalid config value for vid_pll_div\n", __func__); | ||
83 | return parent_rate; | ||
84 | } | ||
85 | |||
86 | return DIV_ROUND_UP_ULL(parent_rate * div->multiplier, div->divider); | ||
87 | } | ||
88 | |||
89 | const struct clk_ops meson_vid_pll_div_ro_ops = { | ||
90 | .recalc_rate = meson_vid_pll_div_recalc_rate, | ||
91 | }; | ||