1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
23a9f5957SFlorian Fainelli /*
33a9f5957SFlorian Fainelli * Broadcom BCM7038 PWM driver
43a9f5957SFlorian Fainelli * Author: Florian Fainelli
53a9f5957SFlorian Fainelli *
63a9f5957SFlorian Fainelli * Copyright (C) 2015 Broadcom Corporation
73a9f5957SFlorian Fainelli */
83a9f5957SFlorian Fainelli
93a9f5957SFlorian Fainelli #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
103a9f5957SFlorian Fainelli
113a9f5957SFlorian Fainelli #include <linux/clk.h>
123a9f5957SFlorian Fainelli #include <linux/export.h>
133a9f5957SFlorian Fainelli #include <linux/init.h>
143a9f5957SFlorian Fainelli #include <linux/io.h>
153a9f5957SFlorian Fainelli #include <linux/kernel.h>
163a9f5957SFlorian Fainelli #include <linux/module.h>
173a9f5957SFlorian Fainelli #include <linux/of.h>
183a9f5957SFlorian Fainelli #include <linux/platform_device.h>
193a9f5957SFlorian Fainelli #include <linux/pwm.h>
203a9f5957SFlorian Fainelli #include <linux/spinlock.h>
213a9f5957SFlorian Fainelli
223a9f5957SFlorian Fainelli #define PWM_CTRL 0x00
233a9f5957SFlorian Fainelli #define CTRL_START BIT(0)
243a9f5957SFlorian Fainelli #define CTRL_OEB BIT(1)
253a9f5957SFlorian Fainelli #define CTRL_FORCE_HIGH BIT(2)
263a9f5957SFlorian Fainelli #define CTRL_OPENDRAIN BIT(3)
273a9f5957SFlorian Fainelli #define CTRL_CHAN_OFFS 4
283a9f5957SFlorian Fainelli
293a9f5957SFlorian Fainelli #define PWM_CTRL2 0x04
303a9f5957SFlorian Fainelli #define CTRL2_OUT_SELECT BIT(0)
313a9f5957SFlorian Fainelli
323a9f5957SFlorian Fainelli #define PWM_CH_SIZE 0x8
333a9f5957SFlorian Fainelli
343a9f5957SFlorian Fainelli #define PWM_CWORD_MSB(ch) (0x08 + ((ch) * PWM_CH_SIZE))
353a9f5957SFlorian Fainelli #define PWM_CWORD_LSB(ch) (0x0c + ((ch) * PWM_CH_SIZE))
363a9f5957SFlorian Fainelli
373a9f5957SFlorian Fainelli /* Number of bits for the CWORD value */
383a9f5957SFlorian Fainelli #define CWORD_BIT_SIZE 16
393a9f5957SFlorian Fainelli
403a9f5957SFlorian Fainelli /*
413a9f5957SFlorian Fainelli * Maximum control word value allowed when variable-frequency PWM is used as a
423a9f5957SFlorian Fainelli * clock for the constant-frequency PMW.
433a9f5957SFlorian Fainelli */
443a9f5957SFlorian Fainelli #define CONST_VAR_F_MAX 32768
453a9f5957SFlorian Fainelli #define CONST_VAR_F_MIN 1
463a9f5957SFlorian Fainelli
473a9f5957SFlorian Fainelli #define PWM_ON(ch) (0x18 + ((ch) * PWM_CH_SIZE))
483a9f5957SFlorian Fainelli #define PWM_ON_MIN 1
493a9f5957SFlorian Fainelli #define PWM_PERIOD(ch) (0x1c + ((ch) * PWM_CH_SIZE))
503a9f5957SFlorian Fainelli #define PWM_PERIOD_MIN 0
513a9f5957SFlorian Fainelli
523a9f5957SFlorian Fainelli #define PWM_ON_PERIOD_MAX 0xff
533a9f5957SFlorian Fainelli
543a9f5957SFlorian Fainelli struct brcmstb_pwm {
553a9f5957SFlorian Fainelli void __iomem *base;
563a9f5957SFlorian Fainelli struct clk *clk;
573a9f5957SFlorian Fainelli struct pwm_chip chip;
583a9f5957SFlorian Fainelli };
593a9f5957SFlorian Fainelli
brcmstb_pwm_readl(struct brcmstb_pwm * p,unsigned int offset)603a9f5957SFlorian Fainelli static inline u32 brcmstb_pwm_readl(struct brcmstb_pwm *p,
613a9f5957SFlorian Fainelli unsigned int offset)
623a9f5957SFlorian Fainelli {
633a9f5957SFlorian Fainelli if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
643a9f5957SFlorian Fainelli return __raw_readl(p->base + offset);
653a9f5957SFlorian Fainelli else
663a9f5957SFlorian Fainelli return readl_relaxed(p->base + offset);
673a9f5957SFlorian Fainelli }
683a9f5957SFlorian Fainelli
brcmstb_pwm_writel(struct brcmstb_pwm * p,u32 value,unsigned int offset)693a9f5957SFlorian Fainelli static inline void brcmstb_pwm_writel(struct brcmstb_pwm *p, u32 value,
703a9f5957SFlorian Fainelli unsigned int offset)
713a9f5957SFlorian Fainelli {
723a9f5957SFlorian Fainelli if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
733a9f5957SFlorian Fainelli __raw_writel(value, p->base + offset);
743a9f5957SFlorian Fainelli else
753a9f5957SFlorian Fainelli writel_relaxed(value, p->base + offset);
763a9f5957SFlorian Fainelli }
773a9f5957SFlorian Fainelli
to_brcmstb_pwm(struct pwm_chip * chip)783a9f5957SFlorian Fainelli static inline struct brcmstb_pwm *to_brcmstb_pwm(struct pwm_chip *chip)
793a9f5957SFlorian Fainelli {
803a9f5957SFlorian Fainelli return container_of(chip, struct brcmstb_pwm, chip);
813a9f5957SFlorian Fainelli }
823a9f5957SFlorian Fainelli
833a9f5957SFlorian Fainelli /*
843a9f5957SFlorian Fainelli * Fv is derived from the variable frequency output. The variable frequency
853a9f5957SFlorian Fainelli * output is configured using this formula:
863a9f5957SFlorian Fainelli *
873a9f5957SFlorian Fainelli * W = cword, if cword < 2 ^ 15 else 16-bit 2's complement of cword
883a9f5957SFlorian Fainelli *
893a9f5957SFlorian Fainelli * Fv = W x 2 ^ -16 x 27Mhz (reference clock)
903a9f5957SFlorian Fainelli *
913a9f5957SFlorian Fainelli * The period is: (period + 1) / Fv and "on" time is on / (period + 1)
923a9f5957SFlorian Fainelli *
933a9f5957SFlorian Fainelli * The PWM core framework specifies that the "duty_ns" parameter is in fact the
943a9f5957SFlorian Fainelli * "on" time, so this translates directly into our HW programming here.
953a9f5957SFlorian Fainelli */
brcmstb_pwm_config(struct pwm_chip * chip,struct pwm_device * pwm,u64 duty_ns,u64 period_ns)963a9f5957SFlorian Fainelli static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
970dcfafe7SUwe Kleine-König u64 duty_ns, u64 period_ns)
983a9f5957SFlorian Fainelli {
993a9f5957SFlorian Fainelli struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
1003a9f5957SFlorian Fainelli unsigned long pc, dc, cword = CONST_VAR_F_MAX;
1013a9f5957SFlorian Fainelli unsigned int channel = pwm->hwpwm;
1023a9f5957SFlorian Fainelli u32 value;
1033a9f5957SFlorian Fainelli
1043a9f5957SFlorian Fainelli /*
1053a9f5957SFlorian Fainelli * If asking for a duty_ns equal to period_ns, we need to substract
1063a9f5957SFlorian Fainelli * the period value by 1 to make it shorter than the "on" time and
1073a9f5957SFlorian Fainelli * produce a flat 100% duty cycle signal, and max out the "on" time
1083a9f5957SFlorian Fainelli */
1093a9f5957SFlorian Fainelli if (duty_ns == period_ns) {
1103a9f5957SFlorian Fainelli dc = PWM_ON_PERIOD_MAX;
1113a9f5957SFlorian Fainelli pc = PWM_ON_PERIOD_MAX - 1;
1123a9f5957SFlorian Fainelli goto done;
1133a9f5957SFlorian Fainelli }
1143a9f5957SFlorian Fainelli
1153a9f5957SFlorian Fainelli while (1) {
1160dcfafe7SUwe Kleine-König u64 rate;
1173a9f5957SFlorian Fainelli
1183a9f5957SFlorian Fainelli /*
1193a9f5957SFlorian Fainelli * Calculate the base rate from base frequency and current
1203a9f5957SFlorian Fainelli * cword
1213a9f5957SFlorian Fainelli */
1223a9f5957SFlorian Fainelli rate = (u64)clk_get_rate(p->clk) * (u64)cword;
1230dcfafe7SUwe Kleine-König rate >>= CWORD_BIT_SIZE;
1243a9f5957SFlorian Fainelli
1250dcfafe7SUwe Kleine-König pc = mul_u64_u64_div_u64(period_ns, rate, NSEC_PER_SEC);
1260dcfafe7SUwe Kleine-König dc = mul_u64_u64_div_u64(duty_ns + 1, rate, NSEC_PER_SEC);
1273a9f5957SFlorian Fainelli
1283a9f5957SFlorian Fainelli /*
1293a9f5957SFlorian Fainelli * We can be called with separate duty and period updates,
1303a9f5957SFlorian Fainelli * so do not reject dc == 0 right away
1313a9f5957SFlorian Fainelli */
1323a9f5957SFlorian Fainelli if (pc == PWM_PERIOD_MIN || (dc < PWM_ON_MIN && duty_ns))
1333a9f5957SFlorian Fainelli return -EINVAL;
1343a9f5957SFlorian Fainelli
1353a9f5957SFlorian Fainelli /* We converged on a calculation */
1363a9f5957SFlorian Fainelli if (pc <= PWM_ON_PERIOD_MAX && dc <= PWM_ON_PERIOD_MAX)
1373a9f5957SFlorian Fainelli break;
1383a9f5957SFlorian Fainelli
1393a9f5957SFlorian Fainelli /*
1403a9f5957SFlorian Fainelli * The cword needs to be a power of 2 for the variable
1413a9f5957SFlorian Fainelli * frequency generator to output a 50% duty cycle variable
1423a9f5957SFlorian Fainelli * frequency which is used as input clock to the fixed
1433a9f5957SFlorian Fainelli * frequency generator.
1443a9f5957SFlorian Fainelli */
1453a9f5957SFlorian Fainelli cword >>= 1;
1463a9f5957SFlorian Fainelli
1473a9f5957SFlorian Fainelli /*
1483a9f5957SFlorian Fainelli * Desired periods are too large, we do not have a divider
1493a9f5957SFlorian Fainelli * for them
1503a9f5957SFlorian Fainelli */
1513a9f5957SFlorian Fainelli if (cword < CONST_VAR_F_MIN)
1523a9f5957SFlorian Fainelli return -EINVAL;
1533a9f5957SFlorian Fainelli }
1543a9f5957SFlorian Fainelli
1553a9f5957SFlorian Fainelli done:
1563a9f5957SFlorian Fainelli /*
1573a9f5957SFlorian Fainelli * Configure the defined "cword" value to have the variable frequency
1583a9f5957SFlorian Fainelli * generator output a base frequency for the constant frequency
1593a9f5957SFlorian Fainelli * generator to derive from.
1603a9f5957SFlorian Fainelli */
1613a9f5957SFlorian Fainelli brcmstb_pwm_writel(p, cword >> 8, PWM_CWORD_MSB(channel));
1623a9f5957SFlorian Fainelli brcmstb_pwm_writel(p, cword & 0xff, PWM_CWORD_LSB(channel));
1633a9f5957SFlorian Fainelli
1643a9f5957SFlorian Fainelli /* Select constant frequency signal output */
1653a9f5957SFlorian Fainelli value = brcmstb_pwm_readl(p, PWM_CTRL2);
1663a9f5957SFlorian Fainelli value |= CTRL2_OUT_SELECT << (channel * CTRL_CHAN_OFFS);
1673a9f5957SFlorian Fainelli brcmstb_pwm_writel(p, value, PWM_CTRL2);
1683a9f5957SFlorian Fainelli
1693a9f5957SFlorian Fainelli /* Configure on and period value */
1703a9f5957SFlorian Fainelli brcmstb_pwm_writel(p, pc, PWM_PERIOD(channel));
1713a9f5957SFlorian Fainelli brcmstb_pwm_writel(p, dc, PWM_ON(channel));
1723a9f5957SFlorian Fainelli
1733a9f5957SFlorian Fainelli return 0;
1743a9f5957SFlorian Fainelli }
1753a9f5957SFlorian Fainelli
brcmstb_pwm_enable_set(struct brcmstb_pwm * p,unsigned int channel,bool enable)1763a9f5957SFlorian Fainelli static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm *p,
1773a9f5957SFlorian Fainelli unsigned int channel, bool enable)
1783a9f5957SFlorian Fainelli {
1793a9f5957SFlorian Fainelli unsigned int shift = channel * CTRL_CHAN_OFFS;
1803a9f5957SFlorian Fainelli u32 value;
1813a9f5957SFlorian Fainelli
1823a9f5957SFlorian Fainelli value = brcmstb_pwm_readl(p, PWM_CTRL);
1833a9f5957SFlorian Fainelli
1843a9f5957SFlorian Fainelli if (enable) {
1853a9f5957SFlorian Fainelli value &= ~(CTRL_OEB << shift);
1863a9f5957SFlorian Fainelli value |= (CTRL_START | CTRL_OPENDRAIN) << shift;
1873a9f5957SFlorian Fainelli } else {
1883a9f5957SFlorian Fainelli value &= ~((CTRL_START | CTRL_OPENDRAIN) << shift);
1893a9f5957SFlorian Fainelli value |= CTRL_OEB << shift;
1903a9f5957SFlorian Fainelli }
1913a9f5957SFlorian Fainelli
1923a9f5957SFlorian Fainelli brcmstb_pwm_writel(p, value, PWM_CTRL);
1933a9f5957SFlorian Fainelli }
1943a9f5957SFlorian Fainelli
brcmstb_pwm_apply(struct pwm_chip * chip,struct pwm_device * pwm,const struct pwm_state * state)1950dcfafe7SUwe Kleine-König static int brcmstb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
1960dcfafe7SUwe Kleine-König const struct pwm_state *state)
1973a9f5957SFlorian Fainelli {
1983a9f5957SFlorian Fainelli struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
1990dcfafe7SUwe Kleine-König int err;
2003a9f5957SFlorian Fainelli
2010dcfafe7SUwe Kleine-König if (state->polarity != PWM_POLARITY_NORMAL)
2020dcfafe7SUwe Kleine-König return -EINVAL;
2030dcfafe7SUwe Kleine-König
2040dcfafe7SUwe Kleine-König if (!state->enabled) {
2050dcfafe7SUwe Kleine-König if (pwm->state.enabled)
2060dcfafe7SUwe Kleine-König brcmstb_pwm_enable_set(p, pwm->hwpwm, false);
2070dcfafe7SUwe Kleine-König
2080dcfafe7SUwe Kleine-König return 0;
2090dcfafe7SUwe Kleine-König }
2100dcfafe7SUwe Kleine-König
2110dcfafe7SUwe Kleine-König err = brcmstb_pwm_config(chip, pwm, state->duty_cycle, state->period);
2120dcfafe7SUwe Kleine-König if (err)
2130dcfafe7SUwe Kleine-König return err;
2140dcfafe7SUwe Kleine-König
2150dcfafe7SUwe Kleine-König if (!pwm->state.enabled)
2163a9f5957SFlorian Fainelli brcmstb_pwm_enable_set(p, pwm->hwpwm, true);
2173a9f5957SFlorian Fainelli
2183a9f5957SFlorian Fainelli return 0;
2193a9f5957SFlorian Fainelli }
2203a9f5957SFlorian Fainelli
2213a9f5957SFlorian Fainelli static const struct pwm_ops brcmstb_pwm_ops = {
2220dcfafe7SUwe Kleine-König .apply = brcmstb_pwm_apply,
2233a9f5957SFlorian Fainelli .owner = THIS_MODULE,
2243a9f5957SFlorian Fainelli };
2253a9f5957SFlorian Fainelli
2263a9f5957SFlorian Fainelli static const struct of_device_id brcmstb_pwm_of_match[] = {
2273a9f5957SFlorian Fainelli { .compatible = "brcm,bcm7038-pwm", },
2283a9f5957SFlorian Fainelli { /* sentinel */ }
2293a9f5957SFlorian Fainelli };
2303a9f5957SFlorian Fainelli MODULE_DEVICE_TABLE(of, brcmstb_pwm_of_match);
2313a9f5957SFlorian Fainelli
brcmstb_pwm_probe(struct platform_device * pdev)2323a9f5957SFlorian Fainelli static int brcmstb_pwm_probe(struct platform_device *pdev)
2333a9f5957SFlorian Fainelli {
2343a9f5957SFlorian Fainelli struct brcmstb_pwm *p;
2353a9f5957SFlorian Fainelli int ret;
2363a9f5957SFlorian Fainelli
2373a9f5957SFlorian Fainelli p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
2383a9f5957SFlorian Fainelli if (!p)
2393a9f5957SFlorian Fainelli return -ENOMEM;
2403a9f5957SFlorian Fainelli
2413a9f5957SFlorian Fainelli p->clk = devm_clk_get(&pdev->dev, NULL);
2423a9f5957SFlorian Fainelli if (IS_ERR(p->clk)) {
2433a9f5957SFlorian Fainelli dev_err(&pdev->dev, "failed to obtain clock\n");
2443a9f5957SFlorian Fainelli return PTR_ERR(p->clk);
2453a9f5957SFlorian Fainelli }
2463a9f5957SFlorian Fainelli
2473a9f5957SFlorian Fainelli ret = clk_prepare_enable(p->clk);
2483a9f5957SFlorian Fainelli if (ret < 0) {
2493a9f5957SFlorian Fainelli dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
2503a9f5957SFlorian Fainelli return ret;
2513a9f5957SFlorian Fainelli }
2523a9f5957SFlorian Fainelli
2533a9f5957SFlorian Fainelli platform_set_drvdata(pdev, p);
2543a9f5957SFlorian Fainelli
2553a9f5957SFlorian Fainelli p->chip.dev = &pdev->dev;
2563a9f5957SFlorian Fainelli p->chip.ops = &brcmstb_pwm_ops;
2573a9f5957SFlorian Fainelli p->chip.npwm = 2;
2583a9f5957SFlorian Fainelli
2595bec839fSYangtao Li p->base = devm_platform_ioremap_resource(pdev, 0);
260c5857e3fSVladimir Zapolskiy if (IS_ERR(p->base)) {
261c5857e3fSVladimir Zapolskiy ret = PTR_ERR(p->base);
2623a9f5957SFlorian Fainelli goto out_clk;
2633a9f5957SFlorian Fainelli }
2643a9f5957SFlorian Fainelli
2653a9f5957SFlorian Fainelli ret = pwmchip_add(&p->chip);
2663a9f5957SFlorian Fainelli if (ret) {
2673a9f5957SFlorian Fainelli dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
2683a9f5957SFlorian Fainelli goto out_clk;
2693a9f5957SFlorian Fainelli }
2703a9f5957SFlorian Fainelli
2713a9f5957SFlorian Fainelli return 0;
2723a9f5957SFlorian Fainelli
2733a9f5957SFlorian Fainelli out_clk:
2743a9f5957SFlorian Fainelli clk_disable_unprepare(p->clk);
2753a9f5957SFlorian Fainelli return ret;
2763a9f5957SFlorian Fainelli }
2773a9f5957SFlorian Fainelli
brcmstb_pwm_remove(struct platform_device * pdev)278e577bffdSUwe Kleine-König static void brcmstb_pwm_remove(struct platform_device *pdev)
2793a9f5957SFlorian Fainelli {
2803a9f5957SFlorian Fainelli struct brcmstb_pwm *p = platform_get_drvdata(pdev);
2813a9f5957SFlorian Fainelli
282b4334246SUwe Kleine-König pwmchip_remove(&p->chip);
2833a9f5957SFlorian Fainelli clk_disable_unprepare(p->clk);
2843a9f5957SFlorian Fainelli }
2853a9f5957SFlorian Fainelli
2863a9f5957SFlorian Fainelli #ifdef CONFIG_PM_SLEEP
brcmstb_pwm_suspend(struct device * dev)2873a9f5957SFlorian Fainelli static int brcmstb_pwm_suspend(struct device *dev)
2883a9f5957SFlorian Fainelli {
2893a9f5957SFlorian Fainelli struct brcmstb_pwm *p = dev_get_drvdata(dev);
2903a9f5957SFlorian Fainelli
291*1498352eSFlorian Fainelli clk_disable_unprepare(p->clk);
2923a9f5957SFlorian Fainelli
2933a9f5957SFlorian Fainelli return 0;
2943a9f5957SFlorian Fainelli }
2953a9f5957SFlorian Fainelli
brcmstb_pwm_resume(struct device * dev)2963a9f5957SFlorian Fainelli static int brcmstb_pwm_resume(struct device *dev)
2973a9f5957SFlorian Fainelli {
2983a9f5957SFlorian Fainelli struct brcmstb_pwm *p = dev_get_drvdata(dev);
2993a9f5957SFlorian Fainelli
300*1498352eSFlorian Fainelli clk_prepare_enable(p->clk);
3013a9f5957SFlorian Fainelli
3023a9f5957SFlorian Fainelli return 0;
3033a9f5957SFlorian Fainelli }
3043a9f5957SFlorian Fainelli #endif
3053a9f5957SFlorian Fainelli
3063a9f5957SFlorian Fainelli static SIMPLE_DEV_PM_OPS(brcmstb_pwm_pm_ops, brcmstb_pwm_suspend,
3073a9f5957SFlorian Fainelli brcmstb_pwm_resume);
3083a9f5957SFlorian Fainelli
3093a9f5957SFlorian Fainelli static struct platform_driver brcmstb_pwm_driver = {
3103a9f5957SFlorian Fainelli .probe = brcmstb_pwm_probe,
311e577bffdSUwe Kleine-König .remove_new = brcmstb_pwm_remove,
3123a9f5957SFlorian Fainelli .driver = {
3133a9f5957SFlorian Fainelli .name = "pwm-brcmstb",
3143a9f5957SFlorian Fainelli .of_match_table = brcmstb_pwm_of_match,
3153a9f5957SFlorian Fainelli .pm = &brcmstb_pwm_pm_ops,
3163a9f5957SFlorian Fainelli },
3173a9f5957SFlorian Fainelli };
3183a9f5957SFlorian Fainelli module_platform_driver(brcmstb_pwm_driver);
3193a9f5957SFlorian Fainelli
3203a9f5957SFlorian Fainelli MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>");
3213a9f5957SFlorian Fainelli MODULE_DESCRIPTION("Broadcom STB PWM driver");
3223a9f5957SFlorian Fainelli MODULE_ALIAS("platform:pwm-brcmstb");
3233a9f5957SFlorian Fainelli MODULE_LICENSE("GPL");
324