diff --git a/config/kernel/linux-mvebu-default.config b/config/kernel/linux-mvebu-default.config index aaa1d5f0d..8a0830549 100644 --- a/config/kernel/linux-mvebu-default.config +++ b/config/kernel/linux-mvebu-default.config @@ -1,6 +1,6 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/arm 4.4.99 Kernel Configuration +# Linux/arm 4.4.110 Kernel Configuration # CONFIG_ARM=y CONFIG_ARM_HAS_SG_CHAIN=y @@ -3028,6 +3028,7 @@ CONFIG_SENSORS_LM75=m # CONFIG_SENSORS_NCT7904 is not set # CONFIG_SENSORS_PCF8591 is not set # CONFIG_PMBUS is not set +CONFIG_SENSORS_PWM_FAN=m # CONFIG_SENSORS_SHT15 is not set # CONFIG_SENSORS_SHT21 is not set # CONFIG_SENSORS_SHTC1 is not set @@ -3276,6 +3277,7 @@ CONFIG_REGULATOR_GPIO=m # CONFIG_REGULATOR_MAX8973 is not set # CONFIG_REGULATOR_MT6311 is not set # CONFIG_REGULATOR_PFUZE100 is not set +# CONFIG_REGULATOR_PWM is not set # CONFIG_REGULATOR_TPS51632 is not set # CONFIG_REGULATOR_TPS62360 is not set # CONFIG_REGULATOR_TPS65023 is not set @@ -3373,11 +3375,14 @@ CONFIG_LCD_LMS501KF03=m CONFIG_LCD_HX8357=m CONFIG_BACKLIGHT_CLASS_DEVICE=m CONFIG_BACKLIGHT_GENERIC=m +# CONFIG_BACKLIGHT_PWM is not set CONFIG_BACKLIGHT_PM8941_WLED=m CONFIG_BACKLIGHT_ADP8860=m CONFIG_BACKLIGHT_ADP8870=m CONFIG_BACKLIGHT_88PM860X=m +# CONFIG_BACKLIGHT_LM3630A is not set CONFIG_BACKLIGHT_LM3639=m +# CONFIG_BACKLIGHT_LP855X is not set CONFIG_BACKLIGHT_GPIO=m CONFIG_BACKLIGHT_LV5207LP=m CONFIG_BACKLIGHT_BD6107=m @@ -3849,6 +3854,7 @@ CONFIG_LEDS_GPIO=m # CONFIG_LEDS_PCA955X is not set # CONFIG_LEDS_PCA963X is not set # CONFIG_LEDS_DAC124S085 is not set +# CONFIG_LEDS_PWM is not set CONFIG_LEDS_REGULATOR=m # CONFIG_LEDS_BD2802 is not set # CONFIG_LEDS_LT3593 is not set @@ -4220,6 +4226,7 @@ CONFIG_COMMON_CLK_SCPI=m # CONFIG_COMMON_CLK_SI570 is not set # CONFIG_COMMON_CLK_CDCE925 is not set # CONFIG_CLK_QORIQ is not set +# CONFIG_COMMON_CLK_PWM is not set # CONFIG_COMMON_CLK_PXA is not set # CONFIG_COMMON_CLK_CDCE706 is not set CONFIG_MVEBU_CLK_COMMON=y @@ -4528,7 +4535,10 @@ CONFIG_IIO_SYSFS_TRIGGER=m # CONFIG_TSYS02D is not set # CONFIG_NTB is not set # CONFIG_VME_BUS is not set -# CONFIG_PWM is not set +CONFIG_PWM=y +CONFIG_PWM_SYSFS=y +# CONFIG_PWM_FSL_FTM is not set +# CONFIG_PWM_PCA9685 is not set CONFIG_IRQCHIP=y CONFIG_ARM_GIC=y CONFIG_ARMADA_370_XP_IRQ=y diff --git a/patch/kernel/mvebu-default/92-gpio-mvebu-enable_hardware_pwm_support.patch b/patch/kernel/mvebu-default/92-gpio-mvebu-enable_hardware_pwm_support.patch new file mode 100644 index 000000000..9ebe57360 --- /dev/null +++ b/patch/kernel/mvebu-default/92-gpio-mvebu-enable_hardware_pwm_support.patch @@ -0,0 +1,523 @@ + 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: