Skip to content

Instantly share code, notes, and snippets.

@kikuchan
Last active May 15, 2024 15:03
Show Gist options
  • Save kikuchan/f21be9dc5947fbb88d7c51dff2f6bdd6 to your computer and use it in GitHub Desktop.
Save kikuchan/f21be9dc5947fbb88d7c51dff2f6bdd6 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Allwinner's new PWM Controller (such as D1 / H616 / T5 series)
*
* Copyright (C) 2024 Hironori KIKUCHI <kikuchan98@gmail.com>
*
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gcd.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
// SoC specific offsets
#define SUN20I_REG_OFFSET_PER_SUN20I (0x0080)
#define SUN20I_REG_OFFSET_PCR_SUN20I (0x0100 + 0x0000)
#define SUN20I_REG_OFFSET_PPR_SUN20I (0x0100 + 0x0004)
#define SUN20I_REG_OFFSET_PER_SUN50I (0x0040)
#define SUN20I_REG_OFFSET_PCR_SUN50I (0x0060 + 0x0000)
#define SUN20I_REG_OFFSET_PPR_SUN50I (0x0060 + 0x0004)
// Register offsets
#define SUN20I_REG_OFFSET_PCCR(chip, ch) (0x0020 + 0x04 * ((ch) >> 1))
#define SUN20I_REG_OFFSET_PCGR(chip, ch) (0x0040)
#define SUN20I_REG_OFFSET_PER(chip, ch) ((chip)->data->reg_per)
#define SUN20I_REG_OFFSET_PCR(chip, ch) ((chip)->data->reg_pcr + 0x20 * (ch))
#define SUN20I_REG_OFFSET_PPR(chip, ch) ((chip)->data->reg_ppr + 0x20 * (ch))
// PCCR: PWMxx Clock Configuration Register
#define SUN20I_REG_PCCR_CLK_SRC_MASK GENMASK(8, 7)
#define SUN20I_REG_PCCR_CLK_DIV_M_MASK GENMASK(3, 0)
#define SUN20I_REG_PCCR_CLK_BYPASS(ch) BIT(5 + ((ch) & 1))
#define SUN20I_REG_PCCR_CLK_GATING(ch) BIT(4)
// PCGR: PWM Clock Gating Register
#define SUN20I_REG_PCGR_CLK_BYPASS(ch) BIT(16 + (ch))
#define SUN20I_REG_PCGR_CLK_GATING(ch) BIT(ch)
// PER: PWM Enable Regsiter
#define SUN20I_REG_PER_ENABLE(ch) BIT(ch)
// PCR: PWM Control Register
#define SUN20I_REG_PCR_ACT_STA BIT(8)
#define SUN20I_REG_PCR_PRESCAL_K_MASK GENMASK(7, 0)
// PPR: PWM Period Register
#define SUN20I_REG_PPR_ENTIRE_CYCLE_MASK GENMASK(31, 16)
#define SUN20I_REG_PPR_ACT_CYCLE_MASK GENMASK(15, 0)
// Constants
#define SUN20I_PWM_CHANNELS_MAX (16)
#define SUN20I_PWM_CLOCK_SRC_HOSC (0)
#define SUN20I_PWM_CLOCK_SRC_APB (1)
#define SUN20I_PWM_DIV_M_SHIFT_MAX (8)
#define SUN20I_PWM_PRESCALE_K_MAX (256)
#define SUN20I_PWM_ENT_CYCLE_MAX (0xffffULL)
// Configuration
#define SUN20I_PWM_DIV_M_SHIFT_DEFAULT (0)
#define SUN20I_PWM_CLOCK_SRC_DEFAULT SUN20I_PWM_CLOCK_SRC_HOSC
struct sun20i_pwm_data {
unsigned long reg_per;
unsigned long reg_pcr;
unsigned long reg_ppr;
bool has_pcgr;
};
struct sun20i_pwm_chip {
struct clk *clk_bus;
struct clk *clk_hosc;
struct clk *clk_apb;
struct reset_control *rst;
void __iomem *base;
spinlock_t ctrl_lock;
const struct sun20i_pwm_data *data;
unsigned int clk_src[(SUN20I_PWM_CHANNELS_MAX + 1) / 2];
unsigned int div_m_shift[(SUN20I_PWM_CHANNELS_MAX + 1) / 2];
};
static inline struct sun20i_pwm_chip *to_sun20i_pwm_chip(struct pwm_chip *chip)
{
return pwmchip_get_drvdata(chip);
}
static inline u32 sun20i_pwm_readl(struct sun20i_pwm_chip *sun20i_chip, unsigned long offset)
{
return readl(sun20i_chip->base + offset);
}
static inline void sun20i_pwm_writel(struct sun20i_pwm_chip *sun20i_chip, u32 val, unsigned long offset)
{
writel(val, sun20i_chip->base + offset);
}
static inline bool sun20i_pwm_is_channel_enabled(struct sun20i_pwm_chip *sun20i_chip, int hwpwm)
{
u32 val;
val = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PER(sun20i_chip, hwpwm));
return val & SUN20I_REG_PER_ENABLE(hwpwm) ? true : false;
}
static inline void sun20i_pwm_enable_channel(struct sun20i_pwm_chip *sun20i_chip, struct pwm_device *pwm, bool enable)
{
u32 val;
// PWM Clock Configuration Register
u32 pccr = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PCCR(sun20i_chip, pwm->hwpwm));
if (enable) {
unsigned int idx = pwm->hwpwm / 2;
// Set clock source
pccr &= ~SUN20I_REG_PCCR_CLK_SRC_MASK;
pccr |= FIELD_PREP(SUN20I_REG_PCCR_CLK_SRC_MASK, sun20i_chip->clk_src[idx]);
// Set DIV_M shift
pccr &= ~SUN20I_REG_PCCR_CLK_DIV_M_MASK;
pccr |= FIELD_PREP(SUN20I_REG_PCCR_CLK_DIV_M_MASK, sun20i_chip->div_m_shift[idx]);
}
if (sun20i_chip->data->has_pcgr) {
// PWM Clock Gating Register
val = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PCGR(sun20i_chip, pwm->hwpwm));
if (enable) {
val &= ~SUN20I_REG_PCGR_CLK_BYPASS(pwm->hwpwm);
val |= SUN20I_REG_PCGR_CLK_GATING(pwm->hwpwm);
} else {
val &= ~SUN20I_REG_PCGR_CLK_GATING(pwm->hwpwm);
}
sun20i_pwm_writel(sun20i_chip, val, SUN20I_REG_OFFSET_PCGR(sun20i_chip, pwm->hwpwm));
} else {
// CLK_BYPASS and CLK_GATING are in PCCR otherwise
if (enable) {
pccr &= ~SUN20I_REG_PCCR_CLK_BYPASS(pwm->hwpwm);
pccr |= SUN20I_REG_PCCR_CLK_GATING(pwm->hwpwm);
} else if (!sun20i_pwm_is_channel_enabled(sun20i_chip, pwm->hwpwm ^ 1)) {
// drop the flag if and only if the counterpart is disabled
pccr &= ~SUN20I_REG_PCCR_CLK_GATING(pwm->hwpwm);
}
}
sun20i_pwm_writel(sun20i_chip, pccr, SUN20I_REG_OFFSET_PCCR(sun20i_chip, pwm->hwpwm));
// PWM Enabling
val = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PER(sun20i_chip, pwm->hwpwm));
if (enable)
val |= SUN20I_REG_PER_ENABLE(pwm->hwpwm);
else
val &= ~SUN20I_REG_PER_ENABLE(pwm->hwpwm);
sun20i_pwm_writel(sun20i_chip, val, SUN20I_REG_OFFSET_PER(sun20i_chip, pwm->hwpwm));
}
static inline u64 sun20i_pwm_get_clock_rate(struct sun20i_pwm_chip *sun20i_chip, unsigned int clksrc)
{
switch (clksrc) {
case SUN20I_PWM_CLOCK_SRC_HOSC:
return clk_get_rate(sun20i_chip->clk_hosc);
case SUN20I_PWM_CLOCK_SRC_APB:
return clk_get_rate(sun20i_chip->clk_apb);
default:
return 0;
}
}
static int sun20i_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state)
{
struct sun20i_pwm_chip *sun20i_chip = to_sun20i_pwm_chip(chip);
u64 clk_rate;
u32 pccr = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PCCR(sun20i_chip, pwm->hwpwm));
u32 pcr = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PCR(sun20i_chip, pwm->hwpwm));
u32 ppr = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PPR(sun20i_chip, pwm->hwpwm));
clk_rate = sun20i_pwm_get_clock_rate(sun20i_chip, FIELD_GET(SUN20I_REG_PCCR_CLK_SRC_MASK, pccr));
if (!clk_rate) {
dev_err(pwmchip_parent(chip), "Invalid CLK_SRC is detected\n");
return -EINVAL;
}
unsigned int prescale_k = FIELD_GET(SUN20I_REG_PCR_PRESCAL_K_MASK, pcr) + 1;
u32 ent_cycle = FIELD_GET(SUN20I_REG_PPR_ENTIRE_CYCLE_MASK, ppr) + 1;
u32 act_cycle = min(ent_cycle, FIELD_GET(SUN20I_REG_PPR_ACT_CYCLE_MASK, ppr));
unsigned int div_m_shift = FIELD_GET(SUN20I_REG_PCCR_CLK_DIV_M_MASK, pccr);
if (div_m_shift > SUN20I_PWM_DIV_M_SHIFT_MAX) {
dev_err(pwmchip_parent(chip), "Invalid DIV_M is detected\n");
return -EINVAL;
}
unsigned int div_m = 1 << div_m_shift;
// set to the state
state->enabled = sun20i_pwm_is_channel_enabled(sun20i_chip, pwm->hwpwm);
state->polarity = (pcr & SUN20I_REG_PCR_ACT_STA) ? PWM_POLARITY_NORMAL : PWM_POLARITY_INVERSED;
state->period = DIV_ROUND_CLOSEST_ULL(ent_cycle * prescale_k * div_m * NSEC_PER_SEC, clk_rate);
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(act_cycle * prescale_k * div_m * NSEC_PER_SEC, clk_rate);
return 0;
}
static inline unsigned long sun20i_pwm_find_prescale_k(unsigned long long ent_cycle, unsigned long long act_cycle)
{
if (ent_cycle == 0 || ent_cycle > SUN20I_PWM_ENT_CYCLE_MAX * SUN20I_PWM_PRESCALE_K_MAX || ent_cycle < act_cycle)
return 0;
return clamp(DIV_ROUND_UP_ULL(ent_cycle, SUN20I_PWM_ENT_CYCLE_MAX), 1, SUN20I_PWM_PRESCALE_K_MAX);
}
static int sun20i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state)
{
struct sun20i_pwm_chip *sun20i_chip = to_sun20i_pwm_chip(chip);
u64 clk_rate;
u32 ctrl;
spin_lock(&sun20i_chip->ctrl_lock);
// Period and Duty cycle
if (state->duty_cycle != pwm->state.duty_cycle || state->period != pwm->state.period) {
unsigned int idx = pwm->hwpwm / 2;
clk_rate = sun20i_pwm_get_clock_rate(sun20i_chip, sun20i_chip->clk_src[idx]);
unsigned long div_m = 1U << sun20i_chip->div_m_shift[idx];
unsigned long long ent_cycle = DIV_ROUND_CLOSEST(state->period * clk_rate, NSEC_PER_SEC * div_m);
unsigned long long act_cycle = min(DIV_ROUND_CLOSEST(state->duty_cycle * clk_rate, NSEC_PER_SEC * div_m), ent_cycle);
unsigned long prescale_k = sun20i_pwm_find_prescale_k(ent_cycle, act_cycle);
if (!prescale_k)
goto err;
ent_cycle = clamp(DIV_ROUND_CLOSEST_ULL(ent_cycle, prescale_k), 1, SUN20I_PWM_ENT_CYCLE_MAX);
act_cycle = clamp(DIV_ROUND_CLOSEST_ULL(act_cycle, prescale_k), 0, ent_cycle);
// Set prescale_k
ctrl = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PCR(sun20i_chip, pwm->hwpwm));
ctrl &= ~SUN20I_REG_PCR_PRESCAL_K_MASK;
ctrl |= FIELD_PREP(SUN20I_REG_PCR_PRESCAL_K_MASK, prescale_k - 1);
sun20i_pwm_writel(sun20i_chip, ctrl, SUN20I_REG_OFFSET_PCR(sun20i_chip, pwm->hwpwm));
// Set period and duty cycle
ctrl = (((ent_cycle - 1) & 0xFFFF) << 16) | (act_cycle & 0xFFFF);
sun20i_pwm_writel(sun20i_chip, ctrl, SUN20I_REG_OFFSET_PPR(sun20i_chip, pwm->hwpwm));
}
// Polarity
if (state->polarity != pwm->state.polarity) {
ctrl = sun20i_pwm_readl(sun20i_chip, SUN20I_REG_OFFSET_PCR(sun20i_chip, pwm->hwpwm));
if (state->polarity == PWM_POLARITY_NORMAL) {
ctrl |= SUN20I_REG_PCR_ACT_STA;
} else {
ctrl &= ~SUN20I_REG_PCR_ACT_STA;
}
sun20i_pwm_writel(sun20i_chip, ctrl, SUN20I_REG_OFFSET_PCR(sun20i_chip, pwm->hwpwm));
}
// Enable
if (state->enabled != pwm->state.enabled)
sun20i_pwm_enable_channel(sun20i_chip, pwm, state->enabled);
spin_unlock(&sun20i_chip->ctrl_lock);
return 0;
err:
spin_unlock(&sun20i_chip->ctrl_lock);
return -EINVAL;
}
static const struct pwm_ops sun20i_pwm_ops = {
.apply = sun20i_pwm_apply,
.get_state = sun20i_pwm_get_state,
};
static const struct sun20i_pwm_data sun20i_d1_pwm_data = {
.reg_per = SUN20I_REG_OFFSET_PER_SUN20I,
.reg_pcr = SUN20I_REG_OFFSET_PCR_SUN20I,
.reg_ppr = SUN20I_REG_OFFSET_PPR_SUN20I,
.has_pcgr = true,
};
static const struct sun20i_pwm_data sun50i_h616_pwm_data = {
.reg_per = SUN20I_REG_OFFSET_PER_SUN50I,
.reg_pcr = SUN20I_REG_OFFSET_PCR_SUN50I,
.reg_ppr = SUN20I_REG_OFFSET_PPR_SUN50I,
.has_pcgr = false,
};
static const struct of_device_id sun20i_pwm_dt_ids[] = {
{
.compatible = "allwinner,sun20i-d1-pwm",
.data = &sun20i_d1_pwm_data,
},
{
.compatible = "allwinner,sun50i-h616-pwm",
.data = &sun50i_h616_pwm_data,
},
{
/* sentinel */
},
};
MODULE_DEVICE_TABLE(of, sun20i_pwm_dt_ids);
static int sun20i_pwm_probe(struct platform_device *pdev)
{
struct pwm_chip *chip;
const struct sun20i_pwm_data *data;
struct sun20i_pwm_chip *sun20i_chip;
u32 npwm;
int ret;
struct device_node *node = pdev->dev.of_node;
data = of_device_get_match_data(&pdev->dev);
if (!data)
return -ENODEV;
ret = of_property_read_u32(node, "allwinner,pwm-channels", &npwm);
if (ret)
return dev_err_probe(&pdev->dev, -EINVAL, "No PWM channels are configured\n");
if (npwm > SUN20I_PWM_CHANNELS_MAX)
return dev_err_probe(&pdev->dev, -EINVAL, "Too many PWM channels are configured\n");
chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*sun20i_chip));
if (IS_ERR(chip))
return PTR_ERR(chip);
sun20i_chip = to_sun20i_pwm_chip(chip);
sun20i_chip->data = data;
for (int i = 0; i < (npwm + 1) / 2; i++) {
sun20i_chip->clk_src[i] = SUN20I_PWM_CLOCK_SRC_DEFAULT;
sun20i_chip->div_m_shift[i] = SUN20I_PWM_DIV_M_SHIFT_DEFAULT;
const char *source;
ret = of_property_read_string_index(node, "allwinner,pwm-paired-channel-clock-sources", i, &source);
if (!ret) {
if (!strcasecmp(source, "hosc"))
sun20i_chip->clk_src[i] = SUN20I_PWM_CLOCK_SRC_HOSC;
else if (!strcasecmp(source, "apb"))
sun20i_chip->clk_src[i] = SUN20I_PWM_CLOCK_SRC_APB;
else
return dev_err_probe(&pdev->dev, -EINVAL, "Unknown clock source: %s\n", source);
}
u32 value;
ret = of_property_read_u32_index(node, "allwinner,pwm-paired-channel-clock-prescales", i, &value);
if (!ret) {
if (value <= SUN20I_PWM_DIV_M_SHIFT_MAX)
sun20i_chip->div_m_shift[i] = value;
else
return dev_err_probe(&pdev->dev, -EINVAL, "Invalid prescale value: %u\n", value);
}
}
sun20i_chip->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(sun20i_chip->base))
return PTR_ERR(sun20i_chip->base);
sun20i_chip->clk_bus = devm_clk_get_enabled(&pdev->dev, "bus");
if (IS_ERR(sun20i_chip->clk_bus))
return dev_err_probe(&pdev->dev, PTR_ERR(sun20i_chip->clk_bus), "Failed to get `bus` clock\n");
sun20i_chip->clk_hosc = devm_clk_get_enabled(&pdev->dev, "hosc");
if (IS_ERR(sun20i_chip->clk_hosc))
return dev_err_probe(&pdev->dev, PTR_ERR(sun20i_chip->clk_hosc), "Failed to get `hosc` clock\n");
sun20i_chip->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb");
if (IS_ERR(sun20i_chip->clk_apb))
return dev_err_probe(&pdev->dev, PTR_ERR(sun20i_chip->clk_apb), "Failed to get `apb` clock\n");
sun20i_chip->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(sun20i_chip->rst))
return dev_err_probe(&pdev->dev, PTR_ERR(sun20i_chip->rst), "Failed to get bus reset\n");
ret = reset_control_deassert(sun20i_chip->rst);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to deassert reset control\n");
chip->ops = &sun20i_pwm_ops;
spin_lock_init(&sun20i_chip->ctrl_lock);
ret = pwmchip_add(chip);
if (ret < 0) {
reset_control_assert(sun20i_chip->rst);
return dev_err_probe(&pdev->dev, ret, "Failed to add PWM chip\n");
}
platform_set_drvdata(pdev, chip);
return 0;
}
static void sun20i_pwm_remove(struct platform_device *pdev)
{
struct pwm_chip *chip = platform_get_drvdata(pdev);
struct sun20i_pwm_chip *sun20i_chip = to_sun20i_pwm_chip(chip);
pwmchip_remove(chip);
reset_control_assert(sun20i_chip->rst);
}
static struct platform_driver sun20i_pwm_driver = {
.driver = {
.name = "sun20i-pwm",
.of_match_table = sun20i_pwm_dt_ids,
},
.probe = sun20i_pwm_probe,
.remove_new = sun20i_pwm_remove,
};
module_platform_driver(sun20i_pwm_driver);
MODULE_ALIAS("platform:sun20i-pwm");
MODULE_AUTHOR("Hironori KIKUCHI <kikuchan98@gmail.com>");
MODULE_DESCRIPTION("Allwinner sun20i PWM driver");
MODULE_LICENSE("GPL v2");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment