summaryrefslogtreecommitdiff
path: root/drivers/pwm/pwm-imx27.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pwm/pwm-imx27.c')
-rw-r--r--drivers/pwm/pwm-imx27.c98
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;