]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 26 Jun 2015 00:51:55 +0000 (17:51 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 26 Jun 2015 00:51:55 +0000 (17:51 -0700)
Pull thermal management updates from Zhang Rui:
 "Specifics:

   - enhance Thermal Framework with several new capabilities:

       * use power estimates
       * compute weights with relative integers instead of percentages
       * allow governors to have private data in thermal zones
       * export thermal zone parameters through sysfs

     Thanks to the ARM thermal team (Javi, Punit, KP).

   - introduce a new thermal governor: power allocator.  First in kernel
     closed loop PI(D) controller for thermal control.  Thanks to ARM
     thermal team.

   - enhance OF thermal to allow thermal zones to have sustainable power
     HW specification.  Thanks to Punit.

   - introduce thermal driver for Intel Quark SoC x1000platform.  Thanks
     to Ong, Boon Leong.

   - introduce QPNP PMIC temperature alarm driver.  Thanks to Ivan T. I.

   - introduce thermal driver for Hisilicon hi6220.  Thanks to
     kongxinwei.

   - enhance Exynos thermal driver to handle Exynos5433 TMU.  Thanks to
     Chanwoo C.

   - TI thermal driver now has a better implementation for EOCZ bit.
     From Pavel M.

   - add id for Skylake processors in int340x processor thermal driver.

   - a couple of small fixes and cleanups."

* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (36 commits)
  thermal: hisilicon: add new hisilicon thermal sensor driver
  dt-bindings: Document the hi6220 thermal sensor bindings
  thermal: of-thermal: add support for reading coefficients property
  thermal: support slope and offset coefficients
  thermal: power_allocator: round the division when divvying up power
  thermal: exynos: Add the support for Exynos5433 TMU
  thermal: cpu_cooling: Fix power calculation when CPUs are offline
  thermal: cpu_cooling: Remove cpu_dev update on policy CPU update
  thermal: export thermal_zone_parameters to sysfs
  thermal: cpu_cooling: Check memory allocation of power_table
  ti-soc-thermal: request temperature periodically if hw can't do that itself
  ti-soc-thermal: implement eocz bit to make driver useful on omap3
  cleanup ti-soc-thermal
  thermal: remove stale THERMAL_POWER_ACTOR select
  thermal: Default OF created trip points to writable
  thermal: core: Add Kconfig option to enable writable trips
  thermal: x86_pkg_temp: drop const for thermal_zone_parameters
  of: thermal: Introduce sustainable power for a thermal zone
  thermal: add trace events to the power allocator governor
  thermal: introduce the Power Allocator governor
  ...

35 files changed:
Documentation/devicetree/bindings/thermal/hisilicon-thermal.txt [new file with mode: 0644]
Documentation/devicetree/bindings/thermal/qcom-spmi-temp-alarm.txt [new file with mode: 0644]
Documentation/devicetree/bindings/thermal/thermal.txt
Documentation/thermal/cpu-cooling-api.txt
Documentation/thermal/power_allocator.txt [new file with mode: 0644]
Documentation/thermal/sysfs-api.txt
drivers/acpi/thermal.c
drivers/platform/x86/acerhdf.c
drivers/thermal/Kconfig
drivers/thermal/Makefile
drivers/thermal/cpu_cooling.c
drivers/thermal/db8500_thermal.c
drivers/thermal/fair_share.c
drivers/thermal/hisi_thermal.c [new file with mode: 0644]
drivers/thermal/imx_thermal.c
drivers/thermal/int340x_thermal/processor_thermal_device.c
drivers/thermal/intel_powerclamp.c
drivers/thermal/intel_quark_dts_thermal.c [new file with mode: 0644]
drivers/thermal/intel_soc_dts_iosf.c [new file with mode: 0644]
drivers/thermal/intel_soc_dts_iosf.h [new file with mode: 0644]
drivers/thermal/intel_soc_dts_thermal.c
drivers/thermal/of-thermal.c
drivers/thermal/power_allocator.c [new file with mode: 0644]
drivers/thermal/qcom-spmi-temp-alarm.c [new file with mode: 0644]
drivers/thermal/samsung/exynos_tmu.c
drivers/thermal/samsung/exynos_tmu.h
drivers/thermal/thermal_core.c
drivers/thermal/thermal_core.h
drivers/thermal/ti-soc-thermal/ti-bandgap.c
drivers/thermal/ti-soc-thermal/ti-thermal-common.c
drivers/thermal/x86_pkg_temp_thermal.c
include/linux/cpu_cooling.h
include/linux/thermal.h
include/trace/events/thermal.h
include/trace/events/thermal_power_allocator.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/thermal/hisilicon-thermal.txt b/Documentation/devicetree/bindings/thermal/hisilicon-thermal.txt
new file mode 100644 (file)
index 0000000..d48fc52
--- /dev/null
@@ -0,0 +1,23 @@
+* Temperature Sensor on hisilicon SoCs
+
+** Required properties :
+
+- compatible: "hisilicon,tsensor".
+- reg: physical base address of thermal sensor and length of memory mapped
+  region.
+- interrupt: The interrupt number to the cpu. Defines the interrupt used
+  by /SOCTHERM/tsensor.
+- clock-names: Input clock name, should be 'thermal_clk'.
+- clocks: phandles for clock specified in "clock-names" property.
+- #thermal-sensor-cells: Should be 1. See ./thermal.txt for a description.
+
+Example :
+
+       tsensor: tsensor@0,f7030700 {
+               compatible = "hisilicon,tsensor";
+               reg = <0x0 0xf7030700 0x0 0x1000>;
+               interrupts = <0 7 0x4>;
+               clocks = <&sys_ctrl HI6220_TSENSOR_CLK>;
+               clock-names = "thermal_clk";
+               #thermal-sensor-cells = <1>;
+       }
diff --git a/Documentation/devicetree/bindings/thermal/qcom-spmi-temp-alarm.txt b/Documentation/devicetree/bindings/thermal/qcom-spmi-temp-alarm.txt
new file mode 100644 (file)
index 0000000..290ec06
--- /dev/null
@@ -0,0 +1,57 @@
+Qualcomm QPNP PMIC Temperature Alarm
+
+QPNP temperature alarm peripherals are found inside of Qualcomm PMIC chips
+that utilize the Qualcomm SPMI implementation. These peripherals provide an
+interrupt signal and status register to identify high PMIC die temperature.
+
+Required properties:
+- compatible:      Should contain "qcom,spmi-temp-alarm".
+- reg:             Specifies the SPMI address and length of the controller's
+                   registers.
+- interrupts:      PMIC temperature alarm interrupt.
+- #thermal-sensor-cells: Should be 0. See thermal.txt for a description.
+
+Optional properties:
+- io-channels:     Should contain IIO channel specifier for the ADC channel,
+                   which report chip die temperature.
+- io-channel-names: Should contain "thermal".
+
+Example:
+
+       pm8941_temp: thermal-alarm@2400 {
+               compatible = "qcom,spmi-temp-alarm";
+               reg = <0x2400 0x100>;
+               interrupts = <0 0x24 0 IRQ_TYPE_EDGE_RISING>;
+               #thermal-sensor-cells = <0>;
+
+               io-channels = <&pm8941_vadc VADC_DIE_TEMP>;
+               io-channel-names = "thermal";
+       };
+
+       thermal-zones {
+               pm8941 {
+                       polling-delay-passive = <250>;
+                       polling-delay = <1000>;
+
+                       thermal-sensors = <&pm8941_temp>;
+
+                       trips {
+                               passive {
+                                       temperature = <1050000>;
+                                       hysteresis = <2000>;
+                                       type = "passive";
+                               };
+                               alert {
+                                       temperature = <125000>;
+                                       hysteresis = <2000>;
+                                       type = "hot";
+                               };
+                               crit {
+                                       temperature = <145000>;
+                                       hysteresis = <2000>;
+                                       type = "critical";
+                               };
+                       };
+               };
+       };
+
index 29fe0bfae38e454975dddf145d20d6acbde8bef6..8a49362dea6e81c377d8762e401696c97555cd74 100644 (file)
@@ -167,6 +167,13 @@ Optional property:
                        by means of sensor ID. Additional coefficients are
                        interpreted as constant offset.
 
+- sustainable-power:   An estimate of the sustainable power (in mW) that the
+  Type: unsigned       thermal zone can dissipate at the desired
+  Size: one cell       control temperature.  For reference, the
+                       sustainable power of a 4'' phone is typically
+                       2000mW, while on a 10'' tablet is around
+                       4500mW.
+
 Note: The delay properties are bound to the maximum dT/dt (temperature
 derivative over time) in two situations for a thermal zone:
 (i)  - when passive cooling is activated (polling-delay-passive); and
@@ -546,6 +553,8 @@ thermal-zones {
                 */
                coefficients =          <1200   -345    890>;
 
+               sustainable-power = <2500>;
+
                trips {
                        /* Trips are based on resulting linear equation */
                        cpu_trip: cpu-trip {
index 753e47cc2e2036cd53e176241f579addd3b43ec1..71653584cd035b5301e888ba3406037f717f0fa6 100644 (file)
@@ -36,8 +36,162 @@ the user. The registration APIs returns the cooling device pointer.
     np: pointer to the cooling device device tree node
     clip_cpus: cpumask of cpus where the frequency constraints will happen.
 
-1.1.3 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+1.1.3 struct thermal_cooling_device *cpufreq_power_cooling_register(
+    const struct cpumask *clip_cpus, u32 capacitance,
+    get_static_t plat_static_func)
+
+Similar to cpufreq_cooling_register, this function registers a cpufreq
+cooling device.  Using this function, the cooling device will
+implement the power extensions by using a simple cpu power model.  The
+cpus must have registered their OPPs using the OPP library.
+
+The additional parameters are needed for the power model (See 2. Power
+models).  "capacitance" is the dynamic power coefficient (See 2.1
+Dynamic power).  "plat_static_func" is a function to calculate the
+static power consumed by these cpus (See 2.2 Static power).
+
+1.1.4 struct thermal_cooling_device *of_cpufreq_power_cooling_register(
+    struct device_node *np, const struct cpumask *clip_cpus, u32 capacitance,
+    get_static_t plat_static_func)
+
+Similar to cpufreq_power_cooling_register, this function register a
+cpufreq cooling device with power extensions using the device tree
+information supplied by the np parameter.
+
+1.1.5 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
 
     This interface function unregisters the "thermal-cpufreq-%x" cooling device.
 
     cdev: Cooling device pointer which has to be unregistered.
+
+2. Power models
+
+The power API registration functions provide a simple power model for
+CPUs.  The current power is calculated as dynamic + (optionally)
+static power.  This power model requires that the operating-points of
+the CPUs are registered using the kernel's opp library and the
+`cpufreq_frequency_table` is assigned to the `struct device` of the
+cpu.  If you are using CONFIG_CPUFREQ_DT then the
+`cpufreq_frequency_table` should already be assigned to the cpu
+device.
+
+The `plat_static_func` parameter of `cpufreq_power_cooling_register()`
+and `of_cpufreq_power_cooling_register()` is optional.  If you don't
+provide it, only dynamic power will be considered.
+
+2.1 Dynamic power
+
+The dynamic power consumption of a processor depends on many factors.
+For a given processor implementation the primary factors are:
+
+- The time the processor spends running, consuming dynamic power, as
+  compared to the time in idle states where dynamic consumption is
+  negligible.  Herein we refer to this as 'utilisation'.
+- The voltage and frequency levels as a result of DVFS.  The DVFS
+  level is a dominant factor governing power consumption.
+- In running time the 'execution' behaviour (instruction types, memory
+  access patterns and so forth) causes, in most cases, a second order
+  variation.  In pathological cases this variation can be significant,
+  but typically it is of a much lesser impact than the factors above.
+
+A high level dynamic power consumption model may then be represented as:
+
+Pdyn = f(run) * Voltage^2 * Frequency * Utilisation
+
+f(run) here represents the described execution behaviour and its
+result has a units of Watts/Hz/Volt^2 (this often expressed in
+mW/MHz/uVolt^2)
+
+The detailed behaviour for f(run) could be modelled on-line.  However,
+in practice, such an on-line model has dependencies on a number of
+implementation specific processor support and characterisation
+factors.  Therefore, in initial implementation that contribution is
+represented as a constant coefficient.  This is a simplification
+consistent with the relative contribution to overall power variation.
+
+In this simplified representation our model becomes:
+
+Pdyn = Capacitance * Voltage^2 * Frequency * Utilisation
+
+Where `capacitance` is a constant that represents an indicative
+running time dynamic power coefficient in fundamental units of
+mW/MHz/uVolt^2.  Typical values for mobile CPUs might lie in range
+from 100 to 500.  For reference, the approximate values for the SoC in
+ARM's Juno Development Platform are 530 for the Cortex-A57 cluster and
+140 for the Cortex-A53 cluster.
+
+
+2.2 Static power
+
+Static leakage power consumption depends on a number of factors.  For a
+given circuit implementation the primary factors are:
+
+- Time the circuit spends in each 'power state'
+- Temperature
+- Operating voltage
+- Process grade
+
+The time the circuit spends in each 'power state' for a given
+evaluation period at first order means OFF or ON.  However,
+'retention' states can also be supported that reduce power during
+inactive periods without loss of context.
+
+Note: The visibility of state entries to the OS can vary, according to
+platform specifics, and this can then impact the accuracy of a model
+based on OS state information alone.  It might be possible in some
+cases to extract more accurate information from system resources.
+
+The temperature, operating voltage and process 'grade' (slow to fast)
+of the circuit are all significant factors in static leakage power
+consumption.  All of these have complex relationships to static power.
+
+Circuit implementation specific factors include the chosen silicon
+process as well as the type, number and size of transistors in both
+the logic gates and any RAM elements included.
+
+The static power consumption modelling must take into account the
+power managed regions that are implemented.  Taking the example of an
+ARM processor cluster, the modelling would take into account whether
+each CPU can be powered OFF separately or if only a single power
+region is implemented for the complete cluster.
+
+In one view, there are others, a static power consumption model can
+then start from a set of reference values for each power managed
+region (e.g. CPU, Cluster/L2) in each state (e.g. ON, OFF) at an
+arbitrary process grade, voltage and temperature point.  These values
+are then scaled for all of the following: the time in each state, the
+process grade, the current temperature and the operating voltage.
+However, since both implementation specific and complex relationships
+dominate the estimate, the appropriate interface to the model from the
+cpu cooling device is to provide a function callback that calculates
+the static power in this platform.  When registering the cpu cooling
+device pass a function pointer that follows the `get_static_t`
+prototype:
+
+    int plat_get_static(cpumask_t *cpumask, int interval,
+                        unsigned long voltage, u32 &power);
+
+`cpumask` is the cpumask of the cpus involved in the calculation.
+`voltage` is the voltage at which they are operating.  The function
+should calculate the average static power for the last `interval`
+milliseconds.  It returns 0 on success, -E* on error.  If it
+succeeds, it should store the static power in `power`.  Reading the
+temperature of the cpus described by `cpumask` is left for
+plat_get_static() to do as the platform knows best which thermal
+sensor is closest to the cpu.
+
+If `plat_static_func` is NULL, static power is considered to be
+negligible for this platform and only dynamic power is considered.
+
+The platform specific callback can then use any combination of tables
+and/or equations to permute the estimated value.  Process grade
+information is not passed to the model since access to such data, from
+on-chip measurement capability or manufacture time data, is platform
+specific.
+
+Note: the significance of static power for CPUs in comparison to
+dynamic power is highly dependent on implementation.  Given the
+potential complexity in implementation, the importance and accuracy of
+its inclusion when using cpu cooling devices should be assessed on a
+case by case basis.
+
diff --git a/Documentation/thermal/power_allocator.txt b/Documentation/thermal/power_allocator.txt
new file mode 100644 (file)
index 0000000..c3797b5
--- /dev/null
@@ -0,0 +1,247 @@
+Power allocator governor tunables
+=================================
+
+Trip points
+-----------
+
+The governor requires the following two passive trip points:
+
+1.  "switch on" trip point: temperature above which the governor
+    control loop starts operating.  This is the first passive trip
+    point of the thermal zone.
+
+2.  "desired temperature" trip point: it should be higher than the
+    "switch on" trip point.  This the target temperature the governor
+    is controlling for.  This is the last passive trip point of the
+    thermal zone.
+
+PID Controller
+--------------
+
+The power allocator governor implements a
+Proportional-Integral-Derivative controller (PID controller) with
+temperature as the control input and power as the controlled output:
+
+    P_max = k_p * e + k_i * err_integral + k_d * diff_err + sustainable_power
+
+where
+    e = desired_temperature - current_temperature
+    err_integral is the sum of previous errors
+    diff_err = e - previous_error
+
+It is similar to the one depicted below:
+
+                                      k_d
+                                       |
+current_temp                           |
+     |                                 v
+     |                +----------+   +---+
+     |         +----->| diff_err |-->| X |------+
+     |         |      +----------+   +---+      |
+     |         |                                |      tdp        actor
+     |         |                      k_i       |       |  get_requested_power()
+     |         |                       |        |       |        |     |
+     |         |                       |        |       |        |     | ...
+     v         |                       v        v       v        v     v
+   +---+       |      +-------+      +---+    +---+   +---+   +----------+
+   | S |-------+----->| sum e |----->| X |--->| S |-->| S |-->|power     |
+   +---+       |      +-------+      +---+    +---+   +---+   |allocation|
+     ^         |                                ^             +----------+
+     |         |                                |                |     |
+     |         |        +---+                   |                |     |
+     |         +------->| X |-------------------+                v     v
+     |                  +---+                               granted performance
+desired_temperature       ^
+                          |
+                          |
+                      k_po/k_pu
+
+Sustainable power
+-----------------
+
+An estimate of the sustainable dissipatable power (in mW) should be
+provided while registering the thermal zone.  This estimates the
+sustained power that can be dissipated at the desired control
+temperature.  This is the maximum sustained power for allocation at
+the desired maximum temperature.  The actual sustained power can vary
+for a number of reasons.  The closed loop controller will take care of
+variations such as environmental conditions, and some factors related
+to the speed-grade of the silicon.  `sustainable_power` is therefore
+simply an estimate, and may be tuned to affect the aggressiveness of
+the thermal ramp. For reference, the sustainable power of a 4" phone
+is typically 2000mW, while on a 10" tablet is around 4500mW (may vary
+depending on screen size).
+
+If you are using device tree, do add it as a property of the
+thermal-zone.  For example:
+
+       thermal-zones {
+               soc_thermal {
+                       polling-delay = <1000>;
+                       polling-delay-passive = <100>;
+                       sustainable-power = <2500>;
+                       ...
+
+Instead, if the thermal zone is registered from the platform code, pass a
+`thermal_zone_params` that has a `sustainable_power`.  If no
+`thermal_zone_params` were being passed, then something like below
+will suffice:
+
+       static const struct thermal_zone_params tz_params = {
+               .sustainable_power = 3500,
+       };
+
+and then pass `tz_params` as the 5th parameter to
+`thermal_zone_device_register()`
+
+k_po and k_pu
+-------------
+
+The implementation of the PID controller in the power allocator
+thermal governor allows the configuration of two proportional term
+constants: `k_po` and `k_pu`.  `k_po` is the proportional term
+constant during temperature overshoot periods (current temperature is
+above "desired temperature" trip point).  Conversely, `k_pu` is the
+proportional term constant during temperature undershoot periods
+(current temperature below "desired temperature" trip point).
+
+These controls are intended as the primary mechanism for configuring
+the permitted thermal "ramp" of the system.  For instance, a lower
+`k_pu` value will provide a slower ramp, at the cost of capping
+available capacity at a low temperature.  On the other hand, a high
+value of `k_pu` will result in the governor granting very high power
+whilst temperature is low, and may lead to temperature overshooting.
+
+The default value for `k_pu` is:
+
+    2 * sustainable_power / (desired_temperature - switch_on_temp)
+
+This means that at `switch_on_temp` the output of the controller's
+proportional term will be 2 * `sustainable_power`.  The default value
+for `k_po` is:
+
+    sustainable_power / (desired_temperature - switch_on_temp)
+
+Focusing on the proportional and feed forward values of the PID
+controller equation we have:
+
+    P_max = k_p * e + sustainable_power
+
+The proportional term is proportional to the difference between the
+desired temperature and the current one.  When the current temperature
+is the desired one, then the proportional component is zero and
+`P_max` = `sustainable_power`.  That is, the system should operate in
+thermal equilibrium under constant load.  `sustainable_power` is only
+an estimate, which is the reason for closed-loop control such as this.
+
+Expanding `k_pu` we get:
+    P_max = 2 * sustainable_power * (T_set - T) / (T_set - T_on) +
+        sustainable_power
+
+where
+    T_set is the desired temperature
+    T is the current temperature
+    T_on is the switch on temperature
+
+When the current temperature is the switch_on temperature, the above
+formula becomes:
+
+    P_max = 2 * sustainable_power * (T_set - T_on) / (T_set - T_on) +
+        sustainable_power = 2 * sustainable_power + sustainable_power =
+        3 * sustainable_power
+
+Therefore, the proportional term alone linearly decreases power from
+3 * `sustainable_power` to `sustainable_power` as the temperature
+rises from the switch on temperature to the desired temperature.
+
+k_i and integral_cutoff
+-----------------------
+
+`k_i` configures the PID loop's integral term constant.  This term
+allows the PID controller to compensate for long term drift and for
+the quantized nature of the output control: cooling devices can't set
+the exact power that the governor requests.  When the temperature
+error is below `integral_cutoff`, errors are accumulated in the
+integral term.  This term is then multiplied by `k_i` and the result
+added to the output of the controller.  Typically `k_i` is set low (1
+or 2) and `integral_cutoff` is 0.
+
+k_d
+---
+
+`k_d` configures the PID loop's derivative term constant.  It's
+recommended to leave it as the default: 0.
+
+Cooling device power API
+========================
+
+Cooling devices controlled by this governor must supply the additional
+"power" API in their `cooling_device_ops`.  It consists on three ops:
+
+1. int get_requested_power(struct thermal_cooling_device *cdev,
+       struct thermal_zone_device *tz, u32 *power);
+@cdev: The `struct thermal_cooling_device` pointer
+@tz: thermal zone in which we are currently operating
+@power: pointer in which to store the calculated power
+
+`get_requested_power()` calculates the power requested by the device
+in milliwatts and stores it in @power .  It should return 0 on
+success, -E* on failure.  This is currently used by the power
+allocator governor to calculate how much power to give to each cooling
+device.
+
+2. int state2power(struct thermal_cooling_device *cdev, struct
+        thermal_zone_device *tz, unsigned long state, u32 *power);
+@cdev: The `struct thermal_cooling_device` pointer
+@tz: thermal zone in which we are currently operating
+@state: A cooling device state
+@power: pointer in which to store the equivalent power
+
+Convert cooling device state @state into power consumption in
+milliwatts and store it in @power.  It should return 0 on success, -E*
+on failure.  This is currently used by thermal core to calculate the
+maximum power that an actor can consume.
+
+3. int power2state(struct thermal_cooling_device *cdev, u32 power,
+       unsigned long *state);
+@cdev: The `struct thermal_cooling_device` pointer
+@power: power in milliwatts
+@state: pointer in which to store the resulting state
+
+Calculate a cooling device state that would make the device consume at
+most @power mW and store it in @state.  It should return 0 on success,
+-E* on failure.  This is currently used by the thermal core to convert
+a given power set by the power allocator governor to a state that the
+cooling device can set.  It is a function because this conversion may
+depend on external factors that may change so this function should the
+best conversion given "current circumstances".
+
+Cooling device weights
+----------------------
+
+Weights are a mechanism to bias the allocation among cooling
+devices.  They express the relative power efficiency of different
+cooling devices.  Higher weight can be used to express higher power
+efficiency.  Weighting is relative such that if each cooling device
+has a weight of one they are considered equal.  This is particularly
+useful in heterogeneous systems where two cooling devices may perform
+the same kind of compute, but with different efficiency.  For example,
+a system with two different types of processors.
+
+If the thermal zone is registered using
+`thermal_zone_device_register()` (i.e., platform code), then weights
+are passed as part of the thermal zone's `thermal_bind_parameters`.
+If the platform is registered using device tree, then they are passed
+as the `contribution` property of each map in the `cooling-maps` node.
+
+Limitations of the power allocator governor
+===========================================
+
+The power allocator governor's PID controller works best if there is a
+periodic tick.  If you have a driver that calls
+`thermal_zone_device_update()` (or anything that ends up calling the
+governor's `throttle()` function) repetitively, the governor response
+won't be very good.  Note that this is not particular to this
+governor, step-wise will also misbehave if you call its throttle()
+faster than the normal thermal framework tick (due to interrupts for
+example) as it will overreact.
index 87519cb379ee45ade675499949ae91bf1051725d..c1f6864a8c5db86def7b40ca7746e028e99bf37a 100644 (file)
@@ -95,7 +95,7 @@ temperature) and throttle appropriate devices.
 1.3 interface for binding a thermal zone device with a thermal cooling device
 1.3.1 int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
        int trip, struct thermal_cooling_device *cdev,
-       unsigned long upper, unsigned long lower);
+       unsigned long upper, unsigned long lower, unsigned int weight);
 
     This interface function bind a thermal cooling device to the certain trip
     point of a thermal zone device.
@@ -110,6 +110,8 @@ temperature) and throttle appropriate devices.
     lower:the Minimum cooling state can be used for this trip point.
           THERMAL_NO_LIMIT means no lower limit,
          and the cooling device can be in cooling state 0.
+    weight: the influence of this cooling device in this thermal
+            zone.  See 1.4.1 below for more information.
 
 1.3.2 int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
                int trip, struct thermal_cooling_device *cdev);
@@ -127,9 +129,15 @@ temperature) and throttle appropriate devices.
     This structure defines the following parameters that are used to bind
     a zone with a cooling device for a particular trip point.
     .cdev: The cooling device pointer
-    .weight: The 'influence' of a particular cooling device on this zone.
-             This is on a percentage scale. The sum of all these weights
-             (for a particular zone) cannot exceed 100.
+    .weight: The 'influence' of a particular cooling device on this
+             zone. This is relative to the rest of the cooling
+             devices. For example, if all cooling devices have a
+             weight of 1, then they all contribute the same. You can
+             use percentages if you want, but it's not mandatory. A
+             weight of 0 means that this cooling device doesn't
+             contribute to the cooling of this zone unless all cooling
+             devices have a weight of 0. If all weights are 0, then
+             they all contribute the same.
     .trip_mask:This is a bit mask that gives the binding relation between
                this thermal zone and cdev, for a particular trip point.
                If nth bit is set, then the cdev and thermal zone are bound
@@ -176,6 +184,14 @@ Thermal zone device sys I/F, created once it's registered:
     |---trip_point_[0-*]_type: Trip point type
     |---trip_point_[0-*]_hyst: Hysteresis value for this trip point
     |---emul_temp:             Emulated temperature set node
