| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Maxim Integrated |
| * 7-bit, Multi-Channel Sink/Source Current DAC Driver |
| * Copyright (C) 2017 Maxim Integrated |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/i2c.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/err.h> |
| #include <linux/delay.h> |
| #include <linux/iio/iio.h> |
| #include <linux/iio/driver.h> |
| #include <linux/iio/machine.h> |
| #include <linux/iio/consumer.h> |
| |
| #define DS4422_MAX_DAC_CHANNELS 2 |
| #define DS4424_MAX_DAC_CHANNELS 4 |
| |
| #define DS4424_DAC_ADDR(chan) ((chan) + 0xf8) |
| #define DS4424_SOURCE_I 1 |
| #define DS4424_SINK_I 0 |
| |
| #define DS4424_CHANNEL(chan) { \ |
| .type = IIO_CURRENT, \ |
| .indexed = 1, \ |
| .output = 1, \ |
| .channel = chan, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| } |
| |
| /* |
| * DS4424 DAC control register 8 bits |
| * [7] 0: to sink; 1: to source |
| * [6:0] steps to sink/source |
| * bit[7] looks like a sign bit, but the value of the register is |
| * not a two's complement code considering the bit[6:0] is a absolute |
| * distance from the zero point. |
| */ |
| union ds4424_raw_data { |
| struct { |
| u8 dx:7; |
| u8 source_bit:1; |
| }; |
| u8 bits; |
| }; |
| |
| enum ds4424_device_ids { |
| ID_DS4422, |
| ID_DS4424, |
| }; |
| |
| struct ds4424_data { |
| struct i2c_client *client; |
| struct mutex lock; |
| uint8_t save[DS4424_MAX_DAC_CHANNELS]; |
| struct regulator *vcc_reg; |
| uint8_t raw[DS4424_MAX_DAC_CHANNELS]; |
| }; |
| |
| static const struct iio_chan_spec ds4424_channels[] = { |
| DS4424_CHANNEL(0), |
| DS4424_CHANNEL(1), |
| DS4424_CHANNEL(2), |
| DS4424_CHANNEL(3), |
| }; |
| |
| static int ds4424_get_value(struct iio_dev *indio_dev, |
| int *val, int channel) |
| { |
| struct ds4424_data *data = iio_priv(indio_dev); |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = i2c_smbus_read_byte_data(data->client, DS4424_DAC_ADDR(channel)); |
| if (ret < 0) |
| goto fail; |
| |
| *val = ret; |
| |
| fail: |
| mutex_unlock(&data->lock); |
| return ret; |
| } |
| |
| static int ds4424_set_value(struct iio_dev *indio_dev, |
| int val, struct iio_chan_spec const *chan) |
| { |
| struct ds4424_data *data = iio_priv(indio_dev); |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = i2c_smbus_write_byte_data(data->client, |
| DS4424_DAC_ADDR(chan->channel), val); |
| if (ret < 0) |
| goto fail; |
| |
| data->raw[chan->channel] = val; |
| |
| fail: |
| mutex_unlock(&data->lock); |
| return ret; |
| } |
| |
| static int ds4424_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int *val, int *val2, long mask) |
| { |
| union ds4424_raw_data raw; |
| int ret; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_RAW: |
| ret = ds4424_get_value(indio_dev, val, chan->channel); |
| if (ret < 0) { |
| pr_err("%s : ds4424_get_value returned %d\n", |
| __func__, ret); |
| return ret; |
| } |
| raw.bits = *val; |
| *val = raw.dx; |
| if (raw.source_bit == DS4424_SINK_I) |
| *val = -*val; |
| return IIO_VAL_INT; |
| |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ds4424_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int val, int val2, long mask) |
| { |
| union ds4424_raw_data raw; |
| |
| if (val2 != 0) |
| return -EINVAL; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_RAW: |
| if (val < S8_MIN || val > S8_MAX) |
| return -EINVAL; |
| |
| if (val > 0) { |
| raw.source_bit = DS4424_SOURCE_I; |
| raw.dx = val; |
| } else { |
| raw.source_bit = DS4424_SINK_I; |
| raw.dx = -val; |
| } |
| |
| return ds4424_set_value(indio_dev, raw.bits, chan); |
| |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ds4424_verify_chip(struct iio_dev *indio_dev) |
| { |
| int ret, val; |
| |
| ret = ds4424_get_value(indio_dev, &val, 0); |
| if (ret < 0) |
| dev_err(&indio_dev->dev, |
| "%s failed. ret: %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int __maybe_unused ds4424_suspend(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct iio_dev *indio_dev = i2c_get_clientdata(client); |
| struct ds4424_data *data = iio_priv(indio_dev); |
| int ret = 0; |
| int i; |
| |
| for (i = 0; i < indio_dev->num_channels; i++) { |
| data->save[i] = data->raw[i]; |
| ret = ds4424_set_value(indio_dev, 0, |
| &indio_dev->channels[i]); |
| if (ret < 0) |
| return ret; |
| } |
| return ret; |
| } |
| |
| static int __maybe_unused ds4424_resume(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct iio_dev *indio_dev = i2c_get_clientdata(client); |
| struct ds4424_data *data = iio_priv(indio_dev); |
| int ret = 0; |
| int i; |
| |
| for (i = 0; i < indio_dev->num_channels; i++) { |
| ret = ds4424_set_value(indio_dev, data->save[i], |
| &indio_dev->channels[i]); |
| if (ret < 0) |
| return ret; |
| } |
| return ret; |
| } |
| |
| static SIMPLE_DEV_PM_OPS(ds4424_pm_ops, ds4424_suspend, ds4424_resume); |
| |
| static const struct iio_info ds4424_info = { |
| .read_raw = ds4424_read_raw, |
| .write_raw = ds4424_write_raw, |
| }; |
| |
| static int ds4424_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct ds4424_data *data; |
| struct iio_dev *indio_dev; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); |
| if (!indio_dev) { |
| dev_err(&client->dev, "iio dev alloc failed.\n"); |
| return -ENOMEM; |
| } |
| |
| data = iio_priv(indio_dev); |
| i2c_set_clientdata(client, indio_dev); |
| data->client = client; |
| indio_dev->name = id->name; |
| indio_dev->dev.of_node = client->dev.of_node; |
| indio_dev->dev.parent = &client->dev; |
| |
| data->vcc_reg = devm_regulator_get(&client->dev, "vcc"); |
| if (IS_ERR(data->vcc_reg)) { |
| dev_err(&client->dev, |
| "Failed to get vcc-supply regulator. err: %ld\n", |
| PTR_ERR(data->vcc_reg)); |
| return PTR_ERR(data->vcc_reg); |
| } |
| |
| mutex_init(&data->lock); |
| ret = regulator_enable(data->vcc_reg); |
| if (ret < 0) { |
| dev_err(&client->dev, |
| "Unable to enable the regulator.\n"); |
| return ret; |
| } |
| |
| usleep_range(1000, 1200); |
| ret = ds4424_verify_chip(indio_dev); |
| if (ret < 0) |
| goto fail; |
| |
| switch (id->driver_data) { |
| case ID_DS4422: |
| indio_dev->num_channels = DS4422_MAX_DAC_CHANNELS; |
| break; |
| case ID_DS4424: |
| indio_dev->num_channels = DS4424_MAX_DAC_CHANNELS; |
| break; |
| default: |
| dev_err(&client->dev, |
| "ds4424: Invalid chip id.\n"); |
| ret = -ENXIO; |
| goto fail; |
| } |
| |
| indio_dev->channels = ds4424_channels; |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| indio_dev->info = &ds4424_info; |
| |
| ret = iio_device_register(indio_dev); |
| if (ret < 0) { |
| dev_err(&client->dev, |
| "iio_device_register failed. ret: %d\n", ret); |
| goto fail; |
| } |
| |
| return ret; |
| |
| fail: |
| regulator_disable(data->vcc_reg); |
| return ret; |
| } |
| |
| static int ds4424_remove(struct i2c_client *client) |
| { |
| struct iio_dev *indio_dev = i2c_get_clientdata(client); |
| struct ds4424_data *data = iio_priv(indio_dev); |
| |
| iio_device_unregister(indio_dev); |
| regulator_disable(data->vcc_reg); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id ds4424_id[] = { |
| { "ds4422", ID_DS4422 }, |
| { "ds4424", ID_DS4424 }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, ds4424_id); |
| |
| static const struct of_device_id ds4424_of_match[] = { |
| { .compatible = "maxim,ds4422" }, |
| { .compatible = "maxim,ds4424" }, |
| { }, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, ds4424_of_match); |
| |
| static struct i2c_driver ds4424_driver = { |
| .driver = { |
| .name = "ds4424", |
| .of_match_table = ds4424_of_match, |
| .pm = &ds4424_pm_ops, |
| }, |
| .probe = ds4424_probe, |
| .remove = ds4424_remove, |
| .id_table = ds4424_id, |
| }; |
| module_i2c_driver(ds4424_driver); |
| |
| MODULE_DESCRIPTION("Maxim DS4424 DAC Driver"); |
| MODULE_AUTHOR("Ismail H. Kose <ismail.kose@maximintegrated.com>"); |
| MODULE_AUTHOR("Vishal Sood <vishal.sood@maximintegrated.com>"); |
| MODULE_AUTHOR("David Jung <david.jung@maximintegrated.com>"); |
| MODULE_LICENSE("GPL v2"); |