1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Marvell Armada 37xx SoC Time Base Generator clocks |
4 | * |
5 | * Copyright (C) 2016 Marvell |
6 | * |
7 | * Gregory CLEMENT <gregory.clement@free-electrons.com> |
8 | */ |
9 | |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/io.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/slab.h> |
17 | |
18 | #define NUM_TBG 4 |
19 | |
20 | #define TBG_CTRL0 0x4 |
21 | #define TBG_CTRL1 0x8 |
22 | #define TBG_CTRL7 0x20 |
23 | #define TBG_CTRL8 0x30 |
24 | |
25 | #define TBG_DIV_MASK 0x1FF |
26 | |
27 | #define TBG_A_REFDIV 0 |
28 | #define TBG_B_REFDIV 16 |
29 | |
30 | #define TBG_A_FBDIV 2 |
31 | #define TBG_B_FBDIV 18 |
32 | |
33 | #define TBG_A_VCODIV_SE 0 |
34 | #define TBG_B_VCODIV_SE 16 |
35 | |
36 | #define TBG_A_VCODIV_DIFF 1 |
37 | #define TBG_B_VCODIV_DIFF 17 |
38 | |
39 | struct tbg_def { |
40 | char *name; |
41 | u32 refdiv_offset; |
42 | u32 fbdiv_offset; |
43 | u32 vcodiv_reg; |
44 | u32 vcodiv_offset; |
45 | }; |
46 | |
47 | static const struct tbg_def tbg[NUM_TBG] = { |
48 | {"TBG-A-P" , TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL8, TBG_A_VCODIV_DIFF}, |
49 | {"TBG-B-P" , TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL8, TBG_B_VCODIV_DIFF}, |
50 | {"TBG-A-S" , TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL1, TBG_A_VCODIV_SE}, |
51 | {"TBG-B-S" , TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL1, TBG_B_VCODIV_SE}, |
52 | }; |
53 | |
54 | static unsigned int tbg_get_mult(void __iomem *reg, const struct tbg_def *ptbg) |
55 | { |
56 | u32 val; |
57 | |
58 | val = readl(addr: reg + TBG_CTRL0); |
59 | |
60 | return ((val >> ptbg->fbdiv_offset) & TBG_DIV_MASK) << 2; |
61 | } |
62 | |
63 | static unsigned int tbg_get_div(void __iomem *reg, const struct tbg_def *ptbg) |
64 | { |
65 | u32 val; |
66 | unsigned int div; |
67 | |
68 | val = readl(addr: reg + TBG_CTRL7); |
69 | |
70 | div = (val >> ptbg->refdiv_offset) & TBG_DIV_MASK; |
71 | if (div == 0) |
72 | div = 1; |
73 | val = readl(addr: reg + ptbg->vcodiv_reg); |
74 | |
75 | div *= 1 << ((val >> ptbg->vcodiv_offset) & TBG_DIV_MASK); |
76 | |
77 | return div; |
78 | } |
79 | |
80 | |
81 | static int armada_3700_tbg_clock_probe(struct platform_device *pdev) |
82 | { |
83 | struct device_node *np = pdev->dev.of_node; |
84 | struct clk_hw_onecell_data *hw_tbg_data; |
85 | struct device *dev = &pdev->dev; |
86 | const char *parent_name; |
87 | struct clk *parent; |
88 | void __iomem *reg; |
89 | int i; |
90 | |
91 | hw_tbg_data = devm_kzalloc(dev: &pdev->dev, |
92 | struct_size(hw_tbg_data, hws, NUM_TBG), |
93 | GFP_KERNEL); |
94 | if (!hw_tbg_data) |
95 | return -ENOMEM; |
96 | hw_tbg_data->num = NUM_TBG; |
97 | platform_set_drvdata(pdev, data: hw_tbg_data); |
98 | |
99 | parent = clk_get(dev, NULL); |
100 | if (IS_ERR(ptr: parent)) { |
101 | dev_err(dev, "Could get the clock parent\n" ); |
102 | return -EINVAL; |
103 | } |
104 | parent_name = __clk_get_name(clk: parent); |
105 | clk_put(clk: parent); |
106 | |
107 | reg = devm_platform_ioremap_resource(pdev, index: 0); |
108 | if (IS_ERR(ptr: reg)) |
109 | return PTR_ERR(ptr: reg); |
110 | |
111 | for (i = 0; i < NUM_TBG; i++) { |
112 | const char *name; |
113 | unsigned int mult, div; |
114 | |
115 | name = tbg[i].name; |
116 | mult = tbg_get_mult(reg, ptbg: &tbg[i]); |
117 | div = tbg_get_div(reg, ptbg: &tbg[i]); |
118 | hw_tbg_data->hws[i] = clk_hw_register_fixed_factor(NULL, name, |
119 | parent_name, flags: 0, mult, div); |
120 | if (IS_ERR(ptr: hw_tbg_data->hws[i])) |
121 | dev_err(dev, "Can't register TBG clock %s\n" , name); |
122 | } |
123 | |
124 | return of_clk_add_hw_provider(np, get: of_clk_hw_onecell_get, data: hw_tbg_data); |
125 | } |
126 | |
127 | static void armada_3700_tbg_clock_remove(struct platform_device *pdev) |
128 | { |
129 | int i; |
130 | struct clk_hw_onecell_data *hw_tbg_data = platform_get_drvdata(pdev); |
131 | |
132 | of_clk_del_provider(np: pdev->dev.of_node); |
133 | for (i = 0; i < hw_tbg_data->num; i++) |
134 | clk_hw_unregister_fixed_factor(hw: hw_tbg_data->hws[i]); |
135 | } |
136 | |
137 | static const struct of_device_id armada_3700_tbg_clock_of_match[] = { |
138 | { .compatible = "marvell,armada-3700-tbg-clock" , }, |
139 | { } |
140 | }; |
141 | |
142 | static struct platform_driver armada_3700_tbg_clock_driver = { |
143 | .probe = armada_3700_tbg_clock_probe, |
144 | .remove_new = armada_3700_tbg_clock_remove, |
145 | .driver = { |
146 | .name = "marvell-armada-3700-tbg-clock" , |
147 | .of_match_table = armada_3700_tbg_clock_of_match, |
148 | }, |
149 | }; |
150 | |
151 | builtin_platform_driver(armada_3700_tbg_clock_driver); |
152 | |