Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Andrea Collamati | 2739 | 100.00% | 1 | 100.00% |
Total | 2739 | 1 |
// 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; struct regulator *vdd_reg; 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 ret; ret = regulator_get_voltage(data->vdd_reg); if (ret < 0) return ret; mcp4728_init_scale_avail(MCP4728_SCALE_VDD, ret / 1000, 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 void mcp4728_reg_disable(void *reg) { regulator_disable(reg); } 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 err; 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; data->vdd_reg = devm_regulator_get(&client->dev, "vdd"); if (IS_ERR(data->vdd_reg)) return PTR_ERR(data->vdd_reg); err = regulator_enable(data->vdd_reg); if (err) return err; err = devm_add_action_or_reset(&client->dev, mcp4728_reg_disable, data->vdd_reg); if (err) return err; /* * 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. */ err = mcp4728_init_channels_data(data); if (err) { return dev_err_probe(&client->dev, err, "failed to read mcp4728 current configuration\n"); } err = mcp4728_init_scales_avail(data); if (err) { return dev_err_probe(&client->dev, err, "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 <andrea.collamati@gmail.com>"); MODULE_DESCRIPTION("MCP4728 12-bit DAC"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1