// SPDX-License-Identifier: GPL-2.0+ /* * Mellanox register access driver * * Copyright (C) 2018 Mellanox Technologies * Copyright (C) 2018 Vadim Pasternak */ #include #include #include #include #include #include #include #include #include /* Attribute parameters. */ #define MLXREG_IO_ATT_SIZE 10 #define MLXREG_IO_ATT_NUM 48 /** * struct mlxreg_io_priv_data - driver's private data: * * @pdev: platform device; * @pdata: platform data; * @hwmon: hwmon device; * @mlxreg_io_attr: sysfs attributes array; * @mlxreg_io_dev_attr: sysfs sensor device attribute array; * @group: sysfs attribute group; * @groups: list of sysfs attribute group for hwmon registration; */ struct mlxreg_io_priv_data { struct platform_device *pdev; struct mlxreg_core_platform_data *pdata; struct device *hwmon; struct attribute *mlxreg_io_attr[MLXREG_IO_ATT_NUM + 1]; struct sensor_device_attribute mlxreg_io_dev_attr[MLXREG_IO_ATT_NUM]; struct attribute_group group; const struct attribute_group *groups[2]; }; static int mlxreg_io_get_reg(void *regmap, struct mlxreg_core_data *data, u32 in_val, bool rw_flag, u32 *regval) { int ret; ret = regmap_read(regmap, data->reg, regval); if (ret) goto access_error; /* * There are three kinds of attributes: single bit, full register's * bits and bit sequence. For the first kind field mask indicates which * bits are not related and field bit is set zero. For the second kind * field mask is set to zero and field bit is set with all bits one. * No special handling for such kind of attributes - pass value as is. * For the third kind, field mask indicates which bits are related and * field bit is set to the first bit number (from 1 to 32) is the bit * sequence. */ if (!data->bit) { /* Single bit. */ if (rw_flag) { /* For show: expose effective bit value as 0 or 1. */ *regval = !!(*regval & ~data->mask); } else { /* For store: set effective bit value. */ *regval &= data->mask; if (in_val) *regval |= ~data->mask; } } else if (data->mask) { /* Bit sequence. */ if (rw_flag) { /* For show: mask and shift right. */ *regval = ror32(*regval & data->mask, (data->bit - 1)); } else { /* For store: shift to the position and mask. */ in_val = rol32(in_val, data->bit - 1) & data->mask; /* Clear relevant bits and set them to new value. */ *regval = (*regval & ~data->mask) | in_val; } } access_error: return ret; } static ssize_t mlxreg_io_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; struct mlxreg_core_data *data = priv->pdata->data + index; u32 regval = 0; int ret; ret = mlxreg_io_get_reg(priv->pdata->regmap, data, 0, true, ®val); if (ret) goto access_error; return sprintf(buf, "%u\n", regval); access_error: return ret; } static ssize_t mlxreg_io_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct mlxreg_io_priv_data *priv = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; struct mlxreg_core_data *data = priv->pdata->data + index; u32 input_val, regval; int ret; if (len > MLXREG_IO_ATT_SIZE) return -EINVAL; /* Convert buffer to input value. */ ret = kstrtou32(buf, len, &input_val); if (ret) return ret; ret = mlxreg_io_get_reg(priv->pdata->regmap, data, input_val, false, ®val); if (ret) goto access_error; ret = regmap_write(priv->pdata->regmap, data->reg, regval); if (ret) goto access_error; return len; access_error: dev_err(&priv->pdev->dev, "Bus access error\n"); return ret; } static struct device_attribute mlxreg_io_devattr_rw = { .show = mlxreg_io_attr_show, .store = mlxreg_io_attr_store, }; static int mlxreg_io_attr_init(struct mlxreg_io_priv_data *priv) { int i; priv->group.attrs = devm_kcalloc(&priv->pdev->dev, priv->pdata->counter, sizeof(struct attribute *), GFP_KERNEL); if (!priv->group.attrs) return -ENOMEM; for (i = 0; i < priv->pdata->counter; i++) { priv->mlxreg_io_attr[i] = &priv->mlxreg_io_dev_attr[i].dev_attr.attr; memcpy(&priv->mlxreg_io_dev_attr[i].dev_attr, &mlxreg_io_devattr_rw, sizeof(struct device_attribute)); /* Set attribute name as a label. */ priv->mlxreg_io_attr[i]->name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, priv->pdata->data[i].label); if (!priv->mlxreg_io_attr[i]->name) { dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n", i + 1); return -ENOMEM; } priv->mlxreg_io_dev_attr[i].dev_attr.attr.mode = priv->pdata->data[i].mode; priv->mlxreg_io_dev_attr[i].dev_attr.attr.name = priv->mlxreg_io_attr[i]->name; priv->mlxreg_io_dev_attr[i].index = i; sysfs_attr_init(&priv->mlxreg_io_dev_attr[i].dev_attr.attr); } priv->group.attrs = priv->mlxreg_io_attr; priv->groups[0] = &priv->group; priv->groups[1] = NULL; return 0; } static int mlxreg_io_probe(struct platform_device *pdev) { struct mlxreg_io_priv_data *priv; int err; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->pdata = dev_get_platdata(&pdev->dev); if (!priv->pdata) { dev_err(&pdev->dev, "Failed to get platform data.\n"); return -EINVAL; } priv->pdev = pdev; err = mlxreg_io_attr_init(priv); if (err) { dev_err(&priv->pdev->dev, "Failed to allocate attributes: %d\n", err); return err; } priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "mlxreg_io", priv, priv->groups); if (IS_ERR(priv->hwmon)) { dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", PTR_ERR(priv->hwmon)); return PTR_ERR(priv->hwmon); } dev_set_drvdata(&pdev->dev, priv); return 0; } static struct platform_driver mlxreg_io_driver = { .driver = { .name = "mlxreg-io", }, .probe = mlxreg_io_probe, }; module_platform_driver(mlxreg_io_driver); MODULE_AUTHOR("Vadim Pasternak "); MODULE_DESCRIPTION("Mellanox regmap I/O access driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:mlxreg-io");