You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
11 KiB
433 lines
11 KiB
Armada 370/XP devices can 'blink' gpio lines with a configurable on
|
|
and off period. This can be modelled as a PWM.
|
|
|
|
However, there are only two sets of PWM configuration registers for
|
|
all the gpio lines. This driver simply allows a single gpio line per
|
|
gpio chip of 32 lines to be used as a PWM. Attempts to use more return
|
|
EBUSY.
|
|
|
|
Due to the interleaving of registers it is not simple to separate the
|
|
PWM driver from the gpio driver. Thus the gpio driver has been
|
|
extended with a PWM driver.
|
|
|
|
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
|
|
---
|
|
drivers/gpio/Kconfig | 5 ++
|
|
drivers/gpio/Makefile | 1 +
|
|
drivers/gpio/gpio-mvebu-pwm.c | 202 ++++++++++++++++++++++++++++++++++++++++++
|
|
drivers/gpio/gpio-mvebu.c | 37 +++-----
|
|
drivers/gpio/gpio-mvebu.h | 79 +++++++++++++++++
|
|
5 files changed, 299 insertions(+), 25 deletions(-)
|
|
create mode 100644 drivers/gpio/gpio-mvebu-pwm.c
|
|
create mode 100644 drivers/gpio/gpio-mvebu.h
|
|
|
|
--- a/drivers/gpio/Kconfig
|
|
+++ b/drivers/gpio/Kconfig
|
|
@@ -295,6 +295,11 @@ config GPIO_MVEBU
|
|
depends on OF
|
|
select GENERIC_IRQ_CHIP
|
|
|
|
+config GPIO_MVEBU_PWM
|
|
+ def_bool y
|
|
+ depends on GPIO_MVEBU
|
|
+ depends on PWM
|
|
+
|
|
config GPIO_MXC
|
|
def_bool y
|
|
depends on ARCH_MXC
|
|
--- a/drivers/gpio/Makefile
|
|
+++ b/drivers/gpio/Makefile
|
|
@@ -67,6 +67,7 @@ obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc52
|
|
obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o
|
|
obj-$(CONFIG_GPIO_MSIC) += gpio-msic.o
|
|
obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o
|
|
+obj-$(CONFIG_GPIO_MVEBU_PWM) += gpio-mvebu-pwm.o
|
|
obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
|
|
obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
|
|
obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
|
|
--- /dev/null
|
|
+++ b/drivers/gpio/gpio-mvebu-pwm.c
|
|
@@ -0,0 +1,202 @@
|
|
+#include <linux/err.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/pwm.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include "gpio-mvebu.h"
|
|
+#include "gpiolib.h"
|
|
+
|
|
+static void __iomem *mvebu_gpioreg_blink_select(struct mvebu_gpio_chip *mvchip)
|
|
+{
|
|
+ return mvchip->membase + GPIO_BLINK_CNT_SELECT;
|
|
+}
|
|
+
|
|
+static inline struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip)
|
|
+{
|
|
+ return container_of(chip, struct mvebu_pwm, chip);
|
|
+}
|
|
+
|
|
+static inline struct mvebu_gpio_chip *to_mvchip(struct mvebu_pwm *pwm)
|
|
+{
|
|
+ return container_of(pwm, struct mvebu_gpio_chip, pwm);
|
|
+}
|
|
+
|
|
+static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwmd)
|
|
+{
|
|
+ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
|
|
+ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
|
|
+ struct gpio_desc *desc = gpio_to_desc(pwmd->pwm);
|
|
+ unsigned long flags;
|
|
+ int ret = 0;
|
|
+
|
|
+ spin_lock_irqsave(&pwm->lock, flags);
|
|
+ if (pwm->used) {
|
|
+ ret = -EBUSY;
|
|
+ } else {
|
|
+ if (!desc) {
|
|
+ ret = -ENODEV;
|
|
+ goto out;
|
|
+ }
|
|
+ ret = gpiod_request(desc, "mvebu-pwm");
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ ret = gpiod_direction_output(desc, 0);
|
|
+ if (ret) {
|
|
+ gpiod_free(desc);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ pwm->pin = pwmd->pwm - mvchip->chip.base;
|
|
+ pwm->used = true;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ spin_unlock_irqrestore(&pwm->lock, flags);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwmd)
|
|
+{
|
|
+ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
|
|
+ struct gpio_desc *desc = gpio_to_desc(pwmd->pwm);
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&pwm->lock, flags);
|
|
+ gpiod_free(desc);
|
|
+ pwm->used = false;
|
|
+ spin_unlock_irqrestore(&pwm->lock, flags);
|
|
+}
|
|
+
|
|
+static int mvebu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwmd,
|
|
+ int duty_ns, int period_ns)
|
|
+{
|
|
+ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
|
|
+ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
|
|
+ unsigned int on, off;
|
|
+ unsigned long long val;
|
|
+ u32 u;
|
|
+
|
|
+ val = (unsigned long long) pwm->clk_rate * duty_ns;
|
|
+ do_div(val, NSEC_PER_SEC);
|
|
+ if (val > UINT_MAX)
|
|
+ return -EINVAL;
|
|
+ if (val)
|
|
+ on = val;
|
|
+ else
|
|
+ on = 1;
|
|
+
|
|
+ val = (unsigned long long) pwm->clk_rate * (period_ns - duty_ns);
|
|
+ do_div(val, NSEC_PER_SEC);
|
|
+ if (val > UINT_MAX)
|
|
+ return -EINVAL;
|
|
+ if (val)
|
|
+ off = val;
|
|
+ else
|
|
+ off = 1;
|
|
+
|
|
+ u = readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
|
|
+ u &= ~(1 << pwm->pin);
|
|
+ u |= (pwm->id << pwm->pin);
|
|
+ writel_relaxed(u, mvebu_gpioreg_blink_select(mvchip));
|
|
+
|
|
+ writel_relaxed(on, pwm->membase + BLINK_ON_DURATION);
|
|
+ writel_relaxed(off, pwm->membase + BLINK_OFF_DURATION);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mvebu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwmd)
|
|
+{
|
|
+ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
|
|
+ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
|
|
+
|
|
+ mvebu_gpio_blink(&mvchip->chip, pwm->pin, 1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void mvebu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwmd)
|
|
+{
|
|
+ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
|
|
+ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
|
|
+
|
|
+ mvebu_gpio_blink(&mvchip->chip, pwm->pin, 0);
|
|
+}
|
|
+
|
|
+static const struct pwm_ops mvebu_pwm_ops = {
|
|
+ .request = mvebu_pwm_request,
|
|
+ .free = mvebu_pwm_free,
|
|
+ .config = mvebu_pwm_config,
|
|
+ .enable = mvebu_pwm_enable,
|
|
+ .disable = mvebu_pwm_disable,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
|
|
+{
|
|
+ struct mvebu_pwm *pwm = &mvchip->pwm;
|
|
+
|
|
+ pwm->blink_select = readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
|
|
+ pwm->blink_on_duration =
|
|
+ readl_relaxed(pwm->membase + BLINK_ON_DURATION);
|
|
+ pwm->blink_off_duration =
|
|
+ readl_relaxed(pwm->membase + BLINK_OFF_DURATION);
|
|
+}
|
|
+
|
|
+void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
|
|
+{
|
|
+ struct mvebu_pwm *pwm = &mvchip->pwm;
|
|
+
|
|
+ writel_relaxed(pwm->blink_select, mvebu_gpioreg_blink_select(mvchip));
|
|
+ writel_relaxed(pwm->blink_on_duration,
|
|
+ pwm->membase + BLINK_ON_DURATION);
|
|
+ writel_relaxed(pwm->blink_off_duration,
|
|
+ pwm->membase + BLINK_OFF_DURATION);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Armada 370/XP has simple PWM support for gpio lines. Other SoCs
|
|
+ * don't have this hardware. So if we don't have the necessary
|
|
+ * resource, it is not an error.
|
|
+ */
|
|
+int mvebu_pwm_probe(struct platform_device *pdev,
|
|
+ struct mvebu_gpio_chip *mvchip,
|
|
+ int id)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct mvebu_pwm *pwm = &mvchip->pwm;
|
|
+ struct resource *res;
|
|
+
|
|
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
|
|
+ if (!res)
|
|
+ return 0;
|
|
+
|
|
+ mvchip->pwm.membase = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(mvchip->pwm.membase))
|
|
+ return PTR_ERR(mvchip->percpu_membase);
|
|
+
|
|
+ if (id < 0 || id > 1)
|
|
+ return -EINVAL;
|
|
+ pwm->id = id;
|
|
+
|
|
+ if (IS_ERR(mvchip->clk))
|
|
+ return PTR_ERR(mvchip->clk);
|
|
+
|
|
+ pwm->clk_rate = clk_get_rate(mvchip->clk);
|
|
+ if (!pwm->clk_rate) {
|
|
+ dev_err(dev, "failed to get clock rate\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ pwm->chip.dev = dev;
|
|
+ pwm->chip.ops = &mvebu_pwm_ops;
|
|
+ pwm->chip.base = mvchip->chip.base;
|
|
+ pwm->chip.npwm = mvchip->chip.ngpio;
|
|
+ pwm->chip.can_sleep = false;
|
|
+
|
|
+ spin_lock_init(&pwm->lock);
|
|
+
|
|
+ return pwmchip_add(&pwm->chip);
|
|
+}
|
|
--- a/drivers/gpio/gpio-mvebu.c
|
|
+++ b/drivers/gpio/gpio-mvebu.c
|
|
@@ -42,10 +42,11 @@
|
|
#include <linux/io.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_device.h>
|
|
+#include <linux/pwm.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/irqchip/chained_irq.h>
|
|
-
|
|
+#include "gpio-mvebu.h"
|
|
/*
|
|
* GPIO unit register offsets.
|
|
*/
|
|
@@ -75,24 +76,6 @@
|
|
|
|
#define MVEBU_MAX_GPIO_PER_BANK 32
|
|
|
|
-struct mvebu_gpio_chip {
|
|
- struct gpio_chip chip;
|
|
- spinlock_t lock;
|
|
- void __iomem *membase;
|
|
- void __iomem *percpu_membase;
|
|
- int irqbase;
|
|
- struct irq_domain *domain;
|
|
- int soc_variant;
|
|
-
|
|
- /* Used to preserve GPIO registers across suspend/resume */
|
|
- u32 out_reg;
|
|
- u32 io_conf_reg;
|
|
- u32 blink_en_reg;
|
|
- u32 in_pol_reg;
|
|
- u32 edge_mask_regs[4];
|
|
- u32 level_mask_regs[4];
|
|
-};
|
|
-
|
|
/*
|
|
* Functions returning addresses of individual registers for a given
|
|
* GPIO controller.
|
|
@@ -218,7 +201,7 @@ static int mvebu_gpio_get(struct gpio_ch
|
|
return (u >> pin) & 1;
|
|
}
|
|
|
|
-static void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value)
|
|
+void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value)
|
|
{
|
|
struct mvebu_gpio_chip *mvchip =
|
|
container_of(chip, struct mvebu_gpio_chip, chip);
|
|
@@ -607,6 +590,8 @@ static int mvebu_gpio_suspend(struct pla
|
|
BUG();
|
|
}
|
|
|
|
+ mvebu_pwm_suspend(mvchip);
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -650,6 +635,8 @@ static int mvebu_gpio_resume(struct plat
|
|
BUG();
|
|
}
|
|
|
|
+ mvebu_pwm_resume(mvchip);
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -661,7 +648,6 @@ static int mvebu_gpio_probe(struct platf
|
|
struct resource *res;
|
|
struct irq_chip_generic *gc;
|
|
struct irq_chip_type *ct;
|
|
- struct clk *clk;
|
|
unsigned int ngpios;
|
|
int soc_variant;
|
|
int i, cpu, id;
|
|
@@ -691,10 +677,10 @@ static int mvebu_gpio_probe(struct platf
|
|
return id;
|
|
}
|
|
|
|
- clk = devm_clk_get(&pdev->dev, NULL);
|
|
+ mvchip->clk = devm_clk_get(&pdev->dev, NULL);
|
|
/* Not all SoCs require a clock.*/
|
|
- if (!IS_ERR(clk))
|
|
- clk_prepare_enable(clk);
|
|
+ if (!IS_ERR(mvchip->clk))
|
|
+ clk_prepare_enable(mvchip->clk);
|
|
|
|
mvchip->soc_variant = soc_variant;
|
|
mvchip->chip.label = dev_name(&pdev->dev);
|
|
@@ -828,7 +814,8 @@ static int mvebu_gpio_probe(struct platf
|
|
goto err_generic_chip;
|
|
}
|
|
|
|
- return 0;
|
|
+ /* Armada 370/XP has simple PWM support for gpio lines */
|
|
+ return mvebu_pwm_probe(pdev, mvchip, id);
|
|
|
|
err_generic_chip:
|
|
irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST,
|
|
--- /dev/null
|
|
+++ b/drivers/gpio/gpio-mvebu.h
|
|
@@ -0,0 +1,79 @@
|
|
+/*
|
|
+ * Interface between MVEBU GPIO driver and PWM driver for GPIO pins
|
|
+ *
|
|
+ * Copyright (C) 2015, Andrew Lunn <andrew@lunn.ch>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License version 2 as
|
|
+ * published by the Free Software Foundation.
|
|
+ */
|
|
+
|
|
+#ifndef MVEBU_GPIO_PWM_H
|
|
+#define MVEBU_GPIO_PWM_H
|
|
+
|
|
+#define BLINK_ON_DURATION 0x0
|
|
+#define BLINK_OFF_DURATION 0x4
|
|
+#define GPIO_BLINK_CNT_SELECT 0x0020
|
|
+
|
|
+struct mvebu_pwm {
|
|
+ void __iomem *membase;
|
|
+ unsigned long clk_rate;
|
|
+ bool used;
|
|
+ unsigned pin;
|
|
+ struct pwm_chip chip;
|
|
+ int id;
|
|
+ spinlock_t lock;
|
|
+
|
|
+ /* Used to preserve GPIO/PWM registers across suspend /
|
|
+ * resume */
|
|
+ u32 blink_select;
|
|
+ u32 blink_on_duration;
|
|
+ u32 blink_off_duration;
|
|
+};
|
|
+
|
|
+struct mvebu_gpio_chip {
|
|
+ struct gpio_chip chip;
|
|
+ spinlock_t lock;
|
|
+ void __iomem *membase;
|
|
+ void __iomem *percpu_membase;
|
|
+ int irqbase;
|
|
+ struct irq_domain *domain;
|
|
+ int soc_variant;
|
|
+ struct clk *clk;
|
|
+#ifdef CONFIG_PWM
|
|
+ struct mvebu_pwm pwm;
|
|
+#endif
|
|
+ /* Used to preserve GPIO registers across suspend/resume */
|
|
+ u32 out_reg;
|
|
+ u32 io_conf_reg;
|
|
+ u32 blink_en_reg;
|
|
+ u32 in_pol_reg;
|
|
+ u32 edge_mask_regs[4];
|
|
+ u32 level_mask_regs[4];
|
|
+};
|
|
+
|
|
+void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value);
|
|
+
|
|
+#ifdef CONFIG_PWM
|
|
+int mvebu_pwm_probe(struct platform_device *pdev,
|
|
+ struct mvebu_gpio_chip *mvchip,
|
|
+ int id);
|
|
+void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip);
|
|
+void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip);
|
|
+#else
|
|
+int mvebu_pwm_probe(struct platform_device *pdev,
|
|
+ struct mvebu_gpio_chip *mvchip,
|
|
+ int id)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
|
|
+{
|
|
+}
|
|
+
|
|
+void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
|
|
+{
|
|
+}
|
|
+#endif
|
|
+#endif
|
|
|