173 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			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_alloc_size	= sizeof(struct pwm_sifive_priv),
 | 
						|
};
 |