162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Mellanox register access driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2018 Mellanox Technologies
662306a36Sopenharmony_ci * Copyright (C) 2018 Vadim Pasternak <vadimp@mellanox.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/bitops.h>
1062306a36Sopenharmony_ci#include <linux/device.h>
1162306a36Sopenharmony_ci#include <linux/hwmon.h>
1262306a36Sopenharmony_ci#include <linux/hwmon-sysfs.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/platform_data/mlxreg.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/regmap.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/* Attribute parameters. */
1962306a36Sopenharmony_ci#define MLXREG_IO_ATT_SIZE	10
2062306a36Sopenharmony_ci#define MLXREG_IO_ATT_NUM	96
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/**
2362306a36Sopenharmony_ci * struct mlxreg_io_priv_data - driver's private data:
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * @pdev: platform device;
2662306a36Sopenharmony_ci * @pdata: platform data;
2762306a36Sopenharmony_ci * @hwmon: hwmon device;
2862306a36Sopenharmony_ci * @mlxreg_io_attr: sysfs attributes array;
2962306a36Sopenharmony_ci * @mlxreg_io_dev_attr: sysfs sensor device attribute array;
3062306a36Sopenharmony_ci * @group: sysfs attribute group;
3162306a36Sopenharmony_ci * @groups: list of sysfs attribute group for hwmon registration;
3262306a36Sopenharmony_ci * @regsize: size of a register value;
3362306a36Sopenharmony_ci * @io_lock: user access locking;
3462306a36Sopenharmony_ci */
3562306a36Sopenharmony_cistruct mlxreg_io_priv_data {
3662306a36Sopenharmony_ci	struct platform_device *pdev;
3762306a36Sopenharmony_ci	struct mlxreg_core_platform_data *pdata;
3862306a36Sopenharmony_ci	struct device *hwmon;
3962306a36Sopenharmony_ci	struct attribute *mlxreg_io_attr[MLXREG_IO_ATT_NUM + 1];
4062306a36Sopenharmony_ci	struct sensor_device_attribute mlxreg_io_dev_attr[MLXREG_IO_ATT_NUM];
4162306a36Sopenharmony_ci	struct attribute_group group;
4262306a36Sopenharmony_ci	const struct attribute_group *groups[2];
4362306a36Sopenharmony_ci	int regsize;
4462306a36Sopenharmony_ci	struct mutex io_lock; /* Protects user access. */
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int
4862306a36Sopenharmony_cimlxreg_io_get_reg(void *regmap, struct mlxreg_core_data *data, u32 in_val,
4962306a36Sopenharmony_ci		  bool rw_flag, int regsize, u32 *regval)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	int i, val, ret;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	ret = regmap_read(regmap, data->reg, regval);
5462306a36Sopenharmony_ci	if (ret)
5562306a36Sopenharmony_ci		goto access_error;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	/*
5862306a36Sopenharmony_ci	 * There are four kinds of attributes: single bit, full register's
5962306a36Sopenharmony_ci	 * bits, bit sequence, bits in few registers For the first kind field
6062306a36Sopenharmony_ci	 * mask indicates which bits are not related and field bit is set zero.
6162306a36Sopenharmony_ci	 * For the second kind field mask is set to zero and field bit is set
6262306a36Sopenharmony_ci	 * with all bits one. No special handling for such kind of attributes -
6362306a36Sopenharmony_ci	 * pass value as is. For the third kind, the field mask indicates which
6462306a36Sopenharmony_ci	 * bits are related and the field bit is set to the first bit number
6562306a36Sopenharmony_ci	 * (from 1 to 32) is the bit sequence. For the fourth kind - the number
6662306a36Sopenharmony_ci	 * of registers which should be read for getting an attribute are
6762306a36Sopenharmony_ci	 * specified through 'data->regnum' field.
6862306a36Sopenharmony_ci	 */
6962306a36Sopenharmony_ci	if (!data->bit) {
7062306a36Sopenharmony_ci		/* Single bit. */
7162306a36Sopenharmony_ci		if (rw_flag) {
7262306a36Sopenharmony_ci			/* For show: expose effective bit value as 0 or 1. */
7362306a36Sopenharmony_ci			*regval = !!(*regval & ~data->mask);
7462306a36Sopenharmony_ci		} else {
7562306a36Sopenharmony_ci			/* For store: set effective bit value. */
7662306a36Sopenharmony_ci			*regval &= data->mask;
7762306a36Sopenharmony_ci			if (in_val)
7862306a36Sopenharmony_ci				*regval |= ~data->mask;
7962306a36Sopenharmony_ci		}
8062306a36Sopenharmony_ci	} else if (data->mask) {
8162306a36Sopenharmony_ci		/* Bit sequence. */
8262306a36Sopenharmony_ci		if (rw_flag) {
8362306a36Sopenharmony_ci			/* For show: mask and shift right. */
8462306a36Sopenharmony_ci			*regval = ror32(*regval & data->mask, (data->bit - 1));
8562306a36Sopenharmony_ci		} else {
8662306a36Sopenharmony_ci			/* For store: shift to the position and mask. */
8762306a36Sopenharmony_ci			in_val = rol32(in_val, data->bit - 1) & data->mask;
8862306a36Sopenharmony_ci			/* Clear relevant bits and set them to new value. */
8962306a36Sopenharmony_ci			*regval = (*regval & ~data->mask) | in_val;
9062306a36Sopenharmony_ci		}
9162306a36Sopenharmony_ci	} else {
9262306a36Sopenharmony_ci		/*
9362306a36Sopenharmony_ci		 * Some attributes could occupied few registers in case regmap
9462306a36Sopenharmony_ci		 * bit size is 8 or 16. Compose such attributes from 'regnum'
9562306a36Sopenharmony_ci		 * registers. Such attributes contain read-only data.
9662306a36Sopenharmony_ci		 */
9762306a36Sopenharmony_ci		for (i = 1; i < data->regnum; i++) {
9862306a36Sopenharmony_ci			ret = regmap_read(regmap, data->reg + i, &val);
9962306a36Sopenharmony_ci			if (ret)
10062306a36Sopenharmony_ci				goto access_error;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci			*regval |= rol32(val, regsize * i * 8);
10362306a36Sopenharmony_ci		}
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ciaccess_error:
10762306a36Sopenharmony_ci	return ret;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic ssize_t
11162306a36Sopenharmony_cimlxreg_io_attr_show(struct device *dev, struct device_attribute *attr,
11262306a36Sopenharmony_ci		    char *buf)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev);
11562306a36Sopenharmony_ci	int index = to_sensor_dev_attr(attr)->index;
11662306a36Sopenharmony_ci	struct mlxreg_core_data *data = priv->pdata->data + index;
11762306a36Sopenharmony_ci	u32 regval = 0;
11862306a36Sopenharmony_ci	int ret;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	mutex_lock(&priv->io_lock);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	ret = mlxreg_io_get_reg(priv->pdata->regmap, data, 0, true,
12362306a36Sopenharmony_ci				priv->regsize, &regval);
12462306a36Sopenharmony_ci	if (ret)
12562306a36Sopenharmony_ci		goto access_error;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	mutex_unlock(&priv->io_lock);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	return sprintf(buf, "%u\n", regval);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ciaccess_error:
13262306a36Sopenharmony_ci	mutex_unlock(&priv->io_lock);
13362306a36Sopenharmony_ci	return ret;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic ssize_t
13762306a36Sopenharmony_cimlxreg_io_attr_store(struct device *dev, struct device_attribute *attr,
13862306a36Sopenharmony_ci		     const char *buf, size_t len)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev);
14162306a36Sopenharmony_ci	int index = to_sensor_dev_attr(attr)->index;
14262306a36Sopenharmony_ci	struct mlxreg_core_data *data = priv->pdata->data + index;
14362306a36Sopenharmony_ci	u32 input_val, regval;
14462306a36Sopenharmony_ci	int ret;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if (len > MLXREG_IO_ATT_SIZE)
14762306a36Sopenharmony_ci		return -EINVAL;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	/* Convert buffer to input value. */
15062306a36Sopenharmony_ci	ret = kstrtou32(buf, 0, &input_val);
15162306a36Sopenharmony_ci	if (ret)
15262306a36Sopenharmony_ci		return ret;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	mutex_lock(&priv->io_lock);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	ret = mlxreg_io_get_reg(priv->pdata->regmap, data, input_val, false,
15762306a36Sopenharmony_ci				priv->regsize, &regval);
15862306a36Sopenharmony_ci	if (ret)
15962306a36Sopenharmony_ci		goto access_error;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	ret = regmap_write(priv->pdata->regmap, data->reg, regval);
16262306a36Sopenharmony_ci	if (ret)
16362306a36Sopenharmony_ci		goto access_error;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	mutex_unlock(&priv->io_lock);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return len;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ciaccess_error:
17062306a36Sopenharmony_ci	mutex_unlock(&priv->io_lock);
17162306a36Sopenharmony_ci	dev_err(&priv->pdev->dev, "Bus access error\n");
17262306a36Sopenharmony_ci	return ret;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic struct device_attribute mlxreg_io_devattr_rw = {
17662306a36Sopenharmony_ci	.show	= mlxreg_io_attr_show,
17762306a36Sopenharmony_ci	.store	= mlxreg_io_attr_store,
17862306a36Sopenharmony_ci};
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int mlxreg_io_attr_init(struct mlxreg_io_priv_data *priv)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	int i;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	priv->group.attrs = devm_kcalloc(&priv->pdev->dev,
18562306a36Sopenharmony_ci					 priv->pdata->counter,
18662306a36Sopenharmony_ci					 sizeof(struct attribute *),
18762306a36Sopenharmony_ci					 GFP_KERNEL);
18862306a36Sopenharmony_ci	if (!priv->group.attrs)
18962306a36Sopenharmony_ci		return -ENOMEM;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	for (i = 0; i < priv->pdata->counter; i++) {
19262306a36Sopenharmony_ci		priv->mlxreg_io_attr[i] =
19362306a36Sopenharmony_ci				&priv->mlxreg_io_dev_attr[i].dev_attr.attr;
19462306a36Sopenharmony_ci		memcpy(&priv->mlxreg_io_dev_attr[i].dev_attr,
19562306a36Sopenharmony_ci		       &mlxreg_io_devattr_rw, sizeof(struct device_attribute));
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		/* Set attribute name as a label. */
19862306a36Sopenharmony_ci		priv->mlxreg_io_attr[i]->name =
19962306a36Sopenharmony_ci				devm_kasprintf(&priv->pdev->dev, GFP_KERNEL,
20062306a36Sopenharmony_ci					       priv->pdata->data[i].label);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci		if (!priv->mlxreg_io_attr[i]->name) {
20362306a36Sopenharmony_ci			dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n",
20462306a36Sopenharmony_ci				i + 1);
20562306a36Sopenharmony_ci			return -ENOMEM;
20662306a36Sopenharmony_ci		}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		priv->mlxreg_io_dev_attr[i].dev_attr.attr.mode =
20962306a36Sopenharmony_ci						priv->pdata->data[i].mode;
21062306a36Sopenharmony_ci		priv->mlxreg_io_dev_attr[i].dev_attr.attr.name =
21162306a36Sopenharmony_ci					priv->mlxreg_io_attr[i]->name;
21262306a36Sopenharmony_ci		priv->mlxreg_io_dev_attr[i].index = i;
21362306a36Sopenharmony_ci		sysfs_attr_init(&priv->mlxreg_io_dev_attr[i].dev_attr.attr);
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	priv->group.attrs = priv->mlxreg_io_attr;
21762306a36Sopenharmony_ci	priv->groups[0] = &priv->group;
21862306a36Sopenharmony_ci	priv->groups[1] = NULL;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic int mlxreg_io_probe(struct platform_device *pdev)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct mlxreg_io_priv_data *priv;
22662306a36Sopenharmony_ci	int err;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
22962306a36Sopenharmony_ci	if (!priv)
23062306a36Sopenharmony_ci		return -ENOMEM;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	priv->pdata = dev_get_platdata(&pdev->dev);
23362306a36Sopenharmony_ci	if (!priv->pdata) {
23462306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to get platform data.\n");
23562306a36Sopenharmony_ci		return -EINVAL;
23662306a36Sopenharmony_ci	}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	priv->pdev = pdev;
23962306a36Sopenharmony_ci	priv->regsize = regmap_get_val_bytes(priv->pdata->regmap);
24062306a36Sopenharmony_ci	if (priv->regsize < 0)
24162306a36Sopenharmony_ci		return priv->regsize;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	err = mlxreg_io_attr_init(priv);
24462306a36Sopenharmony_ci	if (err) {
24562306a36Sopenharmony_ci		dev_err(&priv->pdev->dev, "Failed to allocate attributes: %d\n",
24662306a36Sopenharmony_ci			err);
24762306a36Sopenharmony_ci		return err;
24862306a36Sopenharmony_ci	}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev,
25162306a36Sopenharmony_ci							     "mlxreg_io",
25262306a36Sopenharmony_ci							      priv,
25362306a36Sopenharmony_ci							      priv->groups);
25462306a36Sopenharmony_ci	if (IS_ERR(priv->hwmon)) {
25562306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to register hwmon device %ld\n",
25662306a36Sopenharmony_ci			PTR_ERR(priv->hwmon));
25762306a36Sopenharmony_ci		return PTR_ERR(priv->hwmon);
25862306a36Sopenharmony_ci	}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	mutex_init(&priv->io_lock);
26162306a36Sopenharmony_ci	dev_set_drvdata(&pdev->dev, priv);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	return 0;
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cistatic int mlxreg_io_remove(struct platform_device *pdev)
26762306a36Sopenharmony_ci{
26862306a36Sopenharmony_ci	struct mlxreg_io_priv_data *priv = dev_get_drvdata(&pdev->dev);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	mutex_destroy(&priv->io_lock);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	return 0;
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic struct platform_driver mlxreg_io_driver = {
27662306a36Sopenharmony_ci	.driver = {
27762306a36Sopenharmony_ci	    .name = "mlxreg-io",
27862306a36Sopenharmony_ci	},
27962306a36Sopenharmony_ci	.probe = mlxreg_io_probe,
28062306a36Sopenharmony_ci	.remove = mlxreg_io_remove,
28162306a36Sopenharmony_ci};
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cimodule_platform_driver(mlxreg_io_driver);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ciMODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
28662306a36Sopenharmony_ciMODULE_DESCRIPTION("Mellanox regmap I/O access driver");
28762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
28862306a36Sopenharmony_ciMODULE_ALIAS("platform:mlxreg-io");
289