mvebu: gpio based pwm support

Signed-off-by: Imre Kaloz <kaloz@openwrt.org>

SVN-Revision: 44129
This commit is contained in:
Imre Kaloz 2015-01-25 15:36:47 +00:00
parent 9a172c70eb
commit d9c6ff8c25
8 changed files with 969 additions and 0 deletions

View file

@ -142,6 +142,7 @@ CONFIG_GPIOLIB=y
CONFIG_GPIO_DEVRES=y CONFIG_GPIO_DEVRES=y
CONFIG_GPIO_GENERIC=y CONFIG_GPIO_GENERIC=y
CONFIG_GPIO_MVEBU=y CONFIG_GPIO_MVEBU=y
CONFIG_GPIO_MVEBU_PWM=y
CONFIG_GPIO_SYSFS=y CONFIG_GPIO_SYSFS=y
CONFIG_HANDLE_DOMAIN_IRQ=y CONFIG_HANDLE_DOMAIN_IRQ=y
CONFIG_HARDIRQS_SW_RESEND=y CONFIG_HARDIRQS_SW_RESEND=y
@ -291,6 +292,8 @@ CONFIG_PJ4B_ERRATA_4742=y
# CONFIG_PL310_ERRATA_769419 is not set # CONFIG_PL310_ERRATA_769419 is not set
CONFIG_PLAT_ORION=y CONFIG_PLAT_ORION=y
CONFIG_PM_OPP=y CONFIG_PM_OPP=y
CONFIG_PWM=y
# CONFIG_PWM_FSL_FTM is not set
# CONFIG_PREEMPT_RCU is not set # CONFIG_PREEMPT_RCU is not set
CONFIG_RCU_STALL_COMMON=y CONFIG_RCU_STALL_COMMON=y
CONFIG_RFS_ACCEL=y CONFIG_RFS_ACCEL=y

View file

@ -0,0 +1,223 @@
Wrap some long lines.
Prefer seq_puts() over seq_printf().
space to tab conversions.
Spelling error fix.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/gpio/gpio-mvebu.c | 77 ++++++++++++++++++++++++++---------------------
1 file changed, 42 insertions(+), 35 deletions(-)
--- a/drivers/gpio/gpio-mvebu.c
+++ b/drivers/gpio/gpio-mvebu.c
@@ -59,7 +59,7 @@
#define GPIO_LEVEL_MASK_OFF 0x001c
/* The MV78200 has per-CPU registers for edge mask and level mask */
-#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18)
+#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18)
#define GPIO_LEVEL_MASK_MV78200_OFF(cpu) ((cpu) ? 0x34 : 0x1C)
/* The Armada XP has per-CPU registers for interrupt cause, interrupt
@@ -69,11 +69,11 @@
#define GPIO_EDGE_MASK_ARMADAXP_OFF(cpu) (0x10 + (cpu) * 0x4)
#define GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu) (0x20 + (cpu) * 0x4)
-#define MVEBU_GPIO_SOC_VARIANT_ORION 0x1
-#define MVEBU_GPIO_SOC_VARIANT_MV78200 0x2
+#define MVEBU_GPIO_SOC_VARIANT_ORION 0x1
+#define MVEBU_GPIO_SOC_VARIANT_MV78200 0x2
#define MVEBU_GPIO_SOC_VARIANT_ARMADAXP 0x3
-#define MVEBU_MAX_GPIO_PER_BANK 32
+#define MVEBU_MAX_GPIO_PER_BANK 32
struct mvebu_gpio_chip {
struct gpio_chip chip;
@@ -82,9 +82,9 @@ struct mvebu_gpio_chip {
void __iomem *percpu_membase;
int irqbase;
struct irq_domain *domain;
- int soc_variant;
+ int soc_variant;
- /* Used to preserve GPIO registers accross suspend/resume */
+ /* Used to preserve GPIO registers across suspend/resume */
u32 out_reg;
u32 io_conf_reg;
u32 blink_en_reg;
@@ -107,7 +107,8 @@ static inline void __iomem *mvebu_gpiore
return mvchip->membase + GPIO_BLINK_EN_OFF;
}
-static inline void __iomem *mvebu_gpioreg_io_conf(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_io_conf(struct mvebu_gpio_chip *mvchip)
{
return mvchip->membase + GPIO_IO_CONF_OFF;
}
@@ -117,12 +118,14 @@ static inline void __iomem *mvebu_gpiore
return mvchip->membase + GPIO_IN_POL_OFF;
}
-static inline void __iomem *mvebu_gpioreg_data_in(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_data_in(struct mvebu_gpio_chip *mvchip)
{
return mvchip->membase + GPIO_DATA_IN_OFF;
}
-static inline void __iomem *mvebu_gpioreg_edge_cause(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_edge_cause(struct mvebu_gpio_chip *mvchip)
{
int cpu;
@@ -132,13 +135,15 @@ static inline void __iomem *mvebu_gpiore
return mvchip->membase + GPIO_EDGE_CAUSE_OFF;
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
cpu = smp_processor_id();
- return mvchip->percpu_membase + GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu);
+ return mvchip->percpu_membase +
+ GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu);
default:
BUG();
}
}
-static inline void __iomem *mvebu_gpioreg_edge_mask(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_edge_mask(struct mvebu_gpio_chip *mvchip)
{
int cpu;
@@ -150,7 +155,8 @@ static inline void __iomem *mvebu_gpiore
return mvchip->membase + GPIO_EDGE_MASK_MV78200_OFF(cpu);
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
cpu = smp_processor_id();
- return mvchip->percpu_membase + GPIO_EDGE_MASK_ARMADAXP_OFF(cpu);
+ return mvchip->percpu_membase +
+ GPIO_EDGE_MASK_ARMADAXP_OFF(cpu);
default:
BUG();
}
@@ -168,7 +174,8 @@ static void __iomem *mvebu_gpioreg_level
return mvchip->membase + GPIO_LEVEL_MASK_MV78200_OFF(cpu);
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
cpu = smp_processor_id();
- return mvchip->percpu_membase + GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu);
+ return mvchip->percpu_membase +
+ GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu);
default:
BUG();
}
@@ -364,22 +371,22 @@ static void mvebu_gpio_level_irq_unmask(
* value of the line or the opposite value.
*
* Level IRQ handlers: DATA_IN is used directly as cause register.
- * Interrupt are masked by LEVEL_MASK registers.
+ * Interrupt are masked by LEVEL_MASK registers.
* Edge IRQ handlers: Change in DATA_IN are latched in EDGE_CAUSE.
- * Interrupt are masked by EDGE_MASK registers.
+ * Interrupt are masked by EDGE_MASK registers.
* Both-edge handlers: Similar to regular Edge handlers, but also swaps
- * the polarity to catch the next line transaction.
- * This is a race condition that might not perfectly
- * work on some use cases.
+ * the polarity to catch the next line transaction.
+ * This is a race condition that might not perfectly
+ * work on some use cases.
*
* Every eight GPIO lines are grouped (OR'ed) before going up to main
* cause register.
*
- * EDGE cause mask
- * data-in /--------| |-----| |----\
- * -----| |----- ---- to main cause reg
- * X \----------------| |----/
- * polarity LEVEL mask
+ * EDGE cause mask
+ * data-in /--------| |-----| |----\
+ * -----| |----- ---- to main cause reg
+ * X \----------------| |----/
+ * polarity LEVEL mask
*
****************************************************************************/
@@ -394,9 +401,8 @@ static int mvebu_gpio_irq_set_type(struc
pin = d->hwirq;
u = readl_relaxed(mvebu_gpioreg_io_conf(mvchip)) & (1 << pin);
- if (!u) {
+ if (!u)
return -EINVAL;
- }
type &= IRQ_TYPE_SENSE_MASK;
if (type == IRQ_TYPE_NONE)
@@ -529,13 +535,13 @@ static void mvebu_gpio_dbg_show(struct s
(data_in ^ in_pol) & msk ? "hi" : "lo",
in_pol & msk ? "lo" : "hi");
if (!((edg_msk | lvl_msk) & msk)) {
- seq_printf(s, " disabled\n");
+ seq_puts(s, " disabled\n");
continue;
}
if (edg_msk & msk)
- seq_printf(s, " edge ");
+ seq_puts(s, " edge ");
if (lvl_msk & msk)
- seq_printf(s, " level");
+ seq_puts(s, " level");
seq_printf(s, " (%s)\n", cause & msk ? "pending" : "clear ");
}
}
@@ -546,15 +552,15 @@ static void mvebu_gpio_dbg_show(struct s
static const struct of_device_id mvebu_gpio_of_match[] = {
{
.compatible = "marvell,orion-gpio",
- .data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
},
{
.compatible = "marvell,mv78200-gpio",
- .data = (void *) MVEBU_GPIO_SOC_VARIANT_MV78200,
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_MV78200,
},
{
.compatible = "marvell,armadaxp-gpio",
- .data = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP,
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP,
},
{
/* sentinel */
@@ -668,7 +674,8 @@ static int mvebu_gpio_probe(struct platf
else
soc_variant = MVEBU_GPIO_SOC_VARIANT_ORION;
- mvchip = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_gpio_chip), GFP_KERNEL);
+ mvchip = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_gpio_chip),
+ GFP_KERNEL);
if (!mvchip)
return -ENOMEM;
@@ -767,8 +774,8 @@ static int mvebu_gpio_probe(struct platf
* interrupt handlers, with each handler dealing with 8 GPIO
* pins. */
for (i = 0; i < 4; i++) {
- int irq;
- irq = platform_get_irq(pdev, i);
+ int irq = platform_get_irq(pdev, i);
+
if (irq < 0)
continue;
irq_set_handler_data(irq, mvchip);
@@ -827,7 +834,7 @@ static int mvebu_gpio_probe(struct platf
static struct platform_driver mvebu_gpio_driver = {
.driver = {
- .name = "mvebu-gpio",
+ .name = "mvebu-gpio",
.of_match_table = mvebu_gpio_of_match,
},
.probe = mvebu_gpio_probe,

View file

@ -0,0 +1,63 @@
Ensure that when there is an error during probe that the gpiochip is
removed and the generic irq chip is removed.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/gpio/gpio-mvebu.c | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
--- a/drivers/gpio/gpio-mvebu.c
+++ b/drivers/gpio/gpio-mvebu.c
@@ -667,6 +667,7 @@ static int mvebu_gpio_probe(struct platf
unsigned int ngpios;
int soc_variant;
int i, cpu, id;
+ int err;
match = of_match_device(mvebu_gpio_of_match, &pdev->dev);
if (match)
@@ -785,14 +786,16 @@ static int mvebu_gpio_probe(struct platf
mvchip->irqbase = irq_alloc_descs(-1, 0, ngpios, -1);
if (mvchip->irqbase < 0) {
dev_err(&pdev->dev, "no irqs\n");
- return mvchip->irqbase;
+ err = mvchip->irqbase;
+ goto err_gpiochip_add;
}
gc = irq_alloc_generic_chip("mvebu_gpio_irq", 2, mvchip->irqbase,
mvchip->membase, handle_level_irq);
if (!gc) {
dev_err(&pdev->dev, "Cannot allocate generic irq_chip\n");
- return -ENOMEM;
+ err = -ENOMEM;
+ goto err_gpiochip_add;
}
gc->private = mvchip;
@@ -823,13 +826,21 @@ static int mvebu_gpio_probe(struct platf
if (!mvchip->domain) {
dev_err(&pdev->dev, "couldn't allocate irq domain %s (DT).\n",
mvchip->chip.label);
- irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST,
- IRQ_LEVEL | IRQ_NOPROBE);
- kfree(gc);
- return -ENODEV;
+ err = -ENODEV;
+ goto err_generic_chip;
}
return 0;
+
+err_generic_chip:
+ irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST,
+ IRQ_LEVEL | IRQ_NOPROBE);
+ kfree(gc);
+
+err_gpiochip_add:
+ gpiochip_remove(&mvchip->chip);
+
+ return err;
}
static struct platform_driver mvebu_gpio_driver = {

View file

@ -0,0 +1,433 @@
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
@@ -223,6 +223,11 @@ config GPIO_MVEBU
select GPIO_GENERIC
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
@@ -58,6 +58,7 @@ obj-$(CONFIG_GPIO_MSIC) += gpio-msic.o
obj-$(CONFIG_GPIO_MSM_V1) += gpio-msm-v1.o
obj-$(CONFIG_GPIO_MSM_V2) += gpio-msm-v2.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 "asm/io.h"
+#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.
@@ -228,7 +211,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);
@@ -609,6 +592,8 @@ static int mvebu_gpio_suspend(struct pla
BUG();
}
+ mvebu_pwm_suspend(mvchip);
+
return 0;
}
@@ -652,6 +637,8 @@ static int mvebu_gpio_resume(struct plat
BUG();
}
+ mvebu_pwm_resume(mvchip);
+
return 0;
}
@@ -663,7 +650,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;
@@ -693,10 +679,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);
@@ -830,7 +816,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

View file

@ -0,0 +1,52 @@
Document the optional parameters needed for PWM operation of gpio
lines.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
.../devicetree/bindings/gpio/gpio-mvebu.txt | 31 ++++++++++++++++++++++
1 file changed, 31 insertions(+)
--- a/Documentation/devicetree/bindings/gpio/gpio-mvebu.txt
+++ b/Documentation/devicetree/bindings/gpio/gpio-mvebu.txt
@@ -38,6 +38,23 @@ Required properties:
- #gpio-cells: Should be two. The first cell is the pin number. The
second cell is reserved for flags, unused at the moment.
+Optional properties:
+
+In order to use the gpio lines in PWM mode, some additional optional
+properties are required. Only Armada 370 and XP supports these
+properties.
+
+- reg: an additional register set is needed, for the GPIO Blink
+ Counter on/off registers.
+
+- reg-names: Must contain an entry "pwm" corresponding to the
+ additional register range needed for pwm operation.
+
+- #pwm-cells: Should be two. The first cell is the pin number. The
+ second cell is reserved for flags, unused at the moment.
+
+- clocks: Must be a phandle to the clock for the gpio controller.
+
Example:
gpio0: gpio@d0018100 {
@@ -51,3 +68,17 @@ Example:
#interrupt-cells = <2>;
interrupts = <16>, <17>, <18>, <19>;
};
+
+ gpio1: gpio@18140 {
+ compatible = "marvell,orion-gpio";
+ reg = <0x18140 0x40>, <0x181c8 0x08>;
+ reg-names = "gpio", "pwm";
+ ngpios = <17>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <87>, <88>, <89>;
+ clocks = <&coreclk 0>;
+ };

View file

@ -0,0 +1,149 @@
Add properties to the gpio nodes to allow them to be also used
as pwm lines.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
arch/arm/boot/dts/armada-370.dtsi | 10 ++++++++--
arch/arm/boot/dts/armada-xp-mv78230.dtsi | 10 ++++++++--
arch/arm/boot/dts/armada-xp-mv78260.dtsi | 8 ++++++--
arch/arm/boot/dts/armada-xp-mv78460.dtsi | 10 ++++++++--
4 files changed, 30 insertions(+), 8 deletions(-)
--- a/arch/arm/boot/dts/armada-370.dtsi
+++ b/arch/arm/boot/dts/armada-370.dtsi
@@ -109,24 +109,30 @@
gpio0: gpio@18100 {
compatible = "marvell,orion-gpio";
- reg = <0x18100 0x40>;
+ reg = <0x18100 0x40>, <0x181c0 0x08>;
+ reg-names = "gpio", "pwm";
ngpios = <32>;
gpio-controller;
#gpio-cells = <2>;
+ #pwm-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <82>, <83>, <84>, <85>;
+ clocks = <&coreclk 0>;
};
gpio1: gpio@18140 {
compatible = "marvell,orion-gpio";
- reg = <0x18140 0x40>;
+ reg = <0x18140 0x40>, <0x181c8 0x08>;
+ reg-names = "gpio", "pwm";
ngpios = <32>;
gpio-controller;
#gpio-cells = <2>;
+ #pwm-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <87>, <88>, <89>, <90>;
+ clocks = <&coreclk 0>;
};
gpio2: gpio@18180 {
--- a/arch/arm/boot/dts/armada-xp-mv78230.dtsi
+++ b/arch/arm/boot/dts/armada-xp-mv78230.dtsi
@@ -169,24 +169,30 @@
internal-regs {
gpio0: gpio@18100 {
compatible = "marvell,orion-gpio";
- reg = <0x18100 0x40>;
+ reg = <0x18100 0x40>, <0x181c0 0x08>;
+ reg-names = "gpio", "pwm";
ngpios = <32>;
gpio-controller;
#gpio-cells = <2>;
+ #pwm-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <82>, <83>, <84>, <85>;
+ clocks = <&coreclk 0>;
};
gpio1: gpio@18140 {
compatible = "marvell,orion-gpio";
- reg = <0x18140 0x40>;
+ reg = <0x18140 0x40>, <0x181c8 0x08>;
+ reg-names = "gpio", "pwm";
ngpios = <17>;
gpio-controller;
#gpio-cells = <2>;
+ #pwm-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <87>, <88>, <89>;
+ clocks = <&coreclk 0>;
};
};
};
--- a/arch/arm/boot/dts/armada-xp-mv78260.dtsi
+++ b/arch/arm/boot/dts/armada-xp-mv78260.dtsi
@@ -253,24 +253,28 @@
internal-regs {
gpio0: gpio@18100 {
compatible = "marvell,orion-gpio";
- reg = <0x18100 0x40>;
+ reg = <0x18100 0x40>, <0x181c0 0x08>;
+ reg-names = "gpio", "pwm";
ngpios = <32>;
gpio-controller;
#gpio-cells = <2>;
+ #pwm-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <82>, <83>, <84>, <85>;
+ clocks = <&coreclk 0>;
};
gpio1: gpio@18140 {
compatible = "marvell,orion-gpio";
- reg = <0x18140 0x40>;
+ reg = <0x18140 0x40>, <0x181c8 0x08>;
ngpios = <32>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <87>, <88>, <89>, <90>;
+ clocks = <&coreclk 0>;
};
gpio2: gpio@18180 {
--- a/arch/arm/boot/dts/armada-xp-mv78460.dtsi
+++ b/arch/arm/boot/dts/armada-xp-mv78460.dtsi
@@ -291,24 +291,30 @@
internal-regs {
gpio0: gpio@18100 {
compatible = "marvell,orion-gpio";
- reg = <0x18100 0x40>;
+ reg = <0x18100 0x40>, <0x181c0 0x08>;
+ reg-names = "gpio", "pwm";
ngpios = <32>;
gpio-controller;
#gpio-cells = <2>;
+ #pwm-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <82>, <83>, <84>, <85>;
+ clocks = <&coreclk 0>;
};
gpio1: gpio@18140 {
compatible = "marvell,orion-gpio";
- reg = <0x18140 0x40>;
+ reg = <0x18140 0x40>, <0x181c8 0x08>;
+ reg-names = "gpio", "pwm";
ngpios = <32>;
gpio-controller;
#gpio-cells = <2>;
+ #pwm-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <87>, <88>, <89>, <90>;
+ clocks = <&coreclk 0>;
};
gpio2: gpio@18180 {

View file

@ -0,0 +1,18 @@
Now that the gpio driver also supports PWM operation, enable
the PWM framework in mvebu_v7_defconfig.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
arch/arm/configs/mvebu_v7_defconfig | 1 +
1 file changed, 1 insertion(+)
--- a/arch/arm/configs/mvebu_v7_defconfig
+++ b/arch/arm/configs/mvebu_v7_defconfig
@@ -109,6 +109,7 @@ CONFIG_DMADEVICES=y
CONFIG_MV_XOR=y
# CONFIG_IOMMU_SUPPORT is not set
CONFIG_MEMORY=y
+CONFIG_PWM=y
CONFIG_EXT4_FS=y
CONFIG_ISO9660_FS=y
CONFIG_JOLIET=y

View file

@ -0,0 +1,28 @@
The mvebu gpio driver can also perform PWM on some pins. Us the
pwm-fan driver to control the fan of the WRT1900AC, giving us fine
grain control over its speed and hence noise.
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
---
arch/arm/boot/dts/armada-xp-wrt1900ac.dts | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
--- a/arch/arm/boot/dts/armada-xp-mamba.dts
+++ b/arch/arm/boot/dts/armada-xp-mamba.dts
@@ -302,13 +302,11 @@
};
};
- gpio_fan {
+ pwm_fan {
/* SUNON HA4010V4-0000-C99 */
- compatible = "gpio-fan";
- gpios = <&gpio0 24 0>;
- gpio-fan,speed-map = <0 0
- 4500 1>;
+ compatible = "pwm-fan";
+ pwms = <&gpio0 24 4000 0>;
};
mvsw61xx {