* firewire_ohci resume failure on intel HW
From: Apostolos Bartziokas @ 2012-08-28 13:08 UTC (permalink / raw)
To: linux-pm
Hello
I am trying to get rid of SUSPEND_MODULES from my system and
firewire_ohci prevents the system from resuming when i suspend.
If i remove the modules file i end up wit a blinking cursor on a blank
screen.
This was also the case with kernels 3.3 and 3.4.
lspci
00:00.0 Host bridge: Intel Corporation Xeon E3-1200 v2/3rd Gen Core
processor DRAM Controller (rev 09)
00:01.0 PCI bridge: Intel Corporation Xeon E3-1200 v2/3rd Gen Core
processor PCI Express Root Port (rev 09)
00:14.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset
Family USB xHCI Host Controller (rev 04)
00:16.0 Communication controller: Intel Corporation 7 Series/C210 Series
Chipset Family MEI Controller #1 (rev 04)
00:19.0 Ethernet controller: Intel Corporation 82579V Gigabit Network
Connection (rev 04)
00:1a.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset
Family USB Enhanced Host Controller #2 (rev 04)
00:1b.0 Audio device: Intel Corporation 7 Series/C210 Series Chipset
Family High Definition Audio Controller (rev 04)
00:1c.0 PCI bridge: Intel Corporation 7 Series/C210 Series Chipset
Family PCI Express Root Port 1 (rev c4)
00:1c.2 PCI bridge: Intel Corporation 7 Series/C210 Series Chipset
Family PCI Express Root Port 3 (rev c4)
00:1c.4 PCI bridge: Intel Corporation 7 Series/C210 Series Chipset
Family PCI Express Root Port 5 (rev c4)
00:1c.7 PCI bridge: Intel Corporation 7 Series/C210 Series Chipset
Family PCI Express Root Port 8 (rev c4)
00:1d.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset
Family USB Enhanced Host Controller #1 (rev 04)
00:1f.0 ISA bridge: Intel Corporation Z77 Express Chipset LPC Controller
(rev 04)
00:1f.2 SATA controller: Intel Corporation 7 Series/C210 Series Chipset
Family 6-port SATA Controller [AHCI mode] (rev 04)
00:1f.3 SMBus: Intel Corporation 7 Series/C210 Series Chipset Family
SMBus Controller (rev 04)
01:00.0 VGA compatible controller: Advanced Micro Devices [AMD] nee ATI
Barts PRO [Radeon HD 6800 Series]
01:00.1 Audio device: Advanced Micro Devices [AMD] nee ATI Barts HDMI
Audio [Radeon HD 6800 Series]
03:00.0 PCI bridge: Integrated Technology Express, Inc. Device 8892 (rev 30)
04:02.0 FireWire (IEEE 1394): Texas Instruments TSB43AB22A
IEEE-1394a-2000 Controller (PHY/Link) [iOHCI-Lynx]
05:00.0 USB controller: NEC Corporation uPD720200 USB 3.0 Host
Controller (rev 04)
06:00.0 SATA controller: Marvell Technology Group Ltd. 88SE9172 SATA
6Gb/s Controller (rev 11)
uname -a
Linux mainland 3.5.3-1-ARCH #1 SMP PREEMPT Sun Aug 26 09:14:51 CEST 2012
x86_64 GNU/Linux
I also see this error on my logs but i don't know if it is related to this.
Pid: 0, comm: swapper/0 Not tainted 3.5.3-1-ARCH #1
[ 3.143506] Call Trace:
[ 3.143507] <IRQ> [<ffffffff810d4d3d>] __report_bad_irq+0x3d/0xe0
[ 3.143513] [<ffffffff810d5033>] note_interrupt+0x1a3/0x1f0
[ 3.143515] [<ffffffff810d292f>] handle_irq_event_percpu+0xbf/0x260
[ 3.143516] [<ffffffff810d2b18>] handle_irq_event+0x48/0x70
[ 3.143518] [<ffffffff810d5b4a>] handle_fasteoi_irq+0x5a/0x100
[ 3.143520] [<ffffffff810160c2>] handle_irq+0x22/0x40
[ 3.143522] [<ffffffff81484cea>] do_IRQ+0x5a/0xe0
[ 3.143524] [<ffffffff8147c12a>] common_interrupt+0x6a/0x6a
[ 3.143524] <EOI> [<ffffffffa02b3f01>] ?
acpi_idle_enter_c1+0xda/0x104 [processor]
[ 3.143537] [<ffffffffa02b3edc>] ? acpi_idle_enter_c1+0xb5/0x104
[processor]
[ 3.143541] [<ffffffff8134f159>] cpuidle_enter+0x19/0x20
[ 3.143542] [<ffffffff8134f7a6>] cpuidle_idle_call+0xa6/0x330
[ 3.143544] [<ffffffff8101daaf>] cpu_idle+0xbf/0x130
[ 3.143546] [<ffffffff81456fbc>] rest_init+0x80/0x84
[ 3.143548] [<ffffffff818bbc35>] start_kernel+0x3c1/0x3ce
[ 3.143550] [<ffffffff818bb673>] ? repair_env_string+0x5e/0x5e
[ 3.143551] [<ffffffff818bb356>] x86_64_start_reservations+0x131/0x135
[ 3.143553] [<ffffffff818bb45a>] x86_64_start_kernel+0x100/0x10f
Thanks in advance.
^ permalink raw reply
* RE: [PATCH v4 2/4] mfd: omap: control: core system control driver
From: AnilKumar, Chimata @ 2012-08-29 12:33 UTC (permalink / raw)
To: Konstantin Baydarov, Cousson, Benoit, ABRAHAM, KISHON VIJAY,
Shilimkar, Santosh, tony@atomide.com, paul@pwsan.com
Cc: Balbi, Felipe, amit.kucheria@linaro.org,
linux-pm@lists.linux-foundation.org,
linux-arm-kernel@lists.infradead.org, linux-omap@vger.kernel.org,
amit.kachhap@linaro.org, Valentin, Eduardo
In-Reply-To: <500FD2EC.7060208@dev.rtsoft.ru>
Hi Konstantin,
On Wed, Jul 25, 2012 at 16:35:16, Konstantin Baydarov wrote:
> This patch introduces a MFD core device driver for
> OMAP system control module.
>
> The control module allows software control of
> various static modes supported by the device. It is
> composed of two control submodules: general control
> module and device (padconfiguration) control
> module.
>
> Device driver is probed with postcore_initcall.
> Control module register CONTROL_STATUS that is needed
> by omap_type() is early mapped by postcore_initcall_sync.
>
> Signed-off-by: J Keerthy <j-keerthy@ti.com>
> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
> Signed-off-by: Eduardo Valentin <eduardo.valentin@ti.com>
> Signed-off-by: Konstantin Baydarov <kbaidarov@dev.rtsoft.ru>
> ---
> .../devicetree/bindings/mfd/omap_control.txt | 32 ++++
> arch/arm/mach-omap2/Kconfig | 1 +
> arch/arm/plat-omap/Kconfig | 4 +
> drivers/mfd/Kconfig | 6 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/omap-control-core.c | 162 ++++++++++++++++++++
> include/linux/mfd/omap_control.h | 35 +++++
> 7 files changed, 241 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/mfd/omap_control.txt
> create mode 100644 drivers/mfd/omap-control-core.c
> create mode 100644 include/linux/mfd/omap_control.h
>
> diff --git a/Documentation/devicetree/bindings/mfd/omap_control.txt b/Documentation/devicetree/bindings/mfd/omap_control.txt
> new file mode 100644
> index 0000000..a9dca9e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/omap_control.txt
> @@ -0,0 +1,32 @@
> +* Texas Instrument OMAP System Control Module (SCM) bindings
> +
> +The control module allows software control of various static modes supported by
> +the device. The control module controls the settings of various device modules
> +through register configuration and internal signals. It also controls the pad
> +configuration, pin functional multiplexing, and the routing of internal signals
> +(such as PRCM signals or DMA requests) to output pins configured for hardware
> +observability.
> +
> +Required properties:
> +- compatible : Should be:
> + - "ti,omap3-control" for OMAP3 support
> + - "ti,omap4-control" for OMAP4 support
> + - "ti,omap5-control" for OMAP5 support
> +
> +OMAP specific properties:
> +- ti,hwmods: Name of the hwmod associated to the control module:
> + Should be "ctrl_module_core";
> +
> +OMAP CONTROL_STATUS register:
> +reg = <0x4A0022C4 0x4>; /* CONTROL_STATUS register */
> +
> +Examples:
> +ctrl_module_core: ctrl_module_core@4A0022C4 {
> + compatible = "ti,omap4-control";
> + #address-cells = <1>;
> + #size-cells = <1>;
> + /* ranges; */
> + ti,hwmods = "ctrl_module_core";
> + reg = <0x4A0022C4 0x4>; /* CONTROL_STATUS register */
> +};
> +
> diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig
> index dd0fbf7..1235576 100644
> --- a/arch/arm/mach-omap2/Kconfig
> +++ b/arch/arm/mach-omap2/Kconfig
> @@ -59,6 +59,7 @@ config ARCH_OMAP4
> select ARM_ERRATA_720789
> select ARCH_HAS_OPP
> select PM_RUNTIME if CPU_IDLE
> + select ARCH_HAS_CONTROL_MODULE
> select PM_OPP if PM
> select USB_ARCH_HAS_EHCI if USB_SUPPORT
> select ARM_CPU_SUSPEND if PM
> diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig
> index dd36eba..2b2c9d8 100644
> --- a/arch/arm/plat-omap/Kconfig
> +++ b/arch/arm/plat-omap/Kconfig
> @@ -5,6 +5,10 @@ menu "TI OMAP Common Features"
> config ARCH_OMAP_OTG
> bool
>
> +config ARCH_HAS_CONTROL_MODULE
> + bool
> + select MFD_OMAP_CONTROL
> +
> choice
> prompt "OMAP System Type"
> default ARCH_OMAP2PLUS
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 92144ed..530ac60 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -823,6 +823,12 @@ config MFD_WL1273_CORE
> driver connects the radio-wl1273 V4L2 module and the wl1273
> audio codec.
>
> +config MFD_OMAP_CONTROL
> + bool "Texas Instruments OMAP System control module"
> + depends on ARCH_HAS_CONTROL_MODULE
> + help
> + This is the core driver for system control module.
> +
> config MFD_OMAP_USB_HOST
> bool "Support OMAP USBHS core driver"
> depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 75f6ed6..b037443 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -107,6 +107,7 @@ obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o
> obj-$(CONFIG_MFD_VX855) += vx855.o
> obj-$(CONFIG_MFD_WL1273_CORE) += wl1273-core.o
> obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o
> +obj-$(CONFIG_MFD_OMAP_CONTROL) += omap-control-core.o
> obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o
> obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o
> obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o
> diff --git a/drivers/mfd/omap-control-core.c b/drivers/mfd/omap-control-core.c
> new file mode 100644
> index 0000000..cefbc5a
> --- /dev/null
> +++ b/drivers/mfd/omap-control-core.c
> @@ -0,0 +1,162 @@
> +/*
> + * OMAP system control module driver file
> + *
> + * Copyright (C) 2011-2012 Texas Instruments Incorporated - http://www.ti.com/
> + * Contacts:
> + * Based on original code written by:
> + * J Keerthy <j-keerthy@ti.com>
> + * Moiz Sonasath <m-sonasath@ti.com>
> + * MFD clean up and re-factoring:
> + * Eduardo Valentin <eduardo.valentin@ti.com>
> + * Konstantin Baydarov <kbaidarov@dev.rtsoft.ru>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/export.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_address.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/omap_control.h>
> +
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +
> +void __iomem *omap_control_base;
> +unsigned long omap_control_phys_base;
> +size_t omap_control_mapsize;
> +
> +u32 omap_control_status_read(void)
> +{
> + return __raw_readl(omap_control_base);
> +}
In AM335X, D_CAN IP uses single CONTROL module register to
initialize D_CAN ram (D_CAN_RAMINIT). In order to enable
D_CAN RAM we have to write to that register in sync with
clock enable/disable.
How can I use this driver to handle this usecase? Similar to
above API?
Currently this is handled using platform call back (example below)
In arch/arm/mach-omap2/devices.c:
static void d_can_hw_raminit(unsigned int instance, unsigned int enable)
{
u32 val;
/* Read the value */
val = readl(AM33XX_CTRL_REGADDR(AM33XX_DCAN_RAMINIT_OFFSET));
if (enable) {
/* Set to "1" */
val &= ~AM33XX_DCAN_RAMINIT_START(instance);
val |= AM33XX_DCAN_RAMINIT_START(instance);
writel(val, AM33XX_CTRL_REGADDR(AM33XX_DCAN_RAMINIT_OFFSET));
} else {
/* Set to "0" */
val &= ~AM33XX_DCAN_RAMINIT_START(instance);
writel(val, AM33XX_CTRL_REGADDR(AM33XX_DCAN_RAMINIT_OFFSET));
}
}
In drivers/net/can/c_can/c_can.c:
void d_can_reset_ram(struct d_can_priv *d_can, unsigned int instance,
unsigned int enable)
{
if (d_can->ram_init)
d_can->ram_init(instance, enable);
}
Thanks
AnilKumar
^ permalink raw reply
* [PATCH RESEND] thermal: add generic cpufreq cooling implementation
From: Zhang Rui @ 2012-09-10 6:25 UTC (permalink / raw)
To: linux-pm
Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
linux-acpi, Jean Delvare, Kyungmin Park, Andrew Morton,
Guenter Roeck
[-- Attachment #1: Type: text/plain, Size: 21920 bytes --]
Refreshed to remove the notifier mechanism as we do not have a real user of it.
if there is no problem, I'll apply the whole patch set to thermal next tree.
From: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Date: Thu, 16 Aug 2012 17:11:40 +0530
This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms. As a proof of concept, we have
drivers for the following platforms using this mechanism now:
* Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
* Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale platforms.
Brief Description:
1) The generic cooling devices code is placed inside driver/thermal/*
as placing inside acpi folder will need un-necessary enabling of acpi
code. This code is architecture independent.
2) This patchset adds generic cpu cooling low level implementation
through frequency clipping. In future, other cpu related cooling
devices may be added here. An ACPI version of this already exists
(drivers/acpi/processor_thermal.c) .But this will be useful for
platforms like ARM using the generic thermal interface along with the
generic cpu cooling devices. The cooling device registration API's
return cooling device pointers which can be easily binded with the
thermal zone trip points. The important APIs exposed are,
a) struct thermal_cooling_device *cpufreq_cooling_register(
struct cpumask *clip_cpus)
b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
3) Samsung exynos platform thermal implementation is done using the
generic cpu cooling APIs and the new trip type. The temperature sensor
driver present in the hwmon folder(registered as hwmon driver) is moved
to thermal folder and registered as a thermal driver.
A simple data/control flow diagrams is shown below,
Core Linux thermal <-----> Exynos thermal interface <----- Temperature Sensor
| |
\|/ |
Cpufreq cooling device <---------------
TODO:
*Will send the DT enablement patches later after the driver is merged.
This patch:
Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
Documentation/thermal/cpu-cooling-api.txt | 33 ++
drivers/thermal/Kconfig | 11 +
drivers/thermal/Makefile | 1 +
drivers/thermal/cpu_cooling.c | 450 +++++++++++++++++++++++++++++
include/linux/cpu_cooling.h | 58 ++++
5 files changed, 553 insertions(+), 0 deletions(-)
create mode 100644 Documentation/thermal/cpu-cooling-api.txt
create mode 100644 drivers/thermal/cpu_cooling.c
create mode 100644 include/linux/cpu_cooling.h
diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..6fe9cdb
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,33 @@
+CPU cooling APIs How To
+===================================
+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus)
+
+ This interface function registers the cpufreq cooling device with the name
+ "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ cooling devices.
+
+ clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+ This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+ cdev: Cooling device pointer which has to be unregistered.
+
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
depends on HWMON=y || HWMON=THERMAL
default y
+config CPU_THERMAL
+ bool "generic cpu cooling support"
+ depends on THERMAL && CPU_FREQ
+ help
+ This implements the generic cpu cooling mechanism through frequency
+ reduction, cpu hotplug and any other ways of reducing temperature. An
+ ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+ This will be useful for platforms using the generic thermal interface
+ and not the ACPI interface.
+ If you want this support, you should say Y here.
+
config SPEAR_THERMAL
bool "SPEAr thermal sensor driver"
depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
#
obj-$(CONFIG_THERMAL) += thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..99a5d75
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,450 @@
+/*
+ * linux/drivers/thermal/cpu_cooling.c
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ * registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ * egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ * cooling devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ * frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+ int id;
+ struct thermal_cooling_device *cool_dev;
+ unsigned int cpufreq_state;
+ unsigned int cpufreq_val;
+ struct cpumask allowed_cpus;
+ struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+ int err;
+again:
+ if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
+ return -ENOMEM;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ err = idr_get_new(idr, NULL, id);
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ if (unlikely(err == -EAGAIN))
+ goto again;
+ else if (unlikely(err))
+ return err;
+
+ *id = *id & MAX_ID_MASK;
+ return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+ mutex_lock(&cooling_cpufreq_lock);
+ idr_remove(idr, id);
+ mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+ struct cpufreq_policy policy;
+ return !cpufreq_get_policy(&policy, cpu);
+}
+
+/**
+ * get_cpu_frequency - get the absolute value of frequency from level.
+ * @cpu: cpu for which frequency is fetched.
+ * @level: level of frequency of the CPU
+ * e.g level=1 --> 1st MAX FREQ, LEVEL=2 ---> 2nd MAX FREQ, .... etc
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
+{
+ int ret = 0, i = 0;
+ unsigned long level_index;
+ bool descend = false;
+ struct cpufreq_frequency_table *table =
+ cpufreq_frequency_get_table(cpu);
+ if (!table)
+ return ret;
+
+ while (table[i].frequency != CPUFREQ_TABLE_END) {
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+
+ /*check if table in ascending or descending order*/
+ if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+ (table[i + 1].frequency < table[i].frequency)
+ && !descend) {
+ descend = true;
+ }
+
+ /*return if level matched and table in descending order*/
+ if (descend && i == level)
+ return table[i].frequency;
+ i++;
+ }
+ i--;
+
+ if (level > i || descend)
+ return ret;
+ level_index = i - level;
+
+ /*Scan the table in reverse order and match the level*/
+ while (i >= 0) {
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+ /*return if level matched*/
+ if (i == level_index)
+ return table[i].frequency;
+ i--;
+ }
+ return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ * clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+ unsigned long cooling_state)
+{
+ unsigned int cpuid, clip_freq;
+ struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
+ unsigned int cpu = cpumask_any(maskPtr);
+
+
+ /* Check if the old cooling action is same as new cooling action */
+ if (cpufreq_device->cpufreq_state == cooling_state)
+ return 0;
+
+ clip_freq = get_cpu_frequency(cpu, cooling_state);
+ if (!clip_freq)
+ return -EINVAL;
+
+ cpufreq_device->cpufreq_state = cooling_state;
+ cpufreq_device->cpufreq_val = clip_freq;
+ notify_device = cpufreq_device;
+
+ for_each_cpu(cpuid, maskPtr) {
+ if (is_cpufreq_valid(cpuid))
+ cpufreq_update_policy(cpuid);
+ }
+
+ notify_device = NOTIFY_INVALID;
+
+ return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb: struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct cpufreq_policy *policy = data;
+ unsigned long max_freq = 0;
+
+ if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
+ return 0;
+
+ if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus))
+ max_freq = notify_device->cpufreq_val;
+
+ /* Never exceed user_policy.max*/
+ if (max_freq > policy->user_policy.max)
+ max_freq = policy->user_policy.max;
+
+ if (policy->max != max_freq)
+ cpufreq_verify_within_limits(policy, 0, max_freq);
+
+ return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL, i = 0;
+ struct cpufreq_cooling_device *cpufreq_device;
+ struct cpumask *maskPtr;
+ unsigned int cpu;
+ struct cpufreq_frequency_table *table;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev)
+ break;
+ }
+ if (cpufreq_device == NULL)
+ goto return_get_max_state;
+
+ maskPtr = &cpufreq_device->allowed_cpus;
+ cpu = cpumask_any(maskPtr);
+ table = cpufreq_frequency_get_table(cpu);
+ if (!table) {
+ *state = 0;
+ ret = 0;
+ goto return_get_max_state;
+ }
+
+ while (table[i].frequency != CPUFREQ_TABLE_END) {
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+ i++;
+ }
+ if (i > 0) {
+ *state = --i;
+ ret = 0;
+ }
+
+return_get_max_state:
+ mutex_unlock(&cooling_cpufreq_lock);
+ return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL;
+ struct cpufreq_cooling_device *cpufreq_device;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
+ *state = cpufreq_device->cpufreq_state;
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ int ret = -EINVAL;
+ struct cpufreq_cooling_device *cpufreq_device;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
+ ret = 0;
+ break;
+ }
+ }
+ if (!ret)
+ ret = cpufreq_apply_cooling(cpufreq_device, state);
+
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+ .get_max_state = cpufreq_get_max_state,
+ .get_cur_state = cpufreq_get_cur_state,
+ .set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+ .notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus)
+{
+ struct thermal_cooling_device *cool_dev;
+ struct cpufreq_cooling_device *cpufreq_dev = NULL;
+ unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
+ char dev_name[THERMAL_NAME_LENGTH];
+ int ret = 0, id = 0, i;
+ struct cpufreq_policy policy;
+
+ list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
+ cpufreq_dev_count++;
+
+ /*Verify that all the clip cpus have same freq_min, freq_max limit*/
+ for_each_cpu(i, clip_cpus) {
+ /*continue if cpufreq policy not found and not return error*/
+ if (!cpufreq_get_policy(&policy, i))
+ continue;
+ if (min == 0 && max == 0) {
+ min = policy.cpuinfo.min_freq;
+ max = policy.cpuinfo.max_freq;
+ } else {
+ if (min != policy.cpuinfo.min_freq ||
+ max != policy.cpuinfo.max_freq)
+ return ERR_PTR(-EINVAL);
+}
+ }
+ cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device),
+ GFP_KERNEL);
+ if (!cpufreq_dev)
+ return ERR_PTR(-ENOMEM);
+
+ cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
+
+ if (cpufreq_dev_count == 0)
+ mutex_init(&cooling_cpufreq_lock);
+
+ ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
+ if (ret) {
+ kfree(cpufreq_dev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+ cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+ &cpufreq_cooling_ops);
+ if (!cool_dev) {
+ release_idr(&cpufreq_idr, cpufreq_dev->id);
+ kfree(cpufreq_dev);
+ return ERR_PTR(-EINVAL);
+ }
+ cpufreq_dev->id = id;
+ cpufreq_dev->cool_dev = cool_dev;
+ cpufreq_dev->cpufreq_state = 0;
+ mutex_lock(&cooling_cpufreq_lock);
+ list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
+
+ /* Register the notifier for first cpufreq cooling device */
+ if (cpufreq_dev_count == 0)
+ cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+
+ mutex_unlock(&cooling_cpufreq_lock);
+ return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct cpufreq_cooling_device *cpufreq_dev = NULL;
+ unsigned int cpufreq_dev_count = 0;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
+ if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
+ break;
+ cpufreq_dev_count++;
+ }
+
+ if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
+ mutex_unlock(&cooling_cpufreq_lock);
+ return;
+ }
+
+ list_del(&cpufreq_dev->node);
+
+ /* Unregister the notifier for the last cpufreq cooling device */
+ if (cpufreq_dev_count == 1) {
+ cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+ }
+ mutex_unlock(&cooling_cpufreq_lock);
+ thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
+ release_idr(&cpufreq_idr, cpufreq_dev->id);
+ if (cpufreq_dev_count == 1)
+ mutex_destroy(&cooling_cpufreq_lock);
+ kfree(cpufreq_dev);
+}
+EXPORT_SYMBOL(cpufreq_cooling_unregister);
diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..8515301
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,58 @@
+/*
+ * linux/include/linux/cpu_cooling.h
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START 0
+#define CPUFREQ_COOLING_STOP 1
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus)
+{
+ return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+ struct thermal_cooling_device *cdev)
+{
+ return;
+}
+#endif /* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
--
1.7.7.5
[-- Attachment #2: 0001-thermal-add-generic-cpufreq-cooling-implementation.patch --]
[-- Type: text/x-patch, Size: 21968 bytes --]
>From ed5cb052cd160591e70f5049e6ea539397f84165 Mon Sep 17 00:00:00 2001
From: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Date: Thu, 16 Aug 2012 17:11:40 +0530
Subject: [PATCH 1/5] thermal: add generic cpufreq cooling implementation
This patchset introduces a new generic cooling device based on cpufreq
that can be used on non-ACPI platforms. As a proof of concept, we have
drivers for the following platforms using this mechanism now:
* Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
* Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
There is a small change in cpufreq cooling registration APIs, so a minor
change is needed for Freescale platforms.
Brief Description:
1) The generic cooling devices code is placed inside driver/thermal/*
as placing inside acpi folder will need un-necessary enabling of acpi
code. This code is architecture independent.
2) This patchset adds generic cpu cooling low level implementation
through frequency clipping. In future, other cpu related cooling
devices may be added here. An ACPI version of this already exists
(drivers/acpi/processor_thermal.c) .But this will be useful for
platforms like ARM using the generic thermal interface along with the
generic cpu cooling devices. The cooling device registration API's
return cooling device pointers which can be easily binded with the
thermal zone trip points. The important APIs exposed are,
a) struct thermal_cooling_device *cpufreq_cooling_register(
struct cpumask *clip_cpus)
b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
3) Samsung exynos platform thermal implementation is done using the
generic cpu cooling APIs and the new trip type. The temperature sensor
driver present in the hwmon folder(registered as hwmon driver) is moved
to thermal folder and registered as a thermal driver.
A simple data/control flow diagrams is shown below,
Core Linux thermal <-----> Exynos thermal interface <----- Temperature Sensor
| |
\|/ |
Cpufreq cooling device <---------------
TODO:
*Will send the DT enablement patches later after the driver is merged.
This patch:
Add support for generic cpu thermal cooling low level implementations
using frequency scaling up/down based on the registration parameters.
Different cpu related cooling devices can be registered by the user and
the binding of these cooling devices to the corresponding trip points can
be easily done as the registration APIs return the cooling device pointer.
The user of these APIs are responsible for passing clipping frequency .
The drivers can also register to recieve notification about any cooling
action called.
[akpm@linux-foundation.org: fix comment layout]
Signed-off-by: Amit Daniel Kachhap <amit.kachhap@linaro.org>
Cc: Guenter Roeck <guenter.roeck@ericsson.com>
Cc: SangWook Ju <sw.ju@samsung.com>
Cc: Durgadoss <durgadoss.r@intel.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
---
Documentation/thermal/cpu-cooling-api.txt | 33 ++
drivers/thermal/Kconfig | 11 +
drivers/thermal/Makefile | 1 +
drivers/thermal/cpu_cooling.c | 450 +++++++++++++++++++++++++++++
include/linux/cpu_cooling.h | 58 ++++
5 files changed, 553 insertions(+), 0 deletions(-)
create mode 100644 Documentation/thermal/cpu-cooling-api.txt
create mode 100644 drivers/thermal/cpu_cooling.c
create mode 100644 include/linux/cpu_cooling.h
diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
new file mode 100644
index 0000000..6fe9cdb
--- /dev/null
+++ b/Documentation/thermal/cpu-cooling-api.txt
@@ -0,0 +1,33 @@
+CPU cooling APIs How To
+===================================
+
+Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
+
+Updated: 12 May 2012
+
+Copyright (c) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+
+0. Introduction
+
+The generic cpu cooling(freq clipping) provides registration/unregistration APIs
+to the caller. The binding of the cooling devices to the trip point is left for
+the user. The registration APIs returns the cooling device pointer.
+
+1. cpu cooling APIs
+
+1.1 cpufreq registration/unregistration APIs
+1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus)
+
+ This interface function registers the cpufreq cooling device with the name
+ "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ cooling devices.
+
+ clip_cpus: cpumask of cpus where the frequency constraints will happen.
+
+1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+
+ This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+
+ cdev: Cooling device pointer which has to be unregistered.
+
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 7dd8c34..996003b 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -19,6 +19,17 @@ config THERMAL_HWMON
depends on HWMON=y || HWMON=THERMAL
default y
+config CPU_THERMAL
+ bool "generic cpu cooling support"
+ depends on THERMAL && CPU_FREQ
+ help
+ This implements the generic cpu cooling mechanism through frequency
+ reduction, cpu hotplug and any other ways of reducing temperature. An
+ ACPI version of this already exists(drivers/acpi/processor_thermal.c).
+ This will be useful for platforms using the generic thermal interface
+ and not the ACPI interface.
+ If you want this support, you should say Y here.
+
config SPEAR_THERMAL
bool "SPEAr thermal sensor driver"
depends on THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index fd9369a..aae59ad 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,5 +3,6 @@
#
obj-$(CONFIG_THERMAL) += thermal_sys.o
+obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
new file mode 100644
index 0000000..99a5d75
--- /dev/null
+++ b/drivers/thermal/cpu_cooling.c
@@ -0,0 +1,450 @@
+/*
+ * linux/drivers/thermal/cpu_cooling.c
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/**
+ * struct cpufreq_cooling_device
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ * registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the the
+ * egistered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ * cooling devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ * frequency.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each
+ * cpufreq_cooling_device registered as a list whose head is represented by
+ * cooling_cpufreq_list. In order to prevent corruption of this list a
+ * mutex lock cooling_cpufreq_lock is used.
+ */
+struct cpufreq_cooling_device {
+ int id;
+ struct thermal_cooling_device *cool_dev;
+ unsigned int cpufreq_state;
+ unsigned int cpufreq_val;
+ struct cpumask allowed_cpus;
+ struct list_head node;
+};
+static LIST_HEAD(cooling_cpufreq_list);
+static DEFINE_IDR(cpufreq_idr);
+
+static struct mutex cooling_cpufreq_lock;
+
+/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
+#define NOTIFY_INVALID NULL
+struct cpufreq_cooling_device *notify_device;
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+ int err;
+again:
+ if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
+ return -ENOMEM;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ err = idr_get_new(idr, NULL, id);
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ if (unlikely(err == -EAGAIN))
+ goto again;
+ else if (unlikely(err))
+ return err;
+
+ *id = *id & MAX_ID_MASK;
+ return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+ mutex_lock(&cooling_cpufreq_lock);
+ idr_remove(idr, id);
+ mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
+ * @cpu: cpu for which check is needed.
+ */
+static int is_cpufreq_valid(int cpu)
+{
+ struct cpufreq_policy policy;
+ return !cpufreq_get_policy(&policy, cpu);
+}
+
+/**
+ * get_cpu_frequency - get the absolute value of frequency from level.
+ * @cpu: cpu for which frequency is fetched.
+ * @level: level of frequency of the CPU
+ * e.g level=1 --> 1st MAX FREQ, LEVEL=2 ---> 2nd MAX FREQ, .... etc
+ */
+static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
+{
+ int ret = 0, i = 0;
+ unsigned long level_index;
+ bool descend = false;
+ struct cpufreq_frequency_table *table =
+ cpufreq_frequency_get_table(cpu);
+ if (!table)
+ return ret;
+
+ while (table[i].frequency != CPUFREQ_TABLE_END) {
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+
+ /*check if table in ascending or descending order*/
+ if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
+ (table[i + 1].frequency < table[i].frequency)
+ && !descend) {
+ descend = true;
+ }
+
+ /*return if level matched and table in descending order*/
+ if (descend && i == level)
+ return table[i].frequency;
+ i++;
+ }
+ i--;
+
+ if (level > i || descend)
+ return ret;
+ level_index = i - level;
+
+ /*Scan the table in reverse order and match the level*/
+ while (i >= 0) {
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+ /*return if level matched*/
+ if (i == level_index)
+ return table[i].frequency;
+ i--;
+ }
+ return ret;
+}
+
+/**
+ * cpufreq_apply_cooling - function to apply frequency clipping.
+ * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
+ * clipping data.
+ * @cooling_state: value of the cooling state.
+ */
+static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
+ unsigned long cooling_state)
+{
+ unsigned int cpuid, clip_freq;
+ struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
+ unsigned int cpu = cpumask_any(maskPtr);
+
+
+ /* Check if the old cooling action is same as new cooling action */
+ if (cpufreq_device->cpufreq_state == cooling_state)
+ return 0;
+
+ clip_freq = get_cpu_frequency(cpu, cooling_state);
+ if (!clip_freq)
+ return -EINVAL;
+
+ cpufreq_device->cpufreq_state = cooling_state;
+ cpufreq_device->cpufreq_val = clip_freq;
+ notify_device = cpufreq_device;
+
+ for_each_cpu(cpuid, maskPtr) {
+ if (is_cpufreq_valid(cpuid))
+ cpufreq_update_policy(cpuid);
+ }
+
+ notify_device = NOTIFY_INVALID;
+
+ return 0;
+}
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb: struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct cpufreq_policy *policy = data;
+ unsigned long max_freq = 0;
+
+ if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
+ return 0;
+
+ if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus))
+ max_freq = notify_device->cpufreq_val;
+
+ /* Never exceed user_policy.max*/
+ if (max_freq > policy->user_policy.max)
+ max_freq = policy->user_policy.max;
+
+ if (policy->max != max_freq)
+ cpufreq_verify_within_limits(policy, 0, max_freq);
+
+ return 0;
+}
+
+/*
+ * cpufreq cooling device callback functions are defined below
+ */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ */
+static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL, i = 0;
+ struct cpufreq_cooling_device *cpufreq_device;
+ struct cpumask *maskPtr;
+ unsigned int cpu;
+ struct cpufreq_frequency_table *table;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev)
+ break;
+ }
+ if (cpufreq_device == NULL)
+ goto return_get_max_state;
+
+ maskPtr = &cpufreq_device->allowed_cpus;
+ cpu = cpumask_any(maskPtr);
+ table = cpufreq_frequency_get_table(cpu);
+ if (!table) {
+ *state = 0;
+ ret = 0;
+ goto return_get_max_state;
+ }
+
+ while (table[i].frequency != CPUFREQ_TABLE_END) {
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+ i++;
+ }
+ if (i > 0) {
+ *state = --i;
+ ret = 0;
+ }
+
+return_get_max_state:
+ mutex_unlock(&cooling_cpufreq_lock);
+ return ret;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ int ret = -EINVAL;
+ struct cpufreq_cooling_device *cpufreq_device;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
+ *state = cpufreq_device->cpufreq_state;
+ ret = 0;
+ break;
+ }
+ }
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ return ret;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ */
+static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ int ret = -EINVAL;
+ struct cpufreq_cooling_device *cpufreq_device;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
+ if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
+ ret = 0;
+ break;
+ }
+ }
+ if (!ret)
+ ret = cpufreq_apply_cooling(cpufreq_device, state);
+
+ mutex_unlock(&cooling_cpufreq_lock);
+
+ return ret;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+ .get_max_state = cpufreq_get_max_state,
+ .get_cur_state = cpufreq_get_cur_state,
+ .set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+ .notifier_call = cpufreq_thermal_notifier,
+};
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus)
+{
+ struct thermal_cooling_device *cool_dev;
+ struct cpufreq_cooling_device *cpufreq_dev = NULL;
+ unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
+ char dev_name[THERMAL_NAME_LENGTH];
+ int ret = 0, id = 0, i;
+ struct cpufreq_policy policy;
+
+ list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
+ cpufreq_dev_count++;
+
+ /*Verify that all the clip cpus have same freq_min, freq_max limit*/
+ for_each_cpu(i, clip_cpus) {
+ /*continue if cpufreq policy not found and not return error*/
+ if (!cpufreq_get_policy(&policy, i))
+ continue;
+ if (min == 0 && max == 0) {
+ min = policy.cpuinfo.min_freq;
+ max = policy.cpuinfo.max_freq;
+ } else {
+ if (min != policy.cpuinfo.min_freq ||
+ max != policy.cpuinfo.max_freq)
+ return ERR_PTR(-EINVAL);
+}
+ }
+ cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device),
+ GFP_KERNEL);
+ if (!cpufreq_dev)
+ return ERR_PTR(-ENOMEM);
+
+ cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
+
+ if (cpufreq_dev_count == 0)
+ mutex_init(&cooling_cpufreq_lock);
+
+ ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
+ if (ret) {
+ kfree(cpufreq_dev);
+ return ERR_PTR(-EINVAL);
+ }
+
+ sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
+
+ cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
+ &cpufreq_cooling_ops);
+ if (!cool_dev) {
+ release_idr(&cpufreq_idr, cpufreq_dev->id);
+ kfree(cpufreq_dev);
+ return ERR_PTR(-EINVAL);
+ }
+ cpufreq_dev->id = id;
+ cpufreq_dev->cool_dev = cool_dev;
+ cpufreq_dev->cpufreq_state = 0;
+ mutex_lock(&cooling_cpufreq_lock);
+ list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
+
+ /* Register the notifier for first cpufreq cooling device */
+ if (cpufreq_dev_count == 0)
+ cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+
+ mutex_unlock(&cooling_cpufreq_lock);
+ return cool_dev;
+}
+EXPORT_SYMBOL(cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+ struct cpufreq_cooling_device *cpufreq_dev = NULL;
+ unsigned int cpufreq_dev_count = 0;
+
+ mutex_lock(&cooling_cpufreq_lock);
+ list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
+ if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
+ break;
+ cpufreq_dev_count++;
+ }
+
+ if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
+ mutex_unlock(&cooling_cpufreq_lock);
+ return;
+ }
+
+ list_del(&cpufreq_dev->node);
+
+ /* Unregister the notifier for the last cpufreq cooling device */
+ if (cpufreq_dev_count == 1) {
+ cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
+ CPUFREQ_POLICY_NOTIFIER);
+ }
+ mutex_unlock(&cooling_cpufreq_lock);
+ thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
+ release_idr(&cpufreq_idr, cpufreq_dev->id);
+ if (cpufreq_dev_count == 1)
+ mutex_destroy(&cooling_cpufreq_lock);
+ kfree(cpufreq_dev);
+}
+EXPORT_SYMBOL(cpufreq_cooling_unregister);
diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
new file mode 100644
index 0000000..8515301
--- /dev/null
+++ b/include/linux/cpu_cooling.h
@@ -0,0 +1,58 @@
+/*
+ * linux/include/linux/cpu_cooling.h
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __CPU_COOLING_H__
+#define __CPU_COOLING_H__
+
+#include <linux/thermal.h>
+
+#define CPUFREQ_COOLING_START 0
+#define CPUFREQ_COOLING_STOP 1
+
+#ifdef CONFIG_CPU_THERMAL
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen
+ */
+struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+#else /* !CONFIG_CPU_THERMAL */
+static inline struct thermal_cooling_device *cpufreq_cooling_register(
+ struct cpumask *clip_cpus)
+{
+ return NULL;
+}
+static inline void cpufreq_cooling_unregister(
+ struct thermal_cooling_device *cdev)
+{
+ return;
+}
+#endif /* CONFIG_CPU_THERMAL */
+
+#endif /* __CPU_COOLING_H__ */
--
1.7.7.5
^ permalink raw reply related
* Re: [PATCH RESEND] thermal: add generic cpufreq cooling implementation
From: Valentin, Eduardo @ 2012-09-10 6:50 UTC (permalink / raw)
To: Zhang Rui
Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
linux-acpi, Jean Delvare, Kyungmin Park, linux-pm, Guenter Roeck,
Andrew Morton
In-Reply-To: <1347258341.1682.1062.camel@rui.sh.intel.com>
Hello Rui and Amit,
On Mon, Sep 10, 2012 at 9:25 AM, Zhang Rui <rui.zhang@intel.com> wrote:
> Refreshed to remove the notifier mechanism as we do not have a real user of it.
> if there is no problem, I'll apply the whole patch set to thermal next tree.
>
> From: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Date: Thu, 16 Aug 2012 17:11:40 +0530
>
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms. As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
> * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
> * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
> as placing inside acpi folder will need un-necessary enabling of acpi
> code. This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
> through frequency clipping. In future, other cpu related cooling
> devices may be added here. An ACPI version of this already exists
> (drivers/acpi/processor_thermal.c) .But this will be useful for
> platforms like ARM using the generic thermal interface along with the
> generic cpu cooling devices. The cooling device registration API's
> return cooling device pointers which can be easily binded with the
> thermal zone trip points. The important APIs exposed are,
>
> a) struct thermal_cooling_device *cpufreq_cooling_register(
> struct cpumask *clip_cpus)
> b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
> generic cpu cooling APIs and the new trip type. The temperature sensor
> driver present in the hwmon folder(registered as hwmon driver) is moved
> to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <-----> Exynos thermal interface <----- Temperature Sensor
> | |
> \|/ |
> Cpufreq cooling device <---------------
>
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
>
> This patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
can you please include my Reviewed-by? Also on your patch series I
have reviewed.
Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
> ---
> Documentation/thermal/cpu-cooling-api.txt | 33 ++
> drivers/thermal/Kconfig | 11 +
> drivers/thermal/Makefile | 1 +
> drivers/thermal/cpu_cooling.c | 450 +++++++++++++++++++++++++++++
> include/linux/cpu_cooling.h | 58 ++++
> 5 files changed, 553 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/thermal/cpu-cooling-api.txt
> create mode 100644 drivers/thermal/cpu_cooling.c
> create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..6fe9cdb
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,33 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus)
> +
> + This interface function registers the cpufreq cooling device with the name
> + "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> + cooling devices.
> +
> + clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> + This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> + cdev: Cooling device pointer which has to be unregistered.
> +
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
> depends on HWMON=y || HWMON=THERMAL
> default y
>
> +config CPU_THERMAL
> + bool "generic cpu cooling support"
> + depends on THERMAL && CPU_FREQ
> + help
> + This implements the generic cpu cooling mechanism through frequency
> + reduction, cpu hotplug and any other ways of reducing temperature. An
> + ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> + This will be useful for platforms using the generic thermal interface
> + and not the ACPI interface.
> + If you want this support, you should say Y here.
> +
> config SPEAR_THERMAL
> bool "SPEAr thermal sensor driver"
> depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
> #
>
> obj-$(CONFIG_THERMAL) += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
> obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
> obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..99a5d75
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,450 @@
> +/*
> + * linux/drivers/thermal/cpu_cooling.c
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + * registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + * egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + * cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + * frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> + int id;
> + struct thermal_cooling_device *cool_dev;
> + unsigned int cpufreq_state;
> + unsigned int cpufreq_val;
> + struct cpumask allowed_cpus;
> + struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> + int err;
> +again:
> + if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> + return -ENOMEM;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + err = idr_get_new(idr, NULL, id);
> + mutex_unlock(&cooling_cpufreq_lock);
> +
> + if (unlikely(err == -EAGAIN))
> + goto again;
> + else if (unlikely(err))
> + return err;
> +
> + *id = *id & MAX_ID_MASK;
> + return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> + mutex_lock(&cooling_cpufreq_lock);
> + idr_remove(idr, id);
> + mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> + struct cpufreq_policy policy;
> + return !cpufreq_get_policy(&policy, cpu);
> +}
> +
> +/**
> + * get_cpu_frequency - get the absolute value of frequency from level.
> + * @cpu: cpu for which frequency is fetched.
> + * @level: level of frequency of the CPU
> + * e.g level=1 --> 1st MAX FREQ, LEVEL=2 ---> 2nd MAX FREQ, .... etc
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
> +{
> + int ret = 0, i = 0;
> + unsigned long level_index;
> + bool descend = false;
> + struct cpufreq_frequency_table *table =
> + cpufreq_frequency_get_table(cpu);
> + if (!table)
> + return ret;
> +
> + while (table[i].frequency != CPUFREQ_TABLE_END) {
> + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> + continue;
> +
> + /*check if table in ascending or descending order*/
> + if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> + (table[i + 1].frequency < table[i].frequency)
> + && !descend) {
> + descend = true;
> + }
> +
> + /*return if level matched and table in descending order*/
> + if (descend && i == level)
> + return table[i].frequency;
> + i++;
> + }
> + i--;
> +
> + if (level > i || descend)
> + return ret;
> + level_index = i - level;
> +
> + /*Scan the table in reverse order and match the level*/
> + while (i >= 0) {
> + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> + continue;
> + /*return if level matched*/
> + if (i == level_index)
> + return table[i].frequency;
> + i--;
> + }
> + return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + * clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> + unsigned long cooling_state)
> +{
> + unsigned int cpuid, clip_freq;
> + struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> + unsigned int cpu = cpumask_any(maskPtr);
> +
> +
> + /* Check if the old cooling action is same as new cooling action */
> + if (cpufreq_device->cpufreq_state == cooling_state)
> + return 0;
> +
> + clip_freq = get_cpu_frequency(cpu, cooling_state);
> + if (!clip_freq)
> + return -EINVAL;
> +
> + cpufreq_device->cpufreq_state = cooling_state;
> + cpufreq_device->cpufreq_val = clip_freq;
> + notify_device = cpufreq_device;
> +
> + for_each_cpu(cpuid, maskPtr) {
> + if (is_cpufreq_valid(cpuid))
> + cpufreq_update_policy(cpuid);
> + }
> +
> + notify_device = NOTIFY_INVALID;
> +
> + return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb: struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> + unsigned long event, void *data)
> +{
> + struct cpufreq_policy *policy = data;
> + unsigned long max_freq = 0;
> +
> + if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> + return 0;
> +
> + if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus))
> + max_freq = notify_device->cpufreq_val;
> +
> + /* Never exceed user_policy.max*/
> + if (max_freq > policy->user_policy.max)
> + max_freq = policy->user_policy.max;
> +
> + if (policy->max != max_freq)
> + cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> + return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + int ret = -EINVAL, i = 0;
> + struct cpufreq_cooling_device *cpufreq_device;
> + struct cpumask *maskPtr;
> + unsigned int cpu;
> + struct cpufreq_frequency_table *table;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> + if (cpufreq_device && cpufreq_device->cool_dev == cdev)
> + break;
> + }
> + if (cpufreq_device == NULL)
> + goto return_get_max_state;
> +
> + maskPtr = &cpufreq_device->allowed_cpus;
> + cpu = cpumask_any(maskPtr);
> + table = cpufreq_frequency_get_table(cpu);
> + if (!table) {
> + *state = 0;
> + ret = 0;
> + goto return_get_max_state;
> + }
> +
> + while (table[i].frequency != CPUFREQ_TABLE_END) {
> + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> + continue;
> + i++;
> + }
> + if (i > 0) {
> + *state = --i;
> + ret = 0;
> + }
> +
> +return_get_max_state:
> + mutex_unlock(&cooling_cpufreq_lock);
> + return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling state.
> + */
> +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + int ret = -EINVAL;
> + struct cpufreq_cooling_device *cpufreq_device;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> + if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
> + *state = cpufreq_device->cpufreq_state;
> + ret = 0;
> + break;
> + }
> + }
> + mutex_unlock(&cooling_cpufreq_lock);
> +
> + return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long state)
> +{
> + int ret = -EINVAL;
> + struct cpufreq_cooling_device *cpufreq_device;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> + if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
> + ret = 0;
> + break;
> + }
> + }
> + if (!ret)
> + ret = cpufreq_apply_cooling(cpufreq_device, state);
> +
> + mutex_unlock(&cooling_cpufreq_lock);
> +
> + return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> + .get_max_state = cpufreq_get_max_state,
> + .get_cur_state = cpufreq_get_cur_state,
> + .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> + .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus)
> +{
> + struct thermal_cooling_device *cool_dev;
> + struct cpufreq_cooling_device *cpufreq_dev = NULL;
> + unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
> + char dev_name[THERMAL_NAME_LENGTH];
> + int ret = 0, id = 0, i;
> + struct cpufreq_policy policy;
> +
> + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
> + cpufreq_dev_count++;
> +
> + /*Verify that all the clip cpus have same freq_min, freq_max limit*/
> + for_each_cpu(i, clip_cpus) {
> + /*continue if cpufreq policy not found and not return error*/
> + if (!cpufreq_get_policy(&policy, i))
> + continue;
> + if (min == 0 && max == 0) {
> + min = policy.cpuinfo.min_freq;
> + max = policy.cpuinfo.max_freq;
> + } else {
> + if (min != policy.cpuinfo.min_freq ||
> + max != policy.cpuinfo.max_freq)
> + return ERR_PTR(-EINVAL);
> +}
> + }
> + cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device),
> + GFP_KERNEL);
> + if (!cpufreq_dev)
> + return ERR_PTR(-ENOMEM);
> +
> + cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
> +
> + if (cpufreq_dev_count == 0)
> + mutex_init(&cooling_cpufreq_lock);
> +
> + ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
> + if (ret) {
> + kfree(cpufreq_dev);
> + return ERR_PTR(-EINVAL);
> + }
> +
> + sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> + cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> + &cpufreq_cooling_ops);
> + if (!cool_dev) {
> + release_idr(&cpufreq_idr, cpufreq_dev->id);
> + kfree(cpufreq_dev);
> + return ERR_PTR(-EINVAL);
> + }
> + cpufreq_dev->id = id;
> + cpufreq_dev->cool_dev = cool_dev;
> + cpufreq_dev->cpufreq_state = 0;
> + mutex_lock(&cooling_cpufreq_lock);
> + list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
> +
> + /* Register the notifier for first cpufreq cooling device */
> + if (cpufreq_dev_count == 0)
> + cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
> + CPUFREQ_POLICY_NOTIFIER);
> +
> + mutex_unlock(&cooling_cpufreq_lock);
> + return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> + struct cpufreq_cooling_device *cpufreq_dev = NULL;
> + unsigned int cpufreq_dev_count = 0;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
> + if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
> + break;
> + cpufreq_dev_count++;
> + }
> +
> + if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
> + mutex_unlock(&cooling_cpufreq_lock);
> + return;
> + }
> +
> + list_del(&cpufreq_dev->node);
> +
> + /* Unregister the notifier for the last cpufreq cooling device */
> + if (cpufreq_dev_count == 1) {
> + cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
> + CPUFREQ_POLICY_NOTIFIER);
> + }
> + mutex_unlock(&cooling_cpufreq_lock);
> + thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
> + release_idr(&cpufreq_idr, cpufreq_dev->id);
> + if (cpufreq_dev_count == 1)
> + mutex_destroy(&cooling_cpufreq_lock);
> + kfree(cpufreq_dev);
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_unregister);
> diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..8515301
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,58 @@
> +/*
> + * linux/include/linux/cpu_cooling.h
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +
> +#ifndef __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START 0
> +#define CPUFREQ_COOLING_STOP 1
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus)
> +{
> + return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> + struct thermal_cooling_device *cdev)
> +{
> + return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.7.5
>
>
--
Eduardo Valentin
^ permalink raw reply
* Re: [PATCH RESEND] thermal: add generic cpufreq cooling implementation
From: Zhang Rui @ 2012-09-10 7:20 UTC (permalink / raw)
To: Valentin, Eduardo
Cc: linux-samsung-soc, SangWook Ju, linux-kernel, lm-sensors,
linux-acpi, Jean Delvare, Kyungmin Park, linux-pm, Guenter Roeck,
Andrew Morton
In-Reply-To: <CAGF5oy-NxXGWXUaW-eDdsd-4Ky7=rK5m6yP5p_uU1MeGDd3gMA@mail.gmail.com>
On 一, 2012-09-10 at 09:50 +0300, Valentin, Eduardo wrote:
> Hello Rui and Amit,
>
> On Mon, Sep 10, 2012 at 9:25 AM, Zhang Rui <rui.zhang@intel.com> wrote:
> > Refreshed to remove the notifier mechanism as we do not have a real user of it.
> > if there is no problem, I'll apply the whole patch set to thermal next tree.
> >
> > From: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> > Date: Thu, 16 Aug 2012 17:11:40 +0530
> >
> > This patchset introduces a new generic cooling device based on cpufreq
> > that can be used on non-ACPI platforms. As a proof of concept, we have
> > drivers for the following platforms using this mechanism now:
> >
> > * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
> > * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
> >
> > There is a small change in cpufreq cooling registration APIs, so a minor
> > change is needed for Freescale platforms.
> >
> > Brief Description:
> >
> > 1) The generic cooling devices code is placed inside driver/thermal/*
> > as placing inside acpi folder will need un-necessary enabling of acpi
> > code. This code is architecture independent.
> >
> > 2) This patchset adds generic cpu cooling low level implementation
> > through frequency clipping. In future, other cpu related cooling
> > devices may be added here. An ACPI version of this already exists
> > (drivers/acpi/processor_thermal.c) .But this will be useful for
> > platforms like ARM using the generic thermal interface along with the
> > generic cpu cooling devices. The cooling device registration API's
> > return cooling device pointers which can be easily binded with the
> > thermal zone trip points. The important APIs exposed are,
> >
> > a) struct thermal_cooling_device *cpufreq_cooling_register(
> > struct cpumask *clip_cpus)
> > b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> >
> > 3) Samsung exynos platform thermal implementation is done using the
> > generic cpu cooling APIs and the new trip type. The temperature sensor
> > driver present in the hwmon folder(registered as hwmon driver) is moved
> > to thermal folder and registered as a thermal driver.
> >
> > A simple data/control flow diagrams is shown below,
> >
> > Core Linux thermal <-----> Exynos thermal interface <----- Temperature Sensor
> > | |
> > \|/ |
> > Cpufreq cooling device <---------------
> >
> > TODO:
> > *Will send the DT enablement patches later after the driver is merged.
> >
> > This patch:
> >
> > Add support for generic cpu thermal cooling low level implementations
> > using frequency scaling up/down based on the registration parameters.
> > Different cpu related cooling devices can be registered by the user and
> > the binding of these cooling devices to the corresponding trip points can
> > be easily done as the registration APIs return the cooling device pointer.
> > The user of these APIs are responsible for passing clipping frequency .
> > The drivers can also register to recieve notification about any cooling
> > action called.
> >
> > Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> > Cc: SangWook Ju <sw.ju@samsung.com>
> > Cc: Durgadoss <durgadoss.r@intel.com>
> > Cc: Len Brown <lenb@kernel.org>
> > Cc: Jean Delvare <khali@linux-fr.org>
> > Cc: Kyungmin Park <kmpark@infradead.org>
> > Cc: Kukjin Kim <kgene.kim@samsung.com>
> > Signed-off-by: Zhang Rui <rui.zhang@intel.com>
> > Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> > Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>
>
> can you please include my Reviewed-by? Also on your patch series I
> have reviewed.
>
sure.
> Reviewed-by: Eduardo Valentin <eduardo.valentin@ti.com>
>
thanks!
-rui
>
>
> > ---
> > Documentation/thermal/cpu-cooling-api.txt | 33 ++
> > drivers/thermal/Kconfig | 11 +
> > drivers/thermal/Makefile | 1 +
> > drivers/thermal/cpu_cooling.c | 450 +++++++++++++++++++++++++++++
> > include/linux/cpu_cooling.h | 58 ++++
> > 5 files changed, 553 insertions(+), 0 deletions(-)
> > create mode 100644 Documentation/thermal/cpu-cooling-api.txt
> > create mode 100644 drivers/thermal/cpu_cooling.c
> > create mode 100644 include/linux/cpu_cooling.h
> >
> > diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> > new file mode 100644
> > index 0000000..6fe9cdb
> > --- /dev/null
> > +++ b/Documentation/thermal/cpu-cooling-api.txt
> > @@ -0,0 +1,33 @@
> > +CPU cooling APIs How To
> > +===================================
> > +
> > +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> > +
> > +Updated: 12 May 2012
> > +
> > +Copyright (c) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> > +
> > +0. Introduction
> > +
> > +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> > +to the caller. The binding of the cooling devices to the trip point is left for
> > +the user. The registration APIs returns the cooling device pointer.
> > +
> > +1. cpu cooling APIs
> > +
> > +1.1 cpufreq registration/unregistration APIs
> > +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> > + struct cpumask *clip_cpus)
> > +
> > + This interface function registers the cpufreq cooling device with the name
> > + "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> > + cooling devices.
> > +
> > + clip_cpus: cpumask of cpus where the frequency constraints will happen.
> > +
> > +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> > +
> > + This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> > +
> > + cdev: Cooling device pointer which has to be unregistered.
> > +
> > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> > index 7dd8c34..996003b 100644
> > --- a/drivers/thermal/Kconfig
> > +++ b/drivers/thermal/Kconfig
> > @@ -19,6 +19,17 @@ config THERMAL_HWMON
> > depends on HWMON=y || HWMON=THERMAL
> > default y
> >
> > +config CPU_THERMAL
> > + bool "generic cpu cooling support"
> > + depends on THERMAL && CPU_FREQ
> > + help
> > + This implements the generic cpu cooling mechanism through frequency
> > + reduction, cpu hotplug and any other ways of reducing temperature. An
> > + ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> > + This will be useful for platforms using the generic thermal interface
> > + and not the ACPI interface.
> > + If you want this support, you should say Y here.
> > +
> > config SPEAR_THERMAL
> > bool "SPEAr thermal sensor driver"
> > depends on THERMAL
> > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> > index fd9369a..aae59ad 100644
> > --- a/drivers/thermal/Makefile
> > +++ b/drivers/thermal/Makefile
> > @@ -3,5 +3,6 @@
> > #
> >
> > obj-$(CONFIG_THERMAL) += thermal_sys.o
> > +obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
> > obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
> > obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
> > diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> > new file mode 100644
> > index 0000000..99a5d75
> > --- /dev/null
> > +++ b/drivers/thermal/cpu_cooling.c
> > @@ -0,0 +1,450 @@
> > +/*
> > + * linux/drivers/thermal/cpu_cooling.c
> > + *
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> > + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
> > + *
> > + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; version 2 of the License.
> > + *
> > + * This program is distributed in the hope that it will be useful, but
> > + * WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> > + * General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU General Public License along
> > + * with this program; if not, write to the Free Software Foundation, Inc.,
> > + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> > + *
> > + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + */
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/thermal.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/cpufreq.h>
> > +#include <linux/err.h>
> > +#include <linux/slab.h>
> > +#include <linux/cpu.h>
> > +#include <linux/cpu_cooling.h>
> > +
> > +/**
> > + * struct cpufreq_cooling_device
> > + * @id: unique integer value corresponding to each cpufreq_cooling_device
> > + * registered.
> > + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> > + * egistered cooling device.
> > + * @cpufreq_state: integer value representing the current state of cpufreq
> > + * cooling devices.
> > + * @cpufreq_val: integer value representing the absolute value of the clipped
> > + * frequency.
> > + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> > + * @node: list_head to link all cpufreq_cooling_device together.
> > + *
> > + * This structure is required for keeping information of each
> > + * cpufreq_cooling_device registered as a list whose head is represented by
> > + * cooling_cpufreq_list. In order to prevent corruption of this list a
> > + * mutex lock cooling_cpufreq_lock is used.
> > + */
> > +struct cpufreq_cooling_device {
> > + int id;
> > + struct thermal_cooling_device *cool_dev;
> > + unsigned int cpufreq_state;
> > + unsigned int cpufreq_val;
> > + struct cpumask allowed_cpus;
> > + struct list_head node;
> > +};
> > +static LIST_HEAD(cooling_cpufreq_list);
> > +static DEFINE_IDR(cpufreq_idr);
> > +
> > +static struct mutex cooling_cpufreq_lock;
> > +
> > +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> > +#define NOTIFY_INVALID NULL
> > +struct cpufreq_cooling_device *notify_device;
> > +
> > +/**
> > + * get_idr - function to get a unique id.
> > + * @idr: struct idr * handle used to create a id.
> > + * @id: int * value generated by this function.
> > + */
> > +static int get_idr(struct idr *idr, int *id)
> > +{
> > + int err;
> > +again:
> > + if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> > + return -ENOMEM;
> > +
> > + mutex_lock(&cooling_cpufreq_lock);
> > + err = idr_get_new(idr, NULL, id);
> > + mutex_unlock(&cooling_cpufreq_lock);
> > +
> > + if (unlikely(err == -EAGAIN))
> > + goto again;
> > + else if (unlikely(err))
> > + return err;
> > +
> > + *id = *id & MAX_ID_MASK;
> > + return 0;
> > +}
> > +
> > +/**
> > + * release_idr - function to free the unique id.
> > + * @idr: struct idr * handle used for creating the id.
> > + * @id: int value representing the unique id.
> > + */
> > +static void release_idr(struct idr *idr, int id)
> > +{
> > + mutex_lock(&cooling_cpufreq_lock);
> > + idr_remove(idr, id);
> > + mutex_unlock(&cooling_cpufreq_lock);
> > +}
> > +
> > +/* Below code defines functions to be used for cpufreq as cooling device */
> > +
> > +/**
> > + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> > + * @cpu: cpu for which check is needed.
> > + */
> > +static int is_cpufreq_valid(int cpu)
> > +{
> > + struct cpufreq_policy policy;
> > + return !cpufreq_get_policy(&policy, cpu);
> > +}
> > +
> > +/**
> > + * get_cpu_frequency - get the absolute value of frequency from level.
> > + * @cpu: cpu for which frequency is fetched.
> > + * @level: level of frequency of the CPU
> > + * e.g level=1 --> 1st MAX FREQ, LEVEL=2 ---> 2nd MAX FREQ, .... etc
> > + */
> > +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
> > +{
> > + int ret = 0, i = 0;
> > + unsigned long level_index;
> > + bool descend = false;
> > + struct cpufreq_frequency_table *table =
> > + cpufreq_frequency_get_table(cpu);
> > + if (!table)
> > + return ret;
> > +
> > + while (table[i].frequency != CPUFREQ_TABLE_END) {
> > + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> > + continue;
> > +
> > + /*check if table in ascending or descending order*/
> > + if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> > + (table[i + 1].frequency < table[i].frequency)
> > + && !descend) {
> > + descend = true;
> > + }
> > +
> > + /*return if level matched and table in descending order*/
> > + if (descend && i == level)
> > + return table[i].frequency;
> > + i++;
> > + }
> > + i--;
> > +
> > + if (level > i || descend)
> > + return ret;
> > + level_index = i - level;
> > +
> > + /*Scan the table in reverse order and match the level*/
> > + while (i >= 0) {
> > + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> > + continue;
> > + /*return if level matched*/
> > + if (i == level_index)
> > + return table[i].frequency;
> > + i--;
> > + }
> > + return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_apply_cooling - function to apply frequency clipping.
> > + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> > + * clipping data.
> > + * @cooling_state: value of the cooling state.
> > + */
> > +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> > + unsigned long cooling_state)
> > +{
> > + unsigned int cpuid, clip_freq;
> > + struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> > + unsigned int cpu = cpumask_any(maskPtr);
> > +
> > +
> > + /* Check if the old cooling action is same as new cooling action */
> > + if (cpufreq_device->cpufreq_state == cooling_state)
> > + return 0;
> > +
> > + clip_freq = get_cpu_frequency(cpu, cooling_state);
> > + if (!clip_freq)
> > + return -EINVAL;
> > +
> > + cpufreq_device->cpufreq_state = cooling_state;
> > + cpufreq_device->cpufreq_val = clip_freq;
> > + notify_device = cpufreq_device;
> > +
> > + for_each_cpu(cpuid, maskPtr) {
> > + if (is_cpufreq_valid(cpuid))
> > + cpufreq_update_policy(cpuid);
> > + }
> > +
> > + notify_device = NOTIFY_INVALID;
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> > + * @nb: struct notifier_block * with callback info.
> > + * @event: value showing cpufreq event for which this function invoked.
> > + * @data: callback-specific data
> > + */
> > +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> > + unsigned long event, void *data)
> > +{
> > + struct cpufreq_policy *policy = data;
> > + unsigned long max_freq = 0;
> > +
> > + if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> > + return 0;
> > +
> > + if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus))
> > + max_freq = notify_device->cpufreq_val;
> > +
> > + /* Never exceed user_policy.max*/
> > + if (max_freq > policy->user_policy.max)
> > + max_freq = policy->user_policy.max;
> > +
> > + if (policy->max != max_freq)
> > + cpufreq_verify_within_limits(policy, 0, max_freq);
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * cpufreq cooling device callback functions are defined below
> > + */
> > +
> > +/**
> > + * cpufreq_get_max_state - callback function to get the max cooling state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: fill this variable with the max cooling state.
> > + */
> > +static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
> > + unsigned long *state)
> > +{
> > + int ret = -EINVAL, i = 0;
> > + struct cpufreq_cooling_device *cpufreq_device;
> > + struct cpumask *maskPtr;
> > + unsigned int cpu;
> > + struct cpufreq_frequency_table *table;
> > +
> > + mutex_lock(&cooling_cpufreq_lock);
> > + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> > + if (cpufreq_device && cpufreq_device->cool_dev == cdev)
> > + break;
> > + }
> > + if (cpufreq_device == NULL)
> > + goto return_get_max_state;
> > +
> > + maskPtr = &cpufreq_device->allowed_cpus;
> > + cpu = cpumask_any(maskPtr);
> > + table = cpufreq_frequency_get_table(cpu);
> > + if (!table) {
> > + *state = 0;
> > + ret = 0;
> > + goto return_get_max_state;
> > + }
> > +
> > + while (table[i].frequency != CPUFREQ_TABLE_END) {
> > + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> > + continue;
> > + i++;
> > + }
> > + if (i > 0) {
> > + *state = --i;
> > + ret = 0;
> > + }
> > +
> > +return_get_max_state:
> > + mutex_unlock(&cooling_cpufreq_lock);
> > + return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_get_cur_state - callback function to get the current cooling state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: fill this variable with the current cooling state.
> > + */
> > +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
> > + unsigned long *state)
> > +{
> > + int ret = -EINVAL;
> > + struct cpufreq_cooling_device *cpufreq_device;
> > +
> > + mutex_lock(&cooling_cpufreq_lock);
> > + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> > + if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
> > + *state = cpufreq_device->cpufreq_state;
> > + ret = 0;
> > + break;
> > + }
> > + }
> > + mutex_unlock(&cooling_cpufreq_lock);
> > +
> > + return ret;
> > +}
> > +
> > +/**
> > + * cpufreq_set_cur_state - callback function to set the current cooling state.
> > + * @cdev: thermal cooling device pointer.
> > + * @state: set this variable to the current cooling state.
> > + */
> > +static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
> > + unsigned long state)
> > +{
> > + int ret = -EINVAL;
> > + struct cpufreq_cooling_device *cpufreq_device;
> > +
> > + mutex_lock(&cooling_cpufreq_lock);
> > + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> > + if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
> > + ret = 0;
> > + break;
> > + }
> > + }
> > + if (!ret)
> > + ret = cpufreq_apply_cooling(cpufreq_device, state);
> > +
> > + mutex_unlock(&cooling_cpufreq_lock);
> > +
> > + return ret;
> > +}
> > +
> > +/* Bind cpufreq callbacks to thermal cooling device ops */
> > +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> > + .get_max_state = cpufreq_get_max_state,
> > + .get_cur_state = cpufreq_get_cur_state,
> > + .set_cur_state = cpufreq_set_cur_state,
> > +};
> > +
> > +/* Notifier for cpufreq policy change */
> > +static struct notifier_block thermal_cpufreq_notifier_block = {
> > + .notifier_call = cpufreq_thermal_notifier,
> > +};
> > +
> > +/**
> > + * cpufreq_cooling_register - function to create cpufreq cooling device.
> > + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> > + */
> > +struct thermal_cooling_device *cpufreq_cooling_register(
> > + struct cpumask *clip_cpus)
> > +{
> > + struct thermal_cooling_device *cool_dev;
> > + struct cpufreq_cooling_device *cpufreq_dev = NULL;
> > + unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
> > + char dev_name[THERMAL_NAME_LENGTH];
> > + int ret = 0, id = 0, i;
> > + struct cpufreq_policy policy;
> > +
> > + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
> > + cpufreq_dev_count++;
> > +
> > + /*Verify that all the clip cpus have same freq_min, freq_max limit*/
> > + for_each_cpu(i, clip_cpus) {
> > + /*continue if cpufreq policy not found and not return error*/
> > + if (!cpufreq_get_policy(&policy, i))
> > + continue;
> > + if (min == 0 && max == 0) {
> > + min = policy.cpuinfo.min_freq;
> > + max = policy.cpuinfo.max_freq;
> > + } else {
> > + if (min != policy.cpuinfo.min_freq ||
> > + max != policy.cpuinfo.max_freq)
> > + return ERR_PTR(-EINVAL);
> > +}
> > + }
> > + cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device),
> > + GFP_KERNEL);
> > + if (!cpufreq_dev)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
> > +
> > + if (cpufreq_dev_count == 0)
> > + mutex_init(&cooling_cpufreq_lock);
> > +
> > + ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
> > + if (ret) {
> > + kfree(cpufreq_dev);
> > + return ERR_PTR(-EINVAL);
> > + }
> > +
> > + sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> > +
> > + cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> > + &cpufreq_cooling_ops);
> > + if (!cool_dev) {
> > + release_idr(&cpufreq_idr, cpufreq_dev->id);
> > + kfree(cpufreq_dev);
> > + return ERR_PTR(-EINVAL);
> > + }
> > + cpufreq_dev->id = id;
> > + cpufreq_dev->cool_dev = cool_dev;
> > + cpufreq_dev->cpufreq_state = 0;
> > + mutex_lock(&cooling_cpufreq_lock);
> > + list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
> > +
> > + /* Register the notifier for first cpufreq cooling device */
> > + if (cpufreq_dev_count == 0)
> > + cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
> > + CPUFREQ_POLICY_NOTIFIER);
> > +
> > + mutex_unlock(&cooling_cpufreq_lock);
> > + return cool_dev;
> > +}
> > +EXPORT_SYMBOL(cpufreq_cooling_register);
> > +
> > +/**
> > + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> > + * @cdev: thermal cooling device pointer.
> > + */
> > +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> > +{
> > + struct cpufreq_cooling_device *cpufreq_dev = NULL;
> > + unsigned int cpufreq_dev_count = 0;
> > +
> > + mutex_lock(&cooling_cpufreq_lock);
> > + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
> > + if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
> > + break;
> > + cpufreq_dev_count++;
> > + }
> > +
> > + if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
> > + mutex_unlock(&cooling_cpufreq_lock);
> > + return;
> > + }
> > +
> > + list_del(&cpufreq_dev->node);
> > +
> > + /* Unregister the notifier for the last cpufreq cooling device */
> > + if (cpufreq_dev_count == 1) {
> > + cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
> > + CPUFREQ_POLICY_NOTIFIER);
> > + }
> > + mutex_unlock(&cooling_cpufreq_lock);
> > + thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
> > + release_idr(&cpufreq_idr, cpufreq_dev->id);
> > + if (cpufreq_dev_count == 1)
> > + mutex_destroy(&cooling_cpufreq_lock);
> > + kfree(cpufreq_dev);
> > +}
> > +EXPORT_SYMBOL(cpufreq_cooling_unregister);
> > diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> > new file mode 100644
> > index 0000000..8515301
> > --- /dev/null
> > +++ b/include/linux/cpu_cooling.h
> > @@ -0,0 +1,58 @@
> > +/*
> > + * linux/include/linux/cpu_cooling.h
> > + *
> > + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> > + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
> > + *
> > + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; version 2 of the License.
> > + *
> > + * This program is distributed in the hope that it will be useful, but
> > + * WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> > + * General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU General Public License along
> > + * with this program; if not, write to the Free Software Foundation, Inc.,
> > + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> > + *
> > + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + */
> > +
> > +#ifndef __CPU_COOLING_H__
> > +#define __CPU_COOLING_H__
> > +
> > +#include <linux/thermal.h>
> > +
> > +#define CPUFREQ_COOLING_START 0
> > +#define CPUFREQ_COOLING_STOP 1
> > +
> > +#ifdef CONFIG_CPU_THERMAL
> > +/**
> > + * cpufreq_cooling_register - function to create cpufreq cooling device.
> > + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> > + */
> > +struct thermal_cooling_device *cpufreq_cooling_register(
> > + struct cpumask *clip_cpus);
> > +
> > +/**
> > + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> > + * @cdev: thermal cooling device pointer.
> > + */
> > +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> > +#else /* !CONFIG_CPU_THERMAL */
> > +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> > + struct cpumask *clip_cpus)
> > +{
> > + return NULL;
> > +}
> > +static inline void cpufreq_cooling_unregister(
> > + struct thermal_cooling_device *cdev)
> > +{
> > + return;
> > +}
> > +#endif /* CONFIG_CPU_THERMAL */
> > +
> > +#endif /* __CPU_COOLING_H__ */
> > --
> > 1.7.7.5
> >
> >
>
>
>
^ permalink raw reply
* Re: [PATCH RESEND] thermal: add generic cpufreq cooling implementation
From: Rafael J. Wysocki @ 2012-09-10 20:23 UTC (permalink / raw)
To: linux-pm
Cc: linux-samsung-soc, Kyungmin Park, SangWook Ju, linux-kernel,
lm-sensors, linux-acpi, Jean Delvare, Andrew Morton,
Guenter Roeck
In-Reply-To: <1347258341.1682.1062.camel@rui.sh.intel.com>
On Monday, September 10, 2012, Zhang Rui wrote:
> Refreshed to remove the notifier mechanism as we do not have a real user of it.
> if there is no problem, I'll apply the whole patch set to thermal next tree.
>
> From: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Date: Thu, 16 Aug 2012 17:11:40 +0530
>
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms. As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
> * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
> * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
> as placing inside acpi folder will need un-necessary enabling of acpi
> code. This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
> through frequency clipping. In future, other cpu related cooling
> devices may be added here. An ACPI version of this already exists
> (drivers/acpi/processor_thermal.c) .But this will be useful for
> platforms like ARM using the generic thermal interface along with the
> generic cpu cooling devices. The cooling device registration API's
> return cooling device pointers which can be easily binded with the
> thermal zone trip points. The important APIs exposed are,
>
> a) struct thermal_cooling_device *cpufreq_cooling_register(
> struct cpumask *clip_cpus)
> b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
> generic cpu cooling APIs and the new trip type. The temperature sensor
> driver present in the hwmon folder(registered as hwmon driver) is moved
> to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <-----> Exynos thermal interface <----- Temperature Sensor
> | |
> \|/ |
> Cpufreq cooling device <---------------
>
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
>
> This patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
Can you please resend the patch inline (i.e. not as an attachment)?
Rafael
^ permalink raw reply
* Re: [linux-pm] [PATCH RESEND] thermal: add generic cpufreq cooling implementation
From: Rafael J. Wysocki @ 2012-09-10 20:26 UTC (permalink / raw)
To: linux-pm
Cc: linux-samsung-soc, Kyungmin Park, SangWook Ju, linux-kernel,
lm-sensors, linux-acpi, Jean Delvare, Andrew Morton,
Guenter Roeck
In-Reply-To: <201209102223.53094.rjw@sisk.pl>
On Monday, September 10, 2012, Rafael J. Wysocki wrote:
> On Monday, September 10, 2012, Zhang Rui wrote:
> > Refreshed to remove the notifier mechanism as we do not have a real user of it.
> > if there is no problem, I'll apply the whole patch set to thermal next tree.
> >
> > From: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> > Date: Thu, 16 Aug 2012 17:11:40 +0530
> >
> > This patchset introduces a new generic cooling device based on cpufreq
> > that can be used on non-ACPI platforms. As a proof of concept, we have
> > drivers for the following platforms using this mechanism now:
> >
> > * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
> > * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
> >
> > There is a small change in cpufreq cooling registration APIs, so a minor
> > change is needed for Freescale platforms.
> >
> > Brief Description:
> >
> > 1) The generic cooling devices code is placed inside driver/thermal/*
> > as placing inside acpi folder will need un-necessary enabling of acpi
> > code. This code is architecture independent.
> >
> > 2) This patchset adds generic cpu cooling low level implementation
> > through frequency clipping. In future, other cpu related cooling
> > devices may be added here. An ACPI version of this already exists
> > (drivers/acpi/processor_thermal.c) .But this will be useful for
> > platforms like ARM using the generic thermal interface along with the
> > generic cpu cooling devices. The cooling device registration API's
> > return cooling device pointers which can be easily binded with the
> > thermal zone trip points. The important APIs exposed are,
> >
> > a) struct thermal_cooling_device *cpufreq_cooling_register(
> > struct cpumask *clip_cpus)
> > b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> >
> > 3) Samsung exynos platform thermal implementation is done using the
> > generic cpu cooling APIs and the new trip type. The temperature sensor
> > driver present in the hwmon folder(registered as hwmon driver) is moved
> > to thermal folder and registered as a thermal driver.
> >
> > A simple data/control flow diagrams is shown below,
> >
> > Core Linux thermal <-----> Exynos thermal interface <----- Temperature Sensor
> > | |
> > \|/ |
> > Cpufreq cooling device <---------------
> >
> > TODO:
> > *Will send the DT enablement patches later after the driver is merged.
> >
> > This patch:
> >
> > Add support for generic cpu thermal cooling low level implementations
> > using frequency scaling up/down based on the registration parameters.
> > Different cpu related cooling devices can be registered by the user and
> > the binding of these cooling devices to the corresponding trip points can
> > be easily done as the registration APIs return the cooling device pointer.
> > The user of these APIs are responsible for passing clipping frequency .
> > The drivers can also register to recieve notification about any cooling
> > action called.
> >
> > Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> > Cc: SangWook Ju <sw.ju@samsung.com>
> > Cc: Durgadoss <durgadoss.r@intel.com>
> > Cc: Len Brown <lenb@kernel.org>
> > Cc: Jean Delvare <khali@linux-fr.org>
> > Cc: Kyungmin Park <kmpark@infradead.org>
> > Cc: Kukjin Kim <kgene.kim@samsung.com>
> > Signed-off-by: Zhang Rui <rui.zhang@intel.com>
> > Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> > Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
>
> Can you please resend the patch inline (i.e. not as an attachment)?
Sorry, it was inline (obviously). I seem to be too tired.
Please resend it to linux-pm@vger.kernel.org. The linux-pm list at
linux-foundation.org is not functional any more.
Thanks,
Rafael
^ permalink raw reply
* Re: [linux-pm] [PATCH RESEND] thermal: add generic cpufreq cooling implementation
From: amit kachhap @ 2012-09-11 9:04 UTC (permalink / raw)
To: Zhang Rui
Cc: linux-pm, linux-samsung-soc, SangWook Ju, linux-kernel,
lm-sensors, linux-acpi, Jean Delvare, Kyungmin Park,
Andrew Morton, Guenter Roeck
In-Reply-To: <1347258341.1682.1062.camel@rui.sh.intel.com>
On Mon, Sep 10, 2012 at 3:25 PM, Zhang Rui <rui.zhang@intel.com> wrote:
> Refreshed to remove the notifier mechanism as we do not have a real user of it.
> if there is no problem, I'll apply the whole patch set to thermal next tree.
The removal of notification API looks fine. Thanks for refreshing this.
Thanks,
Amit Daniel
>
> From: Amit Daniel Kachhap <amit.kachhap@linaro.org>
> Date: Thu, 16 Aug 2012 17:11:40 +0530
>
> This patchset introduces a new generic cooling device based on cpufreq
> that can be used on non-ACPI platforms. As a proof of concept, we have
> drivers for the following platforms using this mechanism now:
>
> * Samsung Exynos (Exynos4 and Exynos5) in the current patchset.
> * Freescale i.MX (git://git.linaro.org/people/amitdanielk/linux.git imx6q_thermal)
>
> There is a small change in cpufreq cooling registration APIs, so a minor
> change is needed for Freescale platforms.
>
> Brief Description:
>
> 1) The generic cooling devices code is placed inside driver/thermal/*
> as placing inside acpi folder will need un-necessary enabling of acpi
> code. This code is architecture independent.
>
> 2) This patchset adds generic cpu cooling low level implementation
> through frequency clipping. In future, other cpu related cooling
> devices may be added here. An ACPI version of this already exists
> (drivers/acpi/processor_thermal.c) .But this will be useful for
> platforms like ARM using the generic thermal interface along with the
> generic cpu cooling devices. The cooling device registration API's
> return cooling device pointers which can be easily binded with the
> thermal zone trip points. The important APIs exposed are,
>
> a) struct thermal_cooling_device *cpufreq_cooling_register(
> struct cpumask *clip_cpus)
> b) void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
>
> 3) Samsung exynos platform thermal implementation is done using the
> generic cpu cooling APIs and the new trip type. The temperature sensor
> driver present in the hwmon folder(registered as hwmon driver) is moved
> to thermal folder and registered as a thermal driver.
>
> A simple data/control flow diagrams is shown below,
>
> Core Linux thermal <-----> Exynos thermal interface <----- Temperature Sensor
> | |
> \|/ |
> Cpufreq cooling device <---------------
>
> TODO:
> *Will send the DT enablement patches later after the driver is merged.
>
> This patch:
>
> Add support for generic cpu thermal cooling low level implementations
> using frequency scaling up/down based on the registration parameters.
> Different cpu related cooling devices can be registered by the user and
> the binding of these cooling devices to the corresponding trip points can
> be easily done as the registration APIs return the cooling device pointer.
> The user of these APIs are responsible for passing clipping frequency .
> The drivers can also register to recieve notification about any cooling
> action called.
>
> Cc: Guenter Roeck <guenter.roeck@ericsson.com>
> Cc: SangWook Ju <sw.ju@samsung.com>
> Cc: Durgadoss <durgadoss.r@intel.com>
> Cc: Len Brown <lenb@kernel.org>
> Cc: Jean Delvare <khali@linux-fr.org>
> Cc: Kyungmin Park <kmpark@infradead.org>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Amit Daniel Kachhap <amit.daniel@samsung.com>
> ---
> Documentation/thermal/cpu-cooling-api.txt | 33 ++
> drivers/thermal/Kconfig | 11 +
> drivers/thermal/Makefile | 1 +
> drivers/thermal/cpu_cooling.c | 450 +++++++++++++++++++++++++++++
> include/linux/cpu_cooling.h | 58 ++++
> 5 files changed, 553 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/thermal/cpu-cooling-api.txt
> create mode 100644 drivers/thermal/cpu_cooling.c
> create mode 100644 include/linux/cpu_cooling.h
>
> diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt
> new file mode 100644
> index 0000000..6fe9cdb
> --- /dev/null
> +++ b/Documentation/thermal/cpu-cooling-api.txt
> @@ -0,0 +1,33 @@
> +CPU cooling APIs How To
> +===================================
> +
> +Written by Amit Daniel Kachhap <amit.kachhap@linaro.org>
> +
> +Updated: 12 May 2012
> +
> +Copyright (c) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> +
> +0. Introduction
> +
> +The generic cpu cooling(freq clipping) provides registration/unregistration APIs
> +to the caller. The binding of the cooling devices to the trip point is left for
> +the user. The registration APIs returns the cooling device pointer.
> +
> +1. cpu cooling APIs
> +
> +1.1 cpufreq registration/unregistration APIs
> +1.1.1 struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus)
> +
> + This interface function registers the cpufreq cooling device with the name
> + "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
> + cooling devices.
> +
> + clip_cpus: cpumask of cpus where the frequency constraints will happen.
> +
> +1.1.2 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +
> + This interface function unregisters the "thermal-cpufreq-%x" cooling device.
> +
> + cdev: Cooling device pointer which has to be unregistered.
> +
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index 7dd8c34..996003b 100644
> --- a/drivers/thermal/Kconfig
> +++ b/drivers/thermal/Kconfig
> @@ -19,6 +19,17 @@ config THERMAL_HWMON
> depends on HWMON=y || HWMON=THERMAL
> default y
>
> +config CPU_THERMAL
> + bool "generic cpu cooling support"
> + depends on THERMAL && CPU_FREQ
> + help
> + This implements the generic cpu cooling mechanism through frequency
> + reduction, cpu hotplug and any other ways of reducing temperature. An
> + ACPI version of this already exists(drivers/acpi/processor_thermal.c).
> + This will be useful for platforms using the generic thermal interface
> + and not the ACPI interface.
> + If you want this support, you should say Y here.
> +
> config SPEAR_THERMAL
> bool "SPEAr thermal sensor driver"
> depends on THERMAL
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index fd9369a..aae59ad 100644
> --- a/drivers/thermal/Makefile
> +++ b/drivers/thermal/Makefile
> @@ -3,5 +3,6 @@
> #
>
> obj-$(CONFIG_THERMAL) += thermal_sys.o
> +obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
> obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
> obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..99a5d75
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,450 @@
> +/*
> + * linux/drivers/thermal/cpu_cooling.c
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/platform_device.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/cpu.h>
> +#include <linux/cpu_cooling.h>
> +
> +/**
> + * struct cpufreq_cooling_device
> + * @id: unique integer value corresponding to each cpufreq_cooling_device
> + * registered.
> + * @cool_dev: thermal_cooling_device pointer to keep track of the the
> + * egistered cooling device.
> + * @cpufreq_state: integer value representing the current state of cpufreq
> + * cooling devices.
> + * @cpufreq_val: integer value representing the absolute value of the clipped
> + * frequency.
> + * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
> + * @node: list_head to link all cpufreq_cooling_device together.
> + *
> + * This structure is required for keeping information of each
> + * cpufreq_cooling_device registered as a list whose head is represented by
> + * cooling_cpufreq_list. In order to prevent corruption of this list a
> + * mutex lock cooling_cpufreq_lock is used.
> + */
> +struct cpufreq_cooling_device {
> + int id;
> + struct thermal_cooling_device *cool_dev;
> + unsigned int cpufreq_state;
> + unsigned int cpufreq_val;
> + struct cpumask allowed_cpus;
> + struct list_head node;
> +};
> +static LIST_HEAD(cooling_cpufreq_list);
> +static DEFINE_IDR(cpufreq_idr);
> +
> +static struct mutex cooling_cpufreq_lock;
> +
> +/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
> +#define NOTIFY_INVALID NULL
> +struct cpufreq_cooling_device *notify_device;
> +
> +/**
> + * get_idr - function to get a unique id.
> + * @idr: struct idr * handle used to create a id.
> + * @id: int * value generated by this function.
> + */
> +static int get_idr(struct idr *idr, int *id)
> +{
> + int err;
> +again:
> + if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0))
> + return -ENOMEM;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + err = idr_get_new(idr, NULL, id);
> + mutex_unlock(&cooling_cpufreq_lock);
> +
> + if (unlikely(err == -EAGAIN))
> + goto again;
> + else if (unlikely(err))
> + return err;
> +
> + *id = *id & MAX_ID_MASK;
> + return 0;
> +}
> +
> +/**
> + * release_idr - function to free the unique id.
> + * @idr: struct idr * handle used for creating the id.
> + * @id: int value representing the unique id.
> + */
> +static void release_idr(struct idr *idr, int id)
> +{
> + mutex_lock(&cooling_cpufreq_lock);
> + idr_remove(idr, id);
> + mutex_unlock(&cooling_cpufreq_lock);
> +}
> +
> +/* Below code defines functions to be used for cpufreq as cooling device */
> +
> +/**
> + * is_cpufreq_valid - function to check if a cpu has frequency transition policy.
> + * @cpu: cpu for which check is needed.
> + */
> +static int is_cpufreq_valid(int cpu)
> +{
> + struct cpufreq_policy policy;
> + return !cpufreq_get_policy(&policy, cpu);
> +}
> +
> +/**
> + * get_cpu_frequency - get the absolute value of frequency from level.
> + * @cpu: cpu for which frequency is fetched.
> + * @level: level of frequency of the CPU
> + * e.g level=1 --> 1st MAX FREQ, LEVEL=2 ---> 2nd MAX FREQ, .... etc
> + */
> +static unsigned int get_cpu_frequency(unsigned int cpu, unsigned long level)
> +{
> + int ret = 0, i = 0;
> + unsigned long level_index;
> + bool descend = false;
> + struct cpufreq_frequency_table *table =
> + cpufreq_frequency_get_table(cpu);
> + if (!table)
> + return ret;
> +
> + while (table[i].frequency != CPUFREQ_TABLE_END) {
> + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> + continue;
> +
> + /*check if table in ascending or descending order*/
> + if ((table[i + 1].frequency != CPUFREQ_TABLE_END) &&
> + (table[i + 1].frequency < table[i].frequency)
> + && !descend) {
> + descend = true;
> + }
> +
> + /*return if level matched and table in descending order*/
> + if (descend && i == level)
> + return table[i].frequency;
> + i++;
> + }
> + i--;
> +
> + if (level > i || descend)
> + return ret;
> + level_index = i - level;
> +
> + /*Scan the table in reverse order and match the level*/
> + while (i >= 0) {
> + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> + continue;
> + /*return if level matched*/
> + if (i == level_index)
> + return table[i].frequency;
> + i--;
> + }
> + return ret;
> +}
> +
> +/**
> + * cpufreq_apply_cooling - function to apply frequency clipping.
> + * @cpufreq_device: cpufreq_cooling_device pointer containing frequency
> + * clipping data.
> + * @cooling_state: value of the cooling state.
> + */
> +static int cpufreq_apply_cooling(struct cpufreq_cooling_device *cpufreq_device,
> + unsigned long cooling_state)
> +{
> + unsigned int cpuid, clip_freq;
> + struct cpumask *maskPtr = &cpufreq_device->allowed_cpus;
> + unsigned int cpu = cpumask_any(maskPtr);
> +
> +
> + /* Check if the old cooling action is same as new cooling action */
> + if (cpufreq_device->cpufreq_state == cooling_state)
> + return 0;
> +
> + clip_freq = get_cpu_frequency(cpu, cooling_state);
> + if (!clip_freq)
> + return -EINVAL;
> +
> + cpufreq_device->cpufreq_state = cooling_state;
> + cpufreq_device->cpufreq_val = clip_freq;
> + notify_device = cpufreq_device;
> +
> + for_each_cpu(cpuid, maskPtr) {
> + if (is_cpufreq_valid(cpuid))
> + cpufreq_update_policy(cpuid);
> + }
> +
> + notify_device = NOTIFY_INVALID;
> +
> + return 0;
> +}
> +
> +/**
> + * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
> + * @nb: struct notifier_block * with callback info.
> + * @event: value showing cpufreq event for which this function invoked.
> + * @data: callback-specific data
> + */
> +static int cpufreq_thermal_notifier(struct notifier_block *nb,
> + unsigned long event, void *data)
> +{
> + struct cpufreq_policy *policy = data;
> + unsigned long max_freq = 0;
> +
> + if (event != CPUFREQ_ADJUST || notify_device == NOTIFY_INVALID)
> + return 0;
> +
> + if (cpumask_test_cpu(policy->cpu, ¬ify_device->allowed_cpus))
> + max_freq = notify_device->cpufreq_val;
> +
> + /* Never exceed user_policy.max*/
> + if (max_freq > policy->user_policy.max)
> + max_freq = policy->user_policy.max;
> +
> + if (policy->max != max_freq)
> + cpufreq_verify_within_limits(policy, 0, max_freq);
> +
> + return 0;
> +}
> +
> +/*
> + * cpufreq cooling device callback functions are defined below
> + */
> +
> +/**
> + * cpufreq_get_max_state - callback function to get the max cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the max cooling state.
> + */
> +static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + int ret = -EINVAL, i = 0;
> + struct cpufreq_cooling_device *cpufreq_device;
> + struct cpumask *maskPtr;
> + unsigned int cpu;
> + struct cpufreq_frequency_table *table;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> + if (cpufreq_device && cpufreq_device->cool_dev == cdev)
> + break;
> + }
> + if (cpufreq_device == NULL)
> + goto return_get_max_state;
> +
> + maskPtr = &cpufreq_device->allowed_cpus;
> + cpu = cpumask_any(maskPtr);
> + table = cpufreq_frequency_get_table(cpu);
> + if (!table) {
> + *state = 0;
> + ret = 0;
> + goto return_get_max_state;
> + }
> +
> + while (table[i].frequency != CPUFREQ_TABLE_END) {
> + if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
> + continue;
> + i++;
> + }
> + if (i > 0) {
> + *state = --i;
> + ret = 0;
> + }
> +
> +return_get_max_state:
> + mutex_unlock(&cooling_cpufreq_lock);
> + return ret;
> +}
> +
> +/**
> + * cpufreq_get_cur_state - callback function to get the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: fill this variable with the current cooling state.
> + */
> +static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long *state)
> +{
> + int ret = -EINVAL;
> + struct cpufreq_cooling_device *cpufreq_device;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> + if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
> + *state = cpufreq_device->cpufreq_state;
> + ret = 0;
> + break;
> + }
> + }
> + mutex_unlock(&cooling_cpufreq_lock);
> +
> + return ret;
> +}
> +
> +/**
> + * cpufreq_set_cur_state - callback function to set the current cooling state.
> + * @cdev: thermal cooling device pointer.
> + * @state: set this variable to the current cooling state.
> + */
> +static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
> + unsigned long state)
> +{
> + int ret = -EINVAL;
> + struct cpufreq_cooling_device *cpufreq_device;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_device, &cooling_cpufreq_list, node) {
> + if (cpufreq_device && cpufreq_device->cool_dev == cdev) {
> + ret = 0;
> + break;
> + }
> + }
> + if (!ret)
> + ret = cpufreq_apply_cooling(cpufreq_device, state);
> +
> + mutex_unlock(&cooling_cpufreq_lock);
> +
> + return ret;
> +}
> +
> +/* Bind cpufreq callbacks to thermal cooling device ops */
> +static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
> + .get_max_state = cpufreq_get_max_state,
> + .get_cur_state = cpufreq_get_cur_state,
> + .set_cur_state = cpufreq_set_cur_state,
> +};
> +
> +/* Notifier for cpufreq policy change */
> +static struct notifier_block thermal_cpufreq_notifier_block = {
> + .notifier_call = cpufreq_thermal_notifier,
> +};
> +
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus)
> +{
> + struct thermal_cooling_device *cool_dev;
> + struct cpufreq_cooling_device *cpufreq_dev = NULL;
> + unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
> + char dev_name[THERMAL_NAME_LENGTH];
> + int ret = 0, id = 0, i;
> + struct cpufreq_policy policy;
> +
> + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node)
> + cpufreq_dev_count++;
> +
> + /*Verify that all the clip cpus have same freq_min, freq_max limit*/
> + for_each_cpu(i, clip_cpus) {
> + /*continue if cpufreq policy not found and not return error*/
> + if (!cpufreq_get_policy(&policy, i))
> + continue;
> + if (min == 0 && max == 0) {
> + min = policy.cpuinfo.min_freq;
> + max = policy.cpuinfo.max_freq;
> + } else {
> + if (min != policy.cpuinfo.min_freq ||
> + max != policy.cpuinfo.max_freq)
> + return ERR_PTR(-EINVAL);
> +}
> + }
> + cpufreq_dev = kzalloc(sizeof(struct cpufreq_cooling_device),
> + GFP_KERNEL);
> + if (!cpufreq_dev)
> + return ERR_PTR(-ENOMEM);
> +
> + cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
> +
> + if (cpufreq_dev_count == 0)
> + mutex_init(&cooling_cpufreq_lock);
> +
> + ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
> + if (ret) {
> + kfree(cpufreq_dev);
> + return ERR_PTR(-EINVAL);
> + }
> +
> + sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> + cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> + &cpufreq_cooling_ops);
> + if (!cool_dev) {
> + release_idr(&cpufreq_idr, cpufreq_dev->id);
> + kfree(cpufreq_dev);
> + return ERR_PTR(-EINVAL);
> + }
> + cpufreq_dev->id = id;
> + cpufreq_dev->cool_dev = cool_dev;
> + cpufreq_dev->cpufreq_state = 0;
> + mutex_lock(&cooling_cpufreq_lock);
> + list_add_tail(&cpufreq_dev->node, &cooling_cpufreq_list);
> +
> + /* Register the notifier for first cpufreq cooling device */
> + if (cpufreq_dev_count == 0)
> + cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
> + CPUFREQ_POLICY_NOTIFIER);
> +
> + mutex_unlock(&cooling_cpufreq_lock);
> + return cool_dev;
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_register);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
> +{
> + struct cpufreq_cooling_device *cpufreq_dev = NULL;
> + unsigned int cpufreq_dev_count = 0;
> +
> + mutex_lock(&cooling_cpufreq_lock);
> + list_for_each_entry(cpufreq_dev, &cooling_cpufreq_list, node) {
> + if (cpufreq_dev && cpufreq_dev->cool_dev == cdev)
> + break;
> + cpufreq_dev_count++;
> + }
> +
> + if (!cpufreq_dev || cpufreq_dev->cool_dev != cdev) {
> + mutex_unlock(&cooling_cpufreq_lock);
> + return;
> + }
> +
> + list_del(&cpufreq_dev->node);
> +
> + /* Unregister the notifier for the last cpufreq cooling device */
> + if (cpufreq_dev_count == 1) {
> + cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
> + CPUFREQ_POLICY_NOTIFIER);
> + }
> + mutex_unlock(&cooling_cpufreq_lock);
> + thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
> + release_idr(&cpufreq_idr, cpufreq_dev->id);
> + if (cpufreq_dev_count == 1)
> + mutex_destroy(&cooling_cpufreq_lock);
> + kfree(cpufreq_dev);
> +}
> +EXPORT_SYMBOL(cpufreq_cooling_unregister);
> diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
> new file mode 100644
> index 0000000..8515301
> --- /dev/null
> +++ b/include/linux/cpu_cooling.h
> @@ -0,0 +1,58 @@
> +/*
> + * linux/include/linux/cpu_cooling.h
> + *
> + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
> + * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, write to the Free Software Foundation, Inc.,
> + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +
> +#ifndef __CPU_COOLING_H__
> +#define __CPU_COOLING_H__
> +
> +#include <linux/thermal.h>
> +
> +#define CPUFREQ_COOLING_START 0
> +#define CPUFREQ_COOLING_STOP 1
> +
> +#ifdef CONFIG_CPU_THERMAL
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus);
> +
> +/**
> + * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
> + * @cdev: thermal cooling device pointer.
> + */
> +void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
> +#else /* !CONFIG_CPU_THERMAL */
> +static inline struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus)
> +{
> + return NULL;
> +}
> +static inline void cpufreq_cooling_unregister(
> + struct thermal_cooling_device *cdev)
> +{
> + return;
> +}
> +#endif /* CONFIG_CPU_THERMAL */
> +
> +#endif /* __CPU_COOLING_H__ */
> --
> 1.7.7.5
>
>
^ permalink raw reply
* [PATCH] PM QoS: Use spinlock in the per-device PM QoS constraints code
From: Jean Pihet @ 2012-09-19 9:18 UTC (permalink / raw)
To: rjw, lenb, linux-pm; +Cc: Jean Pihet
The per-device PM QoS locking requires a spinlock to be used. The reasons
are:
- an alignement with the PM QoS core code, which is used by the per-device
PM QoS code for the constraints lists management. The PM QoS core code
uses spinlocks to protect the constraints lists,
- some drivers need to use the per-device PM QoS functionality from
interrupt context or spinlock protected context.
An example of such a driver is the OMAP HSI (high-speed synchronous serial
interface) driver which needs to control the IP block idle state
depending on the FIFO empty state, from interrupt context.
Reported-by: Djamil Elaidi <d-elaidi@ti.com>
Signed-off-by: Jean Pihet <j-pihet@ti.com>
---
drivers/base/power/qos.c | 67 ++++++++++++++++++++++++++++------------------
1 files changed, 41 insertions(+), 26 deletions(-)
diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c
index 74a67e0..968a771 100644
--- a/drivers/base/power/qos.c
+++ b/drivers/base/power/qos.c
@@ -24,26 +24,32 @@
* . a system-wide notification callback using the dev_pm_qos_*_global_notifier
* API. The notification chain data is stored in a static variable.
*
- * Note about the per-device constraint data struct allocation:
- * . The per-device constraints data struct ptr is tored into the device
+ * Notes about the per-device constraint data struct allocation:
+ * . The per-device constraints data struct ptr is stored into the device
* dev_pm_info.
* . To minimize the data usage by the per-device constraints, the data struct
- * is only allocated at the first call to dev_pm_qos_add_request.
+ * is only allocated at the first call to dev_pm_qos_add_request.
* . The data is later free'd when the device is removed from the system.
- * . A global mutex protects the constraints users from the data being
- * allocated and free'd.
+ *
+ * Notes about locking:
+ * . The dev->power.lock lock protects the constraints list
+ * (dev->power.constraints) allocation and free, as triggered by the
+ * driver core code at device insertion and removal,
+ * . A global lock dev_pm_qos_lock protects the constraints list entries
+ * from any modification and the notifiers registration and unregistration.
+ * . For both locks a spinlock is needed since this code can be called from
+ * interrupt context or spinlock protected context.
*/
#include <linux/pm_qos.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/device.h>
-#include <linux/mutex.h>
#include <linux/export.h>
#include "power.h"
-static DEFINE_MUTEX(dev_pm_qos_mtx);
+static DEFINE_SPINLOCK(dev_pm_qos_lock);
static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);
@@ -110,18 +116,19 @@ static int apply_constraint(struct dev_pm_qos_request *req,
* @dev: device to allocate data for
*
* Called at the first call to add_request, for constraint data allocation
- * Must be called with the dev_pm_qos_mtx mutex held
+ * Must be called with the dev_pm_qos_lock lock held
*/
static int dev_pm_qos_constraints_allocate(struct device *dev)
{
struct pm_qos_constraints *c;
struct blocking_notifier_head *n;
+ unsigned long flags;
- c = kzalloc(sizeof(*c), GFP_KERNEL);
+ c = kzalloc(sizeof(*c), GFP_ATOMIC);
if (!c)
return -ENOMEM;
- n = kzalloc(sizeof(*n), GFP_KERNEL);
+ n = kzalloc(sizeof(*n), GFP_ATOMIC);
if (!n) {
kfree(c);
return -ENOMEM;
@@ -134,9 +141,9 @@ static int dev_pm_qos_constraints_allocate(struct device *dev)
c->type = PM_QOS_MIN;
c->notifiers = n;
- spin_lock_irq(&dev->power.lock);
+ spin_lock_irqsave(&dev->power.lock, flags);
dev->power.constraints = c;
- spin_unlock_irq(&dev->power.lock);
+ spin_unlock_irqrestore(&dev->power.lock, flags);
return 0;
}
@@ -150,10 +157,12 @@ static int dev_pm_qos_constraints_allocate(struct device *dev)
*/
void dev_pm_qos_constraints_init(struct device *dev)
{
- mutex_lock(&dev_pm_qos_mtx);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev_pm_qos_lock, flags);
dev->power.constraints = NULL;
dev->power.power_state = PMSG_ON;
- mutex_unlock(&dev_pm_qos_mtx);
+ spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
}
/**
@@ -166,6 +175,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
{
struct dev_pm_qos_request *req, *tmp;
struct pm_qos_constraints *c;
+ unsigned long flags;
/*
* If the device's PM QoS resume latency limit has been exposed to user
@@ -173,7 +183,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
*/
dev_pm_qos_hide_latency_limit(dev);
- mutex_lock(&dev_pm_qos_mtx);
+ spin_lock_irqsave(&dev_pm_qos_lock, flags);
dev->power.power_state = PMSG_INVALID;
c = dev->power.constraints;
@@ -198,7 +208,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
kfree(c);
out:
- mutex_unlock(&dev_pm_qos_mtx);
+ spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
}
/**
@@ -223,6 +233,7 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
s32 value)
{
int ret = 0;
+ unsigned long flags;
if (!dev || !req) /*guard against callers passing in null */
return -EINVAL;
@@ -233,7 +244,7 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
req->dev = dev;
- mutex_lock(&dev_pm_qos_mtx);
+ spin_lock_irqsave(&dev_pm_qos_lock, flags);
if (!dev->power.constraints) {
if (dev->power.power_state.event == PM_EVENT_INVALID) {
@@ -255,7 +266,7 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
ret = apply_constraint(req, PM_QOS_ADD_REQ, value);
out:
- mutex_unlock(&dev_pm_qos_mtx);
+ spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
return ret;
}
@@ -280,6 +291,7 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
s32 new_value)
{
int ret = 0;
+ unsigned long flags;
if (!req) /*guard against callers passing in null */
return -EINVAL;
@@ -288,7 +300,7 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
"%s() called for unknown object\n", __func__))
return -EINVAL;
- mutex_lock(&dev_pm_qos_mtx);
+ spin_lock_irqsave(&dev_pm_qos_lock, flags);
if (req->dev->power.constraints) {
if (new_value != req->node.prio)
@@ -299,7 +311,7 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
ret = -ENODEV;
}
- mutex_unlock(&dev_pm_qos_mtx);
+ spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
@@ -319,6 +331,7 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
{
int ret = 0;
+ unsigned long flags;
if (!req) /*guard against callers passing in null */
return -EINVAL;
@@ -327,7 +340,7 @@ int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
"%s() called for unknown object\n", __func__))
return -EINVAL;
- mutex_lock(&dev_pm_qos_mtx);
+ spin_lock_irqsave(&dev_pm_qos_lock, flags);
if (req->dev->power.constraints) {
ret = apply_constraint(req, PM_QOS_REMOVE_REQ,
@@ -338,7 +351,7 @@ int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
ret = -ENODEV;
}
- mutex_unlock(&dev_pm_qos_mtx);
+ spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request);
@@ -359,8 +372,9 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request);
int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier)
{
int ret = 0;
+ unsigned long flags;
- mutex_lock(&dev_pm_qos_mtx);
+ spin_lock_irqsave(&dev_pm_qos_lock, flags);
if (!dev->power.constraints)
ret = dev->power.power_state.event != PM_EVENT_INVALID ?
@@ -370,7 +384,7 @@ int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier)
ret = blocking_notifier_chain_register(
dev->power.constraints->notifiers, notifier);
- mutex_unlock(&dev_pm_qos_mtx);
+ spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier);
@@ -389,8 +403,9 @@ int dev_pm_qos_remove_notifier(struct device *dev,
struct notifier_block *notifier)
{
int retval = 0;
+ unsigned long flags;
- mutex_lock(&dev_pm_qos_mtx);
+ spin_lock_irqsave(&dev_pm_qos_lock, flags);
/* Silently return if the constraints object is not present. */
if (dev->power.constraints)
@@ -398,7 +413,7 @@ int dev_pm_qos_remove_notifier(struct device *dev,
dev->power.constraints->notifiers,
notifier);
- mutex_unlock(&dev_pm_qos_mtx);
+ spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
return retval;
}
EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier);
--
1.7.7.6
^ permalink raw reply related
* Re: [PATCH] PM QoS: Use spinlock in the per-device PM QoS constraints code
From: Rafael J. Wysocki @ 2012-09-19 12:31 UTC (permalink / raw)
To: Jean Pihet; +Cc: linux-pm, Jean Pihet
In-Reply-To: <1348046283-2495-1-git-send-email-j-pihet@ti.com>
On Wednesday, September 19, 2012, Jean Pihet wrote:
> The per-device PM QoS locking requires a spinlock to be used. The reasons
> are:
> - an alignement with the PM QoS core code, which is used by the per-device
> PM QoS code for the constraints lists management. The PM QoS core code
> uses spinlocks to protect the constraints lists,
> - some drivers need to use the per-device PM QoS functionality from
> interrupt context or spinlock protected context.
> An example of such a driver is the OMAP HSI (high-speed synchronous serial
> interface) driver which needs to control the IP block idle state
> depending on the FIFO empty state, from interrupt context.
>
> Reported-by: Djamil Elaidi <d-elaidi@ti.com>
> Signed-off-by: Jean Pihet <j-pihet@ti.com>
Applied to the linux-next branch of the linux-pm.git tree as v3.7 material.
Next time please send CCs to linux-pm@vger.kernel.org instead of the
Linux Foundation list (of the same name), which has been abandoned.
Thanks,
Rafael
> ---
> drivers/base/power/qos.c | 67 ++++++++++++++++++++++++++++------------------
> 1 files changed, 41 insertions(+), 26 deletions(-)
>
> diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c
> index 74a67e0..968a771 100644
> --- a/drivers/base/power/qos.c
> +++ b/drivers/base/power/qos.c
> @@ -24,26 +24,32 @@
> * . a system-wide notification callback using the dev_pm_qos_*_global_notifier
> * API. The notification chain data is stored in a static variable.
> *
> - * Note about the per-device constraint data struct allocation:
> - * . The per-device constraints data struct ptr is tored into the device
> + * Notes about the per-device constraint data struct allocation:
> + * . The per-device constraints data struct ptr is stored into the device
> * dev_pm_info.
> * . To minimize the data usage by the per-device constraints, the data struct
> - * is only allocated at the first call to dev_pm_qos_add_request.
> + * is only allocated at the first call to dev_pm_qos_add_request.
> * . The data is later free'd when the device is removed from the system.
> - * . A global mutex protects the constraints users from the data being
> - * allocated and free'd.
> + *
> + * Notes about locking:
> + * . The dev->power.lock lock protects the constraints list
> + * (dev->power.constraints) allocation and free, as triggered by the
> + * driver core code at device insertion and removal,
> + * . A global lock dev_pm_qos_lock protects the constraints list entries
> + * from any modification and the notifiers registration and unregistration.
> + * . For both locks a spinlock is needed since this code can be called from
> + * interrupt context or spinlock protected context.
> */
>
> #include <linux/pm_qos.h>
> #include <linux/spinlock.h>
> #include <linux/slab.h>
> #include <linux/device.h>
> -#include <linux/mutex.h>
> #include <linux/export.h>
>
> #include "power.h"
>
> -static DEFINE_MUTEX(dev_pm_qos_mtx);
> +static DEFINE_SPINLOCK(dev_pm_qos_lock);
>
> static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);
>
> @@ -110,18 +116,19 @@ static int apply_constraint(struct dev_pm_qos_request *req,
> * @dev: device to allocate data for
> *
> * Called at the first call to add_request, for constraint data allocation
> - * Must be called with the dev_pm_qos_mtx mutex held
> + * Must be called with the dev_pm_qos_lock lock held
> */
> static int dev_pm_qos_constraints_allocate(struct device *dev)
> {
> struct pm_qos_constraints *c;
> struct blocking_notifier_head *n;
> + unsigned long flags;
>
> - c = kzalloc(sizeof(*c), GFP_KERNEL);
> + c = kzalloc(sizeof(*c), GFP_ATOMIC);
> if (!c)
> return -ENOMEM;
>
> - n = kzalloc(sizeof(*n), GFP_KERNEL);
> + n = kzalloc(sizeof(*n), GFP_ATOMIC);
> if (!n) {
> kfree(c);
> return -ENOMEM;
> @@ -134,9 +141,9 @@ static int dev_pm_qos_constraints_allocate(struct device *dev)
> c->type = PM_QOS_MIN;
> c->notifiers = n;
>
> - spin_lock_irq(&dev->power.lock);
> + spin_lock_irqsave(&dev->power.lock, flags);
> dev->power.constraints = c;
> - spin_unlock_irq(&dev->power.lock);
> + spin_unlock_irqrestore(&dev->power.lock, flags);
>
> return 0;
> }
> @@ -150,10 +157,12 @@ static int dev_pm_qos_constraints_allocate(struct device *dev)
> */
> void dev_pm_qos_constraints_init(struct device *dev)
> {
> - mutex_lock(&dev_pm_qos_mtx);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&dev_pm_qos_lock, flags);
> dev->power.constraints = NULL;
> dev->power.power_state = PMSG_ON;
> - mutex_unlock(&dev_pm_qos_mtx);
> + spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
> }
>
> /**
> @@ -166,6 +175,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
> {
> struct dev_pm_qos_request *req, *tmp;
> struct pm_qos_constraints *c;
> + unsigned long flags;
>
> /*
> * If the device's PM QoS resume latency limit has been exposed to user
> @@ -173,7 +183,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
> */
> dev_pm_qos_hide_latency_limit(dev);
>
> - mutex_lock(&dev_pm_qos_mtx);
> + spin_lock_irqsave(&dev_pm_qos_lock, flags);
>
> dev->power.power_state = PMSG_INVALID;
> c = dev->power.constraints;
> @@ -198,7 +208,7 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
> kfree(c);
>
> out:
> - mutex_unlock(&dev_pm_qos_mtx);
> + spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
> }
>
> /**
> @@ -223,6 +233,7 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
> s32 value)
> {
> int ret = 0;
> + unsigned long flags;
>
> if (!dev || !req) /*guard against callers passing in null */
> return -EINVAL;
> @@ -233,7 +244,7 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
>
> req->dev = dev;
>
> - mutex_lock(&dev_pm_qos_mtx);
> + spin_lock_irqsave(&dev_pm_qos_lock, flags);
>
> if (!dev->power.constraints) {
> if (dev->power.power_state.event == PM_EVENT_INVALID) {
> @@ -255,7 +266,7 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
> ret = apply_constraint(req, PM_QOS_ADD_REQ, value);
>
> out:
> - mutex_unlock(&dev_pm_qos_mtx);
> + spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
>
> return ret;
> }
> @@ -280,6 +291,7 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
> s32 new_value)
> {
> int ret = 0;
> + unsigned long flags;
>
> if (!req) /*guard against callers passing in null */
> return -EINVAL;
> @@ -288,7 +300,7 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
> "%s() called for unknown object\n", __func__))
> return -EINVAL;
>
> - mutex_lock(&dev_pm_qos_mtx);
> + spin_lock_irqsave(&dev_pm_qos_lock, flags);
>
> if (req->dev->power.constraints) {
> if (new_value != req->node.prio)
> @@ -299,7 +311,7 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
> ret = -ENODEV;
> }
>
> - mutex_unlock(&dev_pm_qos_mtx);
> + spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
> return ret;
> }
> EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
> @@ -319,6 +331,7 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
> int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
> {
> int ret = 0;
> + unsigned long flags;
>
> if (!req) /*guard against callers passing in null */
> return -EINVAL;
> @@ -327,7 +340,7 @@ int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
> "%s() called for unknown object\n", __func__))
> return -EINVAL;
>
> - mutex_lock(&dev_pm_qos_mtx);
> + spin_lock_irqsave(&dev_pm_qos_lock, flags);
>
> if (req->dev->power.constraints) {
> ret = apply_constraint(req, PM_QOS_REMOVE_REQ,
> @@ -338,7 +351,7 @@ int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
> ret = -ENODEV;
> }
>
> - mutex_unlock(&dev_pm_qos_mtx);
> + spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
> return ret;
> }
> EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request);
> @@ -359,8 +372,9 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request);
> int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier)
> {
> int ret = 0;
> + unsigned long flags;
>
> - mutex_lock(&dev_pm_qos_mtx);
> + spin_lock_irqsave(&dev_pm_qos_lock, flags);
>
> if (!dev->power.constraints)
> ret = dev->power.power_state.event != PM_EVENT_INVALID ?
> @@ -370,7 +384,7 @@ int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier)
> ret = blocking_notifier_chain_register(
> dev->power.constraints->notifiers, notifier);
>
> - mutex_unlock(&dev_pm_qos_mtx);
> + spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
> return ret;
> }
> EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier);
> @@ -389,8 +403,9 @@ int dev_pm_qos_remove_notifier(struct device *dev,
> struct notifier_block *notifier)
> {
> int retval = 0;
> + unsigned long flags;
>
> - mutex_lock(&dev_pm_qos_mtx);
> + spin_lock_irqsave(&dev_pm_qos_lock, flags);
>
> /* Silently return if the constraints object is not present. */
> if (dev->power.constraints)
> @@ -398,7 +413,7 @@ int dev_pm_qos_remove_notifier(struct device *dev,
> dev->power.constraints->notifiers,
> notifier);
>
> - mutex_unlock(&dev_pm_qos_mtx);
> + spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
> return retval;
> }
> EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier);
>
^ permalink raw reply
* [PATCH] PM / Runtime: let rpm_resume() succeed if RPM_ACTIVE, even when disabled
From: Kevin Hilman @ 2012-09-19 23:39 UTC (permalink / raw)
To: Rafael J. Wysocki, linux-pm
Cc: linux-omap, Santosh Shilimkar, Grygorii Strashko, Nishanth Menon
From: Kevin Hilman <khilman@ti.com>
When runtime PM is disabled, what we want is for callbacks not to be
called from then on. However, currently, when runtime PM is disabled,
operations such as 'get' will also fail even if the device is
currently active.
Since calling 'get' on a device that is already RPM_ACTIVE does not
involve calling the callbacks, it should be allowed to succeed, even
if runtime PM is disabled.
This is particularily useful in runtime PM enabled drivers that are
used during system suspend. Because runtime PM is disabled during
system suspend, currently any driver's use of pm_runtime_get* will
fail with -EACCES. This is expected if the device was already runtime
suspended, but if the device is actually active (due to recent usage,
autosuspend timeout not expired, or pm_runtime_resume() called in
->suspend() method), the pm_runtime_get*() call should actually
succeed.
To permit the usage described above, add a check to rpm_resume() so
that success is returned in the case where a driver is suspended (it's
->suspend callback has been called) but is still RPM_ACTIVE.
This patch was developed in close collaboration with Rafael J. Wysocki
<rjw@sisk.pl>
Tested on AM3730/Beagle-xM where wakeup IRQ firing during the late
suspend phase triggers runtime PM activity in the I2C driver since the
wakeup IRQ is on an I2C-connected PMIC.
Cc: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Kevin Hilman <khilman@ti.com>
---
drivers/base/power/runtime.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index 7d9c1cb..dafa5ec 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -510,7 +510,8 @@ static int rpm_resume(struct device *dev, int rpmflags)
if (dev->power.runtime_error)
retval = -EINVAL;
else if (dev->power.disable_depth > 0)
- retval = -EACCES;
+ retval = dev->power.is_suspended &&
+ dev->power.runtime_status == RPM_ACTIVE ? 1 : -EACCES;
if (retval)
goto out;
--
1.7.9.2
^ permalink raw reply related
* Re: [PATCH] PM / Runtime: let rpm_resume() succeed if RPM_ACTIVE, even when disabled
From: Rafael J. Wysocki @ 2012-09-20 19:09 UTC (permalink / raw)
To: Kevin Hilman; +Cc: Grygorii Strashko, linux-pm, linux-omap
In-Reply-To: <1348097990-5956-1-git-send-email-khilman@deeprootsystems.com>
Hi,
On Thursday, September 20, 2012, Kevin Hilman wrote:
> From: Kevin Hilman <khilman@ti.com>
>
> When runtime PM is disabled, what we want is for callbacks not to be
> called from then on. However, currently, when runtime PM is disabled,
> operations such as 'get' will also fail even if the device is
> currently active.
>
> Since calling 'get' on a device that is already RPM_ACTIVE does not
> involve calling the callbacks, it should be allowed to succeed, even
> if runtime PM is disabled.
>
> This is particularily useful in runtime PM enabled drivers that are
> used during system suspend. Because runtime PM is disabled during
> system suspend, currently any driver's use of pm_runtime_get* will
> fail with -EACCES. This is expected if the device was already runtime
> suspended, but if the device is actually active (due to recent usage,
> autosuspend timeout not expired, or pm_runtime_resume() called in
> ->suspend() method), the pm_runtime_get*() call should actually
> succeed.
>
> To permit the usage described above, add a check to rpm_resume() so
> that success is returned in the case where a driver is suspended (it's
> ->suspend callback has been called) but is still RPM_ACTIVE.
>
> This patch was developed in close collaboration with Rafael J. Wysocki
> <rjw@sisk.pl>
>
> Tested on AM3730/Beagle-xM where wakeup IRQ firing during the late
> suspend phase triggers runtime PM activity in the I2C driver since the
> wakeup IRQ is on an I2C-connected PMIC.
Please resend it with a CC to linux-pm@vger.kernel.org. Nobody reads
linux-pm@lists.linux-foundation.org today, I suppose ...
Thanks,
Rafael
> Cc: Rafael J. Wysocki <rjw@sisk.pl>
> Signed-off-by: Kevin Hilman <khilman@ti.com>
> ---
> drivers/base/power/runtime.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
> index 7d9c1cb..dafa5ec 100644
> --- a/drivers/base/power/runtime.c
> +++ b/drivers/base/power/runtime.c
> @@ -510,7 +510,8 @@ static int rpm_resume(struct device *dev, int rpmflags)
> if (dev->power.runtime_error)
> retval = -EINVAL;
> else if (dev->power.disable_depth > 0)
> - retval = -EACCES;
> + retval = dev->power.is_suspended &&
> + dev->power.runtime_status == RPM_ACTIVE ? 1 : -EACCES;
> if (retval)
> goto out;
>
>
^ permalink raw reply
* Subject: [PATCH] drivers-core: move device_pm_remove behind bus_remove_device
From: Zhang, LongX @ 2012-09-21 1:58 UTC (permalink / raw)
To: linux-kernel@vger.kernel.org, linux-pm@lists.linux-foundation.org
Cc: rjw@sisk.pl, yanmin_zhang@linux.intel.com
From: LongX Zhang <longx.zhang@intel.com>
device_pm_remove will call pm_runtime_remove which would disable
runtime PM of the device. After that pm_runtime_get* or
pm_runtime_put* will be ingored. So if we disable the runtime PM
before device really be removed, drivers' _remove callback may
access HW even pm_runtime_get* fails. That is bad.
Signed-off-by: LongX Zhang <longx.zhang@intel.com>
---
drivers/base/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 5e6e00b..81ea7f2 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -1169,7 +1169,6 @@ void device_del(struct device *dev)
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DEL_DEVICE, dev);
- device_pm_remove(dev);
dpm_sysfs_remove(dev);
if (parent)
klist_del(&dev->p->knode_parent);
@@ -1194,6 +1193,7 @@ void device_del(struct device *dev)
device_remove_file(dev, &uevent_attr);
device_remove_attrs(dev);
bus_remove_device(dev);
+ device_pm_remove(dev);
driver_deferred_probe_del(dev);
/*
--
1.7.10
^ permalink raw reply related
* Re: [PATCH v6 1/6] thermal: add generic cpufreq cooling implementation
From: jonghwa3.lee @ 2012-09-21 2:23 UTC (permalink / raw)
To: Amit Daniel Kachhap
Cc: linux-pm, Andrew Morton, Zhang Rui, Len Brown, linux-samsung-soc,
linux-kernel, linux-acpi, lm-sensors
In-Reply-To: <0MAO00DCVFKI4NS0@ms2.samsung.com>
Hi, Amit,
On 2012년 08월 16일 20:41, Amit Daniel Kachhap wrote:
> diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
> new file mode 100644
> index 0000000..66cbd52
> --- /dev/null
> +++ b/drivers/thermal/cpu_cooling.c
> @@ -0,0 +1,586 @@
> +/**
> + * cpufreq_cooling_register - function to create cpufreq cooling device.
> + * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
> + */
> +struct thermal_cooling_device *cpufreq_cooling_register(
> + struct cpumask *clip_cpus)
> +{
> + struct thermal_cooling_device *cool_dev;
> + struct cpufreq_cooling_device *cpufreq_dev = NULL;
> + unsigned int cpufreq_dev_count = 0, min = 0, max = 0;
> + char dev_name[THERMAL_NAME_LENGTH];
> + int ret = 0, id = 0, i;
> + struct cpufreq_policy policy;
> + ret = get_idr(&cpufreq_idr,&cpufreq_dev->id);
> + if (ret) {
> + kfree(cpufreq_dev);
> + return ERR_PTR(-EINVAL);
> + }
> +
> + sprintf(dev_name, "thermal-cpufreq-%d", cpufreq_dev->id);
> +
> + cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev,
> +&cpufreq_cooling_ops);
> + if (!cool_dev) {
> + release_idr(&cpufreq_idr, cpufreq_dev->id);
> + kfree(cpufreq_dev);
> + return ERR_PTR(-EINVAL);
> + }
> + cpufreq_dev->id = id;
Why is this needed? Should every cpufreq_dev instance's id be same for
zero? It looks odd. And it also has problem which can be occurred when
it release id during unregistering cpufreq_dev. I think it should keep
the idr value taken before.
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [RFC PATCH 0/6] ACPI: ACPI 5.0 device enumeration proposal
From: Zhang Rui @ 2012-09-28 7:37 UTC (permalink / raw)
To: LKML
Cc: Grant Likely, linux-acpi@vger.kernel.org, linux-i2c,
Dirk Brandewie, linux-pm
Hi, all,
I'm working on ACPI device enumeration support recently, and here is the
proposal I made to enumerate devices via ACPI namespace.
the main idea is that, for Serial Buses like I2C and SPI, we enumerate
the controller as a platform device, and then enumerate the slaves via
i2c/spi_register_board_info. And then, when the controller is really
probed and enabled in the platform driver, the SPI/I2C bus code will
enumerate I2C/SPI slaves automatically.
And for the other devices, we will enumerate all of them as platform
devices, which is not covered in this patch set yet.
Patch 1 & 2 Introduce ACPI style device match method in platform_match.
this is because an ACPI device may have multiple hardware ID (_HID) and
Compatible IDs, aka, _CID, but in platform device code, only pdev->name
is used to match the driver id_table currently.
Patch 3 introduces a new API acpi_get_generic_resources().
this API is used to convert ACPI style resources to the generic struct
resource.
Patch 4 changes i2c_register_board_info from __init to __devinit.
This is needed because I want to enumerate the slave devices in ACPI I2C
control Driver, which supports hotplug in theory.
Patch 5 introduces the ACPI I2C controller enumeration drive.
it enumerate the ACPI I2C controller to a platform device and then
enumerate the I2C slaves as I2C_board_info.
Patch 6 is an example driver for an ACPI enuemrated I2C controller.
Note that this is just prototype patch set, which just passes build
test. Because I'd like to get your ideas about this before going on.
any comments are welcome.
thanks,
rui
^ permalink raw reply
* [RFC PATCH 1/6] Introduce acpi_match_device_id().
From: Zhang Rui @ 2012-09-28 7:38 UTC (permalink / raw)
To: LKML
Cc: Grant Likely, linux-acpi@vger.kernel.org, linux-i2c,
Dirk Brandewie, linux-pm
>From 72df5d1f51fb27a4ba7f70a3b07df759d32b8288 Mon Sep 17 00:00:00 2001
From: Zhang Rui <rui.zhang@intel.com>
Date: Thu, 27 Sep 2012 15:11:55 +0800
Subject: [RFC PATCH 1/6] Introduce acpi_match_device_id().
This API is used to check if a device id string is compatible
with an ACPI device,
either PNP id exported via _HID or compatible ids exported
via _CID control method.
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/acpi/scan.c | 22 ++++++++++++++++++++++
include/acpi/acpi_bus.h | 6 ++++++
2 files changed, 28 insertions(+), 0 deletions(-)
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index d1ecca2..936a7c9 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -312,6 +312,28 @@ int acpi_match_device_ids(struct acpi_device *device,
}
EXPORT_SYMBOL(acpi_match_device_ids);
+int acpi_match_device_id(const struct device *dev, const char *id)
+{
+ acpi_handle handle = DEVICE_ACPI_HANDLE(dev);
+ struct acpi_device *device;
+ struct acpi_hardware_id *hwid;
+ acpi_status status;
+
+ if (!handle || !id)
+ return -ENODEV;
+
+ status = acpi_bus_get_device(handle, &device);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ list_for_each_entry(hwid, &device->pnp.ids, list)
+ if (!strcmp(id, hwid->id))
+ return 0;
+
+ return -ENODEV;
+}
+EXPORT_SYMBOL(acpi_match_device_id);
+
static void acpi_free_ids(struct acpi_device *device)
{
struct acpi_hardware_id *id, *tmp;
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index bde976e..8b5b124 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -378,6 +378,7 @@ int acpi_bus_start(struct acpi_device *device);
acpi_status acpi_bus_get_ejd(acpi_handle handle, acpi_handle * ejd);
int acpi_match_device_ids(struct acpi_device *device,
const struct acpi_device_id *ids);
+int acpi_match_device_id(const struct device *, const char *);
int acpi_create_dir(struct acpi_device *);
void acpi_remove_dir(struct acpi_device *);
@@ -448,6 +449,11 @@ static inline int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
static inline int register_acpi_bus_type(void *bus) { return 0; }
static inline int unregister_acpi_bus_type(void *bus) { return 0; }
+static inline int acpi_match_device_id(const struct device *device,
+ const char *name)
+{
+ return -ENODEV;
+}
#endif /* CONFIG_ACPI */
--
1.7.7.6
^ permalink raw reply related
* [RFC PATCH 2/6] Introduce ACPI style match in platform_match
From: Zhang Rui @ 2012-09-28 7:39 UTC (permalink / raw)
To: LKML
Cc: linux-pm, linux-i2c,
linux-acpi-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Len, Brown,
Rafael J. Wysocki, Grant Likely, Dirk Brandewie, Zhang, Rui
>From 5d7ecd12c2994b8c5905d52718c2870c3b62746e Mon Sep 17 00:00:00 2001
From: Zhang Rui <rui.zhang-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
Date: Fri, 28 Sep 2012 14:51:03 +0800
Subject: [RFC PATCH 2/6] Introduce ACPI style match in platform_match
Signed-off-by: Zhang Rui <rui.zhang-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
---
drivers/base/platform.c | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index a1a7225..90e64c6f 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -20,6 +20,7 @@
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
+#include <linux/acpi.h>
#include "base.h"
@@ -635,6 +636,13 @@ static const struct platform_device_id *platform_match_id(
struct platform_device *pdev)
{
while (id->name[0]) {
+#ifdef CONFIG_ACPI
+ /* attempt ACPI style match */
+ if (acpi_match_device_id(&pdev->dev, id->name) == 0) {
+ pdev->id_entry = id;
+ return id;
+ }
+#endif
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
--
1.7.7.6
^ permalink raw reply related
* [RFC PATCH 3/6] ACPI: introduce acpi_get_generic_resources
From: Zhang Rui @ 2012-09-28 7:40 UTC (permalink / raw)
To: LKML
Cc: linux-pm, linux-i2c,
linux-acpi-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Len, Brown,
Rafael J. Wysocki, Grant Likely, Dirk Brandewie, Zhang, Rui
>From 9a851d177794129a89f720c7122cb39fd163126b Mon Sep 17 00:00:00 2001
From: Zhang Rui <rui.zhang-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
Date: Fri, 28 Sep 2012 08:34:05 +0800
Subject: [RFC PATCH 3/6] ACPI: introduce acpi_get_generic_resources
Introduce acpi_get_generic_resources() to convert
ACPI style resources to struct resource.
Signed-off-by: Zhang Rui <rui.zhang-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
---
drivers/acpi/Makefile | 1 +
drivers/acpi/resource.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++
include/acpi/acpi_bus.h | 1 +
3 files changed, 167 insertions(+), 0 deletions(-)
create mode 100644 drivers/acpi/resource.c
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 6b1d535..4b65608 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -46,6 +46,7 @@ acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o
ifdef CONFIG_ACPI_VIDEO
acpi-y += video_detect.o
endif
+acpi-y += resource.o
# These are (potentially) separate modules
obj-$(CONFIG_ACPI_AC) += ac.o
diff --git a/drivers/acpi/resource.c b/drivers/acpi/resource.c
new file mode 100644
index 0000000..30a5204
--- /dev/null
+++ b/drivers/acpi/resource.c
@@ -0,0 +1,165 @@
+/*
+ * resource.c -- convert ACPI resource to generic resource
+ *
+ * Copyright (c) 2012 Zhang Rui <rui.zhang-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/acpi.h>
+
+static int irq_flags(int triggering, int polarity, int sharable)
+{
+ int flags;
+
+ if (triggering == ACPI_LEVEL_SENSITIVE) {
+ if (polarity == ACPI_ACTIVE_LOW)
+ flags = IORESOURCE_IRQ_LOWLEVEL;
+ else
+ flags = IORESOURCE_IRQ_HIGHLEVEL;
+ } else {
+ if (polarity == ACPI_ACTIVE_LOW)
+ flags = IORESOURCE_IRQ_LOWEDGE;
+ else
+ flags = IORESOURCE_IRQ_HIGHEDGE;
+ }
+
+ if (sharable == ACPI_SHARED)
+ flags |= IORESOURCE_IRQ_SHAREABLE;
+
+ return flags;
+}
+
+static void acpi_get_irq_resource(struct acpi_resource *res,
+ struct resource *resource)
+{
+ struct acpi_resource_irq *irq = &res->data.irq;
+ int t, p;
+
+ if (irq->interrupt_count == 0)
+ resource->flags = IORESOURCE_DISABLED;
+
+ if (!acpi_get_override_irq(irq->interrupts[0], &t, &p)) {
+ t = t ? ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
+ p = p ? ACPI_ACTIVE_LOW : ACPI_ACTIVE_HIGH;
+
+ if (irq->triggering != t || irq->polarity != p) {
+ irq->triggering = t;
+ irq->polarity = p;
+ }
+ }
+
+ resource->flags =
+ irq_flags(irq->triggering, irq->polarity, irq->sharable);
+ resource->flags |= IORESOURCE_IRQ;
+ resource->start = irq->interrupts[0];
+ resource->end = irq->interrupts[0];
+}
+
+static void acpi_get_extended_irq_resource(struct acpi_resource *res,
+ struct resource *resource)
+{
+ struct acpi_resource_extended_irq *irq = &res->data.extended_irq;
+ int t, p;
+
+ if (irq->interrupt_count == 0)
+ resource->flags = IORESOURCE_DISABLED;
+
+ if (!acpi_get_override_irq(irq->interrupts[0], &t, &p)) {
+ t = t ? ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
+ p = p ? ACPI_ACTIVE_LOW : ACPI_ACTIVE_HIGH;
+
+ if (irq->triggering != t || irq->polarity != p) {
+ irq->triggering = t;
+ irq->polarity = p;
+ }
+ }
+
+ resource->flags =
+ irq_flags(irq->triggering, irq->polarity, irq->sharable);
+ resource->flags |= IORESOURCE_IRQ;
+ resource->start = irq->interrupts[0];
+ resource->end = irq->interrupts[0];
+}
+
+static void acpi_get_mem_resource(struct acpi_resource *res,
+ struct resource *resource)
+{
+ struct acpi_resource_fixed_memory32 *mem = &res->data.fixed_memory32;
+
+ if (mem->address_length == 0)
+ resource->flags |= IORESOURCE_DISABLED;
+ if (mem->write_protect == ACPI_READ_WRITE_MEMORY)
+ resource->flags |= IORESOURCE_MEM_WRITEABLE;
+
+ resource->flags |= IORESOURCE_MEM;
+ resource->start = mem->address;
+ resource->end = mem->address + mem->address_length - 1;
+}
+
+int acpi_get_generic_resources(struct acpi_device *device,
+ struct resource **resources)
+{
+ acpi_status status;
+ struct acpi_buffer buffer;
+ struct acpi_resource *res;
+ struct resource *p;
+ int res_count;
+ int i;
+
+ status = acpi_get_current_resources(device->handle, &buffer);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&device->dev, "can't get ACPI resources\n");
+ return -EINVAL;
+ }
+
+ res_count = (buffer.length - 1) / sizeof(struct acpi_resource) - 1;
+ p = kzalloc(sizeof(struct resource) * res_count, GFP_KERNEL);
+ if (!resources) {
+ kfree(buffer.pointer);
+ return -ENOMEM;
+ }
+
+ res = (struct acpi_resource *)buffer.pointer;
+ i = 0;
+
+ while (res->type != ACPI_RESOURCE_TYPE_END_TAG) {
+
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_IRQ:
+ acpi_get_irq_resource(res, &p[i]);
+ break;
+ case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
+ acpi_get_extended_irq_resource(res, &p[i]);
+ break;
+ case ACPI_RESOURCE_TYPE_MEMORY32:
+ acpi_get_mem_resource(res, &p[i]);
+ break;
+ default:
+ i--;
+ break;
+ }
+ i++;
+ res = ACPI_NEXT_RESOURCE(res);
+ }
+
+ /* Get rid of unsupported ACPI resources */
+ if (i != res_count)
+ p =
+ kmemdup(p, sizeof(struct resource) * i, GFP_KERNEL);
+
+ *resources = p;
+ kfree(buffer.pointer);
+ return i;
+}
+
+EXPORT_SYMBOL(acpi_get_generic_resources);
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 8b5b124..a4f8eaf 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -381,6 +381,7 @@ int acpi_match_device_ids(struct acpi_device *device,
int acpi_match_device_id(const struct device *, const char *);
int acpi_create_dir(struct acpi_device *);
void acpi_remove_dir(struct acpi_device *);
+int acpi_get_generic_resources(struct acpi_device *, struct resource **);
/*
* Bind physical devices with ACPI devices
--
1.7.7.6
^ permalink raw reply related
* [RFC PATCH 4/6] Change i2c_register_board_info from __init to __devinit
From: Zhang Rui @ 2012-09-28 7:40 UTC (permalink / raw)
To: LKML
Cc: Grant Likely, linux-acpi@vger.kernel.org, linux-i2c,
Dirk Brandewie, linux-pm
>From 34aa38e12c04544d89af2eae46de284dc8a03b8d Mon Sep 17 00:00:00 2001
From: Zhang Rui <rui.zhang@intel.com>
Date: Thu, 27 Sep 2012 15:42:23 +0800
Subject: [RFC PATCH 4/6] Change i2c_register_board_info from __init to
__devinit.
ACPI 5 supports enumerating I2C adapter and its slaves
via ACPI namespace, and this needs to be done at runtime,
after the ACPI I2C controller driver being loaded.
detailed usage of this API can be found in next patch.
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/i2c/i2c-boardinfo.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/i2c/i2c-boardinfo.c b/drivers/i2c/i2c-boardinfo.c
index f24cc64..1ecbbdc 100644
--- a/drivers/i2c/i2c-boardinfo.c
+++ b/drivers/i2c/i2c-boardinfo.c
@@ -61,7 +61,7 @@ EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num);
* The board info passed can safely be __initdata, but be careful of embedded
* pointers (for platform_data, functions, etc) since that won't be copied.
*/
-int __init
+int __devinit
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
--
1.7.7.6
^ permalink raw reply related
* [RFC PATCH 5/6] ACPI: Introduce ACPI I2C controller enumeration driver
From: Zhang Rui @ 2012-09-28 7:40 UTC (permalink / raw)
To: LKML
Cc: Grant Likely, linux-acpi@vger.kernel.org, linux-i2c,
Dirk Brandewie, linux-pm
>From 6077a62f2865201ab6727ca7d628ee5e43aa57e1 Mon Sep 17 00:00:00 2001
From: Zhang Rui <rui.zhang@intel.com>
Date: Fri, 24 Aug 2012 15:18:25 +0800
Subject: [RFC PATCH 5/6] ACPI: Introduce ACPI I2C controller enumeration
driver
This driver is able to
1) enumerate I2C controller via ACPI namespace
and register it as a platform device.
2) enumerate I2C slave devices via ACPI namespace.
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/acpi/Makefile | 1 +
drivers/acpi/i2c_root.c | 229 +++++++++++++++++++++++++++++++++++++++++++
drivers/acpi/sysfs.c | 1 +
include/acpi/acpi_drivers.h | 1 +
4 files changed, 232 insertions(+), 0 deletions(-)
create mode 100644 drivers/acpi/i2c_root.c
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 4b65608..5b14f05 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -35,6 +35,7 @@ acpi-y += scan.o
acpi-y += processor_core.o
acpi-y += ec.o
acpi-y += gpio.o
+acpi-y += i2c_root.o
acpi-$(CONFIG_ACPI_DOCK) += dock.o
acpi-y += pci_root.o pci_link.o pci_irq.o pci_bind.o
acpi-y += power.o
diff --git a/drivers/acpi/i2c_root.c b/drivers/acpi/i2c_root.c
new file mode 100644
index 0000000..b9a042b
--- /dev/null
+++ b/drivers/acpi/i2c_root.c
@@ -0,0 +1,229 @@
+/*
+ * i2c_root.c - ACPI I2C controller Driver ($Revision: 40 $)
+ *
+ * Copyright (C) 2012 Intel Corp
+ * Copyright (C) 2012 Zhang Rui <rui.zhang@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/apei.h>
+
+#define PREFIX "ACPI: "
+
+#define _COMPONENT ACPI_SPB_COMPONENT
+ACPI_MODULE_NAME("i2c_root");
+#define ACPI_I2C_ROOT_CLASS "i2c_root"
+#define ACPI_I2C_ROOT_DEVICE_NAME "I2C Controller"
+
+static int acpi_i2c_root_add(struct acpi_device *device);
+static int acpi_i2c_root_remove(struct acpi_device *device, int type);
+
+static const struct acpi_device_id root_device_ids[] = {
+ {"INT33B1", 0},
+ {"", 0},
+};
+
+MODULE_DEVICE_TABLE(acpi, root_device_ids);
+
+static struct acpi_driver acpi_i2c_root_driver = {
+ .name = "i2c_root",
+ .class = ACPI_I2C_ROOT_CLASS,
+ .ids = root_device_ids,
+ .ops = {
+ .add = acpi_i2c_root_add,
+ .remove = acpi_i2c_root_remove,
+ },
+};
+
+struct acpi_i2c_root {
+ struct acpi_device *device;
+ struct platform_device *pdev;
+ int busnum;
+ int slaves;
+ struct i2c_board_info *info;
+};
+
+static int add_slave(struct acpi_i2c_root *root, struct i2c_board_info *info)
+{
+ struct i2c_board_info *p;
+
+ if (!info)
+ return 0;
+
+ p = kzalloc(sizeof(*p) * (root->slaves + 1), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ memcpy(p, info, sizeof(*p));
+ if (root->info)
+ memcpy(p + 1, root->info, sizeof(*p) * root->slaves);
+
+ kfree(root->info);
+ root->info = p;
+ root->slaves++;
+ return 0;
+}
+
+static int register_slaves(struct acpi_i2c_root *root)
+{
+ return i2c_register_board_info(root->busnum, root->info, root->slaves);
+}
+
+/*
+ * The i2c info registering call back for each i2c slave device
+ */
+acpi_status __init i2c_enumerate_slave(acpi_handle handle, u32 level,
+ void *data, void **return_value)
+{
+ int result;
+ acpi_status status;
+ struct acpi_buffer buffer;
+ struct acpi_resource *resource;
+ struct acpi_resource_gpio *gpio;
+ struct acpi_resource_i2c_serialbus *i2c;
+ int i;
+ struct acpi_i2c_root *root = data;
+ struct i2c_board_info info;
+ struct acpi_device *device;
+
+ if (acpi_bus_get_device(handle, &device))
+ return AE_OK;
+
+ status = acpi_get_current_resources(handle, &buffer);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&device->dev, "Failed to get ACPI resources\n");
+ return AE_OK;
+ }
+
+ for (i = 0; i < buffer.length; i += sizeof(struct acpi_resource)) {
+ resource = (struct acpi_resource *)(buffer.pointer + i);
+
+ switch (resource->type) {
+ case ACPI_RESOURCE_TYPE_GPIO:
+ gpio = &resource->data.gpio;
+
+ if (gpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT) {
+ result =
+ acpi_device_get_gpio_irq
+ (gpio->resource_source.string_ptr,
+ gpio->pin_table[0], &info.irq);
+ if (result)
+ dev_err(&device->dev,
+ "Failed to get IRQ\n");
+ }
+ break;
+ case ACPI_RESOURCE_TYPE_SERIAL_BUS:
+ i2c = &resource->data.i2c_serial_bus;
+
+ info.addr = i2c->slave_address;
+ break;
+ default:
+ break;
+ }
+ }
+
+ add_slave(root, &info);
+
+ kfree(buffer.pointer);
+ return AE_OK;
+}
+
+static int __devinit acpi_i2c_root_add(struct acpi_device *device)
+{
+ acpi_status status;
+ struct acpi_i2c_root *root;
+ struct resource *resources;
+ int result;
+
+ if (!device->pnp.unique_id) {
+ dev_err(&device->dev,
+ "Unsupported ACPI I2C controller. No UID\n");
+ return -ENODEV;
+ }
+
+ root = kzalloc(sizeof(struct acpi_i2c_root), GFP_KERNEL);
+ if (!root)
+ return -ENOMEM;
+
+ root->device = device;
+
+ kstrtoint(device->pnp.unique_id, 10, &root->busnum);
+
+ /* enumerate I2C controller */
+ root->pdev =
+ platform_device_alloc(acpi_device_hid(device), root->busnum);
+ if (!root->pdev) {
+ dev_err(&device->dev, "Failed to alloc platform device\n");
+ goto err;
+ }
+
+ result = acpi_get_generic_resources(device, &resources);
+ if (result < 0) {
+ dev_err(&device->dev, "Failed to get resources\n");
+ goto err;
+ }
+
+ platform_device_add_resources(root->pdev, resources, result);
+ platform_device_add(root->pdev);
+
+ /* enumerate I2C slave devices */
+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, root->device->handle, 1,
+ i2c_enumerate_slave, NULL, root, NULL);
+
+ if (ACPI_FAILURE(status)) {
+ dev_err(&root->device->dev, "i2c ACPI namespace walk error!\n");
+ kfree(root);
+ return -ENODEV;
+ }
+
+ register_slaves(root);
+
+ return 0;
+err:
+ kfree(root);
+ return -ENODEV;
+}
+
+static int acpi_i2c_root_remove(struct acpi_device *device, int type)
+{
+ struct acpi_i2c_root *root = acpi_driver_data(device);
+
+ kfree(root->info);
+ kfree(root);
+
+ return 0;
+}
+
+int __init acpi_i2c_root_init(void)
+{
+ return acpi_bus_register_driver(&acpi_i2c_root_driver);
+}
diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c
index 240a244..0e0c83c 100644
--- a/drivers/acpi/sysfs.c
+++ b/drivers/acpi/sysfs.c
@@ -63,6 +63,7 @@ static const struct acpi_dlayer acpi_debug_layers[] = {
ACPI_DEBUG_INIT(ACPI_MEMORY_DEVICE_COMPONENT),
ACPI_DEBUG_INIT(ACPI_VIDEO_COMPONENT),
ACPI_DEBUG_INIT(ACPI_PROCESSOR_COMPONENT),
+ ACPI_DEBUG_INIT(ACPI_SPB_COMPONENT),
};
static const struct acpi_dlevel acpi_debug_levels[] = {
diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h
index b75a408..499376c 100644
--- a/include/acpi/acpi_drivers.h
+++ b/include/acpi/acpi_drivers.h
@@ -49,6 +49,7 @@
#define ACPI_MEMORY_DEVICE_COMPONENT 0x08000000
#define ACPI_VIDEO_COMPONENT 0x10000000
#define ACPI_PROCESSOR_COMPONENT 0x20000000
+#define ACPI_SPB_COMPONENT 0x40000000
/*
* _HID definitions
--
1.7.7.6
^ permalink raw reply related
* [RFC PATCH 6/6] Introduce INT33B1 I2C controller driver
From: Zhang Rui @ 2012-09-28 7:40 UTC (permalink / raw)
To: LKML
Cc: Grant Likely, linux-acpi@vger.kernel.org, linux-i2c,
Dirk Brandewie, linux-pm
>From 817d814ecae91862f42a0447f455dae7f74cba27 Mon Sep 17 00:00:00 2001
From: Zhang Rui <rui.zhang@intel.com>
Date: Fri, 24 Aug 2012 15:20:38 +0800
Subject: [RFC PATCH 6/6] Introduce INT33B1 I2C controller driver
This is a dummy platform device driver to illustrate my idea about
how a really I2C controller should work on ACPI 5 platforms.
It just probes the INT33B1 I2C controller which is enumerated by ACPI.
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/i2c/busses/Kconfig | 8 +++
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-33b1.c | 117 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 126 insertions(+), 0 deletions(-)
create mode 100644 drivers/i2c/busses/i2c-33b1.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index b4aaa1b..536a19c 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -257,6 +257,14 @@ config I2C_SCMI
To compile this driver as a module, choose M here:
the module will be called i2c-scmi.
+config I2C_33B1
+ tristate "INT33B1 I2C controller"
+ help
+ This driver supports the ACPI enumerated INT33B1 I2C controller.
+
+ To compile this driver as a module, choose M here:
+ the module will be called i2c-scmi.
+
endif # ACPI
comment "Mac SMBus host controller drivers"
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index ce3c2be..8e478bd 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -4,6 +4,7 @@
# ACPI drivers
obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o
+obj-$(CONFIG_I2C_33B1) += i2c-33b1.o
# PC SMBus host controller drivers
obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o
diff --git a/drivers/i2c/busses/i2c-33b1.c b/drivers/i2c/busses/i2c-33b1.c
new file mode 100644
index 0000000..152c3e4
--- /dev/null
+++ b/drivers/i2c/busses/i2c-33b1.c
@@ -0,0 +1,117 @@
+/*
+ * i2c_33b1.c -INT33B1 i2c Controller Driver
+ *
+ * Copyright (c) 2012 Intel Corp
+ * Copyright (c) 2012 Zhang Rui <rui.zhang@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+
+struct int33b1_i2c_private {
+ struct i2c_adapter adap;
+ void __iomem *iobase;
+};
+
+
+static int int33b1_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ return 0;
+}
+
+static u32 int33b1_func(struct i2c_adapter *adap)
+{
+ /* Emulate SMBUS over I2C */
+ return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm int33b1_i2c_algo = {
+ .master_xfer = int33b1_i2c_xfer,
+ .functionality = int33b1_func,
+};
+
+static int __devinit int33b1_i2c_probe(struct platform_device *pdev)
+{
+ struct int33b1_i2c_private *priv;
+ struct resource *res;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->iobase = devm_request_and_ioremap(&pdev->dev, res);
+ if (!priv->iobase) {
+ dev_err(&pdev->dev, "devm_request_and_ioremap failed\n");
+ return -EBUSY;
+ }
+
+ priv->adap.dev.parent = &pdev->dev;
+ priv->adap.owner = THIS_MODULE;
+ priv->adap.algo_data = priv;
+ priv->adap.algo = &int33b1_i2c_algo;
+ priv->adap.nr = pdev->id;
+ priv->adap.class = I2C_CLASS_HWMON;
+ snprintf(priv->adap.name, sizeof(priv->adap.name), "int33b1-i2c");
+
+ i2c_set_adapdata(&priv->adap, priv);
+ ret = i2c_add_numbered_adapter(&priv->adap);
+ if (ret < 0) {
+ dev_err(&priv->adap.dev, "Failed to add i2c bus.\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ dev_info(&priv->adap.dev, "Added I2C Bus.\n");
+ return 0;
+}
+
+static int __devexit int33b1_i2c_remove(struct platform_device *pdev)
+{
+ struct int33b1_i2c_private *priv;
+
+ priv = platform_get_drvdata(pdev);
+ i2c_del_adapter(&priv->adap);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver int33b1_i2c_driver = {
+ .probe = int33b1_i2c_probe,
+ .remove = __devexit_p(int33b1_i2c_remove),
+ .driver = {
+ .name = "int33b1-i2cbus",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(int33b1_i2c_driver);
+
+MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
+MODULE_DESCRIPTION("INT33B1 I2C Controller driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:INT33B1");
--
1.7.7.6
^ permalink raw reply related
* Re: [RFC PATCH 5/6] ACPI: Introduce ACPI I2C controller enumeration driver
From: Alan Cox @ 2012-09-28 12:54 UTC (permalink / raw)
To: Zhang Rui
Cc: Grant Likely, LKML, linux-acpi@vger.kernel.org, linux-i2c,
Dirk Brandewie, linux-pm
In-Reply-To: <1348818032.10877.325.camel@rui.sh.intel.com>
On Fri, 28 Sep 2012 15:40:32 +0800
Zhang Rui <rui.zhang@intel.com> wrote:
> >From 6077a62f2865201ab6727ca7d628ee5e43aa57e1 Mon Sep 17 00:00:00 2001
> From: Zhang Rui <rui.zhang@intel.com>
> Date: Fri, 24 Aug 2012 15:18:25 +0800
> Subject: [RFC PATCH 5/6] ACPI: Introduce ACPI I2C controller enumeration
> driver
>
> This driver is able to
> 1) enumerate I2C controller via ACPI namespace
> and register it as a platform device.
> 2) enumerate I2C slave devices via ACPI namespace.
Will this also trigger and work with the ACPI4 based devices that seem to
have I²C tables that Linux currently doesn't understand (eq some
GMA600/Oaktrail platforms) ?
^ permalink raw reply
* Re: [RFC PATCH 1/6] Introduce acpi_match_device_id().
From: Mika Westerberg @ 2012-09-28 14:13 UTC (permalink / raw)
To: Zhang Rui
Cc: LKML, linux-pm, linux-i2c, linux-acpi@vger.kernel.org, Len, Brown,
Rafael J. Wysocki, Grant Likely, Dirk Brandewie
In-Reply-To: <1348817910.10877.321.camel@rui.sh.intel.com>
On Fri, Sep 28, 2012 at 03:38:30PM +0800, Zhang Rui wrote:
> >From 72df5d1f51fb27a4ba7f70a3b07df759d32b8288 Mon Sep 17 00:00:00 2001
> From: Zhang Rui <rui.zhang@intel.com>
> Date: Thu, 27 Sep 2012 15:11:55 +0800
> Subject: [RFC PATCH 1/6] Introduce acpi_match_device_id().
>
> This API is used to check if a device id string is compatible
> with an ACPI device,
> either PNP id exported via _HID or compatible ids exported
> via _CID control method.
>
> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
> ---
> drivers/acpi/scan.c | 22 ++++++++++++++++++++++
> include/acpi/acpi_bus.h | 6 ++++++
> 2 files changed, 28 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
> index d1ecca2..936a7c9 100644
> --- a/drivers/acpi/scan.c
> +++ b/drivers/acpi/scan.c
> @@ -312,6 +312,28 @@ int acpi_match_device_ids(struct acpi_device *device,
> }
> EXPORT_SYMBOL(acpi_match_device_ids);
>
> +int acpi_match_device_id(const struct device *dev, const char *id)
Would be good idea to implement this in terms of of_match_device() so that
it returns pointer to the matched id. This way drivers can get the
->driver_data pretty easily if needed.
> +{
> + acpi_handle handle = DEVICE_ACPI_HANDLE(dev);
If the device is not bound to an ACPI handle this will return NULL. And I
don't see you doing that in this series meaning that..
> + struct acpi_device *device;
> + struct acpi_hardware_id *hwid;
> + acpi_status status;
> +
> + if (!handle || !id)
> + return -ENODEV;
..you always return -ENODEV here, right?
> +
> + status = acpi_bus_get_device(handle, &device);
> + if (ACPI_FAILURE(status))
> + return -ENODEV;
> +
> + list_for_each_entry(hwid, &device->pnp.ids, list)
> + if (!strcmp(id, hwid->id))
> + return 0;
> +
> + return -ENODEV;
> +}
> +EXPORT_SYMBOL(acpi_match_device_id);
^ permalink raw reply
* RE: [RFC PATCH 1/6] Introduce acpi_match_device_id().
From: Zhang, Rui @ 2012-09-29 13:31 UTC (permalink / raw)
To: Mika Westerberg
Cc: LKML, linux-pm, linux-i2c,
linux-acpi-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Len, Brown,
Rafael J. Wysocki, Grant Likely, Dirk Brandewie
In-Reply-To: <20120928141347.GC15548-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
> -----Original Message-----
> From: Mika Westerberg [mailto:mika.westerberg-VuQAYsv1563Yd54FQh9/CA@public.gmane.org]
> Sent: Friday, September 28, 2012 10:14 PM
> To: Zhang, Rui
> Cc: LKML; linux-pm; linux-i2c; linux-acpi-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; Len, Brown;
> Rafael J. Wysocki; Grant Likely; Dirk Brandewie
> Subject: Re: [RFC PATCH 1/6] Introduce acpi_match_device_id().
> Importance: High
>
> On Fri, Sep 28, 2012 at 03:38:30PM +0800, Zhang Rui wrote:
> > >From 72df5d1f51fb27a4ba7f70a3b07df759d32b8288 Mon Sep 17 00:00:00
> > >2001
> > From: Zhang Rui <rui.zhang-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
> > Date: Thu, 27 Sep 2012 15:11:55 +0800
> > Subject: [RFC PATCH 1/6] Introduce acpi_match_device_id().
> >
> > This API is used to check if a device id string is compatible with an
> > ACPI device, either PNP id exported via _HID or compatible ids
> > exported via _CID control method.
> >
> > Signed-off-by: Zhang Rui <rui.zhang-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
> > ---
> > drivers/acpi/scan.c | 22 ++++++++++++++++++++++
> > include/acpi/acpi_bus.h | 6 ++++++
> > 2 files changed, 28 insertions(+), 0 deletions(-)
> >
> > diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index
> > d1ecca2..936a7c9 100644
> > --- a/drivers/acpi/scan.c
> > +++ b/drivers/acpi/scan.c
> > @@ -312,6 +312,28 @@ int acpi_match_device_ids(struct acpi_device
> > *device, } EXPORT_SYMBOL(acpi_match_device_ids);
> >
> > +int acpi_match_device_id(const struct device *dev, const char *id)
>
> Would be good idea to implement this in terms of of_match_device() so
> that it returns pointer to the matched id. This way drivers can get the
> ->driver_data pretty easily if needed.
>
Good idea.
Will do that in v2.
> > +{
> > + acpi_handle handle = DEVICE_ACPI_HANDLE(dev);
>
> If the device is not bound to an ACPI handle this will return NULL. And
> I don't see you doing that in this series meaning that..
>
You're right, I should set pdev->archdata.acpi_handle to the I2C controller in i2c_root.c.
Thanks,
rui
^ permalink raw reply
* Re: [RFC PATCH 5/6] ACPI: Introduce ACPI I2C controller enumeration driver
From: Zhang, Rui @ 2012-09-29 13:37 UTC (permalink / raw)
To: Alan Cox
Cc: Grant Likely, LKML, linux-acpi@vger.kernel.org, linux-i2c,
Dirk Brandewie, linux-pm
In-Reply-To: <20120928135401.6781d134@pyramind.ukuu.org.uk>
> -----Original Message-----
> From: Alan Cox [mailto:alan@lxorguk.ukuu.org.uk]
> Sent: Friday, September 28, 2012 8:54 PM
> To: Zhang, Rui
> Cc: LKML; linux-pm; linux-i2c; linux-acpi@vger.kernel.org; Len, Brown;
> Rafael J. Wysocki; Grant Likely; Dirk Brandewie
> Subject: Re: [RFC PATCH 5/6] ACPI: Introduce ACPI I2C controller
> enumeration driver
> Importance: High
>
> On Fri, 28 Sep 2012 15:40:32 +0800
> Zhang Rui <rui.zhang@intel.com> wrote:
>
> > >From 6077a62f2865201ab6727ca7d628ee5e43aa57e1 Mon Sep 17 00:00:00
> > >2001
> > From: Zhang Rui <rui.zhang@intel.com>
> > Date: Fri, 24 Aug 2012 15:18:25 +0800
> > Subject: [RFC PATCH 5/6] ACPI: Introduce ACPI I2C controller
> > enumeration driver
> >
> > This driver is able to
> > 1) enumerate I2C controller via ACPI namespace
> > and register it as a platform device.
> > 2) enumerate I2C slave devices via ACPI namespace.
>
> Will this also trigger and work with the ACPI4 based devices that seem
> to have I²C tables that Linux currently doesn't understand (eq some
> GMA600/Oaktrail platforms) ?
I'm not aware of this. I think the answer is probably "No"
because these PNPids are pretty new.
But we can check FADT.revision to make the driver work
on ACPI5 platforms only.
Thanks,
rui
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox