u-boot-brain/drivers/pwm/pwm-sifive.c
Simon Glass 41575d8e4c dm: treewide: Rename auto_alloc_size members to be shorter
This construct is quite long-winded. In earlier days it made some sense
since auto-allocation was a strange concept. But with driver model now
used pretty universally, we can shorten this to 'auto'. This reduces
verbosity and makes it easier to read.

Coincidentally it also ensures that every declaration is on one line,
thus making dtoc's job easier.

Signed-off-by: Simon Glass <sjg@chromium.org>
2020-12-13 08:00:25 -07:00

173 lines
4.6 KiB
C

// 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 = sizeof(struct pwm_sifive_priv),
};