Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Broadcom BCM7038 PWM driver
0004  * Author: Florian Fainelli
0005  *
0006  * Copyright (C) 2015 Broadcom Corporation
0007  */
0008 
0009 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0010 
0011 #include <linux/clk.h>
0012 #include <linux/export.h>
0013 #include <linux/init.h>
0014 #include <linux/io.h>
0015 #include <linux/kernel.h>
0016 #include <linux/module.h>
0017 #include <linux/of.h>
0018 #include <linux/platform_device.h>
0019 #include <linux/pwm.h>
0020 #include <linux/spinlock.h>
0021 
0022 #define PWM_CTRL        0x00
0023 #define  CTRL_START     BIT(0)
0024 #define  CTRL_OEB       BIT(1)
0025 #define  CTRL_FORCE_HIGH    BIT(2)
0026 #define  CTRL_OPENDRAIN     BIT(3)
0027 #define  CTRL_CHAN_OFFS     4
0028 
0029 #define PWM_CTRL2       0x04
0030 #define  CTRL2_OUT_SELECT   BIT(0)
0031 
0032 #define PWM_CH_SIZE     0x8
0033 
0034 #define PWM_CWORD_MSB(ch)   (0x08 + ((ch) * PWM_CH_SIZE))
0035 #define PWM_CWORD_LSB(ch)   (0x0c + ((ch) * PWM_CH_SIZE))
0036 
0037 /* Number of bits for the CWORD value */
0038 #define CWORD_BIT_SIZE      16
0039 
0040 /*
0041  * Maximum control word value allowed when variable-frequency PWM is used as a
0042  * clock for the constant-frequency PMW.
0043  */
0044 #define CONST_VAR_F_MAX     32768
0045 #define CONST_VAR_F_MIN     1
0046 
0047 #define PWM_ON(ch)      (0x18 + ((ch) * PWM_CH_SIZE))
0048 #define  PWM_ON_MIN     1
0049 #define PWM_PERIOD(ch)      (0x1c + ((ch) * PWM_CH_SIZE))
0050 #define  PWM_PERIOD_MIN     0
0051 
0052 #define PWM_ON_PERIOD_MAX   0xff
0053 
0054 struct brcmstb_pwm {
0055     void __iomem *base;
0056     struct clk *clk;
0057     struct pwm_chip chip;
0058 };
0059 
0060 static inline u32 brcmstb_pwm_readl(struct brcmstb_pwm *p,
0061                     unsigned int offset)
0062 {
0063     if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
0064         return __raw_readl(p->base + offset);
0065     else
0066         return readl_relaxed(p->base + offset);
0067 }
0068 
0069 static inline void brcmstb_pwm_writel(struct brcmstb_pwm *p, u32 value,
0070                       unsigned int offset)
0071 {
0072     if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
0073         __raw_writel(value, p->base + offset);
0074     else
0075         writel_relaxed(value, p->base + offset);
0076 }
0077 
0078 static inline struct brcmstb_pwm *to_brcmstb_pwm(struct pwm_chip *chip)
0079 {
0080     return container_of(chip, struct brcmstb_pwm, chip);
0081 }
0082 
0083 /*
0084  * Fv is derived from the variable frequency output. The variable frequency
0085  * output is configured using this formula:
0086  *
0087  * W = cword, if cword < 2 ^ 15 else 16-bit 2's complement of cword
0088  *
0089  * Fv = W x 2 ^ -16 x 27Mhz (reference clock)
0090  *
0091  * The period is: (period + 1) / Fv and "on" time is on / (period + 1)
0092  *
0093  * The PWM core framework specifies that the "duty_ns" parameter is in fact the
0094  * "on" time, so this translates directly into our HW programming here.
0095  */
0096 static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
0097                   u64 duty_ns, u64 period_ns)
0098 {
0099     struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
0100     unsigned long pc, dc, cword = CONST_VAR_F_MAX;
0101     unsigned int channel = pwm->hwpwm;
0102     u32 value;
0103 
0104     /*
0105      * If asking for a duty_ns equal to period_ns, we need to substract
0106      * the period value by 1 to make it shorter than the "on" time and
0107      * produce a flat 100% duty cycle signal, and max out the "on" time
0108      */
0109     if (duty_ns == period_ns) {
0110         dc = PWM_ON_PERIOD_MAX;
0111         pc = PWM_ON_PERIOD_MAX - 1;
0112         goto done;
0113     }
0114 
0115     while (1) {
0116         u64 rate;
0117 
0118         /*
0119          * Calculate the base rate from base frequency and current
0120          * cword
0121          */
0122         rate = (u64)clk_get_rate(p->clk) * (u64)cword;
0123         rate >>= CWORD_BIT_SIZE;
0124 
0125         pc = mul_u64_u64_div_u64(period_ns, rate, NSEC_PER_SEC);
0126         dc = mul_u64_u64_div_u64(duty_ns + 1, rate, NSEC_PER_SEC);
0127 
0128         /*
0129          * We can be called with separate duty and period updates,
0130          * so do not reject dc == 0 right away
0131          */
0132         if (pc == PWM_PERIOD_MIN || (dc < PWM_ON_MIN && duty_ns))
0133             return -EINVAL;
0134 
0135         /* We converged on a calculation */
0136         if (pc <= PWM_ON_PERIOD_MAX && dc <= PWM_ON_PERIOD_MAX)
0137             break;
0138 
0139         /*
0140          * The cword needs to be a power of 2 for the variable
0141          * frequency generator to output a 50% duty cycle variable
0142          * frequency which is used as input clock to the fixed
0143          * frequency generator.
0144          */
0145         cword >>= 1;
0146 
0147         /*
0148          * Desired periods are too large, we do not have a divider
0149          * for them
0150          */
0151         if (cword < CONST_VAR_F_MIN)
0152             return -EINVAL;
0153     }
0154 
0155 done:
0156     /*
0157      * Configure the defined "cword" value to have the variable frequency
0158      * generator output a base frequency for the constant frequency
0159      * generator to derive from.
0160      */
0161     brcmstb_pwm_writel(p, cword >> 8, PWM_CWORD_MSB(channel));
0162     brcmstb_pwm_writel(p, cword & 0xff, PWM_CWORD_LSB(channel));
0163 
0164     /* Select constant frequency signal output */
0165     value = brcmstb_pwm_readl(p, PWM_CTRL2);
0166     value |= CTRL2_OUT_SELECT << (channel * CTRL_CHAN_OFFS);
0167     brcmstb_pwm_writel(p, value, PWM_CTRL2);
0168 
0169     /* Configure on and period value */
0170     brcmstb_pwm_writel(p, pc, PWM_PERIOD(channel));
0171     brcmstb_pwm_writel(p, dc, PWM_ON(channel));
0172 
0173     return 0;
0174 }
0175 
0176 static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm *p,
0177                       unsigned int channel, bool enable)
0178 {
0179     unsigned int shift = channel * CTRL_CHAN_OFFS;
0180     u32 value;
0181 
0182     value = brcmstb_pwm_readl(p, PWM_CTRL);
0183 
0184     if (enable) {
0185         value &= ~(CTRL_OEB << shift);
0186         value |= (CTRL_START | CTRL_OPENDRAIN) << shift;
0187     } else {
0188         value &= ~((CTRL_START | CTRL_OPENDRAIN) << shift);
0189         value |= CTRL_OEB << shift;
0190     }
0191 
0192     brcmstb_pwm_writel(p, value, PWM_CTRL);
0193 }
0194 
0195 static int brcmstb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
0196                  const struct pwm_state *state)
0197 {
0198     struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
0199     int err;
0200 
0201     if (state->polarity != PWM_POLARITY_NORMAL)
0202         return -EINVAL;
0203 
0204     if (!state->enabled) {
0205         if (pwm->state.enabled)
0206             brcmstb_pwm_enable_set(p, pwm->hwpwm, false);
0207 
0208         return 0;
0209     }
0210 
0211     err = brcmstb_pwm_config(chip, pwm, state->duty_cycle, state->period);
0212     if (err)
0213         return err;
0214 
0215     if (!pwm->state.enabled)
0216         brcmstb_pwm_enable_set(p, pwm->hwpwm, true);
0217 
0218     return 0;
0219 }
0220 
0221 static const struct pwm_ops brcmstb_pwm_ops = {
0222     .apply = brcmstb_pwm_apply,
0223     .owner = THIS_MODULE,
0224 };
0225 
0226 static const struct of_device_id brcmstb_pwm_of_match[] = {
0227     { .compatible = "brcm,bcm7038-pwm", },
0228     { /* sentinel */ }
0229 };
0230 MODULE_DEVICE_TABLE(of, brcmstb_pwm_of_match);
0231 
0232 static int brcmstb_pwm_probe(struct platform_device *pdev)
0233 {
0234     struct brcmstb_pwm *p;
0235     int ret;
0236 
0237     p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
0238     if (!p)
0239         return -ENOMEM;
0240 
0241     p->clk = devm_clk_get(&pdev->dev, NULL);
0242     if (IS_ERR(p->clk)) {
0243         dev_err(&pdev->dev, "failed to obtain clock\n");
0244         return PTR_ERR(p->clk);
0245     }
0246 
0247     ret = clk_prepare_enable(p->clk);
0248     if (ret < 0) {
0249         dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
0250         return ret;
0251     }
0252 
0253     platform_set_drvdata(pdev, p);
0254 
0255     p->chip.dev = &pdev->dev;
0256     p->chip.ops = &brcmstb_pwm_ops;
0257     p->chip.npwm = 2;
0258 
0259     p->base = devm_platform_ioremap_resource(pdev, 0);
0260     if (IS_ERR(p->base)) {
0261         ret = PTR_ERR(p->base);
0262         goto out_clk;
0263     }
0264 
0265     ret = pwmchip_add(&p->chip);
0266     if (ret) {
0267         dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
0268         goto out_clk;
0269     }
0270 
0271     return 0;
0272 
0273 out_clk:
0274     clk_disable_unprepare(p->clk);
0275     return ret;
0276 }
0277 
0278 static int brcmstb_pwm_remove(struct platform_device *pdev)
0279 {
0280     struct brcmstb_pwm *p = platform_get_drvdata(pdev);
0281 
0282     pwmchip_remove(&p->chip);
0283     clk_disable_unprepare(p->clk);
0284 
0285     return 0;
0286 }
0287 
0288 #ifdef CONFIG_PM_SLEEP
0289 static int brcmstb_pwm_suspend(struct device *dev)
0290 {
0291     struct brcmstb_pwm *p = dev_get_drvdata(dev);
0292 
0293     clk_disable(p->clk);
0294 
0295     return 0;
0296 }
0297 
0298 static int brcmstb_pwm_resume(struct device *dev)
0299 {
0300     struct brcmstb_pwm *p = dev_get_drvdata(dev);
0301 
0302     clk_enable(p->clk);
0303 
0304     return 0;
0305 }
0306 #endif
0307 
0308 static SIMPLE_DEV_PM_OPS(brcmstb_pwm_pm_ops, brcmstb_pwm_suspend,
0309              brcmstb_pwm_resume);
0310 
0311 static struct platform_driver brcmstb_pwm_driver = {
0312     .probe = brcmstb_pwm_probe,
0313     .remove = brcmstb_pwm_remove,
0314     .driver = {
0315         .name = "pwm-brcmstb",
0316         .of_match_table = brcmstb_pwm_of_match,
0317         .pm = &brcmstb_pwm_pm_ops,
0318     },
0319 };
0320 module_platform_driver(brcmstb_pwm_driver);
0321 
0322 MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>");
0323 MODULE_DESCRIPTION("Broadcom STB PWM driver");
0324 MODULE_ALIAS("platform:pwm-brcmstb");
0325 MODULE_LICENSE("GPL");