Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/drivers/iio/dac/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 14 kB image not shown  

Quelle  mcp4728.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Support for Microchip MCP4728
 *
 * Copyright (C) 2023 Andrea Collamati <andrea.collamati@gmail.com>
 *
 * Based on mcp4725 by Peter Meerwald <pmeerw@pmeerw.net>
 *
 * Driver for the Microchip I2C 12-bit digital-to-analog quad channels
 * converter (DAC).
 *
 * (7-bit I2C slave address 0x60, the three LSBs can be configured in
 * hardware)
 */


#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/property.h>
#include <linux/regulator/consumer.h>

#define MCP4728_RESOLUTION   12
#define MCP4728_N_CHANNELS   4

#define MCP4728_CMD_MASK   GENMASK(7, 3)
#define MCP4728_CHSEL_MASK   GENMASK(2, 1)
#define MCP4728_UDAC_MASK   BIT(0)

#define MCP4728_VREF_MASK   BIT(7)
#define MCP4728_PDMODE_MASK   GENMASK(6, 5)
#define MCP4728_GAIN_MASK   BIT(4)

#define MCP4728_DAC_H_MASK   GENMASK(3, 0)
#define MCP4728_DAC_L_MASK   GENMASK(7, 0)

#define MCP4728_RDY_MASK   BIT(7)

#define MCP4728_MW_CMD    0x08 /* Multiwrite Command */
#define MCP4728_SW_CMD    0x0A /* Sequential Write Command with EEPROM */

#define MCP4728_READ_RESPONSE_LEN (MCP4728_N_CHANNELS * 3 * 2)
#define MCP4728_WRITE_EEPROM_LEN  (1 + MCP4728_N_CHANNELS * 2)

enum vref_mode {
 MCP4728_VREF_EXTERNAL_VDD    = 0,
 MCP4728_VREF_INTERNAL_2048mV = 1,
};

enum gain_mode {
 MCP4728_GAIN_X1 = 0,
 MCP4728_GAIN_X2 = 1,
};

enum iio_powerdown_mode {
 MCP4728_IIO_1K,
 MCP4728_IIO_100K,
 MCP4728_IIO_500K,
};

struct mcp4728_channel_data {
 enum vref_mode ref_mode;
 enum iio_powerdown_mode pd_mode;
 enum gain_mode g_mode;
 u16 dac_value;
};

/* MCP4728 Full Scale Ranges
 * the device available ranges are
 * - VREF = VDD FSR = from 0.0V to VDD
 * - VREF = Internal Gain = 1 FSR = from 0.0V to VREF
 * - VREF = Internal Gain = 2 FSR = from 0.0V to 2*VREF
 */

enum mcp4728_scale {
 MCP4728_SCALE_VDD,
 MCP4728_SCALE_VINT_NO_GAIN,
 MCP4728_SCALE_VINT_GAIN_X2,
 MCP4728_N_SCALES
};

struct mcp4728_data {
 struct i2c_client *client;
 bool powerdown;
 int scales_avail[MCP4728_N_SCALES * 2];
 struct mcp4728_channel_data chdata[MCP4728_N_CHANNELS];
};

#define MCP4728_CHAN(chan) {      \
 .type = IIO_VOLTAGE,      \
 .output = 1,       \
 .indexed = 1,       \
 .channel = chan,      \
 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |  \
         BIT(IIO_CHAN_INFO_SCALE),   \
 .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \
 .ext_info = mcp4728_ext_info,     \
}

static int mcp4728_suspend(struct device *dev);
static int mcp4728_resume(struct device *dev);

