From 5c2dd4cd7a41ec971a23eec9e993717e5aed8744 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 21 Feb 2016 21:08:49 -0700 Subject: [PATCH] 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 Signed-off-by: Minkyu Kang --- arch/arm/cpu/armv7/s5p-common/timer.c | 3 + drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/exynos_pwm.c | 120 ++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 drivers/pwm/exynos_pwm.c diff --git a/arch/arm/cpu/armv7/s5p-common/timer.c b/arch/arm/cpu/armv7/s5p-common/timer.c index 949abb1c8f..b63036c64e 100644 --- a/arch/arm/cpu/armv7/s5p-common/timer.c +++ b/arch/arm/cpu/armv7/s5p-common/timer.c @@ -12,6 +12,9 @@ #include #include #include + +/* Use the old PWM interface for now */ +#undef CONFIG_DM_PWM #include DECLARE_GLOBAL_DATA_PTR; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f0d61e7ab..37ea2b88ea 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -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 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index fd414b1893..af39347aac 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -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 diff --git a/drivers/pwm/exynos_pwm.c b/drivers/pwm/exynos_pwm.c new file mode 100644 index 0000000000..a0edafce40 --- /dev/null +++ b/drivers/pwm/exynos_pwm.c @@ -0,0 +1,120 @@ +/* + * Copyright 2016 Google Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include + +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), +};