mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-24 21:54:01 +00:00
pwm: Add PWM driver for SAMA5D2
Add support for the PWM found on the SAMA5D2 family of devices. Signed-off-by: Dan Sneddon <dan.sneddon@microchip.com>
This commit is contained in:
parent
4c1996ce37
commit
9b075973e9
3 changed files with 214 additions and 0 deletions
|
@ -9,6 +9,12 @@ config DM_PWM
|
|||
frequency/period can be controlled along with the proportion of that
|
||||
time that the signal is high.
|
||||
|
||||
config PWM_AT91
|
||||
bool "Enable support for PWM found on AT91 SoC's"
|
||||
depends on DM_PWM && ARCH_AT91
|
||||
help
|
||||
Support for PWM hardware on AT91 based SoC.
|
||||
|
||||
config PWM_CROS_EC
|
||||
bool "Enable support for the Chrome OS EC PWM"
|
||||
depends on DM_PWM
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
obj-$(CONFIG_DM_PWM) += pwm-uclass.o
|
||||
|
||||
obj-$(CONFIG_PWM_AT91) += pwm-at91.o
|
||||
obj-$(CONFIG_PWM_CROS_EC) += cros_ec_pwm.o
|
||||
obj-$(CONFIG_PWM_EXYNOS) += exynos_pwm.o
|
||||
obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o
|
||||
|
|
207
drivers/pwm/pwm-at91.c
Normal file
207
drivers/pwm/pwm-at91.c
Normal file
|
@ -0,0 +1,207 @@
|
|||
// 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,
|
||||
};
|
Loading…
Reference in a new issue