Skip to content

Commit bafbbef

Browse files
pwm: Add Apple PWM controller
Adds the Apple PWM controller driver. Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com> Acked-by: Sven Peter <sven@svenpeter.dev> Reviewed-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
1 parent 87a3a39 commit bafbbef

3 files changed

Lines changed: 172 additions & 0 deletions

File tree

drivers/pwm/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ config PWM_AB8500
5151
To compile this driver as a module, choose M here: the module
5252
will be called pwm-ab8500.
5353

54+
config PWM_APPLE
55+
tristate "Apple SoC PWM support"
56+
depends on ARCH_APPLE || COMPILE_TEST
57+
help
58+
Generic PWM framework driver for PWM controller present on
59+
Apple SoCs
60+
61+
Say Y here if you have an ARM Apple laptop, otherwise say N
62+
63+
To compile this driver as a module, choose M here: the module
64+
will be called pwm-apple.
65+
5466
config PWM_ATMEL
5567
tristate "Atmel PWM support"
5668
depends on ARCH_AT91 || COMPILE_TEST

drivers/pwm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
obj-$(CONFIG_PWM) += core.o
33
obj-$(CONFIG_PWM_SYSFS) += sysfs.o
44
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
5+
obj-$(CONFIG_PWM_APPLE) += pwm-apple.o
56
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
67
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
78
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o

