Hi Florian, I wrote some observations below that maybe can be useful. El 28/08/15 a las 22:21, Florian Fainelli escribió: > Add support for the BCM7038-style PWM controller found in all BCM7xxx STB SoCs. > This controller has a hardcoded 2 channels per controller, and cascades a > variable frequency generator on top of a fixed frequency generator which offers > a range of a 148ns period all the way to ~622ms periods. > > Signed-off-by: Florian Fainelli <f.fainelli@xxxxxxxxx> > --- > Changes in v3: > > - make clock mandatory > - removed a remaining div64_u64 use > > hanges in v2: > > - properly format comments > - utilize do_div instead of div64_u64 > - avoid using a "done" variable for the while loop > - utilize a parameterized register accessor > - remove a bunch of unnecessary assignments > - provide a module author > - update depends to build on BMIPS_GENERIC (the other user) > - removed artificial padding > - removed used only once variable: dn > - utilize devm_ioremap_resource > - do not print success message > - removed THIS_MODULE from platform_driver structure > > drivers/pwm/Kconfig | 10 ++ > drivers/pwm/Makefile | 1 + > drivers/pwm/pwm-brcmstb.c | 324 ++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 335 insertions(+) > create mode 100644 drivers/pwm/pwm-brcmstb.c > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig > index b1541f40fd8d..363c22b22071 100644 > --- a/drivers/pwm/Kconfig > +++ b/drivers/pwm/Kconfig > @@ -111,6 +111,16 @@ config PWM_CLPS711X > To compile this driver as a module, choose M here: the module > will be called pwm-clps711x. > > +config PWM_BRCMSTB > + tristate "Broadcom STB PWM support" > + depends on ARCH_BRCMSTB || BMIPS_GENERIC > + help > + Generic PWM framework driver for the Broadcom Set-top-Box > + SoCs (BCM7xxx). > + > + To compile this driver as a module, choose M Here: the module > + will be called pwm-brcmstb.c. > + > config PWM_EP93XX > tristate "Cirrus Logic EP93xx PWM support" > depends on ARCH_EP93XX > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile > index ec50eb5b5a8f..dc7b1b82d47e 100644 > --- a/drivers/pwm/Makefile > +++ b/drivers/pwm/Makefile > @@ -7,6 +7,7 @@ obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o > obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o > obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o > obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o > +obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o > obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o > obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o > obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o > diff --git a/drivers/pwm/pwm-brcmstb.c b/drivers/pwm/pwm-brcmstb.c > new file mode 100644 > index 000000000000..9ea73755f281 > --- /dev/null > +++ b/drivers/pwm/pwm-brcmstb.c > @@ -0,0 +1,324 @@ > +/* > + * Broadcom BCM7038 PWM driver > + * Author: Florian Fainelli > + * > + * Copyright (C) 2015 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; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/clk.h> > +#include <linux/export.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/pwm.h> > + > +#define PWM_CTRL 0x00 > +#define CTRL_START BIT(0) > +#define CTRL_OEB BIT(1) > +#define CTRL_FORCE_HIGH BIT(2) > +#define CTRL_OPENDRAIN BIT(3) > +#define CTRL_CHAN_OFFS 4 > + > +#define PWM_CTRL2 0x04 > +#define CTRL2_OUT_SELECT BIT(0) > + > +#define PWM_CWORD_MSB 0x08 > +#define PWM_CWORD_LSB 0x0C > + > +#define PWM_CH_SIZE 0x8 > + > +/* Number of bits for the CWORD value */ > +#define CWORD_BIT_SIZE 16 > + > +/* > + * Maximum control word value allowed when variable-frequency PWM is used as a > + * clock for the constant-frequency PMW. > + */ > +#define CONST_VAR_F_MAX 32768 > +#define CONST_VAR_F_MIN 1 > + > +#define PWM_ON(ch) (0x18 + ((ch) * PWM_CH_SIZE)) > +#define PWM_ON_MIN 1 > +#define PWM_PERIOD(ch) (0x1C + ((ch) * PWM_CH_SIZE)) > +#define PWM_PERIOD_MIN 0 > + > +#define PWM_ON_PERIOD_MAX 0xff > + > +struct brcmstb_pwm_dev { > + void __iomem *base; > + struct clk *clk; > + struct pwm_chip chip; > +}; > + > +static inline u32 pwm_readl(struct brcmstb_pwm_dev *p, u32 off) The function name 'pwm_readl' sounds to be very common. It might be better to use a prefix here, don't you think? Maybe brcmstb_pwm_readl? > +{ > + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) > + return __raw_readl(p->base + off); > + else > + return readl_relaxed(p->base + off); > +} > + > +static inline void pwm_writel(struct brcmstb_pwm_dev *p, u32 val, u32 off) Same as before here. > +{ > + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) > + __raw_writel(val, p->base + off); > + else > + writel_relaxed(val, p->base + off); > +} > + > +static inline struct brcmstb_pwm_dev *to_brcmstb_pwm(struct pwm_chip *ch) > +{ > + return container_of(ch, struct brcmstb_pwm_dev, chip); > +} > + > +/* > + * Fv is derived from the variable frequency output. The variable frequency > + * output is configured using this formula: > + * > + * W = cword, if cword < 2 ^ 15 else 16-bit 2's complement of cword > + * > + * Fv = W x 2 ^ -16 x 27Mhz (reference clock) > + * > + * The period is: (period + 1) / Fv and "on" time is on / (period + 1) > + * > + * The PWM core framework specifies that the "duty_ns" parameter is in fact the > + * "on" time, so this translates directly into our HW programming here. > + */ > +static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, > + int duty_ns, int period_ns) > +{ > + struct brcmstb_pwm_dev *b = to_brcmstb_pwm(chip); > + unsigned long pc, dc, cword = CONST_VAR_F_MAX; > + unsigned int ch = pwm->hwpwm; > + u64 val, rate; > + u32 reg; > + > + /* > + * If asking for a duty_ns equal to period_ns, we need to substract > + * the period value by 1 to make it shorter than the "on" time and > + * produce a flat 100% duty cycle signal, and max out the "on" time > + */ > + if (duty_ns == period_ns) { > + dc = PWM_ON_PERIOD_MAX; > + pc = PWM_ON_PERIOD_MAX - 1; > + goto done; > + } > + > + while (1) { > + /* > + * Calculate the base rate from base frequency and current > + * cword > + */ > + rate = (u64)clk_get_rate(b->clk) * (u64)cword; > + do_div(rate, 1 << CWORD_BIT_SIZE); > + > + val = period_ns * rate; > + do_div(val, NSEC_PER_SEC); > + pc = val; > + > + val = (duty_ns + 1) * rate; > + do_div(val, NSEC_PER_SEC); > + dc = val; > + > + /* > + * We can be called with separate duty and period updates, > + * so do not reject dc == 0 right away > + */ > + if (pc == PWM_PERIOD_MIN || > + (dc < PWM_ON_MIN && duty_ns)) No break needed here, this expression can be written on a single line increasing readability. > + return -EINVAL; > + > + /* We converged on a calculation */ > + if (pc <= PWM_ON_PERIOD_MAX && dc <= PWM_ON_PERIOD_MAX) > + break; > + > + /* > + * The cword needs to be a power of 2 for the variable > + * frequency generator to output a 50% duty cycle variable > + * frequency which is used as input clock to the fixed > + * frequency generator. > + */ > + cword >>= 1; > + > + /* > + * Desired periods are too large, we do not have a divider > + * for them > + */ > + if (cword < CONST_VAR_F_MIN) > + return -EINVAL; > + } > + > +done: > + /* > + * Configure the defined "cword" value to have the variable frequency > + * generator output a base frequency for the constant frequency > + * generator to derive from. > + */ > + pwm_writel(b, cword >> 8, PWM_CWORD_MSB + ch * PWM_CH_SIZE); > + pwm_writel(b, cword & 0xff, PWM_CWORD_LSB + ch * PWM_CH_SIZE); > + > + /* Select constant frequency signal output */ > + reg = pwm_readl(b, PWM_CTRL2); > + reg |= (CTRL2_OUT_SELECT << (ch * CTRL_CHAN_OFFS)); A nitpick here, outer parenthesis can be avoided. > + pwm_writel(b, reg, PWM_CTRL2); This read-modify-write sequence may be protected by some locking mechanism. Notice that, as written in the docs: "PWM core does not enforce any locking to pwm_enable(), pwm_disable() and pwm_config()". > + > + /* Configure on and period value */ > + pwm_writel(b, pc, PWM_PERIOD(ch)); > + pwm_writel(b, dc, PWM_ON(ch)); > + > + return 0; > +} > + > +static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm_dev *b, > + unsigned int ch, bool enable) > +{ > + unsigned int ofs = ch * CTRL_CHAN_OFFS; > + u32 reg; > + > + reg = pwm_readl(b, PWM_CTRL); > + if (enable) { > + reg &= ~(CTRL_OEB << ofs); > + reg |= ((CTRL_START | CTRL_OPENDRAIN) << ofs); Nit, outer parenthesis can be avoided. > + } else { > + reg &= ~((CTRL_START | CTRL_OPENDRAIN) << ofs); > + reg |= (CTRL_OEB << ofs); Also here. > + } > + pwm_writel(b, reg, PWM_CTRL); Again, R-M-W sequence may be protected by some locking mechanism. > +} > + > +static int brcmstb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + struct brcmstb_pwm_dev *b = to_brcmstb_pwm(chip); > + > + brcmstb_pwm_enable_set(b, pwm->hwpwm, true); > + > + return 0; > +} > + > +static void brcmstb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + struct brcmstb_pwm_dev *b = to_brcmstb_pwm(chip); > + > + brcmstb_pwm_enable_set(b, pwm->hwpwm, false); > +} > + > +static const struct pwm_ops brcmstb_pwm_ops = { > + .config = brcmstb_pwm_config, > + .enable = brcmstb_pwm_enable, > + .disable = brcmstb_pwm_disable, > + .owner = THIS_MODULE, > +}; > + > +static const struct of_device_id brcmstb_pwm_of_match[] = { > + { .compatible = "brcm,bcm7038-pwm", }, > + { /* sentinel */ } > +}; > + > +static int brcmstb_pwm_probe(struct platform_device *pdev) > +{ > + struct brcmstb_pwm_dev *p; > + struct resource *r; > + int ret; > + > + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); > + if (!p) > + return -ENOMEM; > + > + /* > + * Try to grab the clock and its rate, if not available, default > + * to the base 27Mhz clock domain this block comes from. > + */ > + p->clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(p->clk)) { > + dev_err(&pdev->dev, "failed to obtain clock\n"); > + return PTR_ERR(p->clk); > + } > + > + clk_prepare_enable(p->clk); > + > + platform_set_drvdata(pdev, p); > + > + p->chip.dev = &pdev->dev; > + p->chip.ops = &brcmstb_pwm_ops; > + /* Dynamically assign a PWM base */ > + p->chip.base = -1; > + /* Static number of PWM channels for this controller */ > + p->chip.npwm = 2; > + p->chip.can_sleep = true; > + > + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + p->base = devm_ioremap_resource(&pdev->dev, r); > + if (!p->base) > + return -ENOMEM; I think you're missing some cleanup routine here. You have a previous call to clk_prepare_enable(), so you may have a call to clk_disable_unprepare() here in order to exit cleanly. > + > + ret = pwmchip_add(&p->chip); > + if (ret) > + dev_err(&pdev->dev, "failed to add PWM chip %d\n", ret); Cleanup needed here too. > + > + return ret; > +} > + > +static int brcmstb_pwm_remove(struct platform_device *pdev) > +{ > + struct brcmstb_pwm_dev *p = platform_get_drvdata(pdev); > + > + clk_disable_unprepare(p->clk); > + > + return pwmchip_remove(&p->chip); AFAIK, pwmchip_remove() may return busy if the PWM chip provides a PWM device that is still requested, so you shouldn't disable the clock before you ensure the PWM chip has been successfuly removed. It think you could do something like: ret = pwmchip_remove(&p->chip); if (ret) return ret; clk_disable_unprepare(p->clk); return 0; > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int brcmstb_pwm_suspend(struct device *d) > +{ > + struct platform_device *pdev = to_platform_device(d); > + struct brcmstb_pwm_dev *p = platform_get_drvdata(pdev); > + > + clk_disable(p->clk); > + > + return 0; > +} > + > +static int brcmstb_pwm_resume(struct device *d) > +{ > + struct platform_device *pdev = to_platform_device(d); > + struct brcmstb_pwm_dev *p = platform_get_drvdata(pdev); > + > + clk_enable(p->clk); > + > + return 0; > +} > +#endif > + > +static SIMPLE_DEV_PM_OPS(brcmstb_pwm_pm_ops, > + brcmstb_pwm_suspend, brcmstb_pwm_resume); > + > +static struct platform_driver brcmstb_pwm_driver = { > + .probe = brcmstb_pwm_probe, > + .remove = brcmstb_pwm_remove, > + .driver = { > + .name = "pwm-brcmstb", > + .of_match_table = brcmstb_pwm_of_match, > + .pm = &brcmstb_pwm_pm_ops, > + }, > +}; > +module_platform_driver(brcmstb_pwm_driver); > + > +MODULE_AUTHOR("Florian Fainelli <f.fainelli@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Broadcom STB PWM driver"); > +MODULE_ALIAS("platform:pwm-brcmstb"); > +MODULE_LICENSE("GPL"); > -- Ariel D'Alessandro, VanguardiaSur www.vanguardiasur.com.ar -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html