static ssize_t mcp4728_store_eeprom(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t len)
{
 struct iio_dev *indio_dev = dev_to_iio_dev(dev);
 struct mcp4728_data *data = iio_priv(indio_dev);
 u8 outbuf[MCP4728_WRITE_EEPROM_LEN];
 int tries = 20;
 u8 inbuf[3];
 bool state;
 int ret;
 unsigned int i;

 ret = kstrtobool(buf, &state);
 if (ret < 0)
  return ret;

 if (!state)
  return 0;

 outbuf[0] = FIELD_PREP(MCP4728_CMD_MASK, MCP4728_SW_CMD);

 for (i = 0; i < MCP4728_N_CHANNELS; i++) {
  struct mcp4728_channel_data *ch = &data->chdata[i];
  int offset   = 1 + i * 2;

  outbuf[offset] = FIELD_PREP(MCP4728_VREF_MASK, ch->ref_mode);

  if (data->powerdown) {
   u8 mcp4728_pd_mode = ch->pd_mode + 1;

   outbuf[offset] |= FIELD_PREP(MCP4728_PDMODE_MASK,
           mcp4728_pd_mode);
  }

  outbuf[offset] |= FIELD_PREP(MCP4728_GAIN_MASK, ch->g_mode);
  outbuf[offset] |=
   FIELD_PREP(MCP4728_DAC_H_MASK, ch->dac_value >> 8);
  outbuf[offset + 1] =
   FIELD_PREP(MCP4728_DAC_L_MASK, ch->dac_value);
 }

 ret = i2c_master_send(data->client, outbuf, MCP4728_WRITE_EEPROM_LEN);
 if (ret < 0)
  return ret;
 else if (ret != MCP4728_WRITE_EEPROM_LEN)
  return -EIO;

 /* wait RDY signal for write complete, takes up to 50ms */
 while (tries--) {
  msleep(20);
  ret = i2c_master_recv(data->client, inbuf, 3);
  if (ret < 0)
   return ret;
  else if (ret != 3)
   return -EIO;

  if (FIELD_GET(MCP4728_RDY_MASK, inbuf[0]))
   break;
 }

 if (tries < 0) {
  dev_err(&data->client->dev, "%s failed, incomplete\n",
   __func__);
  return -EIO;
 }
 return len;
}

static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, mcp4728_store_eeprom, 0);

static struct attribute *mcp4728_attributes[] = {
 &iio_dev_attr_store_eeprom.dev_attr.attr,
 NULL,
};

static const struct attribute_group mcp4728_attribute_group = {
 .attrs = mcp4728_attributes,
};

static int mcp4728_program_channel_cfg(int channel, struct iio_dev *indio_dev)
{
 struct mcp4728_data *data = iio_priv(indio_dev);
 struct mcp4728_channel_data *ch = &data->chdata[channel];
 u8 outbuf[3];
 int ret;

 outbuf[0] = FIELD_PREP(MCP4728_CMD_MASK, MCP4728_MW_CMD);
 outbuf[0] |= FIELD_PREP(MCP4728_CHSEL_MASK, channel);
 outbuf[0] |= FIELD_PREP(MCP4728_UDAC_MASK, 0);

 outbuf[1] = FIELD_PREP(MCP4728_VREF_MASK, ch->ref_mode);

 if (data->powerdown)
  outbuf[1] |= FIELD_PREP(MCP4728_PDMODE_MASK, ch->pd_mode + 1);

 outbuf[1] |= FIELD_PREP(MCP4728_GAIN_MASK, ch->g_mode);
 outbuf[1] |= FIELD_PREP(MCP4728_DAC_H_MASK, ch->dac_value >> 8);
 outbuf[2] = FIELD_PREP(MCP4728_DAC_L_MASK, ch->dac_value);

 ret = i2c_master_send(data->client, outbuf, 3);
 if (ret < 0)
  return ret;
 else if (ret != 3)
  return -EIO;

 return 0;
}

static const char *const mcp4728_powerdown_modes[] = { "1kohm_to_gnd",
             "100kohm_to_gnd",
             "500kohm_to_gnd" };

static int mcp4728_get_powerdown_mode(struct iio_dev *indio_dev,
          const struct iio_chan_spec *chan)
{
 struct mcp4728_data *data = iio_priv(indio_dev);

 return data->chdata[chan->channel].pd_mode;
}

static int mcp4728_set_powerdown_mode(struct iio_dev *indio_dev,
          const struct iio_chan_spec *chan,
          unsigned int mode)
{
 struct mcp4728_data *data = iio_priv(indio_dev);

 data->chdata[chan->channel].pd_mode = mode;

 return 0;
}

