Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Documentation/devicetree/bindings/pwm/apple,fpwm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/apple,fpwm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: Apple M1 FPWM controller

maintainers:
- Pip Cet <pipcet@gmail.com>

description:
Single-channel PWM connected to the keyboard backlight on at least
some MacBook Pros.

allOf:
- $ref: pwm.yaml#

properties:
compatible:
const: apple,t8103-fpwm

reg:
maxItems: 1

clocks:
maxItems: 1

"#pwm-cells":
const: 0

additionalProperties: false

required:
- reg
- clocks

examples:
- |
pwm@235044000 {
compatible = "apple,t8103-fpwm";
reg = <0x35044000 0x4000>;
clocks = <&fpwm0_clk>;
#pwm-cells = <0>;
};
2 changes: 2 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -1702,8 +1702,10 @@ T: git https://github.com/AsahiLinux/linux.git
F: Documentation/devicetree/bindings/arm/apple.yaml
F: Documentation/devicetree/bindings/interrupt-controller/apple,aic.yaml
F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
F: Documentation/devicetree/bindings/pwm/apple,fpwm.yaml
F: arch/arm64/boot/dts/apple/
F: drivers/irqchip/irq-apple-aic.c
F: drivers/pwm/pwm-apple-m1.c
F: include/dt-bindings/interrupt-controller/apple-aic.h
F: include/dt-bindings/pinctrl/apple.h

Expand Down
2 changes: 1 addition & 1 deletion arch/arm64/boot/dts/apple/Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
dtb-$(CONFIG_ARCH_APPLE) += t8103-j274.dtb
dtb-$(CONFIG_ARCH_APPLE) += t8103-j274.dtb t8103-j293.dtb
54 changes: 54 additions & 0 deletions arch/arm64/boot/dts/apple/t8103-j293.dts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0+ OR MIT
/*
* Apple MacBook Pro (13-inch, M1, 2020)
*
* target-type: J293
*
* Copyright The Asahi Linux Contributors
*/

/dts-v1/;

#include "t8103.dtsi"

/ {
compatible = "apple,j293", "apple,t8103", "apple,arm-platform";
model = "Apple MacBook Pro (13-inch, M1, 2020)";

aliases {
serial0 = &serial0;
};

soc {
pwm@235044000 {
compatible = "apple,t8103-fpwm";
reg = <0x2 0x35044000 0x0 0x4000>;
clocks = <&clk24>;
#pwm-cells = <0>;
};
};

chosen {
#address-cells = <2>;
#size-cells = <2>;
ranges;

stdout-path = "serial0";

framebuffer0: framebuffer@0 {
compatible = "apple,simple-framebuffer", "simple-framebuffer";
reg = <0 0 0 0>; /* To be filled by loader */
/* Format properties will be added by loader */
status = "disabled";
};
};

memory@800000000 {
device_type = "memory";
reg = <0x8 0 0x2 0>; /* To be filled by loader */
};
};

&serial0 {
status = "okay";
};
12 changes: 12 additions & 0 deletions drivers/pwm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ config PWM_AB8500
To compile this driver as a module, choose M here: the module
will be called pwm-ab8500.

config PWM_APPLE_M1
tristate "Apple M1 PWM support"
depends on ARCH_APPLE || COMPILE_TEST
depends on HAS_IOMEM && OF
help
PWM driver for the FPWM found on Apple M1 SoCs. This is
connected to the keyboard backlight on at least some
MacBook Pros.

To compile this driver as a module, choose M here: the module
will be called pwm-apple-m1.

config PWM_ATMEL
tristate "Atmel PWM support"
depends on ARCH_AT91 || COMPILE_TEST
Expand Down
1 change: 1 addition & 0 deletions drivers/pwm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
obj-$(CONFIG_PWM) += core.o
obj-$(CONFIG_PWM_SYSFS) += sysfs.o
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
obj-$(CONFIG_PWM_APPLE_M1) += pwm-apple-m1.o
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
Expand Down
165 changes: 165 additions & 0 deletions drivers/pwm/pwm-apple-m1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Apple M1 FPWM
*
* Used for the keyboard backlight on at least some MacBook Pros.
*
* This driver requires a clock, which should provide the standard 24 MHz
* reference clock rate on M1 systems.
*
* The actual hardware appears to provide interrupt facilities and
* other unknown features, but those have not been reverse-engineered
* yet.
*
* Hardware documentation:
*
* https://github.com/AsahiLinux/docs/wiki/HW:MacBook-Pro-keyboard-backlight-(FPWM0)
*
* Copyright (C) 2021 Pip Cet <pipcet@gmail.com>
*
* Based on pwm-twl-led.c, which is:
*
* Copyright (C) 2012 Texas Instruments
* Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
*/

