Blame view

drivers/pwm/pwm-tiecap.c 7.4 KB
74ba9207e   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
2
3
4
  /*
   * ECAP PWM driver
   *
216a094de   Alexander A. Klimov   pwm: Replace HTTP...
5
   * Copyright (C) 2012 Texas Instruments, Inc. - https://www.ti.com/
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
6
7
8
9
10
11
12
13
14
   */
  
  #include <linux/module.h>
  #include <linux/platform_device.h>
  #include <linux/io.h>
  #include <linux/err.h>
  #include <linux/clk.h>
  #include <linux/pm_runtime.h>
  #include <linux/pwm.h>
333b08ee8   Philip, Avinash   pwm: tiecap: Add ...
15
  #include <linux/of_device.h>
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
16
17
18
19
20
21
  /* ECAP registers and bits definitions */
  #define CAP1			0x08
  #define CAP2			0x0C
  #define CAP3			0x10
  #define CAP4			0x14
  #define ECCTL2			0x2A
454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
22
  #define ECCTL2_APWM_POL_LOW	BIT(10)
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
23
24
25
  #define ECCTL2_APWM_MODE	BIT(9)
  #define ECCTL2_SYNC_SEL_DISA	(BIT(7) | BIT(6))
  #define ECCTL2_TSCTR_FREERUN	BIT(4)
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
26
  struct ecap_context {
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
27
28
29
  	u32 cap3;
  	u32 cap4;
  	u16 ecctl2;
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
30
  };
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
31
  struct ecap_pwm_chip {
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
32
33
34
  	struct pwm_chip chip;
  	unsigned int clk_rate;
  	void __iomem *mmio_base;
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
35
  	struct ecap_context ctx;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  };
  
  static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)
  {
  	return container_of(chip, struct ecap_pwm_chip, chip);
  }
  
  /*
   * period_ns = 10^9 * period_cycles / PWM_CLK_RATE
   * duty_ns   = 10^9 * duty_cycles / PWM_CLK_RATE
   */
  static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
  		int duty_ns, int period_ns)
  {
  	struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
51
  	u32 period_cycles, duty_cycles;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
52
  	unsigned long long c;
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
53
  	u16 value;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
54

c2d476a98   Thierry Reding   pwm: Check for ne...
55
  	if (period_ns > NSEC_PER_SEC)
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
56
57
58
59
60
  		return -ERANGE;
  
  	c = pc->clk_rate;
  	c = c * period_ns;
  	do_div(c, NSEC_PER_SEC);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
61
  	period_cycles = (u32)c;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
62
63
64
65
66
67
68
69
  
  	if (period_cycles < 1) {
  		period_cycles = 1;
  		duty_cycles = 1;
  	} else {
  		c = pc->clk_rate;
  		c = c * duty_ns;
  		do_div(c, NSEC_PER_SEC);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
70
  		duty_cycles = (u32)c;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
71
72
73
  	}
  
  	pm_runtime_get_sync(pc->chip.dev);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
74
  	value = readw(pc->mmio_base + ECCTL2);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
75
76
  
  	/* Configure APWM mode & disable sync option */
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
77
  	value |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
78

53c7972d6   Thierry Reding   pwm: tiecap: Misc...
79
  	writew(value, pc->mmio_base + ECCTL2);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
80

5c31252c4   Boris Brezillon   pwm: Add the pwm_...
81
  	if (!pwm_is_enabled(pwm)) {
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
82
83
84
85
86
87
88
89
90
91
92
93
  		/* Update active registers if not running */
  		writel(duty_cycles, pc->mmio_base + CAP2);
  		writel(period_cycles, pc->mmio_base + CAP1);
  	} else {
  		/*
  		 * Update shadow registers to configure period and
  		 * compare values. This helps current PWM period to
  		 * complete on reconfiguring
  		 */
  		writel(duty_cycles, pc->mmio_base + CAP4);
  		writel(period_cycles, pc->mmio_base + CAP3);
  	}
5c31252c4   Boris Brezillon   pwm: Add the pwm_...
94
  	if (!pwm_is_enabled(pwm)) {
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
95
  		value = readw(pc->mmio_base + ECCTL2);
c06fad9d2   Philip, Avinash   pwm: pwm-tiecap: ...
96
  		/* Disable APWM mode to put APWM output Low */
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
97
98
  		value &= ~ECCTL2_APWM_MODE;
  		writew(value, pc->mmio_base + ECCTL2);
c06fad9d2   Philip, Avinash   pwm: pwm-tiecap: ...
99
  	}
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
100
  	pm_runtime_put_sync(pc->chip.dev);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
101

8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
102
103
  	return 0;
  }
454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
104
  static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
105
  				 enum pwm_polarity polarity)
