Merge branches 'powercap' and 'pm-devfreq'
Merge devfreq changes and a new CPU idle time injection framework for 4.19. * powercap: powercap / idle_inject: Add an idle injection framework * pm-devfreq: PM / devfreq: rk3399_dmc: Fix duplicated opp table on reload. PM / devfreq: Init user limits from OPP limits, not viceversa PM / devfreq: rk3399_dmc: fix spelling mistakes. PM / devfreq: rk3399_dmc: do not print error when get supply and clk defer. dt-bindings: devfreq: rk3399_dmc: move interrupts to be optional. PM / devfreq: rk3399_dmc: remove wait for dcf irq event. dt-bindings: clock: add rk3399 DDR3 standard speed bins. dt-bindings: devfreq: rk3399_dmc: improve binding documentation. PM / devfreq: use put_device() instead of kfree() PM / devfreq: exynos-ppmu: Delete an error message for a failed memory allocation in exynos_ppmu_probe()
This commit is contained in:
commit
8f0f2b6211
|
@ -3,12 +3,8 @@
|
|||
Required properties:
|
||||
- compatible: Must be "rockchip,rk3399-dmc".
|
||||
- devfreq-events: Node to get DDR loading, Refer to
|
||||
Documentation/devicetree/bindings/devfreq/
|
||||
Documentation/devicetree/bindings/devfreq/event/
|
||||
rockchip-dfi.txt
|
||||
- interrupts: The interrupt number to the CPU. The interrupt
|
||||
specifier format depends on the interrupt controller.
|
||||
It should be DCF interrupts, when DDR dvfs finish,
|
||||
it will happen.
|
||||
- clocks: Phandles for clock specified in "clock-names" property
|
||||
- clock-names : The name of clock used by the DFI, must be
|
||||
"pclk_ddr_mon";
|
||||
|
@ -17,139 +13,148 @@ Required properties:
|
|||
- center-supply: DMC supply node.
|
||||
- status: Marks the node enabled/disabled.
|
||||
|
||||
Following properties are ddr timing:
|
||||
Optional properties:
|
||||
- interrupts: The CPU interrupt number. The interrupt specifier
|
||||
format depends on the interrupt controller.
|
||||
It should be a DCF interrupt. When DDR DVFS finishes
|
||||
a DCF interrupt is triggered.
|
||||
|
||||
- rockchip,dram_speed_bin : Value reference include/dt-bindings/clock/ddr.h,
|
||||
it select ddr3 cl-trp-trcd type, default value
|
||||
"DDR3_DEFAULT".it must selected according to
|
||||
"Speed Bin" in ddr3 datasheet, DO NOT use
|
||||
smaller "Speed Bin" than ddr3 exactly is.
|
||||
Following properties relate to DDR timing:
|
||||
|
||||
- rockchip,pd_idle : Config the PD_IDLE value, defined the power-down
|
||||
idle period, memories are places into power-down
|
||||
mode if bus is idle for PD_IDLE DFI clocks.
|
||||
- rockchip,dram_speed_bin : Value reference include/dt-bindings/clock/rk3399-ddr.h,
|
||||
it selects the DDR3 cl-trp-trcd type. It must be
|
||||
set according to "Speed Bin" in DDR3 datasheet,
|
||||
DO NOT use a smaller "Speed Bin" than specified
|
||||
for the DDR3 being used.
|
||||
|
||||
- rockchip,sr_idle : Configure the SR_IDLE value, defined the
|
||||
selfrefresh idle period, memories are places
|
||||
into self-refresh mode if bus is idle for
|
||||
SR_IDLE*1024 DFI clocks (DFI clocks freq is
|
||||
half of dram's clocks), defaule value is "0".
|
||||
- rockchip,pd_idle : Configure the PD_IDLE value. Defines the
|
||||
power-down idle period in which memories are
|
||||
placed into power-down mode if bus is idle
|
||||
for PD_IDLE DFI clock cycles.
|
||||
|
||||
- rockchip,sr_mc_gate_idle : Defined the self-refresh with memory and
|
||||
controller clock gating idle period, memories
|
||||
are places into self-refresh mode and memory
|
||||
controller clock arg gating if bus is idle for
|
||||
sr_mc_gate_idle*1024 DFI clocks.
|
||||
- rockchip,sr_idle : Configure the SR_IDLE value. Defines the
|
||||
self-refresh idle period in which memories are
|
||||
placed into self-refresh mode if bus is idle
|
||||
for SR_IDLE * 1024 DFI clock cycles (DFI
|
||||
clocks freq is half of DRAM clock), default
|
||||
value is "0".
|
||||
|
||||
- rockchip,srpd_lite_idle : Defined the self-refresh power down idle
|
||||
period, memories are places into self-refresh
|
||||
power down mode if bus is idle for
|
||||
srpd_lite_idle*1024 DFI clocks. This parameter
|
||||
is for LPDDR4 only.
|
||||
- rockchip,sr_mc_gate_idle : Defines the memory self-refresh and controller
|
||||
clock gating idle period. Memories are placed
|
||||
into self-refresh mode and memory controller
|
||||
clock arg gating started if bus is idle for
|
||||
sr_mc_gate_idle*1024 DFI clock cycles.
|
||||
|
||||
- rockchip,standby_idle : Defined the standby idle period, memories are
|
||||
places into self-refresh than controller, pi,
|
||||
phy and dram clock will gating if bus is idle
|
||||
for standby_idle * DFI clocks.
|
||||
- rockchip,srpd_lite_idle : Defines the self-refresh power down idle
|
||||
period in which memories are placed into
|
||||
self-refresh power down mode if bus is idle
|
||||
for srpd_lite_idle * 1024 DFI clock cycles.
|
||||
This parameter is for LPDDR4 only.
|
||||
|
||||
- rockchip,dram_dll_disb_freq : It's defined the DDR3 dll bypass frequency in
|
||||
MHz, when ddr freq less than DRAM_DLL_DISB_FREQ,
|
||||
ddr3 dll will bypssed note: if dll was bypassed,
|
||||
the odt also stop working.
|
||||
- rockchip,standby_idle : Defines the standby idle period in which
|
||||
memories are placed into self-refresh mode.
|
||||
The controller, pi, PHY and DRAM clock will
|
||||
be gated if bus is idle for standby_idle * DFI
|
||||
clock cycles.
|
||||
|
||||
- rockchip,phy_dll_disb_freq : Defined the PHY dll bypass frequency in
|
||||
MHz (Mega Hz), when ddr freq less than
|
||||
DRAM_DLL_DISB_FREQ, phy dll will bypssed.
|
||||
note: phy dll and phy odt are independent.
|
||||
- rockchip,dram_dll_dis_freq : Defines the DDR3 DLL bypass frequency in MHz.
|
||||
When DDR frequency is less than DRAM_DLL_DISB_FREQ,
|
||||
DDR3 DLL will be bypassed. Note: if DLL was bypassed,
|
||||
the odt will also stop working.
|
||||
|
||||
- rockchip,ddr3_odt_disb_freq : When dram type is DDR3, this parameter defined
|
||||
the odt disable frequency in MHz (Mega Hz),
|
||||
when ddr frequency less then ddr3_odt_disb_freq,
|
||||
the odt on dram side and controller side are
|
||||
- rockchip,phy_dll_dis_freq : Defines the PHY dll bypass frequency in
|
||||
MHz (Mega Hz). When DDR frequency is less than
|
||||
DRAM_DLL_DISB_FREQ, PHY DLL will be bypassed.
|
||||
Note: PHY DLL and PHY ODT are independent.
|
||||
|
||||
- rockchip,ddr3_odt_dis_freq : When the DRAM type is DDR3, this parameter defines
|
||||
the ODT disable frequency in MHz (Mega Hz).
|
||||
when the DDR frequency is less then ddr3_odt_dis_freq,
|
||||
the ODT on the DRAM side and controller side are
|
||||
both disabled.
|
||||
|
||||
- rockchip,ddr3_drv : When dram type is DDR3, this parameter define
|
||||
the dram side driver stength in ohm, default
|
||||
- rockchip,ddr3_drv : When the DRAM type is DDR3, this parameter defines
|
||||
the DRAM side driver strength in ohms. Default
|
||||
value is DDR3_DS_40ohm.
|
||||
|
||||
- rockchip,ddr3_odt : When dram type is DDR3, this parameter define
|
||||
the dram side ODT stength in ohm, default value
|
||||
- rockchip,ddr3_odt : When the DRAM type is DDR3, this parameter defines
|
||||
the DRAM side ODT strength in ohms. Default value
|
||||
is DDR3_ODT_120ohm.
|
||||
|
||||
- rockchip,phy_ddr3_ca_drv : When dram type is DDR3, this parameter define
|
||||
- rockchip,phy_ddr3_ca_drv : When the DRAM type is DDR3, this parameter defines
|
||||
the phy side CA line (incluing command line,
|
||||
address line and clock line) driver strength.
|
||||
Default value is PHY_DRV_ODT_40.
|
||||
|
||||
- rockchip,phy_ddr3_dq_drv : When dram type is DDR3, this parameter define
|
||||
the phy side DQ line(incluing DQS/DQ/DM line)
|
||||
driver strength. default value is PHY_DRV_ODT_40.
|
||||
- rockchip,phy_ddr3_dq_drv : When the DRAM type is DDR3, this parameter defines
|
||||
the PHY side DQ line (including DQS/DQ/DM line)
|
||||
driver strength. Default value is PHY_DRV_ODT_40.
|
||||
|
||||
- rockchip,phy_ddr3_odt : When dram type is DDR3, this parameter define the
|
||||
phy side odt strength, default value is
|
||||
- rockchip,phy_ddr3_odt : When the DRAM type is DDR3, this parameter defines
|
||||
the PHY side ODT strength. Default value is
|
||||
PHY_DRV_ODT_240.
|
||||
|
||||
- rockchip,lpddr3_odt_disb_freq : When dram type is LPDDR3, this parameter defined
|
||||
then odt disable frequency in MHz (Mega Hz),
|
||||
when ddr frequency less then ddr3_odt_disb_freq,
|
||||
the odt on dram side and controller side are
|
||||
- rockchip,lpddr3_odt_dis_freq : When the DRAM type is LPDDR3, this parameter defines
|
||||
then ODT disable frequency in MHz (Mega Hz).
|
||||
When DDR frequency is less then ddr3_odt_dis_freq,
|
||||
the ODT on the DRAM side and controller side are
|
||||
both disabled.
|
||||
|
||||
- rockchip,lpddr3_drv : When dram type is LPDDR3, this parameter define
|
||||
the dram side driver stength in ohm, default
|
||||
- rockchip,lpddr3_drv : When the DRAM type is LPDDR3, this parameter defines
|
||||
the DRAM side driver strength in ohms. Default
|
||||
value is LP3_DS_34ohm.
|
||||
|
||||
- rockchip,lpddr3_odt : When dram type is LPDDR3, this parameter define
|
||||
the dram side ODT stength in ohm, default value
|
||||
- rockchip,lpddr3_odt : When the DRAM type is LPDDR3, this parameter defines
|
||||
the DRAM side ODT strength in ohms. Default value
|
||||
is LP3_ODT_240ohm.
|
||||
|
||||
- rockchip,phy_lpddr3_ca_drv : When dram type is LPDDR3, this parameter define
|
||||
the phy side CA line(incluing command line,
|
||||
- rockchip,phy_lpddr3_ca_drv : When the DRAM type is LPDDR3, this parameter defines
|
||||
the PHY side CA line (including command line,
|
||||
address line and clock line) driver strength.
|
||||
default value is PHY_DRV_ODT_40.
|
||||
Default value is PHY_DRV_ODT_40.
|
||||
|
||||
- rockchip,phy_lpddr3_dq_drv : When dram type is LPDDR3, this parameter define
|
||||
the phy side DQ line(incluing DQS/DQ/DM line)
|
||||
driver strength. default value is
|
||||
- rockchip,phy_lpddr3_dq_drv : When the DRAM type is LPDDR3, this parameter defines
|
||||
the PHY side DQ line (including DQS/DQ/DM line)
|
||||
driver strength. Default value is
|
||||
PHY_DRV_ODT_40.
|
||||
|
||||
- rockchip,phy_lpddr3_odt : When dram type is LPDDR3, this parameter define
|
||||
the phy side odt strength, default value is
|
||||
PHY_DRV_ODT_240.
|
||||
|
||||
- rockchip,lpddr4_odt_disb_freq : When dram type is LPDDR4, this parameter
|
||||
defined the odt disable frequency in
|
||||
MHz (Mega Hz), when ddr frequency less then
|
||||
ddr3_odt_disb_freq, the odt on dram side and
|
||||
- rockchip,lpddr4_odt_dis_freq : When the DRAM type is LPDDR4, this parameter
|
||||
defines the ODT disable frequency in
|
||||
MHz (Mega Hz). When the DDR frequency is less then
|
||||
ddr3_odt_dis_freq, the ODT on the DRAM side and
|
||||
controller side are both disabled.
|
||||
|
||||
- rockchip,lpddr4_drv : When dram type is LPDDR4, this parameter define
|
||||
the dram side driver stength in ohm, default
|
||||
- rockchip,lpddr4_drv : When the DRAM type is LPDDR4, this parameter defines
|
||||
the DRAM side driver strength in ohms. Default
|
||||
value is LP4_PDDS_60ohm.
|
||||
|
||||
- rockchip,lpddr4_dq_odt : When dram type is LPDDR4, this parameter define
|
||||
the dram side ODT on dqs/dq line stength in ohm,
|
||||
default value is LP4_DQ_ODT_40ohm.
|
||||
- rockchip,lpddr4_dq_odt : When the DRAM type is LPDDR4, this parameter defines
|
||||
the DRAM side ODT on DQS/DQ line strength in ohms.
|
||||
Default value is LP4_DQ_ODT_40ohm.
|
||||
|
||||
- rockchip,lpddr4_ca_odt : When dram type is LPDDR4, this parameter define
|
||||
the dram side ODT on ca line stength in ohm,
|
||||
default value is LP4_CA_ODT_40ohm.
|
||||
- rockchip,lpddr4_ca_odt : When the DRAM type is LPDDR4, this parameter defines
|
||||
the DRAM side ODT on CA line strength in ohms.
|
||||
Default value is LP4_CA_ODT_40ohm.
|
||||
|
||||
- rockchip,phy_lpddr4_ca_drv : When dram type is LPDDR4, this parameter define
|
||||
the phy side CA line(incluing command address
|
||||
line) driver strength. default value is
|
||||
- rockchip,phy_lpddr4_ca_drv : When the DRAM type is LPDDR4, this parameter defines
|
||||
the PHY side CA line (including command address
|
||||
line) driver strength. Default value is
|
||||
PHY_DRV_ODT_40.
|
||||
|
||||
- rockchip,phy_lpddr4_ck_cs_drv : When dram type is LPDDR4, this parameter define
|
||||
the phy side clock line and cs line driver
|
||||
strength. default value is PHY_DRV_ODT_80.
|
||||
- rockchip,phy_lpddr4_ck_cs_drv : When the DRAM type is LPDDR4, this parameter defines
|
||||
the PHY side clock line and CS line driver
|
||||
strength. Default value is PHY_DRV_ODT_80.
|
||||
|
||||
- rockchip,phy_lpddr4_dq_drv : When dram type is LPDDR4, this parameter define
|
||||
the phy side DQ line(incluing DQS/DQ/DM line)
|
||||
driver strength. default value is PHY_DRV_ODT_80.
|
||||
- rockchip,phy_lpddr4_dq_drv : When the DRAM type is LPDDR4, this parameter defines
|
||||
the PHY side DQ line (including DQS/DQ/DM line)
|
||||
driver strength. Default value is PHY_DRV_ODT_80.
|
||||
|
||||
- rockchip,phy_lpddr4_odt : When dram type is LPDDR4, this parameter define
|
||||
the phy side odt strength, default value is
|
||||
- rockchip,phy_lpddr4_odt : When the DRAM type is LPDDR4, this parameter defines
|
||||
the PHY side ODT strength. Default value is
|
||||
PHY_DRV_ODT_60.
|
||||
|
||||
Example:
|
||||
|
|
|
@ -604,28 +604,29 @@ struct devfreq *devfreq_add_device(struct device *dev,
|
|||
mutex_lock(&devfreq->lock);
|
||||
}
|
||||
|
||||
devfreq->min_freq = find_available_min_freq(devfreq);
|
||||
if (!devfreq->min_freq) {
|
||||
devfreq->scaling_min_freq = find_available_min_freq(devfreq);
|
||||
if (!devfreq->scaling_min_freq) {
|
||||
mutex_unlock(&devfreq->lock);
|
||||
err = -EINVAL;
|
||||
goto err_dev;
|
||||
}
|
||||
devfreq->scaling_min_freq = devfreq->min_freq;
|
||||
devfreq->min_freq = devfreq->scaling_min_freq;
|
||||
|
||||
devfreq->max_freq = find_available_max_freq(devfreq);
|
||||
if (!devfreq->max_freq) {
|
||||
devfreq->scaling_max_freq = find_available_max_freq(devfreq);
|
||||
if (!devfreq->scaling_max_freq) {
|
||||
mutex_unlock(&devfreq->lock);
|
||||
err = -EINVAL;
|
||||
goto err_dev;
|
||||
}
|
||||
devfreq->scaling_max_freq = devfreq->max_freq;
|
||||
devfreq->max_freq = devfreq->scaling_max_freq;
|
||||
|
||||
dev_set_name(&devfreq->dev, "devfreq%d",
|
||||
atomic_inc_return(&devfreq_no));
|
||||
err = device_register(&devfreq->dev);
|
||||
if (err) {
|
||||
mutex_unlock(&devfreq->lock);
|
||||
goto err_dev;
|
||||
put_device(&devfreq->dev);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
devfreq->trans_table =
|
||||
|
@ -672,6 +673,7 @@ err_init:
|
|||
mutex_unlock(&devfreq_list_lock);
|
||||
|
||||
device_unregister(&devfreq->dev);
|
||||
devfreq = NULL;
|
||||
err_dev:
|
||||
if (devfreq)
|
||||
kfree(devfreq);
|
||||
|
|
|
@ -627,11 +627,9 @@ static int exynos_ppmu_probe(struct platform_device *pdev)
|
|||
|
||||
size = sizeof(struct devfreq_event_dev *) * info->num_events;
|
||||
info->edev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
||||
if (!info->edev) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to allocate memory devfreq-event devices\n");
|
||||
if (!info->edev)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
edev = info->edev;
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
|
|
|
@ -68,15 +68,6 @@ struct rk3399_dmcfreq {
|
|||
struct devfreq_event_dev *edev;
|
||||
struct mutex lock;
|
||||
struct dram_timing timing;
|
||||
|
||||
/*
|
||||
* DDR Converser of Frequency (DCF) is used to implement DDR frequency
|
||||
* conversion without the participation of CPU, we will implement and
|
||||
* control it in arm trust firmware.
|
||||
*/
|
||||
wait_queue_head_t wait_dcf_queue;
|
||||
int irq;
|
||||
int wait_dcf_flag;
|
||||
struct regulator *vdd_center;
|
||||
unsigned long rate, target_rate;
|
||||
unsigned long volt, target_volt;
|
||||
|
@ -112,30 +103,21 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
|
|||
err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
|
||||
target_volt);
|
||||
if (err) {
|
||||
dev_err(dev, "Cannot to set voltage %lu uV\n",
|
||||
dev_err(dev, "Cannot set voltage %lu uV\n",
|
||||
target_volt);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
dmcfreq->wait_dcf_flag = 1;
|
||||
|
||||
err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
|
||||
if (err) {
|
||||
dev_err(dev, "Cannot to set frequency %lu (%d)\n",
|
||||
target_rate, err);
|
||||
dev_err(dev, "Cannot set frequency %lu (%d)\n", target_rate,
|
||||
err);
|
||||
regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
|
||||
dmcfreq->volt);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait until bcf irq happen, it means freq scaling finish in
|
||||
* arm trust firmware, use 100ms as timeout time.
|
||||
*/
|
||||
if (!wait_event_timeout(dmcfreq->wait_dcf_queue,
|
||||
!dmcfreq->wait_dcf_flag, HZ / 10))
|
||||
dev_warn(dev, "Timeout waiting for dcf interrupt\n");
|
||||
|
||||
/*
|
||||
* Check the dpll rate,
|
||||
* There only two result we will get,
|
||||
|
@ -146,8 +128,8 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
|
|||
|
||||
/* If get the incorrect rate, set voltage to old value. */
|
||||
if (dmcfreq->rate != target_rate) {
|
||||
dev_err(dev, "Get wrong ddr frequency, Request frequency %lu,\
|
||||
Current frequency %lu\n", target_rate, dmcfreq->rate);
|
||||
dev_err(dev, "Got wrong frequency, Request %lu, Current %lu\n",
|
||||
target_rate, dmcfreq->rate);
|
||||
regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
|
||||
dmcfreq->volt);
|
||||
goto out;
|
||||
|
@ -155,7 +137,7 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
|
|||
err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
|
||||
target_volt);
|
||||
if (err)
|
||||
dev_err(dev, "Cannot to set vol %lu uV\n", target_volt);
|
||||
dev_err(dev, "Cannot set voltage %lu uV\n", target_volt);
|
||||
|
||||
dmcfreq->rate = target_rate;
|
||||
dmcfreq->volt = target_volt;
|
||||
|
@ -241,22 +223,6 @@ static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev)
|
|||
static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend,
|
||||
rk3399_dmcfreq_resume);
|
||||
|
||||
static irqreturn_t rk3399_dmc_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct rk3399_dmcfreq *dmcfreq = dev_id;
|
||||
struct arm_smccc_res res;
|
||||
|
||||
dmcfreq->wait_dcf_flag = 0;
|
||||
wake_up(&dmcfreq->wait_dcf_queue);
|
||||
|
||||
/* Clear the DCF interrupt */
|
||||
arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0,
|
||||
ROCKCHIP_SIP_CONFIG_DRAM_CLR_IRQ,
|
||||
0, 0, 0, 0, &res);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int of_get_ddr_timings(struct dram_timing *timing,
|
||||
struct device_node *np)
|
||||
{
|
||||
|
@ -330,16 +296,10 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct rk3399_dmcfreq *data;
|
||||
int ret, irq, index, size;
|
||||
int ret, index, size;
|
||||
uint32_t *timing;
|
||||
struct dev_pm_opp *opp;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"Cannot get the dmc interrupt resource: %d\n", irq);
|
||||
return irq;
|
||||
}
|
||||
data = devm_kzalloc(dev, sizeof(struct rk3399_dmcfreq), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
@ -348,27 +308,22 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
|
||||
data->vdd_center = devm_regulator_get(dev, "center");
|
||||
if (IS_ERR(data->vdd_center)) {
|
||||
if (PTR_ERR(data->vdd_center) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
dev_err(dev, "Cannot get the regulator \"center\"\n");
|
||||
return PTR_ERR(data->vdd_center);
|
||||
}
|
||||
|
||||
data->dmc_clk = devm_clk_get(dev, "dmc_clk");
|
||||
if (IS_ERR(data->dmc_clk)) {
|
||||
if (PTR_ERR(data->dmc_clk) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
dev_err(dev, "Cannot get the clk dmc_clk\n");
|
||||
return PTR_ERR(data->dmc_clk);
|
||||
};
|
||||
|
||||
data->irq = irq;
|
||||
ret = devm_request_irq(dev, irq, rk3399_dmc_irq, 0,
|
||||
dev_name(dev), data);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to request dmc irq: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
init_waitqueue_head(&data->wait_dcf_queue);
|
||||
data->wait_dcf_flag = 0;
|
||||
|
||||
data->edev = devfreq_event_get_edev_by_phandle(dev, 0);
|
||||
if (IS_ERR(data->edev))
|
||||
return -EPROBE_DEFER;
|
||||
|
@ -420,8 +375,10 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
data->rate = clk_get_rate(data->dmc_clk);
|
||||
|
||||
opp = devfreq_recommended_opp(dev, &data->rate, 0);
|
||||
if (IS_ERR(opp))
|
||||
return PTR_ERR(opp);
|
||||
if (IS_ERR(opp)) {
|
||||
ret = PTR_ERR(opp);
|
||||
goto err_free_opp;
|
||||
}
|
||||
|
||||
data->rate = dev_pm_opp_get_freq(opp);
|
||||
data->volt = dev_pm_opp_get_voltage(opp);
|
||||
|
@ -433,13 +390,33 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
&rk3399_devfreq_dmc_profile,
|
||||
DEVFREQ_GOV_SIMPLE_ONDEMAND,
|
||||
&data->ondemand_data);
|
||||
if (IS_ERR(data->devfreq))
|
||||
return PTR_ERR(data->devfreq);
|
||||
if (IS_ERR(data->devfreq)) {
|
||||
ret = PTR_ERR(data->devfreq);
|
||||
goto err_free_opp;
|
||||
}
|
||||
|
||||
devm_devfreq_register_opp_notifier(dev, data->devfreq);
|
||||
|
||||
data->dev = dev;
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_opp:
|
||||
dev_pm_opp_of_remove_table(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk3399_dmcfreq_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rk3399_dmcfreq *dmcfreq = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
/*
|
||||
* Before remove the opp table we need to unregister the opp notifier.
|
||||
*/
|
||||
devm_devfreq_unregister_opp_notifier(dmcfreq->dev, dmcfreq->devfreq);
|
||||
dev_pm_opp_of_remove_table(dmcfreq->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -451,6 +428,7 @@ MODULE_DEVICE_TABLE(of, rk3399dmc_devfreq_of_match);
|
|||
|
||||
static struct platform_driver rk3399_dmcfreq_driver = {
|
||||
.probe = rk3399_dmcfreq_probe,
|
||||
.remove = rk3399_dmcfreq_remove,
|
||||
.driver = {
|
||||
.name = "rk3399-dmc-freq",
|
||||
.pm = &rk3399_dmcfreq_pm,
|
||||
|
|
|
@ -29,4 +29,14 @@ config INTEL_RAPL
|
|||
controller, CPU core (Power Plance 0), graphics uncore (Power Plane
|
||||
1), etc.
|
||||
|
||||
config IDLE_INJECT
|
||||
bool "Idle injection framework"
|
||||
depends on CPU_IDLE
|
||||
default n
|
||||
help
|
||||
This enables support for the idle injection framework. It
|
||||
provides a way to force idle periods on a set of specified
|
||||
CPUs for power capping. Idle period can be injected
|
||||
synchronously on a set of specified CPUs or alternatively
|
||||
on a per CPU basis.
|
||||
endif
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
obj-$(CONFIG_POWERCAP) += powercap_sys.o
|
||||
obj-$(CONFIG_INTEL_RAPL) += intel_rapl.o
|
||||
obj-$(CONFIG_IDLE_INJECT) += idle_inject.o
|
||||
|
|
356
drivers/powercap/idle_inject.c
Normal file
356
drivers/powercap/idle_inject.c
Normal file
|
@ -0,0 +1,356 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2018 Linaro Limited
|
||||
*
|
||||
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
*
|
||||
* The idle injection framework provides a way to force CPUs to enter idle
|
||||
* states for a specified fraction of time over a specified period.
|
||||
*
|
||||
* It relies on the smpboot kthreads feature providing common code for CPU
|
||||
* hotplug and thread [un]parking.
|
||||
*
|
||||
* All of the kthreads used for idle injection are created at init time.
|
||||
*
|
||||
* Next, the users of the the idle injection framework provide a cpumask via
|
||||
* its register function. The kthreads will be synchronized with respect to
|
||||
* this cpumask.
|
||||
*
|
||||
* The idle + run duration is specified via separate helpers and that allows
|
||||
* idle injection to be started.
|
||||
*
|
||||
* The idle injection kthreads will call play_idle() with the idle duration
|
||||
* specified as per the above.
|
||||
*
|
||||
* After all of them have been woken up, a timer is set to start the next idle
|
||||
* injection cycle.
|
||||
*
|
||||
* The timer interrupt handler will wake up the idle injection kthreads for
|
||||
* all of the CPUs in the cpumask provided by the user.
|
||||
*
|
||||
* Idle injection is stopped synchronously and no leftover idle injection
|
||||
* kthread activity after its completion is guaranteed.
|
||||
*
|
||||
* It is up to the user of this framework to provide a lock for higher-level
|
||||
* synchronization to prevent race conditions like starting idle injection
|
||||
* while unregistering from the framework.
|
||||
*/
|
||||
#define pr_fmt(fmt) "ii_dev: " fmt
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/smpboot.h>
|
||||
|
||||
#include <uapi/linux/sched/types.h>
|
||||
|
||||
/**
|
||||
* struct idle_inject_thread - task on/off switch structure
|
||||
* @tsk: task injecting the idle cycles
|
||||
* @should_run: whether or not to run the task (for the smpboot kthread API)
|
||||
*/
|
||||
struct idle_inject_thread {
|
||||
struct task_struct *tsk;
|
||||
int should_run;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct idle_inject_device - idle injection data
|
||||
* @timer: idle injection period timer
|
||||
* @idle_duration_ms: duration of CPU idle time to inject
|
||||
* @run_duration_ms: duration of CPU run time to allow
|
||||
* @cpumask: mask of CPUs affected by idle injection
|
||||
*/
|
||||
struct idle_inject_device {
|
||||
struct hrtimer timer;
|
||||
unsigned int idle_duration_ms;
|
||||
unsigned int run_duration_ms;
|
||||
unsigned long int cpumask[0];
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct idle_inject_thread, idle_inject_thread);
|
||||
static DEFINE_PER_CPU(struct idle_inject_device *, idle_inject_device);
|
||||
|
||||
/**
|
||||
* idle_inject_wakeup - Wake up idle injection threads
|
||||
* @ii_dev: target idle injection device
|
||||
*
|
||||
* Every idle injection task associated with the given idle injection device
|
||||
* and running on an online CPU will be woken up.
|
||||
*/
|
||||
static void idle_inject_wakeup(struct idle_inject_device *ii_dev)
|
||||
{
|
||||
struct idle_inject_thread *iit;
|
||||
unsigned int cpu;
|
||||
|
||||
for_each_cpu_and(cpu, to_cpumask(ii_dev->cpumask), cpu_online_mask) {
|
||||
iit = per_cpu_ptr(&idle_inject_thread, cpu);
|
||||
iit->should_run = 1;
|
||||
wake_up_process(iit->tsk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_timer_fn - idle injection timer function
|
||||
* @timer: idle injection hrtimer
|
||||
*
|
||||
* This function is called when the idle injection timer expires. It wakes up
|
||||
* idle injection tasks associated with the timer and they, in turn, invoke
|
||||
* play_idle() to inject a specified amount of CPU idle time.
|
||||
*
|
||||
* Return: HRTIMER_RESTART.
|
||||
*/
|
||||
static enum hrtimer_restart idle_inject_timer_fn(struct hrtimer *timer)
|
||||
{
|
||||
unsigned int duration_ms;
|
||||
struct idle_inject_device *ii_dev =
|
||||
container_of(timer, struct idle_inject_device, timer);
|
||||
|
||||
duration_ms = READ_ONCE(ii_dev->run_duration_ms);
|
||||
duration_ms += READ_ONCE(ii_dev->idle_duration_ms);
|
||||
|
||||
idle_inject_wakeup(ii_dev);
|
||||
|
||||
hrtimer_forward_now(timer, ms_to_ktime(duration_ms));
|
||||
|
||||
return HRTIMER_RESTART;
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_fn - idle injection work function
|
||||
* @cpu: the CPU owning the task
|
||||
*
|
||||
* This function calls play_idle() to inject a specified amount of CPU idle
|
||||
* time.
|
||||
*/
|
||||
static void idle_inject_fn(unsigned int cpu)
|
||||
{
|
||||
struct idle_inject_device *ii_dev;
|
||||
struct idle_inject_thread *iit;
|
||||
|
||||
ii_dev = per_cpu(idle_inject_device, cpu);
|
||||
iit = per_cpu_ptr(&idle_inject_thread, cpu);
|
||||
|
||||
/*
|
||||
* Let the smpboot main loop know that the task should not run again.
|
||||
*/
|
||||
iit->should_run = 0;
|
||||
|
||||
play_idle(READ_ONCE(ii_dev->idle_duration_ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_set_duration - idle and run duration update helper
|
||||
* @run_duration_ms: CPU run time to allow in milliseconds
|
||||
* @idle_duration_ms: CPU idle time to inject in milliseconds
|
||||
*/
|
||||
void idle_inject_set_duration(struct idle_inject_device *ii_dev,
|
||||
unsigned int run_duration_ms,
|
||||
unsigned int idle_duration_ms)
|
||||
{
|
||||
if (run_duration_ms && idle_duration_ms) {
|
||||
WRITE_ONCE(ii_dev->run_duration_ms, run_duration_ms);
|
||||
WRITE_ONCE(ii_dev->idle_duration_ms, idle_duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_get_duration - idle and run duration retrieval helper
|
||||
* @run_duration_ms: memory location to store the current CPU run time
|
||||
* @idle_duration_ms: memory location to store the current CPU idle time
|
||||
*/
|
||||
void idle_inject_get_duration(struct idle_inject_device *ii_dev,
|
||||
unsigned int *run_duration_ms,
|
||||
unsigned int *idle_duration_ms)
|
||||
{
|
||||
*run_duration_ms = READ_ONCE(ii_dev->run_duration_ms);
|
||||
*idle_duration_ms = READ_ONCE(ii_dev->idle_duration_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_start - start idle injections
|
||||
* @ii_dev: idle injection control device structure
|
||||
*
|
||||
* The function starts idle injection by first waking up all of the idle
|
||||
* injection kthreads associated with @ii_dev to let them inject CPU idle time
|
||||
* sets up a timer to start the next idle injection period.
|
||||
*
|
||||
* Return: -EINVAL if the CPU idle or CPU run time is not set or 0 on success.
|
||||
*/
|
||||
int idle_inject_start(struct idle_inject_device *ii_dev)
|
||||
{
|
||||
unsigned int idle_duration_ms = READ_ONCE(ii_dev->idle_duration_ms);
|
||||
unsigned int run_duration_ms = READ_ONCE(ii_dev->run_duration_ms);
|
||||
|
||||
if (!idle_duration_ms || !run_duration_ms)
|
||||
return -EINVAL;
|
||||
|
||||
pr_debug("Starting injecting idle cycles on CPUs '%*pbl'\n",
|
||||
cpumask_pr_args(to_cpumask(ii_dev->cpumask)));
|
||||
|
||||
idle_inject_wakeup(ii_dev);
|
||||
|
||||
hrtimer_start(&ii_dev->timer,
|
||||
ms_to_ktime(idle_duration_ms + run_duration_ms),
|
||||
HRTIMER_MODE_REL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_stop - stops idle injections
|
||||
* @ii_dev: idle injection control device structure
|
||||
*
|
||||
* The function stops idle injection and waits for the threads to finish work.
|
||||
* If CPU idle time is being injected when this function runs, then it will
|
||||
* wait until the end of the cycle.
|
||||
*
|
||||
* When it returns, there is no more idle injection kthread activity. The
|
||||
* kthreads are scheduled out and the periodic timer is off.
|
||||
*/
|
||||
void idle_inject_stop(struct idle_inject_device *ii_dev)
|
||||
{
|
||||
struct idle_inject_thread *iit;
|
||||
unsigned int cpu;
|
||||
|
||||
pr_debug("Stopping idle injection on CPUs '%*pbl'\n",
|
||||
cpumask_pr_args(to_cpumask(ii_dev->cpumask)));
|
||||
|
||||
hrtimer_cancel(&ii_dev->timer);
|
||||
|
||||
/*
|
||||
* Stopping idle injection requires all of the idle injection kthreads
|
||||
* associated with the given cpumask to be parked and stay that way, so
|
||||
* prevent CPUs from going online at this point. Any CPUs going online
|
||||
* after the loop below will be covered by clearing the should_run flag
|
||||
* that will cause the smpboot main loop to schedule them out.
|
||||
*/
|
||||
cpu_hotplug_disable();
|
||||
|
||||
/*
|
||||
* Iterate over all (online + offline) CPUs here in case one of them
|
||||
* goes offline with the should_run flag set so as to prevent its idle
|
||||
* injection kthread from running when the CPU goes online again after
|
||||
* the ii_dev has been freed.
|
||||
*/
|
||||
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) {
|
||||
iit = per_cpu_ptr(&idle_inject_thread, cpu);
|
||||
iit->should_run = 0;
|
||||
|
||||
wait_task_inactive(iit->tsk, 0);
|
||||
}
|
||||
|
||||
cpu_hotplug_enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_setup - prepare the current task for idle injection
|
||||
* @cpu: not used
|
||||
*
|
||||
* Called once, this function is in charge of setting the current task's
|
||||
* scheduler parameters to make it an RT task.
|
||||
*/
|
||||
static void idle_inject_setup(unsigned int cpu)
|
||||
{
|
||||
struct sched_param param = { .sched_priority = MAX_USER_RT_PRIO / 2 };
|
||||
|
||||
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_should_run - function helper for the smpboot API
|
||||
* @cpu: CPU the kthread is running on
|
||||
*
|
||||
* Return: whether or not the thread can run.
|
||||
*/
|
||||
static int idle_inject_should_run(unsigned int cpu)
|
||||
{
|
||||
struct idle_inject_thread *iit =
|
||||
per_cpu_ptr(&idle_inject_thread, cpu);
|
||||
|
||||
return iit->should_run;
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_register - initialize idle injection on a set of CPUs
|
||||
* @cpumask: CPUs to be affected by idle injection
|
||||
*
|
||||
* This function creates an idle injection control device structure for the
|
||||
* given set of CPUs and initializes the timer associated with it. It does not
|
||||
* start any injection cycles.
|
||||
*
|
||||
* Return: NULL if memory allocation fails, idle injection control device
|
||||
* pointer on success.
|
||||
*/
|
||||
struct idle_inject_device *idle_inject_register(struct cpumask *cpumask)
|
||||
{
|
||||
struct idle_inject_device *ii_dev;
|
||||
int cpu, cpu_rb;
|
||||
|
||||
ii_dev = kzalloc(sizeof(*ii_dev) + cpumask_size(), GFP_KERNEL);
|
||||
if (!ii_dev)
|
||||
return NULL;
|
||||
|
||||
cpumask_copy(to_cpumask(ii_dev->cpumask), cpumask);
|
||||
hrtimer_init(&ii_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
ii_dev->timer.function = idle_inject_timer_fn;
|
||||
|
||||
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) {
|
||||
|
||||
if (per_cpu(idle_inject_device, cpu)) {
|
||||
pr_err("cpu%d is already registered\n", cpu);
|
||||
goto out_rollback;
|
||||
}
|
||||
|
||||
per_cpu(idle_inject_device, cpu) = ii_dev;
|
||||
}
|
||||
|
||||
return ii_dev;
|
||||
|
||||
out_rollback:
|
||||
for_each_cpu(cpu_rb, to_cpumask(ii_dev->cpumask)) {
|
||||
if (cpu == cpu_rb)
|
||||
break;
|
||||
per_cpu(idle_inject_device, cpu_rb) = NULL;
|
||||
}
|
||||
|
||||
kfree(ii_dev);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_unregister - unregister idle injection control device
|
||||
* @ii_dev: idle injection control device to unregister
|
||||
*
|
||||
* The function stops idle injection for the given control device,
|
||||
* unregisters its kthreads and frees memory allocated when that device was
|
||||
* created.
|
||||
*/
|
||||
void idle_inject_unregister(struct idle_inject_device *ii_dev)
|
||||
{
|
||||
unsigned int cpu;
|
||||
|
||||
idle_inject_stop(ii_dev);
|
||||
|
||||
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask))
|
||||
per_cpu(idle_inject_device, cpu) = NULL;
|
||||
|
||||
kfree(ii_dev);
|
||||
}
|
||||
|
||||
static struct smp_hotplug_thread idle_inject_threads = {
|
||||
.store = &idle_inject_thread.tsk,
|
||||
.setup = idle_inject_setup,
|
||||
.thread_fn = idle_inject_fn,
|
||||
.thread_comm = "idle_inject/%u",
|
||||
.thread_should_run = idle_inject_should_run,
|
||||
};
|
||||
|
||||
static int __init idle_inject_init(void)
|
||||
{
|
||||
return smpboot_register_percpu_thread(&idle_inject_threads);
|
||||
}
|
||||
early_initcall(idle_inject_init);
|
56
include/dt-bindings/clock/rk3399-ddr.h
Normal file
56
include/dt-bindings/clock/rk3399-ddr.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
|
||||
|
||||
#ifndef DT_BINDINGS_DDR_H
|
||||
#define DT_BINDINGS_DDR_H
|
||||
|
||||
/*
|
||||
* DDR3 SDRAM Standard Speed Bins include tCK, tRCD, tRP, tRAS and tRC for
|
||||
* each corresponding bin.
|
||||
*/
|
||||
|
||||
/* DDR3-800 (5-5-5) */
|
||||
#define DDR3_800D 0
|
||||
/* DDR3-800 (6-6-6) */
|
||||
#define DDR3_800E 1
|
||||
/* DDR3-1066 (6-6-6) */
|
||||
#define DDR3_1066E 2
|
||||
/* DDR3-1066 (7-7-7) */
|
||||
#define DDR3_1066F 3
|
||||
/* DDR3-1066 (8-8-8) */
|
||||
#define DDR3_1066G 4
|
||||
/* DDR3-1333 (7-7-7) */
|
||||
#define DDR3_1333F 5
|
||||
/* DDR3-1333 (8-8-8) */
|
||||
#define DDR3_1333G 6
|
||||
/* DDR3-1333 (9-9-9) */
|
||||
#define DDR3_1333H 7
|
||||
/* DDR3-1333 (10-10-10) */
|
||||
#define DDR3_1333J 8
|
||||
/* DDR3-1600 (8-8-8) */
|
||||
#define DDR3_1600G 9
|
||||
/* DDR3-1600 (9-9-9) */
|
||||
#define DDR3_1600H 10
|
||||
/* DDR3-1600 (10-10-10) */
|
||||
#define DDR3_1600J 11
|
||||
/* DDR3-1600 (11-11-11) */
|
||||
#define DDR3_1600K 12
|
||||
/* DDR3-1600 (10-10-10) */
|
||||
#define DDR3_1866J 13
|
||||
/* DDR3-1866 (11-11-11) */
|
||||
#define DDR3_1866K 14
|
||||
/* DDR3-1866 (12-12-12) */
|
||||
#define DDR3_1866L 15
|
||||
/* DDR3-1866 (13-13-13) */
|
||||
#define DDR3_1866M 16
|
||||
/* DDR3-2133 (11-11-11) */
|
||||
#define DDR3_2133K 17
|
||||
/* DDR3-2133 (12-12-12) */
|
||||
#define DDR3_2133L 18
|
||||
/* DDR3-2133 (13-13-13) */
|
||||
#define DDR3_2133M 19
|
||||
/* DDR3-2133 (14-14-14) */
|
||||
#define DDR3_2133N 20
|
||||
/* DDR3 ATF default */
|
||||
#define DDR3_DEFAULT 21
|
||||
|
||||
#endif
|
29
include/linux/idle_inject.h
Normal file
29
include/linux/idle_inject.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2018 Linaro Ltd
|
||||
*
|
||||
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
*
|
||||
*/
|
||||
#ifndef __IDLE_INJECT_H__
|
||||
#define __IDLE_INJECT_H__
|
||||
|
||||
/* private idle injection device structure */
|
||||
struct idle_inject_device;
|
||||
|
||||
struct idle_inject_device *idle_inject_register(struct cpumask *cpumask);
|
||||
|
||||
void idle_inject_unregister(struct idle_inject_device *ii_dev);
|
||||
|
||||
int idle_inject_start(struct idle_inject_device *ii_dev);
|
||||
|
||||
void idle_inject_stop(struct idle_inject_device *ii_dev);
|
||||
|
||||
void idle_inject_set_duration(struct idle_inject_device *ii_dev,
|
||||
unsigned int run_duration_ms,
|
||||
unsigned int idle_duration_ms);
|
||||
|
||||
void idle_inject_get_duration(struct idle_inject_device *ii_dev,
|
||||
unsigned int *run_duration_ms,
|
||||
unsigned int *idle_duration_ms);
|
||||
#endif /* __IDLE_INJECT_H__ */
|
Loading…
Reference in a new issue