+    |---sustainable_power:      Sustainable dissipatable power
+    |---k_po:                   Proportional term during temperature overshoot
+    |---k_pu:                   Proportional term during temperature undershoot
+    |---k_i:                    PID's integral term in the power allocator gov
+    |---k_d:                    PID's derivative term in the power allocator
+    |---integral_cutoff:        Offset above which errors are accumulated
+    |---slope:                  Slope constant applied as linear extrapolation
+    |---offset:                 Offset constant applied as linear extrapolation
 
 Thermal cooling device sys I/F, created once it's registered:
 /sys/class/thermal/cooling_device[0-*]:
@@ -192,6 +208,8 @@ thermal_zone_bind_cooling_device/thermal_zone_unbind_cooling_device.
 /sys/class/thermal/thermal_zone[0-*]:
     |---cdev[0-*]:             [0-*]th cooling device in current thermal zone
     |---cdev[0-*]_trip_point:  Trip point that cdev[0-*] is associated with
+    |---cdev[0-*]_weight:       Influence of the cooling device in
+                                this thermal zone
 
 Besides the thermal zone device sysfs I/F and cooling device sysfs I/F,
 the generic thermal driver also creates a hwmon sysfs I/F for each _type_
@@ -265,6 +283,14 @@ cdev[0-*]_trip_point
        point.
        RO, Optional
 
+cdev[0-*]_weight
+        The influence of cdev[0-*] in this thermal zone. This value
+        is relative to the rest of cooling devices in the thermal
+        zone. For example, if a cooling device has a weight double
+        than that of other, it's twice as effective in cooling the
+        thermal zone.
+        RW, Optional
+
 passive
        Attribute is only present for zones in which the passive cooling
        policy is not supported by native thermal driver. Default is zero
@@ -289,6 +315,66 @@ emul_temp
          because userland can easily disable the thermal policy by simply
          flooding this sysfs node with low temperature values.
 
+sustainable_power
+       An estimate of the sustained power that can be dissipated by
+       the thermal zone. Used by the power allocator governor. For
+       more information see Documentation/thermal/power_allocator.txt
+       Unit: milliwatts
+       RW, Optional
+
+k_po
+       The proportional term of the power allocator governor's PID
+       controller during temperature overshoot. Temperature overshoot
+       is when the current temperature is above the "desired
+       temperature" trip point. For more information see
+       Documentation/thermal/power_allocator.txt
+       RW, Optional
+
+k_pu
+       The proportional term of the power allocator governor's PID
+       controller during temperature undershoot. Temperature undershoot
+       is when the current temperature is below the "desired
+       temperature" trip point. For more information see
+       Documentation/thermal/power_allocator.txt
+       RW, Optional
+
+k_i
+       The integral term of the power allocator governor's PID
+       controller. This term allows the PID controller to compensate
+       for long term drift. For more information see
+       Documentation/thermal/power_allocator.txt
+       RW, Optional
+
+k_d
+       The derivative term of the power allocator governor's PID
+       controller. For more information see
+       Documentation/thermal/power_allocator.txt
+       RW, Optional
+
+integral_cutoff
+       Temperature offset from the desired temperature trip point
+       above which the integral term of the power allocator
+       governor's PID controller starts accumulating errors. For
+       example, if integral_cutoff is 0, then the integral term only
+       accumulates error when temperature is above the desired
+       temperature trip point. For more information see
+       Documentation/thermal/power_allocator.txt
+       RW, Optional
+
+slope
+       The slope constant used in a linear extrapolation model
+       to determine a hotspot temperature based off the sensor's
+       raw readings. It is up to the device driver to determine
+       the usage of these values.
+       RW, Optional
+
+offset
+       The offset constant used in a linear extrapolation model
+       to determine a hotspot temperature based off the sensor's
+       raw readings. It is up to the device driver to determine
+       the usage of these values.
+       RW, Optional
+
 *****************************
 * Cooling device attributes *
 *****************************
@@ -318,7 +404,8 @@ passive, active. If an ACPI thermal zone supports critical, passive,
 active[0] and active[1] at the same time, it may register itself as a
 thermal_zone_device (thermal_zone1) with 4 trip points in all.
 It has one processor and one fan, which are both registered as
-thermal_cooling_device.
+thermal_cooling_device. Both are considered to have the same
+effectiveness in cooling the thermal zone.
 
 If the processor is listed in _PSL method, and the fan is listed in _AL0
 method, the sys I/F structure will be built like this:
@@ -340,8 +427,10 @@ method, the sys I/F structure will be built like this:
     |---trip_point_3_type:     active1
     |---cdev0:                 --->/sys/class/thermal/cooling_device0
     |---cdev0_trip_point:      1       /* cdev0 can be used for passive */
+    |---cdev0_weight:           1024
     |---cdev1:                 --->/sys/class/thermal/cooling_device3
     |---cdev1_trip_point:      2       /* cdev1 can be used for active[0]*/
+    |---cdev1_weight:           1024
 
 |cooling_device0:
     |---type:                  Processor
index d24fa1964eb8ff1e6195610f9e258cdd00f22b6c..6d4e44ea74acc8bff38f8be0aa7682f3d9560e3c 100644 (file)
@@ -800,7 +800,8 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal,
                                result =
                                        thermal_zone_bind_cooling_device
                                        (thermal, trip, cdev,
-                                        THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
+                                        THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
+                                        THERMAL_WEIGHT_DEFAULT);
                        else
                                result =
                                        thermal_zone_unbind_cooling_device
@@ -824,7 +825,8 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal,
                        if (bind)
                                result = thermal_zone_bind_cooling_device
                                        (thermal, trip, cdev,
-                                        THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
+                                        THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
+                                        THERMAL_WEIGHT_DEFAULT);
                        else
                                result = thermal_zone_unbind_cooling_device
                                        (thermal, trip, cdev);
@@ -841,7 +843,8 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal,
                                result = thermal_zone_bind_cooling_device
                                                (thermal, THERMAL_TRIPS_NONE,
                                                 cdev, THERMAL_NO_LIMIT,
-                                                THERMAL_NO_LIMIT);
+                                                THERMAL_NO_LIMIT,
+                                                THERMAL_WEIGHT_DEFAULT);
                        else
                                result = thermal_zone_unbind_cooling_device
                                                (thermal, THERMAL_TRIPS_NONE,
index 594c918b553d382fa06519d83a51274894644693..1ef02daddb60b81aeba3e627d0c7da5870d5248b 100644 (file)
@@ -372,7 +372,8 @@ static int acerhdf_bind(struct thermal_zone_device *thermal,
                return 0;
 
        if (thermal_zone_bind_cooling_device(thermal, 0, cdev,
-                       THERMAL_NO_LIMIT, THERMAL_NO_LIMIT)) {
+                       THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
+                       THERMAL_WEIGHT_DEFAULT)) {
                pr_err("error binding cooling dev\n");
                return -EINVAL;
        }
index af40db0df58e65ed1d87e1aad9a8ad34433f2b54..118938ee8552ffe3a1146997140ee7bed3e699b1 100644 (file)
@@ -42,6 +42,17 @@ config THERMAL_OF
          Say 'Y' here if you need to build thermal infrastructure
          based on device tree.
 
+config THERMAL_WRITABLE_TRIPS
+       bool "Enable writable trip points"
+       help
+         This option allows the system integrator to choose whether
+         trip temperatures can be changed from userspace. The
+         writable trips need to be specified when setting up the
+         thermal zone but the choice here takes precedence.
+
+         Say 'Y' here if you would like to allow userspace tools to
+         change trip temperatures.
+
 choice
        prompt "Default Thermal governor"
        default THERMAL_DEFAULT_GOV_STEP_WISE
@@ -71,6 +82,14 @@ config THERMAL_DEFAULT_GOV_USER_SPACE
          Select this if you want to let the user space manage the
          platform thermals.
 
+config THERMAL_DEFAULT_GOV_POWER_ALLOCATOR
+       bool "power_allocator"
+       select THERMAL_GOV_POWER_ALLOCATOR
+       help
+         Select this if you want to control temperature based on
+         system and device power allocation. This governor can only
+         operate on cooling devices that implement the power API.
+
 endchoice
 
 config THERMAL_GOV_FAIR_SHARE
@@ -99,6 +118,12 @@ config THERMAL_GOV_USER_SPACE
        help
          Enable this to let the user space manage the platform thermals.
 
+config THERMAL_GOV_POWER_ALLOCATOR
+       bool "Power allocator thermal governor"
+       help
+         Enable this to manage platform thermals by dynamically
+         allocating and limiting power to devices.
+
 config CPU_THERMAL
        bool "generic cpu cooling support"
        depends on CPU_FREQ
@@ -136,6 +161,14 @@ config THERMAL_EMULATION
          because userland can easily disable the thermal policy by simply
          flooding this sysfs node with low temperature values.
 
+config HISI_THERMAL
+       tristate "Hisilicon thermal driver"
+       depends on ARCH_HISI && CPU_THERMAL && OF
+       help
+         Enable this to plug hisilicon's thermal sensor driver into the Linux
+         thermal framework. cpufreq is used as the cooling device to throttle
+         CPUs when the passive trip is crossed.
+
 config IMX_THERMAL
        tristate "Temperature sensor driver for Freescale i.MX SoCs"
        depends on CPU_THERMAL
@@ -249,9 +282,20 @@ config X86_PKG_TEMP_THERMAL
          two trip points which can be set by user to get notifications via thermal
          notification methods.
 
+config INTEL_SOC_DTS_IOSF_CORE
+       tristate
+       depends on X86
+       select IOSF_MBI
+       help
+         This is becoming a common feature for Intel SoCs to expose the additional
+         digital temperature sensors (DTSs) using side band interface (IOSF). This
+         implements the common set of helper functions to register, get temperature
+         and get/set thresholds on DTSs.
+
 config INTEL_SOC_DTS_THERMAL
        tristate "Intel SoCs DTS thermal driver"
-       depends on X86 && IOSF_MBI
+       depends on X86
+       select INTEL_SOC_DTS_IOSF_CORE
        help
          Enable this to register Intel SoCs (e.g. Bay Trail) platform digital
          temperature sensor (DTS). These SoCs have two additional DTSs in
@@ -261,12 +305,23 @@ config INTEL_SOC_DTS_THERMAL
          notification methods.The other trip is a critical trip point, which
          was set by the driver based on the TJ MAX temperature.
 
+config INTEL_QUARK_DTS_THERMAL
+       tristate "Intel Quark DTS thermal driver"
+       depends on X86_INTEL_QUARK
+       help
+         Enable this to register Intel Quark SoC (e.g. X1000) platform digital
+         temperature sensor (DTS). For X1000 SoC, it has one on-die DTS.
+         The DTS will be registered as a thermal zone. There are two trip points:
+         hot & critical. The critical trip point default value is set by
+         underlying BIOS/Firmware.
+
 config INT340X_THERMAL
        tristate "ACPI INT340X thermal drivers"
        depends on X86 && ACPI
        select THERMAL_GOV_USER_SPACE
        select ACPI_THERMAL_REL
        select ACPI_FAN
+       select INTEL_SOC_DTS_IOSF_CORE
        help
          Newer laptops and tablets that use ACPI may have thermal sensors and
          other devices with thermal control capabilities outside the core
@@ -299,4 +354,15 @@ depends on ARCH_STI && OF
 source "drivers/thermal/st/Kconfig"
 endmenu
 
+config QCOM_SPMI_TEMP_ALARM
+       tristate "Qualcomm SPMI PMIC Temperature Alarm"
+       depends on OF && SPMI && IIO
+       select REGMAP_SPMI
+       help
+         This enables a thermal sysfs driver for Qualcomm plug-and-play (QPNP)
+         PMIC devices. It shows up in sysfs as a thermal sensor with multiple
+         trip points. The temperature reported by the thermal sensor reflects the
+         real time die temperature if an ADC is present or an estimate of the
+         temperature based upon the over temperature stage value.
+
 endif
index fa0dc486790f9eb55e4b592735264affc6d75292..535dfee1496fc26d90457b28dc413993f693fe42 100644 (file)
@@ -14,6 +14,7 @@ thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE)  += fair_share.o
 thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG)    += gov_bang_bang.o
 thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE)    += step_wise.o
 thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE)   += user_space.o
+thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR)      += power_allocator.o
 
 # cpufreq cooling
 thermal_sys-$(CONFIG_CPU_THERMAL)      += cpu_cooling.o
@@ -22,6 +23,7 @@ thermal_sys-$(CONFIG_CPU_THERMAL)     += cpu_cooling.o
 thermal_sys-$(CONFIG_CLOCK_THERMAL)    += clock_cooling.o
 
 # platform thermal drivers
+obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM)     += qcom-spmi-temp-alarm.o
 obj-$(CONFIG_SPEAR_THERMAL)    += spear_thermal.o
 obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
 obj-$(CONFIG_RCAR_THERMAL)     += rcar_thermal.o
@@ -34,8 +36,11 @@ obj-$(CONFIG_IMX_THERMAL)    += imx_thermal.o
 obj-$(CONFIG_DB8500_CPUFREQ_COOLING)   += db8500_cpufreq_cooling.o
 obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
 obj-$(CONFIG_X86_PKG_TEMP_THERMAL)     += x86_pkg_temp_thermal.o
+obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE)  += intel_soc_dts_iosf.o
 obj-$(CONFIG_INTEL_SOC_DTS_THERMAL)    += intel_soc_dts_thermal.o
+obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL)  += intel_quark_dts_thermal.o
 obj-$(CONFIG_TI_SOC_THERMAL)   += ti-soc-thermal/
 obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
 obj-$(CONFIG_ST_THERMAL)       += st/
 obj-$(CONFIG_TEGRA_SOCTHERM)   += tegra_soctherm.o
+obj-$(CONFIG_HISI_THERMAL)     += hisi_thermal.o
index f65f0d109fc8c015869a78b355478afb153fde5d..6509c61b96484993333a4198138945f43154fdfd 100644 (file)
 #include <linux/thermal.h>
 #include <linux/cpufreq.h>
 #include <linux/err.h>
+#include <linux/pm_opp.h>
 #include <linux/slab.h>
 #include <linux/cpu.h>
 #include <linux/cpu_cooling.h>
 
+#include <trace/events/thermal.h>
+
 /*
  * Cooling state <-> CPUFreq frequency
  *
  *     ...
  */
 
+/**
+ * struct power_table - frequency to power conversion
+ * @frequency: frequency in KHz
+ * @power:     power in mW
+ *
+ * This structure is built when the cooling device registers and helps
+ * in translating frequency to power and viceversa.
+ */
+struct power_table {
+       u32 frequency;
+       u32 power;
+};
+
 /**
  * struct cpufreq_cooling_device - data for cooling device with cpufreq
  * @id: unique integer value corresponding to each cpufreq_cooling_device
  *     cpufreq frequencies.
  * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
  * @node: list_head to link all cpufreq_cooling_device together.
+ * @last_load: load measured by the latest call to cpufreq_get_actual_power()
+ * @time_in_idle: previous reading of the absolute time that this cpu was idle
+ * @time_in_idle_timestamp: wall time of the last invocation of
+ *     get_cpu_idle_time_us()
+ * @dyn_power_table: array of struct power_table for frequency to power
+ *     conversion, sorted in ascending order.
+ * @dyn_power_table_entries: number of entries in the @dyn_power_table array
+ * @cpu_dev: the first cpu_device from @allowed_cpus that has OPPs registered
+ * @plat_get_static_power: callback to calculate the static power
  *
  * This structure is required for keeping information of each registered
  * cpufreq_cooling_device.
@@ -71,6 +96,13 @@ struct cpufreq_cooling_device {
        unsigned int *freq_table;       /* In descending order */
        struct cpumask allowed_cpus;
        struct list_head node;
+       u32 last_load;
+       u64 *time_in_idle;
+       u64 *time_in_idle_timestamp;
+       struct power_table *dyn_power_table;
+       int dyn_power_table_entries;
+       struct device *cpu_dev;
+       get_static_t plat_get_static_power;
 };
 static DEFINE_IDR(cpufreq_idr);
 static DEFINE_MUTEX(cooling_cpufreq_lock);
@@ -186,23 +218,237 @@ static int cpufreq_thermal_notifier(struct notifier_block *nb,
        unsigned long max_freq = 0;
        struct cpufreq_cooling_device *cpufreq_dev;
 
-       if (event != CPUFREQ_ADJUST)
-               return 0;
+       switch (event) {
 
-       mutex_lock(&cooling_cpufreq_lock);
-       list_for_each_entry(cpufreq_dev, &cpufreq_dev_list, node) {
-               if (!cpumask_test_cpu(policy->cpu,
-                                       &cpufreq_dev->allowed_cpus))
+       case CPUFREQ_ADJUST:
+               mutex_lock(&cooling_cpufreq_lock);
+               list_for_each_entry(cpufreq_dev, &cpufreq_dev_list, node) {
+                       if (!cpumask_test_cpu(policy->cpu,
+                                             &cpufreq_dev->allowed_cpus))
+                               continue;
+
+                       max_freq = cpufreq_dev->cpufreq_val;
+
+                       if (policy->max != max_freq)
+                               cpufreq_verify_within_limits(policy, 0,
+                                                            max_freq);
+               }
+               mutex_unlock(&cooling_cpufreq_lock);
+               break;
+       default:
+               return NOTIFY_DONE;
+       }
+
+       return NOTIFY_OK;
+}
+
+/**
+ * build_dyn_power_table() - create a dynamic power to frequency table
+ * @cpufreq_device:    the cpufreq cooling device in which to store the table
+ * @capacitance: dynamic power coefficient for these cpus
+ *
+ * Build a dynamic power to frequency table for this cpu and store it
+ * in @cpufreq_device.  This table will be used in cpu_power_to_freq() and
+ * cpu_freq_to_power() to convert between power and frequency
+ * efficiently.  Power is stored in mW, frequency in KHz.  The
+ * resulting table is in ascending order.
+ *
+ * Return: 0 on success, -E* on error.
+ */
+static int build_dyn_power_table(struct cpufreq_cooling_device *cpufreq_device,
+                                u32 capacitance)
+{
+       struct power_table *power_table;
+       struct dev_pm_opp *opp;
+       struct device *dev = NULL;
+       int num_opps = 0, cpu, i, ret = 0;
+       unsigned long freq;
+
+       rcu_read_lock();
+
+       for_each_cpu(cpu, &cpufreq_device->allowed_cpus) {
+               dev = get_cpu_device(cpu);
+               if (!dev) {
+                       dev_warn(&cpufreq_device->cool_dev->device,
+                                "No cpu device for cpu %d\n", cpu);
                        continue;
+               }
+
+               num_opps = dev_pm_opp_get_opp_count(dev);
+               if (num_opps > 0) {
+                       break;
+               } else if (num_opps < 0) {
+                       ret = num_opps;
+                       goto unlock;
+               }
+       }
 
-               max_freq = cpufreq_dev->cpufreq_val;
+       if (num_opps == 0) {
+               ret = -EINVAL;
+               goto unlock;
+       }
 
-               if (policy->max != max_freq)
-                       cpufreq_verify_within_limits(policy, 0, max_freq);
+       power_table = kcalloc(num_opps, sizeof(*power_table), GFP_KERNEL);
+       if (!power_table) {
+               ret = -ENOMEM;
+               goto unlock;
        }
-       mutex_unlock(&cooling_cpufreq_lock);
 
-       return 0;
+       for (freq = 0, i = 0;
+            opp = dev_pm_opp_find_freq_ceil(dev, &freq), !IS_ERR(opp);
+            freq++, i++) {
+               u32 freq_mhz, voltage_mv;
+               u64 power;
+
+               freq_mhz = freq / 1000000;
+               voltage_mv = dev_pm_opp_get_voltage(opp) / 1000;
+
+               /*
+                * Do the multiplication with MHz and millivolt so as
+                * to not overflow.
+                */
+               power = (u64)capacitance * freq_mhz * voltage_mv * voltage_mv;
+               do_div(power, 1000000000);
+
+               /* frequency is stored in power_table in KHz */
+               power_table[i].frequency = freq / 1000;
+
+               /* power is stored in mW */
+               power_table[i].power = power;
+       }
+
+       if (i == 0) {
+               ret = PTR_ERR(opp);
+               goto unlock;
+       }
+
+       cpufreq_device->cpu_dev = dev;
+       cpufreq_device->dyn_power_table = power_table;
+       cpufreq_device->dyn_power_table_entries = i;
+
+unlock:
+       rcu_read_unlock();
+       return ret;
+}
+
+static u32 cpu_freq_to_power(struct cpufreq_cooling_device *cpufreq_device,
+                            u32 freq)
+{
+       int i;
+       struct power_table *pt = cpufreq_device->dyn_power_table;
+
+       for (i = 1; i < cpufreq_device->dyn_power_table_entries; i++)
+               if (freq < pt[i].frequency)
+                       break;
+
+       return pt[i - 1].power;
+}
+
+static u32 cpu_power_to_freq(struct cpufreq_cooling_device *cpufreq_device,
+                            u32 power)
+{
+       int i;
+       struct power_table *pt = cpufreq_device->dyn_power_table;
+
+       for (i = 1; i < cpufreq_device->dyn_power_table_entries; i++)
+               if (power < pt[i].power)
+                       break;
+
+       return pt[i - 1].frequency;
+}
+
+/**
+ * get_load() - get load for a cpu since last updated
+ * @cpufreq_device:    &struct cpufreq_cooling_device for this cpu
+ * @cpu:       cpu number
+ *
+ * Return: The average load of cpu @cpu in percentage since this
+ * function was last called.
+ */
+static u32 get_load(struct cpufreq_cooling_device *cpufreq_device, int cpu)
+{
+       u32 load;
+       u64 now, now_idle, delta_time, delta_idle;
+
+       now_idle = get_cpu_idle_time(cpu, &now, 0);
+       delta_idle = now_idle - cpufreq_device->time_in_idle[cpu];
+       delta_time = now - cpufreq_device->time_in_idle_timestamp[cpu];
+
+       if (delta_time <= delta_idle)
+               load = 0;
+       else
+               load = div64_u64(100 * (delta_time - delta_idle), delta_time);
+
+       cpufreq_device->time_in_idle[cpu] = now_idle;
+       cpufreq_device->time_in_idle_timestamp[cpu] = now;
+
+       return load;
+}
+
+/**
+ * get_static_power() - calculate the static power consumed by the cpus
+ * @cpufreq_device:    struct &cpufreq_cooling_device for this cpu cdev
+ * @tz:                thermal zone device in which we're operating
+ * @freq:      frequency in KHz
+ * @power:     pointer in which to store the calculated static power
+ *
+ * Calculate the static power consumed by the cpus described by
+ * @cpu_actor running at frequency @freq.  This function relies on a
+ * platform specific function that should have been provided when the
+ * actor was registered.  If it wasn't, the static power is assumed to
+ * be negligible.  The calculated static power is stored in @power.
+ *
+ * Return: 0 on success, -E* on failure.
+ */
+static int get_static_power(struct cpufreq_cooling_device *cpufreq_device,
+                           struct thermal_zone_device *tz, unsigned long freq,
+                           u32 *power)
+{
+       struct dev_pm_opp *opp;
+       unsigned long voltage;
+       struct cpumask *cpumask = &cpufreq_device->allowed_cpus;
+       unsigned long freq_hz = freq * 1000;
+
+       if (!cpufreq_device->plat_get_static_power ||
+           !cpufreq_device->cpu_dev) {
+               *power = 0;
+               return 0;
+       }
+
+       rcu_read_lock();
+
+       opp = dev_pm_opp_find_freq_exact(cpufreq_device->cpu_dev, freq_hz,
+                                        true);
+       voltage = dev_pm_opp_get_voltage(opp);
+
+       rcu_read_unlock();
+
+       if (voltage == 0) {
+               dev_warn_ratelimited(cpufreq_device->cpu_dev,
+                                    "Failed to get voltage for frequency %lu: %ld\n",
+                                    freq_hz, IS_ERR(opp) ? PTR_ERR(opp) : 0);
+               return -EINVAL;
+       }
+
+       return cpufreq_device->plat_get_static_power(cpumask, tz->passive_delay,
+                                                    voltage, power);
+}
+
+/**
+ * get_dynamic_power() - calculate the dynamic power
+ * @cpufreq_device:    &cpufreq_cooling_device for this cdev
+ * @freq:      current frequency
+ *
+ * Return: the dynamic power consumed by the cpus described by
+ * @cpufreq_device.
+ */
+static u32 get_dynamic_power(struct cpufreq_cooling_device *cpufreq_device,
+                            unsigned long freq)
+{
+       u32 raw_cpu_power;
+
+       raw_cpu_power = cpu_freq_to_power(cpufreq_device, freq);
+       return (raw_cpu_power * cpufreq_device->last_load) / 100;
 }
 
 /* cpufreq cooling device callback functions are defined below */
@@ -280,8 +526,205 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
        return 0;
 }
 
+/**
+ * cpufreq_get_requested_power() - get the current power
+ * @cdev:      &thermal_cooling_device pointer
+ * @tz:                a valid thermal zone device pointer
+ * @power:     pointer in which to store the resulting power
+ *
+ * Calculate the current power consumption of the cpus in milliwatts
+ * and store it in @power.  This function should actually calculate
+ * the requested power, but it's hard to get the frequency that
+ * cpufreq would have assigned if there were no thermal limits.
+ * Instead, we calculate the current power on the assumption that the
+ * immediate future will look like the immediate past.
+ *
+ * We use the current frequency and the average load since this
+ * function was last called.  In reality, there could have been
+ * multiple opps since this function was last called and that affects
+ * the load calculation.  While it's not perfectly accurate, this
+ * simplification is good enough and works.  REVISIT this, as more
+ * complex code may be needed if experiments show that it's not
+ * accurate enough.
+ *
+ * Return: 0 on success, -E* if getting the static power failed.
+ */
+static int cpufreq_get_requested_power(struct thermal_cooling_device *cdev,
+                                      struct thermal_zone_device *tz,
+                                      u32 *power)
+{
+       unsigned long freq;
+       int i = 0, cpu, ret;
+       u32 static_power, dynamic_power, total_load = 0;
+       struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+       u32 *load_cpu = NULL;
+
+       cpu = cpumask_any_and(&cpufreq_device->allowed_cpus, cpu_online_mask);
+
+       /*
+        * All the CPUs are offline, thus the requested power by
+        * the cdev is 0
+        */
+       if (cpu >= nr_cpu_ids) {
+               *power = 0;
+               return 0;
+       }
+
+       freq = cpufreq_quick_get(cpu);
+
+       if (trace_thermal_power_cpu_get_power_enabled()) {
+               u32 ncpus = cpumask_weight(&cpufreq_device->allowed_cpus);
+
+               load_cpu = devm_kcalloc(&cdev->device, ncpus, sizeof(*load_cpu),
+                                       GFP_KERNEL);
+       }
+
+       for_each_cpu(cpu, &cpufreq_device->allowed_cpus) {
+               u32 load;
+
+               if (cpu_online(cpu))
+                       load = get_load(cpufreq_device, cpu);
+               else
+                       load = 0;
+
+               total_load += load;
+               if (trace_thermal_power_cpu_limit_enabled() && load_cpu)
+                       load_cpu[i] = load;
+
+               i++;
+       }
+
+       cpufreq_device->last_load = total_load;
+
+       dynamic_power = get_dynamic_power(cpufreq_device, freq);
+       ret = get_static_power(cpufreq_device, tz, freq, &static_power);
+       if (ret) {
+               if (load_cpu)
+                       devm_kfree(&cdev->device, load_cpu);
+               return ret;
+       }
+
+       if (load_cpu) {
+               trace_thermal_power_cpu_get_power(
+                       &cpufreq_device->allowed_cpus,
+                       freq, load_cpu, i, dynamic_power, static_power);
+
+               devm_kfree(&cdev->device, load_cpu);
+       }
+
+       *power = static_power + dynamic_power;
+       return 0;
+}
+
+/**
+ * cpufreq_state2power() - convert a cpu cdev state to power consumed
+ * @cdev:      &thermal_cooling_device pointer
+ * @tz:                a valid thermal zone device pointer
+ * @state:     cooling device state to be converted
+ * @power:     pointer in which to store the resulting power
+ *
+ * Convert cooling device state @state into power consumption in
+ * milliwatts assuming 100% load.  Store the calculated power in
+ * @power.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device state could not
+ * be converted into a frequency or other -E* if there was an error
+ * when calculating the static power.
+ */
+static int cpufreq_state2power(struct thermal_cooling_device *cdev,
+                              struct thermal_zone_device *tz,
+                              unsigned long state, u32 *power)
+{
+       unsigned int freq, num_cpus;
+       cpumask_t cpumask;
+       u32 static_power, dynamic_power;
+       int ret;
+       struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+       cpumask_and(&cpumask, &cpufreq_device->allowed_cpus, cpu_online_mask);
+       num_cpus = cpumask_weight(&cpumask);
+
+       /* None of our cpus are online, so no power */
+       if (num_cpus == 0) {
+               *power = 0;
+               return 0;
+       }
+
+       freq = cpufreq_device->freq_table[state];
+       if (!freq)
+               return -EINVAL;
+
+       dynamic_power = cpu_freq_to_power(cpufreq_device, freq) * num_cpus;
+       ret = get_static_power(cpufreq_device, tz, freq, &static_power);
+       if (ret)
+               return ret;
+
+       *power = static_power + dynamic_power;
+       return 0;
+}
+
+/**
+ * cpufreq_power2state() - convert power to a cooling device state
+ * @cdev:      &thermal_cooling_device pointer
+ * @tz:                a valid thermal zone device pointer
+ * @power:     power in milliwatts to be converted
+ * @state:     pointer in which to store the resulting state
+ *
+ * Calculate a cooling device state for the cpus described by @cdev
+ * that would allow them to consume at most @power mW and store it in
+ * @state.  Note that this calculation depends on external factors
+ * such as the cpu load or the current static power.  Calling this
+ * function with the same power as input can yield different cooling
+ * device states depending on those external factors.
+ *
+ * Return: 0 on success, -ENODEV if no cpus are online or -EINVAL if
+ * the calculated frequency could not be converted to a valid state.
+ * The latter should not happen unless the frequencies available to
+ * cpufreq have changed since the initialization of the cpu cooling
+ * device.
+ */
+static int cpufreq_power2state(struct thermal_cooling_device *cdev,
+                              struct thermal_zone_device *tz, u32 power,
+                              unsigned long *state)
+{
+       unsigned int cpu, cur_freq, target_freq;
+       int ret;
+       s32 dyn_power;
+       u32 last_load, normalised_power, static_power;
+       struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+       cpu = cpumask_any_and(&cpufreq_device->allowed_cpus, cpu_online_mask);
+
+       /* None of our cpus are online */
+       if (cpu >= nr_cpu_ids)
+               return -ENODEV;
+
+       cur_freq = cpufreq_quick_get(cpu);
+       ret = get_static_power(cpufreq_device, tz, cur_freq, &static_power);
+       if (ret)
+               return ret;
+
+       dyn_power = power - static_power;
+       dyn_power = dyn_power > 0 ? dyn_power : 0;
+       last_load = cpufreq_device->last_load ?: 1;
+       normalised_power = (dyn_power * 100) / last_load;
+       target_freq = cpu_power_to_freq(cpufreq_device, normalised_power);
+
+       *state = cpufreq_cooling_get_level(cpu, target_freq);
+       if (*state == THERMAL_CSTATE_INVALID) {
+               dev_warn_ratelimited(&cdev->device,
+                                    "Failed to convert %dKHz for cpu %d into a cdev state\n",
+                                    target_freq, cpu);
+               return -EINVAL;
+       }
+
+       trace_thermal_power_cpu_limit(&cpufreq_device->allowed_cpus,
+                                     target_freq, *state, power);
+       return 0;
+}
+
 /* Bind cpufreq callbacks to thermal cooling device ops */
-static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+static struct thermal_cooling_device_ops cpufreq_cooling_ops = {
        .get_max_state = cpufreq_get_max_state,
        .get_cur_state = cpufreq_get_cur_state,
        .set_cur_state = cpufreq_set_cur_state,
@@ -311,6 +754,9 @@ static unsigned int find_next_max(struct cpufreq_frequency_table *table,
  * @np: a valid struct device_node to the cooling device device tree node
  * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
  * Normally this should be same as cpufreq policy->related_cpus.
+ * @capacitance: dynamic power coefficient for these cpus
+ * @plat_static_func: function to calculate the static power consumed by these
+ *                    cpus (optional)
  *
  * This interface function registers the cpufreq cooling device with the name
  * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
@@ -322,13 +768,14 @@ static unsigned int find_next_max(struct cpufreq_frequency_table *table,
  */
 static struct thermal_cooling_device *
 __cpufreq_cooling_register(struct device_node *np,
-                          const struct cpumask *clip_cpus)
+                       const struct cpumask *clip_cpus, u32 capacitance,
+                       get_static_t plat_static_func)
 {
        struct thermal_cooling_device *cool_dev;
        struct cpufreq_cooling_device *cpufreq_dev;
        char dev_name[THERMAL_NAME_LENGTH];
        struct cpufreq_frequency_table *pos, *table;
-       unsigned int freq, i;
+       unsigned int freq, i, num_cpus;
        int ret;
 
        table = cpufreq_frequency_get_table(cpumask_first(clip_cpus));
@@ -341,6 +788,23 @@ __cpufreq_cooling_register(struct device_node *np,
        if (!cpufreq_dev)
                return ERR_PTR(-ENOMEM);
 
+       num_cpus = cpumask_weight(clip_cpus);
+       cpufreq_dev->time_in_idle = kcalloc(num_cpus,
+                                           sizeof(*cpufreq_dev->time_in_idle),
+                                           GFP_KERNEL);
+       if (!cpufreq_dev->time_in_idle) {
+               cool_dev = ERR_PTR(-ENOMEM);
+               goto free_cdev;
+       }
+
+       cpufreq_dev->time_in_idle_timestamp =
+               kcalloc(num_cpus, sizeof(*cpufreq_dev->time_in_idle_timestamp),
+                       GFP_KERNEL);
+       if (!cpufreq_dev->time_in_idle_timestamp) {
+               cool_dev = ERR_PTR(-ENOMEM);
+               goto free_time_in_idle;
+       }
+
        /* Find max levels */
        cpufreq_for_each_valid_entry(pos, table)
                cpufreq_dev->max_level++;
@@ -349,7 +813,7 @@ __cpufreq_cooling_register(struct device_node *np,
                                          cpufreq_dev->max_level, GFP_KERNEL);
        if (!cpufreq_dev->freq_table) {
                cool_dev = ERR_PTR(-ENOMEM);
-               goto free_cdev;
+               goto free_time_in_idle_timestamp;
        }
 
        /* max_level is an index, not a counter */
@@ -357,6 +821,20 @@ __cpufreq_cooling_register(struct device_node *np,
 
        cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
 
+       if (capacitance) {
+               cpufreq_cooling_ops.get_requested_power =
+                       cpufreq_get_requested_power;
+               cpufreq_cooling_ops.state2power = cpufreq_state2power;
+               cpufreq_cooling_ops.power2state = cpufreq_power2state;
+               cpufreq_dev->plat_get_static_power = plat_static_func;
+
+               ret = build_dyn_power_table(cpufreq_dev, capacitance);
+               if (ret) {
+                       cool_dev = ERR_PTR(ret);
+                       goto free_table;
+               }
+       }
+
        ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
        if (ret) {
                cool_dev = ERR_PTR(ret);
@@ -402,6 +880,10 @@ remove_idr:
        release_idr(&cpufreq_idr, cpufreq_dev->id);
 free_table:
        kfree(cpufreq_dev->freq_table);
+free_time_in_idle_timestamp:
+       kfree(cpufreq_dev->time_in_idle_timestamp);
+free_time_in_idle:
+       kfree(cpufreq_dev->time_in_idle);
 free_cdev:
        kfree(cpufreq_dev);
 
@@ -422,7 +904,7 @@ free_cdev:
 struct thermal_cooling_device *
 cpufreq_cooling_register(const struct cpumask *clip_cpus)
 {
-       return __cpufreq_cooling_register(NULL, clip_cpus);
+       return __cpufreq_cooling_register(NULL, clip_cpus, 0, NULL);
 }
 EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
 
@@ -446,10 +928,77 @@ of_cpufreq_cooling_register(struct device_node *np,
        if (!np)
                return ERR_PTR(-EINVAL);
 
-       return __cpufreq_cooling_register(np, clip_cpus);
+       return __cpufreq_cooling_register(np, clip_cpus, 0, NULL);
 }
 EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
 
+/**
+ * cpufreq_power_cooling_register() - create cpufreq cooling device with power extensions
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ * @capacitance:       dynamic power coefficient for these cpus
+ * @plat_static_func:  function to calculate the static power consumed by these
+ *                     cpus (optional)
+ *
+ * This interface function registers the cpufreq cooling device with
+ * the name "thermal-cpufreq-%x".  This api can support multiple
+ * instances of cpufreq cooling devices.  Using this function, the
+ * cooling device will implement the power extensions by using a
+ * simple cpu power model.  The cpus must have registered their OPPs
+ * using the OPP library.
+ *
+ * An optional @plat_static_func may be provided to calculate the
+ * static power consumed by these cpus.  If the platform's static
+ * power consumption is unknown or negligible, make it NULL.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+cpufreq_power_cooling_register(const struct cpumask *clip_cpus, u32 capacitance,
+                              get_static_t plat_static_func)
+{
+       return __cpufreq_cooling_register(NULL, clip_cpus, capacitance,
+                               plat_static_func);
+}
+EXPORT_SYMBOL(cpufreq_power_cooling_register);
+
+/**
+ * of_cpufreq_power_cooling_register() - create cpufreq cooling device with power extensions
+ * @np:        a valid struct device_node to the cooling device device tree node
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ * @capacitance:       dynamic power coefficient for these cpus
+ * @plat_static_func:  function to calculate the static power consumed by these
+ *                     cpus (optional)
+ *
+ * This interface function registers the cpufreq cooling device with
+ * the name "thermal-cpufreq-%x".  This api can support multiple
+ * instances of cpufreq cooling devices.  Using this API, the cpufreq
+ * cooling device will be linked to the device tree node provided.
+ * Using this function, the cooling device will implement the power
+ * extensions by using a simple cpu power model.  The cpus must have
+ * registered their OPPs using the OPP library.
+ *
+ * An optional @plat_static_func may be provided to calculate the
+ * static power consumed by these cpus.  If the platform's static
+ * power consumption is unknown or negligible, make it NULL.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+of_cpufreq_power_cooling_register(struct device_node *np,
+                                 const struct cpumask *clip_cpus,
+                                 u32 capacitance,
+                                 get_static_t plat_static_func)
+{
+       if (!np)
+               return ERR_PTR(-EINVAL);
+
+       return __cpufreq_cooling_register(np, clip_cpus, capacitance,
+                               plat_static_func);
+}
+EXPORT_SYMBOL(of_cpufreq_power_cooling_register);
+
 /**
  * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
  * @cdev: thermal cooling device pointer.
@@ -475,6 +1024,8 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
 
        thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
        release_idr(&cpufreq_idr, cpufreq_dev->id);
+       kfree(cpufreq_dev->time_in_idle_timestamp);
+       kfree(cpufreq_dev->time_in_idle);
        kfree(cpufreq_dev->freq_table);
        kfree(cpufreq_dev);
 }
index 20adfbe27df1748bc00e5563cce12828973c8881..2fb273c4baa95b5583052102d793d8b9d288ad44 100644 (file)
@@ -76,7 +76,7 @@ static int db8500_cdev_bind(struct thermal_zone_device *thermal,
                upper = lower = i > max_state ? max_state : i;
 
                ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
-                       upper, lower);
+                       upper, lower, THERMAL_WEIGHT_DEFAULT);
 
                dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
                        i, ret, ret ? "fail" : "succeed");
index 6e0a3fbfae867f9ebab079a2d485677222a3e349..c2c10bbe24d62c7cef116cf3c1ac2a9e5f738905 100644 (file)
@@ -59,17 +59,17 @@ static int get_trip_level(struct thermal_zone_device *tz)
 }
 
 static long get_target_state(struct thermal_zone_device *tz,
-               struct thermal_cooling_device *cdev, int weight, int level)
+               struct thermal_cooling_device *cdev, int percentage, int level)
 {
        unsigned long max_state;
 
        cdev->ops->get_max_state(cdev, &max_state);
 
-       return (long)(weight * level * max_state) / (100 * tz->trips);
+       return (long)(percentage * level * max_state) / (100 * tz->trips);
 }
 
 /**
- * fair_share_throttle - throttles devices asscciated with the given zone
+ * fair_share_throttle - throttles devices associated with the given zone
  * @tz - thermal_zone_device
  *
  * Throttling Logic: This uses three parameters to calculate the new
@@ -77,7 +77,7 @@ static long get_target_state(struct thermal_zone_device *tz,
  *
  * Parameters used for Throttling:
  * P1. max_state: Maximum throttle state exposed by the cooling device.
- * P2. weight[i]/100:
+ * P2. percentage[i]/100:
  *     How 'effective' the 'i'th device is, in cooling the given zone.
  * P3. cur_trip_level/max_no_of_trips:
  *     This describes the extent to which the devices should be throttled.
@@ -88,28 +88,33 @@ static long get_target_state(struct thermal_zone_device *tz,
  */
 static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
 {
-       const struct thermal_zone_params *tzp;
-       struct thermal_cooling_device *cdev;
        struct thermal_instance *instance;
-       int i;
+       int total_weight = 0;
+       int total_instance = 0;
        int cur_trip_level = get_trip_level(tz);
 
-       if (!tz->tzp || !tz->tzp->tbp)
-               return -EINVAL;
+       list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+               if (instance->trip != trip)
+                       continue;
+
+               total_weight += instance->weight;
+               total_instance++;
+       }
 
-       tzp = tz->tzp;
+       list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+               int percentage;
+               struct thermal_cooling_device *cdev = instance->cdev;
 
-       for (i = 0; i < tzp->num_tbps; i++) {
-               if (!tzp->tbp[i].cdev)
+               if (instance->trip != trip)
                        continue;
 
-               cdev = tzp->tbp[i].cdev;
-               instance = get_thermal_instance(tz, cdev, trip);
-               if (!instance)
-                       continue;
+               if (!total_weight)
+                       percentage = 100 / total_instance;
+               else
+                       percentage = (instance->weight * 100) / total_weight;
 
-               instance->target = get_target_state(tz, cdev,
-                                       tzp->tbp[i].weight, cur_trip_level);
+               instance->target = get_target_state(tz, cdev, percentage,
+                                                   cur_trip_level);
 
                instance->cdev->updated = false;
                thermal_cdev_update(cdev);
diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c
new file mode 100644 (file)
index 0000000..d5dd357
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * Hisilicon thermal sensor driver
+ *
+ * Copyright (c) 2014-2015 Hisilicon Limited.
+ * Copyright (c) 2014-2015 Linaro Limited.
+ *
+ * Xinwei Kong <kong.kongxinwei@hisilicon.com>
+ * Leo Yan <leo.yan@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include "thermal_core.h"
+
+#define TEMP0_TH                       (0x4)
+#define TEMP0_RST_TH                   (0x8)
+#define TEMP0_CFG                      (0xC)
+#define TEMP0_EN                       (0x10)
+#define TEMP0_INT_EN                   (0x14)
+#define TEMP0_INT_CLR                  (0x18)
+#define TEMP0_RST_MSK                  (0x1C)
+#define TEMP0_VALUE                    (0x28)
+
+#define HISI_TEMP_BASE                 (-60)
+#define HISI_TEMP_RESET                        (100000)
+
+#define HISI_MAX_SENSORS               4
+
+struct hisi_thermal_sensor {
+       struct hisi_thermal_data *thermal;
+       struct thermal_zone_device *tzd;
+
+       long sensor_temp;
+       uint32_t id;
+       uint32_t thres_temp;
+};
+
+struct hisi_thermal_data {
+       struct mutex thermal_lock;    /* protects register data */
+       struct platform_device *pdev;
+       struct clk *clk;
+       struct hisi_thermal_sensor sensors[HISI_MAX_SENSORS];
+
+       int irq, irq_bind_sensor;
+       bool irq_enabled;
+
+       void __iomem *regs;
+};
+
+/* in millicelsius */
+static inline int _step_to_temp(int step)
+{
+       /*
+        * Every step equals (1 * 200) / 255 celsius, and finally
+        * need convert to millicelsius.
+        */
+       return (HISI_TEMP_BASE + (step * 200 / 255)) * 1000;
+}
+
+static inline long _temp_to_step(long temp)
+{
+       return ((temp / 1000 - HISI_TEMP_BASE) * 255 / 200);
+}
+
+static long hisi_thermal_get_sensor_temp(struct hisi_thermal_data *data,
+                                        struct hisi_thermal_sensor *sensor)
+{
+       long val;
+
+       mutex_lock(&data->thermal_lock);
+
+       /* disable interrupt */
+       writel(0x0, data->regs + TEMP0_INT_EN);
+       writel(0x1, data->regs + TEMP0_INT_CLR);
+
+       /* disable module firstly */
+       writel(0x0, data->regs + TEMP0_EN);
+
+       /* select sensor id */
+       writel((sensor->id << 12), data->regs + TEMP0_CFG);
+
+       /* enable module */
+       writel(0x1, data->regs + TEMP0_EN);
+
+       usleep_range(3000, 5000);
+
+       val = readl(data->regs + TEMP0_VALUE);
+       val = _step_to_temp(val);
+
+       mutex_unlock(&data->thermal_lock);
+
+       return val;
+}
+
+static void hisi_thermal_enable_bind_irq_sensor
+                       (struct hisi_thermal_data *data)
+{
+       struct hisi_thermal_sensor *sensor;
+
+       mutex_lock(&data->thermal_lock);
+
+       sensor = &data->sensors[data->irq_bind_sensor];
+
+       /* setting the hdak time */
+       writel(0x0, data->regs + TEMP0_CFG);
+
+       /* disable module firstly */
+       writel(0x0, data->regs + TEMP0_RST_MSK);
+       writel(0x0, data->regs + TEMP0_EN);
+
+       /* select sensor id */
+       writel((sensor->id << 12), data->regs + TEMP0_CFG);
+
+       /* enable for interrupt */
+       writel(_temp_to_step(sensor->thres_temp) | 0x0FFFFFF00,
+              data->regs + TEMP0_TH);
+
+       writel(_temp_to_step(HISI_TEMP_RESET), data->regs + TEMP0_RST_TH);
+
+       /* enable module */
+       writel(0x1, data->regs + TEMP0_RST_MSK);
+       writel(0x1, data->regs + TEMP0_EN);
+
+       writel(0x0, data->regs + TEMP0_INT_CLR);
+       writel(0x1, data->regs + TEMP0_INT_EN);
+
+       usleep_range(3000, 5000);
+
+       mutex_unlock(&data->thermal_lock);
+}
+
+static void hisi_thermal_disable_sensor(struct hisi_thermal_data *data)
+{
+       mutex_lock(&data->thermal_lock);
+
+       /* disable sensor module */
+       writel(0x0, data->regs + TEMP0_INT_EN);
+       writel(0x0, data->regs + TEMP0_RST_MSK);
+       writel(0x0, data->regs + TEMP0_EN);
+
+       mutex_unlock(&data->thermal_lock);
+}
+
+static int hisi_thermal_get_temp(void *_sensor, long *temp)
+{
+       struct hisi_thermal_sensor *sensor = _sensor;
+       struct hisi_thermal_data *data = sensor->thermal;
+
+       int sensor_id = 0, i;
+       long max_temp = 0;
+
+       *temp = hisi_thermal_get_sensor_temp(data, sensor);
+
+       sensor->sensor_temp = *temp;
+
+       for (i = 0; i < HISI_MAX_SENSORS; i++) {
+               if (data->sensors[i].sensor_temp >= max_temp) {
+                       max_temp = data->sensors[i].sensor_temp;
+                       sensor_id = i;
+               }
+       }
+
+       mutex_lock(&data->thermal_lock);
+       data->irq_bind_sensor = sensor_id;
+       mutex_unlock(&data->thermal_lock);
+
+       dev_dbg(&data->pdev->dev, "id=%d, irq=%d, temp=%ld, thres=%d\n",
+               sensor->id, data->irq_enabled, *temp, sensor->thres_temp);
+       /*
+        * Bind irq to sensor for two cases:
+        *   Reenable alarm IRQ if temperature below threshold;
+        *   if irq has been enabled, always set it;
+        */
+       if (data->irq_enabled) {
+               hisi_thermal_enable_bind_irq_sensor(data);
+               return 0;
+       }
+
+       if (max_temp < sensor->thres_temp) {
+               data->irq_enabled = true;
+               hisi_thermal_enable_bind_irq_sensor(data);
+               enable_irq(data->irq);
+       }
+
+       return 0;
+}
+
+static struct thermal_zone_of_device_ops hisi_of_thermal_ops = {
+       .get_temp = hisi_thermal_get_temp,
+};
+
+static irqreturn_t hisi_thermal_alarm_irq(int irq, void *dev)
+{
+       struct hisi_thermal_data *data = dev;
+
+       disable_irq_nosync(irq);
+       data->irq_enabled = false;
+
+       return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev)
+{
+       struct hisi_thermal_data *data = dev;
+       struct hisi_thermal_sensor *sensor;
+       int i;
+
+       mutex_lock(&data->thermal_lock);
+       sensor = &data->sensors[data->irq_bind_sensor];
+
+       dev_crit(&data->pdev->dev, "THERMAL ALARM: T > %d\n",
+                sensor->thres_temp / 1000);
+       mutex_unlock(&data->thermal_lock);
+
+       for (i = 0; i < HISI_MAX_SENSORS; i++)
+               thermal_zone_device_update(data->sensors[i].tzd);
+
+       return IRQ_HANDLED;
+}
+
+static int hisi_thermal_register_sensor(struct platform_device *pdev,
+                                       struct hisi_thermal_data *data,
+                                       struct hisi_thermal_sensor *sensor,
+                                       int index)
+{
+       int ret, i;
+       const struct thermal_trip *trip;
+
+       sensor->id = index;
+       sensor->thermal = data;
+
+       sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, sensor->id,
+                               sensor, &hisi_of_thermal_ops);
+       if (IS_ERR(sensor->tzd)) {
+               ret = PTR_ERR(sensor->tzd);
+               dev_err(&pdev->dev, "failed to register sensor id %d: %d\n",
+                       sensor->id, ret);
+               return ret;
+       }
+
+       trip = of_thermal_get_trip_points(sensor->tzd);
+
+       for (i = 0; i < of_thermal_get_ntrips(sensor->tzd); i++) {
+               if (trip[i].type == THERMAL_TRIP_PASSIVE) {
+                       sensor->thres_temp = trip[i].temperature;
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+static const struct of_device_id of_hisi_thermal_match[] = {
+       { .compatible = "hisilicon,tsensor" },
+       { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_hisi_thermal_match);
+
+static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor,
+                                      bool on)
+{
+       struct thermal_zone_device *tzd = sensor->tzd;
+
+       tzd->ops->set_mode(tzd,
+               on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
+}
+
+static int hisi_thermal_probe(struct platform_device *pdev)
+{
+       struct hisi_thermal_data *data;
+       struct resource *res;
+       int i;
+       int ret;
+
+       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       mutex_init(&data->thermal_lock);
+       data->pdev = pdev;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       data->regs = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(data->regs)) {
+               dev_err(&pdev->dev, "failed to get io address\n");
+               return PTR_ERR(data->regs);
+       }
+
+       data->irq = platform_get_irq(pdev, 0);
+       if (data->irq < 0)
+               return data->irq;
+
+       ret = devm_request_threaded_irq(&pdev->dev, data->irq,
+                                       hisi_thermal_alarm_irq,
+                                       hisi_thermal_alarm_irq_thread,
+                                       0, "hisi_thermal", data);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
+               return ret;
+       }
+
+       platform_set_drvdata(pdev, data);
+
+       data->clk = devm_clk_get(&pdev->dev, "thermal_clk");
+       if (IS_ERR(data->clk)) {
+               ret = PTR_ERR(data->clk);
+               if (ret != -EPROBE_DEFER)
+                       dev_err(&pdev->dev,
+                               "failed to get thermal clk: %d\n", ret);
+               return ret;
+       }
+
+       /* enable clock for thermal */
+       ret = clk_prepare_enable(data->clk);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
+               return ret;
+       }
+
+       for (i = 0; i < HISI_MAX_SENSORS; ++i) {
+               ret = hisi_thermal_register_sensor(pdev, data,
+                                                  &data->sensors[i], i);
+               if (ret) {
+                       dev_err(&pdev->dev,
+                               "failed to register thermal sensor: %d\n", ret);
+                       goto err_get_sensor_data;
+               }
+       }
+
+       hisi_thermal_enable_bind_irq_sensor(data);
+       data->irq_enabled = true;
+
+       for (i = 0; i < HISI_MAX_SENSORS; i++)
+               hisi_thermal_toggle_sensor(&data->sensors[i], true);
+
+       return 0;
+
+err_get_sensor_data:
+       clk_disable_unprepare(data->clk);
+
+       return ret;
+}
+
+static int hisi_thermal_remove(struct platform_device *pdev)
+{
+       struct hisi_thermal_data *data = platform_get_drvdata(pdev);
+       int i;
+
+       for (i = 0; i < HISI_MAX_SENSORS; i++) {
+               struct hisi_thermal_sensor *sensor = &data->sensors[i];
+
+               hisi_thermal_toggle_sensor(sensor, false);
+               thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd);
+       }
+
+       hisi_thermal_disable_sensor(data);
+       clk_disable_unprepare(data->clk);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hisi_thermal_suspend(struct device *dev)
+{
+       struct hisi_thermal_data *data = dev_get_drvdata(dev);
+
+       hisi_thermal_disable_sensor(data);
+       data->irq_enabled = false;
+
+       clk_disable_unprepare(data->clk);
+
+       return 0;
+}
+
+static int hisi_thermal_resume(struct device *dev)
+{
+       struct hisi_thermal_data *data = dev_get_drvdata(dev);
+
+       clk_prepare_enable(data->clk);
+
+       data->irq_enabled = true;
+       hisi_thermal_enable_bind_irq_sensor(data);
+
+       return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
+                        hisi_thermal_suspend, hisi_thermal_resume);
+
+static struct platform_driver hisi_thermal_driver = {
+       .driver = {
+               .name           = "hisi_thermal",
+               .owner          = THIS_MODULE,
+               .pm             = &hisi_thermal_pm_ops,
+               .of_match_table = of_hisi_thermal_match,
+       },
+       .probe  = hisi_thermal_probe,
+       .remove = hisi_thermal_remove,
+};
+
+module_platform_driver(hisi_thermal_driver);
+
+MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>");
+MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
+MODULE_DESCRIPTION("Hisilicon thermal driver");
+MODULE_LICENSE("GPL v2");
index 2ccbc0788353e9488e3f730de80d52ecafaf9082..fde4c2876d14612c2c3bef5fb0ed55fe78ea216c 100644 (file)
@@ -306,7 +306,8 @@ static int imx_bind(struct thermal_zone_device *tz,
 
        ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
                                               THERMAL_NO_LIMIT,
-                                              THERMAL_NO_LIMIT);
+                                              THERMAL_NO_LIMIT,
+                                              THERMAL_WEIGHT_DEFAULT);
        if (ret) {
                dev_err(&tz->device,
                        "binding zone %s with cdev %s failed:%d\n",
index 5e8d8e91ea6d9d6056c714b16a3eafd9d09f835e..3df3dc34b124261789469208a07ffd85604292db 100644 (file)
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/pci.h>
+#include <linux/interrupt.h>
 #include <linux/platform_device.h>
 #include <linux/acpi.h>
 #include <linux/thermal.h>
 #include "int340x_thermal_zone.h"
+#include "../intel_soc_dts_iosf.h"
 
 /* Broadwell-U/HSB thermal reporting device */
 #define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
 #define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
 
+/* Skylake thermal reporting device */
+#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903
+
 /* Braswell thermal reporting device */
 #define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
 
@@ -42,6 +47,7 @@ struct proc_thermal_device {
        struct acpi_device *adev;
        struct power_config power_limits[2];
        struct int34x_thermal_zone *int340x_zone;
+       struct intel_soc_dts_sensors *soc_dts;
 };
 
 enum proc_thermal_emum_mode_type {
@@ -308,6 +314,18 @@ static int int3401_remove(struct platform_device *pdev)
        return 0;
 }
 
+static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid)
+{
+       struct proc_thermal_device *proc_priv;
+       struct pci_dev *pdev = devid;
+
+       proc_priv = pci_get_drvdata(pdev);
+
+       intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts);
+
+       return IRQ_HANDLED;
+}
+
 static int  proc_thermal_pci_probe(struct pci_dev *pdev,
                                   const struct pci_device_id *unused)
 {
@@ -334,18 +352,57 @@ static int  proc_thermal_pci_probe(struct pci_dev *pdev,
        pci_set_drvdata(pdev, proc_priv);
        proc_thermal_emum_mode = PROC_THERMAL_PCI;
 
+       if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) {
+               /*
+                * Enumerate additional DTS sensors available via IOSF.
+                * But we are not treating as a failure condition, if
+                * there are no aux DTSs enabled or fails. This driver
+                * already exposes sensors, which can be accessed via
+                * ACPI/MSR. So we don't want to fail for auxiliary DTSs.
+                */
+               proc_priv->soc_dts = intel_soc_dts_iosf_init(
+                                       INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0);
+
+               if (proc_priv->soc_dts && pdev->irq) {
+                       ret = pci_enable_msi(pdev);
+                       if (!ret) {
+                               ret = request_threaded_irq(pdev->irq, NULL,
+                                               proc_thermal_pci_msi_irq,
+                                               IRQF_ONESHOT, "proc_thermal",
+                                               pdev);
+                               if (ret) {
+                                       intel_soc_dts_iosf_exit(
+                                                       proc_priv->soc_dts);
+                                       pci_disable_msi(pdev);
+                                       proc_priv->soc_dts = NULL;
+                               }
+                       }
+               } else
+                       dev_err(&pdev->dev, "No auxiliary DTSs enabled\n");
+       }
+
        return 0;
 }
 
 static void  proc_thermal_pci_remove(struct pci_dev *pdev)
 {
-       proc_thermal_remove(pci_get_drvdata(pdev));
+       struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
+
+       if (proc_priv->soc_dts) {
+               intel_soc_dts_iosf_exit(proc_priv->soc_dts);
+               if (pdev->irq) {
+                       free_irq(pdev->irq, pdev);
+                       pci_disable_msi(pdev);
+               }
+       }
+       proc_thermal_remove(proc_priv);
        pci_disable_device(pdev);
 }
 
 static const struct pci_device_id proc_thermal_pci_ids[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)},
        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)},
+       { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL)},
        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)},
        { 0, },
 };
