mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-24 21:54:01 +00:00
exynos: pwm: Add a driver for the exynos5 PWM
This driver supports the standard PWM API. There are 5 PWMs. Four are used normally and the last is normally used as a timer. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
This commit is contained in:
parent
0c84358cb2
commit
5c2dd4cd7a
4 changed files with 133 additions and 0 deletions
|
@ -12,6 +12,9 @@
|
|||
#include <asm/io.h>
|
||||
#include <asm/arch/pwm.h>
|
||||
#include <asm/arch/clk.h>
|
||||
|
||||
/* Use the old PWM interface for now */
|
||||
#undef CONFIG_DM_PWM
|
||||
#include <pwm.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
|
|
@ -9,6 +9,15 @@ config DM_PWM
|
|||
frequency/period can be controlled along with the proportion of that
|
||||
time that the signal is high.
|
||||
|
||||
config PWM_EXYNOS
|
||||
bool "Enable support for the Exynos PWM"
|
||||
depends on DM_PWM
|
||||
help
|
||||
This PWM is found on Samsung Exynos 5250 and other Samsung SoCs. It
|
||||
supports a programmable period and duty cycle. A 32-bit counter is
|
||||
used. It provides 5 channels which can be independently
|
||||
programmed. Channel 4 (the last) is normally used as a timer.
|
||||
|
||||
config PWM_ROCKCHIP
|
||||
bool "Enable support for the Rockchip PWM"
|
||||
depends on DM_PWM
|
||||
|
|
|
@ -15,4 +15,5 @@ obj-$(CONFIG_PWM_ROCKCHIP) += rk_pwm.o
|
|||
obj-$(CONFIG_PWM_IMX) += pwm-imx.o pwm-imx-util.o
|
||||
ifdef CONFIG_DM_PWM
|
||||
obj-$(CONFIG_PWM_TEGRA) += tegra_pwm.o
|
||||
obj-$(CONFIG_PWM_EXYNOS) += exynos_pwm.o
|
||||
endif
|
||||
|
|
120
drivers/pwm/exynos_pwm.c
Normal file
120
drivers/pwm/exynos_pwm.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <pwm.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/clk.h>
|
||||
#include <asm/arch/clock.h>
|
||||
#include <asm/arch/pwm.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
struct exynos_pwm_priv {
|
||||
struct s5p_timer *regs;
|
||||
};
|
||||
|
||||
static int exynos_pwm_set_config(struct udevice *dev, uint channel,
|
||||
uint period_ns, uint duty_ns)
|
||||
{
|
||||
struct exynos_pwm_priv *priv = dev_get_priv(dev);
|
||||
struct s5p_timer *regs = priv->regs;
|
||||
unsigned int offset, prescaler;
|
||||
uint div = 4, rate, rate_ns;
|
||||
u32 val;
|
||||
u32 tcnt, tcmp, tcon;
|
||||
|
||||
if (channel >= 5)
|
||||
return -EINVAL;
|
||||
debug("%s: Configure '%s' channel %u, period_ns %u, duty_ns %u\n",
|
||||
__func__, dev->name, channel, period_ns, duty_ns);
|
||||
|
||||
val = readl(®s->tcfg0);
|
||||
prescaler = (channel < 2 ? val : (val >> 8)) & 0xff;
|
||||
div = (readl(®s->tcfg1) >> MUX_DIV_SHIFT(channel)) & 0xf;
|
||||
|
||||
rate = get_pwm_clk() / ((prescaler + 1) * (1 << div));
|
||||
debug("%s: pwm_clk %lu, rate %u\n", __func__, get_pwm_clk(), rate);
|
||||
|
||||
if (channel < 4) {
|
||||
rate_ns = 1000000000 / rate;
|
||||
tcnt = period_ns / rate_ns;
|
||||
tcmp = duty_ns / rate_ns;
|
||||
debug("%s: tcnt %u, tcmp %u\n", __func__, tcnt, tcmp);
|
||||
offset = channel * 3;
|
||||
writel(tcnt, ®s->tcntb0 + offset);
|
||||
writel(tcmp, ®s->tcmpb0 + offset);
|
||||
}
|
||||
|
||||
tcon = readl(®s->tcon);
|
||||
tcon |= TCON_UPDATE(channel);
|
||||
if (channel < 4)
|
||||
tcon |= TCON_AUTO_RELOAD(channel);
|
||||
else
|
||||
tcon |= TCON4_AUTO_RELOAD;
|
||||
writel(tcon, ®s->tcon);
|
||||
|
||||
tcon &= ~TCON_UPDATE(channel);
|
||||
writel(tcon, ®s->tcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_pwm_set_enable(struct udevice *dev, uint channel,
|
||||
bool enable)
|
||||
{
|
||||
struct exynos_pwm_priv *priv = dev_get_priv(dev);
|
||||
struct s5p_timer *regs = priv->regs;
|
||||
u32 mask;
|
||||
|
||||
if (channel >= 4)
|
||||
return -EINVAL;
|
||||
debug("%s: Enable '%s' channel %u\n", __func__, dev->name, channel);
|
||||
mask = TCON_START(channel);
|
||||
clrsetbits_le32(®s->tcon, mask, enable ? mask : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_pwm_probe(struct udevice *dev)
|
||||
{
|
||||
struct exynos_pwm_priv *priv = dev_get_priv(dev);
|
||||
struct s5p_timer *regs = priv->regs;
|
||||
|
||||
writel(PRESCALER_0 | PRESCALER_1 << 8, ®s->tcfg0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_pwm_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct exynos_pwm_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->regs = (struct s5p_timer *)dev_get_addr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops exynos_pwm_ops = {
|
||||
.set_config = exynos_pwm_set_config,
|
||||
.set_enable = exynos_pwm_set_enable,
|
||||
};
|
||||
|
||||
static const struct udevice_id exynos_channels[] = {
|
||||
{ .compatible = "samsung,exynos4210-pwm" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(exynos_pwm) = {
|
||||
.name = "exynos_pwm",
|
||||
.id = UCLASS_PWM,
|
||||
.of_match = exynos_channels,
|
||||
.ops = &exynos_pwm_ops,
|
||||
.probe = exynos_pwm_probe,
|
||||
.ofdata_to_platdata = exynos_pwm_ofdata_to_platdata,
|
||||
.priv_auto_alloc_size = sizeof(struct exynos_pwm_priv),
|
||||
};
|
Loading…
Reference in a new issue