Merge branch 'pm-devfreq'
* pm-devfreq: PM / devfreq: style/typo fixes PM / devfreq: exynos: Add the detailed correlation for Exynos5422 bus PM / devfreq: event: Find the instance of devfreq-event device by using phandle PM / devfreq: event: Add new Exynos NoC probe driver MAINTAINERS: Add samsung bus frequency driver entry PM / devfreq: exynos: Remove unused exynos4/5 busfreq driver PM / devfreq: exynos: Add the detailed correlation between sub-blocks and power line PM / devfreq: exynos: Update documentation for bus devices using passive governor PM / devfreq: exynos: Add support of bus frequency of sub-blocks using passive governor PM / devfreq: Add new passive governor PM / devfreq: Add new DEVFREQ_TRANSITION_NOTIFIER notifier PM / devfreq: Add devfreq_get_devfreq_by_phandle() PM / devfreq: exynos: Add documentation for generic exynos bus frequency driver PM / devfreq: exynos: Add generic exynos bus frequency driver
This commit is contained in:
commit
7777c2785b
|
@ -0,0 +1,26 @@
|
|||
|
||||
* Samsung Exynos NoC (Network on Chip) Probe device
|
||||
|
||||
The Samsung Exynos542x SoC has NoC (Network on Chip) Probe for NoC bus.
|
||||
NoC provides the primitive values to get the performance data. The packets
|
||||
that the Network on Chip (NoC) probes detects are transported over
|
||||
the network infrastructure to observer units. You can configure probes to
|
||||
capture packets with header or data on the data request response network,
|
||||
or as traffic debug or statistic collectors. Exynos542x bus has multiple
|
||||
NoC probes to provide bandwidth information about behavior of the SoC
|
||||
that you can use while analyzing system performance.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "samsung,exynos5420-nocp"
|
||||
- reg: physical base address of each NoC Probe and length of memory mapped region.
|
||||
|
||||
Optional properties:
|
||||
- clock-names : the name of clock used by the NoC Probe, "nocp"
|
||||
- clocks : phandles for clock specified in "clock-names" property
|
||||
|
||||
Example : NoC Probe nodes in Device Tree are listed below.
|
||||
|
||||
nocp_mem0_0: nocp@10CA1000 {
|
||||
compatible = "samsung,exynos5420-nocp";
|
||||
reg = <0x10CA1000 0x200>;
|
||||
};
|
|
@ -0,0 +1,409 @@
|
|||
* Generic Exynos Bus frequency device
|
||||
|
||||
The Samsung Exynos SoC has many buses for data transfer between DRAM
|
||||
and sub-blocks in SoC. Most Exynos SoCs share the common architecture
|
||||
for buses. Generally, each bus of Exynos SoC includes a source clock
|
||||
and a power line, which are able to change the clock frequency
|
||||
of the bus in runtime. To monitor the usage of each bus in runtime,
|
||||
the driver uses the PPMU (Platform Performance Monitoring Unit), which
|
||||
is able to measure the current load of sub-blocks.
|
||||
|
||||
The Exynos SoC includes the various sub-blocks which have the each AXI bus.
|
||||
The each AXI bus has the owned source clock but, has not the only owned
|
||||
power line. The power line might be shared among one more sub-blocks.
|
||||
So, we can divide into two type of device as the role of each sub-block.
|
||||
There are two type of bus devices as following:
|
||||
- parent bus device
|
||||
- passive bus device
|
||||
|
||||
Basically, parent and passive bus device share the same power line.
|
||||
The parent bus device can only change the voltage of shared power line
|
||||
and the rest bus devices (passive bus device) depend on the decision of
|
||||
the parent bus device. If there are three blocks which share the VDD_xxx
|
||||
power line, Only one block should be parent device and then the rest blocks
|
||||
should depend on the parent device as passive device.
|
||||
|
||||
VDD_xxx |--- A block (parent)
|
||||
|--- B block (passive)
|
||||
|--- C block (passive)
|
||||
|
||||
There are a little different composition among Exynos SoC because each Exynos
|
||||
SoC has different sub-blocks. Therefore, such difference should be specified
|
||||
in devicetree file instead of each device driver. In result, this driver
|
||||
is able to support the bus frequency for all Exynos SoCs.
|
||||
|
||||
Required properties for all bus devices:
|
||||
- compatible: Should be "samsung,exynos-bus".
|
||||
- clock-names : the name of clock used by the bus, "bus".
|
||||
- clocks : phandles for clock specified in "clock-names" property.
|
||||
- operating-points-v2: the OPP table including frequency/voltage information
|
||||
to support DVFS (Dynamic Voltage/Frequency Scaling) feature.
|
||||
|
||||
Required properties only for parent bus device:
|
||||
- vdd-supply: the regulator to provide the buses with the voltage.
|
||||
- devfreq-events: the devfreq-event device to monitor the current utilization
|
||||
of buses.
|
||||
|
||||
Required properties only for passive bus device:
|
||||
- devfreq: the parent bus device.
|
||||
|
||||
Optional properties only for parent bus device:
|
||||
- exynos,saturation-ratio: the percentage value which is used to calibrate
|
||||
the performance count against total cycle count.
|
||||
- exynos,voltage-tolerance: the percentage value for bus voltage tolerance
|
||||
which is used to calculate the max voltage.
|
||||
|
||||
Detailed correlation between sub-blocks and power line according to Exynos SoC:
|
||||
- In case of Exynos3250, there are two power line as following:
|
||||
VDD_MIF |--- DMC
|
||||
|
||||
VDD_INT |--- LEFTBUS (parent device)
|
||||
|--- PERIL
|
||||
|--- MFC
|
||||
|--- G3D
|
||||
|--- RIGHTBUS
|
||||
|--- PERIR
|
||||
|--- FSYS
|
||||
|--- LCD0
|
||||
|--- PERIR
|
||||
|--- ISP
|
||||
|--- CAM
|
||||
|
||||
- In case of Exynos4210, there is one power line as following:
|
||||
VDD_INT |--- DMC (parent device)
|
||||
|--- LEFTBUS
|
||||
|--- PERIL
|
||||
|--- MFC(L)
|
||||
|--- G3D
|
||||
|--- TV
|
||||
|--- LCD0
|
||||
|--- RIGHTBUS
|
||||
|--- PERIR
|
||||
|--- MFC(R)
|
||||
|--- CAM
|
||||
|--- FSYS
|
||||
|--- GPS
|
||||
|--- LCD0
|
||||
|--- LCD1
|
||||
|
||||
- In case of Exynos4x12, there are two power line as following:
|
||||
VDD_MIF |--- DMC
|
||||
|
||||
VDD_INT |--- LEFTBUS (parent device)
|
||||
|--- PERIL
|
||||
|--- MFC(L)
|
||||
|--- G3D
|
||||
|--- TV
|
||||
|--- IMAGE
|
||||
|--- RIGHTBUS
|
||||
|--- PERIR
|
||||
|--- MFC(R)
|
||||
|--- CAM
|
||||
|--- FSYS
|
||||
|--- GPS
|
||||
|--- LCD0
|
||||
|--- ISP
|
||||
|
||||
- In case of Exynos5422, there are two power line as following:
|
||||
VDD_MIF |--- DREX 0 (parent device, DRAM EXpress controller)
|
||||
|--- DREX 1
|
||||
|
||||
VDD_INT |--- NoC_Core (parent device)
|
||||
|--- G2D
|
||||
|--- G3D
|
||||
|--- DISP1
|
||||
|--- NoC_WCORE
|
||||
|--- GSCL
|
||||
|--- MSCL
|
||||
|--- ISP
|
||||
|--- MFC
|
||||
|--- GEN
|
||||
|--- PERIS
|
||||
|--- PERIC
|
||||
|--- FSYS
|
||||
|--- FSYS2
|
||||
|
||||
Example1:
|
||||
Show the AXI buses of Exynos3250 SoC. Exynos3250 divides the buses to
|
||||
power line (regulator). The MIF (Memory Interface) AXI bus is used to
|
||||
transfer data between DRAM and CPU and uses the VDD_MIF regulator.
|
||||
|
||||
- MIF (Memory Interface) block
|
||||
: VDD_MIF |--- DMC (Dynamic Memory Controller)
|
||||
|
||||
- INT (Internal) block
|
||||
: VDD_INT |--- LEFTBUS (parent device)
|
||||
|--- PERIL
|
||||
|--- MFC
|
||||
|--- G3D
|
||||
|--- RIGHTBUS
|
||||
|--- FSYS
|
||||
|--- LCD0
|
||||
|--- PERIR
|
||||
|--- ISP
|
||||
|--- CAM
|
||||
|
||||
- MIF bus's frequency/voltage table
|
||||
-----------------------
|
||||
|Lv| Freq | Voltage |
|
||||
-----------------------
|
||||
|L1| 50000 |800000 |
|
||||
|L2| 100000 |800000 |
|
||||
|L3| 134000 |800000 |
|
||||
|L4| 200000 |825000 |
|
||||
|L5| 400000 |875000 |
|
||||
-----------------------
|
||||
|
||||
- INT bus's frequency/voltage table
|
||||
----------------------------------------------------------
|
||||
|Block|LEFTBUS|RIGHTBUS|MCUISP |ISP |PERIL ||VDD_INT |
|
||||
| name| |LCD0 | | | || |
|
||||
| | |FSYS | | | || |
|
||||
| | |MFC | | | || |
|
||||
----------------------------------------------------------
|
||||
|Mode |*parent|passive |passive|passive|passive|| |
|
||||
----------------------------------------------------------
|
||||
|Lv |Frequency ||Voltage |
|
||||
----------------------------------------------------------
|
||||
|L1 |50000 |50000 |50000 |50000 |50000 ||900000 |
|
||||
|L2 |80000 |80000 |80000 |80000 |80000 ||900000 |
|
||||
|L3 |100000 |100000 |100000 |100000 |100000 ||1000000 |
|
||||
|L4 |134000 |134000 |200000 |200000 | ||1000000 |
|
||||
|L5 |200000 |200000 |400000 |300000 | ||1000000 |
|
||||
----------------------------------------------------------
|
||||
|
||||
Example2 :
|
||||
The bus of DMC (Dynamic Memory Controller) block in exynos3250.dtsi
|
||||
is listed below:
|
||||
|
||||
bus_dmc: bus_dmc {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu_dmc CLK_DIV_DMC>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_dmc_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_dmc_opp_table: opp_table1 {
|
||||
compatible = "operating-points-v2";
|
||||
opp-shared;
|
||||
|
||||
opp@50000000 {
|
||||
opp-hz = /bits/ 64 <50000000>;
|
||||
opp-microvolt = <800000>;
|
||||
};
|
||||
opp@100000000 {
|
||||
opp-hz = /bits/ 64 <100000000>;
|
||||
opp-microvolt = <800000>;
|
||||
};
|
||||
opp@134000000 {
|
||||
opp-hz = /bits/ 64 <134000000>;
|
||||
opp-microvolt = <800000>;
|
||||
};
|
||||
opp@200000000 {
|
||||
opp-hz = /bits/ 64 <200000000>;
|
||||
opp-microvolt = <825000>;
|
||||
};
|
||||
opp@400000000 {
|
||||
opp-hz = /bits/ 64 <400000000>;
|
||||
opp-microvolt = <875000>;
|
||||
};
|
||||
};
|
||||
|
||||
bus_leftbus: bus_leftbus {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_DIV_GDL>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_leftbus_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_rightbus: bus_rightbus {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_DIV_GDR>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_leftbus_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_lcd0: bus_lcd0 {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_DIV_ACLK_160>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_leftbus_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_fsys: bus_fsys {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_DIV_ACLK_200>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_leftbus_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_mcuisp: bus_mcuisp {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_DIV_ACLK_400_MCUISP>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_mcuisp_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_isp: bus_isp {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_DIV_ACLK_266>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_isp_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_peril: bus_peril {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_DIV_ACLK_100>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_peril_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_mfc: bus_mfc {
|
||||
compatible = "samsung,exynos-bus";
|
||||
clocks = <&cmu CLK_SCLK_MFC>;
|
||||
clock-names = "bus";
|
||||
operating-points-v2 = <&bus_leftbus_opp_table>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
bus_leftbus_opp_table: opp_table1 {
|
||||
compatible = "operating-points-v2";
|
||||
opp-shared;
|
||||
|
||||
opp@50000000 {
|
||||
opp-hz = /bits/ 64 <50000000>;
|
||||
opp-microvolt = <900000>;
|
||||
};
|
||||
opp@80000000 {
|
||||
opp-hz = /bits/ 64 <80000000>;
|
||||
opp-microvolt = <900000>;
|
||||
};
|
||||
opp@100000000 {
|
||||
opp-hz = /bits/ 64 <100000000>;
|
||||
opp-microvolt = <1000000>;
|
||||
};
|
||||
opp@134000000 {
|
||||
opp-hz = /bits/ 64 <134000000>;
|
||||
opp-microvolt = <1000000>;
|
||||
};
|
||||
opp@200000000 {
|
||||
opp-hz = /bits/ 64 <200000000>;
|
||||
opp-microvolt = <1000000>;
|
||||
};
|
||||
};
|
||||
|
||||
bus_mcuisp_opp_table: opp_table2 {
|
||||
compatible = "operating-points-v2";
|
||||
opp-shared;
|
||||
|
||||
opp@50000000 {
|
||||
opp-hz = /bits/ 64 <50000000>;
|
||||
};
|
||||
opp@80000000 {
|
||||
opp-hz = /bits/ 64 <80000000>;
|
||||
};
|
||||
opp@100000000 {
|
||||
opp-hz = /bits/ 64 <100000000>;
|
||||
};
|
||||
opp@200000000 {
|
||||
opp-hz = /bits/ 64 <200000000>;
|
||||
};
|
||||
opp@400000000 {
|
||||
opp-hz = /bits/ 64 <400000000>;
|
||||
};
|
||||
};
|
||||
|
||||
bus_isp_opp_table: opp_table3 {
|
||||
compatible = "operating-points-v2";
|
||||
opp-shared;
|
||||
|
||||
opp@50000000 {
|
||||
opp-hz = /bits/ 64 <50000000>;
|
||||
};
|
||||
opp@80000000 {
|
||||
opp-hz = /bits/ 64 <80000000>;
|
||||
};
|
||||
opp@100000000 {
|
||||
opp-hz = /bits/ 64 <100000000>;
|
||||
};
|
||||
opp@200000000 {
|
||||
opp-hz = /bits/ 64 <200000000>;
|
||||
};
|
||||
opp@300000000 {
|
||||
opp-hz = /bits/ 64 <300000000>;
|
||||
};
|
||||
};
|
||||
|
||||
bus_peril_opp_table: opp_table4 {
|
||||
compatible = "operating-points-v2";
|
||||
opp-shared;
|
||||
|
||||
opp@50000000 {
|
||||
opp-hz = /bits/ 64 <50000000>;
|
||||
};
|
||||
opp@80000000 {
|
||||
opp-hz = /bits/ 64 <80000000>;
|
||||
};
|
||||
opp@100000000 {
|
||||
opp-hz = /bits/ 64 <100000000>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Usage case to handle the frequency and voltage of bus on runtime
|
||||
in exynos3250-rinato.dts is listed below:
|
||||
|
||||
&bus_dmc {
|
||||
devfreq-events = <&ppmu_dmc0_3>, <&ppmu_dmc1_3>;
|
||||
vdd-supply = <&buck1_reg>; /* VDD_MIF */
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_leftbus {
|
||||
devfreq-events = <&ppmu_leftbus_3>, <&ppmu_rightbus_3>;
|
||||
vdd-supply = <&buck3_reg>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_rightbus {
|
||||
devfreq = <&bus_leftbus>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_lcd0 {
|
||||
devfreq = <&bus_leftbus>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_fsys {
|
||||
devfreq = <&bus_leftbus>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_mcuisp {
|
||||
devfreq = <&bus_leftbus>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_isp {
|
||||
devfreq = <&bus_leftbus>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_peril {
|
||||
devfreq = <&bus_leftbus>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&bus_mfc {
|
||||
devfreq = <&bus_leftbus>;
|
||||
status = "okay";
|
||||
};
|
|
@ -3540,6 +3540,15 @@ F: drivers/devfreq/devfreq-event.c
|
|||
F: include/linux/devfreq-event.h
|
||||
F: Documentation/devicetree/bindings/devfreq/event/
|
||||
|
||||
BUS FREQUENCY DRIVER FOR SAMSUNG EXYNOS
|
||||
M: Chanwoo Choi <cw00.choi@samsung.com>
|
||||
L: linux-pm@vger.kernel.org
|
||||
L: linux-samsung-soc@vger.kernel.org
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mzx/devfreq.git
|
||||
S: Maintained
|
||||
F: drivers/devfreq/exynos-bus.c
|
||||
F: Documentation/devicetree/bindings/devfreq/exynos-bus.txt
|
||||
|
||||
DEVICE NUMBER REGISTRY
|
||||
M: Torben Mathiasen <device@lanana.org>
|
||||
W: http://lanana.org/docs/device-list/index.html
|
||||
|
|
|
@ -64,30 +64,32 @@ config DEVFREQ_GOV_USERSPACE
|
|||
Otherwise, the governor does not change the frequency
|
||||
given at the initialization.
|
||||
|
||||
config DEVFREQ_GOV_PASSIVE
|
||||
tristate "Passive"
|
||||
help
|
||||
Sets the frequency based on the frequency of its parent devfreq
|
||||
device. This governor does not change the frequency by itself
|
||||
through sysfs entries. The passive governor recommends that
|
||||
devfreq device uses the OPP table to get the frequency/voltage.
|
||||
|
||||
comment "DEVFREQ Drivers"
|
||||
|
||||
config ARM_EXYNOS4_BUS_DEVFREQ
|
||||
bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver"
|
||||
depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM
|
||||
config ARM_EXYNOS_BUS_DEVFREQ
|
||||
bool "ARM EXYNOS Generic Memory Bus DEVFREQ Driver"
|
||||
depends on ARCH_EXYNOS
|
||||
select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
||||
select DEVFREQ_GOV_PASSIVE
|
||||
select DEVFREQ_EVENT_EXYNOS_PPMU
|
||||
select PM_DEVFREQ_EVENT
|
||||
select PM_OPP
|
||||
help
|
||||
This adds the DEVFREQ driver for Exynos4210 memory bus (vdd_int)
|
||||
and Exynos4212/4412 memory interface and bus (vdd_mif + vdd_int).
|
||||
It reads PPMU counters of memory controllers and adjusts
|
||||
the operating frequencies and voltages with OPP support.
|
||||
This adds the common DEVFREQ driver for Exynos Memory bus. Exynos
|
||||
Memory bus has one more group of memory bus (e.g, MIF and INT block).
|
||||
Each memory bus group could contain many memoby bus block. It reads
|
||||
PPMU counters of memory controllers by using DEVFREQ-event device
|
||||
and adjusts the operating frequencies and voltages with OPP support.
|
||||
This does not yet operate with optimal voltages.
|
||||
|
||||
config ARM_EXYNOS5_BUS_DEVFREQ
|
||||
tristate "ARM Exynos5250 Bus DEVFREQ Driver"
|
||||
depends on SOC_EXYNOS5250
|
||||
select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
||||
select PM_OPP
|
||||
help
|
||||
This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int).
|
||||
It reads PPMU counters of memory controllers and adjusts the
|
||||
operating frequencies and voltages with OPP support.
|
||||
|
||||
config ARM_TEGRA_DEVFREQ
|
||||
tristate "Tegra DEVFREQ Driver"
|
||||
depends on ARCH_TEGRA_124_SOC
|
||||
|
|
|
@ -4,10 +4,10 @@ obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) += governor_simpleondemand.o
|
|||
obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
|
||||
obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
|
||||
obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
|
||||
obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o
|
||||
|
||||
# DEVFREQ Drivers
|
||||
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/
|
||||
obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/
|
||||
obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
|
||||
obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o
|
||||
|
||||
# DEVFREQ Event Drivers
|
||||
|
|
|
@ -234,6 +234,11 @@ struct devfreq_event_dev *devfreq_event_get_edev_by_phandle(struct device *dev,
|
|||
return ERR_PTR(-ENODEV);
|
||||
|
||||
mutex_lock(&devfreq_event_list_lock);
|
||||
list_for_each_entry(edev, &devfreq_event_list, node) {
|
||||
if (edev->dev.parent && edev->dev.parent->of_node == node)
|
||||
goto out;
|
||||
}
|
||||
|
||||
list_for_each_entry(edev, &devfreq_event_list, node) {
|
||||
if (!strcmp(edev->desc->name, node->name))
|
||||
goto out;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <linux/list.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/of.h>
|
||||
#include "governor.h"
|
||||
|
||||
static struct class *devfreq_class;
|
||||
|
@ -188,6 +189,29 @@ static struct devfreq_governor *find_devfreq_governor(const char *name)
|
|||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static int devfreq_notify_transition(struct devfreq *devfreq,
|
||||
struct devfreq_freqs *freqs, unsigned int state)
|
||||
{
|
||||
if (!devfreq)
|
||||
return -EINVAL;
|
||||
|
||||
switch (state) {
|
||||
case DEVFREQ_PRECHANGE:
|
||||
srcu_notifier_call_chain(&devfreq->transition_notifier_list,
|
||||
DEVFREQ_PRECHANGE, freqs);
|
||||
break;
|
||||
|
||||
case DEVFREQ_POSTCHANGE:
|
||||
srcu_notifier_call_chain(&devfreq->transition_notifier_list,
|
||||
DEVFREQ_POSTCHANGE, freqs);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Load monitoring helper functions for governors use */
|
||||
|
||||
/**
|
||||
|
@ -199,7 +223,8 @@ static struct devfreq_governor *find_devfreq_governor(const char *name)
|
|||
*/
|
||||
int update_devfreq(struct devfreq *devfreq)
|
||||
{
|
||||
unsigned long freq;
|
||||
struct devfreq_freqs freqs;
|
||||
unsigned long freq, cur_freq;
|
||||
int err = 0;
|
||||
u32 flags = 0;
|
||||
|
||||
|
@ -233,10 +258,22 @@ int update_devfreq(struct devfreq *devfreq)
|
|||
flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
|
||||
}
|
||||
|
||||
if (devfreq->profile->get_cur_freq)
|
||||
devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq);
|
||||
else
|
||||
cur_freq = devfreq->previous_freq;
|
||||
|
||||
freqs.old = cur_freq;
|
||||
freqs.new = freq;
|
||||
devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
|
||||
|
||||
err = devfreq->profile->target(devfreq->dev.parent, &freq, flags);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
freqs.new = freq;
|
||||
devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
|
||||
|
||||
if (devfreq->profile->freq_table)
|
||||
if (devfreq_update_status(devfreq, freq))
|
||||
dev_err(&devfreq->dev,
|
||||
|
@ -541,6 +578,8 @@ struct devfreq *devfreq_add_device(struct device *dev,
|
|||
goto err_out;
|
||||
}
|
||||
|
||||
srcu_init_notifier_head(&devfreq->transition_notifier_list);
|
||||
|
||||
mutex_unlock(&devfreq->lock);
|
||||
|
||||
mutex_lock(&devfreq_list_lock);
|
||||
|
@ -639,6 +678,49 @@ struct devfreq *devm_devfreq_add_device(struct device *dev,
|
|||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_add_device);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
/*
|
||||
* devfreq_get_devfreq_by_phandle - Get the devfreq device from devicetree
|
||||
* @dev - instance to the given device
|
||||
* @index - index into list of devfreq
|
||||
*
|
||||
* return the instance of devfreq device
|
||||
*/
|
||||
struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, int index)
|
||||
{
|
||||
struct device_node *node;
|
||||
struct devfreq *devfreq;
|
||||
|
||||
if (!dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (!dev->of_node)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
node = of_parse_phandle(dev->of_node, "devfreq", index);
|
||||
if (!node)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
mutex_lock(&devfreq_list_lock);
|
||||
list_for_each_entry(devfreq, &devfreq_list, node) {
|
||||
if (devfreq->dev.parent
|
||||
&& devfreq->dev.parent->of_node == node) {
|
||||
mutex_unlock(&devfreq_list_lock);
|
||||
return devfreq;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&devfreq_list_lock);
|
||||
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
#else
|
||||
struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, int index)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
#endif /* CONFIG_OF */
|
||||
EXPORT_SYMBOL_GPL(devfreq_get_devfreq_by_phandle);
|
||||
|
||||
/**
|
||||
* devm_devfreq_remove_device() - Resource-managed devfreq_remove_device()
|
||||
* @dev: the device to add devfreq feature.
|
||||
|
@ -1266,6 +1348,129 @@ void devm_devfreq_unregister_opp_notifier(struct device *dev,
|
|||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier);
|
||||
|
||||
/**
|
||||
* devfreq_register_notifier() - Register a driver with devfreq
|
||||
* @devfreq: The devfreq object.
|
||||
* @nb: The notifier block to register.
|
||||
* @list: DEVFREQ_TRANSITION_NOTIFIER.
|
||||
*/
|
||||
int devfreq_register_notifier(struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!devfreq)
|
||||
return -EINVAL;
|
||||
|
||||
switch (list) {
|
||||
case DEVFREQ_TRANSITION_NOTIFIER:
|
||||
ret = srcu_notifier_chain_register(
|
||||
&devfreq->transition_notifier_list, nb);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(devfreq_register_notifier);
|
||||
|
||||
/*
|
||||
* devfreq_unregister_notifier() - Unregister a driver with devfreq
|
||||
* @devfreq: The devfreq object.
|
||||
* @nb: The notifier block to be unregistered.
|
||||
* @list: DEVFREQ_TRANSITION_NOTIFIER.
|
||||
*/
|
||||
int devfreq_unregister_notifier(struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!devfreq)
|
||||
return -EINVAL;
|
||||
|
||||
switch (list) {
|
||||
case DEVFREQ_TRANSITION_NOTIFIER:
|
||||
ret = srcu_notifier_chain_unregister(
|
||||
&devfreq->transition_notifier_list, nb);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(devfreq_unregister_notifier);
|
||||
|
||||
struct devfreq_notifier_devres {
|
||||
struct devfreq *devfreq;
|
||||
struct notifier_block *nb;
|
||||
unsigned int list;
|
||||
};
|
||||
|
||||
static void devm_devfreq_notifier_release(struct device *dev, void *res)
|
||||
{
|
||||
struct devfreq_notifier_devres *this = res;
|
||||
|
||||
devfreq_unregister_notifier(this->devfreq, this->nb, this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_devfreq_register_notifier()
|
||||
- Resource-managed devfreq_register_notifier()
|
||||
* @dev: The devfreq user device. (parent of devfreq)
|
||||
* @devfreq: The devfreq object.
|
||||
* @nb: The notifier block to be unregistered.
|
||||
* @list: DEVFREQ_TRANSITION_NOTIFIER.
|
||||
*/
|
||||
int devm_devfreq_register_notifier(struct device *dev,
|
||||
struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
struct devfreq_notifier_devres *ptr;
|
||||
int ret;
|
||||
|
||||
ptr = devres_alloc(devm_devfreq_notifier_release, sizeof(*ptr),
|
||||
GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devfreq_register_notifier(devfreq, nb, list);
|
||||
if (ret) {
|
||||
devres_free(ptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptr->devfreq = devfreq;
|
||||
ptr->nb = nb;
|
||||
ptr->list = list;
|
||||
devres_add(dev, ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_register_notifier);
|
||||
|
||||
/**
|
||||
* devm_devfreq_unregister_notifier()
|
||||
- Resource-managed devfreq_unregister_notifier()
|
||||
* @dev: The devfreq user device. (parent of devfreq)
|
||||
* @devfreq: The devfreq object.
|
||||
* @nb: The notifier block to be unregistered.
|
||||
* @list: DEVFREQ_TRANSITION_NOTIFIER.
|
||||
*/
|
||||
void devm_devfreq_unregister_notifier(struct device *dev,
|
||||
struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
WARN_ON(devres_release(dev, devm_devfreq_notifier_release,
|
||||
devm_devfreq_dev_match, devfreq));
|
||||
}
|
||||
EXPORT_SYMBOL(devm_devfreq_unregister_notifier);
|
||||
|
||||
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
|
||||
MODULE_DESCRIPTION("devfreq class support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -13,6 +13,14 @@ menuconfig PM_DEVFREQ_EVENT
|
|||
|
||||
if PM_DEVFREQ_EVENT
|
||||
|
||||
config DEVFREQ_EVENT_EXYNOS_NOCP
|
||||
bool "EXYNOS NoC (Network On Chip) Probe DEVFREQ event Driver"
|
||||
depends on ARCH_EXYNOS
|
||||
select PM_OPP
|
||||
help
|
||||
This add the devfreq-event driver for Exynos SoC. It provides NoC
|
||||
(Network on Chip) Probe counters to measure the bandwidth of AXI bus.
|
||||
|
||||
config DEVFREQ_EVENT_EXYNOS_PPMU
|
||||
bool "EXYNOS PPMU (Platform Performance Monitoring Unit) DEVFREQ event Driver"
|
||||
depends on ARCH_EXYNOS
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
# Exynos DEVFREQ Event Drivers
|
||||
|
||||
obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o
|
||||
obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* exynos-nocp.c - EXYNOS NoC (Network On Chip) Probe support
|
||||
*
|
||||
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
|
||||
* Author : Chanwoo Choi <cw00.choi@samsung.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/devfreq-event.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "exynos-nocp.h"
|
||||
|
||||
struct exynos_nocp {
|
||||
struct devfreq_event_dev *edev;
|
||||
struct devfreq_event_desc desc;
|
||||
|
||||
struct device *dev;
|
||||
|
||||
struct regmap *regmap;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
/*
|
||||
* The devfreq-event ops structure for nocp probe.
|
||||
*/
|
||||
static int exynos_nocp_set_event(struct devfreq_event_dev *edev)
|
||||
{
|
||||
struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev);
|
||||
int ret;
|
||||
|
||||
/* Disable NoC probe */
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
|
||||
NOCP_MAIN_CTL_STATEN_MASK, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(nocp->dev, "failed to disable the NoC probe device\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set a statistics dump period to 0 */
|
||||
ret = regmap_write(nocp->regmap, NOCP_STAT_PERIOD, 0x0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* Set the IntEvent fields of *_SRC */
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_SRC,
|
||||
NOCP_CNT_SRC_INTEVENT_MASK,
|
||||
NOCP_CNT_SRC_INTEVENT_BYTE_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_SRC,
|
||||
NOCP_CNT_SRC_INTEVENT_MASK,
|
||||
NOCP_CNT_SRC_INTEVENT_CHAIN_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_SRC,
|
||||
NOCP_CNT_SRC_INTEVENT_MASK,
|
||||
NOCP_CNT_SRC_INTEVENT_CYCLE_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_SRC,
|
||||
NOCP_CNT_SRC_INTEVENT_MASK,
|
||||
NOCP_CNT_SRC_INTEVENT_CHAIN_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
|
||||
/* Set an alarm with a max/min value of 0 to generate StatALARM */
|
||||
ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MIN, 0x0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MAX, 0x0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* Set AlarmMode */
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_ALARM_MODE,
|
||||
NOCP_CNT_ALARM_MODE_MASK,
|
||||
NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_ALARM_MODE,
|
||||
NOCP_CNT_ALARM_MODE_MASK,
|
||||
NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_ALARM_MODE,
|
||||
NOCP_CNT_ALARM_MODE_MASK,
|
||||
NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_ALARM_MODE,
|
||||
NOCP_CNT_ALARM_MODE_MASK,
|
||||
NOCP_CNT_ALARM_MODE_MIN_MAX_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* Enable the measurements by setting AlarmEn and StatEn */
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
|
||||
NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK,
|
||||
NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* Set GlobalEN */
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_CFG_CTL,
|
||||
NOCP_CFG_CTL_GLOBALEN_MASK,
|
||||
NOCP_CFG_CTL_GLOBALEN_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* Enable NoC probe */
|
||||
ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
|
||||
NOCP_MAIN_CTL_STATEN_MASK,
|
||||
NOCP_MAIN_CTL_STATEN_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
/* Reset NoC probe */
|
||||
if (regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL,
|
||||
NOCP_MAIN_CTL_STATEN_MASK, 0)) {
|
||||
dev_err(nocp->dev, "Failed to reset NoC probe device\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_nocp_get_event(struct devfreq_event_dev *edev,
|
||||
struct devfreq_event_data *edata)
|
||||
{
|
||||
struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev);
|
||||
unsigned int counter[4];
|
||||
int ret;
|
||||
|
||||
/* Read cycle count */
|
||||
ret = regmap_read(nocp->regmap, NOCP_COUNTERS_0_VAL, &counter[0]);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_read(nocp->regmap, NOCP_COUNTERS_1_VAL, &counter[1]);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_read(nocp->regmap, NOCP_COUNTERS_2_VAL, &counter[2]);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_read(nocp->regmap, NOCP_COUNTERS_3_VAL, &counter[3]);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
edata->load_count = ((counter[1] << 16) | counter[0]);
|
||||
edata->total_count = ((counter[3] << 16) | counter[2]);
|
||||
|
||||
dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n", edev->desc->name,
|
||||
edata->load_count, edata->total_count);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
edata->load_count = 0;
|
||||
edata->total_count = 0;
|
||||
|
||||
dev_err(nocp->dev, "Failed to read the counter of NoC probe device\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct devfreq_event_ops exynos_nocp_ops = {
|
||||
.set_event = exynos_nocp_set_event,
|
||||
.get_event = exynos_nocp_get_event,
|
||||
};
|
||||
|
||||
static const struct of_device_id exynos_nocp_id_match[] = {
|
||||
{ .compatible = "samsung,exynos5420-nocp", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
static struct regmap_config exynos_nocp_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.max_register = NOCP_COUNTERS_3_VAL,
|
||||
};
|
||||
|
||||
static int exynos_nocp_parse_dt(struct platform_device *pdev,
|
||||
struct exynos_nocp *nocp)
|
||||
{
|
||||
struct device *dev = nocp->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
|
||||
if (!np) {
|
||||
dev_err(dev, "failed to find devicetree node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
nocp->clk = devm_clk_get(dev, "nocp");
|
||||
if (IS_ERR(nocp->clk))
|
||||
nocp->clk = NULL;
|
||||
|
||||
/* Maps the memory mapped IO to control nocp register */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (IS_ERR(res))
|
||||
return PTR_ERR(res);
|
||||
|
||||
base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
exynos_nocp_regmap_config.max_register = resource_size(res) - 4;
|
||||
|
||||
nocp->regmap = devm_regmap_init_mmio(dev, base,
|
||||
&exynos_nocp_regmap_config);
|
||||
if (IS_ERR(nocp->regmap)) {
|
||||
dev_err(dev, "failed to initialize regmap\n");
|
||||
return PTR_ERR(nocp->regmap);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_nocp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct exynos_nocp *nocp;
|
||||
int ret;
|
||||
|
||||
nocp = devm_kzalloc(&pdev->dev, sizeof(*nocp), GFP_KERNEL);
|
||||
if (!nocp)
|
||||
return -ENOMEM;
|
||||
|
||||
nocp->dev = &pdev->dev;
|
||||
|
||||
/* Parse dt data to get resource */
|
||||
ret = exynos_nocp_parse_dt(pdev, nocp);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to parse devicetree for resource\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Add devfreq-event device to measure the bandwidth of NoC */
|
||||
nocp->desc.ops = &exynos_nocp_ops;
|
||||
nocp->desc.driver_data = nocp;
|
||||
nocp->desc.name = np->full_name;
|
||||
nocp->edev = devm_devfreq_event_add_edev(&pdev->dev, &nocp->desc);
|
||||
if (IS_ERR(nocp->edev)) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to add devfreq-event device\n");
|
||||
return PTR_ERR(nocp->edev);
|
||||
}
|
||||
platform_set_drvdata(pdev, nocp);
|
||||
|
||||
clk_prepare_enable(nocp->clk);
|
||||
|
||||
pr_info("exynos-nocp: new NoC Probe device registered: %s\n",
|
||||
dev_name(dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_nocp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_nocp *nocp = platform_get_drvdata(pdev);
|
||||
|
||||
clk_disable_unprepare(nocp->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver exynos_nocp_driver = {
|
||||
.probe = exynos_nocp_probe,
|
||||
.remove = exynos_nocp_remove,
|
||||
.driver = {
|
||||
.name = "exynos-nocp",
|
||||
.of_match_table = exynos_nocp_id_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(exynos_nocp_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Exynos NoC (Network on Chip) Probe driver");
|
||||
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* exynos-nocp.h - EXYNOS NoC (Network on Chip) Probe header file
|
||||
*
|
||||
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
|
||||
* Author : Chanwoo Choi <cw00.choi@samsung.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __EXYNOS_NOCP_H__
|
||||
#define __EXYNOS_NOCP_H__
|
||||
|
||||
enum nocp_reg {
|
||||
NOCP_ID_REVISION_ID = 0x04,
|
||||
NOCP_MAIN_CTL = 0x08,
|
||||
NOCP_CFG_CTL = 0x0C,
|
||||
|
||||
NOCP_STAT_PERIOD = 0x24,
|
||||
NOCP_STAT_GO = 0x28,
|
||||
NOCP_STAT_ALARM_MIN = 0x2C,
|
||||
NOCP_STAT_ALARM_MAX = 0x30,
|
||||
NOCP_STAT_ALARM_STATUS = 0x34,
|
||||
NOCP_STAT_ALARM_CLR = 0x38,
|
||||
|
||||
NOCP_COUNTERS_0_SRC = 0x138,
|
||||
NOCP_COUNTERS_0_ALARM_MODE = 0x13C,
|
||||
NOCP_COUNTERS_0_VAL = 0x140,
|
||||
|
||||
NOCP_COUNTERS_1_SRC = 0x14C,
|
||||
NOCP_COUNTERS_1_ALARM_MODE = 0x150,
|
||||
NOCP_COUNTERS_1_VAL = 0x154,
|
||||
|
||||
NOCP_COUNTERS_2_SRC = 0x160,
|
||||
NOCP_COUNTERS_2_ALARM_MODE = 0x164,
|
||||
NOCP_COUNTERS_2_VAL = 0x168,
|
||||
|
||||
NOCP_COUNTERS_3_SRC = 0x174,
|
||||
NOCP_COUNTERS_3_ALARM_MODE = 0x178,
|
||||
NOCP_COUNTERS_3_VAL = 0x17C,
|
||||
};
|
||||
|
||||
/* NOCP_MAIN_CTL register */
|
||||
#define NOCP_MAIN_CTL_ERREN_MASK BIT(0)
|
||||
#define NOCP_MAIN_CTL_TRACEEN_MASK BIT(1)
|
||||
#define NOCP_MAIN_CTL_PAYLOADEN_MASK BIT(2)
|
||||
#define NOCP_MAIN_CTL_STATEN_MASK BIT(3)
|
||||
#define NOCP_MAIN_CTL_ALARMEN_MASK BIT(4)
|
||||
#define NOCP_MAIN_CTL_STATCONDDUMP_MASK BIT(5)
|
||||
#define NOCP_MAIN_CTL_INTRUSIVEMODE_MASK BIT(6)
|
||||
|
||||
/* NOCP_CFG_CTL register */
|
||||
#define NOCP_CFG_CTL_GLOBALEN_MASK BIT(0)
|
||||
#define NOCP_CFG_CTL_ACTIVE_MASK BIT(1)
|
||||
|
||||
/* NOCP_COUNTERS_x_SRC register */
|
||||
#define NOCP_CNT_SRC_INTEVENT_SHIFT 0
|
||||
#define NOCP_CNT_SRC_INTEVENT_MASK (0x1F << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_OFF_MASK (0x0 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_CYCLE_MASK (0x1 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_IDLE_MASK (0x2 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_XFER_MASK (0x3 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_BUSY_MASK (0x4 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_WAIT_MASK (0x5 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_PKT_MASK (0x6 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_BYTE_MASK (0x8 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
#define NOCP_CNT_SRC_INTEVENT_CHAIN_MASK (0x10 << NOCP_CNT_SRC_INTEVENT_SHIFT)
|
||||
|
||||
/* NOCP_COUNTERS_x_ALARM_MODE register */
|
||||
#define NOCP_CNT_ALARM_MODE_SHIFT 0
|
||||
#define NOCP_CNT_ALARM_MODE_MASK (0x3 << NOCP_CNT_ALARM_MODE_SHIFT)
|
||||
#define NOCP_CNT_ALARM_MODE_OFF_MASK (0x0 << NOCP_CNT_ALARM_MODE_SHIFT)
|
||||
#define NOCP_CNT_ALARM_MODE_MIN_MASK (0x1 << NOCP_CNT_ALARM_MODE_SHIFT)
|
||||
#define NOCP_CNT_ALARM_MODE_MAX_MASK (0x2 << NOCP_CNT_ALARM_MODE_SHIFT)
|
||||
#define NOCP_CNT_ALARM_MODE_MIN_MAX_MASK (0x3 << NOCP_CNT_ALARM_MODE_SHIFT)
|
||||
|
||||
#endif /* __EXYNOS_NOCP_H__ */
|
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
* Generic Exynos Bus frequency driver with DEVFREQ Framework
|
||||
*
|
||||
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
|
||||
* Author : Chanwoo Choi <cw00.choi@samsung.com>
|
||||
*
|
||||
* This driver support Exynos Bus frequency feature by using
|
||||
* DEVFREQ framework and is based on drivers/devfreq/exynos/exynos4_bus.c.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/devfreq.h>
|
||||
#include <linux/devfreq-event.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define DEFAULT_SATURATION_RATIO 40
|
||||
#define DEFAULT_VOLTAGE_TOLERANCE 2
|
||||
|
||||
struct exynos_bus {
|
||||
struct device *dev;
|
||||
|
||||
struct devfreq *devfreq;
|
||||
struct devfreq_event_dev **edev;
|
||||
unsigned int edev_count;
|
||||
struct mutex lock;
|
||||
|
||||
struct dev_pm_opp *curr_opp;
|
||||
|
||||
struct regulator *regulator;
|
||||
struct clk *clk;
|
||||
unsigned int voltage_tolerance;
|
||||
unsigned int ratio;
|
||||
};
|
||||
|
||||
/*
|
||||
* Control the devfreq-event device to get the current state of bus
|
||||
*/
|
||||
#define exynos_bus_ops_edev(ops) \
|
||||
static int exynos_bus_##ops(struct exynos_bus *bus) \
|
||||
{ \
|
||||
int i, ret; \
|
||||
\
|
||||
for (i = 0; i < bus->edev_count; i++) { \
|
||||
if (!bus->edev[i]) \
|
||||
continue; \
|
||||
ret = devfreq_event_##ops(bus->edev[i]); \
|
||||
if (ret < 0) \
|
||||
return ret; \
|
||||
} \
|
||||
\
|
||||
return 0; \
|
||||
}
|
||||
exynos_bus_ops_edev(enable_edev);
|
||||
exynos_bus_ops_edev(disable_edev);
|
||||
exynos_bus_ops_edev(set_event);
|
||||
|
||||
static int exynos_bus_get_event(struct exynos_bus *bus,
|
||||
struct devfreq_event_data *edata)
|
||||
{
|
||||
struct devfreq_event_data event_data;
|
||||
unsigned long load_count = 0, total_count = 0;
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < bus->edev_count; i++) {
|
||||
if (!bus->edev[i])
|
||||
continue;
|
||||
|
||||
ret = devfreq_event_get_event(bus->edev[i], &event_data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (i == 0 || event_data.load_count > load_count) {
|
||||
load_count = event_data.load_count;
|
||||
total_count = event_data.total_count;
|
||||
}
|
||||
}
|
||||
|
||||
edata->load_count = load_count;
|
||||
edata->total_count = total_count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Must necessary function for devfreq simple-ondemand governor
|
||||
*/
|
||||
static int exynos_bus_target(struct device *dev, unsigned long *freq, u32 flags)
|
||||
{
|
||||
struct exynos_bus *bus = dev_get_drvdata(dev);
|
||||
struct dev_pm_opp *new_opp;
|
||||
unsigned long old_freq, new_freq, old_volt, new_volt, tol;
|
||||
int ret = 0;
|
||||
|
||||
/* Get new opp-bus instance according to new bus clock */
|
||||
rcu_read_lock();
|
||||
new_opp = devfreq_recommended_opp(dev, freq, flags);
|
||||
if (IS_ERR(new_opp)) {
|
||||
dev_err(dev, "failed to get recommended opp instance\n");
|
||||
rcu_read_unlock();
|
||||
return PTR_ERR(new_opp);
|
||||
}
|
||||
|
||||
new_freq = dev_pm_opp_get_freq(new_opp);
|
||||
new_volt = dev_pm_opp_get_voltage(new_opp);
|
||||
old_freq = dev_pm_opp_get_freq(bus->curr_opp);
|
||||
old_volt = dev_pm_opp_get_voltage(bus->curr_opp);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (old_freq == new_freq)
|
||||
return 0;
|
||||
tol = new_volt * bus->voltage_tolerance / 100;
|
||||
|
||||
/* Change voltage and frequency according to new OPP level */
|
||||
mutex_lock(&bus->lock);
|
||||
|
||||
if (old_freq < new_freq) {
|
||||
ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol);
|
||||
if (ret < 0) {
|
||||
dev_err(bus->dev, "failed to set voltage\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_set_rate(bus->clk, new_freq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to change clock of bus\n");
|
||||
clk_set_rate(bus->clk, old_freq);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (old_freq > new_freq) {
|
||||
ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol);
|
||||
if (ret < 0) {
|
||||
dev_err(bus->dev, "failed to set voltage\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
bus->curr_opp = new_opp;
|
||||
|
||||
dev_dbg(dev, "Set the frequency of bus (%lukHz -> %lukHz)\n",
|
||||
old_freq/1000, new_freq/1000);
|
||||
out:
|
||||
mutex_unlock(&bus->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_bus_get_dev_status(struct device *dev,
|
||||
struct devfreq_dev_status *stat)
|
||||
{
|
||||
struct exynos_bus *bus = dev_get_drvdata(dev);
|
||||
struct devfreq_event_data edata;
|
||||
int ret;
|
||||
|
||||
rcu_read_lock();
|
||||
stat->current_frequency = dev_pm_opp_get_freq(bus->curr_opp);
|
||||
rcu_read_unlock();
|
||||
|
||||
ret = exynos_bus_get_event(bus, &edata);
|
||||
if (ret < 0) {
|
||||
stat->total_time = stat->busy_time = 0;
|
||||
goto err;
|
||||
}
|
||||
|
||||
stat->busy_time = (edata.load_count * 100) / bus->ratio;
|
||||
stat->total_time = edata.total_count;
|
||||
|
||||
dev_dbg(dev, "Usage of devfreq-event : %lu/%lu\n", stat->busy_time,
|
||||
stat->total_time);
|
||||
|
||||
err:
|
||||
ret = exynos_bus_set_event(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to set event to devfreq-event devices\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void exynos_bus_exit(struct device *dev)
|
||||
{
|
||||
struct exynos_bus *bus = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = exynos_bus_disable_edev(bus);
|
||||
if (ret < 0)
|
||||
dev_warn(dev, "failed to disable the devfreq-event devices\n");
|
||||
|
||||
if (bus->regulator)
|
||||
regulator_disable(bus->regulator);
|
||||
|
||||
dev_pm_opp_of_remove_table(dev);
|
||||
clk_disable_unprepare(bus->clk);
|
||||
}
|
||||
|
||||
/*
|
||||
* Must necessary function for devfreq passive governor
|
||||
*/
|
||||
static int exynos_bus_passive_target(struct device *dev, unsigned long *freq,
|
||||
u32 flags)
|
||||
{
|
||||
struct exynos_bus *bus = dev_get_drvdata(dev);
|
||||
struct dev_pm_opp *new_opp;
|
||||
unsigned long old_freq, new_freq;
|
||||
int ret = 0;
|
||||
|
||||
/* Get new opp-bus instance according to new bus clock */
|
||||
rcu_read_lock();
|
||||
new_opp = devfreq_recommended_opp(dev, freq, flags);
|
||||
if (IS_ERR(new_opp)) {
|
||||
dev_err(dev, "failed to get recommended opp instance\n");
|
||||
rcu_read_unlock();
|
||||
return PTR_ERR(new_opp);
|
||||
}
|
||||
|
||||
new_freq = dev_pm_opp_get_freq(new_opp);
|
||||
old_freq = dev_pm_opp_get_freq(bus->curr_opp);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (old_freq == new_freq)
|
||||
return 0;
|
||||
|
||||
/* Change the frequency according to new OPP level */
|
||||
mutex_lock(&bus->lock);
|
||||
|
||||
ret = clk_set_rate(bus->clk, new_freq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to set the clock of bus\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
*freq = new_freq;
|
||||
bus->curr_opp = new_opp;
|
||||
|
||||
dev_dbg(dev, "Set the frequency of bus (%lukHz -> %lukHz)\n",
|
||||
old_freq/1000, new_freq/1000);
|
||||
out:
|
||||
mutex_unlock(&bus->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void exynos_bus_passive_exit(struct device *dev)
|
||||
{
|
||||
struct exynos_bus *bus = dev_get_drvdata(dev);
|
||||
|
||||
dev_pm_opp_of_remove_table(dev);
|
||||
clk_disable_unprepare(bus->clk);
|
||||
}
|
||||
|
||||
static int exynos_bus_parent_parse_of(struct device_node *np,
|
||||
struct exynos_bus *bus)
|
||||
{
|
||||
struct device *dev = bus->dev;
|
||||
int i, ret, count, size;
|
||||
|
||||
/* Get the regulator to provide each bus with the power */
|
||||
bus->regulator = devm_regulator_get(dev, "vdd");
|
||||
if (IS_ERR(bus->regulator)) {
|
||||
dev_err(dev, "failed to get VDD regulator\n");
|
||||
return PTR_ERR(bus->regulator);
|
||||
}
|
||||
|
||||
ret = regulator_enable(bus->regulator);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to enable VDD regulator\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the devfreq-event devices to get the current utilization of
|
||||
* buses. This raw data will be used in devfreq ondemand governor.
|
||||
*/
|
||||
count = devfreq_event_get_edev_count(dev);
|
||||
if (count < 0) {
|
||||
dev_err(dev, "failed to get the count of devfreq-event dev\n");
|
||||
ret = count;
|
||||
goto err_regulator;
|
||||
}
|
||||
bus->edev_count = count;
|
||||
|
||||
size = sizeof(*bus->edev) * count;
|
||||
bus->edev = devm_kzalloc(dev, size, GFP_KERNEL);
|
||||
if (!bus->edev) {
|
||||
ret = -ENOMEM;
|
||||
goto err_regulator;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
bus->edev[i] = devfreq_event_get_edev_by_phandle(dev, i);
|
||||
if (IS_ERR(bus->edev[i])) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto err_regulator;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Optionally, Get the saturation ratio according to Exynos SoC
|
||||
* When measuring the utilization of each AXI bus with devfreq-event
|
||||
* devices, the measured real cycle might be much lower than the
|
||||
* total cycle of bus during sampling rate. In result, the devfreq
|
||||
* simple-ondemand governor might not decide to change the current
|
||||
* frequency due to too utilization (= real cycle/total cycle).
|
||||
* So, this property is used to adjust the utilization when calculating
|
||||
* the busy_time in exynos_bus_get_dev_status().
|
||||
*/
|
||||
if (of_property_read_u32(np, "exynos,saturation-ratio", &bus->ratio))
|
||||
bus->ratio = DEFAULT_SATURATION_RATIO;
|
||||
|
||||
if (of_property_read_u32(np, "exynos,voltage-tolerance",
|
||||
&bus->voltage_tolerance))
|
||||
bus->voltage_tolerance = DEFAULT_VOLTAGE_TOLERANCE;
|
||||
|
||||
return 0;
|
||||
|
||||
err_regulator:
|
||||
regulator_disable(bus->regulator);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_bus_parse_of(struct device_node *np,
|
||||
struct exynos_bus *bus)
|
||||
{
|
||||
struct device *dev = bus->dev;
|
||||
unsigned long rate;
|
||||
int ret;
|
||||
|
||||
/* Get the clock to provide each bus with source clock */
|
||||
bus->clk = devm_clk_get(dev, "bus");
|
||||
if (IS_ERR(bus->clk)) {
|
||||
dev_err(dev, "failed to get bus clock\n");
|
||||
return PTR_ERR(bus->clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(bus->clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to get enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the freq and voltage from OPP table to scale the bus freq */
|
||||
rcu_read_lock();
|
||||
ret = dev_pm_opp_of_add_table(dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to get OPP table\n");
|
||||
rcu_read_unlock();
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
rate = clk_get_rate(bus->clk);
|
||||
bus->curr_opp = devfreq_recommended_opp(dev, &rate, 0);
|
||||
if (IS_ERR(bus->curr_opp)) {
|
||||
dev_err(dev, "failed to find dev_pm_opp\n");
|
||||
rcu_read_unlock();
|
||||
ret = PTR_ERR(bus->curr_opp);
|
||||
goto err_opp;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return 0;
|
||||
|
||||
err_opp:
|
||||
dev_pm_opp_of_remove_table(dev);
|
||||
err_clk:
|
||||
clk_disable_unprepare(bus->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_bus_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct devfreq_dev_profile *profile;
|
||||
struct devfreq_simple_ondemand_data *ondemand_data;
|
||||
struct devfreq_passive_data *passive_data;
|
||||
struct devfreq *parent_devfreq;
|
||||
struct exynos_bus *bus;
|
||||
int ret, max_state;
|
||||
unsigned long min_freq, max_freq;
|
||||
|
||||
if (!np) {
|
||||
dev_err(dev, "failed to find devicetree node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
mutex_init(&bus->lock);
|
||||
bus->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, bus);
|
||||
|
||||
/* Parse the device-tree to get the resource information */
|
||||
ret = exynos_bus_parse_of(np, bus);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL);
|
||||
if (!profile) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (of_parse_phandle(dev->of_node, "devfreq", 0))
|
||||
goto passive;
|
||||
else
|
||||
ret = exynos_bus_parent_parse_of(np, bus);
|
||||
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
/* Initialize the struct profile and governor data for parent device */
|
||||
profile->polling_ms = 50;
|
||||
profile->target = exynos_bus_target;
|
||||
profile->get_dev_status = exynos_bus_get_dev_status;
|
||||
profile->exit = exynos_bus_exit;
|
||||
|
||||
ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
|
||||
if (!ondemand_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
ondemand_data->upthreshold = 40;
|
||||
ondemand_data->downdifferential = 5;
|
||||
|
||||
/* Add devfreq device to monitor and handle the exynos bus */
|
||||
bus->devfreq = devm_devfreq_add_device(dev, profile, "simple_ondemand",
|
||||
ondemand_data);
|
||||
if (IS_ERR(bus->devfreq)) {
|
||||
dev_err(dev, "failed to add devfreq device\n");
|
||||
ret = PTR_ERR(bus->devfreq);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Register opp_notifier to catch the change of OPP */
|
||||
ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to register opp notifier\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable devfreq-event to get raw data which is used to determine
|
||||
* current bus load.
|
||||
*/
|
||||
ret = exynos_bus_enable_edev(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to enable devfreq-event devices\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = exynos_bus_set_event(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to set event to devfreq-event devices\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
goto out;
|
||||
passive:
|
||||
/* Initialize the struct profile and governor data for passive device */
|
||||
profile->target = exynos_bus_passive_target;
|
||||
profile->exit = exynos_bus_passive_exit;
|
||||
|
||||
/* Get the instance of parent devfreq device */
|
||||
parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
|
||||
if (IS_ERR(parent_devfreq)) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto err;
|
||||
}
|
||||
|
||||
passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL);
|
||||
if (!passive_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
passive_data->parent = parent_devfreq;
|
||||
|
||||
/* Add devfreq device for exynos bus with passive governor */
|
||||
bus->devfreq = devm_devfreq_add_device(dev, profile, "passive",
|
||||
passive_data);
|
||||
if (IS_ERR(bus->devfreq)) {
|
||||
dev_err(dev,
|
||||
"failed to add devfreq dev with passive governor\n");
|
||||
ret = -EPROBE_DEFER;
|
||||
goto err;
|
||||
}
|
||||
|
||||
out:
|
||||
max_state = bus->devfreq->profile->max_state;
|
||||
min_freq = (bus->devfreq->profile->freq_table[0] / 1000);
|
||||
max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000);
|
||||
pr_info("exynos-bus: new bus device registered: %s (%6ld KHz ~ %6ld KHz)\n",
|
||||
dev_name(dev), min_freq, max_freq);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
dev_pm_opp_of_remove_table(dev);
|
||||
clk_disable_unprepare(bus->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int exynos_bus_resume(struct device *dev)
|
||||
{
|
||||
struct exynos_bus *bus = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = exynos_bus_enable_edev(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to enable the devfreq-event devices\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_bus_suspend(struct device *dev)
|
||||
{
|
||||
struct exynos_bus *bus = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = exynos_bus_disable_edev(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to disable the devfreq-event devices\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops exynos_bus_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(exynos_bus_suspend, exynos_bus_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id exynos_bus_of_match[] = {
|
||||
{ .compatible = "samsung,exynos-bus", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, exynos_bus_of_match);
|
||||
|
||||
static struct platform_driver exynos_bus_platdrv = {
|
||||
.probe = exynos_bus_probe,
|
||||
.driver = {
|
||||
.name = "exynos-bus",
|
||||
.pm = &exynos_bus_pm,
|
||||
.of_match_table = of_match_ptr(exynos_bus_of_match),
|
||||
},
|
||||
};
|
||||
module_platform_driver(exynos_bus_platdrv);
|
||||
|
||||
MODULE_DESCRIPTION("Generic Exynos Bus frequency driver");
|
||||
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -1,3 +0,0 @@
|
|||
# Exynos DEVFREQ Drivers
|
||||
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos_ppmu.o exynos4_bus.o
|
||||
obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o
|
File diff suppressed because it is too large
Load Diff
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* EXYNOS4 BUS header
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __DEVFREQ_EXYNOS4_BUS_H
|
||||
#define __DEVFREQ_EXYNOS4_BUS_H __FILE__
|
||||
|
||||
#include <mach/map.h>
|
||||
|
||||
#define EXYNOS4_CLKDIV_LEFTBUS (S5P_VA_CMU + 0x04500)
|
||||
#define EXYNOS4_CLKDIV_STAT_LEFTBUS (S5P_VA_CMU + 0x04600)
|
||||
|
||||
#define EXYNOS4_CLKDIV_RIGHTBUS (S5P_VA_CMU + 0x08500)
|
||||
#define EXYNOS4_CLKDIV_STAT_RIGHTBUS (S5P_VA_CMU + 0x08600)
|
||||
|
||||
#define EXYNOS4_CLKDIV_TOP (S5P_VA_CMU + 0x0C510)
|
||||
#define EXYNOS4_CLKDIV_CAM (S5P_VA_CMU + 0x0C520)
|
||||
#define EXYNOS4_CLKDIV_MFC (S5P_VA_CMU + 0x0C528)
|
||||
|
||||
#define EXYNOS4_CLKDIV_STAT_TOP (S5P_VA_CMU + 0x0C610)
|
||||
#define EXYNOS4_CLKDIV_STAT_MFC (S5P_VA_CMU + 0x0C628)
|
||||
|
||||
#define EXYNOS4210_CLKGATE_IP_IMAGE (S5P_VA_CMU + 0x0C930)
|
||||
#define EXYNOS4212_CLKGATE_IP_IMAGE (S5P_VA_CMU + 0x04930)
|
||||
|
||||
#define EXYNOS4_CLKDIV_DMC0 (S5P_VA_CMU + 0x10500)
|
||||
#define EXYNOS4_CLKDIV_DMC1 (S5P_VA_CMU + 0x10504)
|
||||
#define EXYNOS4_CLKDIV_STAT_DMC0 (S5P_VA_CMU + 0x10600)
|
||||
#define EXYNOS4_CLKDIV_STAT_DMC1 (S5P_VA_CMU + 0x10604)
|
||||
|
||||
#define EXYNOS4_DMC_PAUSE_CTRL (S5P_VA_CMU + 0x11094)
|
||||
#define EXYNOS4_DMC_PAUSE_ENABLE (1 << 0)
|
||||
|
||||
#define EXYNOS4_CLKDIV_DMC0_ACP_SHIFT (0)
|
||||
#define EXYNOS4_CLKDIV_DMC0_ACP_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_ACP_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT (4)
|
||||
#define EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT (8)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DPHY_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DMC_SHIFT (12)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DMC_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMC_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT (16)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DMCD_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT (20)
|
||||
#define EXYNOS4_CLKDIV_DMC0_DMCP_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT (24)
|
||||
#define EXYNOS4_CLKDIV_DMC0_COPY2_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT (28)
|
||||
#define EXYNOS4_CLKDIV_DMC0_CORETI_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT)
|
||||
|
||||
#define EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT (0)
|
||||
#define EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK (0xf << EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC1_C2C_SHIFT (4)
|
||||
#define EXYNOS4_CLKDIV_DMC1_C2C_MASK (0x7 << EXYNOS4_CLKDIV_DMC1_C2C_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC1_PWI_SHIFT (8)
|
||||
#define EXYNOS4_CLKDIV_DMC1_PWI_MASK (0xf << EXYNOS4_CLKDIV_DMC1_PWI_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT (12)
|
||||
#define EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK (0x7 << EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC1_DVSEM_SHIFT (16)
|
||||
#define EXYNOS4_CLKDIV_DMC1_DVSEM_MASK (0x7f << EXYNOS4_CLKDIV_DMC1_DVSEM_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_DMC1_DPM_SHIFT (24)
|
||||
#define EXYNOS4_CLKDIV_DMC1_DPM_MASK (0x7f << EXYNOS4_CLKDIV_DMC1_DPM_SHIFT)
|
||||
|
||||
#define EXYNOS4_CLKDIV_MFC_SHIFT (0)
|
||||
#define EXYNOS4_CLKDIV_MFC_MASK (0x7 << EXYNOS4_CLKDIV_MFC_SHIFT)
|
||||
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT (0)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK200_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT (4)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK100_MASK (0xF << EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT (8)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK160_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT (12)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK133_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT (16)
|
||||
#define EXYNOS4_CLKDIV_TOP_ONENAND_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT (20)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_SHIFT (24)
|
||||
#define EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_SHIFT)
|
||||
|
||||
#define EXYNOS4_CLKDIV_BUS_GDLR_SHIFT (0)
|
||||
#define EXYNOS4_CLKDIV_BUS_GDLR_MASK (0x7 << EXYNOS4_CLKDIV_BUS_GDLR_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_BUS_GPLR_SHIFT (4)
|
||||
#define EXYNOS4_CLKDIV_BUS_GPLR_MASK (0x7 << EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)
|
||||
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT (0)
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC0_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT (4)
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC1_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT (8)
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC2_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT)
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT (12)
|
||||
#define EXYNOS4_CLKDIV_CAM_FIMC3_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT)
|
||||
|
||||
#define EXYNOS4_CLKDIV_CAM1 (S5P_VA_CMU + 0x0C568)
|
||||
|
||||
#define EXYNOS4_CLKDIV_STAT_CAM1 (S5P_VA_CMU + 0x0C668)
|
||||
|
||||
#define EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT (0)
|
||||
#define EXYNOS4_CLKDIV_CAM1_JPEG_MASK (0xf << EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT)
|
||||
|
||||
#endif /* __DEVFREQ_EXYNOS4_BUS_H */
|
|
@ -1,431 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* EXYNOS5 INT clock frequency scaling support using DEVFREQ framework
|
||||
* Based on work done by Jonghwan Choi <jhbird.choi@samsung.com>
|
||||
* Support for only EXYNOS5250 is present.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/devfreq.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include "exynos_ppmu.h"
|
||||
|
||||
#define MAX_SAFEVOLT 1100000 /* 1.10V */
|
||||
/* Assume that the bus is saturated if the utilization is 25% */
|
||||
#define INT_BUS_SATURATION_RATIO 25
|
||||
|
||||
enum int_level_idx {
|
||||
LV_0,
|
||||
LV_1,
|
||||
LV_2,
|
||||
LV_3,
|
||||
LV_4,
|
||||
_LV_END
|
||||
};
|
||||
|
||||
enum exynos_ppmu_list {
|
||||
PPMU_RIGHT,
|
||||
PPMU_END,
|
||||
};
|
||||
|
||||
struct busfreq_data_int {
|
||||
struct device *dev;
|
||||
struct devfreq *devfreq;
|
||||
struct regulator *vdd_int;
|
||||
struct busfreq_ppmu_data ppmu_data;
|
||||
unsigned long curr_freq;
|
||||
bool disabled;
|
||||
|
||||
struct notifier_block pm_notifier;
|
||||
struct mutex lock;
|
||||
struct pm_qos_request int_req;
|
||||
struct clk *int_clk;
|
||||
};
|
||||
|
||||
struct int_bus_opp_table {
|
||||
unsigned int idx;
|
||||
unsigned long clk;
|
||||
unsigned long volt;
|
||||
};
|
||||
|
||||
static struct int_bus_opp_table exynos5_int_opp_table[] = {
|
||||
{LV_0, 266000, 1025000},
|
||||
{LV_1, 200000, 1025000},
|
||||
{LV_2, 160000, 1025000},
|
||||
{LV_3, 133000, 1025000},
|
||||
{LV_4, 100000, 1025000},
|
||||
{0, 0, 0},
|
||||
};
|
||||
|
||||
static int exynos5_int_setvolt(struct busfreq_data_int *data,
|
||||
unsigned long volt)
|
||||
{
|
||||
return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT);
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq,
|
||||
u32 flags)
|
||||
{
|
||||
int err = 0;
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device,
|
||||
dev);
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
struct dev_pm_opp *opp;
|
||||
unsigned long old_freq, freq;
|
||||
unsigned long volt;
|
||||
|
||||
rcu_read_lock();
|
||||
opp = devfreq_recommended_opp(dev, _freq, flags);
|
||||
if (IS_ERR(opp)) {
|
||||
rcu_read_unlock();
|
||||
dev_err(dev, "%s: Invalid OPP.\n", __func__);
|
||||
return PTR_ERR(opp);
|
||||
}
|
||||
|
||||
freq = dev_pm_opp_get_freq(opp);
|
||||
volt = dev_pm_opp_get_voltage(opp);
|
||||
rcu_read_unlock();
|
||||
|
||||
old_freq = data->curr_freq;
|
||||
|
||||
if (old_freq == freq)
|
||||
return 0;
|
||||
|
||||
dev_dbg(dev, "targeting %lukHz %luuV\n", freq, volt);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
if (data->disabled)
|
||||
goto out;
|
||||
|
||||
if (freq > exynos5_int_opp_table[0].clk)
|
||||
pm_qos_update_request(&data->int_req, freq * 16 / 1000);
|
||||
else
|
||||
pm_qos_update_request(&data->int_req, -1);
|
||||
|
||||
if (old_freq < freq)
|
||||
err = exynos5_int_setvolt(data, volt);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = clk_set_rate(data->int_clk, freq * 1000);
|
||||
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (old_freq > freq)
|
||||
err = exynos5_int_setvolt(data, volt);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
data->curr_freq = freq;
|
||||
out:
|
||||
mutex_unlock(&data->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int exynos5_int_get_dev_status(struct device *dev,
|
||||
struct devfreq_dev_status *stat)
|
||||
{
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device,
|
||||
dev);
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
|
||||
int busier_dmc;
|
||||
|
||||
exynos_read_ppmu(ppmu_data);
|
||||
busier_dmc = exynos_get_busier_ppmu(ppmu_data);
|
||||
|
||||
stat->current_frequency = data->curr_freq;
|
||||
|
||||
/* Number of cycles spent on memory access */
|
||||
stat->busy_time = ppmu_data->ppmu[busier_dmc].count[PPMU_PMNCNT3];
|
||||
stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO;
|
||||
stat->total_time = ppmu_data->ppmu[busier_dmc].ccnt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
|
||||
.initial_freq = 160000,
|
||||
.polling_ms = 100,
|
||||
.target = exynos5_busfreq_int_target,
|
||||
.get_dev_status = exynos5_int_get_dev_status,
|
||||
};
|
||||
|
||||
static int exynos5250_init_int_tables(struct busfreq_data_int *data)
|
||||
{
|
||||
int i, err = 0;
|
||||
|
||||
for (i = LV_0; i < _LV_END; i++) {
|
||||
err = dev_pm_opp_add(data->dev, exynos5_int_opp_table[i].clk,
|
||||
exynos5_int_opp_table[i].volt);
|
||||
if (err) {
|
||||
dev_err(data->dev, "Cannot add opp entries.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct busfreq_data_int *data = container_of(this,
|
||||
struct busfreq_data_int, pm_notifier);
|
||||
struct dev_pm_opp *opp;
|
||||
unsigned long maxfreq = ULONG_MAX;
|
||||
unsigned long freq;
|
||||
unsigned long volt;
|
||||
int err = 0;
|
||||
|
||||
switch (event) {
|
||||
case PM_SUSPEND_PREPARE:
|
||||
/* Set Fastest and Deactivate DVFS */
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
data->disabled = true;
|
||||
|
||||
rcu_read_lock();
|
||||
opp = dev_pm_opp_find_freq_floor(data->dev, &maxfreq);
|
||||
if (IS_ERR(opp)) {
|
||||
rcu_read_unlock();
|
||||
err = PTR_ERR(opp);
|
||||
goto unlock;
|
||||
}
|
||||
freq = dev_pm_opp_get_freq(opp);
|
||||
volt = dev_pm_opp_get_voltage(opp);
|
||||
rcu_read_unlock();
|
||||
|
||||
err = exynos5_int_setvolt(data, volt);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
err = clk_set_rate(data->int_clk, freq * 1000);
|
||||
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
data->curr_freq = freq;
|
||||
unlock:
|
||||
mutex_unlock(&data->lock);
|
||||
if (err)
|
||||
return NOTIFY_BAD;
|
||||
return NOTIFY_OK;
|
||||
case PM_POST_RESTORE:
|
||||
case PM_POST_SUSPEND:
|
||||
/* Reactivate */
|
||||
mutex_lock(&data->lock);
|
||||
data->disabled = false;
|
||||
mutex_unlock(&data->lock);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct busfreq_data_int *data;
|
||||
struct busfreq_ppmu_data *ppmu_data;
|
||||
struct dev_pm_opp *opp;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np;
|
||||
unsigned long initial_freq;
|
||||
unsigned long initial_volt;
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int),
|
||||
GFP_KERNEL);
|
||||
if (data == NULL) {
|
||||
dev_err(dev, "Cannot allocate memory.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ppmu_data = &data->ppmu_data;
|
||||
ppmu_data->ppmu_end = PPMU_END;
|
||||
ppmu_data->ppmu = devm_kzalloc(dev,
|
||||
sizeof(struct exynos_ppmu) * PPMU_END,
|
||||
GFP_KERNEL);
|
||||
if (!ppmu_data->ppmu) {
|
||||
dev_err(dev, "Failed to allocate memory for exynos_ppmu\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu");
|
||||
if (np == NULL) {
|
||||
pr_err("Unable to find PPMU node\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
/* map PPMU memory region */
|
||||
ppmu_data->ppmu[i].hw_base = of_iomap(np, i);
|
||||
if (ppmu_data->ppmu[i].hw_base == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event;
|
||||
data->dev = dev;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
err = exynos5250_init_int_tables(data);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
data->vdd_int = devm_regulator_get(dev, "vdd_int");
|
||||
if (IS_ERR(data->vdd_int)) {
|
||||
dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
|
||||
return PTR_ERR(data->vdd_int);
|
||||
}
|
||||
|
||||
data->int_clk = devm_clk_get(dev, "int_clk");
|
||||
if (IS_ERR(data->int_clk)) {
|
||||
dev_err(dev, "Cannot get clock \"int_clk\"\n");
|
||||
return PTR_ERR(data->int_clk);
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
opp = dev_pm_opp_find_freq_floor(dev,
|
||||
&exynos5_devfreq_int_profile.initial_freq);
|
||||
if (IS_ERR(opp)) {
|
||||
rcu_read_unlock();
|
||||
dev_err(dev, "Invalid initial frequency %lu kHz.\n",
|
||||
exynos5_devfreq_int_profile.initial_freq);
|
||||
return PTR_ERR(opp);
|
||||
}
|
||||
initial_freq = dev_pm_opp_get_freq(opp);
|
||||
initial_volt = dev_pm_opp_get_voltage(opp);
|
||||
rcu_read_unlock();
|
||||
data->curr_freq = initial_freq;
|
||||
|
||||
err = clk_set_rate(data->int_clk, initial_freq * 1000);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to set initial frequency\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = exynos5_int_setvolt(data, initial_volt);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
|
||||
data->devfreq = devm_devfreq_add_device(dev, &exynos5_devfreq_int_profile,
|
||||
"simple_ondemand", NULL);
|
||||
if (IS_ERR(data->devfreq))
|
||||
return PTR_ERR(data->devfreq);
|
||||
|
||||
err = devm_devfreq_register_opp_notifier(dev, data->devfreq);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "Failed to register opp notifier\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = register_pm_notifier(&data->pm_notifier);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to setup pm notifier\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* TODO: Add a new QOS class for int/mif bus */
|
||||
pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos5_busfreq_int_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
|
||||
pm_qos_remove_request(&data->int_req);
|
||||
unregister_pm_notifier(&data->pm_notifier);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int exynos5_busfreq_int_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device,
|
||||
dev);
|
||||
struct busfreq_data_int *data = platform_get_drvdata(pdev);
|
||||
struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
|
||||
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
return 0;
|
||||
}
|
||||
static const struct dev_pm_ops exynos5_busfreq_int_pm = {
|
||||
.resume = exynos5_busfreq_int_resume,
|
||||
};
|
||||
#endif
|
||||
static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm_ops, NULL,
|
||||
exynos5_busfreq_int_resume);
|
||||
|
||||
/* platform device pointer for exynos5 devfreq device. */
|
||||
static struct platform_device *exynos5_devfreq_pdev;
|
||||
|
||||
static struct platform_driver exynos5_busfreq_int_driver = {
|
||||
.probe = exynos5_busfreq_int_probe,
|
||||
.remove = exynos5_busfreq_int_remove,
|
||||
.driver = {
|
||||
.name = "exynos5-bus-int",
|
||||
.pm = &exynos5_busfreq_int_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init exynos5_busfreq_int_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&exynos5_busfreq_int_driver);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
exynos5_devfreq_pdev =
|
||||
platform_device_register_simple("exynos5-bus-int", -1, NULL, 0);
|
||||
if (IS_ERR(exynos5_devfreq_pdev)) {
|
||||
ret = PTR_ERR(exynos5_devfreq_pdev);
|
||||
goto out1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out1:
|
||||
platform_driver_unregister(&exynos5_busfreq_int_driver);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
late_initcall(exynos5_busfreq_int_init);
|
||||
|
||||
static void __exit exynos5_busfreq_int_exit(void)
|
||||
{
|
||||
platform_device_unregister(exynos5_devfreq_pdev);
|
||||
platform_driver_unregister(&exynos5_busfreq_int_driver);
|
||||
}
|
||||
module_exit(exynos5_busfreq_int_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework");
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* EXYNOS - PPMU support
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include "exynos_ppmu.h"
|
||||
|
||||
void exynos_ppmu_reset(void __iomem *ppmu_base)
|
||||
{
|
||||
__raw_writel(PPMU_CYCLE_RESET | PPMU_COUNTER_RESET, ppmu_base);
|
||||
__raw_writel(PPMU_ENABLE_CYCLE |
|
||||
PPMU_ENABLE_COUNT0 |
|
||||
PPMU_ENABLE_COUNT1 |
|
||||
PPMU_ENABLE_COUNT2 |
|
||||
PPMU_ENABLE_COUNT3,
|
||||
ppmu_base + PPMU_CNTENS);
|
||||
}
|
||||
|
||||
void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
|
||||
unsigned int evt)
|
||||
{
|
||||
__raw_writel(evt, ppmu_base + PPMU_BEVTSEL(ch));
|
||||
}
|
||||
|
||||
void exynos_ppmu_start(void __iomem *ppmu_base)
|
||||
{
|
||||
__raw_writel(PPMU_ENABLE, ppmu_base);
|
||||
}
|
||||
|
||||
void exynos_ppmu_stop(void __iomem *ppmu_base)
|
||||
{
|
||||
__raw_writel(PPMU_DISABLE, ppmu_base);
|
||||
}
|
||||
|
||||
unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch)
|
||||
{
|
||||
unsigned int total;
|
||||
|
||||
if (ch == PPMU_PMNCNT3)
|
||||
total = ((__raw_readl(ppmu_base + PMCNT_OFFSET(ch)) << 8) |
|
||||
__raw_readl(ppmu_base + PMCNT_OFFSET(ch + 1)));
|
||||
else
|
||||
total = __raw_readl(ppmu_base + PMCNT_OFFSET(ch));
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base;
|
||||
|
||||
/* Reset the performance and cycle counters */
|
||||
exynos_ppmu_reset(ppmu_base);
|
||||
|
||||
/* Setup count registers to monitor read/write transactions */
|
||||
ppmu_data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
|
||||
exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
|
||||
ppmu_data->ppmu[i].event[PPMU_PMNCNT3]);
|
||||
|
||||
exynos_ppmu_start(ppmu_base);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(busfreq_mon_reset);
|
||||
|
||||
void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base;
|
||||
|
||||
exynos_ppmu_stop(ppmu_base);
|
||||
|
||||
/* Update local data from PPMU */
|
||||
ppmu_data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
|
||||
|
||||
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||
if (ppmu_data->ppmu[i].event[j] == 0)
|
||||
ppmu_data->ppmu[i].count[j] = 0;
|
||||
else
|
||||
ppmu_data->ppmu[i].count[j] =
|
||||
exynos_ppmu_read(ppmu_base, j);
|
||||
}
|
||||
}
|
||||
|
||||
busfreq_mon_reset(ppmu_data);
|
||||
}
|
||||
EXPORT_SYMBOL(exynos_read_ppmu);
|
||||
|
||||
int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
int i, j, busy = 0;
|
||||
|
||||
for (i = 0; i < ppmu_data->ppmu_end; i++) {
|
||||
for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
|
||||
if (ppmu_data->ppmu[i].count[j] > count) {
|
||||
count = ppmu_data->ppmu[i].count[j];
|
||||
busy = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return busy;
|
||||
}
|
||||
EXPORT_SYMBOL(exynos_get_busier_ppmu);
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* EXYNOS PPMU header
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __DEVFREQ_EXYNOS_PPMU_H
|
||||
#define __DEVFREQ_EXYNOS_PPMU_H __FILE__
|
||||
|
||||
#include <linux/ktime.h>
|
||||
|
||||
/* For PPMU Control */
|
||||
#define PPMU_ENABLE BIT(0)
|
||||
#define PPMU_DISABLE 0x0
|
||||
#define PPMU_CYCLE_RESET BIT(1)
|
||||
#define PPMU_COUNTER_RESET BIT(2)
|
||||
|
||||
#define PPMU_ENABLE_COUNT0 BIT(0)
|
||||
#define PPMU_ENABLE_COUNT1 BIT(1)
|
||||
#define PPMU_ENABLE_COUNT2 BIT(2)
|
||||
#define PPMU_ENABLE_COUNT3 BIT(3)
|
||||
#define PPMU_ENABLE_CYCLE BIT(31)
|
||||
|
||||
#define PPMU_CNTENS 0x10
|
||||
#define PPMU_FLAG 0x50
|
||||
#define PPMU_CCNT_OVERFLOW BIT(31)
|
||||
#define PPMU_CCNT 0x100
|
||||
|
||||
#define PPMU_PMCNT0 0x110
|
||||
#define PPMU_PMCNT_OFFSET 0x10
|
||||
#define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x))
|
||||
|
||||
#define PPMU_BEVT0SEL 0x1000
|
||||
#define PPMU_BEVTSEL_OFFSET 0x100
|
||||
#define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (ch * PPMU_BEVTSEL_OFFSET))
|
||||
|
||||
/* For Event Selection */
|
||||
#define RD_DATA_COUNT 0x5
|
||||
#define WR_DATA_COUNT 0x6
|
||||
#define RDWR_DATA_COUNT 0x7
|
||||
|
||||
enum ppmu_counter {
|
||||
PPMU_PMNCNT0,
|
||||
PPMU_PMCCNT1,
|
||||
PPMU_PMNCNT2,
|
||||
PPMU_PMNCNT3,
|
||||
PPMU_PMNCNT_MAX,
|
||||
};
|
||||
|
||||
struct bus_opp_table {
|
||||
unsigned int idx;
|
||||
unsigned long clk;
|
||||
unsigned long volt;
|
||||
};
|
||||
|
||||
struct exynos_ppmu {
|
||||
void __iomem *hw_base;
|
||||
unsigned int ccnt;
|
||||
unsigned int event[PPMU_PMNCNT_MAX];
|
||||
unsigned int count[PPMU_PMNCNT_MAX];
|
||||
unsigned long long ns;
|
||||
ktime_t reset_time;
|
||||
bool ccnt_overflow;
|
||||
bool count_overflow[PPMU_PMNCNT_MAX];
|
||||
};
|
||||
|
||||
struct busfreq_ppmu_data {
|
||||
struct exynos_ppmu *ppmu;
|
||||
int ppmu_end;
|
||||
};
|
||||
|
||||
void exynos_ppmu_reset(void __iomem *ppmu_base);
|
||||
void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
|
||||
unsigned int evt);
|
||||
void exynos_ppmu_start(void __iomem *ppmu_base);
|
||||
void exynos_ppmu_stop(void __iomem *ppmu_base);
|
||||
unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch);
|
||||
void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data);
|
||||
void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data);
|
||||
int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data);
|
||||
#endif /* __DEVFREQ_EXYNOS_PPMU_H */
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* linux/drivers/devfreq/governor_passive.c
|
||||
*
|
||||
* Copyright (C) 2016 Samsung Electronics
|
||||
* Author: Chanwoo Choi <cw00.choi@samsung.com>
|
||||
* Author: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/devfreq.h>
|
||||
#include "governor.h"
|
||||
|
||||
static int devfreq_passive_get_target_freq(struct devfreq *devfreq,
|
||||
unsigned long *freq)
|
||||
{
|
||||
struct devfreq_passive_data *p_data
|
||||
= (struct devfreq_passive_data *)devfreq->data;
|
||||
struct devfreq *parent_devfreq = (struct devfreq *)p_data->parent;
|
||||
unsigned long child_freq = ULONG_MAX;
|
||||
struct dev_pm_opp *opp;
|
||||
int i, count, ret = 0;
|
||||
|
||||
/*
|
||||
* If the devfreq device with passive governor has the specific method
|
||||
* to determine the next frequency, should use the get_target_freq()
|
||||
* of struct devfreq_passive_data.
|
||||
*/
|
||||
if (p_data->get_target_freq) {
|
||||
ret = p_data->get_target_freq(devfreq, freq);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the parent and passive devfreq device uses the OPP table,
|
||||
* get the next frequency by using the OPP table.
|
||||
*/
|
||||
|
||||
/*
|
||||
* - parent devfreq device uses the governors except for passive.
|
||||
* - passive devfreq device uses the passive governor.
|
||||
*
|
||||
* Each devfreq has the OPP table. After deciding the new frequency
|
||||
* from the governor of parent devfreq device, the passive governor
|
||||
* need to get the index of new frequency on OPP table of parent
|
||||
* device. And then the index is used for getting the suitable
|
||||
* new frequency for passive devfreq device.
|
||||
*/
|
||||
if (!devfreq->profile || !devfreq->profile->freq_table
|
||||
|| devfreq->profile->max_state <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The passive governor have to get the correct frequency from OPP
|
||||
* list of parent device. Because in this case, *freq is temporary
|
||||
* value which is decided by ondemand governor.
|
||||
*/
|
||||
rcu_read_lock();
|
||||
opp = devfreq_recommended_opp(parent_devfreq->dev.parent, freq, 0);
|
||||
rcu_read_unlock();
|
||||
if (IS_ERR(opp)) {
|
||||
ret = PTR_ERR(opp);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the OPP table's index of decided freqeuncy by governor
|
||||
* of parent device.
|
||||
*/
|
||||
for (i = 0; i < parent_devfreq->profile->max_state; i++)
|
||||
if (parent_devfreq->profile->freq_table[i] == *freq)
|
||||
break;
|
||||
|
||||
if (i == parent_devfreq->profile->max_state) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Get the suitable frequency by using index of parent device. */
|
||||
if (i < devfreq->profile->max_state) {
|
||||
child_freq = devfreq->profile->freq_table[i];
|
||||
} else {
|
||||
count = devfreq->profile->max_state;
|
||||
child_freq = devfreq->profile->freq_table[count - 1];
|
||||
}
|
||||
|
||||
/* Return the suitable frequency for passive device. */
|
||||
*freq = child_freq;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int update_devfreq_passive(struct devfreq *devfreq, unsigned long freq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!devfreq->governor)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock_nested(&devfreq->lock, SINGLE_DEPTH_NESTING);
|
||||
|
||||
ret = devfreq->governor->get_target_freq(devfreq, &freq);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = devfreq->profile->target(devfreq->dev.parent, &freq, 0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
devfreq->previous_freq = freq;
|
||||
|
||||
out:
|
||||
mutex_unlock(&devfreq->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int devfreq_passive_notifier_call(struct notifier_block *nb,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct devfreq_passive_data *data
|
||||
= container_of(nb, struct devfreq_passive_data, nb);
|
||||
struct devfreq *devfreq = (struct devfreq *)data->this;
|
||||
struct devfreq *parent = (struct devfreq *)data->parent;
|
||||
struct devfreq_freqs *freqs = (struct devfreq_freqs *)ptr;
|
||||
unsigned long freq = freqs->new;
|
||||
|
||||
switch (event) {
|
||||
case DEVFREQ_PRECHANGE:
|
||||
if (parent->previous_freq > freq)
|
||||
update_devfreq_passive(devfreq, freq);
|
||||
break;
|
||||
case DEVFREQ_POSTCHANGE:
|
||||
if (parent->previous_freq < freq)
|
||||
update_devfreq_passive(devfreq, freq);
|
||||
break;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int devfreq_passive_event_handler(struct devfreq *devfreq,
|
||||
unsigned int event, void *data)
|
||||
{
|
||||
struct device *dev = devfreq->dev.parent;
|
||||
struct devfreq_passive_data *p_data
|
||||
= (struct devfreq_passive_data *)devfreq->data;
|
||||
struct devfreq *parent = (struct devfreq *)p_data->parent;
|
||||
struct notifier_block *nb = &p_data->nb;
|
||||
int ret = 0;
|
||||
|
||||
if (!parent)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
switch (event) {
|
||||
case DEVFREQ_GOV_START:
|
||||
if (!p_data->this)
|
||||
p_data->this = devfreq;
|
||||
|
||||
nb->notifier_call = devfreq_passive_notifier_call;
|
||||
ret = devm_devfreq_register_notifier(dev, parent, nb,
|
||||
DEVFREQ_TRANSITION_NOTIFIER);
|
||||
break;
|
||||
case DEVFREQ_GOV_STOP:
|
||||
devm_devfreq_unregister_notifier(dev, parent, nb,
|
||||
DEVFREQ_TRANSITION_NOTIFIER);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct devfreq_governor devfreq_passive = {
|
||||
.name = "passive",
|
||||
.get_target_freq = devfreq_passive_get_target_freq,
|
||||
.event_handler = devfreq_passive_event_handler,
|
||||
};
|
||||
|
||||
static int __init devfreq_passive_init(void)
|
||||
{
|
||||
return devfreq_add_governor(&devfreq_passive);
|
||||
}
|
||||
subsys_initcall(devfreq_passive_init);
|
||||
|
||||
static void __exit devfreq_passive_exit(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = devfreq_remove_governor(&devfreq_passive);
|
||||
if (ret)
|
||||
pr_err("%s: failed remove governor %d\n", __func__, ret);
|
||||
}
|
||||
module_exit(devfreq_passive_exit);
|
||||
|
||||
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
|
||||
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
|
||||
MODULE_DESCRIPTION("DEVFREQ Passive governor");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -19,6 +19,13 @@
|
|||
|
||||
#define DEVFREQ_NAME_LEN 16
|
||||
|
||||
/* DEVFREQ notifier interface */
|
||||
#define DEVFREQ_TRANSITION_NOTIFIER (0)
|
||||
|
||||
/* Transition notifiers of DEVFREQ_TRANSITION_NOTIFIER */
|
||||
#define DEVFREQ_PRECHANGE (0)
|
||||
#define DEVFREQ_POSTCHANGE (1)
|
||||
|
||||
struct devfreq;
|
||||
|
||||
/**
|
||||
|
@ -143,6 +150,7 @@ struct devfreq_governor {
|
|||
* @trans_table: Statistics of devfreq transitions
|
||||
* @time_in_state: Statistics of devfreq states
|
||||
* @last_stat_updated: The last time stat updated
|
||||
* @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier
|
||||
*
|
||||
* This structure stores the devfreq information for a give device.
|
||||
*
|
||||
|
@ -177,6 +185,13 @@ struct devfreq {
|
|||
unsigned int *trans_table;
|
||||
unsigned long *time_in_state;
|
||||
unsigned long last_stat_updated;
|
||||
|
||||
struct srcu_notifier_head transition_notifier_list;
|
||||
};
|
||||
|
||||
struct devfreq_freqs {
|
||||
unsigned long old;
|
||||
unsigned long new;
|
||||
};
|
||||
|
||||
#if defined(CONFIG_PM_DEVFREQ)
|
||||
|
@ -207,6 +222,22 @@ extern int devm_devfreq_register_opp_notifier(struct device *dev,
|
|||
struct devfreq *devfreq);
|
||||
extern void devm_devfreq_unregister_opp_notifier(struct device *dev,
|
||||
struct devfreq *devfreq);
|
||||
extern int devfreq_register_notifier(struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list);
|
||||
extern int devfreq_unregister_notifier(struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list);
|
||||
extern int devm_devfreq_register_notifier(struct device *dev,
|
||||
struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list);
|
||||
extern void devm_devfreq_unregister_notifier(struct device *dev,
|
||||
struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list);
|
||||
extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
|
||||
int index);
|
||||
|
||||
/**
|
||||
* devfreq_update_stats() - update the last_status pointer in struct devfreq
|
||||
|
@ -241,6 +272,39 @@ struct devfreq_simple_ondemand_data {
|
|||
};
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE)
|
||||
/**
|
||||
* struct devfreq_passive_data - void *data fed to struct devfreq
|
||||
* and devfreq_add_device
|
||||
* @parent: the devfreq instance of parent device.
|
||||
* @get_target_freq: Optional callback, Returns desired operating frequency
|
||||
* for the device using passive governor. That is called
|
||||
* when passive governor should decide the next frequency
|
||||
* by using the new frequency of parent devfreq device
|
||||
* using governors except for passive governor.
|
||||
* If the devfreq device has the specific method to decide
|
||||
* the next frequency, should use this callback.
|
||||
* @this: the devfreq instance of own device.
|
||||
* @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER list
|
||||
*
|
||||
* The devfreq_passive_data have to set the devfreq instance of parent
|
||||
* device with governors except for the passive governor. But, don't need to
|
||||
* initialize the 'this' and 'nb' field because the devfreq core will handle
|
||||
* them.
|
||||
*/
|
||||
struct devfreq_passive_data {
|
||||
/* Should set the devfreq instance of parent device */
|
||||
struct devfreq *parent;
|
||||
|
||||
/* Optional callback to decide the next frequency of passvice device */
|
||||
int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
|
||||
|
||||
/* For passive governor's internal use. Don't need to set them */
|
||||
struct devfreq *this;
|
||||
struct notifier_block nb;
|
||||
};
|
||||
#endif
|
||||
|
||||
#else /* !CONFIG_PM_DEVFREQ */
|
||||
static inline struct devfreq *devfreq_add_device(struct device *dev,
|
||||
struct devfreq_dev_profile *profile,
|
||||
|
@ -307,6 +371,41 @@ static inline void devm_devfreq_unregister_opp_notifier(struct device *dev,
|
|||
{
|
||||
}
|
||||
|
||||
static inline int devfreq_register_notifier(struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int devfreq_unregister_notifier(struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int devm_devfreq_register_notifier(struct device *dev,
|
||||
struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void devm_devfreq_unregister_notifier(struct device *dev,
|
||||
struct devfreq *devfreq,
|
||||
struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
{
|
||||
}
|
||||
|
||||
static inline struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
|
||||
int index)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static inline int devfreq_update_stats(struct devfreq *df)
|
||||
{
|
||||
return -EINVAL;
|
||||
|
|
Loading…
Reference in New Issue