mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-06-09 23:36:03 +09:00
pwm: Add PWM driver for SiFive SoC
Adds a PWM driver for PWM chip present in SiFive's HiFive Unleashed SoC This driver is simple port of Linux pwm sifive driver from Linux v5.6 commit: 9e37a53eb051 ("pwm: sifive: Add a driver for SiFive SoC PWM") Signed-off-by: Yash Shah <yash.shah@sifive.com> Reviewed-by: Heiko Schocher <hs@denx.de>
This commit is contained in:
parent
0dae9e24ea
commit
7239a610b7
|
@ -47,6 +47,12 @@ config PWM_SANDBOX
|
|||
useful. The PWM can be enabled but is not connected to any outputs
|
||||
so this is not very useful.
|
||||
|
||||
config PWM_SIFIVE
|
||||
bool "Enable support for SiFive PWM"
|
||||
depends on DM_PWM
|
||||
help
|
||||
This PWM is found SiFive's FU540 and other SoCs.
|
||||
|
||||
config PWM_TEGRA
|
||||
bool "Enable support for the Tegra PWM"
|
||||
depends on DM_PWM
|
||||
|
|
|
@ -15,5 +15,6 @@ obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o
|
|||
obj-$(CONFIG_PWM_MTK) += pwm-mtk.o
|
||||
obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o
|
||||
obj-$(CONFIG_PWM_SANDBOX) += sandbox_pwm.o
|
||||
obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
|
||||
obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o
|
||||
obj-$(CONFIG_PWM_SUNXI) += sunxi_pwm.o
|
||||
|
|
172
drivers/pwm/pwm-sifive.c
Normal file
172
drivers/pwm/pwm-sifive.c
Normal file
|
@ -0,0 +1,172 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2020 SiFive, Inc
|
||||
* For SiFive's PWM IP block documentation please refer Chapter 14 of
|
||||
* Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
|
||||
*
|
||||
* Limitations:
|
||||
* - When changing both duty cycle and period, we cannot prevent in
|
||||
* software that the output might produce a period with mixed
|
||||
* settings (new period length and old duty cycle).
|
||||
* - The hardware cannot generate a 100% duty cycle.
|
||||
* - The hardware generates only inverted output.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <div64.h>
|
||||
#include <dm.h>
|
||||
#include <pwm.h>
|
||||
#include <regmap.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
/* PWMCFG fields */
|
||||
#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0)
|
||||
#define PWM_SIFIVE_PWMCFG_STICKY BIT(8)
|
||||
#define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9)
|
||||
#define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10)
|
||||
#define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12)
|
||||
#define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13)
|
||||
#define PWM_SIFIVE_PWMCFG_CENTER BIT(16)
|
||||
#define PWM_SIFIVE_PWMCFG_GANG BIT(24)
|
||||
#define PWM_SIFIVE_PWMCFG_IP BIT(28)
|
||||
|
||||
/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */
|
||||
#define PWM_SIFIVE_SIZE_PWMCMP 4
|
||||
#define PWM_SIFIVE_CMPWIDTH 16
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
struct pwm_sifive_regs {
|
||||
unsigned long cfg;
|
||||
unsigned long cnt;
|
||||
unsigned long pwms;
|
||||
unsigned long cmp0;
|
||||
};
|
||||
|
||||
struct pwm_sifive_data {
|
||||
struct pwm_sifive_regs regs;
|
||||
};
|
||||
|
||||
struct pwm_sifive_priv {
|
||||
void __iomem *base;
|
||||
ulong freq;
|
||||
const struct pwm_sifive_data *data;
|
||||
};
|
||||
|
||||
static int pwm_sifive_set_config(struct udevice *dev, uint channel,
|
||||
uint period_ns, uint duty_ns)
|
||||
{
|
||||
struct pwm_sifive_priv *priv = dev_get_priv(dev);
|
||||
const struct pwm_sifive_regs *regs = &priv->data->regs;
|
||||
unsigned long scale_pow;
|
||||
unsigned long long num;
|
||||
u32 scale, val = 0, frac;
|
||||
|
||||
debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns);
|
||||
|
||||
/*
|
||||
* The PWM unit is used with pwmzerocmp=0, so the only way to modify the
|
||||
* period length is using pwmscale which provides the number of bits the
|
||||
* counter is shifted before being feed to the comparators. A period
|
||||
* lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks.
|
||||
* (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period
|
||||
*/
|
||||
scale_pow = lldiv((uint64_t)priv->freq * period_ns, 1000000000);
|
||||
scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf);
|
||||
val |= FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale);
|
||||
|
||||
/*
|
||||
* The problem of output producing mixed setting as mentioned at top,
|
||||
* occurs here. To minimize the window for this problem, we are
|
||||
* calculating the register values first and then writing them
|
||||
* consecutively
|
||||
*/
|
||||
num = (u64)duty_ns * (1U << PWM_SIFIVE_CMPWIDTH);
|
||||
frac = DIV_ROUND_CLOSEST_ULL(num, period_ns);
|
||||
frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
|
||||
|
||||
writel(val, priv->base + regs->cfg);
|
||||
writel(frac, priv->base + regs->cmp0 + channel *
|
||||
PWM_SIFIVE_SIZE_PWMCMP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_sifive_set_enable(struct udevice *dev, uint channel, bool enable)
|
||||
{
|
||||
struct pwm_sifive_priv *priv = dev_get_priv(dev);
|
||||
const struct pwm_sifive_regs *regs = &priv->data->regs;
|
||||
u32 val;
|
||||
|
||||
debug("%s: Enable '%s'\n", __func__, dev->name);
|
||||
|
||||
if (enable) {
|
||||
val = readl(priv->base + regs->cfg);
|
||||
val |= PWM_SIFIVE_PWMCFG_EN_ALWAYS;
|
||||
writel(val, priv->base + regs->cfg);
|
||||
} else {
|
||||
writel(0, priv->base + regs->cmp0 + channel *
|
||||
PWM_SIFIVE_SIZE_PWMCMP);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_sifive_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct pwm_sifive_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->base = dev_read_addr_ptr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_sifive_probe(struct udevice *dev)
|
||||
{
|
||||
struct pwm_sifive_priv *priv = dev_get_priv(dev);
|
||||
struct clk clk;
|
||||
int ret = 0;
|
||||
|
||||
ret = clk_get_by_index(dev, 0, &clk);
|
||||
if (ret < 0) {
|
||||
debug("%s get clock fail!\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->freq = clk_get_rate(&clk);
|
||||
priv->data = (struct pwm_sifive_data *)dev_get_driver_data(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops pwm_sifive_ops = {
|
||||
.set_config = pwm_sifive_set_config,
|
||||
.set_enable = pwm_sifive_set_enable,
|
||||
};
|
||||
|
||||
static const struct pwm_sifive_data pwm_data = {
|
||||
.regs = {
|
||||
.cfg = 0x00,
|
||||
.cnt = 0x08,
|
||||
.pwms = 0x10,
|
||||
.cmp0 = 0x20,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct udevice_id pwm_sifive_ids[] = {
|
||||
{ .compatible = "sifive,pwm0", .data = (ulong)&pwm_data},
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(pwm_sifive) = {
|
||||
.name = "pwm_sifive",
|
||||
.id = UCLASS_PWM,
|
||||
.of_match = pwm_sifive_ids,
|
||||
.ops = &pwm_sifive_ops,
|
||||
.ofdata_to_platdata = pwm_sifive_ofdata_to_platdata,
|
||||
.probe = pwm_sifive_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct pwm_sifive_priv),
|
||||
};
|
Loading…
Reference in New Issue
Block a user