454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
106
107
  {
  	struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
108
  	u16 value;
454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
109
110
  
  	pm_runtime_get_sync(pc->chip.dev);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
111
112
  
  	value = readw(pc->mmio_base + ECCTL2);
454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
113
114
  	if (polarity == PWM_POLARITY_INVERSED)
  		/* Duty cycle defines LOW period of PWM */
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
115
  		value |= ECCTL2_APWM_POL_LOW;
454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
116
117
  	else
  		/* Duty cycle defines HIGH period of PWM */
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
118
119
120
  		value &= ~ECCTL2_APWM_POL_LOW;
  
  	writew(value, pc->mmio_base + ECCTL2);
454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
121

454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
122
  	pm_runtime_put_sync(pc->chip.dev);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
123

454870a44   Philip, Avinash   pwm: pwm-tiecap: ...
124
125
  	return 0;
  }
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
126
127
128
  static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
  {
  	struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
129
  	u16 value;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
130
131
132
133
134
135
136
137
  
  	/* Leave clock enabled on enabling PWM */
  	pm_runtime_get_sync(pc->chip.dev);
  
  	/*
  	 * Enable 'Free run Time stamp counter mode' to start counter
  	 * and  'APWM mode' to enable APWM output
  	 */
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
138
139
140
  	value = readw(pc->mmio_base + ECCTL2);
  	value |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;
  	writew(value, pc->mmio_base + ECCTL2);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
141
142
143
144
145
146
  	return 0;
  }
  
  static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
  {
  	struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
147
  	u16 value;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
148
149
150
151
152
  
  	/*
  	 * Disable 'Free run Time stamp counter mode' to stop counter
  	 * and 'APWM mode' to put APWM output to low
  	 */
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
153
154
155
  	value = readw(pc->mmio_base + ECCTL2);
  	value &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);
  	writew(value, pc->mmio_base + ECCTL2);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