index 725718e97a0bc86f8d69d3fc112fba7670543402..2e6716104d3fa8c9aa45f3a39fdafe8c355d4ead 100644 (file)
@@ -697,6 +697,7 @@ static const struct x86_cpu_id intel_powerclamp_ids[] __initconst = {
        { X86_VENDOR_INTEL, 6, 0x4d},
        { X86_VENDOR_INTEL, 6, 0x4f},
        { X86_VENDOR_INTEL, 6, 0x56},
+       { X86_VENDOR_INTEL, 6, 0x57},
        {}
 };
 MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
diff --git a/drivers/thermal/intel_quark_dts_thermal.c b/drivers/thermal/intel_quark_dts_thermal.c
new file mode 100644 (file)
index 0000000..4434ec8
--- /dev/null
@@ -0,0 +1,473 @@
+/*
+ * intel_quark_dts_thermal.c
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2015 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * Contact Information:
+ *  Ong Boon Leong <boon.leong.ong@intel.com>
+ *  Intel Malaysia, Penang
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2015 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Quark DTS thermal driver is implemented by referencing
+ * intel_soc_dts_thermal.c.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/thermal.h>
+#include <asm/cpu_device_id.h>
+#include <asm/iosf_mbi.h>
+
+#define X86_FAMILY_QUARK       0x5
+#define X86_MODEL_QUARK_X1000  0x9
+
+/* DTS reset is programmed via QRK_MBI_UNIT_SOC */
+#define QRK_DTS_REG_OFFSET_RESET       0x34
+#define QRK_DTS_RESET_BIT              BIT(0)
+
+/* DTS enable is programmed via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_ENABLE      0xB0
+#define QRK_DTS_ENABLE_BIT             BIT(15)
+
+/* Temperature Register is read via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_TEMP                0xB1
+#define QRK_DTS_MASK_TEMP              0xFF
+#define QRK_DTS_OFFSET_TEMP            0
+#define QRK_DTS_OFFSET_REL_TEMP                16
+#define QRK_DTS_TEMP_BASE              50
+
+/* Programmable Trip Point Register is configured via QRK_MBI_UNIT_RMU */
+#define QRK_DTS_REG_OFFSET_PTPS                0xB2
+#define QRK_DTS_MASK_TP_THRES          0xFF
+#define QRK_DTS_SHIFT_TP               8
+#define QRK_DTS_ID_TP_CRITICAL         0
+#define QRK_DTS_SAFE_TP_THRES          105
+
+/* Thermal Sensor Register Lock */
+#define QRK_DTS_REG_OFFSET_LOCK                0x71
+#define QRK_DTS_LOCK_BIT               BIT(5)
+
+/* Quark DTS has 2 trip points: hot & catastrophic */
+#define QRK_MAX_DTS_TRIPS      2
+/* If DTS not locked, all trip points are configurable */
+#define QRK_DTS_WR_MASK_SET    0x3
+/* If DTS locked, all trip points are not configurable */
+#define QRK_DTS_WR_MASK_CLR    0
+
+#define DEFAULT_POLL_DELAY     2000
+
+struct soc_sensor_entry {
+       bool locked;
+       u32 store_ptps;
+       u32 store_dts_enable;
+       enum thermal_device_mode mode;
+       struct thermal_zone_device *tzone;
+};
+
+static struct soc_sensor_entry *soc_dts;
+
+static int polling_delay = DEFAULT_POLL_DELAY;
+module_param(polling_delay, int, 0644);
+MODULE_PARM_DESC(polling_delay,
+       "Polling interval for checking trip points (in milliseconds)");
+
+static DEFINE_MUTEX(dts_update_mutex);
+
+static int soc_dts_enable(struct thermal_zone_device *tzd)
+{
+       u32 out;
+       struct soc_sensor_entry *aux_entry = tzd->devdata;
+       int ret;
+
+       ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                                       QRK_DTS_REG_OFFSET_ENABLE, &out);
+       if (ret)
+               return ret;
+
+       if (out & QRK_DTS_ENABLE_BIT) {
+               aux_entry->mode = THERMAL_DEVICE_ENABLED;
+               return 0;
+       }
+
+       if (!aux_entry->locked) {
+               out |= QRK_DTS_ENABLE_BIT;
+               ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+                                       QRK_DTS_REG_OFFSET_ENABLE, out);
+               if (ret)
+                       return ret;
+
+               aux_entry->mode = THERMAL_DEVICE_ENABLED;
+       } else {
+               aux_entry->mode = THERMAL_DEVICE_DISABLED;
+               pr_info("DTS is locked. Cannot enable DTS\n");
+               ret = -EPERM;
+       }
+
+       return ret;
+}
+
+static int soc_dts_disable(struct thermal_zone_device *tzd)
+{
+       u32 out;
+       struct soc_sensor_entry *aux_entry = tzd->devdata;
+       int ret;
+
+       ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                                       QRK_DTS_REG_OFFSET_ENABLE, &out);
+       if (ret)
+               return ret;
+
+       if (!(out & QRK_DTS_ENABLE_BIT)) {
+               aux_entry->mode = THERMAL_DEVICE_DISABLED;
+               return 0;
+       }
+
+       if (!aux_entry->locked) {
+               out &= ~QRK_DTS_ENABLE_BIT;
+               ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+                                       QRK_DTS_REG_OFFSET_ENABLE, out);
+
+               if (ret)
+                       return ret;
+
+               aux_entry->mode = THERMAL_DEVICE_DISABLED;
+       } else {
+               aux_entry->mode = THERMAL_DEVICE_ENABLED;
+               pr_info("DTS is locked. Cannot disable DTS\n");
+               ret = -EPERM;
+       }
+
+       return ret;
+}
+
+static int _get_trip_temp(int trip, unsigned long *temp)
+{
+       int status;
+       u32 out;
+
+       mutex_lock(&dts_update_mutex);
+       status = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                               QRK_DTS_REG_OFFSET_PTPS, &out);
+       mutex_unlock(&dts_update_mutex);
+
+       if (status)
+               return status;
+
+       /*
+        * Thermal Sensor Programmable Trip Point Register has 8-bit
+        * fields for critical (catastrophic) and hot set trip point
+        * thresholds. The threshold value is always offset by its
+        * temperature base (50 degree Celsius).
+        */
+       *temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES;
+       *temp -= QRK_DTS_TEMP_BASE;
+
+       return 0;
+}
+
+static inline int sys_get_trip_temp(struct thermal_zone_device *tzd,
+                               int trip, unsigned long *temp)
+{
+       return _get_trip_temp(trip, temp);
+}
+
+static inline int sys_get_crit_temp(struct thermal_zone_device *tzd,
+                               unsigned long *temp)
+{
+       return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp);
+}
+
+static int update_trip_temp(struct soc_sensor_entry *aux_entry,
+                               int trip, unsigned long temp)
+{
+       u32 out;
+       u32 temp_out;
+       u32 store_ptps;
+       int ret;
+
+       mutex_lock(&dts_update_mutex);
+       if (aux_entry->locked) {
+               ret = -EPERM;
+               goto failed;
+       }
+
+       ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                               QRK_DTS_REG_OFFSET_PTPS, &store_ptps);
+       if (ret)
+               goto failed;
+
+       /*
+        * Protection against unsafe trip point thresdhold value.
+        * As Quark X1000 data-sheet does not provide any recommendation
+        * regarding the safe trip point threshold value to use, we choose
+        * the safe value according to the threshold value set by UEFI BIOS.
+        */
+       if (temp > QRK_DTS_SAFE_TP_THRES)
+               temp = QRK_DTS_SAFE_TP_THRES;
+
+       /*
+        * Thermal Sensor Programmable Trip Point Register has 8-bit
+        * fields for critical (catastrophic) and hot set trip point
+        * thresholds. The threshold value is always offset by its
+        * temperature base (50 degree Celsius).
+        */
+       temp_out = temp + QRK_DTS_TEMP_BASE;
+       out = (store_ptps & ~(QRK_DTS_MASK_TP_THRES <<
+               (trip * QRK_DTS_SHIFT_TP)));
+       out |= (temp_out & QRK_DTS_MASK_TP_THRES) <<
+               (trip * QRK_DTS_SHIFT_TP);
+
+       ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+                               QRK_DTS_REG_OFFSET_PTPS, out);
+
+failed:
+       mutex_unlock(&dts_update_mutex);
+       return ret;
+}
+
+static inline int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
+                               unsigned long temp)
+{
+       return update_trip_temp(tzd->devdata, trip, temp);
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *thermal,
+               int trip, enum thermal_trip_type *type)
+{
+       if (trip)
+               *type = THERMAL_TRIP_HOT;
+       else
+               *type = THERMAL_TRIP_CRITICAL;
+
+       return 0;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd,
+                               unsigned long *temp)
+{
+       u32 out;
+       int ret;
+
+       mutex_lock(&dts_update_mutex);
+       ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                                       QRK_DTS_REG_OFFSET_TEMP, &out);
+       mutex_unlock(&dts_update_mutex);
+
+       if (ret)
+               return ret;
+
+       /*
+        * Thermal Sensor Temperature Register has 8-bit field
+        * for temperature value (offset by temperature base
+        * 50 degree Celsius).
+        */
+       out = (out >> QRK_DTS_OFFSET_TEMP) & QRK_DTS_MASK_TEMP;
+       *temp = out - QRK_DTS_TEMP_BASE;
+
+       return 0;
+}
+
+static int sys_get_mode(struct thermal_zone_device *tzd,
+                               enum thermal_device_mode *mode)
+{
+       struct soc_sensor_entry *aux_entry = tzd->devdata;
+       *mode = aux_entry->mode;
+       return 0;
+}
+
+static int sys_set_mode(struct thermal_zone_device *tzd,
+                               enum thermal_device_mode mode)
+{
+       int ret;
+
+       mutex_lock(&dts_update_mutex);
+       if (mode == THERMAL_DEVICE_ENABLED)
+               ret = soc_dts_enable(tzd);
+       else
+               ret = soc_dts_disable(tzd);
+       mutex_unlock(&dts_update_mutex);
+
+       return ret;
+}
+
+static struct thermal_zone_device_ops tzone_ops = {
+       .get_temp = sys_get_curr_temp,
+       .get_trip_temp = sys_get_trip_temp,
+       .get_trip_type = sys_get_trip_type,
+       .set_trip_temp = sys_set_trip_temp,
+       .get_crit_temp = sys_get_crit_temp,
+       .get_mode = sys_get_mode,
+       .set_mode = sys_set_mode,
+};
+
+static void free_soc_dts(struct soc_sensor_entry *aux_entry)
+{
+       if (aux_entry) {
+               if (!aux_entry->locked) {
+                       mutex_lock(&dts_update_mutex);
+                       iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+                                       QRK_DTS_REG_OFFSET_ENABLE,
+                                       aux_entry->store_dts_enable);
+
+                       iosf_mbi_write(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_WRITE,
+                                       QRK_DTS_REG_OFFSET_PTPS,
+                                       aux_entry->store_ptps);
+                       mutex_unlock(&dts_update_mutex);
+               }
+               thermal_zone_device_unregister(aux_entry->tzone);
+               kfree(aux_entry);
+       }
+}
+
+static struct soc_sensor_entry *alloc_soc_dts(void)
+{
+       struct soc_sensor_entry *aux_entry;
+       int err;
+       u32 out;
+       int wr_mask;
+
+       aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL);
+       if (!aux_entry) {
+               err = -ENOMEM;
+               return ERR_PTR(-ENOMEM);
+       }
+
+       /* Check if DTS register is locked */
+       err = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                                       QRK_DTS_REG_OFFSET_LOCK,
+                                       &out);
+       if (err)
+               goto err_ret;
+
+       if (out & QRK_DTS_LOCK_BIT) {
+               aux_entry->locked = true;
+               wr_mask = QRK_DTS_WR_MASK_CLR;
+       } else {
+               aux_entry->locked = false;
+               wr_mask = QRK_DTS_WR_MASK_SET;
+       }
+
+       /* Store DTS default state if DTS registers are not locked */
+       if (!aux_entry->locked) {
+               /* Store DTS default enable for restore on exit */
+               err = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                                       QRK_DTS_REG_OFFSET_ENABLE,
+                                       &aux_entry->store_dts_enable);
+               if (err)
+                       goto err_ret;
+
+               /* Store DTS default PTPS register for restore on exit */
+               err = iosf_mbi_read(QRK_MBI_UNIT_RMU, QRK_MBI_RMU_READ,
+                                       QRK_DTS_REG_OFFSET_PTPS,
+                                       &aux_entry->store_ptps);
+               if (err)
+                       goto err_ret;
+       }
+
+       aux_entry->tzone = thermal_zone_device_register("quark_dts",
+                       QRK_MAX_DTS_TRIPS,
+                       wr_mask,
+                       aux_entry, &tzone_ops, NULL, 0, polling_delay);
+       if (IS_ERR(aux_entry->tzone)) {
+               err = PTR_ERR(aux_entry->tzone);
+               goto err_ret;
+       }
+
+       mutex_lock(&dts_update_mutex);
+       err = soc_dts_enable(aux_entry->tzone);
+       mutex_unlock(&dts_update_mutex);
+       if (err)
+               goto err_aux_status;
+
+       return aux_entry;
+
+err_aux_status:
+       thermal_zone_device_unregister(aux_entry->tzone);
+err_ret:
+       kfree(aux_entry);
+       return ERR_PTR(err);
+}
+
+static const struct x86_cpu_id qrk_thermal_ids[] __initconst  = {
+       { X86_VENDOR_INTEL, X86_FAMILY_QUARK, X86_MODEL_QUARK_X1000 },
+       {}
+};
+MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids);
+
+static int __init intel_quark_thermal_init(void)
+{
+       int err = 0;
+
+       if (!x86_match_cpu(qrk_thermal_ids) || !iosf_mbi_available())
+               return -ENODEV;
+
+       soc_dts = alloc_soc_dts();
+       if (IS_ERR(soc_dts)) {
+               err = PTR_ERR(soc_dts);
+               goto err_free;
+       }
+
+       return 0;
+
+err_free:
+       free_soc_dts(soc_dts);
+       return err;
+}
+
+static void __exit intel_quark_thermal_exit(void)
+{
+       free_soc_dts(soc_dts);
+}
+
+module_init(intel_quark_thermal_init)
+module_exit(intel_quark_thermal_exit)
+
+MODULE_DESCRIPTION("Intel Quark DTS Thermal Driver");
+MODULE_AUTHOR("Ong Boon Leong <boon.leong.ong@intel.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/thermal/intel_soc_dts_iosf.c b/drivers/thermal/intel_soc_dts_iosf.c
new file mode 100644 (file)
index 0000000..42e4b6a
--- /dev/null
@@ -0,0 +1,478 @@
+/*
+ * intel_soc_dts_iosf.c
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <asm/iosf_mbi.h>
+#include "intel_soc_dts_iosf.h"
+
+#define SOC_DTS_OFFSET_ENABLE          0xB0
+#define SOC_DTS_OFFSET_TEMP            0xB1
+
+#define SOC_DTS_OFFSET_PTPS            0xB2
+#define SOC_DTS_OFFSET_PTTS            0xB3
+#define SOC_DTS_OFFSET_PTTSS           0xB4
+#define SOC_DTS_OFFSET_PTMC            0x80
+#define SOC_DTS_TE_AUX0                        0xB5
+#define SOC_DTS_TE_AUX1                        0xB6
+
+#define SOC_DTS_AUX0_ENABLE_BIT                BIT(0)
+#define SOC_DTS_AUX1_ENABLE_BIT                BIT(1)
+#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16)
+#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17)
+#define SOC_DTS_TE_SCI_ENABLE          BIT(9)
+#define SOC_DTS_TE_SMI_ENABLE          BIT(10)
+#define SOC_DTS_TE_MSI_ENABLE          BIT(11)
+#define SOC_DTS_TE_APICA_ENABLE                BIT(14)
+#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4)
+
+/* DTS encoding for TJ MAX temperature */
+#define SOC_DTS_TJMAX_ENCODING         0x7F
+
+/* Only 2 out of 4 is allowed for OSPM */
+#define SOC_MAX_DTS_TRIPS              2
+
+/* Mask for two trips in status bits */
+#define SOC_DTS_TRIP_MASK              0x03
+
+/* DTS0 and DTS 1 */
+#define SOC_MAX_DTS_SENSORS            2
+
+static int get_tj_max(u32 *tj_max)
+{
+       u32 eax, edx;
+       u32 val;
+       int err;
+
+       err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
+       if (err)
+               goto err_ret;
+       else {
+               val = (eax >> 16) & 0xff;
+               if (val)
+                       *tj_max = val * 1000;
+               else {
+                       err = -EINVAL;
+                       goto err_ret;
+               }
+       }
+
+       return 0;
+err_ret:
+       *tj_max = 0;
+
+       return err;
+}
+
+static int sys_get_trip_temp(struct thermal_zone_device *tzd, int trip,
+                            unsigned long *temp)
+{
+       int status;
+       u32 out;
+       struct intel_soc_dts_sensor_entry *dts;
+       struct intel_soc_dts_sensors *sensors;
+
+       dts = tzd->devdata;
+       sensors = dts->sensors;
+       mutex_lock(&sensors->dts_update_lock);
+       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                              SOC_DTS_OFFSET_PTPS, &out);
+       mutex_unlock(&sensors->dts_update_lock);
+       if (status)
+               return status;
+
+       out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING;
+       if (!out)
+               *temp = 0;
+       else
+               *temp = sensors->tj_max - out * 1000;
+
+       return 0;
+}
+
+static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts,
+                           int thres_index, unsigned long temp,
+                           enum thermal_trip_type trip_type)
+{
+       int status;
+       u32 temp_out;
+       u32 out;
+       u32 store_ptps;
+       u32 store_ptmc;
+       u32 store_te_out;
+       u32 te_out;
+       u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE;
+       struct intel_soc_dts_sensors *sensors = dts->sensors;
+
+       if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI)
+               int_enable_bit |= SOC_DTS_TE_MSI_ENABLE;
+
+       temp_out = (sensors->tj_max - temp) / 1000;
+
+       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                              SOC_DTS_OFFSET_PTPS, &store_ptps);
+       if (status)
+               return status;
+
+       out = (store_ptps & ~(0xFF << (thres_index * 8)));
+       out |= (temp_out & 0xFF) << (thres_index * 8);
+       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                               SOC_DTS_OFFSET_PTPS, out);
+       if (status)
+               return status;
+
+       pr_debug("update_trip_temp PTPS = %x\n", out);
+       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                              SOC_DTS_OFFSET_PTMC, &out);
+       if (status)
+               goto err_restore_ptps;
+
+       store_ptmc = out;
+
+       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                              SOC_DTS_TE_AUX0 + thres_index,
+                              &te_out);
+       if (status)
+               goto err_restore_ptmc;
+
+       store_te_out = te_out;
+       /* Enable for CPU module 0 and module 1 */
+       out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT |
+                                       SOC_DTS_CPU_MODULE1_ENABLE_BIT);
+       if (temp) {
+               if (thres_index)
+                       out |= SOC_DTS_AUX1_ENABLE_BIT;
+               else
+                       out |= SOC_DTS_AUX0_ENABLE_BIT;
+               te_out |= int_enable_bit;
+       } else {
+               if (thres_index)
+                       out &= ~SOC_DTS_AUX1_ENABLE_BIT;
+               else
+                       out &= ~SOC_DTS_AUX0_ENABLE_BIT;
+               te_out &= ~int_enable_bit;
+       }
+       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                               SOC_DTS_OFFSET_PTMC, out);
+       if (status)
+               goto err_restore_te_out;
+
+       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                               SOC_DTS_TE_AUX0 + thres_index,
+                               te_out);
+       if (status)
+               goto err_restore_te_out;
+
+       dts->trip_types[thres_index] = trip_type;
+
+       return 0;
+err_restore_te_out:
+       iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                      SOC_DTS_OFFSET_PTMC, store_te_out);
+err_restore_ptmc:
+       iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                      SOC_DTS_OFFSET_PTMC, store_ptmc);
+err_restore_ptps:
+       iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                      SOC_DTS_OFFSET_PTPS, store_ptps);
+       /* Nothing we can do if restore fails */
+
+       return status;
+}
+
+static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
+                            unsigned long temp)
+{
+       struct intel_soc_dts_sensor_entry *dts = tzd->devdata;
+       struct intel_soc_dts_sensors *sensors = dts->sensors;
+       int status;
+
+       if (temp > sensors->tj_max)
+               return -EINVAL;
+
+       mutex_lock(&sensors->dts_update_lock);
+       status = update_trip_temp(tzd->devdata, trip, temp,
+                                 dts->trip_types[trip]);
+       mutex_unlock(&sensors->dts_update_lock);
+
+       return status;
+}
+
+static int sys_get_trip_type(struct thermal_zone_device *tzd,
+                            int trip, enum thermal_trip_type *type)
+{
+       struct intel_soc_dts_sensor_entry *dts;
+
+       dts = tzd->devdata;
+
+       *type = dts->trip_types[trip];
+
+       return 0;
+}
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzd,
+                            unsigned long *temp)
+{
+       int status;
+       u32 out;
+       struct intel_soc_dts_sensor_entry *dts;
+       struct intel_soc_dts_sensors *sensors;
+
+       dts = tzd->devdata;
+       sensors = dts->sensors;
+       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                              SOC_DTS_OFFSET_TEMP, &out);
+       if (status)
+               return status;
+
+       out = (out & dts->temp_mask) >> dts->temp_shift;
+       out -= SOC_DTS_TJMAX_ENCODING;
+       *temp = sensors->tj_max - out * 1000;
+
+       return 0;
+}
+
+static struct thermal_zone_device_ops tzone_ops = {
+       .get_temp = sys_get_curr_temp,
+       .get_trip_temp = sys_get_trip_temp,
+       .get_trip_type = sys_get_trip_type,
+       .set_trip_temp = sys_set_trip_temp,
+};
+
+static int soc_dts_enable(int id)
+{
+       u32 out;
+       int ret;
+
+       ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                           SOC_DTS_OFFSET_ENABLE, &out);
+       if (ret)
+               return ret;
+
+       if (!(out & BIT(id))) {
+               out |= BIT(id);
+               ret = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                                    SOC_DTS_OFFSET_ENABLE, out);
+               if (ret)
+                       return ret;
+       }
+
+       return ret;
+}
+
+static void remove_dts_thermal_zone(struct intel_soc_dts_sensor_entry *dts)
+{
+       if (dts) {
+               iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                              SOC_DTS_OFFSET_ENABLE, dts->store_status);
+               thermal_zone_device_unregister(dts->tzone);
+       }
+}
+
+static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts,
+                               bool notification_support, int trip_cnt,
+                               int read_only_trip_cnt)
+{
+       char name[10];
+       int trip_count = 0;
+       int trip_mask = 0;
+       u32 store_ptps;
+       int ret;
+       int i;
+
+       /* Store status to restor on exit */
+       ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                           SOC_DTS_OFFSET_ENABLE,
+                           &dts->store_status);
+       if (ret)
+               goto err_ret;
+
+       dts->id = id;
+       dts->temp_mask = 0x00FF << (id * 8);
+       dts->temp_shift = id * 8;
+       if (notification_support) {
+               trip_count = min(SOC_MAX_DTS_TRIPS, trip_cnt);
+               trip_mask = BIT(trip_count - read_only_trip_cnt) - 1;
+       }
+
+       /* Check if the writable trip we provide is not used by BIOS */
+       ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                           SOC_DTS_OFFSET_PTPS, &store_ptps);
+       if (ret)
+               trip_mask = 0;
+       else {
+               for (i = 0; i < trip_count; ++i) {
+                       if (trip_mask & BIT(i))
+                               if (store_ptps & (0xff << (i * 8)))
+                                       trip_mask &= ~BIT(i);
+               }
+       }
+       dts->trip_mask = trip_mask;
+       dts->trip_count = trip_count;
+       snprintf(name, sizeof(name), "soc_dts%d", id);
+       dts->tzone = thermal_zone_device_register(name,
+                                                 trip_count,
+                                                 trip_mask,
+                                                 dts, &tzone_ops,
+                                                 NULL, 0, 0);
+       if (IS_ERR(dts->tzone)) {
+               ret = PTR_ERR(dts->tzone);
+               goto err_ret;
+       }
+
+       ret = soc_dts_enable(id);
+       if (ret)
+               goto err_enable;
+
+       return 0;
+err_enable:
+       thermal_zone_device_unregister(dts->tzone);
+err_ret:
+       return ret;
+}
+
+int intel_soc_dts_iosf_add_read_only_critical_trip(
+       struct intel_soc_dts_sensors *sensors, int critical_offset)
+{
+       int i, j;
+
+       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+               for (j = 0; j < sensors->soc_dts[i].trip_count; ++j) {
+                       if (!(sensors->soc_dts[i].trip_mask & BIT(j))) {
+                               return update_trip_temp(&sensors->soc_dts[i], j,
+                                       sensors->tj_max - critical_offset,
+                                       THERMAL_TRIP_CRITICAL);
+                       }
+               }
+       }
+
+       return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_add_read_only_critical_trip);
+
+void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors)
+{
+       u32 sticky_out;
+       int status;
+       u32 ptmc_out;
+       unsigned long flags;
+
+       spin_lock_irqsave(&sensors->intr_notify_lock, flags);
+
+       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                              SOC_DTS_OFFSET_PTMC, &ptmc_out);
+       ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT;
+       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                               SOC_DTS_OFFSET_PTMC, ptmc_out);
+
+       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
+                              SOC_DTS_OFFSET_PTTSS, &sticky_out);
+       pr_debug("status %d PTTSS %x\n", status, sticky_out);
+       if (sticky_out & SOC_DTS_TRIP_MASK) {
+               int i;
+               /* reset sticky bit */
+               status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
+                                       SOC_DTS_OFFSET_PTTSS, sticky_out);
+               spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
+
+               for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+                       pr_debug("TZD update for zone %d\n", i);
+                       thermal_zone_device_update(sensors->soc_dts[i].tzone);
+               }
+       } else
+               spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_interrupt_handler);
+
+struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
+       enum intel_soc_dts_interrupt_type intr_type, int trip_count,
+       int read_only_trip_count)
+{
+       struct intel_soc_dts_sensors *sensors;
+       bool notification;
+       u32 tj_max;
+       int ret;
+       int i;
+
+       if (!iosf_mbi_available())
+               return ERR_PTR(-ENODEV);
+
+       if (!trip_count || read_only_trip_count > trip_count)
+               return ERR_PTR(-EINVAL);
+
+       if (get_tj_max(&tj_max))
+               return ERR_PTR(-EINVAL);
+
+       sensors = kzalloc(sizeof(*sensors), GFP_KERNEL);
+       if (!sensors)
+               return ERR_PTR(-ENOMEM);
+
+       spin_lock_init(&sensors->intr_notify_lock);
+       mutex_init(&sensors->dts_update_lock);
+       sensors->intr_type = intr_type;
+       sensors->tj_max = tj_max;
+       if (intr_type == INTEL_SOC_DTS_INTERRUPT_NONE)
+               notification = false;
+       else
+               notification = true;
+       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+               sensors->soc_dts[i].sensors = sensors;
+               ret = add_dts_thermal_zone(i, &sensors->soc_dts[i],
+                                          notification, trip_count,
+                                          read_only_trip_count);
+               if (ret)
+                       goto err_free;
+       }
+
+       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+               ret = update_trip_temp(&sensors->soc_dts[i], 0, 0,
+                                      THERMAL_TRIP_PASSIVE);
+               if (ret)
+                       goto err_remove_zone;
+
+               ret = update_trip_temp(&sensors->soc_dts[i], 1, 0,
+                                      THERMAL_TRIP_PASSIVE);
+               if (ret)
+                       goto err_remove_zone;
+       }
+
+       return sensors;
+err_remove_zone:
+       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
+               remove_dts_thermal_zone(&sensors->soc_dts[i]);
+
+err_free:
+       kfree(sensors);
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_init);
+
+void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors)
+{
+       int i;
+
+       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
+               update_trip_temp(&sensors->soc_dts[i], 0, 0, 0);
+               update_trip_temp(&sensors->soc_dts[i], 1, 0, 0);
+               remove_dts_thermal_zone(&sensors->soc_dts[i]);
+       }
+       kfree(sensors);
+}
+EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/intel_soc_dts_iosf.h b/drivers/thermal/intel_soc_dts_iosf.h
new file mode 100644 (file)
index 0000000..625e37b
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * intel_soc_dts_iosf.h
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#ifndef _INTEL_SOC_DTS_IOSF_CORE_H
+#define _INTEL_SOC_DTS_IOSF_CORE_H
+
+#include <linux/thermal.h>
+
+/* DTS0 and DTS 1 */
+#define SOC_MAX_DTS_SENSORS    2
+
+enum intel_soc_dts_interrupt_type {
+       INTEL_SOC_DTS_INTERRUPT_NONE,
+       INTEL_SOC_DTS_INTERRUPT_APIC,
+       INTEL_SOC_DTS_INTERRUPT_MSI,
+       INTEL_SOC_DTS_INTERRUPT_SCI,
+       INTEL_SOC_DTS_INTERRUPT_SMI,
+};
+
+struct intel_soc_dts_sensors;
+
+struct intel_soc_dts_sensor_entry {
+       int id;
+       u32 temp_mask;
+       u32 temp_shift;
+       u32 store_status;
+       u32 trip_mask;
+       u32 trip_count;
+       enum thermal_trip_type trip_types[2];
+       struct thermal_zone_device *tzone;
+       struct intel_soc_dts_sensors *sensors;
+};
+
+struct intel_soc_dts_sensors {
+       u32 tj_max;
+       spinlock_t intr_notify_lock;
+       struct mutex dts_update_lock;
+       enum intel_soc_dts_interrupt_type intr_type;
+       struct intel_soc_dts_sensor_entry soc_dts[SOC_MAX_DTS_SENSORS];
+};
+
+struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
+       enum intel_soc_dts_interrupt_type intr_type, int trip_count,
+       int read_only_trip_count);
+void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors);
+void intel_soc_dts_iosf_interrupt_handler(
+                               struct intel_soc_dts_sensors *sensors);
+int intel_soc_dts_iosf_add_read_only_critical_trip(
+       struct intel_soc_dts_sensors *sensors, int critical_offset);
+#endif
index 9013505e43b7358bb98c2b5f4e7b134ae0ce143d..4ebb31a35a64303bc31a5f429d8e19d924cbde28 100644 (file)
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/module.h>
-#include <linux/slab.h>
 #include <linux/interrupt.h>
