2014-04-18 16:47:06

by Tim Kryger

[permalink] [raw]
Subject: [PATCH v6 0/5] Add Broadcom Kona PWM Support

This series introduces the driver for the Kona PWM controller found in
Broadcom mobile SoCs like bcm281xx and updates the device tree and the
defconfig to enable use of this hardware on the bcm28155 AP board.

Changes since v5:
- Rebased on v3.15-rc1

Changes since v4:
- Added in real polarity support
- Labeled trigger bits as such rather than use the name from hw docs
- Listed unsual hardware characteristics at the top of the file
- Removed default from Kconfig and update defconfig accordingly
- Always use unsigned int for temporary register values

Changes since v3:
- Removed polarity support for now
- Cleaned up whitespace issues, shortened some variable names
- Use container_of instead of dev_get_drvdata to get private data
- Removed workaround for PWM framework bug
- Reworded some binding documentation

Changes since v2:
- SoC DTS file updated to use real clock's phandle + specifier
- Toggle smooth mode off during apply so new settings take immediately

Changes since v1:
- Fixed up macros to be clearer and more complete
- Corrected spelling and punctuation mistakes
- Added support for polarity
- Made peripheral clock use more efficient
- Made prescale and duty computation clearer
- Moved Makefile addition to keep alphabetical
- Split complex lines into multiple steps


Tim Kryger (5):
Documentation: dt: Add Kona PWM binding
pwm: kona: Introduce Kona PWM controller support
ARM: dts: Declare the PWM for bcm11351 (bcm281xx)
ARM: dts: Enable the PWM for bcm28155 AP board
ARM: bcm_defconfig: Enable PWM and Backlight

.../devicetree/bindings/pwm/bcm-kona-pwm.txt | 21 ++
arch/arm/boot/dts/bcm11351.dtsi | 8 +
arch/arm/boot/dts/bcm28155-ap.dts | 4 +
arch/arm/configs/bcm_defconfig | 3 +
drivers/pwm/Kconfig | 9 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-bcm-kona.c | 319 ++++++++++++++++++++
7 files changed, 365 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt
create mode 100644 drivers/pwm/pwm-bcm-kona.c

--
1.7.9.5


2014-04-18 16:45:24

by Tim Kryger

[permalink] [raw]
Subject: [PATCH v6 3/5] ARM: dts: Declare the PWM for bcm11351 (bcm281xx)

Add the device tree node for the PWM on bcm11351 SoCs.

Signed-off-by: Tim Kryger <[email protected]>
Reviewed-by: Alex Elder <[email protected]>
Reviewed-by: Markus Mayer <[email protected]>
---
arch/arm/boot/dts/bcm11351.dtsi | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/arch/arm/boot/dts/bcm11351.dtsi b/arch/arm/boot/dts/bcm11351.dtsi
index 64d069b..6b05ae6 100644
--- a/arch/arm/boot/dts/bcm11351.dtsi
+++ b/arch/arm/boot/dts/bcm11351.dtsi
@@ -193,6 +193,14 @@
status = "disabled";
};

+ pwm: pwm@3e01a000 {
+ compatible = "brcm,bcm11351-pwm", "brcm,kona-pwm";
+ reg = <0x3e01a000 0xcc>;
+ clocks = <&slave_ccu BCM281XX_SLAVE_CCU_PWM>;
+ #pwm-cells = <3>;
+ status = "disabled";
+ };
+
clocks {
#address-cells = <1>;
#size-cells = <1>;
--
1.7.9.5

2014-04-18 16:45:31

by Tim Kryger

[permalink] [raw]
Subject: [PATCH v6 4/5] ARM: dts: Enable the PWM for bcm28155 AP board

Mark the PWM as enabled on the bcm28155 AP board.

Signed-off-by: Tim Kryger <[email protected]>
Reviewed-by: Alex Elder <[email protected]>
Reviewed-by: Markus Mayer <[email protected]>
---
arch/arm/boot/dts/bcm28155-ap.dts | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/arch/arm/boot/dts/bcm28155-ap.dts b/arch/arm/boot/dts/bcm28155-ap.dts
index af3da55..9ce91dd 100644
--- a/arch/arm/boot/dts/bcm28155-ap.dts
+++ b/arch/arm/boot/dts/bcm28155-ap.dts
@@ -69,6 +69,10 @@
status = "okay";
};

+ pwm: pwm@3e01a000 {
+ status = "okay";
+ };
+
usbotg: usb@3f120000 {
vusb_d-supply = <&usbldo_reg>;
vusb_a-supply = <&iosr1_reg>;
--
1.7.9.5

2014-04-18 16:45:20

by Tim Kryger

[permalink] [raw]
Subject: [PATCH v6 1/5] Documentation: dt: Add Kona PWM binding

Add the binding description for the Kona PWM controller found on Broadcom's
mobile SoCs.

Signed-off-by: Tim Kryger <[email protected]>
Reviewed-by: Alex Elder <[email protected]>
Reviewed-by: Markus Mayer <[email protected]>
---
.../devicetree/bindings/pwm/bcm-kona-pwm.txt | 21 ++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt

diff --git a/Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt b/Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt
new file mode 100644
index 0000000..8eae9fe
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/bcm-kona-pwm.txt
@@ -0,0 +1,21 @@
+Broadcom Kona PWM controller device tree bindings
+
+This controller has 6 channels.
+
+Required Properties :
+- compatible: should contain "brcm,kona-pwm"
+- reg: physical base address and length of the controller's registers
+- clocks: phandle + clock specifier pair for the external clock
+- #pwm-cells: Should be 3. See pwm.txt in this directory for a
+ description of the cells format.
+
+Refer to clocks/clock-bindings.txt for generic clock consumer properties.
+
+Example:
+
+pwm: pwm@3e01a000 {
+ compatible = "brcm,bcm11351-pwm", "brcm,kona-pwm";
+ reg = <0x3e01a000 0xc4>;
+ clocks = <&pwm_clk>;
+ #pwm-cells = <3>;
+};
--
1.7.9.5

2014-04-18 16:46:19

by Tim Kryger

[permalink] [raw]
Subject: [PATCH v6 5/5] ARM: bcm_defconfig: Enable PWM and Backlight

Enable PWM drivers and the PWM-based backlight driver.

Signed-off-by: Tim Kryger <[email protected]>
Reviewed-by: Alex Elder <[email protected]>
Reviewed-by: Markus Mayer <[email protected]>
---
arch/arm/configs/bcm_defconfig | 3 +++
1 file changed, 3 insertions(+)

diff --git a/arch/arm/configs/bcm_defconfig b/arch/arm/configs/bcm_defconfig
index 0100464..f09f7c6 100644
--- a/arch/arm/configs/bcm_defconfig
+++ b/arch/arm/configs/bcm_defconfig
@@ -91,6 +91,7 @@ CONFIG_FB=y
CONFIG_BACKLIGHT_LCD_SUPPORT=y
CONFIG_LCD_CLASS_DEVICE=y
CONFIG_BACKLIGHT_CLASS_DEVICE=y
+CONFIG_BACKLIGHT_PWM=y
# CONFIG_USB_SUPPORT is not set
CONFIG_MMC=y
CONFIG_MMC_UNSAFE_RESUME=y
@@ -104,6 +105,8 @@ CONFIG_LEDS_TRIGGERS=y
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_PWM=y
+CONFIG_PWM_BCM_KONA=y
CONFIG_EXT4_FS=y
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_EXT4_FS_SECURITY=y
--
1.7.9.5

2014-04-18 16:46:24

by Tim Kryger

[permalink] [raw]
Subject: [PATCH v6 2/5] pwm: kona: Introduce Kona PWM controller support

Add support for the six-channel Kona PWM controller found on Broadcom
mobile SoCs like bcm281xx.

Signed-off-by: Tim Kryger <[email protected]>
Reviewed-by: Alex Elder <[email protected]>
Reviewed-by: Markus Mayer <[email protected]>
---
drivers/pwm/Kconfig | 9 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-bcm-kona.c | 319 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 329 insertions(+)
create mode 100644 drivers/pwm/pwm-bcm-kona.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 5b34ff2..4ad7b89 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -62,6 +62,15 @@ config PWM_ATMEL_TCB
To compile this driver as a module, choose M here: the module
will be called pwm-atmel-tcb.

+config PWM_BCM_KONA
+ tristate "Kona PWM support"
+ depends on ARCH_BCM_MOBILE
+ help
+ Generic PWM framework driver for Broadcom Kona PWM block.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-bcm-kona.
+
config PWM_BFIN
tristate "Blackfin PWM support"
depends on BFIN_GPTIMERS
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index e57d2c3..5c86a19 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_PWM_SYSFS) += sysfs.o
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
+obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c
new file mode 100644
index 0000000..ee8a59d
--- /dev/null
+++ b/drivers/pwm/pwm-bcm-kona.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+/*
+ * The Kona PWM has some unusual characteristics. Here are the main points.
+ *
+ * 1) There is no disable bit and the hardware docs advise programming a zero
+ * duty to achieve output equivalent to that of a normal disable operation.
+ *
+ * 2) Changes to prescale, duty, period, and polarity do not take effect until
+ * a subsequent rising edge of the trigger bit.
+ *
+ * 3) If the smooth bit and trigger bit are both low, the output is a constant
+ * high signal. Otherwise, the earlier waveform continues to be output.
+ *
+ * 4) If the smooth bit is set on the rising edge of the trigger bit, output
+ * will transition to the new settings on a period boundary (which could be
+ * seconds away). If the smooth bit is clear, new settings will be applied
+ * as soon as possible (the hardware always has a 400ns delay).
+ *
+ * 5) When the external clock that feeds the PWM is disabled, output is pegged
+ * high or low depending on its state at that exact instant.
+ */
+
+#define PWM_CONTROL_OFFSET (0x00000000)
+#define PWM_CONTROL_SMOOTH_SHIFT(chan) (24 + (chan))
+#define PWM_CONTROL_TYPE_SHIFT(chan) (16 + (chan))
+#define PWM_CONTROL_POLARITY_SHIFT(chan) (8 + (chan))
+#define PWM_CONTROL_TRIGGER_SHIFT(chan) (chan)
+
+#define PRESCALE_OFFSET (0x00000004)
+#define PRESCALE_SHIFT(chan) ((chan) << 2)
+#define PRESCALE_MASK(chan) (0x7 << PRESCALE_SHIFT(chan))
+#define PRESCALE_MIN (0x00000000)
+#define PRESCALE_MAX (0x00000007)
+
+#define PERIOD_COUNT_OFFSET(chan) (0x00000008 + ((chan) << 3))
+#define PERIOD_COUNT_MIN (0x00000002)
+#define PERIOD_COUNT_MAX (0x00ffffff)
+
+#define DUTY_CYCLE_HIGH_OFFSET(chan) (0x0000000c + ((chan) << 3))
+#define DUTY_CYCLE_HIGH_MIN (0x00000000)
+#define DUTY_CYCLE_HIGH_MAX (0x00ffffff)
+
+struct kona_pwmc {
+ struct pwm_chip chip;
+ void __iomem *base;
+ struct clk *clk;
+};
+
+static inline struct kona_pwmc *to_kona_pwmc(struct pwm_chip *_chip)
+{
+ return container_of(_chip, struct kona_pwmc, chip);
+}
+
+static void kona_pwmc_apply_settings(struct kona_pwmc *kp, unsigned int chan)
+{
+ unsigned int value = readl(kp->base + PWM_CONTROL_OFFSET);
+
+ /* Clear trigger bit but set smooth bit to maintain old output */
+ value |= 1 << PWM_CONTROL_SMOOTH_SHIFT(chan);
+ value &= ~(1 << PWM_CONTROL_TRIGGER_SHIFT(chan));
+ writel(value, kp->base + PWM_CONTROL_OFFSET);
+
+ /* Set trigger bit and clear smooth bit to apply new settings */
+ value &= ~(1 << PWM_CONTROL_SMOOTH_SHIFT(chan));
+ value |= 1 << PWM_CONTROL_TRIGGER_SHIFT(chan);
+ writel(value, kp->base + PWM_CONTROL_OFFSET);
+}
+
+static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ int duty_ns, int period_ns)
+{
+ struct kona_pwmc *kp = to_kona_pwmc(chip);
+ u64 val, div, rate;
+ unsigned long prescale = PRESCALE_MIN, pc, dc;
+ unsigned int value, chan = pwm->hwpwm;
+
+ /*
+ * Find period count, duty count and prescale to suit duty_ns and
+ * period_ns. This is done according to formulas described below:
+ *
+ * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE
+ * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
+ *
+ * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
+ * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
+ */
+
+ rate = clk_get_rate(kp->clk);
+
+ while (1) {
+ div = 1000000000;
+ div *= 1 + prescale;
+ val = rate * period_ns;
+ pc = div64_u64(val, div);
+ val = rate * duty_ns;
+ dc = div64_u64(val, div);
+
+ /* If duty_ns or period_ns are not achievable then return */
+ if (pc < PERIOD_COUNT_MIN || dc < DUTY_CYCLE_HIGH_MIN)
+ return -EINVAL;
+
+ /* If pc and dc are in bounds, the calculation is done */
+ if (pc <= PERIOD_COUNT_MAX && dc <= DUTY_CYCLE_HIGH_MAX)
+ break;
+
+ /* Otherwise, increase prescale and recalculate pc and dc */
+ if (++prescale > PRESCALE_MAX)
+ return -EINVAL;
+ }
+
+ /* If the PWM channel is enabled, write the settings to the HW */
+ if (test_bit(PWMF_ENABLED, &pwm->flags)) {
+ value = readl(kp->base + PRESCALE_OFFSET);
+ value &= ~PRESCALE_MASK(chan);
+ value |= prescale << PRESCALE_SHIFT(chan);
+ writel(value, kp->base + PRESCALE_OFFSET);
+
+ writel(pc, kp->base + PERIOD_COUNT_OFFSET(chan));
+
+ writel(dc, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan));
+
+ kona_pwmc_apply_settings(kp, chan);
+ }
+
+ return 0;
+}
+
+static int kona_pwmc_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
+ enum pwm_polarity polarity)
+{
+ struct kona_pwmc *kp = to_kona_pwmc(chip);
+ unsigned int chan = pwm->hwpwm;
+ unsigned int value;
+ int ret;
+
+ ret = clk_prepare_enable(kp->clk);
+
+ if (ret < 0) {
+ dev_err(chip->dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ value = readl(kp->base + PWM_CONTROL_OFFSET);
+
+ if (polarity == PWM_POLARITY_NORMAL)
+ value |= 1 << PWM_CONTROL_POLARITY_SHIFT(chan);
+ else
+ value &= ~(1 << PWM_CONTROL_POLARITY_SHIFT(chan));
+
+ writel(value, kp->base + PWM_CONTROL_OFFSET);
+
+ kona_pwmc_apply_settings(kp, chan);
+
+ /* Wait for waveform to settle before gating off the clock */
+ ndelay(400);
+
+ clk_disable_unprepare(kp->clk);
+
+ return 0;
+}
+
+static int kona_pwmc_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct kona_pwmc *kp = to_kona_pwmc(chip);
+ int ret;
+
+ ret = clk_prepare_enable(kp->clk);
+ if (ret < 0) {
+ dev_err(chip->dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = kona_pwmc_config(chip, pwm, pwm->duty_cycle, pwm->period);
+ if (ret < 0) {
+ clk_disable_unprepare(kp->clk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void kona_pwmc_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct kona_pwmc *kp = to_kona_pwmc(chip);
+ unsigned int chan = pwm->hwpwm;
+
+ /* Simulate a disable by configuring for zero duty */
+ writel(0, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan));
+ kona_pwmc_apply_settings(kp, chan);
+
+ /* Wait for waveform to settle before gating off the clock */
+ ndelay(400);
+
+ clk_disable_unprepare(kp->clk);
+}
+
+static const struct pwm_ops kona_pwm_ops = {
+ .config = kona_pwmc_config,
+ .set_polarity = kona_pwmc_set_polarity,
+ .enable = kona_pwmc_enable,
+ .disable = kona_pwmc_disable,
+ .owner = THIS_MODULE,
+};
+
+static int kona_pwmc_probe(struct platform_device *pdev)
+{
+ struct kona_pwmc *kp;
+ struct resource *res;
+ unsigned int chan;
+ unsigned int value = 0;
+ int ret = 0;
+
+ kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL);
+ if (kp == NULL)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, kp);
+
+ kp->chip.dev = &pdev->dev;
+ kp->chip.ops = &kona_pwm_ops;
+ kp->chip.base = -1;
+ kp->chip.npwm = 6;
+ kp->chip.of_xlate = of_pwm_xlate_with_flags;
+ kp->chip.of_pwm_n_cells = 3;
+ kp->chip.can_sleep = true;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ kp->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(kp->base))
+ return PTR_ERR(kp->base);
+
+ kp->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(kp->clk)) {
+ dev_err(&pdev->dev, "failed to get clock: %ld\n",
+ PTR_ERR(kp->clk));
+ return PTR_ERR(kp->clk);
+ }
+
+ ret = clk_prepare_enable(kp->clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+
+ /* Set smooth mode, push/pull, and normal polarity for all channels */
+ for (chan = 0; chan < kp->chip.npwm; chan++) {
+ value |= (1 << PWM_CONTROL_SMOOTH_SHIFT(chan));
+ value |= (1 << PWM_CONTROL_TYPE_SHIFT(chan));
+ value |= (1 << PWM_CONTROL_POLARITY_SHIFT(chan));
+ }
+
+ writel(value, kp->base + PWM_CONTROL_OFFSET);
+
+ clk_disable_unprepare(kp->clk);
+
+ ret = pwmchip_add(&kp->chip);
+ if (ret < 0)
+ dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
+
+ return ret;
+}
+
+static int kona_pwmc_remove(struct platform_device *pdev)
+{
+ struct kona_pwmc *kp = platform_get_drvdata(pdev);
+ unsigned int chan;
+
+ for (chan = 0; chan < kp->chip.npwm; chan++)
+ if (test_bit(PWMF_ENABLED, &kp->chip.pwms[chan].flags))
+ clk_disable_unprepare(kp->clk);
+
+ return pwmchip_remove(&kp->chip);
+}
+
+static const struct of_device_id bcm_kona_pwmc_dt[] = {
+ { .compatible = "brcm,kona-pwm" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bcm_kona_pwmc_dt);
+
+static struct platform_driver kona_pwmc_driver = {
+ .driver = {
+ .name = "bcm-kona-pwm",
+ .of_match_table = bcm_kona_pwmc_dt,
+ },
+ .probe = kona_pwmc_probe,
+ .remove = kona_pwmc_remove,
+};
+
+module_platform_driver(kona_pwmc_driver);
+MODULE_AUTHOR("Broadcom Corporation <[email protected]>");
+MODULE_AUTHOR("Tim Kryger <[email protected]>");
+MODULE_DESCRIPTION("Broadcom Kona PWM driver");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5