]>
Commit | Line | Data |
---|---|---|
a537fa4d SS |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. | |
4 | * | |
5 | * Author: Sam Shih <[email protected]> | |
6 | */ | |
7 | ||
a537fa4d SS |
8 | #include <clk.h> |
9 | #include <dm.h> | |
10 | #include <pwm.h> | |
11 | #include <div64.h> | |
12 | #include <linux/bitops.h> | |
13 | #include <linux/io.h> | |
13248d66 | 14 | #include <linux/time.h> |
a537fa4d SS |
15 | |
16 | /* PWM registers and bits definitions */ | |
17 | #define PWMCON 0x00 | |
18 | #define PWMHDUR 0x04 | |
19 | #define PWMLDUR 0x08 | |
20 | #define PWMGDUR 0x0c | |
21 | #define PWMWAVENUM 0x28 | |
22 | #define PWMDWIDTH 0x2c | |
23 | #define PWM45DWIDTH_FIXUP 0x30 | |
24 | #define PWMTHRES 0x30 | |
25 | #define PWM45THRES_FIXUP 0x34 | |
26 | ||
27 | #define PWM_CLK_DIV_MAX 7 | |
28 | #define MAX_PWM_NUM 8 | |
29 | ||
1ac47958 WG |
30 | enum mtk_pwm_reg_ver { |
31 | PWM_REG_V1, | |
32 | PWM_REG_V2, | |
33 | }; | |
34 | ||
35 | static const unsigned int mtk_pwm_reg_offset_v1[] = { | |
a537fa4d SS |
36 | 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220 |
37 | }; | |
38 | ||
1ac47958 WG |
39 | static const unsigned int mtk_pwm_reg_offset_v2[] = { |
40 | 0x0080, 0x00c0, 0x0100, 0x0140, 0x0180, 0x01c0, 0x0200, 0x0240 | |
41 | }; | |
42 | ||
a537fa4d SS |
43 | struct mtk_pwm_soc { |
44 | unsigned int num_pwms; | |
45 | bool pwm45_fixup; | |
1ac47958 | 46 | enum mtk_pwm_reg_ver reg_ver; |
a537fa4d SS |
47 | }; |
48 | ||
49 | struct mtk_pwm_priv { | |
50 | void __iomem *base; | |
51 | struct clk top_clk; | |
52 | struct clk main_clk; | |
53 | struct clk pwm_clks[MAX_PWM_NUM]; | |
54 | const struct mtk_pwm_soc *soc; | |
55 | }; | |
56 | ||
57 | static void mtk_pwm_w32(struct udevice *dev, uint channel, uint reg, uint val) | |
58 | { | |
59 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
1ac47958 WG |
60 | u32 offset; |
61 | ||
62 | switch (priv->soc->reg_ver) { | |
63 | case PWM_REG_V2: | |
64 | offset = mtk_pwm_reg_offset_v2[channel]; | |
65 | break; | |
66 | ||
67 | default: | |
68 | offset = mtk_pwm_reg_offset_v1[channel]; | |
69 | } | |
a537fa4d SS |
70 | |
71 | writel(val, priv->base + offset + reg); | |
72 | } | |
73 | ||
74 | static int mtk_pwm_set_config(struct udevice *dev, uint channel, | |
75 | uint period_ns, uint duty_ns) | |
76 | { | |
77 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
78 | u32 clkdiv = 0, clksel = 0, cnt_period, cnt_duty, | |
79 | reg_width = PWMDWIDTH, reg_thres = PWMTHRES; | |
80 | u64 resolution; | |
81 | int ret = 0; | |
82 | ||
83 | clk_enable(&priv->top_clk); | |
84 | clk_enable(&priv->main_clk); | |
85 | /* Using resolution in picosecond gets accuracy higher */ | |
86 | resolution = (u64)NSEC_PER_SEC * 1000; | |
87 | do_div(resolution, clk_get_rate(&priv->pwm_clks[channel])); | |
88 | cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution); | |
89 | while (cnt_period > 8191) { | |
90 | resolution *= 2; | |
91 | clkdiv++; | |
92 | cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, | |
93 | resolution); | |
94 | if (clkdiv > PWM_CLK_DIV_MAX && clksel == 0) { | |
95 | clksel = 1; | |
96 | clkdiv = 0; | |
97 | resolution = (u64)NSEC_PER_SEC * 1000 * 1625; | |
98 | do_div(resolution, | |
99 | clk_get_rate(&priv->pwm_clks[channel])); | |
100 | cnt_period = DIV_ROUND_CLOSEST_ULL( | |
101 | (u64)period_ns * 1000, resolution); | |
102 | clk_enable(&priv->pwm_clks[channel]); | |
103 | } | |
104 | } | |
105 | if (clkdiv > PWM_CLK_DIV_MAX && clksel == 1) { | |
106 | printf("pwm period %u not supported\n", period_ns); | |
107 | return -EINVAL; | |
108 | } | |
109 | if (priv->soc->pwm45_fixup && channel > 2) { | |
110 | /* | |
111 | * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES | |
112 | * from the other PWMs on MT7623. | |
113 | */ | |
114 | reg_width = PWM45DWIDTH_FIXUP; | |
115 | reg_thres = PWM45THRES_FIXUP; | |
116 | } | |
117 | cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution); | |
118 | if (clksel == 1) | |
119 | mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | BIT(3) | clkdiv); | |
120 | else | |
121 | mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | clkdiv); | |
122 | mtk_pwm_w32(dev, channel, reg_width, cnt_period); | |
123 | mtk_pwm_w32(dev, channel, reg_thres, cnt_duty); | |
124 | ||
125 | return ret; | |
126 | }; | |
127 | ||
128 | static int mtk_pwm_set_enable(struct udevice *dev, uint channel, bool enable) | |
129 | { | |
130 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
131 | u32 val = 0; | |
132 | ||
133 | val = readl(priv->base); | |
134 | if (enable) | |
135 | val |= BIT(channel); | |
136 | else | |
137 | val &= ~BIT(channel); | |
138 | writel(val, priv->base); | |
139 | ||
140 | return 0; | |
141 | }; | |
142 | ||
143 | static int mtk_pwm_probe(struct udevice *dev) | |
144 | { | |
145 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
146 | int ret = 0; | |
147 | int i; | |
148 | ||
149 | priv->soc = (struct mtk_pwm_soc *)dev_get_driver_data(dev); | |
8613c8d8 | 150 | priv->base = dev_read_addr_ptr(dev); |
a537fa4d SS |
151 | if (!priv->base) |
152 | return -EINVAL; | |
153 | ret = clk_get_by_name(dev, "top", &priv->top_clk); | |
154 | if (ret < 0) | |
155 | return ret; | |
156 | ret = clk_get_by_name(dev, "main", &priv->main_clk); | |
157 | if (ret < 0) | |
158 | return ret; | |
159 | for (i = 0; i < priv->soc->num_pwms; i++) { | |
160 | char name[8]; | |
161 | ||
162 | snprintf(name, sizeof(name), "pwm%d", i + 1); | |
163 | ret = clk_get_by_name(dev, name, &priv->pwm_clks[i]); | |
164 | if (ret < 0) | |
165 | return ret; | |
166 | } | |
167 | ||
168 | return ret; | |
169 | } | |
170 | ||
171 | static const struct pwm_ops mtk_pwm_ops = { | |
172 | .set_config = mtk_pwm_set_config, | |
173 | .set_enable = mtk_pwm_set_enable, | |
174 | }; | |
175 | ||
176 | static const struct mtk_pwm_soc mt7622_data = { | |
177 | .num_pwms = 6, | |
178 | .pwm45_fixup = false, | |
1ac47958 | 179 | .reg_ver = PWM_REG_V1, |
a537fa4d SS |
180 | }; |
181 | ||
182 | static const struct mtk_pwm_soc mt7623_data = { | |
183 | .num_pwms = 5, | |
184 | .pwm45_fixup = true, | |
1ac47958 | 185 | .reg_ver = PWM_REG_V1, |
a537fa4d SS |
186 | }; |
187 | ||
188 | static const struct mtk_pwm_soc mt7629_data = { | |
189 | .num_pwms = 1, | |
190 | .pwm45_fixup = false, | |
1ac47958 WG |
191 | .reg_ver = PWM_REG_V1, |
192 | }; | |
193 | ||
194 | static const struct mtk_pwm_soc mt7981_data = { | |
7071ba26 | 195 | .num_pwms = 3, |
1ac47958 WG |
196 | .pwm45_fixup = false, |
197 | .reg_ver = PWM_REG_V2, | |
a537fa4d SS |
198 | }; |
199 | ||
c986dd9f WG |
200 | static const struct mtk_pwm_soc mt7986_data = { |
201 | .num_pwms = 2, | |
202 | .pwm45_fixup = false, | |
1ac47958 | 203 | .reg_ver = PWM_REG_V1, |
c986dd9f WG |
204 | }; |
205 | ||
fec8ee6a WG |
206 | static const struct mtk_pwm_soc mt7988_data = { |
207 | .num_pwms = 8, | |
208 | .pwm45_fixup = false, | |
209 | .reg_ver = PWM_REG_V2, | |
210 | }; | |
211 | ||
a537fa4d SS |
212 | static const struct udevice_id mtk_pwm_ids[] = { |
213 | { .compatible = "mediatek,mt7622-pwm", .data = (ulong)&mt7622_data }, | |
214 | { .compatible = "mediatek,mt7623-pwm", .data = (ulong)&mt7623_data }, | |
215 | { .compatible = "mediatek,mt7629-pwm", .data = (ulong)&mt7629_data }, | |
1ac47958 | 216 | { .compatible = "mediatek,mt7981-pwm", .data = (ulong)&mt7981_data }, |
c986dd9f | 217 | { .compatible = "mediatek,mt7986-pwm", .data = (ulong)&mt7986_data }, |
fec8ee6a | 218 | { .compatible = "mediatek,mt7988-pwm", .data = (ulong)&mt7988_data }, |
a537fa4d SS |
219 | { } |
220 | }; | |
221 | ||
222 | U_BOOT_DRIVER(mtk_pwm) = { | |
223 | .name = "mtk_pwm", | |
224 | .id = UCLASS_PWM, | |
225 | .of_match = mtk_pwm_ids, | |
226 | .ops = &mtk_pwm_ops, | |
227 | .probe = mtk_pwm_probe, | |
41575d8e | 228 | .priv_auto = sizeof(struct mtk_pwm_priv), |
a537fa4d | 229 | }; |