-
Notifications
You must be signed in to change notification settings - Fork 5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a driver for the RP1 PWM block. Signed-off-by: Phil Elwell <[email protected]>
- Loading branch information
1 parent
01be7b2
commit 0f8a5cf
Showing
4 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) | ||
%YAML 1.2 | ||
--- | ||
$id: http://devicetree.org/schemas/pwm/pwm-rp1.yaml# | ||
$schema: http://devicetree.org/meta-schemas/core.yaml# | ||
|
||
title: Raspberry Pi RP1 PWM controller | ||
|
||
maintainers: | ||
- Naushir Patuck <[email protected]> | ||
|
||
properties: | ||
compatible: | ||
enum: | ||
- raspberrypi,rp1-pwm | ||
|
||
reg: | ||
maxItems: 1 | ||
|
||
"#pwm-cells": | ||
const: 3 | ||
|
||
required: | ||
- compatible | ||
- reg | ||
- clocks | ||
- "#pwm-cells" | ||
|
||
additionalProperties: false | ||
|
||
examples: | ||
- | | ||
pwm0: pwm@98000 { | ||
compatible = "raspberrypi,rp1-pwm"; | ||
reg = <0x0 0x98000 0x0 0x100>; | ||
clocks = <&rp1_sys>; | ||
#pwm-cells = <3>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* pwm-rp1.c | ||
* | ||
* Raspberry Pi RP1 PWM. | ||
* | ||
* Copyright © 2023 Raspberry Pi Ltd. | ||
* | ||
* Author: Naushir Patuck ([email protected]) | ||
* | ||
* Based on the pwm-bcm2835 driver by: | ||
* Bart Tanghe <[email protected]> | ||
*/ | ||
|
||
#include <linux/bitops.h> | ||
#include <linux/clk.h> | ||
#include <linux/err.h> | ||
#include <linux/io.h> | ||
#include <linux/module.h> | ||
#include <linux/of.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/pwm.h> | ||
|
||
#define PWM_GLOBAL_CTRL 0x000 | ||
#define PWM_CHANNEL_CTRL(x) (0x014 + ((x) * 16)) | ||
#define PWM_RANGE(x) (0x018 + ((x) * 16)) | ||
#define PWM_DUTY(x) (0x020 + ((x) * 16)) | ||
|
||
/* 8:FIFO_POP_MASK + 0:Trailing edge M/S modulation */ | ||
#define PWM_CHANNEL_DEFAULT (BIT(8) + BIT(0)) | ||
#define PWM_CHANNEL_ENABLE(x) BIT(x) | ||
#define PWM_POLARITY BIT(3) | ||
#define SET_UPDATE BIT(31) | ||
#define PWM_MODE_MASK GENMASK(1, 0) | ||
|
||
struct rp1_pwm { | ||
struct pwm_chip chip; | ||
struct device *dev; | ||
void __iomem *base; | ||
struct clk *clk; | ||
}; | ||
|
||
static inline struct rp1_pwm *to_rp1_pwm(struct pwm_chip *chip) | ||
{ | ||
return container_of(chip, struct rp1_pwm, chip); | ||
} | ||
|
||
static void rp1_pwm_apply_config(struct pwm_chip *chip, struct pwm_device *pwm) | ||
{ | ||
struct rp1_pwm *pc = to_rp1_pwm(chip); | ||
u32 value; | ||
|
||
value = readl(pc->base + PWM_GLOBAL_CTRL); | ||
value |= SET_UPDATE; | ||
writel(value, pc->base + PWM_GLOBAL_CTRL); | ||
} | ||
|
||
static int rp1_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
{ | ||
struct rp1_pwm *pc = to_rp1_pwm(chip); | ||
|
||
writel(PWM_CHANNEL_DEFAULT, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); | ||
return 0; | ||
} | ||
|
||
static void rp1_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | ||
{ | ||
struct rp1_pwm *pc = to_rp1_pwm(chip); | ||
u32 value; | ||
|
||
value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); | ||
value &= ~PWM_MODE_MASK; | ||
writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); | ||
rp1_pwm_apply_config(chip, pwm); | ||
} | ||
|
||
static int rp1_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, | ||
const struct pwm_state *state) | ||
{ | ||
struct rp1_pwm *pc = to_rp1_pwm(chip); | ||
unsigned long clk_rate = clk_get_rate(pc->clk); | ||
unsigned long clk_period; | ||
u32 value; | ||
|
||
if (!clk_rate) { | ||
dev_err(pc->dev, "failed to get clock rate\n"); | ||
return -EINVAL; | ||
} | ||
|
||
/* set period */ | ||
clk_period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, clk_rate); | ||
|
||
writel(DIV_ROUND_CLOSEST(state->duty_cycle, clk_period), | ||
pc->base + PWM_DUTY(pwm->hwpwm)); | ||
|
||
/* set duty cycle */ | ||
writel(DIV_ROUND_CLOSEST(state->period, clk_period), | ||
pc->base + PWM_RANGE(pwm->hwpwm)); | ||
|
||
/* set polarity */ | ||
value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); | ||
if (state->polarity == PWM_POLARITY_NORMAL) | ||
value &= ~PWM_POLARITY; | ||
else | ||
value |= PWM_POLARITY; | ||
writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); | ||
|
||
/* enable/disable */ | ||
value = readl(pc->base + PWM_GLOBAL_CTRL); | ||
if (state->enabled) | ||
value |= PWM_CHANNEL_ENABLE(pwm->hwpwm); | ||
else | ||
value &= ~PWM_CHANNEL_ENABLE(pwm->hwpwm); | ||
writel(value, pc->base + PWM_GLOBAL_CTRL); | ||
|
||
rp1_pwm_apply_config(chip, pwm); | ||
|
||
return 0; | ||
} | ||
|
||
static const struct pwm_ops rp1_pwm_ops = { | ||
.request = rp1_pwm_request, | ||
.free = rp1_pwm_free, | ||
.apply = rp1_pwm_apply, | ||
}; | ||
|
||
static int rp1_pwm_probe(struct platform_device *pdev) | ||
{ | ||
struct rp1_pwm *pc; | ||
struct resource *res; | ||
int ret; | ||
|
||
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); | ||
if (!pc) | ||
return -ENOMEM; | ||
|
||
pc->dev = &pdev->dev; | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
pc->base = devm_ioremap_resource(&pdev->dev, res); | ||
if (IS_ERR(pc->base)) | ||
return PTR_ERR(pc->base); | ||
|
||
pc->clk = devm_clk_get(&pdev->dev, NULL); | ||
if (IS_ERR(pc->clk)) | ||
return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), | ||
"clock not found\n"); | ||
|
||
ret = clk_prepare_enable(pc->clk); | ||
if (ret) | ||
return ret; | ||
|
||
pc->chip.dev = &pdev->dev; | ||
pc->chip.ops = &rp1_pwm_ops; | ||
pc->chip.npwm = 4; | ||
pc->chip.of_xlate = of_pwm_xlate_with_flags; | ||
|
||
platform_set_drvdata(pdev, pc); | ||
|
||
ret = pwmchip_add(&pc->chip); | ||
if (ret < 0) | ||
goto add_fail; | ||
|
||
return 0; | ||
|
||
add_fail: | ||
clk_disable_unprepare(pc->clk); | ||
return ret; | ||
} | ||
|
||
static int rp1_pwm_remove(struct platform_device *pdev) | ||
{ | ||
struct rp1_pwm *pc = platform_get_drvdata(pdev); | ||
|
||
clk_disable_unprepare(pc->clk); | ||
|
||
pwmchip_remove(&pc->chip); | ||
|
||
return 0; | ||
} | ||
|
||
static const struct of_device_id rp1_pwm_of_match[] = { | ||
{ .compatible = "raspberrypi,rp1-pwm" }, | ||
{ /* sentinel */ } | ||
}; | ||
MODULE_DEVICE_TABLE(of, rp1_pwm_of_match); | ||
|
||
static struct platform_driver rp1_pwm_driver = { | ||
.driver = { | ||
.name = "rpi-pwm", | ||
.of_match_table = rp1_pwm_of_match, | ||
}, | ||
.probe = rp1_pwm_probe, | ||
.remove = rp1_pwm_remove, | ||
}; | ||
module_platform_driver(rp1_pwm_driver); | ||
|
||
MODULE_AUTHOR("Naushir Patuck <[email protected]"); | ||
MODULE_DESCRIPTION("RP1 PWM driver"); | ||
MODULE_LICENSE("GPL"); |