-#include <linux/thermal.h>
 #include <asm/cpu_device_id.h>
-#include <asm/iosf_mbi.h>
-
-#define SOC_DTS_OFFSET_ENABLE  0xB0
-#define SOC_DTS_OFFSET_TEMP    0xB1
-
-#define SOC_DTS_OFFSET_PTPS    0xB2
-#define SOC_DTS_OFFSET_PTTS    0xB3
-#define SOC_DTS_OFFSET_PTTSS   0xB4
-#define SOC_DTS_OFFSET_PTMC    0x80
-#define SOC_DTS_TE_AUX0                0xB5
-#define SOC_DTS_TE_AUX1                0xB6
-
-#define SOC_DTS_AUX0_ENABLE_BIT                BIT(0)
-#define SOC_DTS_AUX1_ENABLE_BIT                BIT(1)
-#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16)
-#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17)
-#define SOC_DTS_TE_SCI_ENABLE          BIT(9)
-#define SOC_DTS_TE_SMI_ENABLE          BIT(10)
-#define SOC_DTS_TE_MSI_ENABLE          BIT(11)
-#define SOC_DTS_TE_APICA_ENABLE                BIT(14)
-#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4)
-
-/* DTS encoding for TJ MAX temperature */
-#define SOC_DTS_TJMAX_ENCODING 0x7F
-
-/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */
-#define BYT_SOC_DTS_APIC_IRQ   86
-
-/* Only 2 out of 4 is allowed for OSPM */
-#define SOC_MAX_DTS_TRIPS      2
-
-/* Mask for two trips in status bits */
-#define SOC_DTS_TRIP_MASK      0x03
-
-/* DTS0 and DTS 1 */
-#define SOC_MAX_DTS_SENSORS    2
+#include "intel_soc_dts_iosf.h"
 
 #define CRITICAL_OFFSET_FROM_TJ_MAX    5000
 
-struct soc_sensor_entry {
-       int id;
-       u32 tj_max;
-       u32 temp_mask;
-       u32 temp_shift;
-       u32 store_status;
-       struct thermal_zone_device *tzone;
-};
-
-static struct soc_sensor_entry *soc_dts[SOC_MAX_DTS_SENSORS];
-
 static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX;
 module_param(crit_offset, int, 0644);
 MODULE_PARM_DESC(crit_offset,
        "Critical Temperature offset from tj max in millidegree Celsius.");
 
-static DEFINE_MUTEX(aux_update_mutex);
-static spinlock_t intr_notify_lock;
-static int soc_dts_thres_irq;
-
-static int get_tj_max(u32 *tj_max)
-{
-       u32 eax, edx;
-       u32 val;
-       int err;
-
-       err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
-       if (err)
-               goto err_ret;
-       else {
-               val = (eax >> 16) & 0xff;
-               if (val)
-                       *tj_max = val * 1000;
-               else {
-                       err = -EINVAL;
-                       goto err_ret;
-               }
-       }
-
-       return 0;
-err_ret:
-       *tj_max = 0;
-
-       return err;
-}
-
-static int sys_get_trip_temp(struct thermal_zone_device *tzd,
-                                       int trip, unsigned long *temp)
-{
-       int status;
-       u32 out;
-       struct soc_sensor_entry *aux_entry;
-
-       aux_entry = tzd->devdata;
-
-       if (!trip) {
-               /* Just return the critical temp */
-               *temp = aux_entry->tj_max - crit_offset;
-               return 0;
-       }
-
-       mutex_lock(&aux_update_mutex);
-       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                                       SOC_DTS_OFFSET_PTPS, &out);
-       mutex_unlock(&aux_update_mutex);
-       if (status)
-               return status;
-
-       out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING;
-
-       if (!out)
-               *temp = 0;
-       else
-               *temp = aux_entry->tj_max - out * 1000;
-
-       return 0;
-}
-
-static int update_trip_temp(struct soc_sensor_entry *aux_entry,
-                               int thres_index, unsigned long temp)
-{
-       int status;
-       u32 temp_out;
-       u32 out;
-       u32 store_ptps;
-       u32 store_ptmc;
-       u32 store_te_out;
-       u32 te_out;
-
-       u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE |
-                                               SOC_DTS_TE_MSI_ENABLE;
-
-       temp_out = (aux_entry->tj_max - temp) / 1000;
-
-       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                               SOC_DTS_OFFSET_PTPS, &store_ptps);
-       if (status)
-               return status;
-
-       out = (store_ptps & ~(0xFF << (thres_index * 8)));
-       out |= (temp_out & 0xFF) << (thres_index * 8);
-       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                               SOC_DTS_OFFSET_PTPS, out);
-       if (status)
-               return status;
-       pr_debug("update_trip_temp PTPS = %x\n", out);
-       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                                       SOC_DTS_OFFSET_PTMC, &out);
-       if (status)
-               goto err_restore_ptps;
-
-       store_ptmc = out;
-
-       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                                       SOC_DTS_TE_AUX0 + thres_index,
-                                       &te_out);
-       if (status)
-               goto err_restore_ptmc;
-
-       store_te_out = te_out;
-
-       /* Enable for CPU module 0 and module 1 */
-       out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT |
-                                       SOC_DTS_CPU_MODULE1_ENABLE_BIT);
-       if (temp) {
-               if (thres_index)
-                       out |= SOC_DTS_AUX1_ENABLE_BIT;
-               else
-                       out |= SOC_DTS_AUX0_ENABLE_BIT;
-               te_out |= int_enable_bit;
-       } else {
-               if (thres_index)
-                       out &= ~SOC_DTS_AUX1_ENABLE_BIT;
-               else
-                       out &= ~SOC_DTS_AUX0_ENABLE_BIT;
-               te_out &= ~int_enable_bit;
-       }
-       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                                       SOC_DTS_OFFSET_PTMC, out);
-       if (status)
-               goto err_restore_te_out;
-
-       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                                       SOC_DTS_TE_AUX0 + thres_index,
-                                       te_out);
-       if (status)
-               goto err_restore_te_out;
-
-       return 0;
-
-err_restore_te_out:
-       iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                               SOC_DTS_OFFSET_PTMC, store_te_out);
-err_restore_ptmc:
-       iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                               SOC_DTS_OFFSET_PTMC, store_ptmc);
-err_restore_ptps:
-       iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                               SOC_DTS_OFFSET_PTPS, store_ptps);
-       /* Nothing we can do if restore fails */
-
-       return status;
-}
-
-static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
-                                                       unsigned long temp)
-{
-       struct soc_sensor_entry *aux_entry = tzd->devdata;
-       int status;
-
-       if (temp > (aux_entry->tj_max - crit_offset))
-               return -EINVAL;
-
-       mutex_lock(&aux_update_mutex);
-       status = update_trip_temp(tzd->devdata, trip, temp);
-       mutex_unlock(&aux_update_mutex);
-
-       return status;
-}
-
-static int sys_get_trip_type(struct thermal_zone_device *thermal,
-               int trip, enum thermal_trip_type *type)
-{
-       if (trip)
-               *type = THERMAL_TRIP_PASSIVE;
-       else
-               *type = THERMAL_TRIP_CRITICAL;
-
-       return 0;
-}
-
-static int sys_get_curr_temp(struct thermal_zone_device *tzd,
-                                               unsigned long *temp)
-{
-       int status;
-       u32 out;
-       struct soc_sensor_entry *aux_entry;
-
-       aux_entry = tzd->devdata;
-
-       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                                       SOC_DTS_OFFSET_TEMP, &out);
-       if (status)
-               return status;
-
-       out = (out & aux_entry->temp_mask) >> aux_entry->temp_shift;
-       out -= SOC_DTS_TJMAX_ENCODING;
-       *temp = aux_entry->tj_max - out * 1000;
-
-       return 0;
-}
-
-static struct thermal_zone_device_ops tzone_ops = {
-       .get_temp = sys_get_curr_temp,
-       .get_trip_temp = sys_get_trip_temp,
-       .get_trip_type = sys_get_trip_type,
-       .set_trip_temp = sys_set_trip_temp,
-};
-
-static void free_soc_dts(struct soc_sensor_entry *aux_entry)
-{
-       if (aux_entry) {
-               iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                       SOC_DTS_OFFSET_ENABLE, aux_entry->store_status);
-               thermal_zone_device_unregister(aux_entry->tzone);
-               kfree(aux_entry);
-       }
-}
-
-static int soc_dts_enable(int id)
-{
-       u32 out;
-       int ret;
-
-       ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                                       SOC_DTS_OFFSET_ENABLE, &out);
-       if (ret)
-               return ret;
-
-       if (!(out & BIT(id))) {
-               out |= BIT(id);
-               ret = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                                       SOC_DTS_OFFSET_ENABLE, out);
-               if (ret)
-                       return ret;
-       }
-
-       return ret;
-}
-
-static struct soc_sensor_entry *alloc_soc_dts(int id, u32 tj_max,
-                                             bool notification_support)
-{
-       struct soc_sensor_entry *aux_entry;
-       char name[10];
-       int trip_count = 0;
-       int trip_mask = 0;
-       int err;
-
-       aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL);
-       if (!aux_entry) {
-               err = -ENOMEM;
-               return ERR_PTR(-ENOMEM);
-       }
-
-       /* Store status to restor on exit */
-       err = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                                       SOC_DTS_OFFSET_ENABLE,
-                                       &aux_entry->store_status);
-       if (err)
-               goto err_ret;
-
-       aux_entry->id = id;
-       aux_entry->tj_max = tj_max;
-       aux_entry->temp_mask = 0x00FF << (id * 8);
-       aux_entry->temp_shift = id * 8;
-       if (notification_support) {
-               trip_count = SOC_MAX_DTS_TRIPS;
-               trip_mask = 0x02;
-       }
-       snprintf(name, sizeof(name), "soc_dts%d", id);
-       aux_entry->tzone = thermal_zone_device_register(name,
-                                                       trip_count,
-                                                       trip_mask,
-                                                       aux_entry, &tzone_ops,
-                                                       NULL, 0, 0);
-       if (IS_ERR(aux_entry->tzone)) {
-               err = PTR_ERR(aux_entry->tzone);
-               goto err_ret;
-       }
-
-       err = soc_dts_enable(id);
-       if (err)
-               goto err_aux_status;
-
-       return aux_entry;
-
-err_aux_status:
-       thermal_zone_device_unregister(aux_entry->tzone);
-err_ret:
-       kfree(aux_entry);
-       return ERR_PTR(err);
-}
-
-static void proc_thermal_interrupt(void)
-{
-       u32 sticky_out;
-       int status;
-       u32 ptmc_out;
-       unsigned long flags;
-
-       spin_lock_irqsave(&intr_notify_lock, flags);
-
-       /* Clear APIC interrupt */
-       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                               SOC_DTS_OFFSET_PTMC, &ptmc_out);
-
-       ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT;
-       status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                                       SOC_DTS_OFFSET_PTMC, ptmc_out);
-
-       /* Read status here */
-       status = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ,
-                                       SOC_DTS_OFFSET_PTTSS, &sticky_out);
-       pr_debug("status %d PTTSS %x\n", status, sticky_out);
-       if (sticky_out & SOC_DTS_TRIP_MASK) {
-               int i;
-               /* reset sticky bit */
-               status = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE,
-                                       SOC_DTS_OFFSET_PTTSS, sticky_out);
-               spin_unlock_irqrestore(&intr_notify_lock, flags);
-
-               for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
-                       pr_debug("TZD update for zone %d\n", i);
-                       thermal_zone_device_update(soc_dts[i]->tzone);
-               }
-       } else
-               spin_unlock_irqrestore(&intr_notify_lock, flags);
+/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */
+#define BYT_SOC_DTS_APIC_IRQ   86
 
-}
+static int soc_dts_thres_irq;
+static struct intel_soc_dts_sensors *soc_dts;
 
 static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data)
 {
-       proc_thermal_interrupt();
        pr_debug("proc_thermal_interrupt\n");
+       intel_soc_dts_iosf_interrupt_handler(soc_dts);
 
        return IRQ_HANDLED;
 }
 
 static const struct x86_cpu_id soc_thermal_ids[] = {
        { X86_VENDOR_INTEL, X86_FAMILY_ANY, 0x37, 0, BYT_SOC_DTS_APIC_IRQ},
-       { X86_VENDOR_INTEL, X86_FAMILY_ANY, 0x4c, 0, 0},
        {}
 };
 MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids);
 
 static int __init intel_soc_thermal_init(void)
 {
-       u32 tj_max;
        int err = 0;
-       int i;
        const struct x86_cpu_id *match_cpu;
 
        match_cpu = x86_match_cpu(soc_thermal_ids);
        if (!match_cpu)
                return -ENODEV;
 
-       if (get_tj_max(&tj_max))
-               return -EINVAL;
-
-       soc_dts_thres_irq = (int)match_cpu->driver_data;
-
-       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
-               soc_dts[i] = alloc_soc_dts(i, tj_max,
-                                          soc_dts_thres_irq ? true : false);
-               if (IS_ERR(soc_dts[i])) {
-                       err = PTR_ERR(soc_dts[i]);
-                       goto err_free;
-               }
+       /* Create a zone with 2 trips with marked as read only */
+       soc_dts = intel_soc_dts_iosf_init(INTEL_SOC_DTS_INTERRUPT_APIC, 2, 1);
+       if (IS_ERR(soc_dts)) {
+               err = PTR_ERR(soc_dts);
+               return err;
        }
 
-       spin_lock_init(&intr_notify_lock);
+       soc_dts_thres_irq = (int)match_cpu->driver_data;
 
        if (soc_dts_thres_irq) {
                err = request_threaded_irq(soc_dts_thres_irq, NULL,
@@ -449,42 +72,31 @@ static int __init intel_soc_thermal_init(void)
                                           "soc_dts", soc_dts);
                if (err) {
                        pr_err("request_threaded_irq ret %d\n", err);
-                       goto err_free;
+                       goto error_irq;
                }
        }
 
-       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
-               err = update_trip_temp(soc_dts[i], 0, tj_max - crit_offset);
-               if (err)
-                       goto err_trip_temp;
-       }
+       err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts,
+                                                            crit_offset);
+       if (err)
+               goto error_trips;
 
        return 0;
 
-err_trip_temp:
-       i = SOC_MAX_DTS_SENSORS;
+error_trips:
        if (soc_dts_thres_irq)
                free_irq(soc_dts_thres_irq, soc_dts);
-err_free:
-       while (--i >= 0)
-               free_soc_dts(soc_dts[i]);
+error_irq:
+       intel_soc_dts_iosf_exit(soc_dts);
 
        return err;
 }
 
 static void __exit intel_soc_thermal_exit(void)
 {
-       int i;
-
-       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
-               update_trip_temp(soc_dts[i], 0, 0);
-
        if (soc_dts_thres_irq)
                free_irq(soc_dts_thres_irq, soc_dts);
-
-       for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
-               free_soc_dts(soc_dts[i]);
-
+       intel_soc_dts_iosf_exit(soc_dts);
 }
 
 module_init(intel_soc_thermal_init)