drivers/pwm/pwm-apple.c

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR MIT
2+
/*
3+
* Driver for the Apple SoC PWM controller
4+
*
5+
* Copyright The Asahi Linux Contributors
6+
*
7+
* Limitations:
8+
* - The writes to cycle registers are shadowed until a write to
9+
* the control register.
10+
* - If both OFF_CYCLES and ON_CYCLES are set to 0, the output
11+
* is a constant off signal.
12+
* - When APPLE_PWM_CTRL is set to 0, the output is constant low
13+
*/
14+
15+
#include <linux/module.h>
16+
#include <linux/platform_device.h>
17+
#include <linux/pwm.h>
18+
#include <linux/io.h>
19+
#include <linux/clk.h>
20+
#include <linux/math64.h>
21+
22+
#define APPLE_PWM_CTRL 0x00
23+
#define APPLE_PWM_ON_CYCLES 0x1c
24+
#define APPLE_PWM_OFF_CYCLES 0x18
25+
26+
#define APPLE_PWM_CTRL_ENABLE BIT(0)
27+
#define APPLE_PWM_CTRL_MODE BIT(2)
28+
#define APPLE_PWM_CTRL_UPDATE BIT(5)
29+
#define APPLE_PWM_CTRL_TRIGGER BIT(9)
30+
#define APPLE_PWM_CTRL_INVERT BIT(10)
31+
#define APPLE_PWM_CTRL_OUTPUT_ENABLE BIT(14)
32+
33+
struct apple_pwm {
34+
struct pwm_chip chip;
35+
void __iomem *base;
36+
u64 clkrate;
37+
};
38+
39+
static inline struct apple_pwm *to_apple_pwm(struct pwm_chip *chip)
40+
{
41+
return container_of(chip, struct apple_pwm, chip);
42+
}
43+
44+
static int apple_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
45+
const struct pwm_state *state)
46+
{
47+
struct apple_pwm *fpwm;
48+
49+
if (state->polarity == PWM_POLARITY_INVERSED)
50+
return -EINVAL;
51+
52+
fpwm = to_apple_pwm(chip);
53+
if (state->enabled) {
54+
u64 on_cycles, off_cycles;
55+
56+
on_cycles = mul_u64_u64_div_u64(fpwm->clkrate,
57+
state->duty_cycle, NSEC_PER_SEC);
58+
if (on_cycles > 0xFFFFFFFF)
59+
on_cycles = 0xFFFFFFFF;
60+
61+
off_cycles = mul_u64_u64_div_u64(fpwm->clkrate,
62+
state->period, NSEC_PER_SEC) - on_cycles;
63+
if (off_cycles > 0xFFFFFFFF)
64+
off_cycles = 0xFFFFFFFF;
65+
66+
writel(on_cycles, fpwm->base + APPLE_PWM_ON_CYCLES);
67+
writel(off_cycles, fpwm->base + APPLE_PWM_OFF_CYCLES);
68+
writel(APPLE_PWM_CTRL_ENABLE | APPLE_PWM_CTRL_OUTPUT_ENABLE | APPLE_PWM_CTRL_UPDATE,
69+
fpwm->base + APPLE_PWM_CTRL);
70+
} else {
71+
writel(0, fpwm->base + APPLE_PWM_CTRL);
72+
}
73+
return 0;
74+
}
75+
76+
static int apple_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
77+
struct pwm_state *state)
78+
{
79+
struct apple_pwm *fpwm;
80+
u32 on_cycles, off_cycles, ctrl;
81+
82+
fpwm = to_apple_pwm(chip);
83+
84+
ctrl = readl(fpwm->base + APPLE_PWM_CTRL);
85+
on_cycles = readl(fpwm->base + APPLE_PWM_ON_CYCLES);
86+
off_cycles = readl(fpwm->base + APPLE_PWM_OFF_CYCLES);
87+
88+
state->enabled = (ctrl & APPLE_PWM_CTRL_ENABLE) && (ctrl & APPLE_PWM_CTRL_OUTPUT_ENABLE);
89+
state->polarity = PWM_POLARITY_NORMAL;
90+
// on_cycles + off_cycles is 33 bits, NSEC_PER_SEC is 30, there is no overflow
91+
state->duty_cycle = DIV64_U64_ROUND_UP((u64)on_cycles * NSEC_PER_SEC, fpwm->clkrate);
92+
state->period = DIV64_U64_ROUND_UP(((u64)off_cycles + (u64)on_cycles) *
93+
NSEC_PER_SEC, fpwm->clkrate);
94+
95+
return 0;
96+
}
97+
98+
static const struct pwm_ops apple_pwm_ops = {
99+
.apply = apple_pwm_apply,
100+
.get_state = apple_pwm_get_state,
101+
.owner = THIS_MODULE,
102+
};
103+
104+
static int apple_pwm_probe(struct platform_device *pdev)
105+
{
106+
struct apple_pwm *fpwm;
107+
struct clk *clk;
108+
int ret;
109+
110+
fpwm = devm_kzalloc(&pdev->dev, sizeof(*fpwm), GFP_KERNEL);
111+
if (!fpwm)
112+
return -ENOMEM;
113+
114+
fpwm->base = devm_platform_ioremap_resource(pdev, 0);
115+
if (IS_ERR(fpwm->base))
116+
return PTR_ERR(fpwm->base);
117+
118+
clk = devm_clk_get_enabled(&pdev->dev, NULL);
119+
if (IS_ERR(clk))
120+
return dev_err_probe(&pdev->dev, PTR_ERR(clk), "unable to get the clock");
121+
122+
/*
123+
* Uses the 24MHz system clock on all existing devices, can only
124+
* happen if the device tree is broken
125+
*
126+
* This check is done to prevent an overflow in .apply
127+
*/
128+
fpwm->clkrate = clk_get_rate(clk);
129+
if (fpwm->clkrate > NSEC_PER_SEC)
130+
return dev_err_probe(&pdev->dev, -EINVAL, "pwm clock out of range");
131+
132+
fpwm->chip.dev = &pdev->dev;
133+
fpwm->chip.npwm = 1;
134+
fpwm->chip.ops = &apple_pwm_ops;
135+
136+
ret = devm_pwmchip_add(&pdev->dev, &fpwm->chip);
137+
if (ret < 0)
138+
return dev_err_probe(&pdev->dev, ret, "unable to add pwm chip");
139+
140+
return 0;
141+
}
142+
143+
static const struct of_device_id apple_pwm_of_match[] = {
144+
{ .compatible = "apple,s5l-fpwm" },
145+
{}
146+
};
147+
MODULE_DEVICE_TABLE(of, apple_pwm_of_match);
148+
149+
static struct platform_driver apple_pwm_driver = {
150+
.probe = apple_pwm_probe,
151+
.driver = {
152+
.name = "apple-pwm",
153+
.of_match_table = apple_pwm_of_match,
154+
},
155+
};
156+
module_platform_driver(apple_pwm_driver);
157+
158+
MODULE_DESCRIPTION("Apple SoC PWM driver");
159+
MODULE_LICENSE("Dual MIT/GPL");

0 commit comments

Comments
 (0)