| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Marvell EBU Armada SoCs thermal sensor driver |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (C) 2013 Marvell |
|---|
| 5 | | - * |
|---|
| 6 | | - * This software is licensed under the terms of the GNU General Public |
|---|
| 7 | | - * License version 2, as published by the Free Software Foundation, and |
|---|
| 8 | | - * may be copied, distributed, and modified under those terms. |
|---|
| 9 | | - * |
|---|
| 10 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 11 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | | - * GNU General Public License for more details. |
|---|
| 14 | | - * |
|---|
| 15 | 6 | */ |
|---|
| 16 | 7 | #include <linux/device.h> |
|---|
| 17 | 8 | #include <linux/err.h> |
|---|
| .. | .. |
|---|
| 26 | 17 | #include <linux/iopoll.h> |
|---|
| 27 | 18 | #include <linux/mfd/syscon.h> |
|---|
| 28 | 19 | #include <linux/regmap.h> |
|---|
| 20 | +#include <linux/interrupt.h> |
|---|
| 21 | + |
|---|
| 22 | +#include "thermal_core.h" |
|---|
| 29 | 23 | |
|---|
| 30 | 24 | /* Thermal Manager Control and Status Register */ |
|---|
| 31 | 25 | #define PMU_TDC0_SW_RST_MASK (0x1 << 1) |
|---|
| .. | .. |
|---|
| 57 | 51 | #define CONTROL0_TSEN_MODE_EXTERNAL 0x2 |
|---|
| 58 | 52 | #define CONTROL0_TSEN_MODE_MASK 0x3 |
|---|
| 59 | 53 | |
|---|
| 60 | | -#define CONTROL1_TSEN_AVG_SHIFT 0 |
|---|
| 61 | 54 | #define CONTROL1_TSEN_AVG_MASK 0x7 |
|---|
| 62 | 55 | #define CONTROL1_EXT_TSEN_SW_RESET BIT(7) |
|---|
| 63 | 56 | #define CONTROL1_EXT_TSEN_HW_RESETn BIT(8) |
|---|
| 57 | +#define CONTROL1_TSEN_INT_EN BIT(25) |
|---|
| 58 | +#define CONTROL1_TSEN_SELECT_OFF 21 |
|---|
| 59 | +#define CONTROL1_TSEN_SELECT_MASK 0x3 |
|---|
| 64 | 60 | |
|---|
| 65 | 61 | #define STATUS_POLL_PERIOD_US 1000 |
|---|
| 66 | 62 | #define STATUS_POLL_TIMEOUT_US 100000 |
|---|
| 63 | +#define OVERHEAT_INT_POLL_DELAY_MS 1000 |
|---|
| 67 | 64 | |
|---|
| 68 | 65 | struct armada_thermal_data; |
|---|
| 69 | 66 | |
|---|
| .. | .. |
|---|
| 75 | 72 | /* serialize temperature reads/updates */ |
|---|
| 76 | 73 | struct mutex update_lock; |
|---|
| 77 | 74 | struct armada_thermal_data *data; |
|---|
| 75 | + struct thermal_zone_device *overheat_sensor; |
|---|
| 76 | + int interrupt_source; |
|---|
| 78 | 77 | int current_channel; |
|---|
| 78 | + long current_threshold; |
|---|
| 79 | + long current_hysteresis; |
|---|
| 79 | 80 | }; |
|---|
| 80 | 81 | |
|---|
| 81 | 82 | struct armada_thermal_data { |
|---|
| .. | .. |
|---|
| 93 | 94 | /* Register shift and mask to access the sensor temperature */ |
|---|
| 94 | 95 | unsigned int temp_shift; |
|---|
| 95 | 96 | unsigned int temp_mask; |
|---|
| 97 | + unsigned int thresh_shift; |
|---|
| 98 | + unsigned int hyst_shift; |
|---|
| 99 | + unsigned int hyst_mask; |
|---|
| 96 | 100 | u32 is_valid_bit; |
|---|
| 97 | 101 | |
|---|
| 98 | 102 | /* Syscon access */ |
|---|
| 99 | 103 | unsigned int syscon_control0_off; |
|---|
| 100 | 104 | unsigned int syscon_control1_off; |
|---|
| 101 | 105 | unsigned int syscon_status_off; |
|---|
| 106 | + unsigned int dfx_irq_cause_off; |
|---|
| 107 | + unsigned int dfx_irq_mask_off; |
|---|
| 108 | + unsigned int dfx_overheat_irq; |
|---|
| 109 | + unsigned int dfx_server_irq_mask_off; |
|---|
| 110 | + unsigned int dfx_server_irq_en; |
|---|
| 102 | 111 | |
|---|
| 103 | 112 | /* One sensor is in the thermal IC, the others are in the CPUs if any */ |
|---|
| 104 | 113 | unsigned int cpu_nr; |
|---|
| .. | .. |
|---|
| 142 | 151 | /* Reset the sensor */ |
|---|
| 143 | 152 | reg |= PMU_TDC0_SW_RST_MASK; |
|---|
| 144 | 153 | |
|---|
| 154 | + regmap_write(priv->syscon, data->syscon_control1_off, reg); |
|---|
| 155 | + |
|---|
| 156 | + reg &= ~PMU_TDC0_SW_RST_MASK; |
|---|
| 145 | 157 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
|---|
| 146 | 158 | |
|---|
| 147 | 159 | /* Enable the sensor */ |
|---|
| .. | .. |
|---|
| 255 | 267 | |
|---|
| 256 | 268 | /* Average the output value over 2^1 = 2 samples */ |
|---|
| 257 | 269 | regmap_read(priv->syscon, data->syscon_control1_off, ®); |
|---|
| 258 | | - reg &= ~CONTROL1_TSEN_AVG_MASK << CONTROL1_TSEN_AVG_SHIFT; |
|---|
| 259 | | - reg |= 1 << CONTROL1_TSEN_AVG_SHIFT; |
|---|
| 270 | + reg &= ~CONTROL1_TSEN_AVG_MASK; |
|---|
| 271 | + reg |= 1; |
|---|
| 260 | 272 | regmap_write(priv->syscon, data->syscon_control1_off, reg); |
|---|
| 261 | 273 | } |
|---|
| 262 | 274 | |
|---|
| .. | .. |
|---|
| 270 | 282 | regmap_read(priv->syscon, priv->data->syscon_status_off, ®); |
|---|
| 271 | 283 | |
|---|
| 272 | 284 | return reg & priv->data->is_valid_bit; |
|---|
| 285 | +} |
|---|
| 286 | + |
|---|
| 287 | +static void armada_enable_overheat_interrupt(struct armada_thermal_priv *priv) |
|---|
| 288 | +{ |
|---|
| 289 | + struct armada_thermal_data *data = priv->data; |
|---|
| 290 | + u32 reg; |
|---|
| 291 | + |
|---|
| 292 | + /* Clear DFX temperature IRQ cause */ |
|---|
| 293 | + regmap_read(priv->syscon, data->dfx_irq_cause_off, ®); |
|---|
| 294 | + |
|---|
| 295 | + /* Enable DFX Temperature IRQ */ |
|---|
| 296 | + regmap_read(priv->syscon, data->dfx_irq_mask_off, ®); |
|---|
| 297 | + reg |= data->dfx_overheat_irq; |
|---|
| 298 | + regmap_write(priv->syscon, data->dfx_irq_mask_off, reg); |
|---|
| 299 | + |
|---|
| 300 | + /* Enable DFX server IRQ */ |
|---|
| 301 | + regmap_read(priv->syscon, data->dfx_server_irq_mask_off, ®); |
|---|
| 302 | + reg |= data->dfx_server_irq_en; |
|---|
| 303 | + regmap_write(priv->syscon, data->dfx_server_irq_mask_off, reg); |
|---|
| 304 | + |
|---|
| 305 | + /* Enable overheat interrupt */ |
|---|
| 306 | + regmap_read(priv->syscon, data->syscon_control1_off, ®); |
|---|
| 307 | + reg |= CONTROL1_TSEN_INT_EN; |
|---|
| 308 | + regmap_write(priv->syscon, data->syscon_control1_off, reg); |
|---|
| 309 | +} |
|---|
| 310 | + |
|---|
| 311 | +static void __maybe_unused |
|---|
| 312 | +armada_disable_overheat_interrupt(struct armada_thermal_priv *priv) |
|---|
| 313 | +{ |
|---|
| 314 | + struct armada_thermal_data *data = priv->data; |
|---|
| 315 | + u32 reg; |
|---|
| 316 | + |
|---|
| 317 | + regmap_read(priv->syscon, data->syscon_control1_off, ®); |
|---|
| 318 | + reg &= ~CONTROL1_TSEN_INT_EN; |
|---|
| 319 | + regmap_write(priv->syscon, data->syscon_control1_off, reg); |
|---|
| 273 | 320 | } |
|---|
| 274 | 321 | |
|---|
| 275 | 322 | /* There is currently no board with more than one sensor per channel */ |
|---|
| .. | .. |
|---|
| 388 | 435 | |
|---|
| 389 | 436 | /* Do the actual reading */ |
|---|
| 390 | 437 | ret = armada_read_sensor(priv, temp); |
|---|
| 438 | + if (ret) |
|---|
| 439 | + goto unlock_mutex; |
|---|
| 440 | + |
|---|
| 441 | + /* |
|---|
| 442 | + * Select back the interrupt source channel from which a potential |
|---|
| 443 | + * critical trip point has been set. |
|---|
| 444 | + */ |
|---|
| 445 | + ret = armada_select_channel(priv, priv->interrupt_source); |
|---|
| 391 | 446 | |
|---|
| 392 | 447 | unlock_mutex: |
|---|
| 393 | 448 | mutex_unlock(&priv->update_lock); |
|---|
| .. | .. |
|---|
| 395 | 450 | return ret; |
|---|
| 396 | 451 | } |
|---|
| 397 | 452 | |
|---|
| 398 | | -static struct thermal_zone_of_device_ops of_ops = { |
|---|
| 453 | +static const struct thermal_zone_of_device_ops of_ops = { |
|---|
| 399 | 454 | .get_temp = armada_get_temp, |
|---|
| 400 | 455 | }; |
|---|
| 456 | + |
|---|
| 457 | +static unsigned int armada_mc_to_reg_temp(struct armada_thermal_data *data, |
|---|
| 458 | + unsigned int temp_mc) |
|---|
| 459 | +{ |
|---|
| 460 | + s64 b = data->coef_b; |
|---|
| 461 | + s64 m = data->coef_m; |
|---|
| 462 | + s64 div = data->coef_div; |
|---|
| 463 | + unsigned int sample; |
|---|
| 464 | + |
|---|
| 465 | + if (data->inverted) |
|---|
| 466 | + sample = div_s64(((temp_mc * div) + b), m); |
|---|
| 467 | + else |
|---|
| 468 | + sample = div_s64((b - (temp_mc * div)), m); |
|---|
| 469 | + |
|---|
| 470 | + return sample & data->temp_mask; |
|---|
| 471 | +} |
|---|
| 472 | + |
|---|
| 473 | +/* |
|---|
| 474 | + * The documentation states: |
|---|
| 475 | + * high/low watermark = threshold +/- 0.4761 * 2^(hysteresis + 2) |
|---|
| 476 | + * which is the mathematical derivation for: |
|---|
| 477 | + * 0x0 <=> 1.9°C, 0x1 <=> 3.8°C, 0x2 <=> 7.6°C, 0x3 <=> 15.2°C |
|---|
| 478 | + */ |
|---|
| 479 | +static unsigned int hyst_levels_mc[] = {1900, 3800, 7600, 15200}; |
|---|
| 480 | + |
|---|
| 481 | +static unsigned int armada_mc_to_reg_hyst(struct armada_thermal_data *data, |
|---|
| 482 | + unsigned int hyst_mc) |
|---|
| 483 | +{ |
|---|
| 484 | + int i; |
|---|
| 485 | + |
|---|
| 486 | + /* |
|---|
| 487 | + * We will always take the smallest possible hysteresis to avoid risking |
|---|
| 488 | + * the hardware integrity by enlarging the threshold by +8°C in the |
|---|
| 489 | + * worst case. |
|---|
| 490 | + */ |
|---|
| 491 | + for (i = ARRAY_SIZE(hyst_levels_mc) - 1; i > 0; i--) |
|---|
| 492 | + if (hyst_mc >= hyst_levels_mc[i]) |
|---|
| 493 | + break; |
|---|
| 494 | + |
|---|
| 495 | + return i & data->hyst_mask; |
|---|
| 496 | +} |
|---|
| 497 | + |
|---|
| 498 | +static void armada_set_overheat_thresholds(struct armada_thermal_priv *priv, |
|---|
| 499 | + int thresh_mc, int hyst_mc) |
|---|
| 500 | +{ |
|---|
| 501 | + struct armada_thermal_data *data = priv->data; |
|---|
| 502 | + unsigned int threshold = armada_mc_to_reg_temp(data, thresh_mc); |
|---|
| 503 | + unsigned int hysteresis = armada_mc_to_reg_hyst(data, hyst_mc); |
|---|
| 504 | + u32 ctrl1; |
|---|
| 505 | + |
|---|
| 506 | + regmap_read(priv->syscon, data->syscon_control1_off, &ctrl1); |
|---|
| 507 | + |
|---|
| 508 | + /* Set Threshold */ |
|---|
| 509 | + if (thresh_mc >= 0) { |
|---|
| 510 | + ctrl1 &= ~(data->temp_mask << data->thresh_shift); |
|---|
| 511 | + ctrl1 |= threshold << data->thresh_shift; |
|---|
| 512 | + priv->current_threshold = thresh_mc; |
|---|
| 513 | + } |
|---|
| 514 | + |
|---|
| 515 | + /* Set Hysteresis */ |
|---|
| 516 | + if (hyst_mc >= 0) { |
|---|
| 517 | + ctrl1 &= ~(data->hyst_mask << data->hyst_shift); |
|---|
| 518 | + ctrl1 |= hysteresis << data->hyst_shift; |
|---|
| 519 | + priv->current_hysteresis = hyst_mc; |
|---|
| 520 | + } |
|---|
| 521 | + |
|---|
| 522 | + regmap_write(priv->syscon, data->syscon_control1_off, ctrl1); |
|---|
| 523 | +} |
|---|
| 524 | + |
|---|
| 525 | +static irqreturn_t armada_overheat_isr(int irq, void *blob) |
|---|
| 526 | +{ |
|---|
| 527 | + /* |
|---|
| 528 | + * Disable the IRQ and continue in thread context (thermal core |
|---|
| 529 | + * notification and temperature monitoring). |
|---|
| 530 | + */ |
|---|
| 531 | + disable_irq_nosync(irq); |
|---|
| 532 | + |
|---|
| 533 | + return IRQ_WAKE_THREAD; |
|---|
| 534 | +} |
|---|
| 535 | + |
|---|
| 536 | +static irqreturn_t armada_overheat_isr_thread(int irq, void *blob) |
|---|
| 537 | +{ |
|---|
| 538 | + struct armada_thermal_priv *priv = blob; |
|---|
| 539 | + int low_threshold = priv->current_threshold - priv->current_hysteresis; |
|---|
| 540 | + int temperature; |
|---|
| 541 | + u32 dummy; |
|---|
| 542 | + int ret; |
|---|
| 543 | + |
|---|
| 544 | + /* Notify the core in thread context */ |
|---|
| 545 | + thermal_zone_device_update(priv->overheat_sensor, |
|---|
| 546 | + THERMAL_EVENT_UNSPECIFIED); |
|---|
| 547 | + |
|---|
| 548 | + /* |
|---|
| 549 | + * The overheat interrupt must be cleared by reading the DFX interrupt |
|---|
| 550 | + * cause _after_ the temperature has fallen down to the low threshold. |
|---|
| 551 | + * Otherwise future interrupts might not be served. |
|---|
| 552 | + */ |
|---|
| 553 | + do { |
|---|
| 554 | + msleep(OVERHEAT_INT_POLL_DELAY_MS); |
|---|
| 555 | + mutex_lock(&priv->update_lock); |
|---|
| 556 | + ret = armada_read_sensor(priv, &temperature); |
|---|
| 557 | + mutex_unlock(&priv->update_lock); |
|---|
| 558 | + if (ret) |
|---|
| 559 | + goto enable_irq; |
|---|
| 560 | + } while (temperature >= low_threshold); |
|---|
| 561 | + |
|---|
| 562 | + regmap_read(priv->syscon, priv->data->dfx_irq_cause_off, &dummy); |
|---|
| 563 | + |
|---|
| 564 | + /* Notify the thermal core that the temperature is acceptable again */ |
|---|
| 565 | + thermal_zone_device_update(priv->overheat_sensor, |
|---|
| 566 | + THERMAL_EVENT_UNSPECIFIED); |
|---|
| 567 | + |
|---|
| 568 | +enable_irq: |
|---|
| 569 | + enable_irq(irq); |
|---|
| 570 | + |
|---|
| 571 | + return IRQ_HANDLED; |
|---|
| 572 | +} |
|---|
| 401 | 573 | |
|---|
| 402 | 574 | static const struct armada_thermal_data armadaxp_data = { |
|---|
| 403 | 575 | .init = armadaxp_init, |
|---|
| .. | .. |
|---|
| 407 | 579 | .coef_m = 10000000ULL, |
|---|
| 408 | 580 | .coef_div = 13825, |
|---|
| 409 | 581 | .syscon_status_off = 0xb0, |
|---|
| 410 | | - .syscon_control1_off = 0xd0, |
|---|
| 582 | + .syscon_control1_off = 0x2d0, |
|---|
| 411 | 583 | }; |
|---|
| 412 | 584 | |
|---|
| 413 | 585 | static const struct armada_thermal_data armada370_data = { |
|---|
| .. | .. |
|---|
| 454 | 626 | .is_valid_bit = BIT(16), |
|---|
| 455 | 627 | .temp_shift = 0, |
|---|
| 456 | 628 | .temp_mask = 0x3ff, |
|---|
| 629 | + .thresh_shift = 3, |
|---|
| 630 | + .hyst_shift = 19, |
|---|
| 631 | + .hyst_mask = 0x3, |
|---|
| 457 | 632 | .coef_b = -150000LL, |
|---|
| 458 | 633 | .coef_m = 423ULL, |
|---|
| 459 | 634 | .coef_div = 1, |
|---|
| .. | .. |
|---|
| 462 | 637 | .syscon_control0_off = 0x84, |
|---|
| 463 | 638 | .syscon_control1_off = 0x88, |
|---|
| 464 | 639 | .syscon_status_off = 0x8C, |
|---|
| 640 | + .dfx_irq_cause_off = 0x108, |
|---|
| 641 | + .dfx_irq_mask_off = 0x10C, |
|---|
| 642 | + .dfx_overheat_irq = BIT(22), |
|---|
| 643 | + .dfx_server_irq_mask_off = 0x104, |
|---|
| 644 | + .dfx_server_irq_en = BIT(1), |
|---|
| 465 | 645 | .cpu_nr = 4, |
|---|
| 466 | 646 | }; |
|---|
| 467 | 647 | |
|---|
| .. | .. |
|---|
| 470 | 650 | .is_valid_bit = BIT(10), |
|---|
| 471 | 651 | .temp_shift = 0, |
|---|
| 472 | 652 | .temp_mask = 0x3ff, |
|---|
| 653 | + .thresh_shift = 16, |
|---|
| 654 | + .hyst_shift = 26, |
|---|
| 655 | + .hyst_mask = 0x3, |
|---|
| 473 | 656 | .coef_b = 1172499100ULL, |
|---|
| 474 | 657 | .coef_m = 2000096ULL, |
|---|
| 475 | 658 | .coef_div = 4201, |
|---|
| .. | .. |
|---|
| 477 | 660 | .syscon_control0_off = 0x70, |
|---|
| 478 | 661 | .syscon_control1_off = 0x74, |
|---|
| 479 | 662 | .syscon_status_off = 0x78, |
|---|
| 663 | + .dfx_irq_cause_off = 0x108, |
|---|
| 664 | + .dfx_irq_mask_off = 0x10C, |
|---|
| 665 | + .dfx_overheat_irq = BIT(20), |
|---|
| 666 | + .dfx_server_irq_mask_off = 0x104, |
|---|
| 667 | + .dfx_server_irq_en = BIT(1), |
|---|
| 480 | 668 | }; |
|---|
| 481 | 669 | |
|---|
| 482 | 670 | static const struct of_device_id armada_thermal_id_table[] = { |
|---|
| .. | .. |
|---|
| 526 | 714 | |
|---|
| 527 | 715 | /* First memory region points towards the status register */ |
|---|
| 528 | 716 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|---|
| 529 | | - if (!res) |
|---|
| 530 | | - return -EIO; |
|---|
| 531 | | - |
|---|
| 532 | | - /* |
|---|
| 533 | | - * Edit the resource start address and length to map over all the |
|---|
| 534 | | - * registers, instead of pointing at them one by one. |
|---|
| 535 | | - */ |
|---|
| 536 | | - res->start -= data->syscon_status_off; |
|---|
| 537 | | - res->end = res->start + max(data->syscon_status_off, |
|---|
| 538 | | - max(data->syscon_control0_off, |
|---|
| 539 | | - data->syscon_control1_off)) + |
|---|
| 540 | | - sizeof(unsigned int) - 1; |
|---|
| 541 | | - |
|---|
| 542 | 717 | base = devm_ioremap_resource(&pdev->dev, res); |
|---|
| 543 | 718 | if (IS_ERR(base)) |
|---|
| 544 | 719 | return PTR_ERR(base); |
|---|
| 545 | 720 | |
|---|
| 721 | + /* |
|---|
| 722 | + * Fix up from the old individual DT register specification to |
|---|
| 723 | + * cover all the registers. We do this by adjusting the ioremap() |
|---|
| 724 | + * result, which should be fine as ioremap() deals with pages. |
|---|
| 725 | + * However, validate that we do not cross a page boundary while |
|---|
| 726 | + * making this adjustment. |
|---|
| 727 | + */ |
|---|
| 728 | + if (((unsigned long)base & ~PAGE_MASK) < data->syscon_status_off) |
|---|
| 729 | + return -EINVAL; |
|---|
| 730 | + base -= data->syscon_status_off; |
|---|
| 731 | + |
|---|
| 546 | 732 | priv->syscon = devm_regmap_init_mmio(&pdev->dev, base, |
|---|
| 547 | 733 | &armada_thermal_regmap_config); |
|---|
| 548 | | - if (IS_ERR(priv->syscon)) |
|---|
| 549 | | - return PTR_ERR(priv->syscon); |
|---|
| 550 | | - |
|---|
| 551 | | - return 0; |
|---|
| 734 | + return PTR_ERR_OR_ZERO(priv->syscon); |
|---|
| 552 | 735 | } |
|---|
| 553 | 736 | |
|---|
| 554 | 737 | static int armada_thermal_probe_syscon(struct platform_device *pdev, |
|---|
| 555 | 738 | struct armada_thermal_priv *priv) |
|---|
| 556 | 739 | { |
|---|
| 557 | 740 | priv->syscon = syscon_node_to_regmap(pdev->dev.parent->of_node); |
|---|
| 558 | | - if (IS_ERR(priv->syscon)) |
|---|
| 559 | | - return PTR_ERR(priv->syscon); |
|---|
| 560 | | - |
|---|
| 561 | | - return 0; |
|---|
| 741 | + return PTR_ERR_OR_ZERO(priv->syscon); |
|---|
| 562 | 742 | } |
|---|
| 563 | 743 | |
|---|
| 564 | 744 | static void armada_set_sane_name(struct platform_device *pdev, |
|---|
| .. | .. |
|---|
| 592 | 772 | } while (insane_char); |
|---|
| 593 | 773 | } |
|---|
| 594 | 774 | |
|---|
| 775 | +/* |
|---|
| 776 | + * The IP can manage to trigger interrupts on overheat situation from all the |
|---|
| 777 | + * sensors. However, the interrupt source changes along with the last selected |
|---|
| 778 | + * source (ie. the last read sensor), which is an inconsistent behavior. Avoid |
|---|
| 779 | + * possible glitches by always selecting back only one channel (arbitrarily: the |
|---|
| 780 | + * first in the DT which has a critical trip point). We also disable sensor |
|---|
| 781 | + * switch during overheat situations. |
|---|
| 782 | + */ |
|---|
| 783 | +static int armada_configure_overheat_int(struct armada_thermal_priv *priv, |
|---|
| 784 | + struct thermal_zone_device *tz, |
|---|
| 785 | + int sensor_id) |
|---|
| 786 | +{ |
|---|
| 787 | + /* Retrieve the critical trip point to enable the overheat interrupt */ |
|---|
| 788 | + const struct thermal_trip *trips = of_thermal_get_trip_points(tz); |
|---|
| 789 | + int ret; |
|---|
| 790 | + int i; |
|---|
| 791 | + |
|---|
| 792 | + if (!trips) |
|---|
| 793 | + return -EINVAL; |
|---|
| 794 | + |
|---|
| 795 | + for (i = 0; i < of_thermal_get_ntrips(tz); i++) |
|---|
| 796 | + if (trips[i].type == THERMAL_TRIP_CRITICAL) |
|---|
| 797 | + break; |
|---|
| 798 | + |
|---|
| 799 | + if (i == of_thermal_get_ntrips(tz)) |
|---|
| 800 | + return -EINVAL; |
|---|
| 801 | + |
|---|
| 802 | + ret = armada_select_channel(priv, sensor_id); |
|---|
| 803 | + if (ret) |
|---|
| 804 | + return ret; |
|---|
| 805 | + |
|---|
| 806 | + armada_set_overheat_thresholds(priv, |
|---|
| 807 | + trips[i].temperature, |
|---|
| 808 | + trips[i].hysteresis); |
|---|
| 809 | + priv->overheat_sensor = tz; |
|---|
| 810 | + priv->interrupt_source = sensor_id; |
|---|
| 811 | + |
|---|
| 812 | + armada_enable_overheat_interrupt(priv); |
|---|
| 813 | + |
|---|
| 814 | + return 0; |
|---|
| 815 | +} |
|---|
| 816 | + |
|---|
| 595 | 817 | static int armada_thermal_probe(struct platform_device *pdev) |
|---|
| 596 | 818 | { |
|---|
| 597 | 819 | struct thermal_zone_device *tz; |
|---|
| .. | .. |
|---|
| 599 | 821 | struct armada_drvdata *drvdata; |
|---|
| 600 | 822 | const struct of_device_id *match; |
|---|
| 601 | 823 | struct armada_thermal_priv *priv; |
|---|
| 602 | | - int sensor_id; |
|---|
| 824 | + int sensor_id, irq; |
|---|
| 603 | 825 | int ret; |
|---|
| 604 | 826 | |
|---|
| 605 | 827 | match = of_match_device(armada_thermal_id_table, &pdev->dev); |
|---|
| .. | .. |
|---|
| 652 | 874 | return PTR_ERR(tz); |
|---|
| 653 | 875 | } |
|---|
| 654 | 876 | |
|---|
| 877 | + ret = thermal_zone_device_enable(tz); |
|---|
| 878 | + if (ret) { |
|---|
| 879 | + thermal_zone_device_unregister(tz); |
|---|
| 880 | + return ret; |
|---|
| 881 | + } |
|---|
| 882 | + |
|---|
| 655 | 883 | drvdata->type = LEGACY; |
|---|
| 656 | 884 | drvdata->data.tz = tz; |
|---|
| 657 | 885 | platform_set_drvdata(pdev, drvdata); |
|---|
| .. | .. |
|---|
| 668 | 896 | drvdata->type = SYSCON; |
|---|
| 669 | 897 | drvdata->data.priv = priv; |
|---|
| 670 | 898 | platform_set_drvdata(pdev, drvdata); |
|---|
| 899 | + |
|---|
| 900 | + irq = platform_get_irq(pdev, 0); |
|---|
| 901 | + if (irq == -EPROBE_DEFER) |
|---|
| 902 | + return irq; |
|---|
| 903 | + |
|---|
| 904 | + /* The overheat interrupt feature is not mandatory */ |
|---|
| 905 | + if (irq > 0) { |
|---|
| 906 | + ret = devm_request_threaded_irq(&pdev->dev, irq, |
|---|
| 907 | + armada_overheat_isr, |
|---|
| 908 | + armada_overheat_isr_thread, |
|---|
| 909 | + 0, NULL, priv); |
|---|
| 910 | + if (ret) { |
|---|
| 911 | + dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n", |
|---|
| 912 | + irq); |
|---|
| 913 | + return ret; |
|---|
| 914 | + } |
|---|
| 915 | + } |
|---|
| 671 | 916 | |
|---|
| 672 | 917 | /* |
|---|
| 673 | 918 | * There is one channel for the IC and one per CPU (if any), each |
|---|
| .. | .. |
|---|
| 692 | 937 | devm_kfree(&pdev->dev, sensor); |
|---|
| 693 | 938 | continue; |
|---|
| 694 | 939 | } |
|---|
| 940 | + |
|---|
| 941 | + /* |
|---|
| 942 | + * The first channel that has a critical trip point registered |
|---|
| 943 | + * in the DT will serve as interrupt source. Others possible |
|---|
| 944 | + * critical trip points will simply be ignored by the driver. |
|---|
| 945 | + */ |
|---|
| 946 | + if (irq > 0 && !priv->overheat_sensor) |
|---|
| 947 | + armada_configure_overheat_int(priv, tz, sensor->id); |
|---|
| 695 | 948 | } |
|---|
| 696 | 949 | |
|---|
| 950 | + /* Just complain if no overheat interrupt was set up */ |
|---|
| 951 | + if (!priv->overheat_sensor) |
|---|
| 952 | + dev_warn(&pdev->dev, "Overheat interrupt not available\n"); |
|---|
| 953 | + |
|---|
| 697 | 954 | return 0; |
|---|
| 698 | 955 | } |
|---|
| 699 | 956 | |
|---|