Skip to content

Commit b634f1c

Browse files
committed
FPWM driver.
See AsahiLinux#5
1 parent 1746f4d commit b634f1c

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

drivers/pwm/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ 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_M1
55+
tristate "Apple M1 FPWM support"
56+
depends on ARCH_APPLE
57+
help
58+
PWM driver for the Apple M1's FPWM (which controls the
59+
keyboard backlight in some MacBook Pros)
60+
61+
To compile this driver as a module, choose M here; the module
62+
will be called pwm-apple-m1-fpwm.
63+
5464
config PWM_ATMEL
5565
tristate "Atmel PWM support"
5666
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_M1) += pwm-apple-m1.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-m1.c

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Driver for Apple M1 FPWM. Used for the keyboard backlight on at
4+
* least some MacBook Pros.
5+
*
6+
* Copyright (C) 2021 Pip Cet <pipcet@gmail.com>
7+
*
8+
* Based on pwm-twl-led.c, which is:
9+
*
10+
* Copyright (C) 2012 Texas Instruments
11+
* Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
12+
*/
13+
14+
#include <linux/clk.h>
15+
#include <linux/module.h>
16+
#include <linux/of.h>
17+
#include <linux/io.h>
18+
#include <linux/platform_device.h>
19+
#include <linux/pwm.h>
20+
#include <linux/slab.h>
21+
22+
#define FPWM_CONTROL 0x00
23+
#define FPWM_CONTROL_UPDATE 0x4239
24+
#define FPWM_CONTROL_DISABLE 0
25+
#define FPWM_STATUS 0x08
26+
#define FPWM_COUNT_OFF 0x18
27+
#define FPWM_COUNT_ON 0x1c
28+
#define FPWM_HZ (24 * 1000 * 1000)
29+
30+
struct fpwm_chip {
31+
struct pwm_chip chip;
32+
void __iomem *regbase;
33+
struct clk *clk;
34+
struct mutex mutex; // XXX
35+
};
36+
37+
static inline struct fpwm_chip *to_fpwm(struct pwm_chip *chip)
38+
{
39+
return container_of(chip, struct fpwm_chip, chip);
40+
}
41+
42+
static int fpwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
43+
int duty_ns, int period_ns)
44+
{
45+
struct fpwm_chip *fpwm = to_fpwm(chip);
46+
long duty_ticks = duty_ns * 3L / (1000000000L * 3 / FPWM_HZ);
47+
long period_ticks = period_ns * 3L / (1000000000L * 3 / FPWM_HZ);
48+
long off_ticks = period_ticks - duty_ticks;
49+
50+
writel(duty_ticks, fpwm->regbase + FPWM_COUNT_ON);
51+
writel(off_ticks, fpwm->regbase + FPWM_COUNT_OFF);
52+
writel(FPWM_CONTROL_UPDATE, fpwm->regbase + FPWM_CONTROL);
53+
54+
return 0;
55+
}
56+
57+
static int fpwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
58+
{
59+
struct fpwm_chip *fpwm = to_fpwm(chip);
60+
61+
writel(FPWM_CONTROL_UPDATE, fpwm->regbase + FPWM_CONTROL);
62+
63+
return 0;
64+
}
65+
66+
static void fpwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
67+
{
68+
struct fpwm_chip *fpwm = to_fpwm(chip);
69+
70+
writel(FPWM_CONTROL_DISABLE, fpwm->regbase + FPWM_CONTROL);
71+
}
72+
73+
static const struct pwm_ops fpwm_ops = {
74+
.enable = fpwm_enable,
75+
.disable = fpwm_disable,
76+
.config = fpwm_config,
77+
.owner = THIS_MODULE,
78+
};
79+
80+
static int fpwm_probe(struct platform_device *pdev)
81+
{
82+
struct fpwm_chip *fpwm;
83+
struct resource *rsrc;
84+
int ret;
85+
86+
fpwm = devm_kzalloc(&pdev->dev, sizeof(*fpwm), GFP_KERNEL);
87+
if (!fpwm)
88+
return -ENOMEM;
89+
90+
fpwm->chip.ops = &fpwm_ops;
91+
fpwm->chip.npwm = 1;
92+
fpwm->chip.dev = &pdev->dev;
93+
fpwm->chip.base = -1;
94+
95+
mutex_init(&fpwm->mutex);
96+
97+
ret = pwmchip_add(&fpwm->chip);
98+
if(ret < 0)
99+
return ret;
100+
101+
platform_set_drvdata(pdev, fpwm);
102+
103+
rsrc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
104+
fpwm->regbase = devm_ioremap_resource(&pdev->dev, rsrc);
105+
if(IS_ERR(fpwm->regbase))
106+
return PTR_ERR(fpwm->regbase);
107+
108+
fpwm->clk = devm_clk_get(&pdev->dev, NULL);
109+
if(IS_ERR(fpwm->clk)) {
110+
dev_err(&pdev->dev, "unable to get clock: %ld\n",
111+
PTR_ERR(fpwm->clk));
112+
return PTR_ERR(fpwm->clk);
113+
}
114+
115+
ret = clk_prepare_enable(fpwm->clk);
116+
if(ret)
117+
return ret;
118+
119+
return 0;
120+
}
121+
122+
static int fpwm_remove(struct platform_device *pdev)
123+
{
124+
struct fpwm_chip *fpwm = platform_get_drvdata(pdev);
125+
126+
int ret = pwmchip_remove(&fpwm->chip);
127+
128+
if (ret < 0)
129+
return ret;
130+
131+
clk_disable_unprepare(fpwm->clk);
132+
133+
return 0;
134+
}
135+
136+
#ifdef CONFIG_OF
137+
static const struct of_device_id fpwm_of_match[] = {
138+
{ .compatible = "apple,fpwm-t8101" },
139+
{ .compatible = "apple,fpwm-s5l8920x" },
140+
{ },
141+
};
142+
MODULE_DEVICE_TABLE(of, fpwm_of_match);
143+
#endif
144+
145+
static struct platform_driver fpwm_driver = {
146+
.driver = {
147+
.name = "apple-m1-fpwm",
148+
.of_match_table = of_match_ptr(fpwm_of_match),
149+
},
150+
.probe = fpwm_probe,
151+
.remove = fpwm_remove,
152+
};
153+
module_platform_driver(fpwm_driver);
154+
155+
MODULE_AUTHOR("Pip Cet <pipcet@gmail.com>");
156+
MODULE_DESCRIPTION("PWM driver for Apple M1 FPWM");
157+
MODULE_ALIAS("platform:apple-m1-fpwm");
158+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)