#include <linux/clk.h>
#include <linux/io.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>

#define FPWM_CONTROL 0x00
#define CONTROL_UPDATE 0x4239
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm concerned, that's just a magic constant, derived at experimentally. Should there be a comment explaining that?

#define CONTROL_DISABLE 0
#define FPWM_STATUS 0x08
#define FPWM_COUNT_OFF 0x18
#define FPWM_COUNT_ON 0x1c

struct fpwm_chip {
struct pwm_chip chip;
void __iomem *reg;
struct clk *clk;
u64 rate;
};

static inline struct fpwm_chip *to_fpwm(struct pwm_chip *chip)
{
return container_of(chip, struct fpwm_chip, chip);
}

static int fpwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct fpwm_chip *fpwm = to_fpwm(chip);
long duty_ticks = div_u64(duty_ns * fpwm->rate, 1000000000);
long period_ticks = div_u64(period_ns * fpwm->rate, 1000000000);
long off_ticks = period_ticks - duty_ticks;

writel(duty_ticks, fpwm->reg + FPWM_COUNT_ON);
writel(off_ticks, fpwm->reg + FPWM_COUNT_OFF);
writel(CONTROL_UPDATE, fpwm->reg + FPWM_CONTROL);

return 0;
}

static int fpwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct fpwm_chip *fpwm = to_fpwm(chip);

writel(CONTROL_UPDATE, fpwm->reg + FPWM_CONTROL);

return 0;
}

static void fpwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct fpwm_chip *fpwm = to_fpwm(chip);

writel(CONTROL_DISABLE, fpwm->reg + FPWM_CONTROL);
}

static const struct pwm_ops fpwm_ops = {
.enable = fpwm_enable,
.disable = fpwm_disable,
.config = fpwm_config,
.owner = THIS_MODULE,
};

static int fpwm_probe(struct platform_device *pdev)
{
struct fpwm_chip *fpwm;
struct resource *res;
int ret;

fpwm = devm_kzalloc(&pdev->dev, sizeof(*fpwm), GFP_KERNEL);
if (!fpwm)
return -ENOMEM;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
Copy link
Member

@alyssarosenzweig alyssarosenzweig Aug 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retracted fpwm is leaked here you're fine


fpwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(fpwm->clk))
return PTR_ERR(fpwm->clk);

ret = clk_prepare_enable(fpwm->clk);
if (ret)
return ret;

fpwm->rate = clk_get_rate(fpwm->clk);

fpwm->reg = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(fpwm->reg)) {
clk_disable_unprepare(fpwm->clk);
return PTR_ERR(fpwm->reg);
}

fpwm->chip.ops = &fpwm_ops;
fpwm->chip.npwm = 1;
fpwm->chip.dev = &pdev->dev;
fpwm->chip.base = -1;

ret = devm_pwmchip_add(&pdev->dev, &fpwm->chip);
if (ret < 0) {
clk_disable_unprepare(fpwm->clk);
return ret;
}

platform_set_drvdata(pdev, fpwm);

return 0;
}

static int fpwm_remove(struct platform_device *pdev)
{
struct fpwm_chip *fpwm = platform_get_drvdata(pdev);

clk_disable_unprepare(fpwm->clk);

return 0;
}

static const struct of_device_id fpwm_of_match[] = {
{ .compatible = "apple,t8103-fpwm" },
{ },
};
MODULE_DEVICE_TABLE(of, fpwm_of_match);

static struct platform_driver fpwm_driver = {
.driver = {
.name = "apple-m1-fpwm",
.of_match_table = of_match_ptr(fpwm_of_match),
},
.probe = fpwm_probe,
.remove = fpwm_remove,
};
module_platform_driver(fpwm_driver);

MODULE_AUTHOR("Pip Cet <pipcet@gmail.com>");
MODULE_DESCRIPTION("PWM driver for Apple M1 FPWM");
MODULE_LICENSE("GPL");