static ssize_t mcp4728_read_powerdown(struct iio_dev *indio_dev,
          uintptr_t private,
          const struct iio_chan_spec *chan,
          char *buf)
{
 struct mcp4728_data *data = iio_priv(indio_dev);

 return sysfs_emit(buf, "%d\n", data->powerdown);
}

static ssize_t mcp4728_write_powerdown(struct iio_dev *indio_dev,
           uintptr_t private,
           const struct iio_chan_spec *chan,
           const char *buf, size_t len)
{
 struct mcp4728_data *data = iio_priv(indio_dev);
 bool state;
 int ret;

 ret = kstrtobool(buf, &state);
 if (ret)
  return ret;

 if (state)
  ret = mcp4728_suspend(&data->client->dev);
 else
  ret = mcp4728_resume(&data->client->dev);

 if (ret < 0)
  return ret;

 return len;
}

static const struct iio_enum mcp4728_powerdown_mode_enum = {
 .items    = mcp4728_powerdown_modes,
 .num_items = ARRAY_SIZE(mcp4728_powerdown_modes),
 .get    = mcp4728_get_powerdown_mode,
 .set    = mcp4728_set_powerdown_mode,
};

static const struct iio_chan_spec_ext_info mcp4728_ext_info[] = {
 {
  .name = "powerdown",
  .read = mcp4728_read_powerdown,
  .write = mcp4728_write_powerdown,
  .shared = IIO_SEPARATE,
 },
 IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp4728_powerdown_mode_enum),
 IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE,
      &mcp4728_powerdown_mode_enum),
 { }
};

static const struct iio_chan_spec mcp4728_channels[MCP4728_N_CHANNELS] = {
 MCP4728_CHAN(0),
 MCP4728_CHAN(1),
 MCP4728_CHAN(2),
 MCP4728_CHAN(3),
};

static void mcp4728_get_scale_avail(enum mcp4728_scale scale,
        struct mcp4728_data *data, int *val,
        int *val2)
{
 *val  = data->scales_avail[scale * 2];
 *val2 = data->scales_avail[scale * 2 + 1];
}

static void mcp4728_get_scale(int channel, struct mcp4728_data *data, int *val,
         int *val2)
{
 int ref_mode = data->chdata[channel].ref_mode;
 int g_mode   = data->chdata[channel].g_mode;

 if (ref_mode == MCP4728_VREF_EXTERNAL_VDD) {
  mcp4728_get_scale_avail(MCP4728_SCALE_VDD, data, val, val2);
 } else {
  if (g_mode == MCP4728_GAIN_X1) {
   mcp4728_get_scale_avail(MCP4728_SCALE_VINT_NO_GAIN,
      data, val, val2);
  } else {
   mcp4728_get_scale_avail(MCP4728_SCALE_VINT_GAIN_X2,
      data, val, val2);
  }
 }
}

static int mcp4728_find_matching_scale(struct mcp4728_data *data, int val,
           int val2)
{
 for (int i = 0; i < MCP4728_N_SCALES; i++) {
  if (data->scales_avail[i * 2] == val &&
      data->scales_avail[i * 2 + 1] == val2)
   return i;
 }
 return -EINVAL;
}

static int mcp4728_set_scale(int channel, struct mcp4728_data *data, int val,
        int val2)
{
 int scale = mcp4728_find_matching_scale(data, val, val2);

 if (scale < 0)
  return scale;

 switch (scale) {
 case MCP4728_SCALE_VDD:
  data->chdata[channel].ref_mode = MCP4728_VREF_EXTERNAL_VDD;
  return 0;
 case MCP4728_SCALE_VINT_NO_GAIN:
  data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV;
  data->chdata[channel].g_mode   = MCP4728_GAIN_X1;
  return 0;
 case MCP4728_SCALE_VINT_GAIN_X2:
  data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV;
  data->chdata[channel].g_mode   = MCP4728_GAIN_X2;
  return 0;
 default:
  return -EINVAL;
 }
}

