Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  pwm-imx27.c

  Sprache: C
 

// SPDX-License-Identifier: GPL-2.0
/*
 * simple driver for PWM (Pulse Width Modulator) controller
 *
 * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
 *
 * Limitations:
 * - When disabled the output is driven to 0 independent of the configured
 *   polarity.
 */


#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.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>
#include <linux/slab.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(2726)
#define MX3_PWMCR_STOPEN  BIT(25)
#define MX3_PWMCR_DOZEN   BIT(24)
#define MX3_PWMCR_WAITEN  BIT(23)
#define MX3_PWMCR_DBGEN   BIT(22)
#define MX3_PWMCR_BCTR   BIT(21)
#define MX3_PWMCR_HCTR   BIT(20)

#define MX3_PWMCR_POUTC   GENMASK(1918)
#define MX3_PWMCR_POUTC_NORMAL  0
#define MX3_PWMCR_POUTC_INVERTED 1
#define MX3_PWMCR_POUTC_OFF  2

#define MX3_PWMCR_CLKSRC  GENMASK(1716)
#define MX3_PWMCR_CLKSRC_OFF  0
#define MX3_PWMCR_CLKSRC_IPG  1
#define MX3_PWMCR_CLKSRC_IPG_HIGH 2
#define MX3_PWMCR_CLKSRC_IPG_32K 3

#define MX3_PWMCR_PRESCALER  GENMASK(154)

#define MX3_PWMCR_SWR   BIT(3)

#define MX3_PWMCR_REPEAT  GENMASK(21)
#define MX3_PWMCR_REPEAT_1X  0
#define MX3_PWMCR_REPEAT_2X  1
#define MX3_PWMCR_REPEAT_4X  2
#define MX3_PWMCR_REPEAT_8X  3

#define MX3_PWMCR_EN   BIT(0)

#define MX3_PWMSR_FWE   BIT(6)
#define MX3_PWMSR_CMP   BIT(5)
#define MX3_PWMSR_ROV   BIT(4)
#define MX3_PWMSR_FE   BIT(3)

#define MX3_PWMSR_FIFOAV  GENMASK(20)
#define MX3_PWMSR_FIFOAV_EMPTY  0
#define MX3_PWMSR_FIFOAV_1WORD  1
#define MX3_PWMSR_FIFOAV_2WORDS  2
#define MX3_PWMSR_FIFOAV_3WORDS  3
#define MX3_PWMSR_FIFOAV_4WORDS  4

#define MX3_PWMCR_PRESCALER_SET(x) FIELD_PREP(MX3_PWMCR_PRESCALER, (x) - 1)
#define MX3_PWMCR_PRESCALER_GET(x) (FIELD_GET(MX3_PWMCR_PRESCALER, \
         (x)) + 1)

#define MX3_PWM_SWR_LOOP  5

/* PWMPR register value of 0xffff has the same effect as 0xfffe */
#define MX3_PWMPR_MAX   0xfffe

static const char * const pwm_imx27_clks[] = {"ipg""per"};
#define PWM_IMX27_PER   1

struct pwm_imx27_chip {
 struct clk_bulk_data clks[ARRAY_SIZE(pwm_imx27_clks)];
 int clks_cnt;
 void __iomem *mmio_base;

 /*
 * The driver cannot read the current duty cycle from the hardware if
 * the hardware is disabled. Cache the last programmed duty cycle
 * value to return in that case.
 */

 unsigned int duty_cycle;
};

static inline struct pwm_imx27_chip *to_pwm_imx27_chip(struct pwm_chip *chip)
{
 return pwmchip_get_drvdata(chip);
}

static int pwm_imx27_get_state(struct pwm_chip *chip,
          struct pwm_device *pwm, struct pwm_state *state)
{
 struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
 u32 period, prescaler, pwm_clk, val;
 u64 tmp;
 int ret;

 ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
 if (ret < 0)
  return ret;

 val = readl(imx->mmio_base + MX3_PWMCR);

 if (val & MX3_PWMCR_EN)
  state->enabled = true;
 else
  state->enabled = false;

 switch (FIELD_GET(MX3_PWMCR_POUTC, val)) {
 case MX3_PWMCR_POUTC_NORMAL:
  state->polarity = PWM_POLARITY_NORMAL;
  break;
 case MX3_PWMCR_POUTC_INVERTED:
  state->polarity = PWM_POLARITY_INVERSED;
  break;
 default:
  dev_warn(pwmchip_parent(chip), "can't set polarity, output disconnected");
 }

 prescaler = MX3_PWMCR_PRESCALER_GET(val);
 pwm_clk = clk_get_rate(imx->clks[PWM_IMX27_PER].clk);
 val = readl(imx->mmio_base + MX3_PWMPR);
 period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;

 /* PWMOUT (Hz) = PWMCLK / (PWMPR + 2) */
 tmp = NSEC_PER_SEC * (u64)(period + 2) * prescaler;
 state->period = DIV_ROUND_UP_ULL(tmp, pwm_clk);

