Blame view

drivers/pwm/pwm-mxs.c 4.69 KB
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  /*
   * Copyright 2012 Freescale Semiconductor, Inc.
   *
   * The code contained herein is licensed under the GNU General Public
   * License. You may obtain a copy of the GNU General Public License
   * Version 2 or later at the following locations:
   *
   * http://www.opensource.org/licenses/gpl-license.html
   * http://www.gnu.org/copyleft/gpl.html
   */
  
  #include <linux/clk.h>
  #include <linux/err.h>
  #include <linux/io.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/of.h>
  #include <linux/of_address.h>
  #include <linux/platform_device.h>
  #include <linux/pwm.h>
  #include <linux/slab.h>
01bf32e94   Shawn Guo   pwm: pwm-mxs: use...
22
  #include <linux/stmp_device.h>
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  
  #define SET	0x4
  #define CLR	0x8
  #define TOG	0xc
  
  #define PWM_CTRL		0x0
  #define PWM_ACTIVE0		0x10
  #define PWM_PERIOD0		0x20
  #define  PERIOD_PERIOD(p)	((p) & 0xffff)
  #define  PERIOD_PERIOD_MAX	0x10000
  #define  PERIOD_ACTIVE_HIGH	(3 << 16)
  #define  PERIOD_INACTIVE_LOW	(2 << 18)
  #define  PERIOD_CDIV(div)	(((div) & 0x7) << 20)
  #define  PERIOD_CDIV_MAX	8
24ccea1ce   Gaetan Hug   pwm: mxs: Fix per...
37
38
39
  static const unsigned int cdiv[PERIOD_CDIV_MAX] = {
  	1, 2, 4, 8, 16, 64, 256, 1024
  };
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
40
41
  struct mxs_pwm_chip {
  	struct pwm_chip chip;
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  	struct clk *clk;
  	void __iomem *base;
  };
  
  #define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip)
  
  static int mxs_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
  			  int duty_ns, int period_ns)
  {
  	struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
  	int ret, div = 0;
  	unsigned int period_cycles, duty_cycles;
  	unsigned long rate;
  	unsigned long long c;
  
  	rate = clk_get_rate(mxs->clk);
  	while (1) {
24ccea1ce   Gaetan Hug   pwm: mxs: Fix per...
59
  		c = rate / cdiv[div];
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
60
61
62
63
64
  		c = c * period_ns;
  		do_div(c, 1000000000);
  		if (c < PERIOD_PERIOD_MAX)
  			break;
  		div++;
24ccea1ce   Gaetan Hug   pwm: mxs: Fix per...
65
  		if (div >= PERIOD_CDIV_MAX)
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
66
67
68
69
70
71
72
73
74
75
76
77
  			return -EINVAL;
  	}
  
  	period_cycles = c;
  	c *= duty_ns;
  	do_div(c, period_ns);
  	duty_cycles = c;
  
  	/*
  	 * If the PWM channel is disabled, make sure to turn on the clock
  	 * before writing the register. Otherwise, keep it enabled.
  	 */
5c31252c4   Boris Brezillon   pwm: Add the pwm_...
78
  	if (!pwm_is_enabled(pwm)) {
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
79
80
81
82
83
84
85
86
87
88
89
90
91
92
  		ret = clk_prepare_enable(mxs->clk);
  		if (ret)
  			return ret;
  	}
  
  	writel(duty_cycles << 16,
  			mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20);
  	writel(PERIOD_PERIOD(period_cycles) | PERIOD_ACTIVE_HIGH |
  	       PERIOD_INACTIVE_LOW | PERIOD_CDIV(div),
  			mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20);
  
  	/*
  	 * If the PWM is not enabled, turn the clock off again to save power.
  	 */
5c31252c4   Boris Brezillon   pwm: Add the pwm_...
93
  	if (!pwm_is_enabled(pwm))
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
  		clk_disable_unprepare(mxs->clk);
  
  	return 0;
  }
  
  static int mxs_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
  {
  	struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
  	int ret;
  
  	ret = clk_prepare_enable(mxs->clk);
  	if (ret)
  		return ret;
  
  	writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET);
  
  	return 0;
  }
  
  static void mxs_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
  {
  	struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
  
  	writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR);
  
  	clk_disable_unprepare(mxs->clk);
  }
  
  static const struct pwm_ops mxs_pwm_ops = {
  	.config = mxs_pwm_config,
  	.enable = mxs_pwm_enable,
  	.disable = mxs_pwm_disable,
  	.owner = THIS_MODULE,
  };
  
  static int mxs_pwm_probe(struct platform_device *pdev)
  {
  	struct device_node *np = pdev->dev.of_node;
  	struct mxs_pwm_chip *mxs;
22d260bd8   Shawn Guo   pwm: pwm-mxs: use...
133
  	struct resource *res;
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
134
135
136
137
138
  	int ret;
  
  	mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL);
  	if (!mxs)
  		return -ENOMEM;
