9b075973e9
Add support for the PWM found on the SAMA5D2 family of devices. Signed-off-by: Dan Sneddon <dan.sneddon@microchip.com>
208 lines
4.7 KiB
C
208 lines
4.7 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* PWM support for Microchip AT91 architectures.
|
|
*
|
|
* Copyright (C) 2021 Microchip Technology Inc. and its subsidiaries
|
|
*
|
|
* Author: Dan Sneddon <daniel.sneddon@microchip.com>
|
|
*
|
|
* Based on drivers/pwm/pwm-atmel.c from Linux.
|
|
*/
|
|
#include <clk.h>
|
|
#include <common.h>
|
|
#include <div64.h>
|
|
#include <dm.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/io.h>
|
|
#include <pwm.h>
|
|
|
|
#define PERIOD_BITS 16
|
|
#define PWM_MAX_PRES 10
|
|
#define NSEC_PER_SEC 1000000000L
|
|
|
|
#define PWM_ENA 0x04
|
|
#define PWM_CHANNEL_OFFSET 0x20
|
|
#define PWM_CMR 0x200
|
|
#define PWM_CMR_CPRE_MSK GENMASK(3, 0)
|
|
#define PWM_CMR_CPOL BIT(9)
|
|
#define PWM_CDTY 0x204
|
|
#define PWM_CPRD 0x20C
|
|
|
|
struct at91_pwm_priv {
|
|
void __iomem *base;
|
|
struct clk pclk;
|
|
u32 clkrate;
|
|
};
|
|
|
|
static int at91_pwm_calculate_cprd_and_pres(struct udevice *dev,
|
|
unsigned long clkrate,
|
|
uint period_ns, uint duty_ns,
|
|
unsigned long *cprd, u32 *pres)
|
|
{
|
|
u64 cycles = period_ns;
|
|
int shift;
|
|
|
|
/* Calculate the period cycles and prescale value */
|
|
cycles *= clkrate;
|
|
do_div(cycles, NSEC_PER_SEC);
|
|
|
|
/*
|
|
* The register for the period length is period_bits bits wide.
|
|
* So for each bit the number of clock cycles is wider divide the input
|
|
* clock frequency by two using pres and shift cprd accordingly.
|
|
*/
|
|
shift = fls(cycles) - PERIOD_BITS;
|
|
|
|
if (shift > PWM_MAX_PRES) {
|
|
return -EINVAL;
|
|
} else if (shift > 0) {
|
|
*pres = shift;
|
|
cycles >>= *pres;
|
|
} else {
|
|
*pres = 0;
|
|
}
|
|
|
|
*cprd = cycles;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void at91_pwm_calculate_cdty(uint period_ns, uint duty_ns,
|
|
unsigned long clkrate, unsigned long cprd,
|
|
u32 pres, unsigned long *cdty)
|
|
{
|
|
u64 cycles = duty_ns;
|
|
|
|
cycles *= clkrate;
|
|
do_div(cycles, NSEC_PER_SEC);
|
|
cycles >>= pres;
|
|
*cdty = cprd - cycles;
|
|
}
|
|
|
|
/**
|
|
* Returns: channel status after set operation
|
|
*/
|
|
static bool at91_pwm_set(void __iomem *base, uint channel, bool enable)
|
|
{
|
|
u32 val, cur_status;
|
|
|
|
val = ioread32(base + PWM_ENA);
|
|
cur_status = !!(val & BIT(channel));
|
|
|
|
/* if channel is already in that state, do nothing */
|
|
if (!(enable ^ cur_status))
|
|
return cur_status;
|
|
|
|
if (enable)
|
|
val |= BIT(channel);
|
|
else
|
|
val &= ~(BIT(channel));
|
|
|
|
iowrite32(val, base + PWM_ENA);
|
|
|
|
return cur_status;
|
|
}
|
|
|
|
static int at91_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
|
|
{
|
|
struct at91_pwm_priv *priv = dev_get_priv(dev);
|
|
|
|
at91_pwm_set(priv->base, channel, enable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int at91_pwm_set_config(struct udevice *dev, uint channel,
|
|
uint period_ns, uint duty_ns)
|
|
{
|
|
struct at91_pwm_priv *priv = dev_get_priv(dev);
|
|
unsigned long cprd, cdty;
|
|
u32 pres, val;
|
|
int channel_enabled;
|
|
int ret;
|
|
|
|
ret = at91_pwm_calculate_cprd_and_pres(dev, priv->clkrate, period_ns,
|
|
duty_ns, &cprd, &pres);
|
|
if (ret)
|
|
return ret;
|
|
|
|
at91_pwm_calculate_cdty(period_ns, duty_ns, priv->clkrate, cprd, pres, &cdty);
|
|
|
|
/* disable the channel */
|
|
channel_enabled = at91_pwm_set(priv->base, channel, false);
|
|
|
|
/* It is necessary to preserve CPOL, inside CMR */
|
|
val = ioread32(priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
|
|
val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
|
|
iowrite32(val, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
|
|
|
|
iowrite32(cprd, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CPRD);
|
|
|
|
iowrite32(cdty, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CDTY);
|
|
|
|
/* renable the channel if needed */
|
|
if (channel_enabled)
|
|
at91_pwm_set(priv->base, channel, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int at91_pwm_set_invert(struct udevice *dev, uint channel,
|
|
bool polarity)
|
|
{
|
|
struct at91_pwm_priv *priv = dev_get_priv(dev);
|
|
u32 val;
|
|
|
|
val = ioread32(priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
|
|
if (polarity)
|
|
val |= PWM_CMR_CPOL;
|
|
else
|
|
val &= ~PWM_CMR_CPOL;
|
|
iowrite32(val, priv->base + (channel * PWM_CHANNEL_OFFSET) + PWM_CMR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int at91_pwm_probe(struct udevice *dev)
|
|
{
|
|
struct at91_pwm_priv *priv = dev_get_priv(dev);
|
|
int ret;
|
|
|
|
priv->base = dev_read_addr_ptr(dev);
|
|
if (!priv->base)
|
|
return -EINVAL;
|
|
|
|
ret = clk_get_by_index(dev, 0, &priv->pclk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* clocks aren't ref-counted so just enabled them once here */
|
|
ret = clk_enable(&priv->pclk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
priv->clkrate = clk_get_rate(&priv->pclk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct pwm_ops at91_pwm_ops = {
|
|
.set_config = at91_pwm_set_config,
|
|
.set_enable = at91_pwm_set_enable,
|
|
.set_invert = at91_pwm_set_invert,
|
|
};
|
|
|
|
static const struct udevice_id at91_pwm_of_match[] = {
|
|
{ .compatible = "atmel,sama5d2-pwm" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(at91_pwm) = {
|
|
.name = "at91_pwm",
|
|
.id = UCLASS_PWM,
|
|
.of_match = at91_pwm_of_match,
|
|
.probe = at91_pwm_probe,
|
|
.priv_auto = sizeof(struct at91_pwm_priv),
|
|
.ops = &at91_pwm_ops,
|
|
};
|