On Wed, Jun 27, 2018 at 02:26:22PM +0000, Vadim Pasternak wrote:
> Driver obtains PWM and tachometers registers location according to the
> system configuration and creates FAN/PWM hwmon objects and a cooling
> device. PWM and tachometers are controlled through the on-board
> programmable device, which exports its register map. This device could be
> attached to any bus type, for which register mapping is supported. Single
> instance is created with one PWM control, up to 12 tachometers and one
> cooling device. It could be as many instances as programmable device
> supports.
> 
> Currently driver will be activated from the Mellanox platform driver:
> drivers/platform/x86/mlx-platform.c.
> For the future ARM based systems it could be activated from the ARM
> platform module.
> 
> Signed-off-by: Vadim Pasternak <[email protected]>
> ---
> v2->v3:
>  Comments pointed out by Guenter:
>  - Add documentation.
>  - Simplify MLXREG_FAN_GET_FAULT macros.
>  - Change type of fileds reg and mask in the structures
>    mlxreg_fan_tacho and mlxreg_fan_pwm from int to u32
>    for alignment with mlxreg_read and mlxreg_write.
>  - Add validation for divider and round to ensure that
>    there are no divide by zero. Validation is added to
>    the probe routine.
>  - Prevent setting of multiple "conf" entries.
>  - Use driver name directly in
>    devm_hwmon_device_register_with_info.
> v1->v2:
>  Comments pointed out by Guenter:
>  - In Kconfig change "depends on REGMAP" to select REGMAP and
>    use "depends on THERMAL || THERMAL=n".
>  - Remove include for hwmon-sysfs.h.
>  - Change comments for the structures description: remove
>    replace "/**" with "/*" and mark as for internal use.
>  - Fix multi-line comments (two occurrences).
>  - Add dev handle to mlxreg_fan private data go get rid of
>    dereferencing it.
>  - Fix layout of "if" condition in mlxreg_fan_write.
>  - Pass mlxreg_core_platform_data as argument to avoid needing it in
>    mlxreg_fan.
>  - Remove dev_info call from mlxreg_fan_config.
>  - Replace dev_set_drvdata with platform_set_drvdata.
>  - Remove assignment for name mlxreg_fan{%d}, use always mlxreg_fan.
>  - Allow driver probing in case subsystem is not configured.
>    Use IS_REACHABLE(CONFIG_THERMAL) for test.
> ---
>  Documentation/hwmon/mlxreg-fan |  60 ++++++
>  drivers/hwmon/Kconfig          |  12 ++
>  drivers/hwmon/Makefile         |   1 +
>  drivers/hwmon/mlxreg-fan.c     | 476 
> +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 549 insertions(+)
>  create mode 100644 Documentation/hwmon/mlxreg-fan
>  create mode 100644 drivers/hwmon/mlxreg-fan.c
> 
> diff --git a/Documentation/hwmon/mlxreg-fan b/Documentation/hwmon/mlxreg-fan
> new file mode 100644
> index 0000000..fc531c6
> --- /dev/null
> +++ b/Documentation/hwmon/mlxreg-fan
> @@ -0,0 +1,60 @@
> +Kernel driver mlxreg-fan
> +========================
> +
> +Provides FAN control for the next Mellanox systems:
> +QMB700, equipped with 40x200GbE InfiniBand ports;
> +MSN3700, equipped with 32x200GbE or 16x400GbE Ethernet ports;
> +MSN3410, equipped with 6x400GbE plus 48x50GbE Ethernet ports;
> +MSN3800, equipped with 64x1000GbE Ethernet ports;
> +These are the Top of the Rack systems, equipped with Mellanox switch
> +board with Mellanox Quantum or Spectrume-2 devices.
> +FAN controller is implemented by the programmable device logic.
> +
> +The default registers offsets set within the programmable device is as
> +following:
> +- pwm1                       0xe3
> +- fan1 (tacho1)              0xe4
> +- fan2 (tacho2)              0xe5
> +- fan3 (tacho3)              0xe6
> +- fan4 (tacho4)              0xe7
> +- fan5 (tacho5)              0xe8
> +- fan6 (tacho6)              0xe9
> +- fan7 (tacho7)              0xea
> +- fan8 (tacho8)              0xeb
> +- fan9 (tacho9)              0xec
> +- fan10 (tacho10)    0xed
> +- fan11 (tacho11)    0xee
> +- fan12 (tacho12)    0xef
> +This setup can be re-programmed with other registers.
> +
> +Author: Vadim Pasternak <[email protected]>
> +
> +Description
> +-----------
> +
> +The driver implements a simple interface for driving a fan connected to
> +a PWM output and tachometer inputs.
> +This driver obtains PWM and tachometers registers location according to
> +the system configuration and creates FAN/PWM hwmon objects and a cooling
> +device. PWM and tachometers are sensed through the on-board programmable
> +device, which exports its register map. This device could be attached to
> +any bus type, for which register mapping is supported.
> +Single instance is created with one PWM control, up to 12 tachometers and
> +one cooling device. It could be as many instances as programmable device
> +supports.
> +The driver exposes the fan to the user space through the hwmon's and
> +thermal's sysfs interfaces.
> +
> +/sys files in hwmon subsystem
> +-----------------------------
> +
> +fan[1-12]_fault - RO files for tachometers TACH1-TACH12 fault indication
> +fan[1-12]_input - RO files for tachometers TACH1-TACH12 input (in RPM)
> +pwm1         - RW file for fan[1-12] target duty cycle (0..255)
> +
> +/sys files in thermal subsystem
> +-------------------------------
> +
> +cur_state    - RW file for current cooling state of the cooling device
> +               (0..max_state)
> +max_state    - RO file for maximum cooling state of the cooling device
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index f10840a..cd9ec2e 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -937,6 +937,18 @@ config SENSORS_MCP3021
>         This driver can also be built as a module.  If so, the module
>         will be called mcp3021.
>  
> +config SENSORS_MLXREG_FAN
> +     tristate "Mellanox Mellanox FAN driver"
> +     depends on MELLANOX_PLATFORM
> +     depends on THERMAL || THERMAL=n
> +     select REGMAP
> +     help
> +       This option enables support for the FAN control on the Mellanox
> +       Ethernet and InfiniBand switches. The driver can be activated by the
> +       platform device add call. Say Y to enable these. To compile this
> +       driver as a module, choose 'M' here: the module will be called
> +       mlxreg-fan.
> +
>  config SENSORS_TC654
>       tristate "Microchip TC654/TC655 and compatibles"
>       depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index e7d52a3..cac3c06 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -129,6 +129,7 @@ obj-$(CONFIG_SENSORS_MAX31790)    += max31790.o
>  obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
>  obj-$(CONFIG_SENSORS_MCP3021)        += mcp3021.o
>  obj-$(CONFIG_SENSORS_TC654)  += tc654.o
> +obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
>  obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
>  obj-$(CONFIG_SENSORS_NCT6683)        += nct6683.o
>  obj-$(CONFIG_SENSORS_NCT6775)        += nct6775.o
> diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c
> new file mode 100644
> index 0000000..127e1cd
> --- /dev/null
> +++ b/drivers/hwmon/mlxreg-fan.c
> @@ -0,0 +1,476 @@
> +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
> +//
> +// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
> +// Copyright (c) 2018 Vadim Pasternak <[email protected]>
> +
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/hwmon.h>
> +#include <linux/module.h>
> +#include <linux/platform_data/mlxreg.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/thermal.h>
> +
> +#define MLXREG_FAN_MAX_TACHO         12
> +#define MLXREG_FAN_MAX_STATE         10
> +#define MLXREG_FAN_MIN_DUTY          51      /* 20% */
> +#define MLXREG_FAN_MAX_DUTY          255     /* 100% */
> +/*
> + * Minimum and maximum FAN allowed speed in percent: from 20% to 100%. Values
> + * MLXREG_FAN_MAX_STATE + x, where x is between 2 and 10 are used for
> + * setting FAN speed dynamic minimum. For example, if value is set to 14 
> (40%)
> + * cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to
> + * introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 
> 100.
> + */
> +#define MLXREG_FAN_SPEED_MIN         (MLXREG_FAN_MAX_STATE + 2)
> +#define MLXREG_FAN_SPEED_MAX         (MLXREG_FAN_MAX_STATE * 2)
> +#define MLXREG_FAN_SPEED_MIN_LEVEL   2       /* 20 percent */
> +#define MLXREG_FAN_TACHO_ROUND_DEF   500
> +#define MLXREG_FAN_TACHO_DIVIDER_DEF 1132
> +#define MLXREG_FAN_GET_RPM(val, d, r)        (15000000 / ((val) * (d) / 100 
> + (r)))
> +#define MLXREG_FAN_GET_FAULT(val, mask) (!!((val) ^ (mask)))
> +#define MLXREG_FAN_PWM_DUTY2STATE(duty)      (DIV_ROUND_CLOSEST((duty) *     
> \
> +                                      MLXREG_FAN_MAX_STATE,          \
> +                                      MLXREG_FAN_MAX_DUTY))
> +#define MLXREG_FAN_PWM_STATE2DUTY(stat)      (DIV_ROUND_CLOSEST((stat) *     
> \
> +                                      MLXREG_FAN_MAX_DUTY,           \
> +                                      MLXREG_FAN_MAX_STATE))
> +
> +/*
> + * struct mlxreg_fan_tacho - tachometer data (internal use):
> + *
> + * @connected: indicates if tachometer is connected;
> + * @reg: register offset;
> + * @mask: fault mask;
> + */
> +struct mlxreg_fan_tacho {
> +     bool connected;
> +     u32 reg;
> +     u32 mask;
> +};
> +
> +/*
> + * struct mlxreg_fan_pwm - PWM data (internal use):
> + *
> + * @connected: indicates if PWM is connected;
> + * @reg: register offset;
> + */
> +struct mlxreg_fan_pwm {
> +     bool connected;
> +     u32 reg;
> +};
> +
> +/*
> + * struct mlxreg_fan - private data (internal use):
> + *
> + * @pdev: platform device;
> + * @dev: basic device;
> + * @pdata: platform data;
> + * @tacho: tachometer data;
> + * @pwm: PWM data;
> + * @round: round value for tachometer RPM calculation;
> + * @divider: divider value for tachometer RPM calculation;
> + * @configured: indicates if device is configured with non-default 
> parameters;
> + * @cooling: cooling device levels;
> + * @cdev: cooling device;
> + */
> +struct mlxreg_fan {
> +     struct platform_device *pdev;

Not used anywhere.

> +     struct device *dev;
> +     struct mlxreg_core_platform_data *pdata;

The only use is to get regmap from it. Store regmap directly instead.

> +     struct mlxreg_fan_tacho tacho[MLXREG_FAN_MAX_TACHO];
> +     struct mlxreg_fan_pwm pwm;
> +     int round;
> +     int divider;
> +     bool configured;

Not needed here. Can be kept as local variable in mlxreg_fan_config().

> +     u8 cooling_levels[MLXREG_FAN_MAX_STATE + 1];
> +     struct thermal_cooling_device *cdev;
> +};
> +
> +static int
> +mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +             int channel, long *val)
> +{
> +     struct mlxreg_fan *mlxreg_fan = dev_get_drvdata(dev);
> +     struct mlxreg_fan_tacho *tacho;
> +     u32 regval;
> +     int err;
> +
> +     switch (type) {
> +     case hwmon_fan:
> +             tacho = &mlxreg_fan->tacho[channel];
> +             switch (attr) {
> +             case hwmon_fan_input:
> +                     err = regmap_read(mlxreg_fan->pdata->regmap,
> +                                       tacho->reg, &regval);
> +                     if (err)
> +                             return err;
> +

You'll need to make sure that regval is not 0 here to avoid a divide by 0
error.

> +                     *val = MLXREG_FAN_GET_RPM(regval, mlxreg_fan->divider,
> +                                               mlxreg_fan->round);
> +                     break;
> +
> +             case hwmon_fan_fault:
> +                     err = regmap_read(mlxreg_fan->pdata->regmap,
> +                                       tacho->reg, &regval);
> +                     if (err)
> +                             return err;
> +
> +                     *val = MLXREG_FAN_GET_FAULT(regval, tacho->mask);
> +                     break;
> +
> +             default:
> +                     return -EOPNOTSUPP;
> +             }
> +             break;
> +
> +     case hwmon_pwm:
> +             switch (attr) {
> +             case hwmon_pwm_input:
> +                     err = regmap_read(mlxreg_fan->pdata->regmap,
> +                                       mlxreg_fan->pwm.reg, &regval);
> +                     if (err)
> +                             return err;
> +
> +                     *val = regval;
> +                     break;
> +
> +             default:
> +                     return -EOPNOTSUPP;
> +             }
> +             break;
> +
> +     default:
> +             return -EOPNOTSUPP;
> +     }
> +
> +     return 0;
> +}
> +
> +static int
> +mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +              int channel, long val)
> +{
> +     struct mlxreg_fan *mlxreg_fan = dev_get_drvdata(dev);
> +
> +     switch (type) {
> +     case hwmon_pwm:
> +             switch (attr) {
> +             case hwmon_pwm_input:
> +                     if (val < MLXREG_FAN_MIN_DUTY ||
> +                         val > MLXREG_FAN_MAX_DUTY)
> +                             return -EINVAL;
> +                     return regmap_write(mlxreg_fan->pdata->regmap,
> +                                         mlxreg_fan->pwm.reg, val);
> +             default:
> +                     return -EOPNOTSUPP;
> +             }
> +             break;
> +
> +     default:
> +             return -EOPNOTSUPP;
> +     }
> +
> +     return -EOPNOTSUPP;
> +}
> +
> +static umode_t
> +mlxreg_fan_is_visible(const void *data, enum hwmon_sensor_types type, u32 
> attr,
> +                   int channel)
> +{
> +     switch (type) {
> +     case hwmon_fan:
> +             if (!(((struct mlxreg_fan *)data)->tacho[channel].connected))
> +                     return 0;
> +
> +             switch (attr) {
> +             case hwmon_fan_input:
> +             case hwmon_fan_fault:
> +                     return 0444;
> +             default:
> +                     break;
> +             }
> +             break;
> +
> +     case hwmon_pwm:

Check .connected ?

> +             switch (attr) {
> +             case hwmon_pwm_input:
> +                     return 0644;
> +             default:
> +                     break;
> +             }
> +             break;
> +
> +     default:
> +             break;
> +     }
> +
> +     return 0;
> +}
> +
> +static const u32 mlxreg_fan_hwmon_fan_config[] = {
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     HWMON_F_INPUT | HWMON_F_FAULT,
> +     0
> +};
> +
> +static const struct hwmon_channel_info mlxreg_fan_hwmon_fan = {
> +     .type = hwmon_fan,
> +     .config = mlxreg_fan_hwmon_fan_config,
> +};
> +
> +static const u32 mlxreg_fan_hwmon_pwm_config[] = {
> +     HWMON_PWM_INPUT,
> +     0
> +};
> +
> +static const struct hwmon_channel_info mlxreg_fan_hwmon_pwm = {
> +     .type = hwmon_pwm,
> +     .config = mlxreg_fan_hwmon_pwm_config,
> +};
> +
> +static const struct hwmon_channel_info *mlxreg_fan_hwmon_info[] = {
> +     &mlxreg_fan_hwmon_fan,
> +     &mlxreg_fan_hwmon_pwm,
> +     NULL
> +};
> +
> +static const struct hwmon_ops mlxreg_fan_hwmon_hwmon_ops = {
> +     .is_visible = mlxreg_fan_is_visible,
> +     .read = mlxreg_fan_read,
> +     .write = mlxreg_fan_write,
> +};
> +
> +static const struct hwmon_chip_info mlxreg_fan_hwmon_chip_info = {
> +     .ops = &mlxreg_fan_hwmon_hwmon_ops,
> +     .info = mlxreg_fan_hwmon_info,
> +};
> +
> +static int mlxreg_fan_get_max_state(struct thermal_cooling_device *cdev,
> +                                 unsigned long *state)
> +{
> +     *state = MLXREG_FAN_MAX_STATE;
> +     return 0;
> +}
> +
> +static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev,
> +                                 unsigned long *state)
> +
> +{
> +     struct mlxreg_fan *mlxreg_fan = cdev->devdata;
> +     u32 regval;
> +     int err;
> +
> +     err = regmap_read(mlxreg_fan->pdata->regmap, mlxreg_fan->pwm.reg,
> +                       &regval);
> +     if (err) {
> +             dev_err(mlxreg_fan->dev, "Failed to query PWM duty\n");
> +             return err;
> +     }
> +
> +     *state = MLXREG_FAN_PWM_DUTY2STATE(regval);
> +
> +     return 0;
> +}
> +
> +static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
> +                                 unsigned long state)
> +
> +{
> +     struct mlxreg_fan *mlxreg_fan = cdev->devdata;
> +     unsigned long cur_state;
> +     u32 regval;
> +     int i;
> +     int err;
> +
> +     /*
> +      * Verify if this request is for changing allowed FAN dynamical
> +      * minimum. If it is - update cooling levels accordingly and update
> +      * state, if current state is below the newly requested minimum state.
> +      * For example, if current state is 5, and minimal state is to be
> +      * changed from 4 to 6, mlxreg_fan->cooling_levels[0 to 5] will be
> +      * changed all from 4 to 6. And state 5 (mlxreg_fan->cooling_levels[4])
> +      * should be overwritten.
> +      */
> +     if (state >= MLXREG_FAN_SPEED_MIN && state <= MLXREG_FAN_SPEED_MAX) {
> +             state -= MLXREG_FAN_MAX_STATE;
> +             for (i = 0; i < state; i++)
> +                     mlxreg_fan->cooling_levels[i] = state;
> +             for (i = state; i <= MLXREG_FAN_MAX_STATE; i++)
> +                     mlxreg_fan->cooling_levels[i] = i;
> +
> +             err = regmap_read(mlxreg_fan->pdata->regmap,
> +                               mlxreg_fan->pwm.reg, &regval);
> +             if (err) {
> +                     dev_err(mlxreg_fan->dev, "Failed to query PWM duty\n");
> +                     return err;
> +             }
> +
> +             cur_state = MLXREG_FAN_PWM_DUTY2STATE(regval);
> +             if (state < cur_state)
> +                     return 0;
> +
> +             state = cur_state;
> +     }
> +
> +     if (state > MLXREG_FAN_MAX_STATE)
> +             return -EINVAL;
> +
> +     /* Normalize the state to the valid speed range. */
> +     state = mlxreg_fan->cooling_levels[state];
> +     err = regmap_write(mlxreg_fan->pdata->regmap, mlxreg_fan->pwm.reg,
> +                        MLXREG_FAN_PWM_STATE2DUTY(state));
> +     if (err) {
> +             dev_err(mlxreg_fan->dev, "Failed to write PWM duty\n");
> +             return err;
> +     }
> +     return 0;
> +}
> +
> +static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = {
> +     .get_max_state  = mlxreg_fan_get_max_state,
> +     .get_cur_state  = mlxreg_fan_get_cur_state,
> +     .set_cur_state  = mlxreg_fan_set_cur_state,
> +};
> +
> +static int mlxreg_fan_config(struct mlxreg_fan *mlxreg_fan,
> +                          struct mlxreg_core_platform_data *pdata)
> +{
> +     struct mlxreg_core_data *data = pdata->data;
> +     int tacho_num = 0, i;
> +
> +     mlxreg_fan->round = MLXREG_FAN_TACHO_ROUND_DEF;
> +     mlxreg_fan->divider = MLXREG_FAN_TACHO_DIVIDER_DEF;
> +     for (i = 0; i < pdata->counter; i++, data++) {
> +             if (strnstr(data->label, "tacho", sizeof(data->label))) {
> +                     if (tacho_num == MLXREG_FAN_MAX_TACHO) {
> +                             dev_err(mlxreg_fan->dev, "invalid tacho entry: 
> %s\n",

"too many tacho entries" might be better.

> +                                     data->label);
> +                             return -EINVAL;
> +                     }
> +                     mlxreg_fan->tacho[tacho_num].reg = data->reg;
> +                     mlxreg_fan->tacho[tacho_num].mask = data->mask;
> +                     mlxreg_fan->tacho[tacho_num++].connected = true;
> +             } else if (strnstr(data->label, "pwm", sizeof(data->label))) {
> +                     if (mlxreg_fan->pwm.connected) {
> +                             dev_err(mlxreg_fan->dev, "invalid pwm entry: 
> %s\n",

"duplicate" ?

> +                                     data->label);
> +                             return -EINVAL;
> +                     }
> +                     mlxreg_fan->pwm.reg = data->reg;
> +                     mlxreg_fan->pwm.connected = true;
> +             } else if (strnstr(data->label, "conf", sizeof(data->label))) {
> +                     if (mlxreg_fan->configured) {
> +                             dev_err(mlxreg_fan->dev, "invalid conf entry: 
> %s\n",
> +                                     data->label);

"duplicate" ?

> +                             return -EINVAL;
> +                     }
> +                     /* Validate that divider and round are not zeros. */
> +                     if (!mlxreg_fan->round || !mlxreg_fan->divider) {

You need to validate after writing the values, not before. As written,
the code validates the default values which is not very useful.

Also, the usage of "round" is "100 + (r)". A value of 0 is no problem.
A value of -100 is problematic. Which makes me wonder - what is the point
of the offset ? And why "round" ? This looks like a fractional divider
to me, where
        d(real) = d / (100 + (r))
It might be useful to explain that somewhere, and use a better variable
name than 'round' to describe the fraction.

> +                             dev_err(mlxreg_fan->dev, "invalid conf entry 
> params: %s\n",
> +                                     data->label);
> +                             return -EINVAL;
> +                     }
> +                     mlxreg_fan->round = data->mask;
> +                     mlxreg_fan->divider = data->bit;
> +                     mlxreg_fan->configured = true;
> +             } else {
> +                     dev_err(mlxreg_fan->dev, "invalid label: %s\n",
> +                             data->label);
> +                     return -EINVAL;
> +             }
> +     }
> +
> +     /* Init cooling levels per PWM state. */
> +     for (i = 0; i < MLXREG_FAN_SPEED_MIN_LEVEL; i++)
> +             mlxreg_fan->cooling_levels[i] = MLXREG_FAN_SPEED_MIN_LEVEL;
> +     for (i = MLXREG_FAN_SPEED_MIN_LEVEL; i <= MLXREG_FAN_MAX_STATE; i++)
> +             mlxreg_fan->cooling_levels[i] = i;
> +
> +     return 0;
> +}
> +
> +static int mlxreg_fan_probe(struct platform_device *pdev)
> +{
> +     struct mlxreg_core_platform_data *pdata;
> +     struct mlxreg_fan *mlxreg_fan;
> +     struct device *hwm;
> +     int err;
> +
> +     pdata = dev_get_platdata(&pdev->dev);
> +     if (!pdata) {
> +             dev_err(&pdev->dev, "Failed to get platform data.\n");
> +             return -EINVAL;
> +     }
> +
> +     mlxreg_fan = devm_kzalloc(&pdev->dev, sizeof(*mlxreg_fan), GFP_KERNEL);
> +     if (!mlxreg_fan)
> +             return -ENOMEM;
> +
> +     mlxreg_fan->pdev = pdev;
> +     mlxreg_fan->dev = &pdev->dev;
> +     mlxreg_fan->pdata = pdata;
> +     platform_set_drvdata(pdev, mlxreg_fan);
> +
> +     err = mlxreg_fan_config(mlxreg_fan, pdata);
> +     if (err)
> +             return err;
> +
> +     hwm = devm_hwmon_device_register_with_info(&pdev->dev, "mlxreg_fan",
> +                                                mlxreg_fan,
> +                                                &mlxreg_fan_hwmon_chip_info,
> +                                                NULL);
> +     if (IS_ERR(hwm)) {
> +             dev_err(&pdev->dev, "Failed to register hwmon device\n");
> +             return PTR_ERR(hwm);
> +     }
> +
> +     if (IS_REACHABLE(CONFIG_THERMAL)) {
> +             mlxreg_fan->cdev = thermal_cooling_device_register(
> +                                             "mlxreg_fan",
> +                                             mlxreg_fan,
> +                                             &mlxreg_fan_cooling_ops);
> +             if (IS_ERR(mlxreg_fan->cdev)) {
> +                     dev_err(&pdev->dev, "Failed to register cooling 
> device\n");
> +                     return PTR_ERR(mlxreg_fan->cdev);
> +             }
> +     }
> +
> +     return 0;
> +}
> +
> +static int mlxreg_fan_remove(struct platform_device *pdev)
> +{
> +     struct mlxreg_fan *mlxreg_fan = platform_get_drvdata(pdev);
> +
> +     if (IS_REACHABLE(CONFIG_THERMAL))
> +             thermal_cooling_device_unregister(mlxreg_fan->cdev);
> +
> +     return 0;
> +}
> +
> +static struct platform_driver mlxreg_fan_driver = {
> +     .driver = {
> +         .name = "mlxreg-fan",
> +     },
> +     .probe = mlxreg_fan_probe,
> +     .remove = mlxreg_fan_remove,
> +};
> +
> +module_platform_driver(mlxreg_fan_driver);
> +
> +MODULE_AUTHOR("Vadim Pasternak <[email protected]>");
> +MODULE_DESCRIPTION("Mellanox FAN driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:mlxreg-fan");
> -- 
> 2.1.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in
> the body of a message to [email protected]
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to