index 668fb1bdea9eff9b0443453430f1de92335c4104..b295b2b6c191ea056f5d4f42dd7974ae31a9e1f6 100644 (file)
@@ -58,6 +58,8 @@ struct __thermal_bind_params {
  * @mode: current thermal zone device mode (enabled/disabled)
  * @passive_delay: polling interval while passive cooling is activated
  * @polling_delay: zone polling interval
+ * @slope: slope of the temperature adjustment curve
+ * @offset: offset of the temperature adjustment curve
  * @ntrips: number of trip points
  * @trips: an array of trip points (0..ntrips - 1)
  * @num_tbps: number of thermal bind params
@@ -70,6 +72,8 @@ struct __thermal_zone {
        enum thermal_device_mode mode;
        int passive_delay;
        int polling_delay;
+       int slope;
+       int offset;
 
        /* trip data */
        int ntrips;
@@ -227,7 +231,8 @@ static int of_thermal_bind(struct thermal_zone_device *thermal,
                        ret = thermal_zone_bind_cooling_device(thermal,
                                                tbp->trip_id, cdev,
                                                tbp->max,
-                                               tbp->min);
+                                               tbp->min,
+                                               tbp->usage);
                        if (ret)
                                return ret;
                }
@@ -581,7 +586,7 @@ static int thermal_of_populate_bind_params(struct device_node *np,
        u32 prop;
 
        /* Default weight. Usage is optional */
-       __tbp->usage = 0;
+       __tbp->usage = THERMAL_WEIGHT_DEFAULT;
        ret = of_property_read_u32(np, "contribution", &prop);
        if (ret == 0)
                __tbp->usage = prop;
@@ -715,7 +720,7 @@ static int thermal_of_populate_trip(struct device_node *np,
  * @np parameter and fills the read data into a __thermal_zone data structure
  * and return this pointer.
  *
- * TODO: Missing properties to parse: thermal-sensor-names and coefficients
+ * TODO: Missing properties to parse: thermal-sensor-names
  *
  * Return: On success returns a valid struct __thermal_zone,
  * otherwise, it returns a corresponding ERR_PTR(). Caller must
@@ -727,7 +732,7 @@ thermal_of_build_thermal_zone(struct device_node *np)
        struct device_node *child = NULL, *gchild;
        struct __thermal_zone *tz;
        int ret, i;
-       u32 prop;
+       u32 prop, coef[2];
 
        if (!np) {
                pr_err("no thermal zone np\n");
@@ -752,6 +757,20 @@ thermal_of_build_thermal_zone(struct device_node *np)
        }
        tz->polling_delay = prop;
 
+       /*
+        * REVIST: for now, the thermal framework supports only
+        * one sensor per thermal zone. Thus, we are considering
+        * only the first two values as slope and offset.
+        */
+       ret = of_property_read_u32_array(np, "coefficients", coef, 2);
+       if (ret == 0) {
+               tz->slope = coef[0];
+               tz->offset = coef[1];
+       } else {
+               tz->slope = 1;
+               tz->offset = 0;
+       }
+
        /* trips */
        child = of_get_child_by_name(np, "trips");
 
@@ -865,6 +884,8 @@ int __init of_parse_thermal_zones(void)
        for_each_child_of_node(np, child) {
                struct thermal_zone_device *zone;
                struct thermal_zone_params *tzp;
+               int i, mask = 0;
+               u32 prop;
 
                /* Check whether child is enabled or not */
                if (!of_device_is_available(child))
@@ -891,8 +912,18 @@ int __init of_parse_thermal_zones(void)
                /* No hwmon because there might be hwmon drivers registering */
                tzp->no_hwmon = true;
 
+               if (!of_property_read_u32(child, "sustainable-power", &prop))
+                       tzp->sustainable_power = prop;
+
+               for (i = 0; i < tz->ntrips; i++)
+                       mask |= 1 << i;
+
+               /* these two are left for temperature drivers to use */
+               tzp->slope = tz->slope;
+               tzp->offset = tz->offset;
+
                zone = thermal_zone_device_register(child->name, tz->ntrips,
-                                                   0, tz,
+                                                   mask, tz,
                                                    ops, tzp,
                                                    tz->passive_delay,
                                                    tz->polling_delay);
diff --git a/drivers/thermal/power_allocator.c b/drivers/thermal/power_allocator.c
new file mode 100644 (file)
index 0000000..4672250
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+ * A power allocator to manage temperature
+ *
+ * Copyright (C) 2014 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "Power allocator: " fmt
+
+#include <linux/rculist.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/thermal_power_allocator.h>
+
+#include "thermal_core.h"
+
+#define FRAC_BITS 10
+#define int_to_frac(x) ((x) << FRAC_BITS)
+#define frac_to_int(x) ((x) >> FRAC_BITS)
+
+/**
+ * mul_frac() - multiply two fixed-point numbers
+ * @x: first multiplicand
+ * @y: second multiplicand
+ *
+ * Return: the result of multiplying two fixed-point numbers.  The
+ * result is also a fixed-point number.
+ */
+static inline s64 mul_frac(s64 x, s64 y)
+{
+       return (x * y) >> FRAC_BITS;
+}
+
+/**
+ * div_frac() - divide two fixed-point numbers
+ * @x: the dividend
+ * @y: the divisor
+ *
+ * Return: the result of dividing two fixed-point numbers.  The
+ * result is also a fixed-point number.
+ */
+static inline s64 div_frac(s64 x, s64 y)
+{
+       return div_s64(x << FRAC_BITS, y);
+}
+
+/**
+ * struct power_allocator_params - parameters for the power allocator governor
+ * @err_integral:      accumulated error in the PID controller.
+ * @prev_err:  error in the previous iteration of the PID controller.
+ *             Used to calculate the derivative term.
+ * @trip_switch_on:    first passive trip point of the thermal zone.  The
+ *                     governor switches on when this trip point is crossed.
+ * @trip_max_desired_temperature:      last passive trip point of the thermal
+ *                                     zone.  The temperature we are
+ *                                     controlling for.
+ */
+struct power_allocator_params {
+       s64 err_integral;
+       s32 prev_err;
+       int trip_switch_on;
+       int trip_max_desired_temperature;
+};
+
+/**
+ * pid_controller() - PID controller
+ * @tz:        thermal zone we are operating in
+ * @current_temp:      the current temperature in millicelsius
+ * @control_temp:      the target temperature in millicelsius
+ * @max_allocatable_power:     maximum allocatable power for this thermal zone
+ *
+ * This PID controller increases the available power budget so that the
+ * temperature of the thermal zone gets as close as possible to
+ * @control_temp and limits the power if it exceeds it.  k_po is the
+ * proportional term when we are overshooting, k_pu is the
+ * proportional term when we are undershooting.  integral_cutoff is a
+ * threshold below which we stop accumulating the error.  The
+ * accumulated error is only valid if the requested power will make
+ * the system warmer.  If the system is mostly idle, there's no point
+ * in accumulating positive error.
+ *
+ * Return: The power budget for the next period.
+ */
+static u32 pid_controller(struct thermal_zone_device *tz,
+                         unsigned long current_temp,
+                         unsigned long control_temp,
+                         u32 max_allocatable_power)
+{
+       s64 p, i, d, power_range;
+       s32 err, max_power_frac;
+       struct power_allocator_params *params = tz->governor_data;
+
+       max_power_frac = int_to_frac(max_allocatable_power);
+
+       err = ((s32)control_temp - (s32)current_temp);
+       err = int_to_frac(err);
+
+       /* Calculate the proportional term */
+       p = mul_frac(err < 0 ? tz->tzp->k_po : tz->tzp->k_pu, err);
+
+       /*
+        * Calculate the integral term
+        *
+        * if the error is less than cut off allow integration (but
+        * the integral is limited to max power)
+        */
+       i = mul_frac(tz->tzp->k_i, params->err_integral);
+
+       if (err < int_to_frac(tz->tzp->integral_cutoff)) {
+               s64 i_next = i + mul_frac(tz->tzp->k_i, err);
+
+               if (abs64(i_next) < max_power_frac) {
+                       i = i_next;
+                       params->err_integral += err;
+               }
+       }
+
+       /*
+        * Calculate the derivative term
+        *
+        * We do err - prev_err, so with a positive k_d, a decreasing
+        * error (i.e. driving closer to the line) results in less
+        * power being applied, slowing down the controller)
+        */
+       d = mul_frac(tz->tzp->k_d, err - params->prev_err);
+       d = div_frac(d, tz->passive_delay);
+       params->prev_err = err;
+
+       power_range = p + i + d;
+
+       /* feed-forward the known sustainable dissipatable power */
+       power_range = tz->tzp->sustainable_power + frac_to_int(power_range);
+
+       power_range = clamp(power_range, (s64)0, (s64)max_allocatable_power);
+
+       trace_thermal_power_allocator_pid(tz, frac_to_int(err),
+                                         frac_to_int(params->err_integral),
+                                         frac_to_int(p), frac_to_int(i),
+                                         frac_to_int(d), power_range);
+
+       return power_range;
+}
+
+/**
+ * divvy_up_power() - divvy the allocated power between the actors
+ * @req_power: each actor's requested power
+ * @max_power: each actor's maximum available power
+ * @num_actors:        size of the @req_power, @max_power and @granted_power's array
+ * @total_req_power: sum of @req_power
+ * @power_range:       total allocated power
+ * @granted_power:     output array: each actor's granted power
+ * @extra_actor_power: an appropriately sized array to be used in the
+ *                     function as temporary storage of the extra power given
+ *                     to the actors
+ *
+ * This function divides the total allocated power (@power_range)
+ * fairly between the actors.  It first tries to give each actor a
+ * share of the @power_range according to how much power it requested
+ * compared to the rest of the actors.  For example, if only one actor
+ * requests power, then it receives all the @power_range.  If
+ * three actors each requests 1mW, each receives a third of the
+ * @power_range.
+ *
+ * If any actor received more than their maximum power, then that
+ * surplus is re-divvied among the actors based on how far they are
+ * from their respective maximums.
+ *
+ * Granted power for each actor is written to @granted_power, which
+ * should've been allocated by the calling function.
+ */
+static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors,
+                          u32 total_req_power, u32 power_range,
+                          u32 *granted_power, u32 *extra_actor_power)
+{
+       u32 extra_power, capped_extra_power;
+       int i;
+
+       /*
+        * Prevent division by 0 if none of the actors request power.
+        */
+       if (!total_req_power)
+               total_req_power = 1;
+
+       capped_extra_power = 0;
+       extra_power = 0;
+       for (i = 0; i < num_actors; i++) {
+               u64 req_range = req_power[i] * power_range;
+
+               granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range,
+                                                        total_req_power);
+
+               if (granted_power[i] > max_power[i]) {
+                       extra_power += granted_power[i] - max_power[i];
+                       granted_power[i] = max_power[i];
+               }
+
+               extra_actor_power[i] = max_power[i] - granted_power[i];
+               capped_extra_power += extra_actor_power[i];
+       }
+
+       if (!extra_power)
+               return;
+
+       /*
+        * Re-divvy the reclaimed extra among actors based on
+        * how far they are from the max
+        */
+       extra_power = min(extra_power, capped_extra_power);
+       if (capped_extra_power > 0)
+               for (i = 0; i < num_actors; i++)
+                       granted_power[i] += (extra_actor_power[i] *
+                                       extra_power) / capped_extra_power;
+}
+
+static int allocate_power(struct thermal_zone_device *tz,
+                         unsigned long current_temp,
+                         unsigned long control_temp)
+{
+       struct thermal_instance *instance;
+       struct power_allocator_params *params = tz->governor_data;
+       u32 *req_power, *max_power, *granted_power, *extra_actor_power;
+       u32 total_req_power, max_allocatable_power;
+       u32 total_granted_power, power_range;
+       int i, num_actors, total_weight, ret = 0;
+       int trip_max_desired_temperature = params->trip_max_desired_temperature;
+
+       mutex_lock(&tz->lock);
+
+       num_actors = 0;
+       total_weight = 0;
+       list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+               if ((instance->trip == trip_max_desired_temperature) &&
+                   cdev_is_power_actor(instance->cdev)) {
+                       num_actors++;
+                       total_weight += instance->weight;
+               }
+       }
+
+       /*
+        * We need to allocate three arrays of the same size:
+        * req_power, max_power and granted_power.  They are going to
+        * be needed until this function returns.  Allocate them all
+        * in one go to simplify the allocation and deallocation
+        * logic.
+        */
+       BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power));
+       BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power));
+       BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power));
+       req_power = devm_kcalloc(&tz->device, num_actors * 4,
+                                sizeof(*req_power), GFP_KERNEL);
+       if (!req_power) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       max_power = &req_power[num_actors];
+       granted_power = &req_power[2 * num_actors];
+       extra_actor_power = &req_power[3 * num_actors];
+
+       i = 0;
+       total_req_power = 0;
+       max_allocatable_power = 0;
+
+       list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+               int weight;
+               struct thermal_cooling_device *cdev = instance->cdev;
+
+               if (instance->trip != trip_max_desired_temperature)
+                       continue;
+
+               if (!cdev_is_power_actor(cdev))
+                       continue;
+
+               if (cdev->ops->get_requested_power(cdev, tz, &req_power[i]))
+                       continue;
+
+               if (!total_weight)
+                       weight = 1 << FRAC_BITS;
+               else
+                       weight = instance->weight;
+
+               req_power[i] = frac_to_int(weight * req_power[i]);
+
+               if (power_actor_get_max_power(cdev, tz, &max_power[i]))
+                       continue;
+
+               total_req_power += req_power[i];
+               max_allocatable_power += max_power[i];
+
+               i++;
+       }
+
+       power_range = pid_controller(tz, current_temp, control_temp,
+                                    max_allocatable_power);
+
+       divvy_up_power(req_power, max_power, num_actors, total_req_power,
+                      power_range, granted_power, extra_actor_power);
+
+       total_granted_power = 0;
+       i = 0;
+       list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+               if (instance->trip != trip_max_desired_temperature)
+                       continue;
+
+               if (!cdev_is_power_actor(instance->cdev))
+                       continue;
+
+               power_actor_set_power(instance->cdev, instance,
+                                     granted_power[i]);
+               total_granted_power += granted_power[i];
+
+               i++;
+       }
+
+       trace_thermal_power_allocator(tz, req_power, total_req_power,
+                                     granted_power, total_granted_power,
+                                     num_actors, power_range,
+                                     max_allocatable_power, current_temp,
+                                     (s32)control_temp - (s32)current_temp);
+
+       devm_kfree(&tz->device, req_power);
+unlock:
+       mutex_unlock(&tz->lock);
+
+       return ret;
+}
+
+static int get_governor_trips(struct thermal_zone_device *tz,
+                             struct power_allocator_params *params)
+{
+       int i, ret, last_passive;
+       bool found_first_passive;
+
+       found_first_passive = false;
+       last_passive = -1;
+       ret = -EINVAL;
+
+       for (i = 0; i < tz->trips; i++) {
+               enum thermal_trip_type type;
+
+               ret = tz->ops->get_trip_type(tz, i, &type);
+               if (ret)
+                       return ret;
+
+               if (!found_first_passive) {
+                       if (type == THERMAL_TRIP_PASSIVE) {
+                               params->trip_switch_on = i;
+                               found_first_passive = true;
+                       }
+               } else if (type == THERMAL_TRIP_PASSIVE) {
+                       last_passive = i;
+               } else {
+                       break;
+               }
+       }
+
+       if (last_passive != -1) {
+               params->trip_max_desired_temperature = last_passive;
+               ret = 0;
+       } else {
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+static void reset_pid_controller(struct power_allocator_params *params)
+{
+       params->err_integral = 0;
+       params->prev_err = 0;
+}
+
+static void allow_maximum_power(struct thermal_zone_device *tz)
+{
+       struct thermal_instance *instance;
+       struct power_allocator_params *params = tz->governor_data;
+
+       list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
+               if ((instance->trip != params->trip_max_desired_temperature) ||
+                   (!cdev_is_power_actor(instance->cdev)))
+                       continue;
+
+               instance->target = 0;
+               instance->cdev->updated = false;
+               thermal_cdev_update(instance->cdev);
+       }
+}
+
+/**
+ * power_allocator_bind() - bind the power_allocator governor to a thermal zone
+ * @tz:        thermal zone to bind it to
+ *
+ * Check that the thermal zone is valid for this governor, that is, it
+ * has two thermal trips.  If so, initialize the PID controller
+ * parameters and bind it to the thermal zone.
+ *
+ * Return: 0 on success, -EINVAL if the trips were invalid or -ENOMEM
+ * if we ran out of memory.
+ */
+static int power_allocator_bind(struct thermal_zone_device *tz)
+{
+       int ret;
+       struct power_allocator_params *params;
+       unsigned long switch_on_temp, control_temp;
+       u32 temperature_threshold;
+
+       if (!tz->tzp || !tz->tzp->sustainable_power) {
+               dev_err(&tz->device,
+                       "power_allocator: missing sustainable_power\n");
+               return -EINVAL;
+       }
+
+       params = devm_kzalloc(&tz->device, sizeof(*params), GFP_KERNEL);
+       if (!params)
+               return -ENOMEM;
+
+       ret = get_governor_trips(tz, params);
+       if (ret) {
+               dev_err(&tz->device,
+                       "thermal zone %s has wrong trip setup for power allocator\n",
+                       tz->type);
+               goto free;
+       }
+
+       ret = tz->ops->get_trip_temp(tz, params->trip_switch_on,
+                                    &switch_on_temp);
+       if (ret)
+               goto free;
+
+       ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature,
+                                    &control_temp);
+       if (ret)
+               goto free;
+
+       temperature_threshold = control_temp - switch_on_temp;
+
+       tz->tzp->k_po = tz->tzp->k_po ?:
+               int_to_frac(tz->tzp->sustainable_power) / temperature_threshold;
+       tz->tzp->k_pu = tz->tzp->k_pu ?:
+               int_to_frac(2 * tz->tzp->sustainable_power) /
+               temperature_threshold;
+       tz->tzp->k_i = tz->tzp->k_i ?: int_to_frac(10) / 1000;
+       /*
+        * The default for k_d and integral_cutoff is 0, so we can
+        * leave them as they are.
+        */
+
+       reset_pid_controller(params);
+
+       tz->governor_data = params;
+
+       return 0;
+
+free:
+       devm_kfree(&tz->device, params);
+       return ret;
+}
+
+static void power_allocator_unbind(struct thermal_zone_device *tz)
+{
+       dev_dbg(&tz->device, "Unbinding from thermal zone %d\n", tz->id);
+       devm_kfree(&tz->device, tz->governor_data);
+       tz->governor_data = NULL;
+}
+
+static int power_allocator_throttle(struct thermal_zone_device *tz, int trip)
+{
+       int ret;
+       unsigned long switch_on_temp, control_temp, current_temp;
+       struct power_allocator_params *params = tz->governor_data;
+
+       /*
+        * We get called for every trip point but we only need to do
+        * our calculations once
+        */
+       if (trip != params->trip_max_desired_temperature)
+               return 0;
+
+       ret = thermal_zone_get_temp(tz, &current_temp);
+       if (ret) {
+               dev_warn(&tz->device, "Failed to get temperature: %d\n", ret);
+               return ret;
+       }
+
+       ret = tz->ops->get_trip_temp(tz, params->trip_switch_on,
+                                    &switch_on_temp);
+       if (ret) {
+               dev_warn(&tz->device,
+                        "Failed to get switch on temperature: %d\n", ret);
+               return ret;
+       }
+
+       if (current_temp < switch_on_temp) {
+               tz->passive = 0;
+               reset_pid_controller(params);
+               allow_maximum_power(tz);
+               return 0;
+       }
+
+       tz->passive = 1;
+
+       ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature,
+                               &control_temp);
+       if (ret) {
+               dev_warn(&tz->device,
+                        "Failed to get the maximum desired temperature: %d\n",
+                        ret);
+               return ret;
+       }
+
+       return allocate_power(tz, current_temp, control_temp);
+}
+
+static struct thermal_governor thermal_gov_power_allocator = {
+       .name           = "power_allocator",
+       .bind_to_tz     = power_allocator_bind,
+       .unbind_from_tz = power_allocator_unbind,
+       .throttle       = power_allocator_throttle,
+};
+
+int thermal_gov_power_allocator_register(void)
+{
+       return thermal_register_governor(&thermal_gov_power_allocator);
+}
+
+void thermal_gov_power_allocator_unregister(void)
+{
+       thermal_unregister_governor(&thermal_gov_power_allocator);
+}
diff --git a/drivers/thermal/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom-spmi-temp-alarm.c
new file mode 100644 (file)
index 0000000..c8d27b8
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#define QPNP_TM_REG_TYPE               0x04
+#define QPNP_TM_REG_SUBTYPE            0x05
+#define QPNP_TM_REG_STATUS             0x08
+#define QPNP_TM_REG_SHUTDOWN_CTRL1     0x40
+#define QPNP_TM_REG_ALARM_CTRL         0x46
+
+#define QPNP_TM_TYPE                   0x09
+#define QPNP_TM_SUBTYPE                        0x08
+
+#define STATUS_STAGE_MASK              0x03
+
+#define SHUTDOWN_CTRL1_THRESHOLD_MASK  0x03
+
+#define ALARM_CTRL_FORCE_ENABLE                0x80
+
+/*
+ * Trip point values based on threshold control
+ * 0 = {105 C, 125 C, 145 C}
+ * 1 = {110 C, 130 C, 150 C}
+ * 2 = {115 C, 135 C, 155 C}
+ * 3 = {120 C, 140 C, 160 C}
+*/
+#define TEMP_STAGE_STEP                        20000   /* Stage step: 20.000 C */
+#define TEMP_STAGE_HYSTERESIS          2000
+
+#define TEMP_THRESH_MIN                        105000  /* Threshold Min: 105 C */
+#define TEMP_THRESH_STEP               5000    /* Threshold step: 5 C */
+
+#define THRESH_MIN                     0
+
+/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */
+#define DEFAULT_TEMP                   37000
+
+struct qpnp_tm_chip {
+       struct regmap                   *map;
+       struct thermal_zone_device      *tz_dev;
+       long                            temp;
+       unsigned int                    thresh;
+       unsigned int                    stage;
+       unsigned int                    prev_stage;
+       unsigned int                    base;
+       struct iio_channel              *adc;
+};
+
+static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data)
+{
+       unsigned int val;
+       int ret;
+
+       ret = regmap_read(chip->map, chip->base + addr, &val);
+       if (ret < 0)
+               return ret;
+
+       *data = val;
+       return 0;
+}
+
+static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data)
+{
+       return regmap_write(chip->map, chip->base + addr, data);
+}
+
+/*
+ * This function updates the internal temp value based on the
+ * current thermal stage and threshold as well as the previous stage
+ */
+static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+       unsigned int stage;
+       int ret;
+       u8 reg = 0;
+
+       ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
+       if (ret < 0)
+               return ret;
+
+       stage = reg & STATUS_STAGE_MASK;
+
+       if (stage > chip->stage) {
+               /* increasing stage, use lower bound */
+               chip->temp = (stage - 1) * TEMP_STAGE_STEP +
+                            chip->thresh * TEMP_THRESH_STEP +
+                            TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+       } else if (stage < chip->stage) {
+               /* decreasing stage, use upper bound */
+               chip->temp = stage * TEMP_STAGE_STEP +
+                            chip->thresh * TEMP_THRESH_STEP -
+                            TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+       }
+
+       chip->stage = stage;
+
+       return 0;
+}
+
+static int qpnp_tm_get_temp(void *data, long *temp)
+{
+       struct qpnp_tm_chip *chip = data;
+       int ret, mili_celsius;
+
+       if (!temp)
+               return -EINVAL;
+
+       if (IS_ERR(chip->adc)) {
+               ret = qpnp_tm_update_temp_no_adc(chip);
+               if (ret < 0)
+                       return ret;
+       } else {
+               ret = iio_read_channel_processed(chip->adc, &mili_celsius);
+               if (ret < 0)
+                       return ret;
+
+               chip->temp = mili_celsius;
+       }
+
+       *temp = chip->temp < 0 ? 0 : chip->temp;
+
+       return 0;
+}
+
+static const struct thermal_zone_of_device_ops qpnp_tm_sensor_ops = {
+       .get_temp = qpnp_tm_get_temp,
+};
+
+static irqreturn_t qpnp_tm_isr(int irq, void *data)
+{
+       struct qpnp_tm_chip *chip = data;
+
+       thermal_zone_device_update(chip->tz_dev);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * This function initializes the internal temp value based on only the
+ * current thermal stage and threshold. Setup threshold control and
+ * disable shutdown override.
+ */
+static int qpnp_tm_init(struct qpnp_tm_chip *chip)
+{
+       int ret;
+       u8 reg;
+
+       chip->thresh = THRESH_MIN;
+       chip->temp = DEFAULT_TEMP;
+
+       ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
+       if (ret < 0)
+               return ret;
+
+       chip->stage = reg & STATUS_STAGE_MASK;
+
+       if (chip->stage)
+               chip->temp = chip->thresh * TEMP_THRESH_STEP +
+                            (chip->stage - 1) * TEMP_STAGE_STEP +
+                            TEMP_THRESH_MIN;
+
+       /*
+        * Set threshold and disable software override of stage 2 and 3
+        * shutdowns.
+        */
+       reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+       ret = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg);
+       if (ret < 0)
+               return ret;
+
+       /* Enable the thermal alarm PMIC module in always-on mode. */
+       reg = ALARM_CTRL_FORCE_ENABLE;
+       ret = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, reg);
+
+       return ret;
+}
+
+static int qpnp_tm_probe(struct platform_device *pdev)
+{
+       struct qpnp_tm_chip *chip;
+       struct device_node *node;
+       u8 type, subtype;
+       u32 res[2];
+       int ret, irq;
+
+       node = pdev->dev.of_node;
+
+       chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+       if (!chip)
+               return -ENOMEM;
+
+       dev_set_drvdata(&pdev->dev, chip);
+
+       chip->map = dev_get_regmap(pdev->dev.parent, NULL);
+       if (!chip->map)
+               return -ENXIO;
+
+       ret = of_property_read_u32_array(node, "reg", res, 2);
+       if (ret < 0)
+               return ret;
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       /* ADC based measurements are optional */
+       chip->adc = iio_channel_get(&pdev->dev, "thermal");
+       if (PTR_ERR(chip->adc) == -EPROBE_DEFER)
+               return PTR_ERR(chip->adc);
+
+       chip->base = res[0];
+
+       ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "could not read type\n");
+               goto fail;
+       }
+
+       ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "could not read subtype\n");
+               goto fail;
+       }
+
+       if (type != QPNP_TM_TYPE || subtype != QPNP_TM_SUBTYPE) {
+               dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n",
+                       type, subtype);
+               ret = -ENODEV;
+               goto fail;
+       }
+
+       ret = qpnp_tm_init(chip);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "init failed\n");
+               goto fail;
+       }
+
+       ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr,
+                                       IRQF_ONESHOT, node->name, chip);
+       if (ret < 0)
+               goto fail;
+
+       chip->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, 0, chip,
+                                                       &qpnp_tm_sensor_ops);
+       if (IS_ERR(chip->tz_dev)) {
+               dev_err(&pdev->dev, "failed to register sensor\n");
+               ret = PTR_ERR(chip->tz_dev);
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       if (!IS_ERR(chip->adc))
+               iio_channel_release(chip->adc);
+
+       return ret;
+}
+
+static int qpnp_tm_remove(struct platform_device *pdev)
+{
+       struct qpnp_tm_chip *chip = dev_get_drvdata(&pdev->dev);
+
+       thermal_zone_of_sensor_unregister(&pdev->dev, chip->tz_dev);
+       if (!IS_ERR(chip->adc))
+               iio_channel_release(chip->adc);
+
+       return 0;
+}
+
+static const struct of_device_id qpnp_tm_match_table[] = {
+       { .compatible = "qcom,spmi-temp-alarm" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, qpnp_tm_match_table);
+
+static struct platform_driver qpnp_tm_driver = {
+       .driver = {
+               .name = "spmi-temp-alarm",
+               .of_match_table = qpnp_tm_match_table,
+       },
+       .probe  = qpnp_tm_probe,
+       .remove = qpnp_tm_remove,
+};
+module_platform_driver(qpnp_tm_driver);
+
+MODULE_ALIAS("platform:spmi-temp-alarm");
+MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
+MODULE_LICENSE("GPL v2");
index 1d30b0975651547bdc1464befd517eb450957fc0..531f4b179871f63da7fea6f32af19b27af18e9a3 100644 (file)
 #define EXYNOS4412_MUX_ADDR_VALUE          6
 #define EXYNOS4412_MUX_ADDR_SHIFT          20
 
+/* Exynos5433 specific registers */
+#define EXYNOS5433_TMU_REG_CONTROL1            0x024
+#define EXYNOS5433_TMU_SAMPLING_INTERVAL       0x02c
+#define EXYNOS5433_TMU_COUNTER_VALUE0          0x030
+#define EXYNOS5433_TMU_COUNTER_VALUE1          0x034
+#define EXYNOS5433_TMU_REG_CURRENT_TEMP1       0x044
+#define EXYNOS5433_THD_TEMP_RISE3_0            0x050
+#define EXYNOS5433_THD_TEMP_RISE7_4            0x054
+#define EXYNOS5433_THD_TEMP_FALL3_0            0x060
+#define EXYNOS5433_THD_TEMP_FALL7_4            0x064
+#define EXYNOS5433_TMU_REG_INTEN               0x0c0
+#define EXYNOS5433_TMU_REG_INTPEND             0x0c8
+#define EXYNOS5433_TMU_EMUL_CON                        0x110
+#define EXYNOS5433_TMU_PD_DET_EN               0x130
+
+#define EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT    16
+#define EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT    23
+#define EXYNOS5433_TRIMINFO_SENSOR_ID_MASK     \
+                       (0xf << EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT)
+#define EXYNOS5433_TRIMINFO_CALIB_SEL_MASK     BIT(23)
+
+#define EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING 0
+#define EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING 1
+
+#define EXYNOS5433_PD_DET_EN                   1
+
 /*exynos5440 specific registers*/
 #define EXYNOS5440_TMU_S0_7_TRIM               0x000
 #define EXYNOS5440_TMU_S0_7_CTRL               0x020
@@ -484,6 +510,101 @@ out:
        return ret;
 }
 