 /*
 * PWMSAR can be read only if PWM is enabled. If the PWM is disabled,
 * use the cached value.
 */

 if (state->enabled)
  val = readl(imx->mmio_base + MX3_PWMSAR);
 else
  val = imx->duty_cycle;

 tmp = NSEC_PER_SEC * (u64)(val) * prescaler;
 state->duty_cycle = DIV_ROUND_UP_ULL(tmp, pwm_clk);

 clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);

 return 0;
}

static void pwm_imx27_sw_reset(struct pwm_chip *chip)
{
 struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
 struct device *dev = pwmchip_parent(chip);
 int wait_count = 0;
 u32 cr;

 writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR);
 do {
  usleep_range(2001000);
  cr = readl(imx->mmio_base + MX3_PWMCR);
 } while ((cr & MX3_PWMCR_SWR) &&
   (wait_count++ < MX3_PWM_SWR_LOOP));

 if (cr & MX3_PWMCR_SWR)
  dev_warn(dev, "software reset timeout\n");
}

static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip,
         struct pwm_device *pwm)
{
 struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
 struct device *dev = pwmchip_parent(chip);
 unsigned int period_ms;
 int fifoav;
 u32 sr;

 sr = readl(imx->mmio_base + MX3_PWMSR);
 fifoav = FIELD_GET(MX3_PWMSR_FIFOAV, sr);
 if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) {
  period_ms = DIV_ROUND_UP_ULL(pwm->state.period,
          NSEC_PER_MSEC);
  msleep(period_ms);

  sr = readl(imx->mmio_base + MX3_PWMSR);
  if (fifoav == FIELD_GET(MX3_PWMSR_FIFOAV, sr))
   dev_warn(dev, "there is no free FIFO slot\n");
 }
}

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, period_us, tmp;
 struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
 unsigned long long c;
 unsigned long long clkrate;
 unsigned long flags;
 int val;
 int ret;
 u32 cr;

 clkrate = clk_get_rate(imx->clks[PWM_IMX27_PER].clk);
 c = clkrate * state->period;

 do_div(c, NSEC_PER_SEC);
 period_cycles = c;

 prescale = period_cycles / 0x10000 + 1;

 period_cycles /= prescale;
 c = clkrate * state->duty_cycle;
 do_div(c, NSEC_PER_SEC);
 duty_cycles = c;
 duty_cycles /= prescale;

 /*
 * according to imx pwm RM, the real period value should be PERIOD
 * value in PWMPR plus 2.
 */

 if (period_cycles > 2)
  period_cycles -= 2;
 else
  period_cycles = 0;

 /*
 * Wait for a free FIFO slot if the PWM is already enabled, and flush
 * the FIFO if the PWM was disabled and is about to be enabled.
 */

 if (pwm->state.enabled) {
  pwm_imx27_wait_fifo_slot(chip, pwm);
 } else {
  ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
  if (ret)
   return ret;

  pwm_imx27_sw_reset(chip);
 }

 val = readl(imx->mmio_base + MX3_PWMPR);
 val = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;
 cr = readl(imx->mmio_base + MX3_PWMCR);
 tmp = NSEC_PER_SEC * (u64)(val + 2) * MX3_PWMCR_PRESCALER_GET(cr);
 tmp = DIV_ROUND_UP_ULL(tmp, clkrate);
 period_us = DIV_ROUND_UP_ULL(tmp, 1000);

 /*
 * ERR051198:
 * PWM: PWM output may not function correctly if the FIFO is empty when
 * a new SAR value is programmed
 *
 * Description:
 * When the PWM FIFO is empty, a new value programmed to the PWM Sample
 * register (PWM_PWMSAR) will be directly applied even if the current
 * timer period has not expired.
 *
 * If the new SAMPLE value programmed in the PWM_PWMSAR register is
 * less than the previous value, and the PWM counter register
 * (PWM_PWMCNR) that contains the current COUNT value is greater than
 * the new programmed SAMPLE value, the current period will not flip
 * the level. This may result in an output pulse with a duty cycle of
 * 100%.
 *
 * Consider a change from
 *     ________
 *    /        \______/
 *    ^      *        ^
 * to
 *     ____
 *    /    \__________/
 *    ^               ^
 * At the time marked by *, the new write value will be directly applied
 * to SAR even the current period is not over if FIFO is empty.
 *
 *     ________        ____________________
 *    /        \______/                    \__________/
 *    ^               ^      *        ^               ^
 *    |<-- old SAR -->|               |<-- new SAR -->|
 *
 * That is the output is active for a whole period.
 *
 * Workaround:
 * Check new SAR less than old SAR and current counter is in errata
 * windows, write extra old SAR into FIFO and new SAR will effect at
 * next period.
 *
 * Sometime period is quite long, such as over 1 second. If add old SAR
 * into FIFO unconditional, new SAR have to wait for next period. It
 * may be too long.
 *
 * Turn off the interrupt to ensure that not IRQ and schedule happen
 * during above operations. If any irq and schedule happen, counter
 * in PWM will be out of data and take wrong action.
 *
 * Add a safety margin 1.5us because it needs some time to complete
 * IO write.
 *
 * Use writel_relaxed() 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. No software workaround is available if PWM
 * period is shorter than IO write. Just try best to fill old data
 * into FIFO.
 */

 c = clkrate * 1500;
 do_div(c, NSEC_PER_SEC);

 local_irq_save(flags);
 val = FIELD_GET(MX3_PWMSR_FIFOAV, readl_relaxed(imx->mmio_base + MX3_PWMSR));

 if (duty_cycles < imx->duty_cycle && (cr & MX3_PWMCR_EN)) {
  if (period_us < 2) { /* 2us = 500 kHz */
   /* Best effort attempt to fix up >500 kHz case */
   udelay(3 * period_us);
   writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
   writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
  } else if (val < MX3_PWMSR_FIFOAV_2WORDS) {
   val = readl_relaxed(imx->mmio_base + MX3_PWMCNR);
   /*
 * If counter is close to period, controller may roll over when
 * next IO write.
 */

   if ((val + c >= duty_cycles && val < imx->duty_cycle) ||
       val + c >= period_cycles)
    writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
  }
 }
 writel_relaxed(duty_cycles, imx->mmio_base + MX3_PWMSAR);
 local_irq_restore(flags);

 writel(period_cycles, imx->mmio_base + MX3_PWMPR);

 /*
 * Store the duty cycle for future reference in cases where the
 * MX3_PWMSAR register can't be read (i.e. when the PWM is disabled).
 */

 imx->duty_cycle = duty_cycles;

 cr = MX3_PWMCR_PRESCALER_SET(prescale) |
      MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEN | MX3_PWMCR_WAITEN |
      FIELD_PREP(MX3_PWMCR_CLKSRC, MX3_PWMCR_CLKSRC_IPG_HIGH) |
      MX3_PWMCR_DBGEN;

 if (state->polarity == PWM_POLARITY_INVERSED)
  cr |= FIELD_PREP(MX3_PWMCR_POUTC,
    MX3_PWMCR_POUTC_INVERTED);

 if (state->enabled)
  cr |= MX3_PWMCR_EN;

 writel(cr, imx->mmio_base + MX3_PWMCR);

 if (!state->enabled)
  clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);

 return 0;
}

static const struct pwm_ops pwm_imx27_ops = {
 .apply = pwm_imx27_apply,
 .get_state = pwm_imx27_get_state,
};

static const struct of_device_id pwm_imx27_dt_ids[] = {
 { .compatible = "fsl,imx27-pwm", },
 { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pwm_imx27_dt_ids);

static int pwm_imx27_probe(struct platform_device *pdev)
{
 struct pwm_chip *chip;
 struct pwm_imx27_chip *imx;
 int ret;
 u32 pwmcr;
 int i;

 chip = devm_pwmchip_alloc(&pdev->dev, 1sizeof(*imx));
 if (IS_ERR(chip))
  return PTR_ERR(chip);
 imx = to_pwm_imx27_chip(chip);

 imx->clks_cnt = ARRAY_SIZE(pwm_imx27_clks);
 for (i = 0; i < imx->clks_cnt; ++i)
  imx->clks[i].id = pwm_imx27_clks[i];

 ret = devm_clk_bulk_get(&pdev->dev, imx->clks_cnt, imx->clks);

 if (ret)
  return dev_err_probe(&pdev->dev, ret,
         "getting clocks failed\n");

 chip->ops = &pwm_imx27_ops;

 imx->mmio_base = devm_platform_ioremap_resource(pdev, 0);
 if (IS_ERR(imx->mmio_base))
  return PTR_ERR(imx->mmio_base);

 ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
 if (ret)
  return ret;

 /* keep clks on if pwm is running */
 pwmcr = readl(imx->mmio_base + MX3_PWMCR);
 if (!(pwmcr & MX3_PWMCR_EN))
  clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);

 return devm_pwmchip_add(&pdev->dev, chip);
}

static struct platform_driver imx_pwm_driver = {
 .driver = {
  .name = "pwm-imx27",
  .of_match_table = pwm_imx27_dt_ids,
 },
 .probe = pwm_imx27_probe,
};
module_platform_driver(imx_pwm_driver);

MODULE_DESCRIPTION("i.MX27 and later i.MX SoCs Pulse Width Modulator driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");

Messung V0.5 in Prozent
C=97 H=94 G=95

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet am  2026-06-08) ¤

*© Formatika GbR, Deutschland






Versionsinformation zu Columbo

Bemerkung:

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Anfrage:

Dauer der Verarbeitung:

Sekunden

sprechenden Kalenders






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002