22d260bd8   Shawn Guo   pwm: pwm-mxs: use...
139
  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
6d4294d16   Thierry Reding   pwm: Convert to d...
140
141
142
  	mxs->base = devm_ioremap_resource(&pdev->dev, res);
  	if (IS_ERR(mxs->base))
  		return PTR_ERR(mxs->base);
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
143

22d260bd8   Shawn Guo   pwm: pwm-mxs: use...
144
145
146
  	mxs->clk = devm_clk_get(&pdev->dev, NULL);
  	if (IS_ERR(mxs->clk))
  		return PTR_ERR(mxs->clk);
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
147
148
149
150
  
  	mxs->chip.dev = &pdev->dev;
  	mxs->chip.ops = &mxs_pwm_ops;
  	mxs->chip.base = -1;
8c0216f37   Thierry Reding   pwm: Remove .can_...
151

4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
152
153
154
155
  	ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm);
  	if (ret < 0) {
  		dev_err(&pdev->dev, "failed to get pwm number: %d
  ", ret);
22d260bd8   Shawn Guo   pwm: pwm-mxs: use...
156
  		return ret;
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
157
158
159
160
161
162
  	}
  
  	ret = pwmchip_add(&mxs->chip);
  	if (ret < 0) {
  		dev_err(&pdev->dev, "failed to add pwm chip %d
  ", ret);
22d260bd8   Shawn Guo   pwm: pwm-mxs: use...
163
  		return ret;
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
164
  	}
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
165
  	platform_set_drvdata(pdev, mxs);
cfb9e4c40   Fabio Estevam   pwm: mxs: Check t...
166
167
168
  	ret = stmp_reset_block(mxs->base);
  	if (ret)
  		goto pwm_remove;
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
169
170
  
  	return 0;
cfb9e4c40   Fabio Estevam   pwm: mxs: Check t...
171
172
173
174
  
  pwm_remove:
  	pwmchip_remove(&mxs->chip);
  	return ret;
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
175
  }
77f37917a   Bill Pemberton   pwm: remove use o...
176
  static int mxs_pwm_remove(struct platform_device *pdev)
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
177
178
  {
  	struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev);
457fd768e   Axel Lin   pwm: pwm-mxs: Ret...
179
  	return pwmchip_remove(&mxs->chip);
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
180
  }
f1a8870ae   Thierry Reding   pwm: Constify OF ...
181
  static const struct of_device_id mxs_pwm_dt_ids[] = {
071407eef   Shawn Guo   pwm: pwm-mxs: enc...
182
  	{ .compatible = "fsl,imx23-pwm", },
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
183
184
185
186
187
188
189
  	{ /* sentinel */ }
  };
  MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids);
  
  static struct platform_driver mxs_pwm_driver = {
  	.driver = {
  		.name = "mxs-pwm",
de02cb887   Sachin Kamat   pwm: mxs: Remove ...
190
  		.of_match_table = mxs_pwm_dt_ids,
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
191
192
  	},
  	.probe = mxs_pwm_probe,
fd1091125   Bill Pemberton   pwm: remove use o...
193
  	.remove = mxs_pwm_remove,
4dce82c1e   Shawn Guo   pwm: add pwm-mxs ...
194
195
196
197
198
199
200
  };
  module_platform_driver(mxs_pwm_driver);
  
  MODULE_ALIAS("platform:mxs-pwm");
  MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
  MODULE_DESCRIPTION("Freescale MXS PWM Driver");
  MODULE_LICENSE("GPL v2");