+static int exynos5433_tmu_initialize(struct platform_device *pdev)
+{
+       struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+       struct exynos_tmu_platform_data *pdata = data->pdata;
+       struct thermal_zone_device *tz = data->tzd;
+       unsigned int status, trim_info;
+       unsigned int rising_threshold = 0, falling_threshold = 0;
+       unsigned long temp, temp_hist;
+       int ret = 0, threshold_code, i, sensor_id, cal_type;
+
+       status = readb(data->base + EXYNOS_TMU_REG_STATUS);
+       if (!status) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
+       sanitize_temp_error(data, trim_info);
+
+       /* Read the temperature sensor id */
+       sensor_id = (trim_info & EXYNOS5433_TRIMINFO_SENSOR_ID_MASK)
+                               >> EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT;
+       dev_info(&pdev->dev, "Temperature sensor ID: 0x%x\n", sensor_id);
+
+       /* Read the calibration mode */
+       writel(trim_info, data->base + EXYNOS_TMU_REG_TRIMINFO);
+       cal_type = (trim_info & EXYNOS5433_TRIMINFO_CALIB_SEL_MASK)
+                               >> EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT;
+
+       switch (cal_type) {
+       case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING:
+               pdata->cal_type = TYPE_ONE_POINT_TRIMMING;
+               break;
+       case EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING:
+               pdata->cal_type = TYPE_TWO_POINT_TRIMMING;
+               break;
+       default:
+               pdata->cal_type = TYPE_ONE_POINT_TRIMMING;
+               break;
+       };
+
+       dev_info(&pdev->dev, "Calibration type is %d-point calibration\n",
+                       cal_type ?  2 : 1);
+
+       /* Write temperature code for rising and falling threshold */
+       for (i = 0; i < of_thermal_get_ntrips(tz); i++) {
+               int rising_reg_offset, falling_reg_offset;
+               int j = 0;
+
+               switch (i) {
+               case 0:
+               case 1:
+               case 2:
+               case 3:
+                       rising_reg_offset = EXYNOS5433_THD_TEMP_RISE3_0;
+                       falling_reg_offset = EXYNOS5433_THD_TEMP_FALL3_0;
+                       j = i;
+                       break;
+               case 4:
+               case 5:
+               case 6:
+               case 7:
+                       rising_reg_offset = EXYNOS5433_THD_TEMP_RISE7_4;
+                       falling_reg_offset = EXYNOS5433_THD_TEMP_FALL7_4;
+                       j = i - 4;
+                       break;
+               default:
+                       continue;
+               }
+
+               /* Write temperature code for rising threshold */
+               tz->ops->get_trip_temp(tz, i, &temp);
+               temp /= MCELSIUS;
+               threshold_code = temp_to_code(data, temp);
+
+               rising_threshold = readl(data->base + rising_reg_offset);
+               rising_threshold |= (threshold_code << j * 8);
+               writel(rising_threshold, data->base + rising_reg_offset);
+
+               /* Write temperature code for falling threshold */
+               tz->ops->get_trip_hyst(tz, i, &temp_hist);
+               temp_hist = temp - (temp_hist / MCELSIUS);
+               threshold_code = temp_to_code(data, temp_hist);
+
+               falling_threshold = readl(data->base + falling_reg_offset);
+               falling_threshold &= ~(0xff << j * 8);
+               falling_threshold |= (threshold_code << j * 8);
+               writel(falling_threshold, data->base + falling_reg_offset);
+       }
+
+       data->tmu_clear_irqs(data);
+out:
+       return ret;
+}
+
 static int exynos5440_tmu_initialize(struct platform_device *pdev)
 {
        struct exynos_tmu_data *data = platform_get_drvdata(pdev);
@@ -643,6 +764,48 @@ static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
        writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
 }
 
+static void exynos5433_tmu_control(struct platform_device *pdev, bool on)
+{
+       struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+       struct thermal_zone_device *tz = data->tzd;
+       unsigned int con, interrupt_en, pd_det_en;
+
+       con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
+
+       if (on) {
+               con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
+               interrupt_en =
+                       (of_thermal_is_trip_valid(tz, 7)
+                       << EXYNOS7_TMU_INTEN_RISE7_SHIFT) |
+                       (of_thermal_is_trip_valid(tz, 6)
+                       << EXYNOS7_TMU_INTEN_RISE6_SHIFT) |
+                       (of_thermal_is_trip_valid(tz, 5)
+                       << EXYNOS7_TMU_INTEN_RISE5_SHIFT) |
+                       (of_thermal_is_trip_valid(tz, 4)
+                       << EXYNOS7_TMU_INTEN_RISE4_SHIFT) |
+                       (of_thermal_is_trip_valid(tz, 3)
+                       << EXYNOS7_TMU_INTEN_RISE3_SHIFT) |
+                       (of_thermal_is_trip_valid(tz, 2)
+                       << EXYNOS7_TMU_INTEN_RISE2_SHIFT) |
+                       (of_thermal_is_trip_valid(tz, 1)
+                       << EXYNOS7_TMU_INTEN_RISE1_SHIFT) |
+                       (of_thermal_is_trip_valid(tz, 0)
+                       << EXYNOS7_TMU_INTEN_RISE0_SHIFT);
+
+               interrupt_en |=
+                       interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
+       } else {
+               con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
+               interrupt_en = 0; /* Disable all interrupts */
+       }
+
+       pd_det_en = on ? EXYNOS5433_PD_DET_EN : 0;
+
+       writel(pd_det_en, data->base + EXYNOS5433_TMU_PD_DET_EN);
+       writel(interrupt_en, data->base + EXYNOS5433_TMU_REG_INTEN);
+       writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
+}
+
 static void exynos5440_tmu_control(struct platform_device *pdev, bool on)
 {
        struct exynos_tmu_data *data = platform_get_drvdata(pdev);
@@ -770,6 +933,8 @@ static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
 
        if (data->soc == SOC_ARCH_EXYNOS5260)
                emul_con = EXYNOS5260_EMUL_CON;
+       if (data->soc == SOC_ARCH_EXYNOS5433)
+               emul_con = EXYNOS5433_TMU_EMUL_CON;
        else if (data->soc == SOC_ARCH_EXYNOS7)
                emul_con = EXYNOS7_TMU_REG_EMUL_CON;
        else
@@ -882,6 +1047,9 @@ static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
        } else if (data->soc == SOC_ARCH_EXYNOS7) {
                tmu_intstat = EXYNOS7_TMU_REG_INTPEND;
                tmu_intclear = EXYNOS7_TMU_REG_INTPEND;
+       } else if (data->soc == SOC_ARCH_EXYNOS5433) {
+               tmu_intstat = EXYNOS5433_TMU_REG_INTPEND;
+               tmu_intclear = EXYNOS5433_TMU_REG_INTPEND;
        } else {
                tmu_intstat = EXYNOS_TMU_REG_INTSTAT;
                tmu_intclear = EXYNOS_TMU_REG_INTCLEAR;
@@ -926,6 +1094,7 @@ static const struct of_device_id exynos_tmu_match[] = {
        { .compatible = "samsung,exynos5260-tmu", },
        { .compatible = "samsung,exynos5420-tmu", },
        { .compatible = "samsung,exynos5420-tmu-ext-triminfo", },
+       { .compatible = "samsung,exynos5433-tmu", },
        { .compatible = "samsung,exynos5440-tmu", },
        { .compatible = "samsung,exynos7-tmu", },
        { /* sentinel */ },
@@ -949,6 +1118,8 @@ static int exynos_of_get_soc_type(struct device_node *np)
        else if (of_device_is_compatible(np,
                                         "samsung,exynos5420-tmu-ext-triminfo"))
                return SOC_ARCH_EXYNOS5420_TRIMINFO;
+       else if (of_device_is_compatible(np, "samsung,exynos5433-tmu"))
+               return SOC_ARCH_EXYNOS5433;
        else if (of_device_is_compatible(np, "samsung,exynos5440-tmu"))
                return SOC_ARCH_EXYNOS5440;
        else if (of_device_is_compatible(np, "samsung,exynos7-tmu"))
@@ -1069,6 +1240,13 @@ static int exynos_map_dt_data(struct platform_device *pdev)
                data->tmu_set_emulation = exynos4412_tmu_set_emulation;
                data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
                break;
+       case SOC_ARCH_EXYNOS5433:
+               data->tmu_initialize = exynos5433_tmu_initialize;
+               data->tmu_control = exynos5433_tmu_control;
+               data->tmu_read = exynos4412_tmu_read;
+               data->tmu_set_emulation = exynos4412_tmu_set_emulation;
+               data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
+               break;
        case SOC_ARCH_EXYNOS5440:
                data->tmu_initialize = exynos5440_tmu_initialize;
                data->tmu_control = exynos5440_tmu_control;
@@ -1172,7 +1350,9 @@ static int exynos_tmu_probe(struct platform_device *pdev)
                goto err_clk_sec;
        }
 
-       if (data->soc == SOC_ARCH_EXYNOS7) {
+       switch (data->soc) {
+       case SOC_ARCH_EXYNOS5433:
+       case SOC_ARCH_EXYNOS7:
                data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
                if (IS_ERR(data->sclk)) {
                        dev_err(&pdev->dev, "Failed to get sclk\n");
@@ -1184,7 +1364,10 @@ static int exynos_tmu_probe(struct platform_device *pdev)
                                goto err_clk;
                        }
                }
-       }
+               break;
+       default:
+               break;
+       };
 
        ret = exynos_tmu_initialize(pdev);
        if (ret) {
index 4d71ec6c9aa0ba10b86c3975d92d5ebcea00d543..440c7140b6603f8b20e493b448cd42a403e76708 100644 (file)
@@ -33,6 +33,7 @@ enum soc_type {
        SOC_ARCH_EXYNOS5260,
        SOC_ARCH_EXYNOS5420,
        SOC_ARCH_EXYNOS5420_TRIMINFO,
+       SOC_ARCH_EXYNOS5433,
        SOC_ARCH_EXYNOS5440,
        SOC_ARCH_EXYNOS7,
 };
index 4108db7e10c1094622d4abca08017db3125cf80e..04659bfb888b73237257b75f944bf9130bc241b6 100644 (file)
@@ -75,6 +75,58 @@ static struct thermal_governor *__find_governor(const char *name)
        return NULL;
 }
 
+/**
+ * bind_previous_governor() - bind the previous governor of the thermal zone
+ * @tz:                a valid pointer to a struct thermal_zone_device
+ * @failed_gov_name:   the name of the governor that failed to register
+ *
+ * Register the previous governor of the thermal zone after a new
+ * governor has failed to be bound.
+ */
+static void bind_previous_governor(struct thermal_zone_device *tz,
+                                  const char *failed_gov_name)
+{
+       if (tz->governor && tz->governor->bind_to_tz) {
+               if (tz->governor->bind_to_tz(tz)) {
+                       dev_err(&tz->device,
+                               "governor %s failed to bind and the previous one (%s) failed to bind again, thermal zone %s has no governor\n",
+                               failed_gov_name, tz->governor->name, tz->type);
+                       tz->governor = NULL;
+               }
+       }
+}
+
+/**
+ * thermal_set_governor() - Switch to another governor
+ * @tz:                a valid pointer to a struct thermal_zone_device
+ * @new_gov:   pointer to the new governor
+ *
+ * Change the governor of thermal zone @tz.
+ *
+ * Return: 0 on success, an error if the new governor's bind_to_tz() failed.
+ */
+static int thermal_set_governor(struct thermal_zone_device *tz,
+                               struct thermal_governor *new_gov)
+{
+       int ret = 0;
+
+       if (tz->governor && tz->governor->unbind_from_tz)
+               tz->governor->unbind_from_tz(tz);
+
+       if (new_gov && new_gov->bind_to_tz) {
+               ret = new_gov->bind_to_tz(tz);
+               if (ret) {
+                       bind_previous_governor(tz, new_gov->name);
+
+                       return ret;
+               }
+       }
+
+       tz->governor = new_gov;
+
+       return ret;
+}
+
 int thermal_register_governor(struct thermal_governor *governor)
 {
        int err;
@@ -107,8 +159,15 @@ int thermal_register_governor(struct thermal_governor *governor)
 
                name = pos->tzp->governor_name;
 
-               if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH))
-                       pos->governor = governor;
+               if (!strncasecmp(name, governor->name, THERMAL_NAME_LENGTH)) {
+                       int ret;
+
+                       ret = thermal_set_governor(pos, governor);
+                       if (ret)
+                               dev_err(&pos->device,
+                                       "Failed to set governor %s for thermal zone %s: %d\n",
+                                       governor->name, pos->type, ret);
+               }
        }
 
        mutex_unlock(&thermal_list_lock);
@@ -134,7 +193,7 @@ void thermal_unregister_governor(struct thermal_governor *governor)
        list_for_each_entry(pos, &thermal_tz_list, node) {
                if (!strncasecmp(pos->governor->name, governor->name,
                                                THERMAL_NAME_LENGTH))
-                       pos->governor = NULL;
+                       thermal_set_governor(pos, NULL);
        }
 
        mutex_unlock(&thermal_list_lock);
@@ -218,7 +277,8 @@ static void print_bind_err_msg(struct thermal_zone_device *tz,
 
 static void __bind(struct thermal_zone_device *tz, int mask,
                        struct thermal_cooling_device *cdev,
-                       unsigned long *limits)
+                       unsigned long *limits,
+                       unsigned int weight)
 {
        int i, ret;
 
@@ -233,7 +293,8 @@ static void __bind(struct thermal_zone_device *tz, int mask,
                                upper = limits[i * 2 + 1];
                        }
                        ret = thermal_zone_bind_cooling_device(tz, i, cdev,
-                                                              upper, lower);
+                                                              upper, lower,
+                                                              weight);
                        if (ret)
                                print_bind_err_msg(tz, cdev, ret);
                }
@@ -280,7 +341,8 @@ static void bind_cdev(struct thermal_cooling_device *cdev)
                                continue;
                        tzp->tbp[i].cdev = cdev;
                        __bind(pos, tzp->tbp[i].trip_mask, cdev,
-                              tzp->tbp[i].binding_limits);
+                              tzp->tbp[i].binding_limits,
+                              tzp->tbp[i].weight);
                }
        }
 
@@ -319,7 +381,8 @@ static void bind_tz(struct thermal_zone_device *tz)
                                continue;
                        tzp->tbp[i].cdev = pos;
                        __bind(tz, tzp->tbp[i].trip_mask, pos,
-                              tzp->tbp[i].binding_limits);
+                              tzp->tbp[i].binding_limits,
+                              tzp->tbp[i].weight);
                }
        }
 exit:
@@ -713,7 +776,8 @@ passive_store(struct device *dev, struct device_attribute *attr,
                                thermal_zone_bind_cooling_device(tz,
                                                THERMAL_TRIPS_NONE, cdev,
                                                THERMAL_NO_LIMIT,
-                                               THERMAL_NO_LIMIT);
+                                               THERMAL_NO_LIMIT,
+                                               THERMAL_WEIGHT_DEFAULT);
                }
                mutex_unlock(&thermal_list_lock);
                if (!tz->passive_delay)
@@ -765,8 +829,9 @@ policy_store(struct device *dev, struct device_attribute *attr,
        if (!gov)
                goto exit;
 
-       tz->governor = gov;
-       ret = count;
+       ret = thermal_set_governor(tz, gov);
+       if (!ret)
+               ret = count;
 
 exit:
        mutex_unlock(&tz->lock);
@@ -810,6 +875,158 @@ emul_temp_store(struct device *dev, struct device_attribute *attr,
 static DEVICE_ATTR(emul_temp, S_IWUSR, NULL, emul_temp_store);
 #endif/*CONFIG_THERMAL_EMULATION*/
 
+static ssize_t
+sustainable_power_show(struct device *dev, struct device_attribute *devattr,
+                      char *buf)
+{
+       struct thermal_zone_device *tz = to_thermal_zone(dev);
+
+       if (tz->tzp)
+               return sprintf(buf, "%u\n", tz->tzp->sustainable_power);
+       else
+               return -EIO;
+}
+
+static ssize_t
+sustainable_power_store(struct device *dev, struct device_attribute *devattr,
+                       const char *buf, size_t count)
+{
+       struct thermal_zone_device *tz = to_thermal_zone(dev);
+       u32 sustainable_power;
+
+       if (!tz->tzp)
+               return -EIO;
+
+       if (kstrtou32(buf, 10, &sustainable_power))
+               return -EINVAL;
+
+       tz->tzp->sustainable_power = sustainable_power;
+
+       return count;
+}
+static DEVICE_ATTR(sustainable_power, S_IWUSR | S_IRUGO, sustainable_power_show,
+               sustainable_power_store);
+
+#define create_s32_tzp_attr(name)                                      \
+       static ssize_t                                                  \
+       name##_show(struct device *dev, struct device_attribute *devattr, \
+               char *buf)                                              \
+       {                                                               \
+       struct thermal_zone_device *tz = to_thermal_zone(dev);          \
+                                                                       \
+       if (tz->tzp)                                                    \
+               return sprintf(buf, "%u\n", tz->tzp->name);             \
+       else                                                            \
+               return -EIO;                                            \
+       }                                                               \
+                                                                       \
+       static ssize_t                                                  \
+       name##_store(struct device *dev, struct device_attribute *devattr, \
+               const char *buf, size_t count)                          \
+       {                                                               \
+               struct thermal_zone_device *tz = to_thermal_zone(dev);  \
+               s32 value;                                              \
+                                                                       \
+               if (!tz->tzp)                                           \
+                       return -EIO;                                    \
+                                                                       \
+               if (kstrtos32(buf, 10, &value))                         \
+                       return -EINVAL;                                 \
+                                                                       \
+               tz->tzp->name = value;                                  \
+                                                                       \
+               return count;                                           \
+       }                                                               \
+       static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, name##_show, name##_store)
+
+create_s32_tzp_attr(k_po);
+create_s32_tzp_attr(k_pu);
+create_s32_tzp_attr(k_i);
+create_s32_tzp_attr(k_d);
+create_s32_tzp_attr(integral_cutoff);
+create_s32_tzp_attr(slope);
+create_s32_tzp_attr(offset);
+#undef create_s32_tzp_attr
+
+static struct device_attribute *dev_tzp_attrs[] = {
+       &dev_attr_sustainable_power,
+       &dev_attr_k_po,
+       &dev_attr_k_pu,
+       &dev_attr_k_i,
+       &dev_attr_k_d,
+       &dev_attr_integral_cutoff,
+       &dev_attr_slope,
+       &dev_attr_offset,
+};
+
+static int create_tzp_attrs(struct device *dev)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(dev_tzp_attrs); i++) {
+               int ret;
+               struct device_attribute *dev_attr = dev_tzp_attrs[i];
+
+               ret = device_create_file(dev, dev_attr);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/**
+ * power_actor_get_max_power() - get the maximum power that a cdev can consume
+ * @cdev:      pointer to &thermal_cooling_device
+ * @tz:                a valid thermal zone device pointer
+ * @max_power: pointer in which to store the maximum power
+ *
+ * Calculate the maximum power consumption in milliwats that the
+ * cooling device can currently consume and store it in @max_power.
+ *
+ * Return: 0 on success, -EINVAL if @cdev doesn't support the
+ * power_actor API or -E* on other error.
+ */
+int power_actor_get_max_power(struct thermal_cooling_device *cdev,
+                             struct thermal_zone_device *tz, u32 *max_power)
+{
+       if (!cdev_is_power_actor(cdev))
+               return -EINVAL;
+
+       return cdev->ops->state2power(cdev, tz, 0, max_power);
+}
+
+/**
+ * power_actor_set_power() - limit the maximum power that a cooling device can consume
+ * @cdev:      pointer to &thermal_cooling_device
+ * @instance:  thermal instance to update
+ * @power:     the power in milliwatts
+ *
+ * Set the cooling device to consume at most @power milliwatts.
+ *
+ * Return: 0 on success, -EINVAL if the cooling device does not
+ * implement the power actor API or -E* for other failures.
+ */
+int power_actor_set_power(struct thermal_cooling_device *cdev,
+                         struct thermal_instance *instance, u32 power)
+{
+       unsigned long state;
+       int ret;
+
+       if (!cdev_is_power_actor(cdev))
+               return -EINVAL;
+
+       ret = cdev->ops->power2state(cdev, instance->tz, power, &state);
+       if (ret)
+               return ret;
+
+       instance->target = state;
+       cdev->updated = false;
+       thermal_cdev_update(cdev);
+
+       return 0;
+}
+
 static DEVICE_ATTR(type, 0444, type_show, NULL);
 static DEVICE_ATTR(temp, 0444, temp_show, NULL);
 static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
@@ -917,6 +1134,34 @@ static const struct attribute_group *cooling_device_attr_groups[] = {
        NULL,
 };
 
+static ssize_t
+thermal_cooling_device_weight_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct thermal_instance *instance;
+
+       instance = container_of(attr, struct thermal_instance, weight_attr);
+
+       return sprintf(buf, "%d\n", instance->weight);
+}
+
+static ssize_t
+thermal_cooling_device_weight_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t count)
+{
+       struct thermal_instance *instance;
+       int ret, weight;
+
+       ret = kstrtoint(buf, 0, &weight);
+       if (ret)
+               return ret;
+
+       instance = container_of(attr, struct thermal_instance, weight_attr);
+       instance->weight = weight;
+
+       return count;
+}
 /* Device management */
 
 /**
@@ -931,6 +1176,9 @@ static const struct attribute_group *cooling_device_attr_groups[] = {
  * @lower:     the Minimum cooling state can be used for this trip point.
  *             THERMAL_NO_LIMIT means no lower limit,
  *             and the cooling device can be in cooling state 0.
+ * @weight:    The weight of the cooling device to be bound to the
+ *             thermal zone. Use THERMAL_WEIGHT_DEFAULT for the
+ *             default value
  *
  * This interface function bind a thermal cooling device to the certain trip
  * point of a thermal zone device.
@@ -941,7 +1189,8 @@ static const struct attribute_group *cooling_device_attr_groups[] = {
 int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
                                     int trip,
                                     struct thermal_cooling_device *cdev,
-                                    unsigned long upper, unsigned long lower)
+                                    unsigned long upper, unsigned long lower,
+                                    unsigned int weight)
 {
        struct thermal_instance *dev;
        struct thermal_instance *pos;
@@ -986,6 +1235,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
        dev->upper = upper;
        dev->lower = lower;
        dev->target = THERMAL_NO_TARGET;
+       dev->weight = weight;
 
        result = get_idr(&tz->idr, &tz->lock, &dev->id);
        if (result)
@@ -1006,6 +1256,16 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
        if (result)
                goto remove_symbol_link;
 
+       sprintf(dev->weight_attr_name, "cdev%d_weight", dev->id);
+       sysfs_attr_init(&dev->weight_attr.attr);
+       dev->weight_attr.attr.name = dev->weight_attr_name;
+       dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO;
+       dev->weight_attr.show = thermal_cooling_device_weight_show;
+       dev->weight_attr.store = thermal_cooling_device_weight_store;
+       result = device_create_file(&tz->device, &dev->weight_attr);
+       if (result)
+               goto remove_trip_file;
+
        mutex_lock(&tz->lock);
        mutex_lock(&cdev->lock);
        list_for_each_entry(pos, &tz->thermal_instances, tz_node)
@@ -1023,6 +1283,8 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
        if (!result)
                return 0;
 
+       device_remove_file(&tz->device, &dev->weight_attr);
+remove_trip_file:
        device_remove_file(&tz->device, &dev->attr);
 remove_symbol_link:
        sysfs_remove_link(&tz->device.kobj, dev->name);
@@ -1377,7 +1639,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
                                                tz->trip_temp_attrs[indx].name;
                tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
                tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show;
-               if (mask & (1 << indx)) {
+               if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) &&
+                   mask & (1 << indx)) {
                        tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
                        tz->trip_temp_attrs[indx].attr.store =
                                                        trip_point_temp_store;
@@ -1454,7 +1717,7 @@ static void remove_trip_attrs(struct thermal_zone_device *tz)
 struct thermal_zone_device *thermal_zone_device_register(const char *type,
        int trips, int mask, void *devdata,
        struct thermal_zone_device_ops *ops,
-       const struct thermal_zone_params *tzp,
+       struct thermal_zone_params *tzp,
        int passive_delay, int polling_delay)
 {
        struct thermal_zone_device *tz;
@@ -1462,6 +1725,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
        int result;
        int count;
        int passive = 0;
+       struct thermal_governor *governor;
 
        if (type && strlen(type) >= THERMAL_NAME_LENGTH)
                return ERR_PTR(-EINVAL);
@@ -1548,13 +1812,24 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
        if (result)
                goto unregister;
 
+       /* Add thermal zone params */
+       result = create_tzp_attrs(&tz->device);
+       if (result)
+               goto unregister;
+
        /* Update 'this' zone's governor information */
        mutex_lock(&thermal_governor_lock);
 
        if (tz->tzp)
-               tz->governor = __find_governor(tz->tzp->governor_name);
+               governor = __find_governor(tz->tzp->governor_name);
        else
-               tz->governor = def_governor;
+               governor = def_governor;
+
+       result = thermal_set_governor(tz, governor);
+       if (result) {
+               mutex_unlock(&thermal_governor_lock);
+               goto unregister;
+       }
 
        mutex_unlock(&thermal_governor_lock);
 
@@ -1643,7 +1918,7 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
                device_remove_file(&tz->device, &dev_attr_mode);
        device_remove_file(&tz->device, &dev_attr_policy);
        remove_trip_attrs(tz);
-       tz->governor = NULL;
+       thermal_set_governor(tz, NULL);
 
        thermal_remove_hwmon_sysfs(tz);
        release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
@@ -1799,7 +2074,11 @@ static int __init thermal_register_governors(void)
        if (result)
                return result;
 
-       return thermal_gov_user_space_register();
+       result = thermal_gov_user_space_register();
+       if (result)
+               return result;
+
+       return thermal_gov_power_allocator_register();
 }
 
 static void thermal_unregister_governors(void)
@@ -1808,6 +2087,7 @@ static void thermal_unregister_governors(void)
        thermal_gov_fair_share_unregister();
        thermal_gov_bang_bang_unregister();
        thermal_gov_user_space_unregister();
+       thermal_gov_power_allocator_unregister();
 }
 
 static int __init thermal_init(void)
index 8e391812e50377664f079cb83f61c380b16e6d93..d7ac1fccd6598a80453dc51da655e37b30b85ac7 100644 (file)
@@ -46,8 +46,11 @@ struct thermal_instance {
        unsigned long target;   /* expected cooling state */
        char attr_name[THERMAL_NAME_LENGTH];
        struct device_attribute attr;
+       char weight_attr_name[THERMAL_NAME_LENGTH];
+       struct device_attribute weight_attr;
        struct list_head tz_node; /* node in tz->thermal_instances */
        struct list_head cdev_node; /* node in cdev->thermal_instances */
+       unsigned int weight; /* The weight of the cooling device */
 };
 
 int thermal_register_governor(struct thermal_governor *);
@@ -85,6 +88,14 @@ static inline int thermal_gov_user_space_register(void) { return 0; }
 static inline void thermal_gov_user_space_unregister(void) {}
 #endif /* CONFIG_THERMAL_GOV_USER_SPACE */
 
+#ifdef CONFIG_THERMAL_GOV_POWER_ALLOCATOR
+int thermal_gov_power_allocator_register(void);
+void thermal_gov_power_allocator_unregister(void);
+#else
+static inline int thermal_gov_power_allocator_register(void) { return 0; }
+static inline void thermal_gov_power_allocator_unregister(void) {}
+#endif /* CONFIG_THERMAL_GOV_POWER_ALLOCATOR */
+
 /* device tree support */
 #ifdef CONFIG_THERMAL_OF
 int of_parse_thermal_zones(void);