156
157
158
159
160
161
162
  
  	/* Disable clock on PWM disable */
  	pm_runtime_put_sync(pc->chip.dev);
  }
  
  static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
  {
5c31252c4   Boris Brezillon   pwm: Add the pwm_...
163
  	if (pwm_is_enabled(pwm)) {
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
164
165
166
167
168
169
170
  		dev_warn(chip->dev, "Removing PWM device without disabling
  ");
  		pm_runtime_put_sync(chip->dev);
  	}
  }
  
  static const struct pwm_ops ecap_pwm_ops = {
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
171
172
173
174
175
176
  	.free = ecap_pwm_free,
  	.config = ecap_pwm_config,
  	.set_polarity = ecap_pwm_set_polarity,
  	.enable = ecap_pwm_enable,
  	.disable = ecap_pwm_disable,
  	.owner = THIS_MODULE,
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
177
  };
333b08ee8   Philip, Avinash   pwm: tiecap: Add ...
178
  static const struct of_device_id ecap_of_match[] = {
ae5200d22   Cooper Jr., Franklin   pwm: pwm-ti*: Get...
179
  	{ .compatible	= "ti,am3352-ecap" },
333b08ee8   Philip, Avinash   pwm: tiecap: Add ...
180
181
182
183
  	{ .compatible	= "ti,am33xx-ecap" },
  	{},
  };
  MODULE_DEVICE_TABLE(of, ecap_of_match);
3e9fe83d2   Bill Pemberton   pwm: remove use o...
184
  static int ecap_pwm_probe(struct platform_device *pdev)
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
185
  {
ae5200d22   Cooper Jr., Franklin   pwm: pwm-ti*: Get...
186
  	struct device_node *np = pdev->dev.of_node;
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
187
  	struct ecap_pwm_chip *pc;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
188
189
  	struct resource *r;
  	struct clk *clk;
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
190
  	int ret;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
191
192
  
  	pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
c10d50631   Jingoo Han   pwm: pwm-tiecap: ...
193
  	if (!pc)
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
194
  		return -ENOMEM;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
195
196
197
  
  	clk = devm_clk_get(&pdev->dev, "fck");
  	if (IS_ERR(clk)) {
ae5200d22   Cooper Jr., Franklin   pwm: pwm-ti*: Get...
198
199
200
201
202
203
204
205
  		if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
  			dev_warn(&pdev->dev, "Binding is obsolete.
  ");
  			clk = devm_clk_get(pdev->dev.parent, "fck");
  		}
  	}
  
  	if (IS_ERR(clk)) {
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
  		dev_err(&pdev->dev, "failed to get clock
  ");
  		return PTR_ERR(clk);
  	}
  
  	pc->clk_rate = clk_get_rate(clk);
  	if (!pc->clk_rate) {
  		dev_err(&pdev->dev, "failed to get clock rate
  ");
  		return -EINVAL;
  	}
  
  	pc->chip.dev = &pdev->dev;
  	pc->chip.ops = &ecap_pwm_ops;
333b08ee8   Philip, Avinash   pwm: tiecap: Add ...
220
221
  	pc->chip.of_xlate = of_pwm_xlate_with_flags;
  	pc->chip.of_pwm_n_cells = 3;
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
222
223
224
225
  	pc->chip.base = -1;
  	pc->chip.npwm = 1;
  
  	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
6d4294d16   Thierry Reding   pwm: Convert to d...
226
227
228
  	pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
  	if (IS_ERR(pc->mmio_base))
  		return PTR_ERR(pc->mmio_base);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
229
230
231
232
233
234
235
  
  	ret = pwmchip_add(&pc->chip);
  	if (ret < 0) {
  		dev_err(&pdev->dev, "pwmchip_add() failed: %d
  ", ret);
  		return ret;
  	}
23f373e6f   Thierry Reding   pwm: tiecap: Set ...
236
  	platform_set_drvdata(pdev, pc);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
237
  	pm_runtime_enable(&pdev->dev);
333b08ee8   Philip, Avinash   pwm: tiecap: Add ...
238

8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
239
240
  	return 0;
  }
77f37917a   Bill Pemberton   pwm: remove use o...
241
  static int ecap_pwm_remove(struct platform_device *pdev)
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
242
243
  {
  	struct ecap_pwm_chip *pc = platform_get_drvdata(pdev);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
244
  	pm_runtime_disable(&pdev->dev);
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
245

8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
246
247
  	return pwmchip_remove(&pc->chip);
  }
3943a650f   Jingoo Han   pwm: tiecap: add ...
248
  #ifdef CONFIG_PM_SLEEP
a38c98985   Axel Lin   pwm: tiecap: Stat...
249
  static void ecap_pwm_save_context(struct ecap_pwm_chip *pc)
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
250
251
252
253
254
255
256
  {
  	pm_runtime_get_sync(pc->chip.dev);
  	pc->ctx.ecctl2 = readw(pc->mmio_base + ECCTL2);
  	pc->ctx.cap4 = readl(pc->mmio_base + CAP4);
  	pc->ctx.cap3 = readl(pc->mmio_base + CAP3);
  	pm_runtime_put_sync(pc->chip.dev);
  }
a38c98985   Axel Lin   pwm: tiecap: Stat...
257
  static void ecap_pwm_restore_context(struct ecap_pwm_chip *pc)
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
258
259
260
261
262
263
264
265
266
267
268
269
270
271
  {
  	writel(pc->ctx.cap3, pc->mmio_base + CAP3);
  	writel(pc->ctx.cap4, pc->mmio_base + CAP4);
  	writew(pc->ctx.ecctl2, pc->mmio_base + ECCTL2);
  }
  
  static int ecap_pwm_suspend(struct device *dev)
  {
  	struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
  	struct pwm_device *pwm = pc->chip.pwms;
  
  	ecap_pwm_save_context(pc);
  
  	/* Disable explicitly if PWM is running */
5c31252c4   Boris Brezillon   pwm: Add the pwm_...
272
  	if (pwm_is_enabled(pwm))
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
273
274
275
276
277
278
279
280
281
282
283
  		pm_runtime_put_sync(dev);
  
  	return 0;
  }
  
  static int ecap_pwm_resume(struct device *dev)
  {
  	struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
  	struct pwm_device *pwm = pc->chip.pwms;
  
  	/* Enable explicitly if PWM was running */
5c31252c4   Boris Brezillon   pwm: Add the pwm_...
284
  	if (pwm_is_enabled(pwm))
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
285
286
287
288
289
  		pm_runtime_get_sync(dev);
  
  	ecap_pwm_restore_context(pc);
  	return 0;
  }
b78f5fc92   Jingoo Han   pwm: pwm-tiecap: ...
290
  #endif
0d75c203e   Philip Avinash   pwm: pwm-tiecap: ...
291
292
  
  static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume);
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
293
294
  static struct platform_driver ecap_pwm_driver = {
  	.driver = {
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
295
  		.name = "ecap",
333b08ee8   Philip, Avinash   pwm: tiecap: Add ...
296
  		.of_match_table = ecap_of_match,
53c7972d6   Thierry Reding   pwm: tiecap: Misc...
297
  		.pm = &ecap_pwm_pm_ops,
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
298
299
  	},
  	.probe = ecap_pwm_probe,
fd1091125   Bill Pemberton   pwm: remove use o...
300
  	.remove = ecap_pwm_remove,
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
301
  };
8e0cb05b3   Philip, Avinash   pwm: pwm-tiecap: ...
302
303
304
305
306
  module_platform_driver(ecap_pwm_driver);
  
  MODULE_DESCRIPTION("ECAP PWM driver");
  MODULE_AUTHOR("Texas Instruments");
  MODULE_LICENSE("GPL");