static int mcp4728_read_raw(struct iio_dev *indio_dev,
       struct iio_chan_spec const *chan, int *val,
       int *val2, long mask)
{
 struct mcp4728_data *data = iio_priv(indio_dev);

 switch (mask) {
 case IIO_CHAN_INFO_RAW:
  *val = data->chdata[chan->channel].dac_value;
  return IIO_VAL_INT;
 case IIO_CHAN_INFO_SCALE:
  mcp4728_get_scale(chan->channel, data, val, val2);
  return IIO_VAL_INT_PLUS_MICRO;
 }
 return -EINVAL;
}

static int mcp4728_write_raw(struct iio_dev *indio_dev,
        struct iio_chan_spec const *chan, int val,
        int val2, long mask)
{
 struct mcp4728_data *data = iio_priv(indio_dev);
 int ret;

 switch (mask) {
 case IIO_CHAN_INFO_RAW:
  if (val < 0 || val > GENMASK(MCP4728_RESOLUTION - 1, 0))
   return -EINVAL;
  data->chdata[chan->channel].dac_value = val;
  return mcp4728_program_channel_cfg(chan->channel, indio_dev);
 case IIO_CHAN_INFO_SCALE:
  ret = mcp4728_set_scale(chan->channel, data, val, val2);
  if (ret)
   return ret;

  return mcp4728_program_channel_cfg(chan->channel, indio_dev);
 default:
  return -EINVAL;
 }
}

static void mcp4728_init_scale_avail(enum mcp4728_scale scale, int vref_mv,
         struct mcp4728_data *data)
{
 s64 tmp;
 int value_micro;
 int value_int;

 tmp   = (s64)vref_mv * 1000000LL >> MCP4728_RESOLUTION;
 value_int = div_s64_rem(tmp, 1000000LL, &value_micro);

 data->scales_avail[scale * 2]   = value_int;
 data->scales_avail[scale * 2 + 1] = value_micro;
}

static int mcp4728_init_scales_avail(struct mcp4728_data *data, int vdd_mv)
{
 mcp4728_init_scale_avail(MCP4728_SCALE_VDD, vdd_mv, data);
 mcp4728_init_scale_avail(MCP4728_SCALE_VINT_NO_GAIN, 2048, data);
 mcp4728_init_scale_avail(MCP4728_SCALE_VINT_GAIN_X2, 4096, data);

 return 0;
}

static int mcp4728_read_avail(struct iio_dev *indio_dev,
         struct iio_chan_spec const *chan,
         const int **vals, int *type, int *length,
         long info)
{
 struct mcp4728_data *data = iio_priv(indio_dev);

 switch (info) {
 case IIO_CHAN_INFO_SCALE:
  *type = IIO_VAL_INT_PLUS_MICRO;

  switch (chan->type) {
  case IIO_VOLTAGE:
   *vals = data->scales_avail;
   *length = MCP4728_N_SCALES * 2;
   return IIO_AVAIL_LIST;
  default:
   return -EINVAL;
  }
 default:
  return -EINVAL;
 }
}

static const struct iio_info mcp4728_info = {
 .read_raw   = mcp4728_read_raw,
 .write_raw  = mcp4728_write_raw,
 .read_avail = &mcp4728_read_avail,
 .attrs     = &mcp4728_attribute_group,
};

static int mcp4728_suspend(struct device *dev)
{
 struct iio_dev *indio_dev = dev_get_drvdata(dev);
 struct mcp4728_data *data = iio_priv(indio_dev);
 unsigned int i;

 data->powerdown = true;

 for (i = 0; i < MCP4728_N_CHANNELS; i++) {
  int err = mcp4728_program_channel_cfg(i, indio_dev);

  if (err)
   return err;
 }
 return 0;
}

static int mcp4728_resume(struct device *dev)
{
 struct iio_dev *indio_dev = dev_get_drvdata(dev);
 struct mcp4728_data *data = iio_priv(indio_dev);
 int err     = 0;
 unsigned int i;

 data->powerdown = false;

 for (i = 0; i < MCP4728_N_CHANNELS; i++) {
  int ret = mcp4728_program_channel_cfg(i, indio_dev);

  if (ret)
   err = ret;
 }
 return err;
}

