diff options
Diffstat (limited to 'drivers/pwm/pwm-imx27.c')
-rw-r--r-- | drivers/pwm/pwm-imx27.c | 98 |
1 files changed, 88 insertions, 10 deletions
diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c index ea91a2f81a9f..07445bdb41c7 100644 --- a/drivers/pwm/pwm-imx27.c +++ b/drivers/pwm/pwm-imx27.c @@ -21,11 +21,13 @@ #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/slab.h> +#include <linux/spinlock.h> #define MX3_PWMCR 0x00 /* PWM Control Register */ #define MX3_PWMSR 0x04 /* PWM Status Register */ #define MX3_PWMSAR 0x0C /* PWM Sample Register */ #define MX3_PWMPR 0x10 /* PWM Period Register */ +#define MX3_PWMCNR 0x14 /* PWM Counter Register */ #define MX3_PWMCR_FWM GENMASK(27, 26) #define MX3_PWMCR_STOPEN BIT(25) @@ -82,6 +84,7 @@ struct pwm_imx27_chip { struct clk *clk_ipg; struct clk *clk_per; + struct clk *clk_32k; void __iomem *mmio_base; struct pwm_chip chip; @@ -91,6 +94,7 @@ struct pwm_imx27_chip { * value to return in that case. */ unsigned int duty_cycle; + spinlock_t lock; }; #define to_pwm_imx27_chip(chip) container_of(chip, struct pwm_imx27_chip, chip) @@ -99,23 +103,36 @@ static int pwm_imx27_clk_prepare_enable(struct pwm_imx27_chip *imx) { int ret; + if (imx->clk_32k) { + ret = clk_prepare_enable(imx->clk_32k); + if (ret) + goto err1; + } + ret = clk_prepare_enable(imx->clk_ipg); if (ret) - return ret; + goto err2; ret = clk_prepare_enable(imx->clk_per); - if (ret) { - clk_disable_unprepare(imx->clk_ipg); - return ret; - } + if (ret) + goto err3; return 0; +err3: + clk_disable_unprepare(imx->clk_ipg); +err2: + if (imx->clk_32k) + clk_disable_unprepare(imx->clk_32k); +err1: + return ret; } static void pwm_imx27_clk_disable_unprepare(struct pwm_imx27_chip *imx) { clk_disable_unprepare(imx->clk_per); clk_disable_unprepare(imx->clk_ipg); + if (imx->clk_32k) + clk_disable_unprepare(imx->clk_32k); } static void pwm_imx27_get_state(struct pwm_chip *chip, @@ -201,10 +218,10 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip, sr = readl(imx->mmio_base + MX3_PWMSR); fifoav = FIELD_GET(MX3_PWMSR_FIFOAV, sr); - if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) { + if (fifoav >= MX3_PWMSR_FIFOAV_3WORDS) { period_ms = DIV_ROUND_UP_ULL(pwm_get_period(pwm), NSEC_PER_MSEC); - msleep(period_ms); + msleep(period_ms * (fifoav - 2)); sr = readl(imx->mmio_base + MX3_PWMSR); if (fifoav == FIELD_GET(MX3_PWMSR_FIFOAV, sr)) @@ -215,13 +232,15 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip, static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { - unsigned long period_cycles, duty_cycles, prescale; + unsigned long period_cycles, duty_cycles, prescale, counter_check, flags; struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + void __iomem *reg_sar = imx->mmio_base + MX3_PWMSAR; + __force u32 sar_last, sar_current; struct pwm_state cstate; unsigned long long c; unsigned long long clkrate; int ret; - u32 cr; + u32 cr, timeout = 1000; pwm_get_state(pwm, &cstate); @@ -262,7 +281,57 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, pwm_imx27_sw_reset(chip); } - writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + /* + * This is a limited workaround. When the SAR FIFO is empty, the new + * write value will be directly applied to SAR even the current period + * is not over. + * If the new SAR value is less than the old one, and the counter is + * greater than the new SAR value, the current period will not filp + * the level. This will result in a pulse with a duty cycle of 100%. + * So, writing the current value of the SAR to SAR here before updating + * the new SAR value can avoid this issue. + * + * Add a spin lock and turn off the interrupt to ensure that the + * real-time performance can be guaranteed as much as possible when + * operating the following operations. + * + * 1. Add a threshold of 1.5us. If the time T between the read current + * count value CNR and the end of the cycle is less than 1.5us, wait + * for T to be longer than 1.5us before updating the SAR register. + * This is to avoid the situation that when the first SAR is written, + * the current cycle just ends and the SAR FIFO that just be written + * is emptied again. + * + * 2. Use __raw_writel() to minimize the interval between two writes to + * the SAR register to increase the fastest pwm frequency supported. + * + * When the PWM period is longer than 2us(or <500KHz), this workaround + * can solve this problem. + */ + if (duty_cycles < imx->duty_cycle) { + c = clkrate * 1500; + do_div(c, NSEC_PER_SEC); + counter_check = c; + sar_last = cpu_to_le32(imx->duty_cycle); + sar_current = cpu_to_le32(duty_cycles); + + spin_lock_irqsave(&imx->lock, flags); + if (state->period >= 2000) { + while ((period_cycles - + readl_relaxed(imx->mmio_base + MX3_PWMCNR)) + < counter_check) { + if (!--timeout) + break; + }; + } + if (!(MX3_PWMSR_FIFOAV & + readl_relaxed(imx->mmio_base + MX3_PWMSR))) + __raw_writel(sar_last, reg_sar); + __raw_writel(sar_current, reg_sar); + spin_unlock_irqrestore(&imx->lock, flags); + } else + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); /* @@ -323,6 +392,15 @@ static int pwm_imx27_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per), "failed to get peripheral clock\n"); + imx->clk_32k = devm_clk_get_optional(&pdev->dev, "32k"); + if (IS_ERR(imx->clk_32k)) { + dev_err(&pdev->dev, "getting 32k clock failed with %ld\n", + PTR_ERR(imx->clk_32k)); + return PTR_ERR(imx->clk_32k); + } + + spin_lock_init(&imx->lock); + imx->duty_cycle = 0; imx->chip.ops = &pwm_imx27_ops; imx->chip.dev = &pdev->dev; imx->chip.npwm = 1; |