index bc14dc874594e4d9fd37a68874bb6adb6eb4b562..10c47c048f7a3b0e4f5538c3698492fd5482c21b 100644 (file)
@@ -43,6 +43,8 @@
 
 #include "ti-bandgap.h"
 
+static int ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id);
+
 /***   Helper functions to access registers and their bitfields   ***/
 
 /**
@@ -103,19 +105,15 @@ do {                                                              \
  */
 static int ti_bandgap_power(struct ti_bandgap *bgp, bool on)
 {
-       int i, ret = 0;
+       int i;
 
-       if (!TI_BANDGAP_HAS(bgp, POWER_SWITCH)) {
-               ret = -ENOTSUPP;
-               goto exit;
-       }
+       if (!TI_BANDGAP_HAS(bgp, POWER_SWITCH))
+               return -ENOTSUPP;
 
        for (i = 0; i < bgp->conf->sensor_count; i++)
                /* active on 0 */
                RMW_BITS(bgp, i, temp_sensor_ctrl, bgap_tempsoff_mask, !on);
-
-exit:
-       return ret;
+       return 0;
 }
 
 /**
@@ -298,18 +296,13 @@ static
 int ti_bandgap_adc_to_mcelsius(struct ti_bandgap *bgp, int adc_val, int *t)
 {
        const struct ti_bandgap_data *conf = bgp->conf;
-       int ret = 0;
 
        /* look up for temperature in the table and return the temperature */
-       if (adc_val < conf->adc_start_val || adc_val > conf->adc_end_val) {
-               ret = -ERANGE;
-               goto exit;
-       }
+       if (adc_val < conf->adc_start_val || adc_val > conf->adc_end_val)
+               return -ERANGE;
 
        *t = bgp->conf->conv_table[adc_val - conf->adc_start_val];
-
-exit:
-       return ret;
+       return 0;
 }
 
 /**
@@ -330,16 +323,14 @@ int ti_bandgap_mcelsius_to_adc(struct ti_bandgap *bgp, long temp, int *adc)
 {
        const struct ti_bandgap_data *conf = bgp->conf;
        const int *conv_table = bgp->conf->conv_table;
-       int high, low, mid, ret = 0;
+       int high, low, mid;
 
        low = 0;
        high = conf->adc_end_val - conf->adc_start_val;
        mid = (high + low) / 2;
 
-       if (temp < conv_table[low] || temp > conv_table[high]) {
-               ret = -ERANGE;
-               goto exit;
-       }
+       if (temp < conv_table[low] || temp > conv_table[high])
+               return -ERANGE;
 
        while (low < high) {
                if (temp < conv_table[mid])
@@ -350,9 +341,7 @@ int ti_bandgap_mcelsius_to_adc(struct ti_bandgap *bgp, long temp, int *adc)
        }
 
        *adc = conf->adc_start_val + low;
-
-exit:
-       return ret;
+       return 0;
 }
 
 /**
@@ -378,13 +367,11 @@ int ti_bandgap_add_hyst(struct ti_bandgap *bgp, int adc_val, int hyst_val,
         */
        ret = ti_bandgap_adc_to_mcelsius(bgp, adc_val, &temp);
        if (ret < 0)
-               goto exit;
+               return ret;
 
        temp += hyst_val;
 
        ret = ti_bandgap_mcelsius_to_adc(bgp, temp, sum);
-
-exit:
        return ret;
 }
 
@@ -542,22 +529,18 @@ exit:
  */
 static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id)
 {
-       int ret = 0;
-
        if (!bgp || IS_ERR(bgp)) {
                pr_err("%s: invalid bandgap pointer\n", __func__);
-               ret = -EINVAL;
-               goto exit;
+               return -EINVAL;
        }
 
        if ((id < 0) || (id >= bgp->conf->sensor_count)) {
                dev_err(bgp->dev, "%s: sensor id out of range (%d)\n",
                        __func__, id);
-               ret = -ERANGE;
+               return -ERANGE;
        }
 
-exit:
-       return ret;
+       return 0;
 }
 
 /**
@@ -585,12 +568,10 @@ static int _ti_bandgap_write_threshold(struct ti_bandgap *bgp, int id, int val,
 
        ret = ti_bandgap_validate(bgp, id);
        if (ret)
-               goto exit;
+               return ret;
 
-       if (!TI_BANDGAP_HAS(bgp, TALERT)) {
-               ret = -ENOTSUPP;
-               goto exit;
-       }
+       if (!TI_BANDGAP_HAS(bgp, TALERT))
+               return -ENOTSUPP;
 
        ts_data = bgp->conf->sensors[id].ts_data;
        tsr = bgp->conf->sensors[id].registers;
@@ -603,17 +584,15 @@ static int _ti_bandgap_write_threshold(struct ti_bandgap *bgp, int id, int val,
        }
 
        if (ret)
-               goto exit;
+               return ret;
 
        ret = ti_bandgap_mcelsius_to_adc(bgp, val, &adc_val);
        if (ret < 0)
-               goto exit;
+               return ret;
 
        spin_lock(&bgp->lock);
        ret = ti_bandgap_update_alert_threshold(bgp, id, adc_val, hot);
        spin_unlock(&bgp->lock);
-
-exit:
        return ret;
 }
 
@@ -656,7 +635,7 @@ static int _ti_bandgap_read_threshold(struct ti_bandgap *bgp, int id,
 
        temp = ti_bandgap_readl(bgp, tsr->bgap_threshold);
        temp = (temp & mask) >> __ffs(mask);
-       ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
+       ret = ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
        if (ret) {
                dev_err(bgp->dev, "failed to read thot\n");
                ret = -EIO;
@@ -926,11 +905,17 @@ int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id,
        if (ret)
                return ret;
 
+       if (!TI_BANDGAP_HAS(bgp, MODE_CONFIG)) {
+               ret = ti_bandgap_force_single_read(bgp, id);
+               if (ret)
+                       return ret;
+       }
+
        spin_lock(&bgp->lock);
        temp = ti_bandgap_read_temp(bgp, id);
        spin_unlock(&bgp->lock);
 
-       ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
+       ret = ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
        if (ret)
                return -EIO;
 
@@ -991,7 +976,8 @@ void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id)
 static int
 ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id)
 {
-       u32 temp = 0, counter = 1000;
+       u32 counter = 1000;
+       struct temp_sensor_registers *tsr;
 
        /* Select single conversion mode */
        if (TI_BANDGAP_HAS(bgp, MODE_CONFIG))
@@ -999,16 +985,27 @@ ti_bandgap_force_single_read(struct ti_bandgap *bgp, int id)
 
        /* Start of Conversion = 1 */
        RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 1);
-       /* Wait until DTEMP is updated */
-       temp = ti_bandgap_read_temp(bgp, id);
 
-       while ((temp == 0) && --counter)
-               temp = ti_bandgap_read_temp(bgp, id);
-       /* REVISIT: Check correct condition for end of conversion */
+       /* Wait for EOCZ going up */
+       tsr = bgp->conf->sensors[id].registers;
+
+       while (--counter) {
+               if (ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
+                   tsr->bgap_eocz_mask)
+                       break;
+       }
 
        /* Start of Conversion = 0 */
        RMW_BITS(bgp, id, temp_sensor_ctrl, bgap_soc_mask, 0);
 
+       /* Wait for EOCZ going down */
+       counter = 1000;
+       while (--counter) {
+               if (!(ti_bandgap_readl(bgp, tsr->temp_sensor_ctrl) &
+                     tsr->bgap_eocz_mask))
+                       break;
+       }
+
        return 0;
 }
 
@@ -1294,11 +1291,10 @@ int ti_bandgap_probe(struct platform_device *pdev)
                goto free_irqs;
        }
 
-       bgp->div_clk = clk_get(NULL,  bgp->conf->div_ck_name);
+       bgp->div_clk = clk_get(NULL, bgp->conf->div_ck_name);
        ret = IS_ERR(bgp->div_clk);
        if (ret) {
-               dev_err(&pdev->dev,
-                       "failed to request div_ts_ck clock ref\n");
+               dev_err(&pdev->dev, "failed to request div_ts_ck clock ref\n");
                ret = PTR_ERR(bgp->div_clk);
                goto free_irqs;
        }
index a38c1756442aa2611e0e207466aabba4e72b94cc..c7c5b3779dacc28bb40e0d86bb3d49eaa1e66154 100644 (file)
@@ -75,7 +75,7 @@ static inline int ti_thermal_hotspot_temperature(int t, int s, int c)
 }
 
 /* thermal zone ops */
-/* Get temperature callback function for thermal zone*/
+/* Get temperature callback function for thermal zone */
 static inline int __ti_thermal_get_temp(void *devdata, long *temp)
 {
        struct thermal_zone_device *pcb_tz = NULL;
@@ -146,7 +146,8 @@ static int ti_thermal_bind(struct thermal_zone_device *thermal,
        return thermal_zone_bind_cooling_device(thermal, 0, cdev,
        /* bind with min and max states defined by cpu_cooling */
                                                THERMAL_NO_LIMIT,
-                                               THERMAL_NO_LIMIT);
+                                               THERMAL_NO_LIMIT,
+                                               THERMAL_WEIGHT_DEFAULT);
 }
 
 /* Unbind callback functions for thermal zone */
index 9ea3d9d49ffc55d4fc8c9679a5e3e88e7e5549f4..50d1d2cb091a538b02753f81c2a5909a1f8be9d9 100644 (file)
@@ -68,7 +68,7 @@ struct phy_dev_entry {
        struct thermal_zone_device *tzone;
 };
 
-static const struct thermal_zone_params pkg_temp_tz_params = {
+static struct thermal_zone_params pkg_temp_tz_params = {
        .no_hwmon       = true,
 };
 
index bd955270d5aae60f77cc8f936a5820d9690ac6ac..c156f50827589c9547d48aac0072a30bf97672a5 100644 (file)
@@ -28,6 +28,9 @@
 #include <linux/thermal.h>
 #include <linux/cpumask.h>
 
+typedef int (*get_static_t)(cpumask_t *cpumask, int interval,
+                           unsigned long voltage, u32 *power);
+
 #ifdef CONFIG_CPU_THERMAL
 /**
  * cpufreq_cooling_register - function to create cpufreq cooling device.
 struct thermal_cooling_device *
 cpufreq_cooling_register(const struct cpumask *clip_cpus);
 
+struct thermal_cooling_device *
+cpufreq_power_cooling_register(const struct cpumask *clip_cpus,
+                              u32 capacitance, get_static_t plat_static_func);
+
 /**
  * of_cpufreq_cooling_register - create cpufreq cooling device based on DT.
  * @np: a valid struct device_node to the cooling device device tree node.
@@ -45,6 +52,12 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus);
 struct thermal_cooling_device *
 of_cpufreq_cooling_register(struct device_node *np,
                            const struct cpumask *clip_cpus);
+
+struct thermal_cooling_device *
+of_cpufreq_power_cooling_register(struct device_node *np,
+                                 const struct cpumask *clip_cpus,
+                                 u32 capacitance,
+                                 get_static_t plat_static_func);
 #else
 static inline struct thermal_cooling_device *
 of_cpufreq_cooling_register(struct device_node *np,
@@ -52,6 +65,15 @@ of_cpufreq_cooling_register(struct device_node *np,
 {
        return ERR_PTR(-ENOSYS);
 }
+
+static inline struct thermal_cooling_device *
+of_cpufreq_power_cooling_register(struct device_node *np,
+                                 const struct cpumask *clip_cpus,
+                                 u32 capacitance,
+                                 get_static_t plat_static_func)
+{
+       return NULL;
+}
 #endif
 
 /**
@@ -67,12 +89,29 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus)
 {
        return ERR_PTR(-ENOSYS);
 }
+static inline struct thermal_cooling_device *
+cpufreq_power_cooling_register(const struct cpumask *clip_cpus,
+                              u32 capacitance, get_static_t plat_static_func)
+{
+       return NULL;
+}
+
 static inline struct thermal_cooling_device *
 of_cpufreq_cooling_register(struct device_node *np,
                            const struct cpumask *clip_cpus)
 {
        return ERR_PTR(-ENOSYS);
 }
+
+static inline struct thermal_cooling_device *
+of_cpufreq_power_cooling_register(struct device_node *np,
+                                 const struct cpumask *clip_cpus,
+                                 u32 capacitance,
+                                 get_static_t plat_static_func)
+{
+       return NULL;
+}
+
 static inline
 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
 {
index 5eac316490eab9ca0ad1a864be2b6fedb823f332..037e9df2f6101bd70f2ad90e79e642599e56bc89 100644 (file)
@@ -40,6 +40,9 @@
 /* No upper/lower limit requirement */
 #define THERMAL_NO_LIMIT       ((u32)~0)
 
+/* Default weight of a bound cooling device */
+#define THERMAL_WEIGHT_DEFAULT 0
+
 /* Unit conversion macros */
 #define KELVIN_TO_CELSIUS(t)   (long)(((long)t-2732 >= 0) ?    \
                                ((long)t-2732+5)/10 : ((long)t-2732-5)/10)
 #define DEFAULT_THERMAL_GOVERNOR       "fair_share"
 #elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE)
 #define DEFAULT_THERMAL_GOVERNOR       "user_space"
+#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR)
+#define DEFAULT_THERMAL_GOVERNOR       "power_allocator"
 #endif
 
 struct thermal_zone_device;
 struct thermal_cooling_device;
+struct thermal_instance;
 
 enum thermal_device_mode {
        THERMAL_DEVICE_DISABLED = 0,
@@ -113,6 +119,12 @@ struct thermal_cooling_device_ops {
        int (*get_max_state) (struct thermal_cooling_device *, unsigned long *);
        int (*get_cur_state) (struct thermal_cooling_device *, unsigned long *);
        int (*set_cur_state) (struct thermal_cooling_device *, unsigned long);
+       int (*get_requested_power)(struct thermal_cooling_device *,
+                                  struct thermal_zone_device *, u32 *);
+       int (*state2power)(struct thermal_cooling_device *,
+                          struct thermal_zone_device *, unsigned long, u32 *);
+       int (*power2state)(struct thermal_cooling_device *,
+                          struct thermal_zone_device *, u32, unsigned long *);
 };
 
 struct thermal_cooling_device {
@@ -144,8 +156,7 @@ struct thermal_attr {
  * @devdata:   private pointer for device private data
  * @trips:     number of trip points the thermal zone supports
  * @passive_delay:     number of milliseconds to wait between polls when
- *                     performing passive cooling.  Currenty only used by the
- *                     step-wise governor
+ *                     performing passive cooling.
  * @polling_delay:     number of milliseconds to wait between polls when
  *                     checking whether trip points have been crossed (0 for
  *                     interrupt driven systems)
@@ -155,13 +166,13 @@ struct thermal_attr {
  * @last_temperature:  previous temperature read
  * @emul_temperature:  emulated temperature when using CONFIG_THERMAL_EMULATION
  * @passive:           1 if you've crossed a passive trip point, 0 otherwise.
- *                     Currenty only used by the step-wise governor.
  * @forced_passive:    If > 0, temperature at which to switch on all ACPI
  *                     processor cooling devices.  Currently only used by the
  *                     step-wise governor.
  * @ops:       operations this &thermal_zone_device supports
  * @tzp:       thermal zone parameters
  * @governor:  pointer to the governor for this thermal zone
+ * @governor_data:     private pointer for governor data
  * @thermal_instances: list of &struct thermal_instance of this thermal zone
  * @idr:       &struct idr to generate unique id for this zone's cooling
  *             devices
@@ -186,8 +197,9 @@ struct thermal_zone_device {
        int passive;
        unsigned int forced_passive;
        struct thermal_zone_device_ops *ops;
-       const struct thermal_zone_params *tzp;
+       struct thermal_zone_params *tzp;
        struct thermal_governor *governor;
+       void *governor_data;
        struct list_head thermal_instances;
        struct idr idr;
        struct mutex lock;
@@ -198,12 +210,19 @@ struct thermal_zone_device {
 /**
  * struct thermal_governor - structure that holds thermal governor information
  * @name:      name of the governor
+ * @bind_to_tz: callback called when binding to a thermal zone.  If it
+ *             returns 0, the governor is bound to the thermal zone,
+ *             otherwise it fails.
+ * @unbind_from_tz:    callback called when a governor is unbound from a
+ *                     thermal zone.
  * @throttle:  callback called for every trip point even if temperature is
  *             below the trip point temperature
  * @governor_list:     node in thermal_governor_list (in thermal_core.c)
  */
 struct thermal_governor {
        char name[THERMAL_NAME_LENGTH];
+       int (*bind_to_tz)(struct thermal_zone_device *tz);
+       void (*unbind_from_tz)(struct thermal_zone_device *tz);
        int (*throttle)(struct thermal_zone_device *tz, int trip);
        struct list_head        governor_list;
 };
@@ -214,9 +233,12 @@ struct thermal_bind_params {
 
        /*
         * This is a measure of 'how effectively these devices can
-        * cool 'this' thermal zone. The shall be determined by platform
-        * characterization. This is on a 'percentage' scale.
-        * See Documentation/thermal/sysfs-api.txt for more information.
+        * cool 'this' thermal zone. It shall be determined by
+        * platform characterization. This value is relative to the
+        * rest of the weights so a cooling device whose weight is
+        * double that of another cooling device is twice as
+        * effective. See Documentation/thermal/sysfs-api.txt for more
+        * information.
         */
        int weight;
 
@@ -253,6 +275,44 @@ struct thermal_zone_params {
 
        int num_tbps;   /* Number of tbp entries */
        struct thermal_bind_params *tbp;
+
+       /*
+        * Sustainable power (heat) that this thermal zone can dissipate in
+        * mW
+        */
+       u32 sustainable_power;
+
+       /*
+        * Proportional parameter of the PID controller when
+        * overshooting (i.e., when temperature is below the target)
+        */
+       s32 k_po;
+
+       /*
+        * Proportional parameter of the PID controller when
+        * undershooting
+        */
+       s32 k_pu;
+
+       /* Integral parameter of the PID controller */
+       s32 k_i;
+
+       /* Derivative parameter of the PID controller */
+       s32 k_d;
+
+       /* threshold below which the error is no longer accumulated */
+       s32 integral_cutoff;
+
+       /*
+        * @slope:      slope of a linear temperature adjustment curve.
+        *              Used by thermal zone drivers.
+        */
+       int slope;
+       /*
+        * @offset:     offset of a linear temperature adjustment curve.
+        *              Used by thermal zone drivers (default 0).
+        */
+       int offset;
 };
 
 struct thermal_genl_event {
@@ -316,14 +376,25 @@ void thermal_zone_of_sensor_unregister(struct device *dev,
 #endif
 
 #if IS_ENABLED(CONFIG_THERMAL)
+static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
+{
+       return cdev->ops->get_requested_power && cdev->ops->state2power &&
+               cdev->ops->power2state;
+}
+
+int power_actor_get_max_power(struct thermal_cooling_device *,
+                             struct thermal_zone_device *tz, u32 *max_power);
+int power_actor_set_power(struct thermal_cooling_device *,
+                         struct thermal_instance *, u32);
 struct thermal_zone_device *thermal_zone_device_register(const char *, int, int,
                void *, struct thermal_zone_device_ops *,
-               const struct thermal_zone_params *, int, int);
+               struct thermal_zone_params *, int, int);
 void thermal_zone_device_unregister(struct thermal_zone_device *);
 
 int thermal_zone_bind_cooling_device(struct thermal_zone_device *, int,
                                     struct thermal_cooling_device *,
-                                    unsigned long, unsigned long);
+                                    unsigned long, unsigned long,
+                                    unsigned int);
 int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int,
                                       struct thermal_cooling_device *);
 void thermal_zone_device_update(struct thermal_zone_device *);
@@ -343,6 +414,14 @@ struct thermal_instance *get_thermal_instance(struct thermal_zone_device *,
 void thermal_cdev_update(struct thermal_cooling_device *);
 void thermal_notify_framework(struct thermal_zone_device *, int);
 #else
+static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
+{ return false; }
+static inline int power_actor_get_max_power(struct thermal_cooling_device *cdev,
+                             struct thermal_zone_device *tz, u32 *max_power)
+{ return 0; }
+static inline int power_actor_set_power(struct thermal_cooling_device *cdev,
+                         struct thermal_instance *tz, u32 power)
+{ return 0; }
 static inline struct thermal_zone_device *thermal_zone_device_register(
        const char *type, int trips, int mask, void *devdata,
        struct thermal_zone_device_ops *ops,
index 0f4f95d63c031b921eb1fa8e4a66848442145dd8..8b1f80682b8047c1f77a08830a16e7b529ddfb00 100644 (file)
@@ -77,6 +77,64 @@ TRACE_EVENT(thermal_zone_trip,
                __entry->trip_type)
 );
 
+TRACE_EVENT(thermal_power_cpu_get_power,
+       TP_PROTO(const struct cpumask *cpus, unsigned long freq, u32 *load,
+               size_t load_len, u32 dynamic_power, u32 static_power),
+
+       TP_ARGS(cpus, freq, load, load_len, dynamic_power, static_power),
+
+       TP_STRUCT__entry(
+               __bitmask(cpumask, num_possible_cpus())
+               __field(unsigned long, freq          )
+               __dynamic_array(u32,   load, load_len)
+               __field(size_t,        load_len      )
+               __field(u32,           dynamic_power )
+               __field(u32,           static_power  )
+       ),
+
+       TP_fast_assign(
+               __assign_bitmask(cpumask, cpumask_bits(cpus),
+                               num_possible_cpus());
+               __entry->freq = freq;
+               memcpy(__get_dynamic_array(load), load,
+                       load_len * sizeof(*load));
+               __entry->load_len = load_len;
+               __entry->dynamic_power = dynamic_power;
+               __entry->static_power = static_power;
+       ),
+
+       TP_printk("cpus=%s freq=%lu load={%s} dynamic_power=%d static_power=%d",
+               __get_bitmask(cpumask), __entry->freq,
+               __print_array(__get_dynamic_array(load), __entry->load_len, 4),
+               __entry->dynamic_power, __entry->static_power)
+);
+
+TRACE_EVENT(thermal_power_cpu_limit,
+       TP_PROTO(const struct cpumask *cpus, unsigned int freq,
+               unsigned long cdev_state, u32 power),
+
+       TP_ARGS(cpus, freq, cdev_state, power),
+
+       TP_STRUCT__entry(
+               __bitmask(cpumask, num_possible_cpus())
+               __field(unsigned int,  freq      )
+               __field(unsigned long, cdev_state)
+               __field(u32,           power     )
+       ),
+
+       TP_fast_assign(
+               __assign_bitmask(cpumask, cpumask_bits(cpus),
+                               num_possible_cpus());
+               __entry->freq = freq;
+               __entry->cdev_state = cdev_state;
+               __entry->power = power;
+       ),
+
+       TP_printk("cpus=%s freq=%u cdev_state=%lu power=%u",
+               __get_bitmask(cpumask), __entry->freq, __entry->cdev_state,
+               __entry->power)
+);
+
 #endif /* _TRACE_THERMAL_H */
 
 /* This part must be outside protection */
diff --git a/include/trace/events/thermal_power_allocator.h b/include/trace/events/thermal_power_allocator.h
new file mode 100644 (file)
index 0000000..12e1321
--- /dev/null
@@ -0,0 +1,87 @@
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM thermal_power_allocator
+
+#if !defined(_TRACE_THERMAL_POWER_ALLOCATOR_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_THERMAL_POWER_ALLOCATOR_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(thermal_power_allocator,
+       TP_PROTO(struct thermal_zone_device *tz, u32 *req_power,
+                u32 total_req_power, u32 *granted_power,
+                u32 total_granted_power, size_t num_actors,
+                u32 power_range, u32 max_allocatable_power,
+                unsigned long current_temp, s32 delta_temp),
+       TP_ARGS(tz, req_power, total_req_power, granted_power,
+               total_granted_power, num_actors, power_range,
+               max_allocatable_power, current_temp, delta_temp),
+       TP_STRUCT__entry(
+               __field(int,           tz_id          )
+               __dynamic_array(u32,   req_power, num_actors    )
+               __field(u32,           total_req_power          )
+               __dynamic_array(u32,   granted_power, num_actors)
+               __field(u32,           total_granted_power      )
+               __field(size_t,        num_actors               )
+               __field(u32,           power_range              )
+               __field(u32,           max_allocatable_power    )
+               __field(unsigned long, current_temp             )
+               __field(s32,           delta_temp               )
+       ),
+       TP_fast_assign(
+               __entry->tz_id = tz->id;
+               memcpy(__get_dynamic_array(req_power), req_power,
+                       num_actors * sizeof(*req_power));
+               __entry->total_req_power = total_req_power;
+               memcpy(__get_dynamic_array(granted_power), granted_power,
+                       num_actors * sizeof(*granted_power));
+               __entry->total_granted_power = total_granted_power;
+               __entry->num_actors = num_actors;
+               __entry->power_range = power_range;
+               __entry->max_allocatable_power = max_allocatable_power;
+               __entry->current_temp = current_temp;
+               __entry->delta_temp = delta_temp;
+       ),
+
+       TP_printk("thermal_zone_id=%d req_power={%s} total_req_power=%u granted_power={%s} total_granted_power=%u power_range=%u max_allocatable_power=%u current_temperature=%lu delta_temperature=%d",
+               __entry->tz_id,
+               __print_array(__get_dynamic_array(req_power),
+                              __entry->num_actors, 4),
+               __entry->total_req_power,
+               __print_array(__get_dynamic_array(granted_power),
+                              __entry->num_actors, 4),
+               __entry->total_granted_power, __entry->power_range,
+               __entry->max_allocatable_power, __entry->current_temp,
+               __entry->delta_temp)
+);
+
+TRACE_EVENT(thermal_power_allocator_pid,
+       TP_PROTO(struct thermal_zone_device *tz, s32 err, s32 err_integral,
+                s64 p, s64 i, s64 d, s32 output),
+       TP_ARGS(tz, err, err_integral, p, i, d, output),
+       TP_STRUCT__entry(
+               __field(int, tz_id       )
+               __field(s32, err         )
+               __field(s32, err_integral)
+               __field(s64, p           )
+               __field(s64, i           )
+               __field(s64, d           )
+               __field(s32, output      )
+       ),
+       TP_fast_assign(
+               __entry->tz_id = tz->id;
+               __entry->err = err;
+               __entry->err_integral = err_integral;
+               __entry->p = p;
+               __entry->i = i;
+               __entry->d = d;
+               __entry->output = output;
+       ),
+
+       TP_printk("thermal_zone_id=%d err=%d err_integral=%d p=%lld i=%lld d=%lld output=%d",
+                 __entry->tz_id, __entry->err, __entry->err_integral,
+                 __entry->p, __entry->i, __entry->d, __entry->output)
+);
+#endif /* _TRACE_THERMAL_POWER_ALLOCATOR_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>