static DEFINE_SIMPLE_DEV_PM_OPS(mcp4728_pm_ops, mcp4728_suspend,
    mcp4728_resume);

static int mcp4728_init_channels_data(struct mcp4728_data *data)
{
 u8 inbuf[MCP4728_READ_RESPONSE_LEN];
 int ret;
 unsigned int i;

 ret = i2c_master_recv(data->client, inbuf, MCP4728_READ_RESPONSE_LEN);
 if (ret < 0) {
  return dev_err_probe(&data->client->dev, ret,
         "failed to read mcp4728 conf.\n");
 } else if (ret != MCP4728_READ_RESPONSE_LEN) {
  return dev_err_probe(&data->client->dev, -EIO,
   "failed to read mcp4728 conf. Wrong Response Len ret=%d\n",
   ret);
 }

 for (i = 0; i < MCP4728_N_CHANNELS; i++) {
  struct mcp4728_channel_data *ch = &data->chdata[i];
  u8 r2    = inbuf[i * 6 + 1];
  u8 r3    = inbuf[i * 6 + 2];

  ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 |
    FIELD_GET(MCP4728_DAC_L_MASK, r3);
  ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2);
  ch->pd_mode  = FIELD_GET(MCP4728_PDMODE_MASK, r2);
  ch->g_mode   = FIELD_GET(MCP4728_GAIN_MASK, r2);
 }

 return 0;
}

static int mcp4728_probe(struct i2c_client *client)
{
 const struct i2c_device_id *id = i2c_client_get_device_id(client);
 struct mcp4728_data *data;
 struct iio_dev *indio_dev;
 int ret, vdd_mv;

 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 if (!indio_dev)
  return -ENOMEM;

 data = iio_priv(indio_dev);
 i2c_set_clientdata(client, indio_dev);
 data->client = client;

 ret = devm_regulator_get_enable_read_voltage(&client->dev, "vdd");
 if (ret < 0)
  return ret;

 vdd_mv = ret / 1000;

 /*
 * MCP4728 has internal EEPROM that save each channel boot
 * configuration. It means that device configuration is unknown to the
 * driver at kernel boot. mcp4728_init_channels_data() reads back DAC
 * settings and stores them in data structure.
 */

 ret = mcp4728_init_channels_data(data);
 if (ret) {
  return dev_err_probe(&client->dev, ret,
   "failed to read mcp4728 current configuration\n");
 }

 ret = mcp4728_init_scales_avail(data, vdd_mv);
 if (ret) {
  return dev_err_probe(&client->dev, ret,
         "failed to init scales\n");
 }

 indio_dev->name  = id->name;
 indio_dev->info  = &mcp4728_info;
 indio_dev->channels = mcp4728_channels;
 indio_dev->num_channels = MCP4728_N_CHANNELS;
 indio_dev->modes = INDIO_DIRECT_MODE;

 return devm_iio_device_register(&client->dev, indio_dev);
}

static const struct i2c_device_id mcp4728_id[] = {
 { "mcp4728" },
 { }
};
MODULE_DEVICE_TABLE(i2c, mcp4728_id);

static const struct of_device_id mcp4728_of_match[] = {
 { .compatible = "microchip,mcp4728" },
 { }
};
MODULE_DEVICE_TABLE(of, mcp4728_of_match);

static struct i2c_driver mcp4728_driver = {
 .driver = {
  .name = "mcp4728",
  .of_match_table = mcp4728_of_match,
  .pm = pm_sleep_ptr(&mcp4728_pm_ops),
 },
 .probe = mcp4728_probe,
 .id_table = mcp4728_id,
};
module_i2c_driver(mcp4728_driver);

MODULE_AUTHOR("Andrea Collamati ");
MODULE_DESCRIPTION("MCP4728 12-bit DAC");
MODULE_LICENSE("GPL");

Messung V0.5
C=98 H=90 G=94

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© 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.