diff options
author | Imre Kaloz <kaloz@openwrt.org> | 2015-01-25 15:37:31 +0000 |
---|---|---|
committer | Imre Kaloz <kaloz@openwrt.org> | 2015-01-25 15:37:31 +0000 |
commit | 18012a41981270d25a4628846470b127467ae028 (patch) | |
tree | 33a046c32e2a3e140c5c2f7dc78dcdf9f4579db7 /target/linux/mvebu/patches-3.19/202-gpio_mvebu_add_limited_pwm_support.patch | |
parent | d9c6ff8c2524c66801d9e1f9eedfc102a6464498 (diff) | |
download | upstream-18012a41981270d25a4628846470b127467ae028.tar.gz upstream-18012a41981270d25a4628846470b127467ae028.tar.bz2 upstream-18012a41981270d25a4628846470b127467ae028.zip |
mvebu: preliminary 3.19 support
Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
SVN-Revision: 44130
Diffstat (limited to 'target/linux/mvebu/patches-3.19/202-gpio_mvebu_add_limited_pwm_support.patch')
-rw-r--r-- | target/linux/mvebu/patches-3.19/202-gpio_mvebu_add_limited_pwm_support.patch | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/target/linux/mvebu/patches-3.19/202-gpio_mvebu_add_limited_pwm_support.patch b/target/linux/mvebu/patches-3.19/202-gpio_mvebu_add_limited_pwm_support.patch new file mode 100644 index 0000000000..19376f82b5 --- /dev/null +++ b/target/linux/mvebu/patches-3.19/202-gpio_mvebu_add_limited_pwm_support.patch @@ -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 +@@ -239,6 +239,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 +@@ -60,6 +60,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 <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 |