cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

mcp4725.c (12434B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * mcp4725.c - Support for Microchip MCP4725/6
      4 *
      5 * Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net>
      6 *
      7 * Based on max517 by Roland Stigge <stigge@antcom.de>
      8 *
      9 * driver for the Microchip I2C 12-bit digital-to-analog converter (DAC)
     10 * (7-bit I2C slave address 0x60, the three LSBs can be configured in
     11 * hardware)
     12 */
     13
     14#include <linux/module.h>
     15#include <linux/i2c.h>
     16#include <linux/err.h>
     17#include <linux/delay.h>
     18#include <linux/regulator/consumer.h>
     19#include <linux/mod_devicetable.h>
     20#include <linux/property.h>
     21
     22#include <linux/iio/iio.h>
     23#include <linux/iio/sysfs.h>
     24
     25#include <linux/iio/dac/mcp4725.h>
     26
     27#define MCP4725_DRV_NAME "mcp4725"
     28
     29#define MCP472X_REF_VDD			0x00
     30#define MCP472X_REF_VREF_UNBUFFERED	0x02
     31#define MCP472X_REF_VREF_BUFFERED	0x03
     32
     33struct mcp4725_data {
     34	struct i2c_client *client;
     35	int id;
     36	unsigned ref_mode;
     37	bool vref_buffered;
     38	u16 dac_value;
     39	bool powerdown;
     40	unsigned powerdown_mode;
     41	struct regulator *vdd_reg;
     42	struct regulator *vref_reg;
     43};
     44
     45static int __maybe_unused mcp4725_suspend(struct device *dev)
     46{
     47	struct mcp4725_data *data = iio_priv(i2c_get_clientdata(
     48		to_i2c_client(dev)));
     49	u8 outbuf[2];
     50
     51	outbuf[0] = (data->powerdown_mode + 1) << 4;
     52	outbuf[1] = 0;
     53	data->powerdown = true;
     54
     55	return i2c_master_send(data->client, outbuf, 2);
     56}
     57
     58static int __maybe_unused mcp4725_resume(struct device *dev)
     59{
     60	struct mcp4725_data *data = iio_priv(i2c_get_clientdata(
     61		to_i2c_client(dev)));
     62	u8 outbuf[2];
     63
     64	/* restore previous DAC value */
     65	outbuf[0] = (data->dac_value >> 8) & 0xf;
     66	outbuf[1] = data->dac_value & 0xff;
     67	data->powerdown = false;
     68
     69	return i2c_master_send(data->client, outbuf, 2);
     70}
     71static SIMPLE_DEV_PM_OPS(mcp4725_pm_ops, mcp4725_suspend, mcp4725_resume);
     72
     73static ssize_t mcp4725_store_eeprom(struct device *dev,
     74	struct device_attribute *attr, const char *buf, size_t len)
     75{
     76	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
     77	struct mcp4725_data *data = iio_priv(indio_dev);
     78	int tries = 20;
     79	u8 inoutbuf[3];
     80	bool state;
     81	int ret;
     82
     83	ret = kstrtobool(buf, &state);
     84	if (ret < 0)
     85		return ret;
     86
     87	if (!state)
     88		return 0;
     89
     90	inoutbuf[0] = 0x60; /* write EEPROM */
     91	inoutbuf[0] |= data->ref_mode << 3;
     92	inoutbuf[0] |= data->powerdown ? ((data->powerdown_mode + 1) << 1) : 0;
     93	inoutbuf[1] = data->dac_value >> 4;
     94	inoutbuf[2] = (data->dac_value & 0xf) << 4;
     95
     96	ret = i2c_master_send(data->client, inoutbuf, 3);
     97	if (ret < 0)
     98		return ret;
     99	else if (ret != 3)
    100		return -EIO;
    101
    102	/* wait for write complete, takes up to 50ms */
    103	while (tries--) {
    104		msleep(20);
    105		ret = i2c_master_recv(data->client, inoutbuf, 3);
    106		if (ret < 0)
    107			return ret;
    108		else if (ret != 3)
    109			return -EIO;
    110
    111		if (inoutbuf[0] & 0x80)
    112			break;
    113	}
    114
    115	if (tries < 0) {
    116		dev_err(&data->client->dev,
    117			"mcp4725_store_eeprom() failed, incomplete\n");
    118		return -EIO;
    119	}
    120
    121	return len;
    122}
    123
    124static IIO_DEVICE_ATTR(store_eeprom, S_IWUSR, NULL, mcp4725_store_eeprom, 0);
    125
    126static struct attribute *mcp4725_attributes[] = {
    127	&iio_dev_attr_store_eeprom.dev_attr.attr,
    128	NULL,
    129};
    130
    131static const struct attribute_group mcp4725_attribute_group = {
    132	.attrs = mcp4725_attributes,
    133};
    134
    135static const char * const mcp4725_powerdown_modes[] = {
    136	"1kohm_to_gnd",
    137	"100kohm_to_gnd",
    138	"500kohm_to_gnd"
    139};
    140
    141static const char * const mcp4726_powerdown_modes[] = {
    142	"1kohm_to_gnd",
    143	"125kohm_to_gnd",
    144	"640kohm_to_gnd"
    145};
    146
    147static int mcp4725_get_powerdown_mode(struct iio_dev *indio_dev,
    148	const struct iio_chan_spec *chan)
    149{
    150	struct mcp4725_data *data = iio_priv(indio_dev);
    151
    152	return data->powerdown_mode;
    153}
    154
    155static int mcp4725_set_powerdown_mode(struct iio_dev *indio_dev,
    156	const struct iio_chan_spec *chan, unsigned mode)
    157{
    158	struct mcp4725_data *data = iio_priv(indio_dev);
    159
    160	data->powerdown_mode = mode;
    161
    162	return 0;
    163}
    164
    165static ssize_t mcp4725_read_powerdown(struct iio_dev *indio_dev,
    166	uintptr_t private, const struct iio_chan_spec *chan, char *buf)
    167{
    168	struct mcp4725_data *data = iio_priv(indio_dev);
    169
    170	return sysfs_emit(buf, "%d\n", data->powerdown);
    171}
    172
    173static ssize_t mcp4725_write_powerdown(struct iio_dev *indio_dev,
    174	 uintptr_t private, const struct iio_chan_spec *chan,
    175	 const char *buf, size_t len)
    176{
    177	struct mcp4725_data *data = iio_priv(indio_dev);
    178	bool state;
    179	int ret;
    180
    181	ret = kstrtobool(buf, &state);
    182	if (ret)
    183		return ret;
    184
    185	if (state)
    186		ret = mcp4725_suspend(&data->client->dev);
    187	else
    188		ret = mcp4725_resume(&data->client->dev);
    189	if (ret < 0)
    190		return ret;
    191
    192	return len;
    193}
    194
    195enum chip_id {
    196	MCP4725,
    197	MCP4726,
    198};
    199
    200static const struct iio_enum mcp472x_powerdown_mode_enum[] = {
    201	[MCP4725] = {
    202		.items = mcp4725_powerdown_modes,
    203		.num_items = ARRAY_SIZE(mcp4725_powerdown_modes),
    204		.get = mcp4725_get_powerdown_mode,
    205		.set = mcp4725_set_powerdown_mode,
    206	},
    207	[MCP4726] = {
    208		.items = mcp4726_powerdown_modes,
    209		.num_items = ARRAY_SIZE(mcp4726_powerdown_modes),
    210		.get = mcp4725_get_powerdown_mode,
    211		.set = mcp4725_set_powerdown_mode,
    212	},
    213};
    214
    215static const struct iio_chan_spec_ext_info mcp4725_ext_info[] = {
    216	{
    217		.name = "powerdown",
    218		.read = mcp4725_read_powerdown,
    219		.write = mcp4725_write_powerdown,
    220		.shared = IIO_SEPARATE,
    221	},
    222	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
    223			&mcp472x_powerdown_mode_enum[MCP4725]),
    224	IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE,
    225			   &mcp472x_powerdown_mode_enum[MCP4725]),
    226	{ },
    227};
    228
    229static const struct iio_chan_spec_ext_info mcp4726_ext_info[] = {
    230	{
    231		.name = "powerdown",
    232		.read = mcp4725_read_powerdown,
    233		.write = mcp4725_write_powerdown,
    234		.shared = IIO_SEPARATE,
    235	},
    236	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
    237			&mcp472x_powerdown_mode_enum[MCP4726]),
    238	IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE,
    239			   &mcp472x_powerdown_mode_enum[MCP4726]),
    240	{ },
    241};
    242
    243static const struct iio_chan_spec mcp472x_channel[] = {
    244	[MCP4725] = {
    245		.type		= IIO_VOLTAGE,
    246		.indexed	= 1,
    247		.output		= 1,
    248		.channel	= 0,
    249		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
    250		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    251		.ext_info	= mcp4725_ext_info,
    252	},
    253	[MCP4726] = {
    254		.type		= IIO_VOLTAGE,
    255		.indexed	= 1,
    256		.output		= 1,
    257		.channel	= 0,
    258		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
    259		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
    260		.ext_info	= mcp4726_ext_info,
    261	},
    262};
    263
    264static int mcp4725_set_value(struct iio_dev *indio_dev, int val)
    265{
    266	struct mcp4725_data *data = iio_priv(indio_dev);
    267	u8 outbuf[2];
    268	int ret;
    269
    270	if (val >= (1 << 12) || val < 0)
    271		return -EINVAL;
    272
    273	outbuf[0] = (val >> 8) & 0xf;
    274	outbuf[1] = val & 0xff;
    275
    276	ret = i2c_master_send(data->client, outbuf, 2);
    277	if (ret < 0)
    278		return ret;
    279	else if (ret != 2)
    280		return -EIO;
    281	else
    282		return 0;
    283}
    284
    285static int mcp4726_set_cfg(struct iio_dev *indio_dev)
    286{
    287	struct mcp4725_data *data = iio_priv(indio_dev);
    288	u8 outbuf[3];
    289	int ret;
    290
    291	outbuf[0] = 0x40;
    292	outbuf[0] |= data->ref_mode << 3;
    293	if (data->powerdown)
    294		outbuf[0] |= data->powerdown << 1;
    295	outbuf[1] = data->dac_value >> 4;
    296	outbuf[2] = (data->dac_value & 0xf) << 4;
    297
    298	ret = i2c_master_send(data->client, outbuf, 3);
    299	if (ret < 0)
    300		return ret;
    301	else if (ret != 3)
    302		return -EIO;
    303	else
    304		return 0;
    305}
    306
    307static int mcp4725_read_raw(struct iio_dev *indio_dev,
    308			   struct iio_chan_spec const *chan,
    309			   int *val, int *val2, long mask)
    310{
    311	struct mcp4725_data *data = iio_priv(indio_dev);
    312	int ret;
    313
    314	switch (mask) {
    315	case IIO_CHAN_INFO_RAW:
    316		*val = data->dac_value;
    317		return IIO_VAL_INT;
    318	case IIO_CHAN_INFO_SCALE:
    319		if (data->ref_mode == MCP472X_REF_VDD)
    320			ret = regulator_get_voltage(data->vdd_reg);
    321		else
    322			ret = regulator_get_voltage(data->vref_reg);
    323
    324		if (ret < 0)
    325			return ret;
    326
    327		*val = ret / 1000;
    328		*val2 = 12;
    329		return IIO_VAL_FRACTIONAL_LOG2;
    330	}
    331	return -EINVAL;
    332}
    333
    334static int mcp4725_write_raw(struct iio_dev *indio_dev,
    335			       struct iio_chan_spec const *chan,
    336			       int val, int val2, long mask)
    337{
    338	struct mcp4725_data *data = iio_priv(indio_dev);
    339	int ret;
    340
    341	switch (mask) {
    342	case IIO_CHAN_INFO_RAW:
    343		ret = mcp4725_set_value(indio_dev, val);
    344		data->dac_value = val;
    345		break;
    346	default:
    347		ret = -EINVAL;
    348		break;
    349	}
    350
    351	return ret;
    352}
    353
    354static const struct iio_info mcp4725_info = {
    355	.read_raw = mcp4725_read_raw,
    356	.write_raw = mcp4725_write_raw,
    357	.attrs = &mcp4725_attribute_group,
    358};
    359
    360static int mcp4725_probe_dt(struct device *dev,
    361			    struct mcp4725_platform_data *pdata)
    362{
    363	/* check if is the vref-supply defined */
    364	pdata->use_vref = device_property_read_bool(dev, "vref-supply");
    365	pdata->vref_buffered =
    366		device_property_read_bool(dev, "microchip,vref-buffered");
    367
    368	return 0;
    369}
    370
    371static int mcp4725_probe(struct i2c_client *client,
    372			 const struct i2c_device_id *id)
    373{
    374	struct mcp4725_data *data;
    375	struct iio_dev *indio_dev;
    376	struct mcp4725_platform_data *pdata, pdata_dt;
    377	u8 inbuf[4];
    378	u8 pd;
    379	u8 ref;
    380	int err;
    381
    382	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
    383	if (indio_dev == NULL)
    384		return -ENOMEM;
    385	data = iio_priv(indio_dev);
    386	i2c_set_clientdata(client, indio_dev);
    387	data->client = client;
    388	if (dev_fwnode(&client->dev))
    389		data->id = (uintptr_t)device_get_match_data(&client->dev);
    390	else
    391		data->id = id->driver_data;
    392	pdata = dev_get_platdata(&client->dev);
    393
    394	if (!pdata) {
    395		err = mcp4725_probe_dt(&client->dev, &pdata_dt);
    396		if (err) {
    397			dev_err(&client->dev,
    398				"invalid platform or devicetree data");
    399			return err;
    400		}
    401		pdata = &pdata_dt;
    402	}
    403
    404	if (data->id == MCP4725 && pdata->use_vref) {
    405		dev_err(&client->dev,
    406			"external reference is unavailable on MCP4725");
    407		return -EINVAL;
    408	}
    409
    410	if (!pdata->use_vref && pdata->vref_buffered) {
    411		dev_err(&client->dev,
    412			"buffering is unavailable on the internal reference");
    413		return -EINVAL;
    414	}
    415
    416	if (!pdata->use_vref)
    417		data->ref_mode = MCP472X_REF_VDD;
    418	else
    419		data->ref_mode = pdata->vref_buffered ?
    420			MCP472X_REF_VREF_BUFFERED :
    421			MCP472X_REF_VREF_UNBUFFERED;
    422
    423	data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
    424	if (IS_ERR(data->vdd_reg))
    425		return PTR_ERR(data->vdd_reg);
    426
    427	err = regulator_enable(data->vdd_reg);
    428	if (err)
    429		return err;
    430
    431	if (pdata->use_vref) {
    432		data->vref_reg = devm_regulator_get(&client->dev, "vref");
    433		if (IS_ERR(data->vref_reg)) {
    434			err = PTR_ERR(data->vref_reg);
    435			goto err_disable_vdd_reg;
    436		}
    437
    438		err = regulator_enable(data->vref_reg);
    439		if (err)
    440			goto err_disable_vdd_reg;
    441	}
    442
    443	indio_dev->name = id->name;
    444	indio_dev->info = &mcp4725_info;
    445	indio_dev->channels = &mcp472x_channel[id->driver_data];
    446	indio_dev->num_channels = 1;
    447	indio_dev->modes = INDIO_DIRECT_MODE;
    448
    449	/* read current DAC value and settings */
    450	err = i2c_master_recv(client, inbuf, data->id == MCP4725 ? 3 : 4);
    451
    452	if (err < 0) {
    453		dev_err(&client->dev, "failed to read DAC value");
    454		goto err_disable_vref_reg;
    455	}
    456	pd = (inbuf[0] >> 1) & 0x3;
    457	data->powerdown = pd > 0;
    458	data->powerdown_mode = pd ? pd - 1 : 2; /* largest resistor to gnd */
    459	data->dac_value = (inbuf[1] << 4) | (inbuf[2] >> 4);
    460	if (data->id == MCP4726)
    461		ref = (inbuf[3] >> 3) & 0x3;
    462
    463	if (data->id == MCP4726 && ref != data->ref_mode) {
    464		dev_info(&client->dev,
    465			"voltage reference mode differs (conf: %u, eeprom: %u), setting %u",
    466			data->ref_mode, ref, data->ref_mode);
    467		err = mcp4726_set_cfg(indio_dev);
    468		if (err < 0)
    469			goto err_disable_vref_reg;
    470	}
    471 
    472	err = iio_device_register(indio_dev);
    473	if (err)
    474		goto err_disable_vref_reg;
    475
    476	return 0;
    477
    478err_disable_vref_reg:
    479	if (data->vref_reg)
    480		regulator_disable(data->vref_reg);
    481
    482err_disable_vdd_reg:
    483	regulator_disable(data->vdd_reg);
    484
    485	return err;
    486}
    487
    488static int mcp4725_remove(struct i2c_client *client)
    489{
    490	struct iio_dev *indio_dev = i2c_get_clientdata(client);
    491	struct mcp4725_data *data = iio_priv(indio_dev);
    492
    493	iio_device_unregister(indio_dev);
    494
    495	if (data->vref_reg)
    496		regulator_disable(data->vref_reg);
    497	regulator_disable(data->vdd_reg);
    498
    499	return 0;
    500}
    501
    502static const struct i2c_device_id mcp4725_id[] = {
    503	{ "mcp4725", MCP4725 },
    504	{ "mcp4726", MCP4726 },
    505	{ }
    506};
    507MODULE_DEVICE_TABLE(i2c, mcp4725_id);
    508
    509static const struct of_device_id mcp4725_of_match[] = {
    510	{
    511		.compatible = "microchip,mcp4725",
    512		.data = (void *)MCP4725
    513	},
    514	{
    515		.compatible = "microchip,mcp4726",
    516		.data = (void *)MCP4726
    517	},
    518	{ }
    519};
    520MODULE_DEVICE_TABLE(of, mcp4725_of_match);
    521
    522static struct i2c_driver mcp4725_driver = {
    523	.driver = {
    524		.name	= MCP4725_DRV_NAME,
    525		.of_match_table = mcp4725_of_match,
    526		.pm	= &mcp4725_pm_ops,
    527	},
    528	.probe		= mcp4725_probe,
    529	.remove		= mcp4725_remove,
    530	.id_table	= mcp4725_id,
    531};
    532module_i2c_driver(mcp4725_driver);
    533
    534MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
    535MODULE_DESCRIPTION("MCP4725/6 12-bit DAC");
    536MODULE_LICENSE("GPL");