arch/arm/boot/dts/armada-38x.dtsi | 16 +- drivers/gpio/gpio-mvebu.c | 369 +++++++++++++++++++++++++++-- 2 files changed, 367 insertions(+), 18 deletions(-) diff --git a/arch/arm/boot/dts/armada-38x.dtsi b/arch/arm/boot/dts/armada-38x.dtsi index e4e1546..5894e45 100644 --- a/arch/arm/boot/dts/armada-38x.dtsi +++ b/arch/arm/boot/dts/armada-38x.dtsi @@ -339,31 +339,39 @@ }; gpio0: gpio@18100 { - compatible = "marvell,orion-gpio"; - reg = <0x18100 0x40>; + compatible = "marvell,armada-370-gpio", + "marvell,orion-gpio"; + 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 = , , , ; + clocks = <&coreclk 0>; }; gpio1: gpio@18140 { - compatible = "marvell,orion-gpio"; - reg = <0x18140 0x40>; + compatible = "marvell,armada-370-gpio", + "marvell,orion-gpio"; + reg = <0x18140 0x40>, <0x181c8 0x08>; + reg-names = "gpio", "pwm"; ngpios = <28>; gpio-controller; #gpio-cells = <2>; + #pwm-cells = <2>; interrupt-controller; #interrupt-cells = <2>; interrupts = , , , ; + clocks = <&coreclk 0>; }; system-controller@18200 { diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index ffea532..9fe65f7 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -42,29 +42,43 @@ #include #include #include +#include #include #include #include +#include + +#include "gpiolib.h" /* * GPIO unit register offsets. */ -#define GPIO_OUT_OFF 0x0000 -#define GPIO_IO_CONF_OFF 0x0004 -#define GPIO_BLINK_EN_OFF 0x0008 -#define GPIO_IN_POL_OFF 0x000c -#define GPIO_DATA_IN_OFF 0x0010 -#define GPIO_EDGE_CAUSE_OFF 0x0014 -#define GPIO_EDGE_MASK_OFF 0x0018 -#define GPIO_LEVEL_MASK_OFF 0x001c +#define GPIO_OUT_OFF 0x0000 +#define GPIO_IO_CONF_OFF 0x0004 +#define GPIO_BLINK_EN_OFF 0x0008 +#define GPIO_IN_POL_OFF 0x000c +#define GPIO_DATA_IN_OFF 0x0010 +#define GPIO_EDGE_CAUSE_OFF 0x0014 +#define GPIO_EDGE_MASK_OFF 0x0018 +#define GPIO_LEVEL_MASK_OFF 0x001c +#define GPIO_BLINK_CNT_SELECT_OFF 0x0020 + +/* + * PWM register offsets. + */ +#define PWM_BLINK_ON_DURATION_OFF 0x0 +#define PWM_BLINK_OFF_DURATION_OFF 0x4 + /* The MV78200 has per-CPU registers for edge mask and level mask */ #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 +/* + * The Armada XP has per-CPU registers for interrupt cause, interrupt * mask and interrupt level mask. Those are relative to the - * percpu_membase. */ + * percpu_membase. + */ #define GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu) ((cpu) * 0x4) #define GPIO_EDGE_MASK_ARMADAXP_OFF(cpu) (0x10 + (cpu) * 0x4) #define GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu) (0x20 + (cpu) * 0x4) @@ -75,6 +89,41 @@ #define MVEBU_MAX_GPIO_PER_BANK 32 +enum mvebu_pwm_ctrl { + MVEBU_PWM_CTRL_SET_A = 0, + MVEBU_PWM_CTRL_SET_B, + MVEBU_PWM_CTRL_MAX +}; + +struct mvebu_pwmchip { + void __iomem *membase; + unsigned long clk_rate; + spinlock_t lock; + bool in_use; + + /* Used to preserve GPIO/PWM registers across suspend/resume */ + u32 blink_on_duration; + u32 blink_off_duration; +}; + +struct mvebu_pwm_chip_drv { + enum mvebu_pwm_ctrl ctrl; + struct gpio_desc *gpiod; + bool master; +}; + +struct mvebu_pwm { + struct pwm_chip chip; + struct mvebu_gpio_chip *mvchip; + struct mvebu_pwmchip controller; + enum mvebu_pwm_ctrl default_counter; + + /* Used to preserve GPIO/PWM registers across suspend/resume */ + u32 blink_select; +}; + +static struct mvebu_pwmchip *mvebu_pwm_list[MVEBU_PWM_CTRL_MAX]; + struct mvebu_gpio_chip { struct gpio_chip chip; spinlock_t lock; @@ -84,6 +133,10 @@ struct mvebu_gpio_chip { struct irq_domain *domain; int soc_variant; + /* Used for PWM support */ + struct clk *clk; + struct mvebu_pwm *mvpwm; + /* Used to preserve GPIO registers across suspend/resume */ u32 out_reg; u32 io_conf_reg; @@ -102,6 +155,11 @@ static inline void __iomem *mvebu_gpioreg_out(struct mvebu_gpio_chip *mvchip) return mvchip->membase + GPIO_OUT_OFF; } +static void __iomem *mvebu_gpioreg_blink_select(struct mvebu_gpio_chip *mvchip) +{ + return mvchip->membase + GPIO_BLINK_CNT_SELECT_OFF; +} + static inline void __iomem *mvebu_gpioreg_blink(struct mvebu_gpio_chip *mvchip) { return mvchip->membase + GPIO_BLINK_EN_OFF; @@ -182,6 +240,20 @@ static void __iomem *mvebu_gpioreg_level_mask(struct mvebu_gpio_chip *mvchip) } /* + * Functions returning addresses of individual registers for a given + * PWM controller. + */ +static void __iomem *mvebu_pwmreg_blink_on_duration(struct mvebu_pwmchip *mvpwm) +{ + return mvpwm->membase + PWM_BLINK_ON_DURATION_OFF; +} + +static void __iomem *mvebu_pwmreg_blink_off_duration(struct mvebu_pwmchip *mvpwm) +{ + return mvpwm->membase + PWM_BLINK_OFF_DURATION_OFF; +} + +/* * Functions implementing the gpio_chip methods */ @@ -489,6 +561,262 @@ static void mvebu_gpio_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } +/* + * Functions implementing the pwm_chip methods + */ +static struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct mvebu_pwm, chip); +} + +static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip); + struct mvebu_gpio_chip *mvchip = mvpwm->mvchip; + struct gpio_desc *desc; + enum mvebu_pwm_ctrl id; + int ret = 0; + struct mvebu_pwm_chip_drv *chip_data; + u32 u; + + spin_lock(&mvpwm->controller.lock); + + mvchip->blink_en_reg = readl_relaxed(mvebu_gpioreg_blink(mvchip)); + if (pwm->chip_data || (mvchip->blink_en_reg & BIT(pwm->hwpwm))) + return -EBUSY; + + desc = gpio_to_desc(mvchip->chip.base + pwm->hwpwm); + 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; + } + + chip_data = kzalloc(sizeof(struct mvebu_pwm_chip_drv), GFP_KERNEL); + if (!chip_data) { + gpiod_free(desc); + ret = -ENOMEM; + goto out; + } + + for (id = MVEBU_PWM_CTRL_SET_A;id < MVEBU_PWM_CTRL_MAX; id++) { + if (!mvebu_pwm_list[id]->in_use) { + chip_data->ctrl = id; + chip_data->master = true; + mvebu_pwm_list[id]->in_use = true; + break; + } + } + + if (!chip_data->master) + chip_data->ctrl = mvpwm->default_counter; + + u = readl_relaxed(mvebu_gpioreg_blink_select(mvchip)); + if (id) + u |= (1 << pwm->hwpwm); + else + u &= ~(1 << pwm->hwpwm); + writel_relaxed(u, mvebu_gpioreg_blink_select(mvchip)); + + chip_data->gpiod = desc; + pwm->chip_data = chip_data; + mvpwm->blink_select = u; +out: + spin_unlock(&mvpwm->controller.lock); + return ret; +} + +static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip); + struct mvebu_pwm_chip_drv *chip_data = (struct mvebu_pwm_chip_drv*) pwm->chip_data; + unsigned long flags; + + spin_lock_irqsave(&mvpwm->controller.lock, flags); + if (chip_data->master) + mvebu_pwm_list[chip_data->ctrl]->in_use = false; + + gpiod_free(chip_data->gpiod); + kfree(chip_data); + pwm->chip_data = NULL; + spin_unlock_irqrestore(&mvpwm->controller.lock, flags); +} + +static int mvebu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwmd, + int duty_ns, int period_ns) +{ + struct mvebu_pwm_chip_drv *chip_data = (struct mvebu_pwm_chip_drv*) pwmd->chip_data; + struct mvebu_pwmchip *controller = mvebu_pwm_list[chip_data->ctrl]; + unsigned int on, off; + unsigned long long val; + unsigned long flags; + + val = (unsigned long long) controller->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) controller->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; + + spin_lock_irqsave(&controller->lock, flags); + writel_relaxed(on, mvebu_pwmreg_blink_on_duration(controller)); + writel_relaxed(off, mvebu_pwmreg_blink_off_duration(controller)); + spin_unlock_irqrestore(&controller->lock, flags); + + return 0; +} + +static int mvebu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip); + struct mvebu_gpio_chip *mvchip = mvpwm->mvchip; + + mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 1); + + return 0; +} + +static void mvebu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mvebu_pwm *mvpwm = to_mvebu_pwm(chip); + struct mvebu_gpio_chip *mvchip = mvpwm->mvchip; + + mvebu_gpio_blink(&mvchip->chip, pwm->hwpwm, 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, +}; + +static void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip) +{ + struct mvebu_pwm *mvpwm = mvchip->mvpwm; + + mvpwm->blink_select = + readl_relaxed(mvebu_gpioreg_blink_select(mvchip)); + mvpwm->controller.blink_on_duration = + readl_relaxed(mvebu_pwmreg_blink_on_duration(&mvpwm->controller)); + mvpwm->controller.blink_off_duration = + readl_relaxed(mvebu_pwmreg_blink_off_duration(&mvpwm->controller)); +} + +static void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip) +{ + struct mvebu_pwm *mvpwm = mvchip->mvpwm; + + writel_relaxed(mvpwm->blink_select, + mvebu_gpioreg_blink_select(mvchip)); + writel_relaxed(mvpwm->controller.blink_on_duration, + mvebu_pwmreg_blink_on_duration(&mvpwm->controller)); + writel_relaxed(mvpwm->controller.blink_off_duration, + mvebu_pwmreg_blink_off_duration(&mvpwm->controller)); +} + +/* + * 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. + */ +static int mvebu_pwm_probe(struct platform_device *pdev, + struct mvebu_gpio_chip *mvchip, + int id) +{ + struct device *dev = &pdev->dev; + struct mvebu_pwm *mvpwm; + struct resource *res; + u32 set; + enum mvebu_pwm_ctrl ctrl_set; + + if (!of_device_is_compatible(mvchip->chip.of_node, + "marvell,armada-370-gpio")) + return 0; + + if (IS_ERR(mvchip->clk)) + return PTR_ERR(mvchip->clk); + + /* + * There are only two sets of PWM configuration registers for + * all the GPIO lines on those SoCs which this driver reserves + * for the first two GPIO chips. So if the resource is missing + * we can't treat it as an error. + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm"); + if (!res) + return 0; + + /* + * Use set A for lines of GPIO chip with id 0, B for GPIO chip + * with id 1. Don't allow further GPIO chips to be used for PWM. + */ + if (id == 0) { + set = 0; + ctrl_set = MVEBU_PWM_CTRL_SET_A; + } else if (id == 1) { + set = U32_MAX; + ctrl_set = MVEBU_PWM_CTRL_SET_B; + } else { + return -EINVAL; + } + writel_relaxed(set, mvebu_gpioreg_blink_select(mvchip)); + + mvpwm = devm_kzalloc(dev, sizeof(struct mvebu_pwm), GFP_KERNEL); + if (!mvpwm) + return -ENOMEM; + mvchip->mvpwm = mvpwm; + mvpwm->mvchip = mvchip; + + mvpwm->controller.membase = devm_ioremap_resource(dev, res); + if (IS_ERR(mvpwm->controller.membase)) + return PTR_ERR(mvpwm->controller.membase); + + mvpwm->controller.clk_rate = clk_get_rate(mvchip->clk); + if (!mvpwm->controller.clk_rate) + return -EINVAL; + + mvpwm->chip.dev = dev; + mvpwm->chip.ops = &mvebu_pwm_ops; + mvpwm->chip.npwm = mvchip->chip.ngpio; + + /* + * There may already be some PWM allocated, so we can't force + * mvpwm->chip.base to a fixed point like mvchip->chip.base. + * So, we let pwmchip_add() do the numbering and take the next free + * region. + */ + mvpwm->chip.base = -1; + + spin_lock_init(&mvpwm->controller.lock); + mvpwm->default_counter = ctrl_set; + mvebu_pwm_list[ctrl_set] = &mvpwm->controller; + + return pwmchip_add(&mvpwm->chip); +} + #ifdef CONFIG_DEBUG_FS #include @@ -561,6 +889,10 @@ static const struct of_device_id mvebu_gpio_of_match[] = { .data = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP, }, { + .compatible = "marvell,armada-370-gpio", + .data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION, + }, + { /* sentinel */ }, }; @@ -607,6 +939,9 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state) BUG(); } + if (IS_ENABLED(CONFIG_PWM)) + mvebu_pwm_suspend(mvchip); + return 0; } @@ -650,6 +985,9 @@ static int mvebu_gpio_resume(struct platform_device *pdev) BUG(); } + if (IS_ENABLED(CONFIG_PWM)) + mvebu_pwm_resume(mvchip); + return 0; } @@ -661,7 +999,6 @@ static int mvebu_gpio_probe(struct platform_device *pdev) struct resource *res; struct irq_chip_generic *gc; struct irq_chip_type *ct; - struct clk *clk; unsigned int ngpios; unsigned int gpio_base = -1; int soc_variant; @@ -695,10 +1032,10 @@ static int mvebu_gpio_probe(struct platform_device *pdev) 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); @@ -835,6 +1172,10 @@ static int mvebu_gpio_probe(struct platform_device *pdev) goto err_generic_chip; } + /* Armada 370/XP has simple PWM support for GPIO lines */ + if (IS_ENABLED(CONFIG_PWM)) + return mvebu_pwm_probe(pdev, mvchip, id); + return 0; err_generic_chip: