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


Quelle  pwm-mc33xs2410.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH
 *
 * Reference Manual : https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf
 *
 * Limitations:
 * - Supports frequencies between 0.5Hz and 2048Hz with following steps:
 *   - 0.5 Hz steps from 0.5 Hz to 32 Hz
 *   - 2 Hz steps from 2 Hz to 128 Hz
 *   - 8 Hz steps from 8 Hz to 512 Hz
 *   - 32 Hz steps from 32 Hz to 2048 Hz
 * - Cannot generate a 0 % duty cycle.
 * - Always produces low output if disabled.
 * - Configuration isn't atomic. When changing polarity, duty cycle or period
 *   the data is taken immediately, counters not being affected, resulting in a
 *   behavior of the output pin that is neither the old nor the new state,
 *   rather something in between.
 */

#define DEFAULT_SYMBOL_NAMESPACE  "PWM_MC33XS2410"

#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/math64.h>
#include <linux/mc33xs2410.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pwm.h>

#include <linux/spi/spi.h>

#define MC33XS2410_GLB_CTRL   0x00
#define MC33XS2410_GLB_CTRL_MODE  GENMASK(7, 6)
#define MC33XS2410_GLB_CTRL_MODE_NORMAL  FIELD_PREP(MC33XS2410_GLB_CTRL_MODE, 1)

#define MC33XS2410_PWM_CTRL1   0x05
/* chan in { 1 ... 4 } */
#define MC33XS2410_PWM_CTRL1_POL_INV(chan) BIT((chan) + 1)

#define MC33XS2410_PWM_CTRL3   0x07
/* chan in { 1 ... 4 } */
#define MC33XS2410_PWM_CTRL3_EN(chan)  BIT(4 + (chan) - 1)

/* chan in { 1 ... 4 } */
#define MC33XS2410_PWM_FREQ(chan)  (0x08 + (chan) - 1)
#define MC33XS2410_PWM_FREQ_STEP  GENMASK(7, 6)
#define MC33XS2410_PWM_FREQ_COUNT  GENMASK(5, 0)

/* chan in { 1 ... 4 } */
#define MC33XS2410_PWM_DC(chan)   (0x0c + (chan) - 1)

#define MC33XS2410_WDT    0x14

#define MC33XS2410_PWM_MIN_PERIOD  488282
/* step in { 0 ... 3 } */
#define MC33XS2410_PWM_MAX_PERIOD(step)  (2000000000 >> (2 * (step)))

#define MC33XS2410_FRAME_IN_ADDR  GENMASK(15, 8)
#define MC33XS2410_FRAME_IN_DATA  GENMASK(7, 0)
#define MC33XS2410_FRAME_IN_ADDR_WR  BIT(7)
#define MC33XS2410_FRAME_IN_DATA_RD  BIT(7)
#define MC33XS2410_FRAME_OUT_DATA  GENMASK(13, 0)

#define MC33XS2410_MAX_TRANSFERS  5

static int mc33xs2410_write_regs(struct spi_device *spi, u8 *reg, u8 *val,
     unsigned int len)
{
 u16 tx[MC33XS2410_MAX_TRANSFERS];
 int i;

 if (len > MC33XS2410_MAX_TRANSFERS)
  return -EINVAL;

 for (i = 0; i < len; i++)
  tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, val[i]) |
   FIELD_PREP(MC33XS2410_FRAME_IN_ADDR,
       MC33XS2410_FRAME_IN_ADDR_WR | reg[i]);

 return spi_write(spi, tx, len * 2);
}

static int mc33xs2410_read_regs(struct spi_device *spi, u8 *reg, u8 flag,
    u16 *val, unsigned int len)
{
 u16 tx[MC33XS2410_MAX_TRANSFERS];
 u16 rx[MC33XS2410_MAX_TRANSFERS];
 struct spi_transfer t = {
  .tx_buf = tx,
  .rx_buf = rx,
 };
 int i, ret;

 len++;
 if (len > MC33XS2410_MAX_TRANSFERS)
  return -EINVAL;

 t.len = len * 2;
 for (i = 0; i < len - 1; i++)
  tx[i] = FIELD_PREP(MC33XS2410_FRAME_IN_DATA, flag) |
   FIELD_PREP(MC33XS2410_FRAME_IN_ADDR, reg[i]);

 ret = spi_sync_transfer(spi, &t, 1);
 if (ret < 0)
  return ret;

 for (i = 1; i < len; i++)
  val[i - 1] = FIELD_GET(MC33XS2410_FRAME_OUT_DATA, rx[i]);

 return 0;
}

static int mc33xs2410_write_reg(struct spi_device *spi, u8 reg, u8 val)
{
 return mc33xs2410_write_regs(spi, ®, &val, 1);
}

static int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, u8 flag)
{
 return mc33xs2410_read_regs(spi, ®, flag, val, 1);
}

int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val)
{
 return mc33xs2410_read_reg(spi, reg, val, MC33XS2410_FRAME_IN_DATA_RD);
}
EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_ctrl);

int mc33xs2410_read_reg_diag(struct spi_device *spi, u8 reg, u16 *val)
{
 return mc33xs2410_read_reg(spi, reg, val, 0);
}
EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_diag);

int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val)
{
 u16 tmp;
 int ret;

 ret = mc33xs2410_read_reg_ctrl(spi, reg, &tmp);
 if (ret < 0)
  return ret;

 tmp &= ~mask;
 tmp |= val & mask;

 return mc33xs2410_write_reg(spi, reg, tmp);
}
EXPORT_SYMBOL_GPL(mc33xs2410_modify_reg);

static u8 mc33xs2410_pwm_get_freq(u64 period)
{
 u8 step, count;

 /*
 * Check which step [0 .. 3] is appropriate for the given period. The
 * period ranges for the different step values overlap. Prefer big step
 * values as these allow more finegrained period and duty cycle
 * selection.
 */


 switch (period) {
 case MC33XS2410_PWM_MIN_PERIOD ... MC33XS2410_PWM_MAX_PERIOD(3):
  step = 3;
  break;
 case MC33XS2410_PWM_MAX_PERIOD(3) + 1 ... MC33XS2410_PWM_MAX_PERIOD(2):
  step = 2;
  break;
 case MC33XS2410_PWM_MAX_PERIOD(2) + 1 ... MC33XS2410_PWM_MAX_PERIOD(1):
  step = 1;
  break;
 case MC33XS2410_PWM_MAX_PERIOD(1) + 1 ... MC33XS2410_PWM_MAX_PERIOD(0):
  step = 0;
  break;
 }

 /*
 * Round up here because a higher count results in a higher frequency
 * and so a smaller period.
 */

 count = DIV_ROUND_UP((u32)MC33XS2410_PWM_MAX_PERIOD(step), (u32)period);
 return FIELD_PREP(MC33XS2410_PWM_FREQ_STEP, step) |
        FIELD_PREP(MC33XS2410_PWM_FREQ_COUNT, count - 1);
}

static u64 mc33xs2410_pwm_get_period(u8 reg)
{
 u32 doubled_freq, code, doubled_steps;

 /*
 * steps:
 *   - 0 = 0.5Hz
 *   - 1 = 2Hz
 *   - 2 = 8Hz
 *   - 3 = 32Hz
 * frequency = (code + 1) x steps.
 *
 * To avoid losing precision in case steps value is zero, scale the
 * steps value for now by two and keep it in mind when calculating the
 * period that the frequency had been doubled.
 */

 doubled_steps = 1 << (FIELD_GET(MC33XS2410_PWM_FREQ_STEP, reg) * 2);
 code = FIELD_GET(MC33XS2410_PWM_FREQ_COUNT, reg);
 doubled_freq = (code + 1) * doubled_steps;

 /* Convert frequency to period, considering the doubled frequency. */
 return DIV_ROUND_UP(2 * NSEC_PER_SEC, doubled_freq);
}

/*
 * The hardware cannot generate a 0% relative duty cycle for normal and inversed
 * polarity. For normal polarity, the channel must be disabled, the device then
 * emits a constant low signal.
 * For inverted polarity, the channel must be enabled, the polarity must be set
 * to normal and the relative duty cylce must be set to 100%. The device then
 * emits a constant high signal.
 */

static int mc33xs2410_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
    const struct pwm_state *state)
{
 struct spi_device *spi = pwmchip_get_drvdata(chip);
 u8 reg[4] = {
   MC33XS2410_PWM_FREQ(pwm->hwpwm + 1),
   MC33XS2410_PWM_DC(pwm->hwpwm + 1),
   MC33XS2410_PWM_CTRL1,
   MC33XS2410_PWM_CTRL3
      };
 u64 period, duty_cycle;
 int ret, rel_dc;
 u16 rd_val[2];
 u8 wr_val[4];
 u8 mask;

 period = min(state->period, MC33XS2410_PWM_MAX_PERIOD(0));
 if (period < MC33XS2410_PWM_MIN_PERIOD)
  return -EINVAL;

 ret = mc33xs2410_read_regs(spi, ®[2], MC33XS2410_FRAME_IN_DATA_RD, rd_val, 2);
 if (ret < 0)
  return ret;

 /* Frequency */
 wr_val[0] = mc33xs2410_pwm_get_freq(period);
 /* Continue calculations with the possibly truncated period */
 period = mc33xs2410_pwm_get_period(wr_val[0]);

 /* Duty cycle */
 duty_cycle = min(period, state->duty_cycle);
 rel_dc = div64_u64(duty_cycle * 256, period) - 1;
 if (rel_dc >= 0)
  wr_val[1] = rel_dc;
 else if (state->polarity == PWM_POLARITY_NORMAL)
  wr_val[1] = 0;
 else
  wr_val[1] = 255;

 /* Polarity */
 mask = MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1);
 if (state->polarity == PWM_POLARITY_INVERSED && rel_dc >= 0)
  wr_val[2] = rd_val[0] | mask;
 else
  wr_val[2] = rd_val[0] & ~mask;

 /* Enable */
 mask = MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1);
 if (state->enabled &&
     !(state->polarity == PWM_POLARITY_NORMAL && rel_dc < 0))
  wr_val[3] = rd_val[1] | mask;
 else
  wr_val[3] = rd_val[1] & ~mask;

 return mc33xs2410_write_regs(spi, reg, wr_val, 4);
}

static int mc33xs2410_pwm_get_state(struct pwm_chip *chip,
        struct pwm_device *pwm,
        struct pwm_state *state)
{
 struct spi_device *spi = pwmchip_get_drvdata(chip);
 u8 reg[4] = {
   MC33XS2410_PWM_FREQ(pwm->hwpwm + 1),
   MC33XS2410_PWM_DC(pwm->hwpwm + 1),
   MC33XS2410_PWM_CTRL1,
   MC33XS2410_PWM_CTRL3,
      };
 u16 val[4];
 int ret;

 ret = mc33xs2410_read_regs(spi, reg, MC33XS2410_FRAME_IN_DATA_RD, val,
       ARRAY_SIZE(reg));
 if (ret < 0)
  return ret;

 state->period = mc33xs2410_pwm_get_period(val[0]);
 state->polarity = (val[2] & MC33XS2410_PWM_CTRL1_POL_INV(pwm->hwpwm + 1)) ?
     PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
 state->enabled = !!(val[3] & MC33XS2410_PWM_CTRL3_EN(pwm->hwpwm + 1));
 state->duty_cycle = DIV_ROUND_UP_ULL((val[1] + 1) * state->period, 256);

 return 0;
}

static const struct pwm_ops mc33xs2410_pwm_ops = {
 .apply = mc33xs2410_pwm_apply,
 .get_state = mc33xs2410_pwm_get_state,
};

static int mc33xs2410_reset(struct device *dev)
{
 struct gpio_desc *reset_gpio;

 reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
 if (IS_ERR_OR_NULL(reset_gpio))
  return PTR_ERR_OR_ZERO(reset_gpio);

 /* Wake-up time */
 fsleep(10000);

 return 0;
}

static int mc33xs2410_probe(struct spi_device *spi)
{
 struct device *dev = &spi->dev;
 struct auxiliary_device *adev;
 struct pwm_chip *chip;
 int ret;

 chip = devm_pwmchip_alloc(dev, 4, 0);
 if (IS_ERR(chip))
  return PTR_ERR(chip);

 spi->bits_per_word = 16;
 spi->mode |= SPI_CS_WORD;
 ret = spi_setup(spi);
 if (ret < 0)
  return ret;

 pwmchip_set_drvdata(chip, spi);
 chip->ops = &mc33xs2410_pwm_ops;

 /*
 * Deasserts the reset of the device. Shouldn't change the output signal
 * if the device was setup prior to probing.
 */

 ret = mc33xs2410_reset(dev);
 if (ret)
  return ret;

 /*
 * Disable watchdog and keep in mind that the watchdog won't trigger a
 * reset of the machine when running into an timeout, instead the
 * control over the outputs is handed over to the INx input logic
 * signals of the device. Disabling it here just deactivates this
 * feature until a proper solution is found.
 */

 ret = mc33xs2410_write_reg(spi, MC33XS2410_WDT, 0x0);
 if (ret < 0)
  return dev_err_probe(dev, ret, "Failed to disable watchdog\n");

 /* Transition to normal mode */
 ret = mc33xs2410_modify_reg(spi, MC33XS2410_GLB_CTRL,
        MC33XS2410_GLB_CTRL_MODE,
        MC33XS2410_GLB_CTRL_MODE_NORMAL);
 if (ret < 0)
  return dev_err_probe(dev, ret,
         "Failed to transition to normal mode\n");

 ret = devm_pwmchip_add(dev, chip);
 if (ret < 0)
  return dev_err_probe(dev, ret, "Failed to add pwm chip\n");

 adev = devm_auxiliary_device_create(dev, "hwmon", NULL);
 if (!adev)
  return dev_err_probe(dev, -ENODEV, "Failed to register hwmon device\n");

 return 0;
}

static const struct spi_device_id mc33xs2410_spi_id[] = {
 { "mc33xs2410" },
 { }
};
MODULE_DEVICE_TABLE(spi, mc33xs2410_spi_id);

static const struct of_device_id mc33xs2410_of_match[] = {
 { .compatible = "nxp,mc33xs2410" },
 { }
};
MODULE_DEVICE_TABLE(of, mc33xs2410_of_match);

static struct spi_driver mc33xs2410_driver = {
 .driver = {
  .name = "mc33xs2410-pwm",
  .of_match_table = mc33xs2410_of_match,
 },
 .probe = mc33xs2410_probe,
 .id_table = mc33xs2410_spi_id,
};
module_spi_driver(mc33xs2410_driver);

MODULE_DESCRIPTION("NXP MC33XS2410 high-side switch driver");
MODULE_AUTHOR("Dimitri Fedrau ");
MODULE_LICENSE("GPL");

Messung V0.5
C=96 H=94 G=94

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge