diff options
Diffstat (limited to 'drivers/pwm/pwm-img.c')
-rw-r--r-- | drivers/pwm/pwm-img.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c new file mode 100644 index 000000000000..476171a768d6 --- /dev/null +++ b/drivers/pwm/pwm-img.c | |||
@@ -0,0 +1,249 @@ | |||
1 | /* | ||
2 | * Imagination Technologies Pulse Width Modulator driver | ||
3 | * | ||
4 | * Copyright (c) 2014-2015, Imagination Technologies | ||
5 | * | ||
6 | * Based on drivers/pwm/pwm-tegra.c, Copyright (c) 2010, NVIDIA Corporation | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License. | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk.h> | ||
14 | #include <linux/err.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/mfd/syscon.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/of.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/pwm.h> | ||
21 | #include <linux/regmap.h> | ||
22 | #include <linux/slab.h> | ||
23 | |||
24 | /* PWM registers */ | ||
25 | #define PWM_CTRL_CFG 0x0000 | ||
26 | #define PWM_CTRL_CFG_NO_SUB_DIV 0 | ||
27 | #define PWM_CTRL_CFG_SUB_DIV0 1 | ||
28 | #define PWM_CTRL_CFG_SUB_DIV1 2 | ||
29 | #define PWM_CTRL_CFG_SUB_DIV0_DIV1 3 | ||
30 | #define PWM_CTRL_CFG_DIV_SHIFT(ch) ((ch) * 2 + 4) | ||
31 | #define PWM_CTRL_CFG_DIV_MASK 0x3 | ||
32 | |||
33 | #define PWM_CH_CFG(ch) (0x4 + (ch) * 4) | ||
34 | #define PWM_CH_CFG_TMBASE_SHIFT 0 | ||
35 | #define PWM_CH_CFG_DUTY_SHIFT 16 | ||
36 | |||
37 | #define PERIP_PWM_PDM_CONTROL 0x0140 | ||
38 | #define PERIP_PWM_PDM_CONTROL_CH_MASK 0x1 | ||
39 | #define PERIP_PWM_PDM_CONTROL_CH_SHIFT(ch) ((ch) * 4) | ||
40 | |||
41 | #define MAX_TMBASE_STEPS 65536 | ||
42 | |||
43 | struct img_pwm_chip { | ||
44 | struct device *dev; | ||
45 | struct pwm_chip chip; | ||
46 | struct clk *pwm_clk; | ||
47 | struct clk *sys_clk; | ||
48 | void __iomem *base; | ||
49 | struct regmap *periph_regs; | ||
50 | }; | ||
51 | |||
52 | static inline struct img_pwm_chip *to_img_pwm_chip(struct pwm_chip *chip) | ||
53 | { | ||
54 | return container_of(chip, struct img_pwm_chip, chip); | ||
55 | } | ||
56 | |||
57 | static inline void img_pwm_writel(struct img_pwm_chip *chip, | ||
58 | u32 reg, u32 val) | ||
59 | { | ||
60 | writel(val, chip->base + reg); | ||
61 | } | ||
62 | |||
63 | static inline u32 img_pwm_readl(struct img_pwm_chip *chip, | ||
64 | u32 reg) | ||
65 | { | ||
66 | return readl(chip->base + reg); | ||
67 | } | ||
68 | |||
69 | static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
70 | int duty_ns, int period_ns) | ||
71 | { | ||
72 | u32 val, div, duty, timebase; | ||
73 | unsigned long mul, output_clk_hz, input_clk_hz; | ||
74 | struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); | ||
75 | |||
76 | input_clk_hz = clk_get_rate(pwm_chip->pwm_clk); | ||
77 | output_clk_hz = DIV_ROUND_UP(NSEC_PER_SEC, period_ns); | ||
78 | |||
79 | mul = DIV_ROUND_UP(input_clk_hz, output_clk_hz); | ||
80 | if (mul <= MAX_TMBASE_STEPS) { | ||
81 | div = PWM_CTRL_CFG_NO_SUB_DIV; | ||
82 | timebase = DIV_ROUND_UP(mul, 1); | ||
83 | } else if (mul <= MAX_TMBASE_STEPS * 8) { | ||
84 | div = PWM_CTRL_CFG_SUB_DIV0; | ||
85 | timebase = DIV_ROUND_UP(mul, 8); | ||
86 | } else if (mul <= MAX_TMBASE_STEPS * 64) { | ||
87 | div = PWM_CTRL_CFG_SUB_DIV1; | ||
88 | timebase = DIV_ROUND_UP(mul, 64); | ||
89 | } else if (mul <= MAX_TMBASE_STEPS * 512) { | ||
90 | div = PWM_CTRL_CFG_SUB_DIV0_DIV1; | ||
91 | timebase = DIV_ROUND_UP(mul, 512); | ||
92 | } else if (mul > MAX_TMBASE_STEPS * 512) { | ||
93 | dev_err(chip->dev, | ||
94 | "failed to configure timebase steps/divider value\n"); | ||
95 | return -EINVAL; | ||
96 | } | ||
97 | |||
98 | duty = DIV_ROUND_UP(timebase * duty_ns, period_ns); | ||
99 | |||
100 | val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||
101 | val &= ~(PWM_CTRL_CFG_DIV_MASK << PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm)); | ||
102 | val |= (div & PWM_CTRL_CFG_DIV_MASK) << | ||
103 | PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm); | ||
104 | img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||
105 | |||
106 | val = (duty << PWM_CH_CFG_DUTY_SHIFT) | | ||
107 | (timebase << PWM_CH_CFG_TMBASE_SHIFT); | ||
108 | img_pwm_writel(pwm_chip, PWM_CH_CFG(pwm->hwpwm), val); | ||
109 | |||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | static int img_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
114 | { | ||
115 | u32 val; | ||
116 | struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); | ||
117 | |||
118 | val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||
119 | val |= BIT(pwm->hwpwm); | ||
120 | img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||
121 | |||
122 | regmap_update_bits(pwm_chip->periph_regs, PERIP_PWM_PDM_CONTROL, | ||
123 | PERIP_PWM_PDM_CONTROL_CH_MASK << | ||
124 | PERIP_PWM_PDM_CONTROL_CH_SHIFT(pwm->hwpwm), 0); | ||
125 | |||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | static void img_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
130 | { | ||
131 | u32 val; | ||
132 | struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); | ||
133 | |||
134 | val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||
135 | val &= ~BIT(pwm->hwpwm); | ||
136 | img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||
137 | } | ||
138 | |||
139 | static const struct pwm_ops img_pwm_ops = { | ||
140 | .config = img_pwm_config, | ||
141 | .enable = img_pwm_enable, | ||
142 | .disable = img_pwm_disable, | ||
143 | .owner = THIS_MODULE, | ||
144 | }; | ||
145 | |||
146 | static int img_pwm_probe(struct platform_device *pdev) | ||
147 | { | ||
148 | int ret; | ||
149 | struct resource *res; | ||
150 | struct img_pwm_chip *pwm; | ||
151 | |||
152 | pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); | ||
153 | if (!pwm) | ||
154 | return -ENOMEM; | ||
155 | |||
156 | pwm->dev = &pdev->dev; | ||
157 | |||
158 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
159 | pwm->base = devm_ioremap_resource(&pdev->dev, res); | ||
160 | if (IS_ERR(pwm->base)) | ||
161 | return PTR_ERR(pwm->base); | ||
162 | |||
163 | pwm->periph_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | ||
164 | "img,cr-periph"); | ||
165 | if (IS_ERR(pwm->periph_regs)) | ||
166 | return PTR_ERR(pwm->periph_regs); | ||
167 | |||
168 | pwm->sys_clk = devm_clk_get(&pdev->dev, "sys"); | ||
169 | if (IS_ERR(pwm->sys_clk)) { | ||
170 | dev_err(&pdev->dev, "failed to get system clock\n"); | ||
171 | return PTR_ERR(pwm->sys_clk); | ||
172 | } | ||
173 | |||
174 | pwm->pwm_clk = devm_clk_get(&pdev->dev, "pwm"); | ||
175 | if (IS_ERR(pwm->pwm_clk)) { | ||
176 | dev_err(&pdev->dev, "failed to get pwm clock\n"); | ||
177 | return PTR_ERR(pwm->pwm_clk); | ||
178 | } | ||
179 | |||
180 | ret = clk_prepare_enable(pwm->sys_clk); | ||
181 | if (ret < 0) { | ||
182 | dev_err(&pdev->dev, "could not prepare or enable sys clock\n"); | ||
183 | return ret; | ||
184 | } | ||
185 | |||
186 | ret = clk_prepare_enable(pwm->pwm_clk); | ||
187 | if (ret < 0) { | ||
188 | dev_err(&pdev->dev, "could not prepare or enable pwm clock\n"); | ||
189 | goto disable_sysclk; | ||
190 | } | ||
191 | |||
192 | pwm->chip.dev = &pdev->dev; | ||
193 | pwm->chip.ops = &img_pwm_ops; | ||
194 | pwm->chip.base = -1; | ||
195 | pwm->chip.npwm = 4; | ||
196 | |||
197 | ret = pwmchip_add(&pwm->chip); | ||
198 | if (ret < 0) { | ||
199 | dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret); | ||
200 | goto disable_pwmclk; | ||
201 | } | ||
202 | |||
203 | platform_set_drvdata(pdev, pwm); | ||
204 | return 0; | ||
205 | |||
206 | disable_pwmclk: | ||
207 | clk_disable_unprepare(pwm->pwm_clk); | ||
208 | disable_sysclk: | ||
209 | clk_disable_unprepare(pwm->sys_clk); | ||
210 | return ret; | ||
211 | } | ||
212 | |||
213 | static int img_pwm_remove(struct platform_device *pdev) | ||
214 | { | ||
215 | struct img_pwm_chip *pwm_chip = platform_get_drvdata(pdev); | ||
216 | u32 val; | ||
217 | unsigned int i; | ||
218 | |||
219 | for (i = 0; i < pwm_chip->chip.npwm; i++) { | ||
220 | val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); | ||
221 | val &= ~BIT(i); | ||
222 | img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); | ||
223 | } | ||
224 | |||
225 | clk_disable_unprepare(pwm_chip->pwm_clk); | ||
226 | clk_disable_unprepare(pwm_chip->sys_clk); | ||
227 | |||
228 | return pwmchip_remove(&pwm_chip->chip); | ||
229 | } | ||
230 | |||
231 | static const struct of_device_id img_pwm_of_match[] = { | ||
232 | { .compatible = "img,pistachio-pwm", }, | ||
233 | { } | ||
234 | }; | ||
235 | MODULE_DEVICE_TABLE(of, img_pwm_of_match); | ||
236 | |||
237 | static struct platform_driver img_pwm_driver = { | ||
238 | .driver = { | ||
239 | .name = "img-pwm", | ||
240 | .of_match_table = img_pwm_of_match, | ||
241 | }, | ||
242 | .probe = img_pwm_probe, | ||
243 | .remove = img_pwm_remove, | ||
244 | }; | ||
245 | module_platform_driver(img_pwm_driver); | ||
246 | |||
247 | MODULE_AUTHOR("Sai Masarapu <Sai.Masarapu@imgtec.com>"); | ||
248 | MODULE_DESCRIPTION("Imagination Technologies PWM DAC driver"); | ||
249 | MODULE_LICENSE("GPL v2"); | ||