Source
1
1
/*
2
2
* Copyright (c) 2007 Ben Dooks
3
3
* Copyright (c) 2008 Simtec Electronics
4
4
* Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
5
5
* Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com>
6
+
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
6
7
*
7
8
* PWM driver for Samsung SoCs
8
9
*
9
10
* This program is free software; you can redistribute it and/or modify
10
11
* it under the terms of the GNU General Public License as published by
11
12
* the Free Software Foundation; either version 2 of the License.
12
13
*/
13
14
14
15
#include <linux/bitops.h>
15
16
#include <linux/clk.h>
67
68
u32 period_ns;
68
69
u32 duty_ns;
69
70
u32 tin_ns;
70
71
};
71
72
72
73
/**
73
74
* struct samsung_pwm_chip - private data of PWM chip
74
75
* @chip: generic PWM chip
75
76
* @variant: local copy of hardware variant data
76
77
* @inverter_mask: inverter status for all channels - one bit per channel
78
+
* @disabled_mask: disabled status for all channels - one bit per channel
77
79
* @base: base address of mapped PWM registers
78
80
* @base_clk: base clock used to drive the timers
79
81
* @tclk0: external clock 0 (can be ERR_PTR if not present)
80
82
* @tclk1: external clock 1 (can be ERR_PTR if not present)
81
83
*/
82
84
struct samsung_pwm_chip {
83
85
struct pwm_chip chip;
84
86
struct samsung_pwm_variant variant;
85
87
u8 inverter_mask;
88
+
u8 disabled_mask;
86
89
87
90
void __iomem *base;
88
91
struct clk *base_clk;
89
92
struct clk *tclk0;
90
93
struct clk *tclk1;
91
94
};
92
95
93
96
#ifndef CONFIG_CLKSRC_SAMSUNG_PWM
94
97
/*
95
98
* PWM block is shared between pwm-samsung and samsung_pwm_timer drivers
250
253
tcon = readl(our_chip->base + REG_TCON);
251
254
252
255
tcon &= ~TCON_START(tcon_chan);
253
256
tcon |= TCON_MANUALUPDATE(tcon_chan);
254
257
writel(tcon, our_chip->base + REG_TCON);
255
258
256
259
tcon &= ~TCON_MANUALUPDATE(tcon_chan);
257
260
tcon |= TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan);
258
261
writel(tcon, our_chip->base + REG_TCON);
259
262
263
+
our_chip->disabled_mask &= ~BIT(pwm->hwpwm);
264
+
260
265
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
261
266
262
267
return 0;
263
268
}
264
269
265
270
static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm)
266
271
{
267
272
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
268
273
unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
269
274
unsigned long flags;
270
275
u32 tcon;
271
276
272
277
spin_lock_irqsave(&samsung_pwm_lock, flags);
273
278
274
279
tcon = readl(our_chip->base + REG_TCON);
275
280
tcon &= ~TCON_AUTORELOAD(tcon_chan);
276
281
writel(tcon, our_chip->base + REG_TCON);
277
282
283
+
our_chip->disabled_mask |= BIT(pwm->hwpwm);
284
+
278
285
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
279
286
}
280
287
281
288
static void pwm_samsung_manual_update(struct samsung_pwm_chip *chip,
282
289
struct pwm_device *pwm)
283
290
{
284
291
unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
285
292
u32 tcon;
286
293
unsigned long flags;
287
294
290
297
tcon = readl(chip->base + REG_TCON);
291
298
tcon |= TCON_MANUALUPDATE(tcon_chan);
292
299
writel(tcon, chip->base + REG_TCON);
293
300
294
301
tcon &= ~TCON_MANUALUPDATE(tcon_chan);
295
302
writel(tcon, chip->base + REG_TCON);
296
303
297
304
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
298
305
}
299
306
300
-
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
301
-
int duty_ns, int period_ns)
307
+
static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
308
+
int duty_ns, int period_ns, bool force_period)
302
309
{
303
310
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
304
311
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
305
312
u32 tin_ns = chan->tin_ns, tcnt, tcmp, oldtcmp;
306
313
307
314
/*
308
315
* We currently avoid using 64bit arithmetic by using the
309
316
* fact that anything faster than 1Hz is easily representable
310
317
* by 32bits.
311
318
*/
312
319
if (period_ns > NSEC_PER_SEC)
313
320
return -ERANGE;
314
321
315
322
tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm));
316
323
oldtcmp = readl(our_chip->base + REG_TCMPB(pwm->hwpwm));
317
324
318
325
/* We need tick count for calculation, not last tick. */
319
326
++tcnt;
320
327
321
328
/* Check to see if we are changing the clock rate of the PWM. */
322
-
if (chan->period_ns != period_ns) {
329
+
if (chan->period_ns != period_ns || force_period) {
323
330
unsigned long tin_rate;
324
331
u32 period;
325
332
326
333
period = NSEC_PER_SEC / period_ns;
327
334
328
335
dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n",
329
336
duty_ns, period_ns, period);
330
337
331
338
tin_rate = pwm_samsung_calc_tin(our_chip, pwm->hwpwm, period);
332
339
371
378
pwm_samsung_manual_update(our_chip, pwm);
372
379
}
373
380
374
381
chan->period_ns = period_ns;
375
382
chan->tin_ns = tin_ns;
376
383
chan->duty_ns = duty_ns;
377
384
378
385
return 0;
379
386
}
380
387
388
+
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
389
+
int duty_ns, int period_ns)
390
+
{
391
+
return __pwm_samsung_config(chip, pwm, duty_ns, period_ns, false);
392
+
}
393
+
381
394
static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
382
395
unsigned int channel, bool invert)
383
396
{
384
397
unsigned int tcon_chan = to_tcon_channel(channel);
385
398
unsigned long flags;
386
399
u32 tcon;
387
400
388
401
spin_lock_irqsave(&samsung_pwm_lock, flags);
389
402
390
403
tcon = readl(chip->base + REG_TCON);
582
595
ret = pwmchip_remove(&chip->chip);
583
596
if (ret < 0)
584
597
return ret;
585
598
586
599
clk_disable_unprepare(chip->base_clk);
587
600
588
601
return 0;
589
602
}
590
603
591
604
#ifdef CONFIG_PM_SLEEP
592
-
static int pwm_samsung_suspend(struct device *dev)
605
+
static int pwm_samsung_resume(struct device *dev)
593
606
{
594
-
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
607
+
struct samsung_pwm_chip *our_chip = dev_get_drvdata(dev);
608
+
struct pwm_chip *chip = &our_chip->chip;
595
609
unsigned int i;
596
610
597
-
/*
598
-
* No one preserves these values during suspend so reset them.
599
-
* Otherwise driver leaves PWM unconfigured if same values are
600
-
* passed to pwm_config() next time.
601
-
*/
602
-
for (i = 0; i < SAMSUNG_PWM_NUM; ++i) {
603
-
struct pwm_device *pwm = &chip->chip.pwms[i];
611
+
for (i = 0; i < SAMSUNG_PWM_NUM; i++) {
612
+
struct pwm_device *pwm = &chip->pwms[i];
604
613
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
605
614
606
615
if (!chan)
607
616
continue;
608
617
609
-
chan->period_ns = 0;
610
-
chan->duty_ns = 0;
611
-
}
612
-
613
-
return 0;
614
-
}
618
+
if (our_chip->variant.output_mask & BIT(i))
619
+
pwm_samsung_set_invert(our_chip, i,
620
+
our_chip->inverter_mask & BIT(i));
615
621
616
-
static int pwm_samsung_resume(struct device *dev)
617
-
{
618
-
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
619
-
unsigned int chan;
622
+
if (chan->period_ns) {
623
+
__pwm_samsung_config(chip, pwm, chan->duty_ns,
624
+
chan->period_ns, true);
625
+
/* needed to make PWM disable work on Odroid-XU3 */
626
+
pwm_samsung_manual_update(our_chip, pwm);
627
+
}
620
628
621
-
/*
622
-
* Inverter setting must be preserved across suspend/resume
623
-
* as nobody really seems to configure it more than once.
624
-
*/
625
-
for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) {
626
-
if (chip->variant.output_mask & BIT(chan))
627
-
pwm_samsung_set_invert(chip, chan,
628
-
chip->inverter_mask & BIT(chan));
629
+
if (our_chip->disabled_mask & BIT(i))
630
+
pwm_samsung_disable(chip, pwm);
631
+
else
632
+
pwm_samsung_enable(chip, pwm);
629
633
}
630
634
631
635
return 0;
632
636
}
633
637
#endif
634
638
635
-
static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, pwm_samsung_suspend,
636
-
pwm_samsung_resume);
639
+
static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, NULL, pwm_samsung_resume);
637
640
638
641
static struct platform_driver pwm_samsung_driver = {
639
642
.driver = {
640
643
.name = "samsung-pwm",
641
644
.pm = &pwm_samsung_pm_ops,
642
645
.of_match_table = of_match_ptr(samsung_pwm_matches),
643
646
},
644
647
.probe = pwm_samsung_probe,
645
648
.remove = pwm_samsung_remove,
646
649
};