]>
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 | ||
8 | #include <common.h> | |
9 | #include <clk.h> | |
10 | #include <dm.h> | |
11 | #include <pwm.h> | |
12 | #include <div64.h> | |
13 | #include <linux/bitops.h> | |
14 | #include <linux/io.h> | |
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 | ||
30 | #define NSEC_PER_SEC 1000000000L | |
31 | ||
32 | static const unsigned int mtk_pwm_reg_offset[] = { | |
33 | 0x0010, 0x0050, 0x0090, 0x00d0, 0x0110, 0x0150, 0x0190, 0x0220 | |
34 | }; | |
35 | ||
36 | struct mtk_pwm_soc { | |
37 | unsigned int num_pwms; | |
38 | bool pwm45_fixup; | |
39 | }; | |
40 | ||
41 | struct mtk_pwm_priv { | |
42 | void __iomem *base; | |
43 | struct clk top_clk; | |
44 | struct clk main_clk; | |
45 | struct clk pwm_clks[MAX_PWM_NUM]; | |
46 | const struct mtk_pwm_soc *soc; | |
47 | }; | |
48 | ||
49 | static void mtk_pwm_w32(struct udevice *dev, uint channel, uint reg, uint val) | |
50 | { | |
51 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
52 | u32 offset = mtk_pwm_reg_offset[channel]; | |
53 | ||
54 | writel(val, priv->base + offset + reg); | |
55 | } | |
56 | ||
57 | static int mtk_pwm_set_config(struct udevice *dev, uint channel, | |
58 | uint period_ns, uint duty_ns) | |
59 | { | |
60 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
61 | u32 clkdiv = 0, clksel = 0, cnt_period, cnt_duty, | |
62 | reg_width = PWMDWIDTH, reg_thres = PWMTHRES; | |
63 | u64 resolution; | |
64 | int ret = 0; | |
65 | ||
66 | clk_enable(&priv->top_clk); | |
67 | clk_enable(&priv->main_clk); | |
68 | /* Using resolution in picosecond gets accuracy higher */ | |
69 | resolution = (u64)NSEC_PER_SEC * 1000; | |
70 | do_div(resolution, clk_get_rate(&priv->pwm_clks[channel])); | |
71 | cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution); | |
72 | while (cnt_period > 8191) { | |
73 | resolution *= 2; | |
74 | clkdiv++; | |
75 | cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, | |
76 | resolution); | |
77 | if (clkdiv > PWM_CLK_DIV_MAX && clksel == 0) { | |
78 | clksel = 1; | |
79 | clkdiv = 0; | |
80 | resolution = (u64)NSEC_PER_SEC * 1000 * 1625; | |
81 | do_div(resolution, | |
82 | clk_get_rate(&priv->pwm_clks[channel])); | |
83 | cnt_period = DIV_ROUND_CLOSEST_ULL( | |
84 | (u64)period_ns * 1000, resolution); | |
85 | clk_enable(&priv->pwm_clks[channel]); | |
86 | } | |
87 | } | |
88 | if (clkdiv > PWM_CLK_DIV_MAX && clksel == 1) { | |
89 | printf("pwm period %u not supported\n", period_ns); | |
90 | return -EINVAL; | |
91 | } | |
92 | if (priv->soc->pwm45_fixup && channel > 2) { | |
93 | /* | |
94 | * PWM[4,5] has distinct offset for PWMDWIDTH and PWMTHRES | |
95 | * from the other PWMs on MT7623. | |
96 | */ | |
97 | reg_width = PWM45DWIDTH_FIXUP; | |
98 | reg_thres = PWM45THRES_FIXUP; | |
99 | } | |
100 | cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution); | |
101 | if (clksel == 1) | |
102 | mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | BIT(3) | clkdiv); | |
103 | else | |
104 | mtk_pwm_w32(dev, channel, PWMCON, BIT(15) | clkdiv); | |
105 | mtk_pwm_w32(dev, channel, reg_width, cnt_period); | |
106 | mtk_pwm_w32(dev, channel, reg_thres, cnt_duty); | |
107 | ||
108 | return ret; | |
109 | }; | |
110 | ||
111 | static int mtk_pwm_set_enable(struct udevice *dev, uint channel, bool enable) | |
112 | { | |
113 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
114 | u32 val = 0; | |
115 | ||
116 | val = readl(priv->base); | |
117 | if (enable) | |
118 | val |= BIT(channel); | |
119 | else | |
120 | val &= ~BIT(channel); | |
121 | writel(val, priv->base); | |
122 | ||
123 | return 0; | |
124 | }; | |
125 | ||
126 | static int mtk_pwm_probe(struct udevice *dev) | |
127 | { | |
128 | struct mtk_pwm_priv *priv = dev_get_priv(dev); | |
129 | int ret = 0; | |
130 | int i; | |
131 | ||
132 | priv->soc = (struct mtk_pwm_soc *)dev_get_driver_data(dev); | |
8613c8d8 | 133 | priv->base = dev_read_addr_ptr(dev); |
a537fa4d SS |
134 | if (!priv->base) |
135 | return -EINVAL; | |
136 | ret = clk_get_by_name(dev, "top", &priv->top_clk); | |
137 | if (ret < 0) | |
138 | return ret; | |
139 | ret = clk_get_by_name(dev, "main", &priv->main_clk); | |
140 | if (ret < 0) | |
141 | return ret; | |
142 | for (i = 0; i < priv->soc->num_pwms; i++) { | |
143 | char name[8]; | |
144 | ||
145 | snprintf(name, sizeof(name), "pwm%d", i + 1); | |
146 | ret = clk_get_by_name(dev, name, &priv->pwm_clks[i]); | |
147 | if (ret < 0) | |
148 | return ret; | |
149 | } | |
150 | ||
151 | return ret; | |
152 | } | |
153 | ||
154 | static const struct pwm_ops mtk_pwm_ops = { | |
155 | .set_config = mtk_pwm_set_config, | |
156 | .set_enable = mtk_pwm_set_enable, | |
157 | }; | |
158 | ||
159 | static const struct mtk_pwm_soc mt7622_data = { | |
160 | .num_pwms = 6, | |
161 | .pwm45_fixup = false, | |
162 | }; | |
163 | ||
164 | static const struct mtk_pwm_soc mt7623_data = { | |
165 | .num_pwms = 5, | |
166 | .pwm45_fixup = true, | |
167 | }; | |
168 | ||
169 | static const struct mtk_pwm_soc mt7629_data = { | |
170 | .num_pwms = 1, | |
171 | .pwm45_fixup = false, | |
172 | }; | |
173 | ||
174 | static const struct udevice_id mtk_pwm_ids[] = { | |
175 | { .compatible = "mediatek,mt7622-pwm", .data = (ulong)&mt7622_data }, | |
176 | { .compatible = "mediatek,mt7623-pwm", .data = (ulong)&mt7623_data }, | |
177 | { .compatible = "mediatek,mt7629-pwm", .data = (ulong)&mt7629_data }, | |
178 | { } | |
179 | }; | |
180 | ||
181 | U_BOOT_DRIVER(mtk_pwm) = { | |
182 | .name = "mtk_pwm", | |
183 | .id = UCLASS_PWM, | |
184 | .of_match = mtk_pwm_ids, | |
185 | .ops = &mtk_pwm_ops, | |
186 | .probe = mtk_pwm_probe, | |
41575d8e | 187 | .priv_auto = sizeof(struct mtk_pwm_priv), |
a537fa4d | 188 | }; |