diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-06 16:21:16 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-06 16:21:16 -0400 |
commit | 8e73e367f7dc50f1d1bc22a63e5764bb4eea9b48 (patch) | |
tree | 9bf593c1fc7612bcdd64b9ba46e41d340f9e94d3 /drivers/pwm | |
parent | d2f3e9eb7c9e12e89f0ac5f0dbc7a9aed0ea925d (diff) | |
parent | 7323f219533e01cc075ba45a76f3e5b214adb23f (diff) |
Merge tag 'cleanup-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC cleanups from Olof Johansson:
"This branch contains code cleanups, moves and removals for 3.12.
There's a large number of various cleanups, and a nice net removal of
13500 lines of code.
Highlights worth mentioning are:
- A series of patches from Stephen Boyd removing the ARM local timer
API.
- Move of Qualcomm MSM IOMMU code to drivers/iommu.
- Samsung PWM driver cleanups from Tomasz Figa, removing legacy PWM
driver and switching over to the drivers/pwm one.
- Removal of some unusued auto-generated headers for OMAP2+ (PRM/CM).
There's also a move of a header file out of include/linux/i2c/ to
platform_data, where it really belongs. It touches mostly ARM
platform code for include changes so we took it through our tree"
* tag 'cleanup-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (83 commits)
ARM: OMAP2+: Add back the define for AM33XX_RST_GLOBAL_WARM_SW_MASK
gpio: (gpio-pca953x) move header to linux/platform_data/
arm: zynq: hotplug: Remove unreachable code
ARM: SAMSUNG: Remove unnecessary exynos4_default_sdhci*()
tegra: simplify use of devm_ioremap_resource
ARM: SAMSUNG: Remove plat/regs-timer.h header
ARM: SAMSUNG: Remove remaining uses of plat/regs-timer.h header
ARM: SAMSUNG: Remove pwm-clock infrastructure
ARM: SAMSUNG: Remove old PWM timer platform devices
pwm: Remove superseded pwm-samsung-legacy driver
ARM: SAMSUNG: Modify board files to use new PWM platform device
ARM: SAMSUNG: Rework private data handling in dev-backlight
pwm: Add new pwm-samsung driver
ARM: mach-mvebu: remove redundant DT parsing and validation
ARM: msm: Only compile io.c on platforms that use it
iommu/msm: Move mach includes to iommu directory
ARM: msm: Remove devices-iommu.c
ARM: msm: Move mach/board.h contents to common.h
ARM: msm: Migrate msm_timer to CLOCKSOURCE_OF_DECLARE
ARM: msm: Remove TMR and TMR0 static mappings
...
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/pwm-samsung.c | 709 |
1 files changed, 487 insertions, 222 deletions
diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index a0ece50d70bb..fcc8b9adde9f 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c | |||
@@ -1,353 +1,618 @@ | |||
1 | /* drivers/pwm/pwm-samsung.c | 1 | /* |
2 | * | ||
3 | * Copyright (c) 2007 Ben Dooks | 2 | * Copyright (c) 2007 Ben Dooks |
4 | * Copyright (c) 2008 Simtec Electronics | 3 | * Copyright (c) 2008 Simtec Electronics |
5 | * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org> | 4 | * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org> |
5 | * Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com> | ||
6 | * | 6 | * |
7 | * S3C series PWM device core | 7 | * PWM driver for Samsung SoCs |
8 | * | 8 | * |
9 | * This program is free software; you can redistribute it and/or modify | 9 | * This program is free software; you can redistribute it and/or modify |
10 | * it under the terms of the GNU General Public License as published by | 10 | * it under the terms of the GNU General Public License as published by |
11 | * the Free Software Foundation; either version 2 of the License. | 11 | * the Free Software Foundation; either version 2 of the License. |
12 | */ | 12 | */ |
13 | |||
14 | #define pr_fmt(fmt) "pwm-samsung: " fmt | ||
15 | 13 | ||
14 | #include <linux/bitops.h> | ||
15 | #include <linux/clk.h> | ||
16 | #include <linux/export.h> | 16 | #include <linux/export.h> |
17 | #include <linux/kernel.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/err.h> | 17 | #include <linux/err.h> |
21 | #include <linux/clk.h> | ||
22 | #include <linux/io.h> | 18 | #include <linux/io.h> |
19 | #include <linux/kernel.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/platform_device.h> | ||
23 | #include <linux/pwm.h> | 22 | #include <linux/pwm.h> |
23 | #include <linux/slab.h> | ||
24 | #include <linux/spinlock.h> | ||
25 | #include <linux/time.h> | ||
24 | 26 | ||
25 | #include <mach/map.h> | 27 | /* For struct samsung_timer_variant and samsung_pwm_lock. */ |
28 | #include <clocksource/samsung_pwm.h> | ||
26 | 29 | ||
27 | #include <plat/regs-timer.h> | 30 | #define REG_TCFG0 0x00 |
31 | #define REG_TCFG1 0x04 | ||
32 | #define REG_TCON 0x08 | ||
28 | 33 | ||
29 | struct s3c_chip { | 34 | #define REG_TCNTB(chan) (0x0c + ((chan) * 0xc)) |
30 | struct platform_device *pdev; | 35 | #define REG_TCMPB(chan) (0x10 + ((chan) * 0xc)) |
31 | 36 | ||
32 | struct clk *clk_div; | 37 | #define TCFG0_PRESCALER_MASK 0xff |
33 | struct clk *clk; | 38 | #define TCFG0_PRESCALER1_SHIFT 8 |
34 | const char *label; | ||
35 | 39 | ||
36 | unsigned int period_ns; | 40 | #define TCFG1_MUX_MASK 0xf |
37 | unsigned int duty_ns; | 41 | #define TCFG1_SHIFT(chan) (4 * (chan)) |
38 | 42 | ||
39 | unsigned char tcon_base; | 43 | /* |
40 | unsigned char pwm_id; | 44 | * Each channel occupies 4 bits in TCON register, but there is a gap of 4 |
41 | struct pwm_chip chip; | 45 | * bits (one channel) after channel 0, so channels have different numbering |
46 | * when accessing TCON register. See to_tcon_channel() function. | ||
47 | * | ||
48 | * In addition, the location of autoreload bit for channel 4 (TCON channel 5) | ||
49 | * in its set of bits is 2 as opposed to 3 for other channels. | ||
50 | */ | ||
51 | #define TCON_START(chan) BIT(4 * (chan) + 0) | ||
52 | #define TCON_MANUALUPDATE(chan) BIT(4 * (chan) + 1) | ||
53 | #define TCON_INVERT(chan) BIT(4 * (chan) + 2) | ||
54 | #define _TCON_AUTORELOAD(chan) BIT(4 * (chan) + 3) | ||
55 | #define _TCON_AUTORELOAD4(chan) BIT(4 * (chan) + 2) | ||
56 | #define TCON_AUTORELOAD(chan) \ | ||
57 | ((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan)) | ||
58 | |||
59 | /** | ||
60 | * struct samsung_pwm_channel - private data of PWM channel | ||
61 | * @period_ns: current period in nanoseconds programmed to the hardware | ||
62 | * @duty_ns: current duty time in nanoseconds programmed to the hardware | ||
63 | * @tin_ns: time of one timer tick in nanoseconds with current timer rate | ||
64 | */ | ||
65 | struct samsung_pwm_channel { | ||
66 | u32 period_ns; | ||
67 | u32 duty_ns; | ||
68 | u32 tin_ns; | ||
42 | }; | 69 | }; |
43 | 70 | ||
44 | #define to_s3c_chip(chip) container_of(chip, struct s3c_chip, chip) | 71 | /** |
45 | 72 | * struct samsung_pwm_chip - private data of PWM chip | |
46 | #define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) | 73 | * @chip: generic PWM chip |
74 | * @variant: local copy of hardware variant data | ||
75 | * @inverter_mask: inverter status for all channels - one bit per channel | ||
76 | * @base: base address of mapped PWM registers | ||
77 | * @base_clk: base clock used to drive the timers | ||
78 | * @tclk0: external clock 0 (can be ERR_PTR if not present) | ||
79 | * @tclk1: external clock 1 (can be ERR_PTR if not present) | ||
80 | */ | ||
81 | struct samsung_pwm_chip { | ||
82 | struct pwm_chip chip; | ||
83 | struct samsung_pwm_variant variant; | ||
84 | u8 inverter_mask; | ||
85 | |||
86 | void __iomem *base; | ||
87 | struct clk *base_clk; | ||
88 | struct clk *tclk0; | ||
89 | struct clk *tclk1; | ||
90 | }; | ||
47 | 91 | ||
48 | static struct clk *clk_scaler[2]; | 92 | #ifndef CONFIG_CLKSRC_SAMSUNG_PWM |
93 | /* | ||
94 | * PWM block is shared between pwm-samsung and samsung_pwm_timer drivers | ||
95 | * and some registers need access synchronization. If both drivers are | ||
96 | * compiled in, the spinlock is defined in the clocksource driver, | ||
97 | * otherwise following definition is used. | ||
98 | * | ||
99 | * Currently we do not need any more complex synchronization method | ||
100 | * because all the supported SoCs contain only one instance of the PWM | ||
101 | * IP. Should this change, both drivers will need to be modified to | ||
102 | * properly synchronize accesses to particular instances. | ||
103 | */ | ||
104 | static DEFINE_SPINLOCK(samsung_pwm_lock); | ||
105 | #endif | ||
49 | 106 | ||
50 | static inline int pwm_is_tdiv(struct s3c_chip *chip) | 107 | static inline |
108 | struct samsung_pwm_chip *to_samsung_pwm_chip(struct pwm_chip *chip) | ||
51 | { | 109 | { |
52 | return clk_get_parent(chip->clk) == chip->clk_div; | 110 | return container_of(chip, struct samsung_pwm_chip, chip); |
53 | } | 111 | } |
54 | 112 | ||
55 | #define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) | 113 | static inline unsigned int to_tcon_channel(unsigned int channel) |
56 | #define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) | 114 | { |
57 | #define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) | 115 | /* TCON register has a gap of 4 bits (1 channel) after channel 0 */ |
58 | #define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1)) | 116 | return (channel == 0) ? 0 : (channel + 1); |
117 | } | ||
59 | 118 | ||
60 | static int s3c_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | 119 | static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm, |
120 | unsigned int channel, u8 divisor) | ||
61 | { | 121 | { |
62 | struct s3c_chip *s3c = to_s3c_chip(chip); | 122 | u8 shift = TCFG1_SHIFT(channel); |
63 | unsigned long flags; | 123 | unsigned long flags; |
64 | unsigned long tcon; | 124 | u32 reg; |
125 | u8 bits; | ||
65 | 126 | ||
66 | local_irq_save(flags); | 127 | bits = (fls(divisor) - 1) - pwm->variant.div_base; |
67 | 128 | ||
68 | tcon = __raw_readl(S3C2410_TCON); | 129 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
69 | tcon |= pwm_tcon_start(s3c); | ||
70 | __raw_writel(tcon, S3C2410_TCON); | ||
71 | 130 | ||
72 | local_irq_restore(flags); | 131 | reg = readl(pwm->base + REG_TCFG1); |
132 | reg &= ~(TCFG1_MUX_MASK << shift); | ||
133 | reg |= bits << shift; | ||
134 | writel(reg, pwm->base + REG_TCFG1); | ||
73 | 135 | ||
74 | return 0; | 136 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); |
75 | } | 137 | } |
76 | 138 | ||
77 | static void s3c_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | 139 | static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip, unsigned int chan) |
78 | { | 140 | { |
79 | struct s3c_chip *s3c = to_s3c_chip(chip); | 141 | struct samsung_pwm_variant *variant = &chip->variant; |
80 | unsigned long flags; | 142 | u32 reg; |
81 | unsigned long tcon; | 143 | |
144 | reg = readl(chip->base + REG_TCFG1); | ||
145 | reg >>= TCFG1_SHIFT(chan); | ||
146 | reg &= TCFG1_MUX_MASK; | ||
147 | |||
148 | return (BIT(reg) & variant->tclk_mask) == 0; | ||
149 | } | ||
150 | |||
151 | static unsigned long pwm_samsung_get_tin_rate(struct samsung_pwm_chip *chip, | ||
152 | unsigned int chan) | ||
153 | { | ||
154 | unsigned long rate; | ||
155 | u32 reg; | ||
82 | 156 | ||
83 | local_irq_save(flags); | 157 | rate = clk_get_rate(chip->base_clk); |
84 | 158 | ||
85 | tcon = __raw_readl(S3C2410_TCON); | 159 | reg = readl(chip->base + REG_TCFG0); |
86 | tcon &= ~pwm_tcon_start(s3c); | 160 | if (chan >= 2) |
87 | __raw_writel(tcon, S3C2410_TCON); | 161 | reg >>= TCFG0_PRESCALER1_SHIFT; |
162 | reg &= TCFG0_PRESCALER_MASK; | ||
88 | 163 | ||
89 | local_irq_restore(flags); | 164 | return rate / (reg + 1); |
90 | } | 165 | } |
91 | 166 | ||
92 | static unsigned long pwm_calc_tin(struct s3c_chip *s3c, unsigned long freq) | 167 | static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip, |
168 | unsigned int chan, unsigned long freq) | ||
93 | { | 169 | { |
94 | unsigned long tin_parent_rate; | 170 | struct samsung_pwm_variant *variant = &chip->variant; |
95 | unsigned int div; | 171 | unsigned long rate; |
172 | struct clk *clk; | ||
173 | u8 div; | ||
174 | |||
175 | if (!pwm_samsung_is_tdiv(chip, chan)) { | ||
176 | clk = (chan < 2) ? chip->tclk0 : chip->tclk1; | ||
177 | if (!IS_ERR(clk)) { | ||
178 | rate = clk_get_rate(clk); | ||
179 | if (rate) | ||
180 | return rate; | ||
181 | } | ||
182 | |||
183 | dev_warn(chip->chip.dev, | ||
184 | "tclk of PWM %d is inoperational, using tdiv\n", chan); | ||
185 | } | ||
186 | |||
187 | rate = pwm_samsung_get_tin_rate(chip, chan); | ||
188 | dev_dbg(chip->chip.dev, "tin parent at %lu\n", rate); | ||
189 | |||
190 | /* | ||
191 | * Compare minimum PWM frequency that can be achieved with possible | ||
192 | * divider settings and choose the lowest divisor that can generate | ||
193 | * frequencies lower than requested. | ||
194 | */ | ||
195 | for (div = variant->div_base; div < 4; ++div) | ||
196 | if ((rate >> (variant->bits + div)) < freq) | ||
197 | break; | ||
96 | 198 | ||
97 | tin_parent_rate = clk_get_rate(clk_get_parent(s3c->clk_div)); | 199 | pwm_samsung_set_divisor(chip, chan, BIT(div)); |
98 | pwm_dbg(s3c, "tin parent at %lu\n", tin_parent_rate); | ||
99 | 200 | ||
100 | for (div = 2; div <= 16; div *= 2) { | 201 | return rate >> div; |
101 | if ((tin_parent_rate / (div << 16)) < freq) | 202 | } |
102 | return tin_parent_rate / div; | 203 | |
204 | static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
205 | { | ||
206 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
207 | struct samsung_pwm_channel *our_chan; | ||
208 | |||
209 | if (!(our_chip->variant.output_mask & BIT(pwm->hwpwm))) { | ||
210 | dev_warn(chip->dev, | ||
211 | "tried to request PWM channel %d without output\n", | ||
212 | pwm->hwpwm); | ||
213 | return -EINVAL; | ||
103 | } | 214 | } |
104 | 215 | ||
105 | return tin_parent_rate / 16; | 216 | our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL); |
217 | if (!our_chan) | ||
218 | return -ENOMEM; | ||
219 | |||
220 | pwm_set_chip_data(pwm, our_chan); | ||
221 | |||
222 | return 0; | ||
106 | } | 223 | } |
107 | 224 | ||
108 | #define NS_IN_HZ (1000000000UL) | 225 | static void pwm_samsung_free(struct pwm_chip *chip, struct pwm_device *pwm) |
226 | { | ||
227 | pwm_set_chip_data(pwm, NULL); | ||
228 | devm_kfree(chip->dev, pwm_get_chip_data(pwm)); | ||
229 | } | ||
109 | 230 | ||
110 | static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | 231 | static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm) |
111 | int duty_ns, int period_ns) | ||
112 | { | 232 | { |
113 | struct s3c_chip *s3c = to_s3c_chip(chip); | 233 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); |
114 | unsigned long tin_rate; | 234 | unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); |
115 | unsigned long tin_ns; | ||
116 | unsigned long period; | ||
117 | unsigned long flags; | 235 | unsigned long flags; |
118 | unsigned long tcon; | 236 | u32 tcon; |
119 | unsigned long tcnt; | ||
120 | long tcmp; | ||
121 | 237 | ||
122 | /* We currently avoid using 64bit arithmetic by using the | 238 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
123 | * fact that anything faster than 1Hz is easily representable | 239 | |
124 | * by 32bits. */ | 240 | tcon = readl(our_chip->base + REG_TCON); |
241 | |||
242 | tcon &= ~TCON_START(tcon_chan); | ||
243 | tcon |= TCON_MANUALUPDATE(tcon_chan); | ||
244 | writel(tcon, our_chip->base + REG_TCON); | ||
245 | |||
246 | tcon &= ~TCON_MANUALUPDATE(tcon_chan); | ||
247 | tcon |= TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan); | ||
248 | writel(tcon, our_chip->base + REG_TCON); | ||
249 | |||
250 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); | ||
251 | |||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
256 | { | ||
257 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
258 | unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); | ||
259 | unsigned long flags; | ||
260 | u32 tcon; | ||
261 | |||
262 | spin_lock_irqsave(&samsung_pwm_lock, flags); | ||
263 | |||
264 | tcon = readl(our_chip->base + REG_TCON); | ||
265 | tcon &= ~TCON_AUTORELOAD(tcon_chan); | ||
266 | writel(tcon, our_chip->base + REG_TCON); | ||
267 | |||
268 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); | ||
269 | } | ||
125 | 270 | ||
126 | if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) | 271 | static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm, |
272 | int duty_ns, int period_ns) | ||
273 | { | ||
274 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
275 | struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); | ||
276 | u32 tin_ns = chan->tin_ns, tcnt, tcmp; | ||
277 | |||
278 | /* | ||
279 | * We currently avoid using 64bit arithmetic by using the | ||
280 | * fact that anything faster than 1Hz is easily representable | ||
281 | * by 32bits. | ||
282 | */ | ||
283 | if (period_ns > NSEC_PER_SEC) | ||
127 | return -ERANGE; | 284 | return -ERANGE; |
128 | 285 | ||
129 | if (period_ns == s3c->period_ns && | 286 | if (period_ns == chan->period_ns && duty_ns == chan->duty_ns) |
130 | duty_ns == s3c->duty_ns) | ||
131 | return 0; | 287 | return 0; |
132 | 288 | ||
133 | /* The TCMP and TCNT can be read without a lock, they're not | 289 | tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm)); |
134 | * shared between the timers. */ | ||
135 | 290 | ||
136 | tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id)); | 291 | /* We need tick count for calculation, not last tick. */ |
137 | tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id)); | 292 | ++tcnt; |
138 | 293 | ||
139 | period = NS_IN_HZ / period_ns; | 294 | /* Check to see if we are changing the clock rate of the PWM. */ |
295 | if (chan->period_ns != period_ns) { | ||
296 | unsigned long tin_rate; | ||
297 | u32 period; | ||
140 | 298 | ||
141 | pwm_dbg(s3c, "duty_ns=%d, period_ns=%d (%lu)\n", | 299 | period = NSEC_PER_SEC / period_ns; |
142 | duty_ns, period_ns, period); | ||
143 | 300 | ||
144 | /* Check to see if we are changing the clock rate of the PWM */ | 301 | dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n", |
302 | duty_ns, period_ns, period); | ||
145 | 303 | ||
146 | if (s3c->period_ns != period_ns) { | 304 | tin_rate = pwm_samsung_calc_tin(our_chip, pwm->hwpwm, period); |
147 | if (pwm_is_tdiv(s3c)) { | ||
148 | tin_rate = pwm_calc_tin(s3c, period); | ||
149 | clk_set_rate(s3c->clk_div, tin_rate); | ||
150 | } else | ||
151 | tin_rate = clk_get_rate(s3c->clk); | ||
152 | 305 | ||
153 | s3c->period_ns = period_ns; | 306 | dev_dbg(our_chip->chip.dev, "tin_rate=%lu\n", tin_rate); |
154 | 307 | ||
155 | pwm_dbg(s3c, "tin_rate=%lu\n", tin_rate); | 308 | tin_ns = NSEC_PER_SEC / tin_rate; |
156 | |||
157 | tin_ns = NS_IN_HZ / tin_rate; | ||
158 | tcnt = period_ns / tin_ns; | 309 | tcnt = period_ns / tin_ns; |
159 | } else | 310 | } |
160 | tin_ns = NS_IN_HZ / clk_get_rate(s3c->clk); | ||
161 | 311 | ||
162 | /* Note, counters count down */ | 312 | /* Period is too short. */ |
313 | if (tcnt <= 1) | ||
314 | return -ERANGE; | ||
163 | 315 | ||
316 | /* Note that counters count down. */ | ||
164 | tcmp = duty_ns / tin_ns; | 317 | tcmp = duty_ns / tin_ns; |
318 | |||
319 | /* 0% duty is not available */ | ||
320 | if (!tcmp) | ||
321 | ++tcmp; | ||
322 | |||
165 | tcmp = tcnt - tcmp; | 323 | tcmp = tcnt - tcmp; |
166 | /* the pwm hw only checks the compare register after a decrement, | ||
167 | so the pin never toggles if tcmp = tcnt */ | ||
168 | if (tcmp == tcnt) | ||
169 | tcmp--; | ||
170 | 324 | ||
171 | pwm_dbg(s3c, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); | 325 | /* Decrement to get tick numbers, instead of tick counts. */ |
326 | --tcnt; | ||
327 | /* -1UL will give 100% duty. */ | ||
328 | --tcmp; | ||
329 | |||
330 | dev_dbg(our_chip->chip.dev, | ||
331 | "tin_ns=%u, tcmp=%u/%u\n", tin_ns, tcmp, tcnt); | ||
332 | |||
333 | /* Update PWM registers. */ | ||
334 | writel(tcnt, our_chip->base + REG_TCNTB(pwm->hwpwm)); | ||
335 | writel(tcmp, our_chip->base + REG_TCMPB(pwm->hwpwm)); | ||
172 | 336 | ||
173 | if (tcmp < 0) | 337 | if (test_bit(PWMF_ENABLED, &pwm->flags)) |
174 | tcmp = 0; | 338 | pwm_samsung_enable(chip, pwm); |
175 | 339 | ||
176 | /* Update the PWM register block. */ | 340 | chan->period_ns = period_ns; |
341 | chan->tin_ns = tin_ns; | ||
342 | chan->duty_ns = duty_ns; | ||
177 | 343 | ||
178 | local_irq_save(flags); | 344 | return 0; |
345 | } | ||
179 | 346 | ||
180 | __raw_writel(tcmp, S3C2410_TCMPB(s3c->pwm_id)); | 347 | static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip, |
181 | __raw_writel(tcnt, S3C2410_TCNTB(s3c->pwm_id)); | 348 | unsigned int channel, bool invert) |
349 | { | ||
350 | unsigned int tcon_chan = to_tcon_channel(channel); | ||
351 | unsigned long flags; | ||
352 | u32 tcon; | ||
182 | 353 | ||
183 | tcon = __raw_readl(S3C2410_TCON); | 354 | spin_lock_irqsave(&samsung_pwm_lock, flags); |
184 | tcon |= pwm_tcon_manulupdate(s3c); | ||
185 | tcon |= pwm_tcon_autoreload(s3c); | ||
186 | __raw_writel(tcon, S3C2410_TCON); | ||
187 | 355 | ||
188 | tcon &= ~pwm_tcon_manulupdate(s3c); | 356 | tcon = readl(chip->base + REG_TCON); |
189 | __raw_writel(tcon, S3C2410_TCON); | 357 | |
358 | if (invert) { | ||
359 | chip->inverter_mask |= BIT(channel); | ||
360 | tcon |= TCON_INVERT(tcon_chan); | ||
361 | } else { | ||
362 | chip->inverter_mask &= ~BIT(channel); | ||
363 | tcon &= ~TCON_INVERT(tcon_chan); | ||
364 | } | ||
365 | |||
366 | writel(tcon, chip->base + REG_TCON); | ||
367 | |||
368 | spin_unlock_irqrestore(&samsung_pwm_lock, flags); | ||
369 | } | ||
370 | |||
371 | static int pwm_samsung_set_polarity(struct pwm_chip *chip, | ||
372 | struct pwm_device *pwm, | ||
373 | enum pwm_polarity polarity) | ||
374 | { | ||
375 | struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); | ||
376 | bool invert = (polarity == PWM_POLARITY_NORMAL); | ||
190 | 377 | ||
191 | local_irq_restore(flags); | 378 | /* Inverted means normal in the hardware. */ |
379 | pwm_samsung_set_invert(our_chip, pwm->hwpwm, invert); | ||
192 | 380 | ||
193 | return 0; | 381 | return 0; |
194 | } | 382 | } |
195 | 383 | ||
196 | static struct pwm_ops s3c_pwm_ops = { | 384 | static const struct pwm_ops pwm_samsung_ops = { |
197 | .enable = s3c_pwm_enable, | 385 | .request = pwm_samsung_request, |
198 | .disable = s3c_pwm_disable, | 386 | .free = pwm_samsung_free, |
199 | .config = s3c_pwm_config, | 387 | .enable = pwm_samsung_enable, |
200 | .owner = THIS_MODULE, | 388 | .disable = pwm_samsung_disable, |
389 | .config = pwm_samsung_config, | ||
390 | .set_polarity = pwm_samsung_set_polarity, | ||
391 | .owner = THIS_MODULE, | ||
201 | }; | 392 | }; |
202 | 393 | ||
203 | static int s3c_pwm_probe(struct platform_device *pdev) | 394 | #ifdef CONFIG_OF |
395 | static const struct samsung_pwm_variant s3c24xx_variant = { | ||
396 | .bits = 16, | ||
397 | .div_base = 1, | ||
398 | .has_tint_cstat = false, | ||
399 | .tclk_mask = BIT(4), | ||
400 | }; | ||
401 | |||
402 | static const struct samsung_pwm_variant s3c64xx_variant = { | ||
403 | .bits = 32, | ||
404 | .div_base = 0, | ||
405 | .has_tint_cstat = true, | ||
406 | .tclk_mask = BIT(7) | BIT(6) | BIT(5), | ||
407 | }; | ||
408 | |||
409 | static const struct samsung_pwm_variant s5p64x0_variant = { | ||
410 | .bits = 32, | ||
411 | .div_base = 0, | ||
412 | .has_tint_cstat = true, | ||
413 | .tclk_mask = 0, | ||
414 | }; | ||
415 | |||
416 | static const struct samsung_pwm_variant s5pc100_variant = { | ||
417 | .bits = 32, | ||
418 | .div_base = 0, | ||
419 | .has_tint_cstat = true, | ||
420 | .tclk_mask = BIT(5), | ||
421 | }; | ||
422 | |||
423 | static const struct of_device_id samsung_pwm_matches[] = { | ||
424 | { .compatible = "samsung,s3c2410-pwm", .data = &s3c24xx_variant }, | ||
425 | { .compatible = "samsung,s3c6400-pwm", .data = &s3c64xx_variant }, | ||
426 | { .compatible = "samsung,s5p6440-pwm", .data = &s5p64x0_variant }, | ||
427 | { .compatible = "samsung,s5pc100-pwm", .data = &s5pc100_variant }, | ||
428 | { .compatible = "samsung,exynos4210-pwm", .data = &s5p64x0_variant }, | ||
429 | {}, | ||
430 | }; | ||
431 | |||
432 | static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) | ||
433 | { | ||
434 | struct device_node *np = chip->chip.dev->of_node; | ||
435 | const struct of_device_id *match; | ||
436 | struct property *prop; | ||
437 | const __be32 *cur; | ||
438 | u32 val; | ||
439 | |||
440 | match = of_match_node(samsung_pwm_matches, np); | ||
441 | if (!match) | ||
442 | return -ENODEV; | ||
443 | |||
444 | memcpy(&chip->variant, match->data, sizeof(chip->variant)); | ||
445 | |||
446 | of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { | ||
447 | if (val >= SAMSUNG_PWM_NUM) { | ||
448 | dev_err(chip->chip.dev, | ||
449 | "%s: invalid channel index in samsung,pwm-outputs property\n", | ||
450 | __func__); | ||
451 | continue; | ||
452 | } | ||
453 | chip->variant.output_mask |= BIT(val); | ||
454 | } | ||
455 | |||
456 | return 0; | ||
457 | } | ||
458 | #else | ||
459 | static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) | ||
460 | { | ||
461 | return -ENODEV; | ||
462 | } | ||
463 | #endif | ||
464 | |||
465 | static int pwm_samsung_probe(struct platform_device *pdev) | ||
204 | { | 466 | { |
205 | struct device *dev = &pdev->dev; | 467 | struct device *dev = &pdev->dev; |
206 | struct s3c_chip *s3c; | 468 | struct samsung_pwm_chip *chip; |
207 | unsigned long flags; | 469 | struct resource *res; |
208 | unsigned long tcon; | 470 | unsigned int chan; |
209 | unsigned int id = pdev->id; | ||
210 | int ret; | 471 | int ret; |
211 | 472 | ||
212 | if (id == 4) { | 473 | chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); |
213 | dev_err(dev, "TIMER4 is currently not supported\n"); | 474 | if (chip == NULL) |
214 | return -ENXIO; | ||
215 | } | ||
216 | |||
217 | s3c = devm_kzalloc(&pdev->dev, sizeof(*s3c), GFP_KERNEL); | ||
218 | if (s3c == NULL) { | ||
219 | dev_err(dev, "failed to allocate pwm_device\n"); | ||
220 | return -ENOMEM; | 475 | return -ENOMEM; |
221 | } | ||
222 | 476 | ||
223 | /* calculate base of control bits in TCON */ | 477 | chip->chip.dev = &pdev->dev; |
224 | s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4; | 478 | chip->chip.ops = &pwm_samsung_ops; |
225 | s3c->pwm_id = id; | 479 | chip->chip.base = -1; |
226 | s3c->chip.dev = &pdev->dev; | 480 | chip->chip.npwm = SAMSUNG_PWM_NUM; |
227 | s3c->chip.ops = &s3c_pwm_ops; | 481 | chip->inverter_mask = BIT(SAMSUNG_PWM_NUM) - 1; |
228 | s3c->chip.base = -1; | 482 | |
229 | s3c->chip.npwm = 1; | 483 | if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { |
230 | 484 | ret = pwm_samsung_parse_dt(chip); | |
231 | s3c->clk = devm_clk_get(dev, "pwm-tin"); | 485 | if (ret) |
232 | if (IS_ERR(s3c->clk)) { | 486 | return ret; |
233 | dev_err(dev, "failed to get pwm tin clk\n"); | 487 | |
234 | return PTR_ERR(s3c->clk); | 488 | chip->chip.of_xlate = of_pwm_xlate_with_flags; |
489 | chip->chip.of_pwm_n_cells = 3; | ||
490 | } else { | ||
491 | if (!pdev->dev.platform_data) { | ||
492 | dev_err(&pdev->dev, "no platform data specified\n"); | ||
493 | return -EINVAL; | ||
494 | } | ||
495 | |||
496 | memcpy(&chip->variant, pdev->dev.platform_data, | ||
497 | sizeof(chip->variant)); | ||
235 | } | 498 | } |
236 | 499 | ||
237 | s3c->clk_div = devm_clk_get(dev, "pwm-tdiv"); | 500 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
238 | if (IS_ERR(s3c->clk_div)) { | 501 | chip->base = devm_ioremap_resource(&pdev->dev, res); |
239 | dev_err(dev, "failed to get pwm tdiv clk\n"); | 502 | if (IS_ERR(chip->base)) |
240 | return PTR_ERR(s3c->clk_div); | 503 | return PTR_ERR(chip->base); |
504 | |||
505 | chip->base_clk = devm_clk_get(&pdev->dev, "timers"); | ||
506 | if (IS_ERR(chip->base_clk)) { | ||
507 | dev_err(dev, "failed to get timer base clk\n"); | ||
508 | return PTR_ERR(chip->base_clk); | ||
241 | } | 509 | } |
242 | 510 | ||
243 | clk_enable(s3c->clk); | 511 | ret = clk_prepare_enable(chip->base_clk); |
244 | clk_enable(s3c->clk_div); | 512 | if (ret < 0) { |
513 | dev_err(dev, "failed to enable base clock\n"); | ||
514 | return ret; | ||
515 | } | ||
245 | 516 | ||
246 | local_irq_save(flags); | 517 | for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) |
518 | if (chip->variant.output_mask & BIT(chan)) | ||
519 | pwm_samsung_set_invert(chip, chan, true); | ||
247 | 520 | ||
248 | tcon = __raw_readl(S3C2410_TCON); | 521 | /* Following clocks are optional. */ |
249 | tcon |= pwm_tcon_invert(s3c); | 522 | chip->tclk0 = devm_clk_get(&pdev->dev, "pwm-tclk0"); |
250 | __raw_writel(tcon, S3C2410_TCON); | 523 | chip->tclk1 = devm_clk_get(&pdev->dev, "pwm-tclk1"); |
251 | 524 | ||
252 | local_irq_restore(flags); | 525 | platform_set_drvdata(pdev, chip); |
253 | 526 | ||
254 | ret = pwmchip_add(&s3c->chip); | 527 | ret = pwmchip_add(&chip->chip); |
255 | if (ret < 0) { | 528 | if (ret < 0) { |
256 | dev_err(dev, "failed to register pwm\n"); | 529 | dev_err(dev, "failed to register PWM chip\n"); |
257 | goto err_clk_tdiv; | 530 | clk_disable_unprepare(chip->base_clk); |
531 | return ret; | ||
258 | } | 532 | } |
259 | 533 | ||
260 | pwm_dbg(s3c, "config bits %02x\n", | 534 | dev_dbg(dev, "base_clk at %lu, tclk0 at %lu, tclk1 at %lu\n", |
261 | (__raw_readl(S3C2410_TCON) >> s3c->tcon_base) & 0x0f); | 535 | clk_get_rate(chip->base_clk), |
262 | 536 | !IS_ERR(chip->tclk0) ? clk_get_rate(chip->tclk0) : 0, | |
263 | dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n", | 537 | !IS_ERR(chip->tclk1) ? clk_get_rate(chip->tclk1) : 0); |
264 | clk_get_rate(s3c->clk), | ||
265 | clk_get_rate(s3c->clk_div), | ||
266 | pwm_is_tdiv(s3c) ? "div" : "ext", s3c->tcon_base); | ||
267 | 538 | ||
268 | platform_set_drvdata(pdev, s3c); | ||
269 | return 0; | 539 | return 0; |
270 | |||
271 | err_clk_tdiv: | ||
272 | clk_disable(s3c->clk_div); | ||
273 | clk_disable(s3c->clk); | ||
274 | return ret; | ||
275 | } | 540 | } |
276 | 541 | ||
277 | static int s3c_pwm_remove(struct platform_device *pdev) | 542 | static int pwm_samsung_remove(struct platform_device *pdev) |
278 | { | 543 | { |
279 | struct s3c_chip *s3c = platform_get_drvdata(pdev); | 544 | struct samsung_pwm_chip *chip = platform_get_drvdata(pdev); |
280 | int err; | 545 | int ret; |
281 | 546 | ||
282 | err = pwmchip_remove(&s3c->chip); | 547 | ret = pwmchip_remove(&chip->chip); |
283 | if (err < 0) | 548 | if (ret < 0) |
284 | return err; | 549 | return ret; |
285 | 550 | ||
286 | clk_disable(s3c->clk_div); | 551 | clk_disable_unprepare(chip->base_clk); |
287 | clk_disable(s3c->clk); | ||
288 | 552 | ||
289 | return 0; | 553 | return 0; |
290 | } | 554 | } |
291 | 555 | ||
292 | #ifdef CONFIG_PM_SLEEP | 556 | #ifdef CONFIG_PM_SLEEP |
293 | static int s3c_pwm_suspend(struct device *dev) | 557 | static int pwm_samsung_suspend(struct device *dev) |
294 | { | 558 | { |
295 | struct s3c_chip *s3c = dev_get_drvdata(dev); | 559 | struct samsung_pwm_chip *chip = dev_get_drvdata(dev); |
560 | unsigned int i; | ||
296 | 561 | ||
297 | /* No one preserve these values during suspend so reset them | 562 | /* |
298 | * Otherwise driver leaves PWM unconfigured if same values | 563 | * No one preserves these values during suspend so reset them. |
299 | * passed to pwm_config | 564 | * Otherwise driver leaves PWM unconfigured if same values are |
565 | * passed to pwm_config() next time. | ||
300 | */ | 566 | */ |
301 | s3c->period_ns = 0; | 567 | for (i = 0; i < SAMSUNG_PWM_NUM; ++i) { |
302 | s3c->duty_ns = 0; | 568 | struct pwm_device *pwm = &chip->chip.pwms[i]; |
569 | struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); | ||
570 | |||
571 | if (!chan) | ||
572 | continue; | ||
573 | |||
574 | chan->period_ns = 0; | ||
575 | chan->duty_ns = 0; | ||
576 | } | ||
303 | 577 | ||
304 | return 0; | 578 | return 0; |
305 | } | 579 | } |
306 | 580 | ||
307 | static int s3c_pwm_resume(struct device *dev) | 581 | static int pwm_samsung_resume(struct device *dev) |
308 | { | 582 | { |
309 | struct s3c_chip *s3c = dev_get_drvdata(dev); | 583 | struct samsung_pwm_chip *chip = dev_get_drvdata(dev); |
310 | unsigned long tcon; | 584 | unsigned int chan; |
311 | 585 | ||
312 | /* Restore invertion */ | 586 | /* |
313 | tcon = __raw_readl(S3C2410_TCON); | 587 | * Inverter setting must be preserved across suspend/resume |
314 | tcon |= pwm_tcon_invert(s3c); | 588 | * as nobody really seems to configure it more than once. |
315 | __raw_writel(tcon, S3C2410_TCON); | 589 | */ |
590 | for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) { | ||
591 | if (chip->variant.output_mask & BIT(chan)) | ||
592 | pwm_samsung_set_invert(chip, chan, | ||
593 | chip->inverter_mask & BIT(chan)); | ||
594 | } | ||
316 | 595 | ||
317 | return 0; | 596 | return 0; |
318 | } | 597 | } |
319 | #endif | 598 | #endif |
320 | 599 | ||
321 | static SIMPLE_DEV_PM_OPS(s3c_pwm_pm_ops, s3c_pwm_suspend, | 600 | static const struct dev_pm_ops pwm_samsung_pm_ops = { |
322 | s3c_pwm_resume); | 601 | SET_SYSTEM_SLEEP_PM_OPS(pwm_samsung_suspend, pwm_samsung_resume) |
602 | }; | ||
323 | 603 | ||
324 | static struct platform_driver s3c_pwm_driver = { | 604 | static struct platform_driver pwm_samsung_driver = { |
325 | .driver = { | 605 | .driver = { |
326 | .name = "s3c24xx-pwm", | 606 | .name = "samsung-pwm", |
327 | .owner = THIS_MODULE, | 607 | .owner = THIS_MODULE, |
328 | .pm = &s3c_pwm_pm_ops, | 608 | .pm = &pwm_samsung_pm_ops, |
609 | .of_match_table = of_match_ptr(samsung_pwm_matches), | ||
329 | }, | 610 | }, |
330 | .probe = s3c_pwm_probe, | 611 | .probe = pwm_samsung_probe, |
331 | .remove = s3c_pwm_remove, | 612 | .remove = pwm_samsung_remove, |
332 | }; | 613 | }; |
614 | module_platform_driver(pwm_samsung_driver); | ||
333 | 615 | ||
334 | static int __init pwm_init(void) | 616 | MODULE_LICENSE("GPL"); |
335 | { | 617 | MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>"); |
336 | int ret; | 618 | MODULE_ALIAS("platform:samsung-pwm"); |
337 | |||
338 | clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); | ||
339 | clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); | ||
340 | |||
341 | if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { | ||
342 | pr_err("failed to get scaler clocks\n"); | ||
343 | return -EINVAL; | ||
344 | } | ||
345 | |||
346 | ret = platform_driver_register(&s3c_pwm_driver); | ||
347 | if (ret) | ||
348 | pr_err("failed to add pwm driver\n"); | ||
349 | |||
350 | return ret; | ||
351 | } | ||
352 | |||
353 | arch_initcall(pwm_init); | ||