| .. | .. |
|---|
| 27 | 27 | #define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2) |
|---|
| 28 | 28 | #define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ |
|---|
| 29 | 29 | #define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44 |
|---|
| 30 | | -#define MLXREG_FAN_TACHO_DIVIDER_DEF 1132 |
|---|
| 30 | +#define MLXREG_FAN_TACHO_DIV_MIN 283 |
|---|
| 31 | +#define MLXREG_FAN_TACHO_DIV_DEF (MLXREG_FAN_TACHO_DIV_MIN * 4) |
|---|
| 32 | +#define MLXREG_FAN_TACHO_DIV_SCALE_MAX 64 |
|---|
| 31 | 33 | /* |
|---|
| 32 | 34 | * FAN datasheet defines the formula for RPM calculations as RPM = 15/t-high. |
|---|
| 33 | 35 | * The logic in a programmable device measures the time t-high by sampling the |
|---|
| .. | .. |
|---|
| 51 | 53 | */ |
|---|
| 52 | 54 | #define MLXREG_FAN_GET_RPM(rval, d, s) (DIV_ROUND_CLOSEST(15000000 * 100, \ |
|---|
| 53 | 55 | ((rval) + (s)) * (d))) |
|---|
| 54 | | -#define MLXREG_FAN_GET_FAULT(val, mask) (!((val) ^ (mask))) |
|---|
| 56 | +#define MLXREG_FAN_GET_FAULT(val, mask) ((val) == (mask)) |
|---|
| 55 | 57 | #define MLXREG_FAN_PWM_DUTY2STATE(duty) (DIV_ROUND_CLOSEST((duty) * \ |
|---|
| 56 | 58 | MLXREG_FAN_MAX_STATE, \ |
|---|
| 57 | 59 | MLXREG_FAN_MAX_DUTY)) |
|---|
| .. | .. |
|---|
| 124 | 126 | err = regmap_read(fan->regmap, tacho->reg, ®val); |
|---|
| 125 | 127 | if (err) |
|---|
| 126 | 128 | return err; |
|---|
| 129 | + |
|---|
| 130 | + if (MLXREG_FAN_GET_FAULT(regval, tacho->mask)) { |
|---|
| 131 | + /* FAN is broken - return zero for FAN speed. */ |
|---|
| 132 | + *val = 0; |
|---|
| 133 | + return 0; |
|---|
| 134 | + } |
|---|
| 127 | 135 | |
|---|
| 128 | 136 | *val = MLXREG_FAN_GET_RPM(regval, fan->divider, |
|---|
| 129 | 137 | fan->samples); |
|---|
| .. | .. |
|---|
| 227 | 235 | return 0; |
|---|
| 228 | 236 | } |
|---|
| 229 | 237 | |
|---|
| 230 | | -static const u32 mlxreg_fan_hwmon_fan_config[] = { |
|---|
| 231 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 232 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 233 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 234 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 235 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 236 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 237 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 238 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 239 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 240 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 241 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 242 | | - HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 243 | | - 0 |
|---|
| 244 | | -}; |
|---|
| 245 | | - |
|---|
| 246 | | -static const struct hwmon_channel_info mlxreg_fan_hwmon_fan = { |
|---|
| 247 | | - .type = hwmon_fan, |
|---|
| 248 | | - .config = mlxreg_fan_hwmon_fan_config, |
|---|
| 249 | | -}; |
|---|
| 250 | | - |
|---|
| 251 | | -static const u32 mlxreg_fan_hwmon_pwm_config[] = { |
|---|
| 252 | | - HWMON_PWM_INPUT, |
|---|
| 253 | | - 0 |
|---|
| 254 | | -}; |
|---|
| 255 | | - |
|---|
| 256 | | -static const struct hwmon_channel_info mlxreg_fan_hwmon_pwm = { |
|---|
| 257 | | - .type = hwmon_pwm, |
|---|
| 258 | | - .config = mlxreg_fan_hwmon_pwm_config, |
|---|
| 259 | | -}; |
|---|
| 260 | | - |
|---|
| 261 | 238 | static const struct hwmon_channel_info *mlxreg_fan_hwmon_info[] = { |
|---|
| 262 | | - &mlxreg_fan_hwmon_fan, |
|---|
| 263 | | - &mlxreg_fan_hwmon_pwm, |
|---|
| 239 | + HWMON_CHANNEL_INFO(fan, |
|---|
| 240 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 241 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 242 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 243 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 244 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 245 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 246 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 247 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 248 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 249 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 250 | + HWMON_F_INPUT | HWMON_F_FAULT, |
|---|
| 251 | + HWMON_F_INPUT | HWMON_F_FAULT), |
|---|
| 252 | + HWMON_CHANNEL_INFO(pwm, |
|---|
| 253 | + HWMON_PWM_INPUT), |
|---|
| 264 | 254 | NULL |
|---|
| 265 | 255 | }; |
|---|
| 266 | 256 | |
|---|
| .. | .. |
|---|
| 366 | 356 | .set_cur_state = mlxreg_fan_set_cur_state, |
|---|
| 367 | 357 | }; |
|---|
| 368 | 358 | |
|---|
| 359 | +static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan, |
|---|
| 360 | + struct mlxreg_core_data *data) |
|---|
| 361 | +{ |
|---|
| 362 | + u32 regval; |
|---|
| 363 | + int err; |
|---|
| 364 | + |
|---|
| 365 | + err = regmap_read(fan->regmap, data->capability, ®val); |
|---|
| 366 | + if (err) { |
|---|
| 367 | + dev_err(fan->dev, "Failed to query capability register 0x%08x\n", |
|---|
| 368 | + data->capability); |
|---|
| 369 | + return err; |
|---|
| 370 | + } |
|---|
| 371 | + |
|---|
| 372 | + return !!(regval & data->bit); |
|---|
| 373 | +} |
|---|
| 374 | + |
|---|
| 375 | +static int mlxreg_fan_speed_divider_get(struct mlxreg_fan *fan, |
|---|
| 376 | + struct mlxreg_core_data *data) |
|---|
| 377 | +{ |
|---|
| 378 | + u32 regval; |
|---|
| 379 | + int err; |
|---|
| 380 | + |
|---|
| 381 | + err = regmap_read(fan->regmap, data->capability, ®val); |
|---|
| 382 | + if (err) { |
|---|
| 383 | + dev_err(fan->dev, "Failed to query capability register 0x%08x\n", |
|---|
| 384 | + data->capability); |
|---|
| 385 | + return err; |
|---|
| 386 | + } |
|---|
| 387 | + |
|---|
| 388 | + /* |
|---|
| 389 | + * Set divider value according to the capability register, in case it |
|---|
| 390 | + * contains valid value. Otherwise use default value. The purpose of |
|---|
| 391 | + * this validation is to protect against the old hardware, in which |
|---|
| 392 | + * this register can return zero. |
|---|
| 393 | + */ |
|---|
| 394 | + if (regval > 0 && regval <= MLXREG_FAN_TACHO_DIV_SCALE_MAX) |
|---|
| 395 | + fan->divider = regval * MLXREG_FAN_TACHO_DIV_MIN; |
|---|
| 396 | + |
|---|
| 397 | + return 0; |
|---|
| 398 | +} |
|---|
| 399 | + |
|---|
| 369 | 400 | static int mlxreg_fan_config(struct mlxreg_fan *fan, |
|---|
| 370 | 401 | struct mlxreg_core_platform_data *pdata) |
|---|
| 371 | 402 | { |
|---|
| 372 | 403 | struct mlxreg_core_data *data = pdata->data; |
|---|
| 373 | 404 | bool configured = false; |
|---|
| 374 | 405 | int tacho_num = 0, i; |
|---|
| 406 | + int err; |
|---|
| 375 | 407 | |
|---|
| 376 | 408 | fan->samples = MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF; |
|---|
| 377 | | - fan->divider = MLXREG_FAN_TACHO_DIVIDER_DEF; |
|---|
| 409 | + fan->divider = MLXREG_FAN_TACHO_DIV_DEF; |
|---|
| 378 | 410 | for (i = 0; i < pdata->counter; i++, data++) { |
|---|
| 379 | 411 | if (strnstr(data->label, "tacho", sizeof(data->label))) { |
|---|
| 380 | 412 | if (tacho_num == MLXREG_FAN_MAX_TACHO) { |
|---|
| .. | .. |
|---|
| 382 | 414 | data->label); |
|---|
| 383 | 415 | return -EINVAL; |
|---|
| 384 | 416 | } |
|---|
| 417 | + |
|---|
| 418 | + if (data->capability) { |
|---|
| 419 | + err = mlxreg_fan_connect_verify(fan, data); |
|---|
| 420 | + if (err < 0) |
|---|
| 421 | + return err; |
|---|
| 422 | + else if (!err) { |
|---|
| 423 | + tacho_num++; |
|---|
| 424 | + continue; |
|---|
| 425 | + } |
|---|
| 426 | + } |
|---|
| 427 | + |
|---|
| 385 | 428 | fan->tacho[tacho_num].reg = data->reg; |
|---|
| 386 | 429 | fan->tacho[tacho_num].mask = data->mask; |
|---|
| 387 | 430 | fan->tacho[tacho_num++].connected = true; |
|---|
| .. | .. |
|---|
| 400 | 443 | return -EINVAL; |
|---|
| 401 | 444 | } |
|---|
| 402 | 445 | /* Validate that conf parameters are not zeros. */ |
|---|
| 403 | | - if (!data->mask || !data->bit) { |
|---|
| 446 | + if (!data->mask && !data->bit && !data->capability) { |
|---|
| 404 | 447 | dev_err(fan->dev, "invalid conf entry params: %s\n", |
|---|
| 405 | 448 | data->label); |
|---|
| 406 | 449 | return -EINVAL; |
|---|
| 407 | 450 | } |
|---|
| 408 | | - fan->samples = data->mask; |
|---|
| 409 | | - fan->divider = data->bit; |
|---|
| 451 | + if (data->capability) { |
|---|
| 452 | + err = mlxreg_fan_speed_divider_get(fan, data); |
|---|
| 453 | + if (err) |
|---|
| 454 | + return err; |
|---|
| 455 | + } else { |
|---|
| 456 | + if (data->mask) |
|---|
| 457 | + fan->samples = data->mask; |
|---|
| 458 | + if (data->bit) |
|---|
| 459 | + fan->divider = data->bit; |
|---|
| 460 | + } |
|---|
| 410 | 461 | configured = true; |
|---|
| 411 | 462 | } else { |
|---|
| 412 | 463 | dev_err(fan->dev, "invalid label: %s\n", data->label); |
|---|
| .. | .. |
|---|
| 426 | 477 | static int mlxreg_fan_probe(struct platform_device *pdev) |
|---|
| 427 | 478 | { |
|---|
| 428 | 479 | struct mlxreg_core_platform_data *pdata; |
|---|
| 480 | + struct device *dev = &pdev->dev; |
|---|
| 429 | 481 | struct mlxreg_fan *fan; |
|---|
| 430 | 482 | struct device *hwm; |
|---|
| 431 | 483 | int err; |
|---|
| 432 | 484 | |
|---|
| 433 | | - pdata = dev_get_platdata(&pdev->dev); |
|---|
| 485 | + pdata = dev_get_platdata(dev); |
|---|
| 434 | 486 | if (!pdata) { |
|---|
| 435 | | - dev_err(&pdev->dev, "Failed to get platform data.\n"); |
|---|
| 487 | + dev_err(dev, "Failed to get platform data.\n"); |
|---|
| 436 | 488 | return -EINVAL; |
|---|
| 437 | 489 | } |
|---|
| 438 | 490 | |
|---|
| 439 | | - fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); |
|---|
| 491 | + fan = devm_kzalloc(dev, sizeof(*fan), GFP_KERNEL); |
|---|
| 440 | 492 | if (!fan) |
|---|
| 441 | 493 | return -ENOMEM; |
|---|
| 442 | 494 | |
|---|
| 443 | | - fan->dev = &pdev->dev; |
|---|
| 495 | + fan->dev = dev; |
|---|
| 444 | 496 | fan->regmap = pdata->regmap; |
|---|
| 445 | | - platform_set_drvdata(pdev, fan); |
|---|
| 446 | 497 | |
|---|
| 447 | 498 | err = mlxreg_fan_config(fan, pdata); |
|---|
| 448 | 499 | if (err) |
|---|
| 449 | 500 | return err; |
|---|
| 450 | 501 | |
|---|
| 451 | | - hwm = devm_hwmon_device_register_with_info(&pdev->dev, "mlxreg_fan", |
|---|
| 502 | + hwm = devm_hwmon_device_register_with_info(dev, "mlxreg_fan", |
|---|
| 452 | 503 | fan, |
|---|
| 453 | 504 | &mlxreg_fan_hwmon_chip_info, |
|---|
| 454 | 505 | NULL); |
|---|
| 455 | 506 | if (IS_ERR(hwm)) { |
|---|
| 456 | | - dev_err(&pdev->dev, "Failed to register hwmon device\n"); |
|---|
| 507 | + dev_err(dev, "Failed to register hwmon device\n"); |
|---|
| 457 | 508 | return PTR_ERR(hwm); |
|---|
| 458 | 509 | } |
|---|
| 459 | 510 | |
|---|
| 460 | 511 | if (IS_REACHABLE(CONFIG_THERMAL)) { |
|---|
| 461 | | - fan->cdev = thermal_cooling_device_register("mlxreg_fan", fan, |
|---|
| 462 | | - &mlxreg_fan_cooling_ops); |
|---|
| 512 | + fan->cdev = devm_thermal_of_cooling_device_register(dev, |
|---|
| 513 | + NULL, "mlxreg_fan", fan, &mlxreg_fan_cooling_ops); |
|---|
| 463 | 514 | if (IS_ERR(fan->cdev)) { |
|---|
| 464 | | - dev_err(&pdev->dev, "Failed to register cooling device\n"); |
|---|
| 515 | + dev_err(dev, "Failed to register cooling device\n"); |
|---|
| 465 | 516 | return PTR_ERR(fan->cdev); |
|---|
| 466 | 517 | } |
|---|
| 467 | 518 | } |
|---|
| 468 | | - |
|---|
| 469 | | - return 0; |
|---|
| 470 | | -} |
|---|
| 471 | | - |
|---|
| 472 | | -static int mlxreg_fan_remove(struct platform_device *pdev) |
|---|
| 473 | | -{ |
|---|
| 474 | | - struct mlxreg_fan *fan = platform_get_drvdata(pdev); |
|---|
| 475 | | - |
|---|
| 476 | | - if (IS_REACHABLE(CONFIG_THERMAL)) |
|---|
| 477 | | - thermal_cooling_device_unregister(fan->cdev); |
|---|
| 478 | 519 | |
|---|
| 479 | 520 | return 0; |
|---|
| 480 | 521 | } |
|---|
| .. | .. |
|---|
| 484 | 525 | .name = "mlxreg-fan", |
|---|
| 485 | 526 | }, |
|---|
| 486 | 527 | .probe = mlxreg_fan_probe, |
|---|
| 487 | | - .remove = mlxreg_fan_remove, |
|---|
| 488 | 528 | }; |
|---|
| 489 | 529 | |
|---|
| 490 | 530 | module_platform_driver(mlxreg_fan_driver); |
|---|