ARM System Control and Power Interface(SCPI) support
It adds support for the following features provided by SCP firmware using different subsystems in Linux: 1. SCPI mailbox protocol driver which using mailbox framework 2. Clocks provided by SCP using clock framework 3. CPU DVFS(cpufreq) using existing arm-big-little driver 4. SCPI based sensors including temperature sensors -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCAAGBQJWF7ifAAoJEABBurwxfuKYAfMP/34ka/n4+U/aPQXzStNIwr3v Nme9WSf3mUPv26MstRDrWRYi1G2WLOTlc196MpdIt6m6QLOjxzEl3tSq5ILrj7yN KoLojtISmu/pbhVcJN5fllxgpcJzufLoEWBa5T/Y/4GoIhh1NCYa82QpNgzPmsMd rPCkYHqwT6I3sIS+/mbDkGA/QnwJ2qtJ8sp3+fL+dyJbI7Aa1zJZP6ectPsxK22+ HFoFTY45rdFv/ojZZFZL8E/gcblYwRWKzIgwdASHuDXxIhd/IPwjrex2Iyv75AQK zusRQ5Xv82GaYWHVa9GXmZqXkTsvBg4AJwc4Uq2JdB0qOi2a4tc8PkK7Ts5YdHgS YVGxbY1POtMBi2bJUjsviMY7dGR3I+iEXJTYnbPnkVa+GTv8/FViVmOOLQnnBF4R fN5FN0vfuL6zaQzOPYLGx3SuEHix3ko2DCAcMg6idIxuBHArlJuS7XKECWdHuc0+ +qn6Iqf8YSKIZ1zrWMggqY/sXuxjtABUBXe3jP3iTKQh8h+9SLfN3wgQM4GFJJcB gNfvk3Hl5aPFy/7gsgSDlaYbhGKPwTup+R8Fqd6nSBQO+rpRXvQQftwigYQiIEcE IiOS3BntVQWjoVr9WIifguf6rHG1ZoSMTHdtVVEaqsspT/OGJyq/ynEFJYSFqcqX NRPdQJNuoXGolGhyoWxD =9u+p -----END PGP SIGNATURE----- Merge tag 'arm-scpi-for-v4.4' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into next/drivers Merge "ARM System Control and Power Interface(SCPI) support" from Sudeep Holla It adds support for the following features provided by SCP firmware using different subsystems in Linux: 1. SCPI mailbox protocol driver which using mailbox framework 2. Clocks provided by SCP using clock framework 3. CPU DVFS(cpufreq) using existing arm-big-little driver 4. SCPI based sensors including temperature sensors * tag 'arm-scpi-for-v4.4' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux: hwmon: Support thermal zones registration for SCP temperature sensors hwmon: Support sensors exported via ARM SCP interface firmware: arm_scpi: Extend to support sensors Documentation: add DT bindings for ARM SCPI sensors cpufreq: arm_big_little: add SCPI interface driver clk: scpi: add support for cpufreq virtual device clk: add support for clocks provided by SCP(System Control Processor) firmware: add support for ARM System Control and Power Interface(SCPI) protocol Documentation: add DT binding for ARM System Control and Power Interface(SCPI) protocol
This commit is contained in:
commit
c049adc9fd
|
@ -0,0 +1,188 @@
|
|||
System Control and Power Interface (SCPI) Message Protocol
|
||||
----------------------------------------------------------
|
||||
|
||||
Firmware implementing the SCPI described in ARM document number ARM DUI 0922B
|
||||
("ARM Compute Subsystem SCP: Message Interface Protocols")[0] can be used
|
||||
by Linux to initiate various system control and power operations.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : should be "arm,scpi"
|
||||
- mboxes: List of phandle and mailbox channel specifiers
|
||||
All the channels reserved by remote SCP firmware for use by
|
||||
SCPI message protocol should be specified in any order
|
||||
- shmem : List of phandle pointing to the shared memory(SHM) area between the
|
||||
processors using these mailboxes for IPC, one for each mailbox
|
||||
SHM can be any memory reserved for the purpose of this communication
|
||||
between the processors.
|
||||
|
||||
See Documentation/devicetree/bindings/mailbox/mailbox.txt
|
||||
for more details about the generic mailbox controller and
|
||||
client driver bindings.
|
||||
|
||||
Clock bindings for the clocks based on SCPI Message Protocol
|
||||
------------------------------------------------------------
|
||||
|
||||
This binding uses the common clock binding[1].
|
||||
|
||||
Container Node
|
||||
==============
|
||||
Required properties:
|
||||
- compatible : should be "arm,scpi-clocks"
|
||||
All the clocks provided by SCP firmware via SCPI message
|
||||
protocol much be listed as sub-nodes under this node.
|
||||
|
||||
Sub-nodes
|
||||
=========
|
||||
Required properties:
|
||||
- compatible : shall include one of the following
|
||||
"arm,scpi-dvfs-clocks" - all the clocks that are variable and index based.
|
||||
These clocks don't provide an entire range of values between the
|
||||
limits but only discrete points within the range. The firmware
|
||||
provides the mapping for each such operating frequency and the
|
||||
index associated with it. The firmware also manages the
|
||||
voltage scaling appropriately with the clock scaling.
|
||||
"arm,scpi-variable-clocks" - all the clocks that are variable and provide full
|
||||
range within the specified range. The firmware provides the
|
||||
range of values within a specified range.
|
||||
|
||||
Other required properties for all clocks(all from common clock binding):
|
||||
- #clock-cells : Should be 1. Contains the Clock ID value used by SCPI commands.
|
||||
- clock-output-names : shall be the corresponding names of the outputs.
|
||||
- clock-indices: The identifying number for the clocks(i.e.clock_id) in the
|
||||
node. It can be non linear and hence provide the mapping of identifiers
|
||||
into the clock-output-names array.
|
||||
|
||||
SRAM and Shared Memory for SCPI
|
||||
-------------------------------
|
||||
|
||||
A small area of SRAM is reserved for SCPI communication between application
|
||||
processors and SCP.
|
||||
|
||||
Required properties:
|
||||
- compatible : should be "arm,juno-sram-ns" for Non-secure SRAM on Juno
|
||||
|
||||
The rest of the properties should follow the generic mmio-sram description
|
||||
found in ../../misc/sysram.txt
|
||||
|
||||
Each sub-node represents the reserved area for SCPI.
|
||||
|
||||
Required sub-node properties:
|
||||
- reg : The base offset and size of the reserved area with the SRAM
|
||||
- compatible : should be "arm,juno-scp-shmem" for Non-secure SRAM based
|
||||
shared memory on Juno platforms
|
||||
|
||||
Sensor bindings for the sensors based on SCPI Message Protocol
|
||||
--------------------------------------------------------------
|
||||
SCPI provides an API to access the various sensors on the SoC.
|
||||
|
||||
Required properties:
|
||||
- compatible : should be "arm,scpi-sensors".
|
||||
- #thermal-sensor-cells: should be set to 1. This property follows the
|
||||
thermal device tree bindings[2].
|
||||
|
||||
Valid cell values are raw identifiers (Sensor
|
||||
ID) as used by the firmware. Refer to
|
||||
platform documentation for your
|
||||
implementation for the IDs to use. For Juno
|
||||
R0 and Juno R1 refer to [3].
|
||||
|
||||
[0] http://infocenter.arm.com/help/topic/com.arm.doc.dui0922b/index.html
|
||||
[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
|
||||
[2] Documentation/devicetree/bindings/thermal/thermal.txt
|
||||
[3] http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0922b/apas03s22.html
|
||||
|
||||
Example:
|
||||
|
||||
sram: sram@50000000 {
|
||||
compatible = "arm,juno-sram-ns", "mmio-sram";
|
||||
reg = <0x0 0x50000000 0x0 0x10000>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0 0x0 0x50000000 0x10000>;
|
||||
|
||||
cpu_scp_lpri: scp-shmem@0 {
|
||||
compatible = "arm,juno-scp-shmem";
|
||||
reg = <0x0 0x200>;
|
||||
};
|
||||
|
||||
cpu_scp_hpri: scp-shmem@200 {
|
||||
compatible = "arm,juno-scp-shmem";
|
||||
reg = <0x200 0x200>;
|
||||
};
|
||||
};
|
||||
|
||||
mailbox: mailbox0@40000000 {
|
||||
....
|
||||
#mbox-cells = <1>;
|
||||
};
|
||||
|
||||
scpi_protocol: scpi@2e000000 {
|
||||
compatible = "arm,scpi";
|
||||
mboxes = <&mailbox 0 &mailbox 1>;
|
||||
shmem = <&cpu_scp_lpri &cpu_scp_hpri>;
|
||||
|
||||
clocks {
|
||||
compatible = "arm,scpi-clocks";
|
||||
|
||||
scpi_dvfs: scpi_clocks@0 {
|
||||
compatible = "arm,scpi-dvfs-clocks";
|
||||
#clock-cells = <1>;
|
||||
clock-indices = <0>, <1>, <2>;
|
||||
clock-output-names = "atlclk", "aplclk","gpuclk";
|
||||
};
|
||||
scpi_clk: scpi_clocks@3 {
|
||||
compatible = "arm,scpi-variable-clocks";
|
||||
#clock-cells = <1>;
|
||||
clock-indices = <3>, <4>;
|
||||
clock-output-names = "pxlclk0", "pxlclk1";
|
||||
};
|
||||
};
|
||||
|
||||
scpi_sensors0: sensors {
|
||||
compatible = "arm,scpi-sensors";
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
cpu@0 {
|
||||
...
|
||||
reg = <0 0>;
|
||||
clocks = <&scpi_dvfs 0>;
|
||||
};
|
||||
|
||||
hdlcd@7ff60000 {
|
||||
...
|
||||
reg = <0 0x7ff60000 0 0x1000>;
|
||||
clocks = <&scpi_clk 4>;
|
||||
};
|
||||
|
||||
thermal-zones {
|
||||
soc_thermal {
|
||||
polling-delay-passive = <100>;
|
||||
polling-delay = <1000>;
|
||||
|
||||
/* sensor ID */
|
||||
thermal-sensors = <&scpi_sensors0 3>;
|
||||
...
|
||||
};
|
||||
};
|
||||
|
||||
In the above example, the #clock-cells is set to 1 as required.
|
||||
scpi_dvfs has 3 output clocks namely: atlclk, aplclk, and gpuclk with 0,
|
||||
1 and 2 as clock-indices. scpi_clk has 2 output clocks namely: pxlclk0
|
||||
and pxlclk1 with 3 and 4 as clock-indices.
|
||||
|
||||
The first consumer in the example is cpu@0 and it has '0' as the clock
|
||||
specifier which points to the first entry in the output clocks of
|
||||
scpi_dvfs i.e. "atlclk".
|
||||
|
||||
Similarly the second example is hdlcd@7ff60000 and it has pxlclk1 as input
|
||||
clock. '4' in the clock specifier here points to the second entry
|
||||
in the output clocks of scpi_clocks i.e. "pxlclk1"
|
||||
|
||||
The thermal-sensors property in the soc_thermal node uses the
|
||||
temperature sensor provided by SCP firmware to setup a thermal
|
||||
zone. The ID "3" is the sensor identifier for the temperature sensor
|
||||
as used by the firmware.
|
|
@ -0,0 +1,33 @@
|
|||
Kernel driver scpi-hwmon
|
||||
========================
|
||||
|
||||
Supported chips:
|
||||
* Chips based on ARM System Control Processor Interface
|
||||
Addresses scanned: -
|
||||
Datasheet: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0922b/index.html
|
||||
|
||||
Author: Punit Agrawal <punit.agrawal@arm.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for SoC's based on the ARM
|
||||
System Control Processor (SCP) implementing the System Control
|
||||
Processor Interface (SCPI). The following sensor types are supported
|
||||
by the SCP -
|
||||
|
||||
* temperature
|
||||
* voltage
|
||||
* current
|
||||
* power
|
||||
|
||||
The SCP interface provides an API to query the available sensors and
|
||||
their values which are then exported to userspace by this driver.
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
The driver relies on device tree node to indicate the presence of SCPI
|
||||
support in the kernel. See
|
||||
Documentation/devicetree/bindings/arm/arm,scpi.txt for details of the
|
||||
devicetree node.
|
10
MAINTAINERS
10
MAINTAINERS
|
@ -9153,6 +9153,16 @@ W: http://www.sunplus.com
|
|||
S: Supported
|
||||
F: arch/score/
|
||||
|
||||
SYSTEM CONTROL & POWER INTERFACE (SCPI) Message Protocol drivers
|
||||
M: Sudeep Holla <sudeep.holla@arm.com>
|
||||
L: linux-arm-kernel@lists.infradead.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/arm/arm,scpi.txt
|
||||
F: drivers/clk/clk-scpi.c
|
||||
F: drivers/cpufreq/scpi-cpufreq.c
|
||||
F: drivers/firmware/arm_scpi.c
|
||||
F: include/linux/scpi_protocol.h
|
||||
|
||||
SCSI CDROM DRIVER
|
||||
M: Jens Axboe <axboe@kernel.dk>
|
||||
L: linux-scsi@vger.kernel.org
|
||||
|
|
|
@ -59,6 +59,16 @@ config COMMON_CLK_RK808
|
|||
clocked at 32KHz each. Clkout1 is always on, Clkout2 can off
|
||||
by control register.
|
||||
|
||||
config COMMON_CLK_SCPI
|
||||
tristate "Clock driver controlled via SCPI interface"
|
||||
depends on ARM_SCPI_PROTOCOL || COMPILE_TEST
|
||||
---help---
|
||||
This driver provides support for clocks that are controlled
|
||||
by firmware that implements the SCPI interface.
|
||||
|
||||
This driver uses SCPI Message Protocol to interact with the
|
||||
firmware providing all the clock controls.
|
||||
|
||||
config COMMON_CLK_SI5351
|
||||
tristate "Clock driver for SiLabs 5351A/B/C"
|
||||
depends on I2C
|
||||
|
|
|
@ -36,6 +36,7 @@ obj-$(CONFIG_COMMON_CLK_PALMAS) += clk-palmas.o
|
|||
obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o
|
||||
obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o
|
||||
obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
|
||||
obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
|
||||
obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
|
||||
obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
|
||||
obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* System Control and Power Interface (SCPI) Protocol based clock driver
|
||||
*
|
||||
* Copyright (C) 2015 ARM Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/scpi_protocol.h>
|
||||
|
||||
struct scpi_clk {
|
||||
u32 id;
|
||||
struct clk_hw hw;
|
||||
struct scpi_dvfs_info *info;
|
||||
struct scpi_ops *scpi_ops;
|
||||
};
|
||||
|
||||
#define to_scpi_clk(clk) container_of(clk, struct scpi_clk, hw)
|
||||
|
||||
static struct platform_device *cpufreq_dev;
|
||||
|
||||
static unsigned long scpi_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct scpi_clk *clk = to_scpi_clk(hw);
|
||||
|
||||
return clk->scpi_ops->clk_get_val(clk->id);
|
||||
}
|
||||
|
||||
static long scpi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *parent_rate)
|
||||
{
|
||||
/*
|
||||
* We can't figure out what rate it will be, so just return the
|
||||
* rate back to the caller. scpi_clk_recalc_rate() will be called
|
||||
* after the rate is set and we'll know what rate the clock is
|
||||
* running at then.
|
||||
*/
|
||||
return rate;
|
||||
}
|
||||
|
||||
static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct scpi_clk *clk = to_scpi_clk(hw);
|
||||
|
||||
return clk->scpi_ops->clk_set_val(clk->id, rate);
|
||||
}
|
||||
|
||||
static const struct clk_ops scpi_clk_ops = {
|
||||
.recalc_rate = scpi_clk_recalc_rate,
|
||||
.round_rate = scpi_clk_round_rate,
|
||||
.set_rate = scpi_clk_set_rate,
|
||||
};
|
||||
|
||||
/* find closest match to given frequency in OPP table */
|
||||
static int __scpi_dvfs_round_rate(struct scpi_clk *clk, unsigned long rate)
|
||||
{
|
||||
int idx;
|
||||
u32 fmin = 0, fmax = ~0, ftmp;
|
||||
const struct scpi_opp *opp = clk->info->opps;
|
||||
|
||||
for (idx = 0; idx < clk->info->count; idx++, opp++) {
|
||||
ftmp = opp->freq;
|
||||
if (ftmp >= (u32)rate) {
|
||||
if (ftmp <= fmax)
|
||||
fmax = ftmp;
|
||||
break;
|
||||
} else if (ftmp >= fmin) {
|
||||
fmin = ftmp;
|
||||
}
|
||||
}
|
||||
return fmax != ~0 ? fmax : fmin;
|
||||
}
|
||||
|
||||
static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct scpi_clk *clk = to_scpi_clk(hw);
|
||||
int idx = clk->scpi_ops->dvfs_get_idx(clk->id);
|
||||
const struct scpi_opp *opp;
|
||||
|
||||
if (idx < 0)
|
||||
return 0;
|
||||
|
||||
opp = clk->info->opps + idx;
|
||||
return opp->freq;
|
||||
}
|
||||
|
||||
static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *parent_rate)
|
||||
{
|
||||
struct scpi_clk *clk = to_scpi_clk(hw);
|
||||
|
||||
return __scpi_dvfs_round_rate(clk, rate);
|
||||
}
|
||||
|
||||
static int __scpi_find_dvfs_index(struct scpi_clk *clk, unsigned long rate)
|
||||
{
|
||||
int idx, max_opp = clk->info->count;
|
||||
const struct scpi_opp *opp = clk->info->opps;
|
||||
|
||||
for (idx = 0; idx < max_opp; idx++, opp++)
|
||||
if (opp->freq == rate)
|
||||
return idx;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int scpi_dvfs_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct scpi_clk *clk = to_scpi_clk(hw);
|
||||
int ret = __scpi_find_dvfs_index(clk, rate);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return clk->scpi_ops->dvfs_set_idx(clk->id, (u8)ret);
|
||||
}
|
||||
|
||||
static const struct clk_ops scpi_dvfs_ops = {
|
||||
.recalc_rate = scpi_dvfs_recalc_rate,
|
||||
.round_rate = scpi_dvfs_round_rate,
|
||||
.set_rate = scpi_dvfs_set_rate,
|
||||
};
|
||||
|
||||
static const struct of_device_id scpi_clk_match[] = {
|
||||
{ .compatible = "arm,scpi-dvfs-clocks", .data = &scpi_dvfs_ops, },
|
||||
{ .compatible = "arm,scpi-variable-clocks", .data = &scpi_clk_ops, },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct clk *
|
||||
scpi_clk_ops_init(struct device *dev, const struct of_device_id *match,
|
||||
struct scpi_clk *sclk, const char *name)
|
||||
{
|
||||
struct clk_init_data init;
|
||||
struct clk *clk;
|
||||
unsigned long min = 0, max = 0;
|
||||
|
||||
init.name = name;
|
||||
init.flags = CLK_IS_ROOT;
|
||||
init.num_parents = 0;
|
||||
init.ops = match->data;
|
||||
sclk->hw.init = &init;
|
||||
sclk->scpi_ops = get_scpi_ops();
|
||||
|
||||
if (init.ops == &scpi_dvfs_ops) {
|
||||
sclk->info = sclk->scpi_ops->dvfs_get_info(sclk->id);
|
||||
if (IS_ERR(sclk->info))
|
||||
return NULL;
|
||||
} else if (init.ops == &scpi_clk_ops) {
|
||||
if (sclk->scpi_ops->clk_get_range(sclk->id, &min, &max) || !max)
|
||||
return NULL;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
clk = devm_clk_register(dev, &sclk->hw);
|
||||
if (!IS_ERR(clk) && max)
|
||||
clk_hw_set_rate_range(&sclk->hw, min, max);
|
||||
return clk;
|
||||
}
|
||||
|
||||
struct scpi_clk_data {
|
||||
struct scpi_clk **clk;
|
||||
unsigned int clk_num;
|
||||
};
|
||||
|
||||
static struct clk *
|
||||
scpi_of_clk_src_get(struct of_phandle_args *clkspec, void *data)
|
||||
{
|
||||
struct scpi_clk *sclk;
|
||||
struct scpi_clk_data *clk_data = data;
|
||||
unsigned int idx = clkspec->args[0], count;
|
||||
|
||||
for (count = 0; count < clk_data->clk_num; count++) {
|
||||
sclk = clk_data->clk[count];
|
||||
if (idx == sclk->id)
|
||||
return sclk->hw.clk;
|
||||
}
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static int scpi_clk_add(struct device *dev, struct device_node *np,
|
||||
const struct of_device_id *match)
|
||||
{
|
||||
struct clk **clks;
|
||||
int idx, count;
|
||||
struct scpi_clk_data *clk_data;
|
||||
|
||||
count = of_property_count_strings(np, "clock-output-names");
|
||||
if (count < 0) {
|
||||
dev_err(dev, "%s: invalid clock output count\n", np->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk_data = devm_kmalloc(dev, sizeof(*clk_data), GFP_KERNEL);
|
||||
if (!clk_data)
|
||||
return -ENOMEM;
|
||||
|
||||
clk_data->clk_num = count;
|
||||
clk_data->clk = devm_kcalloc(dev, count, sizeof(*clk_data->clk),
|
||||
GFP_KERNEL);
|
||||
if (!clk_data->clk)
|
||||
return -ENOMEM;
|
||||
|
||||
clks = devm_kcalloc(dev, count, sizeof(*clks), GFP_KERNEL);
|
||||
if (!clks)
|
||||
return -ENOMEM;
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
struct scpi_clk *sclk;
|
||||
const char *name;
|
||||
u32 val;
|
||||
|
||||
sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
|
||||
if (!sclk)
|
||||
return -ENOMEM;
|
||||
|
||||
if (of_property_read_string_index(np, "clock-output-names",
|
||||
idx, &name)) {
|
||||
dev_err(dev, "invalid clock name @ %s\n", np->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (of_property_read_u32_index(np, "clock-indices",
|
||||
idx, &val)) {
|
||||
dev_err(dev, "invalid clock index @ %s\n", np->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sclk->id = val;
|
||||
|
||||
clks[idx] = scpi_clk_ops_init(dev, match, sclk, name);
|
||||
if (IS_ERR_OR_NULL(clks[idx]))
|
||||
dev_err(dev, "failed to register clock '%s'\n", name);
|
||||
else
|
||||
dev_dbg(dev, "Registered clock '%s'\n", name);
|
||||
clk_data->clk[idx] = sclk;
|
||||
}
|
||||
|
||||
return of_clk_add_provider(np, scpi_of_clk_src_get, clk_data);
|
||||
}
|
||||
|
||||
static int scpi_clocks_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *child, *np = dev->of_node;
|
||||
|
||||
if (cpufreq_dev) {
|
||||
platform_device_unregister(cpufreq_dev);
|
||||
cpufreq_dev = NULL;
|
||||
}
|
||||
|
||||
for_each_available_child_of_node(np, child)
|
||||
of_clk_del_provider(np);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scpi_clocks_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *child, *np = dev->of_node;
|
||||
const struct of_device_id *match;
|
||||
|
||||
if (!get_scpi_ops())
|
||||
return -ENXIO;
|
||||
|
||||
for_each_available_child_of_node(np, child) {
|
||||
match = of_match_node(scpi_clk_match, child);
|
||||
if (!match)
|
||||
continue;
|
||||
ret = scpi_clk_add(dev, child, match);
|
||||
if (ret) {
|
||||
scpi_clocks_remove(pdev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
/* Add the virtual cpufreq device */
|
||||
cpufreq_dev = platform_device_register_simple("scpi-cpufreq",
|
||||
-1, NULL, 0);
|
||||
if (!cpufreq_dev)
|
||||
pr_warn("unable to register cpufreq device");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id scpi_clocks_ids[] = {
|
||||
{ .compatible = "arm,scpi-clocks", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, scpi_clocks_ids);
|
||||
|
||||
static struct platform_driver scpi_clocks_driver = {
|
||||
.driver = {
|
||||
.name = "scpi_clocks",
|
||||
.of_match_table = scpi_clocks_ids,
|
||||
},
|
||||
.probe = scpi_clocks_probe,
|
||||
.remove = scpi_clocks_remove,
|
||||
};
|
||||
module_platform_driver(scpi_clocks_driver);
|
||||
|
||||
MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
|
||||
MODULE_DESCRIPTION("ARM SCPI clock driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -199,6 +199,16 @@ config ARM_SA1100_CPUFREQ
|
|||
config ARM_SA1110_CPUFREQ
|
||||
bool
|
||||
|
||||
config ARM_SCPI_CPUFREQ
|
||||
tristate "SCPI based CPUfreq driver"
|
||||
depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL
|
||||
help
|
||||
This adds the CPUfreq driver support for ARM big.LITTLE platforms
|
||||
using SCPI protocol for CPU power management.
|
||||
|
||||
This driver uses SCPI Message Protocol driver to interact with the
|
||||
firmware providing the CPU DVFS functionality.
|
||||
|
||||
config ARM_SPEAR_CPUFREQ
|
||||
bool "SPEAr CPUFreq support"
|
||||
depends on PLAT_SPEAR
|
||||
|
|
|
@ -72,6 +72,7 @@ obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
|
|||
obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
|
||||
obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o
|
||||
obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
|
||||
obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o
|
||||
obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
|
||||
obj-$(CONFIG_ARM_TEGRA20_CPUFREQ) += tegra20-cpufreq.o
|
||||
obj-$(CONFIG_ARM_TEGRA124_CPUFREQ) += tegra124-cpufreq.o
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* System Control and Power Interface (SCPI) based CPUFreq Interface driver
|
||||
*
|
||||
* It provides necessary ops to arm_big_little cpufreq driver.
|
||||
*
|
||||
* Copyright (C) 2015 ARM Ltd.
|
||||
* Sudeep Holla <sudeep.holla@arm.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
||||
* kind, whether express or implied; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/scpi_protocol.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "arm_big_little.h"
|
||||
|
||||
static struct scpi_ops *scpi_ops;
|
||||
|
||||
static struct scpi_dvfs_info *scpi_get_dvfs_info(struct device *cpu_dev)
|
||||
{
|
||||
u8 domain = topology_physical_package_id(cpu_dev->id);
|
||||
|
||||
if (domain < 0)
|
||||
return ERR_PTR(-EINVAL);
|
||||
return scpi_ops->dvfs_get_info(domain);
|
||||
}
|
||||
|
||||
static int scpi_opp_table_ops(struct device *cpu_dev, bool remove)
|
||||
{
|
||||
int idx, ret = 0;
|
||||
struct scpi_opp *opp;
|
||||
struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev);
|
||||
|
||||
if (IS_ERR(info))
|
||||
return PTR_ERR(info);
|
||||
|
||||
if (!info->opps)
|
||||
return -EIO;
|
||||
|
||||
for (opp = info->opps, idx = 0; idx < info->count; idx++, opp++) {
|
||||
if (remove)
|
||||
dev_pm_opp_remove(cpu_dev, opp->freq);
|
||||
else
|
||||
ret = dev_pm_opp_add(cpu_dev, opp->freq,
|
||||
opp->m_volt * 1000);
|
||||
if (ret) {
|
||||
dev_warn(cpu_dev, "failed to add opp %uHz %umV\n",
|
||||
opp->freq, opp->m_volt);
|
||||
while (idx-- > 0)
|
||||
dev_pm_opp_remove(cpu_dev, (--opp)->freq);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scpi_get_transition_latency(struct device *cpu_dev)
|
||||
{
|
||||
struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev);
|
||||
|
||||
if (IS_ERR(info))
|
||||
return PTR_ERR(info);
|
||||
return info->latency;
|
||||
}
|
||||
|
||||
static int scpi_init_opp_table(struct device *cpu_dev)
|
||||
{
|
||||
return scpi_opp_table_ops(cpu_dev, false);
|
||||
}
|
||||
|
||||
static void scpi_free_opp_table(struct device *cpu_dev)
|
||||
{
|
||||
scpi_opp_table_ops(cpu_dev, true);
|
||||
}
|
||||
|
||||
static struct cpufreq_arm_bL_ops scpi_cpufreq_ops = {
|
||||
.name = "scpi",
|
||||
.get_transition_latency = scpi_get_transition_latency,
|
||||
.init_opp_table = scpi_init_opp_table,
|
||||
.free_opp_table = scpi_free_opp_table,
|
||||
};
|
||||
|
||||
static int scpi_cpufreq_probe(struct platform_device *pdev)
|
||||
{
|
||||
scpi_ops = get_scpi_ops();
|
||||
if (!scpi_ops)
|
||||
return -EIO;
|
||||
|
||||
return bL_cpufreq_register(&scpi_cpufreq_ops);
|
||||
}
|
||||
|
||||
static int scpi_cpufreq_remove(struct platform_device *pdev)
|
||||
{
|
||||
bL_cpufreq_unregister(&scpi_cpufreq_ops);
|
||||
scpi_ops = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver scpi_cpufreq_platdrv = {
|
||||
.driver = {
|
||||
.name = "scpi-cpufreq",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = scpi_cpufreq_probe,
|
||||
.remove = scpi_cpufreq_remove,
|
||||
};
|
||||
module_platform_driver(scpi_cpufreq_platdrv);
|
||||
|
||||
MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
|
||||
MODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -8,6 +8,25 @@ menu "Firmware Drivers"
|
|||
config ARM_PSCI_FW
|
||||
bool
|
||||
|
||||
config ARM_SCPI_PROTOCOL
|
||||
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
|
||||
depends on ARM_MHU
|
||||
help
|
||||
System Control and Power Interface (SCPI) Message Protocol is
|
||||
defined for the purpose of communication between the Application
|
||||
Cores(AP) and the System Control Processor(SCP). The MHU peripheral
|
||||
provides a mechanism for inter-processor communication between SCP
|
||||
and AP.
|
||||
|
||||
SCP controls most of the power managament on the Application
|
||||
Processors. It offers control and management of: the core/cluster
|
||||
power states, various power domain DVFS including the core/cluster,
|
||||
certain system clocks configuration, thermal sensors and many
|
||||
others.
|
||||
|
||||
This protocol library provides interface for all the client drivers
|
||||
making use of the features offered by the SCP.
|
||||
|
||||
config EDD
|
||||
tristate "BIOS Enhanced Disk Drive calls determine boot disk"
|
||||
depends on X86
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# Makefile for the linux kernel.
|
||||
#
|
||||
obj-$(CONFIG_ARM_PSCI_FW) += psci.o
|
||||
obj-$(CONFIG_ARM_SCPI_PROTOCOL) += arm_scpi.o
|
||||
obj-$(CONFIG_DMI) += dmi_scan.o
|
||||
obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
|
||||
obj-$(CONFIG_EDD) += edd.o
|
||||
|
|
|
@ -0,0 +1,771 @@
|
|||
/*
|
||||
* System Control and Power Interface (SCPI) Message Protocol driver
|
||||
*
|
||||
* SCPI Message Protocol is used between the System Control Processor(SCP)
|
||||
* and the Application Processors(AP). The Message Handling Unit(MHU)
|
||||
* provides a mechanism for inter-processor communication between SCP's
|
||||
* Cortex M3 and AP.
|
||||
*
|
||||
* SCP offers control and management of the core/cluster power states,
|
||||
* various power domain DVFS including the core/cluster, certain system
|
||||
* clocks configuration, thermal sensors and many others.
|
||||
*
|
||||
* Copyright (C) 2015 ARM Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mailbox_client.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/scpi_protocol.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sort.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define CMD_ID_SHIFT 0
|
||||
#define CMD_ID_MASK 0x7f
|
||||
#define CMD_TOKEN_ID_SHIFT 8
|
||||
#define CMD_TOKEN_ID_MASK 0xff
|
||||
#define CMD_DATA_SIZE_SHIFT 16
|
||||
#define CMD_DATA_SIZE_MASK 0x1ff
|
||||
#define PACK_SCPI_CMD(cmd_id, tx_sz) \
|
||||
((((cmd_id) & CMD_ID_MASK) << CMD_ID_SHIFT) | \
|
||||
(((tx_sz) & CMD_DATA_SIZE_MASK) << CMD_DATA_SIZE_SHIFT))
|
||||
#define ADD_SCPI_TOKEN(cmd, token) \
|
||||
((cmd) |= (((token) & CMD_TOKEN_ID_MASK) << CMD_TOKEN_ID_SHIFT))
|
||||
|
||||
#define CMD_SIZE(cmd) (((cmd) >> CMD_DATA_SIZE_SHIFT) & CMD_DATA_SIZE_MASK)
|
||||
#define CMD_UNIQ_MASK (CMD_TOKEN_ID_MASK << CMD_TOKEN_ID_SHIFT | CMD_ID_MASK)
|
||||
#define CMD_XTRACT_UNIQ(cmd) ((cmd) & CMD_UNIQ_MASK)
|
||||
|
||||
#define SCPI_SLOT 0
|
||||
|
||||
#define MAX_DVFS_DOMAINS 8
|
||||
#define MAX_DVFS_OPPS 8
|
||||
#define DVFS_LATENCY(hdr) (le32_to_cpu(hdr) >> 16)
|
||||
#define DVFS_OPP_COUNT(hdr) ((le32_to_cpu(hdr) >> 8) & 0xff)
|
||||
|
||||
#define PROTOCOL_REV_MINOR_BITS 16
|
||||
#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1)
|
||||
#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
|
||||
#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
|
||||
|
||||
#define FW_REV_MAJOR_BITS 24
|
||||
#define FW_REV_MINOR_BITS 16
|
||||
#define FW_REV_PATCH_MASK ((1U << FW_REV_MINOR_BITS) - 1)
|
||||
#define FW_REV_MINOR_MASK ((1U << FW_REV_MAJOR_BITS) - 1)
|
||||
#define FW_REV_MAJOR(x) ((x) >> FW_REV_MAJOR_BITS)
|
||||
#define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS)
|
||||
#define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK)
|
||||
|
||||
#define MAX_RX_TIMEOUT (msecs_to_jiffies(20))
|
||||
|
||||
enum scpi_error_codes {
|
||||
SCPI_SUCCESS = 0, /* Success */
|
||||
SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */
|
||||
SCPI_ERR_ALIGN = 2, /* Invalid alignment */
|
||||
SCPI_ERR_SIZE = 3, /* Invalid size */
|
||||
SCPI_ERR_HANDLER = 4, /* Invalid handler/callback */
|
||||
SCPI_ERR_ACCESS = 5, /* Invalid access/permission denied */
|
||||
SCPI_ERR_RANGE = 6, /* Value out of range */
|
||||
SCPI_ERR_TIMEOUT = 7, /* Timeout has occurred */
|
||||
SCPI_ERR_NOMEM = 8, /* Invalid memory area or pointer */
|
||||
SCPI_ERR_PWRSTATE = 9, /* Invalid power state */
|
||||
SCPI_ERR_SUPPORT = 10, /* Not supported or disabled */
|
||||
SCPI_ERR_DEVICE = 11, /* Device error */
|
||||
SCPI_ERR_BUSY = 12, /* Device busy */
|
||||
SCPI_ERR_MAX
|
||||
};
|
||||
|
||||
enum scpi_std_cmd {
|
||||
SCPI_CMD_INVALID = 0x00,
|
||||
SCPI_CMD_SCPI_READY = 0x01,
|
||||
SCPI_CMD_SCPI_CAPABILITIES = 0x02,
|
||||
SCPI_CMD_SET_CSS_PWR_STATE = 0x03,
|
||||
SCPI_CMD_GET_CSS_PWR_STATE = 0x04,
|
||||
SCPI_CMD_SET_SYS_PWR_STATE = 0x05,
|
||||
SCPI_CMD_SET_CPU_TIMER = 0x06,
|
||||
SCPI_CMD_CANCEL_CPU_TIMER = 0x07,
|
||||
SCPI_CMD_DVFS_CAPABILITIES = 0x08,
|
||||
SCPI_CMD_GET_DVFS_INFO = 0x09,
|
||||
SCPI_CMD_SET_DVFS = 0x0a,
|
||||
SCPI_CMD_GET_DVFS = 0x0b,
|
||||
SCPI_CMD_GET_DVFS_STAT = 0x0c,
|
||||
SCPI_CMD_CLOCK_CAPABILITIES = 0x0d,
|
||||
SCPI_CMD_GET_CLOCK_INFO = 0x0e,
|
||||
SCPI_CMD_SET_CLOCK_VALUE = 0x0f,
|
||||
SCPI_CMD_GET_CLOCK_VALUE = 0x10,
|
||||
SCPI_CMD_PSU_CAPABILITIES = 0x11,
|
||||
SCPI_CMD_GET_PSU_INFO = 0x12,
|
||||
SCPI_CMD_SET_PSU = 0x13,
|
||||
SCPI_CMD_GET_PSU = 0x14,
|
||||
SCPI_CMD_SENSOR_CAPABILITIES = 0x15,
|
||||
SCPI_CMD_SENSOR_INFO = 0x16,
|
||||
SCPI_CMD_SENSOR_VALUE = 0x17,
|
||||
SCPI_CMD_SENSOR_CFG_PERIODIC = 0x18,
|
||||
SCPI_CMD_SENSOR_CFG_BOUNDS = 0x19,
|
||||
SCPI_CMD_SENSOR_ASYNC_VALUE = 0x1a,
|
||||
SCPI_CMD_SET_DEVICE_PWR_STATE = 0x1b,
|
||||
SCPI_CMD_GET_DEVICE_PWR_STATE = 0x1c,
|
||||
SCPI_CMD_COUNT
|
||||
};
|
||||
|
||||
struct scpi_xfer {
|
||||
u32 slot; /* has to be first element */
|
||||
u32 cmd;
|
||||
u32 status;
|
||||
const void *tx_buf;
|
||||
void *rx_buf;
|
||||
unsigned int tx_len;
|
||||
unsigned int rx_len;
|
||||
struct list_head node;
|
||||
struct completion done;
|
||||
};
|
||||
|
||||
struct scpi_chan {
|
||||
struct mbox_client cl;
|
||||
struct mbox_chan *chan;
|
||||
void __iomem *tx_payload;
|
||||
void __iomem *rx_payload;
|
||||
struct list_head rx_pending;
|
||||
struct list_head xfers_list;
|
||||
struct scpi_xfer *xfers;
|
||||
spinlock_t rx_lock; /* locking for the rx pending list */
|
||||
struct mutex xfers_lock;
|
||||
u8 token;
|
||||
};
|
||||
|
||||
struct scpi_drvinfo {
|
||||
u32 protocol_version;
|
||||
u32 firmware_version;
|
||||
int num_chans;
|
||||
atomic_t next_chan;
|
||||
struct scpi_ops *scpi_ops;
|
||||
struct scpi_chan *channels;
|
||||
struct scpi_dvfs_info *dvfs[MAX_DVFS_DOMAINS];
|
||||
};
|
||||
|
||||
/*
|
||||
* The SCP firmware only executes in little-endian mode, so any buffers
|
||||
* shared through SCPI should have their contents converted to little-endian
|
||||
*/
|
||||
struct scpi_shared_mem {
|
||||
__le32 command;
|
||||
__le32 status;
|
||||
u8 payload[0];
|
||||
} __packed;
|
||||
|
||||
struct scp_capabilities {
|
||||
__le32 protocol_version;
|
||||
__le32 event_version;
|
||||
__le32 platform_version;
|
||||
__le32 commands[4];
|
||||
} __packed;
|
||||
|
||||
struct clk_get_info {
|
||||
__le16 id;
|
||||
__le16 flags;
|
||||
__le32 min_rate;
|
||||
__le32 max_rate;
|
||||
u8 name[20];
|
||||
} __packed;
|
||||
|
||||
struct clk_get_value {
|
||||
__le32 rate;
|
||||
} __packed;
|
||||
|
||||
struct clk_set_value {
|
||||
__le16 id;
|
||||
__le16 reserved;
|
||||
__le32 rate;
|
||||
} __packed;
|
||||
|
||||
struct dvfs_info {
|
||||
__le32 header;
|
||||
struct {
|
||||
__le32 freq;
|
||||
__le32 m_volt;
|
||||
} opps[MAX_DVFS_OPPS];
|
||||
} __packed;
|
||||
|
||||
struct dvfs_get {
|
||||
u8 index;
|
||||
} __packed;
|
||||
|
||||
struct dvfs_set {
|
||||
u8 domain;
|
||||
u8 index;
|
||||
} __packed;
|
||||
|
||||
struct sensor_capabilities {
|
||||
__le16 sensors;
|
||||
} __packed;
|
||||
|
||||
struct _scpi_sensor_info {
|
||||
__le16 sensor_id;
|
||||
u8 class;
|
||||
u8 trigger_type;
|
||||
char name[20];
|
||||
};
|
||||
|
||||
struct sensor_value {
|
||||
__le32 val;
|
||||
} __packed;
|
||||
|
||||
static struct scpi_drvinfo *scpi_info;
|
||||
|
||||
static int scpi_linux_errmap[SCPI_ERR_MAX] = {
|
||||
/* better than switch case as long as return value is continuous */
|
||||
0, /* SCPI_SUCCESS */
|
||||
-EINVAL, /* SCPI_ERR_PARAM */
|
||||
-ENOEXEC, /* SCPI_ERR_ALIGN */
|
||||
-EMSGSIZE, /* SCPI_ERR_SIZE */
|
||||
-EINVAL, /* SCPI_ERR_HANDLER */
|
||||
-EACCES, /* SCPI_ERR_ACCESS */
|
||||
-ERANGE, /* SCPI_ERR_RANGE */
|
||||
-ETIMEDOUT, /* SCPI_ERR_TIMEOUT */
|
||||
-ENOMEM, /* SCPI_ERR_NOMEM */
|
||||
-EINVAL, /* SCPI_ERR_PWRSTATE */
|
||||
-EOPNOTSUPP, /* SCPI_ERR_SUPPORT */
|
||||
-EIO, /* SCPI_ERR_DEVICE */
|
||||
-EBUSY, /* SCPI_ERR_BUSY */
|
||||
};
|
||||
|
||||
static inline int scpi_to_linux_errno(int errno)
|
||||
{
|
||||
if (errno >= SCPI_SUCCESS && errno < SCPI_ERR_MAX)
|
||||
return scpi_linux_errmap[errno];
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct scpi_xfer *t, *match = NULL;
|
||||
|
||||
spin_lock_irqsave(&ch->rx_lock, flags);
|
||||
if (list_empty(&ch->rx_pending)) {
|
||||
spin_unlock_irqrestore(&ch->rx_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
list_for_each_entry(t, &ch->rx_pending, node)
|
||||
if (CMD_XTRACT_UNIQ(t->cmd) == CMD_XTRACT_UNIQ(cmd)) {
|
||||
list_del(&t->node);
|
||||
match = t;
|
||||
break;
|
||||
}
|
||||
/* check if wait_for_completion is in progress or timed-out */
|
||||
if (match && !completion_done(&match->done)) {
|
||||
struct scpi_shared_mem *mem = ch->rx_payload;
|
||||
unsigned int len = min(match->rx_len, CMD_SIZE(cmd));
|
||||
|
||||
match->status = le32_to_cpu(mem->status);
|
||||
memcpy_fromio(match->rx_buf, mem->payload, len);
|
||||
if (match->rx_len > len)
|
||||
memset(match->rx_buf + len, 0, match->rx_len - len);
|
||||
complete(&match->done);
|
||||
}
|
||||
spin_unlock_irqrestore(&ch->rx_lock, flags);
|
||||
}
|
||||
|
||||
static void scpi_handle_remote_msg(struct mbox_client *c, void *msg)
|
||||
{
|
||||
struct scpi_chan *ch = container_of(c, struct scpi_chan, cl);
|
||||
struct scpi_shared_mem *mem = ch->rx_payload;
|
||||
u32 cmd = le32_to_cpu(mem->command);
|
||||
|
||||
scpi_process_cmd(ch, cmd);
|
||||
}
|
||||
|
||||
static void scpi_tx_prepare(struct mbox_client *c, void *msg)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct scpi_xfer *t = msg;
|
||||
struct scpi_chan *ch = container_of(c, struct scpi_chan, cl);
|
||||
struct scpi_shared_mem *mem = (struct scpi_shared_mem *)ch->tx_payload;
|
||||
|
||||
if (t->tx_buf)
|
||||
memcpy_toio(mem->payload, t->tx_buf, t->tx_len);
|
||||
if (t->rx_buf) {
|
||||
if (!(++ch->token))
|
||||
++ch->token;
|
||||
ADD_SCPI_TOKEN(t->cmd, ch->token);
|
||||
spin_lock_irqsave(&ch->rx_lock, flags);
|
||||
list_add_tail(&t->node, &ch->rx_pending);
|
||||
spin_unlock_irqrestore(&ch->rx_lock, flags);
|
||||
}
|
||||
mem->command = cpu_to_le32(t->cmd);
|
||||
}
|
||||
|
||||
static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch)
|
||||
{
|
||||
struct scpi_xfer *t;
|
||||
|
||||
mutex_lock(&ch->xfers_lock);
|
||||
if (list_empty(&ch->xfers_list)) {
|
||||
mutex_unlock(&ch->xfers_lock);
|
||||
return NULL;
|
||||
}
|
||||
t = list_first_entry(&ch->xfers_list, struct scpi_xfer, node);
|
||||
list_del(&t->node);
|
||||
mutex_unlock(&ch->xfers_lock);
|
||||
return t;
|
||||
}
|
||||
|
||||
static void put_scpi_xfer(struct scpi_xfer *t, struct scpi_chan *ch)
|
||||
{
|
||||
mutex_lock(&ch->xfers_lock);
|
||||
list_add_tail(&t->node, &ch->xfers_list);
|
||||
mutex_unlock(&ch->xfers_lock);
|
||||
}
|
||||
|
||||
static int scpi_send_message(u8 cmd, void *tx_buf, unsigned int tx_len,
|
||||
void *rx_buf, unsigned int rx_len)
|
||||
{
|
||||
int ret;
|
||||
u8 chan;
|
||||
struct scpi_xfer *msg;
|
||||
struct scpi_chan *scpi_chan;
|
||||
|
||||
chan = atomic_inc_return(&scpi_info->next_chan) % scpi_info->num_chans;
|
||||
scpi_chan = scpi_info->channels + chan;
|
||||
|
||||
msg = get_scpi_xfer(scpi_chan);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
msg->slot = BIT(SCPI_SLOT);
|
||||
msg->cmd = PACK_SCPI_CMD(cmd, tx_len);
|
||||
msg->tx_buf = tx_buf;
|
||||
msg->tx_len = tx_len;
|
||||
msg->rx_buf = rx_buf;
|
||||
msg->rx_len = rx_len;
|
||||
init_completion(&msg->done);
|
||||
|
||||
ret = mbox_send_message(scpi_chan->chan, msg);
|
||||
if (ret < 0 || !rx_buf)
|
||||
goto out;
|
||||
|
||||
if (!wait_for_completion_timeout(&msg->done, MAX_RX_TIMEOUT))
|
||||
ret = -ETIMEDOUT;
|
||||
else
|
||||
/* first status word */
|
||||
ret = le32_to_cpu(msg->status);
|
||||
out:
|
||||
if (ret < 0 && rx_buf) /* remove entry from the list if timed-out */
|
||||
scpi_process_cmd(scpi_chan, msg->cmd);
|
||||
|
||||
put_scpi_xfer(msg, scpi_chan);
|
||||
/* SCPI error codes > 0, translate them to Linux scale*/
|
||||
return ret > 0 ? scpi_to_linux_errno(ret) : ret;
|
||||
}
|
||||
|
||||
static u32 scpi_get_version(void)
|
||||
{
|
||||
return scpi_info->protocol_version;
|
||||
}
|
||||
|
||||
static int
|
||||
scpi_clk_get_range(u16 clk_id, unsigned long *min, unsigned long *max)
|
||||
{
|
||||
int ret;
|
||||
struct clk_get_info clk;
|
||||
__le16 le_clk_id = cpu_to_le16(clk_id);
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_GET_CLOCK_INFO, &le_clk_id,
|
||||
sizeof(le_clk_id), &clk, sizeof(clk));
|
||||
if (!ret) {
|
||||
*min = le32_to_cpu(clk.min_rate);
|
||||
*max = le32_to_cpu(clk.max_rate);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned long scpi_clk_get_val(u16 clk_id)
|
||||
{
|
||||
int ret;
|
||||
struct clk_get_value clk;
|
||||
__le16 le_clk_id = cpu_to_le16(clk_id);
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_GET_CLOCK_VALUE, &le_clk_id,
|
||||
sizeof(le_clk_id), &clk, sizeof(clk));
|
||||
return ret ? ret : le32_to_cpu(clk.rate);
|
||||
}
|
||||
|
||||
static int scpi_clk_set_val(u16 clk_id, unsigned long rate)
|
||||
{
|
||||
int stat;
|
||||
struct clk_set_value clk = {
|
||||
.id = cpu_to_le16(clk_id),
|
||||
.rate = cpu_to_le32(rate)
|
||||
};
|
||||
|
||||
return scpi_send_message(SCPI_CMD_SET_CLOCK_VALUE, &clk, sizeof(clk),
|
||||
&stat, sizeof(stat));
|
||||
}
|
||||
|
||||
static int scpi_dvfs_get_idx(u8 domain)
|
||||
{
|
||||
int ret;
|
||||
struct dvfs_get dvfs;
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_GET_DVFS, &domain, sizeof(domain),
|
||||
&dvfs, sizeof(dvfs));
|
||||
return ret ? ret : dvfs.index;
|
||||
}
|
||||
|
||||
static int scpi_dvfs_set_idx(u8 domain, u8 index)
|
||||
{
|
||||
int stat;
|
||||
struct dvfs_set dvfs = {domain, index};
|
||||
|
||||
return scpi_send_message(SCPI_CMD_SET_DVFS, &dvfs, sizeof(dvfs),
|
||||
&stat, sizeof(stat));
|
||||
}
|
||||
|
||||
static int opp_cmp_func(const void *opp1, const void *opp2)
|
||||
{
|
||||
const struct scpi_opp *t1 = opp1, *t2 = opp2;
|
||||
|
||||
return t1->freq - t2->freq;
|
||||
}
|
||||
|
||||
static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain)
|
||||
{
|
||||
struct scpi_dvfs_info *info;
|
||||
struct scpi_opp *opp;
|
||||
struct dvfs_info buf;
|
||||
int ret, i;
|
||||
|
||||
if (domain >= MAX_DVFS_DOMAINS)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (scpi_info->dvfs[domain]) /* data already populated */
|
||||
return scpi_info->dvfs[domain];
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_GET_DVFS_INFO, &domain, sizeof(domain),
|
||||
&buf, sizeof(buf));
|
||||
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
info = kmalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
info->count = DVFS_OPP_COUNT(buf.header);
|
||||
info->latency = DVFS_LATENCY(buf.header) * 1000; /* uS to nS */
|
||||
|
||||
info->opps = kcalloc(info->count, sizeof(*opp), GFP_KERNEL);
|
||||
if (!info->opps) {
|
||||
kfree(info);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
for (i = 0, opp = info->opps; i < info->count; i++, opp++) {
|
||||
opp->freq = le32_to_cpu(buf.opps[i].freq);
|
||||
opp->m_volt = le32_to_cpu(buf.opps[i].m_volt);
|
||||
}
|
||||
|
||||
sort(info->opps, info->count, sizeof(*opp), opp_cmp_func, NULL);
|
||||
|
||||
scpi_info->dvfs[domain] = info;
|
||||
return info;
|
||||
}
|
||||
|
||||
static int scpi_sensor_get_capability(u16 *sensors)
|
||||
{
|
||||
struct sensor_capabilities cap_buf;
|
||||
int ret;
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_SENSOR_CAPABILITIES, NULL, 0, &cap_buf,
|
||||
sizeof(cap_buf));
|
||||
if (!ret)
|
||||
*sensors = le16_to_cpu(cap_buf.sensors);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info)
|
||||
{
|
||||
__le16 id = cpu_to_le16(sensor_id);
|
||||
struct _scpi_sensor_info _info;
|
||||
int ret;
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_SENSOR_INFO, &id, sizeof(id),
|
||||
&_info, sizeof(_info));
|
||||
if (!ret) {
|
||||
memcpy(info, &_info, sizeof(*info));
|
||||
info->sensor_id = le16_to_cpu(_info.sensor_id);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scpi_sensor_get_value(u16 sensor, u32 *val)
|
||||
{
|
||||
struct sensor_value buf;
|
||||
int ret;
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_SENSOR_VALUE, &sensor, sizeof(sensor),
|
||||
&buf, sizeof(buf));
|
||||
if (!ret)
|
||||
*val = le32_to_cpu(buf.val);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct scpi_ops scpi_ops = {
|
||||
.get_version = scpi_get_version,
|
||||
.clk_get_range = scpi_clk_get_range,
|
||||
.clk_get_val = scpi_clk_get_val,
|
||||
.clk_set_val = scpi_clk_set_val,
|
||||
.dvfs_get_idx = scpi_dvfs_get_idx,
|
||||
.dvfs_set_idx = scpi_dvfs_set_idx,
|
||||
.dvfs_get_info = scpi_dvfs_get_info,
|
||||
.sensor_get_capability = scpi_sensor_get_capability,
|
||||
.sensor_get_info = scpi_sensor_get_info,
|
||||
.sensor_get_value = scpi_sensor_get_value,
|
||||
};
|
||||
|
||||
struct scpi_ops *get_scpi_ops(void)
|
||||
{
|
||||
return scpi_info ? scpi_info->scpi_ops : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(get_scpi_ops);
|
||||
|
||||
static int scpi_init_versions(struct scpi_drvinfo *info)
|
||||
{
|
||||
int ret;
|
||||
struct scp_capabilities caps;
|
||||
|
||||
ret = scpi_send_message(SCPI_CMD_SCPI_CAPABILITIES, NULL, 0,
|
||||
&caps, sizeof(caps));
|
||||
if (!ret) {
|
||||
info->protocol_version = le32_to_cpu(caps.protocol_version);
|
||||
info->firmware_version = le32_to_cpu(caps.platform_version);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t protocol_version_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d.%d\n",
|
||||
PROTOCOL_REV_MAJOR(scpi_info->protocol_version),
|
||||
PROTOCOL_REV_MINOR(scpi_info->protocol_version));
|
||||
}
|
||||
static DEVICE_ATTR_RO(protocol_version);
|
||||
|
||||
static ssize_t firmware_version_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d.%d.%d\n",
|
||||
FW_REV_MAJOR(scpi_info->firmware_version),
|
||||
FW_REV_MINOR(scpi_info->firmware_version),
|
||||
FW_REV_PATCH(scpi_info->firmware_version));
|
||||
}
|
||||
static DEVICE_ATTR_RO(firmware_version);
|
||||
|
||||
static struct attribute *versions_attrs[] = {
|
||||
&dev_attr_firmware_version.attr,
|
||||
&dev_attr_protocol_version.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(versions);
|
||||
|
||||
static void
|
||||
scpi_free_channels(struct device *dev, struct scpi_chan *pchan, int count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count && pchan->chan; i++, pchan++) {
|
||||
mbox_free_channel(pchan->chan);
|
||||
devm_kfree(dev, pchan->xfers);
|
||||
devm_iounmap(dev, pchan->rx_payload);
|
||||
}
|
||||
}
|
||||
|
||||
static int scpi_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct scpi_drvinfo *info = platform_get_drvdata(pdev);
|
||||
|
||||
scpi_info = NULL; /* stop exporting SCPI ops through get_scpi_ops */
|
||||
|
||||
of_platform_depopulate(dev);
|
||||
sysfs_remove_groups(&dev->kobj, versions_groups);
|
||||
scpi_free_channels(dev, info->channels, info->num_chans);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
for (i = 0; i < MAX_DVFS_DOMAINS && info->dvfs[i]; i++) {
|
||||
kfree(info->dvfs[i]->opps);
|
||||
kfree(info->dvfs[i]);
|
||||
}
|
||||
devm_kfree(dev, info->channels);
|
||||
devm_kfree(dev, info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_SCPI_XFERS 10
|
||||
static int scpi_alloc_xfer_list(struct device *dev, struct scpi_chan *ch)
|
||||
{
|
||||
int i;
|
||||
struct scpi_xfer *xfers;
|
||||
|
||||
xfers = devm_kzalloc(dev, MAX_SCPI_XFERS * sizeof(*xfers), GFP_KERNEL);
|
||||
if (!xfers)
|
||||
return -ENOMEM;
|
||||
|
||||
ch->xfers = xfers;
|
||||
for (i = 0; i < MAX_SCPI_XFERS; i++, xfers++)
|
||||
list_add_tail(&xfers->node, &ch->xfers_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scpi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int count, idx, ret;
|
||||
struct resource res;
|
||||
struct scpi_chan *scpi_chan;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
scpi_info = devm_kzalloc(dev, sizeof(*scpi_info), GFP_KERNEL);
|
||||
if (!scpi_info)
|
||||
return -ENOMEM;
|
||||
|
||||
count = of_count_phandle_with_args(np, "mboxes", "#mbox-cells");
|
||||
if (count < 0) {
|
||||
dev_err(dev, "no mboxes property in '%s'\n", np->full_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
scpi_chan = devm_kcalloc(dev, count, sizeof(*scpi_chan), GFP_KERNEL);
|
||||
if (!scpi_chan)
|
||||
return -ENOMEM;
|
||||
|
||||
for (idx = 0; idx < count; idx++) {
|
||||
resource_size_t size;
|
||||
struct scpi_chan *pchan = scpi_chan + idx;
|
||||
struct mbox_client *cl = &pchan->cl;
|
||||
struct device_node *shmem = of_parse_phandle(np, "shmem", idx);
|
||||
|
||||
if (of_address_to_resource(shmem, 0, &res)) {
|
||||
dev_err(dev, "failed to get SCPI payload mem resource\n");
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
size = resource_size(&res);
|
||||
pchan->rx_payload = devm_ioremap(dev, res.start, size);
|
||||
if (!pchan->rx_payload) {
|
||||
dev_err(dev, "failed to ioremap SCPI payload\n");
|
||||
ret = -EADDRNOTAVAIL;
|
||||
goto err;
|
||||
}
|
||||
pchan->tx_payload = pchan->rx_payload + (size >> 1);
|
||||
|
||||
cl->dev = dev;
|
||||
cl->rx_callback = scpi_handle_remote_msg;
|
||||
cl->tx_prepare = scpi_tx_prepare;
|
||||
cl->tx_block = true;
|
||||
cl->tx_tout = 50;
|
||||
cl->knows_txdone = false; /* controller can't ack */
|
||||
|
||||
INIT_LIST_HEAD(&pchan->rx_pending);
|
||||
INIT_LIST_HEAD(&pchan->xfers_list);
|
||||
spin_lock_init(&pchan->rx_lock);
|
||||
mutex_init(&pchan->xfers_lock);
|
||||
|
||||
ret = scpi_alloc_xfer_list(dev, pchan);
|
||||
if (!ret) {
|
||||
pchan->chan = mbox_request_channel(cl, idx);
|
||||
if (!IS_ERR(pchan->chan))
|
||||
continue;
|
||||
ret = PTR_ERR(pchan->chan);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to get channel%d err %d\n",
|
||||
idx, ret);
|
||||
}
|
||||
err:
|
||||
scpi_free_channels(dev, scpi_chan, idx);
|
||||
scpi_info = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
scpi_info->channels = scpi_chan;
|
||||
scpi_info->num_chans = count;
|
||||
platform_set_drvdata(pdev, scpi_info);
|
||||
|
||||
ret = scpi_init_versions(scpi_info);
|
||||
if (ret) {
|
||||
dev_err(dev, "incorrect or no SCP firmware found\n");
|
||||
scpi_remove(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
_dev_info(dev, "SCP Protocol %d.%d Firmware %d.%d.%d version\n",
|
||||
PROTOCOL_REV_MAJOR(scpi_info->protocol_version),
|
||||
PROTOCOL_REV_MINOR(scpi_info->protocol_version),
|
||||
FW_REV_MAJOR(scpi_info->firmware_version),
|
||||
FW_REV_MINOR(scpi_info->firmware_version),
|
||||
FW_REV_PATCH(scpi_info->firmware_version));
|
||||
scpi_info->scpi_ops = &scpi_ops;
|
||||
|
||||
ret = sysfs_create_groups(&dev->kobj, versions_groups);
|
||||
if (ret)
|
||||
dev_err(dev, "unable to create sysfs version group\n");
|
||||
|
||||
return of_platform_populate(dev->of_node, NULL, NULL, dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id scpi_of_match[] = {
|
||||
{.compatible = "arm,scpi"},
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, scpi_of_match);
|
||||
|
||||
static struct platform_driver scpi_driver = {
|
||||
.driver = {
|
||||
.name = "scpi_protocol",
|
||||
.of_match_table = scpi_of_match,
|
||||
},
|
||||
.probe = scpi_probe,
|
||||
.remove = scpi_remove,
|
||||
};
|
||||
module_platform_driver(scpi_driver);
|
||||
|
||||
MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
|
||||
MODULE_DESCRIPTION("ARM SCPI mailbox protocol driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -321,6 +321,14 @@ config SENSORS_APPLESMC
|
|||
Say Y here if you have an applicable laptop and want to experience
|
||||
the awesome power of applesmc.
|
||||
|
||||
config SENSORS_ARM_SCPI
|
||||
tristate "ARM SCPI Sensors"
|
||||
depends on ARM_SCPI_PROTOCOL
|
||||
help
|
||||
This driver provides support for temperature, voltage, current
|
||||
and power sensors available on ARM Ltd's SCP based platforms. The
|
||||
actual number and type of sensors exported depend on the platform.
|
||||
|
||||
config SENSORS_ASB100
|
||||
tristate "Asus ASB100 Bach"
|
||||
depends on X86 && I2C
|
||||
|
|
|
@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
|
|||
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
|
||||
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
|
||||
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
|
||||
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
|
||||
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
||||
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
|
||||
|
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* System Control and Power Interface(SCPI) based hwmon sensor driver
|
||||
*
|
||||
* Copyright (C) 2015 ARM Ltd.
|
||||
* Punit Agrawal <punit.agrawal@arm.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
||||
* kind, whether express or implied; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/scpi_protocol.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
struct sensor_data {
|
||||
struct scpi_sensor_info info;
|
||||
struct device_attribute dev_attr_input;
|
||||
struct device_attribute dev_attr_label;
|
||||
char input[20];
|
||||
char label[20];
|
||||
};
|
||||
|
||||
struct scpi_thermal_zone {
|
||||
struct list_head list;
|
||||
int sensor_id;
|
||||
struct scpi_sensors *scpi_sensors;
|
||||
struct thermal_zone_device *tzd;
|
||||
};
|
||||
|
||||
struct scpi_sensors {
|
||||
struct scpi_ops *scpi_ops;
|
||||
struct sensor_data *data;
|
||||
struct list_head thermal_zones;
|
||||
struct attribute **attrs;
|
||||
struct attribute_group group;
|
||||
const struct attribute_group *groups[2];
|
||||
};
|
||||
|
||||
static int scpi_read_temp(void *dev, int *temp)
|
||||
{
|
||||
struct scpi_thermal_zone *zone = dev;
|
||||
struct scpi_sensors *scpi_sensors = zone->scpi_sensors;
|
||||
struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
|
||||
struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id];
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*temp = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* hwmon callback functions */
|
||||
static ssize_t
|
||||
scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev);
|
||||
struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
|
||||
struct sensor_data *sensor;
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
sensor = container_of(attr, struct sensor_data, dev_attr_input);
|
||||
|
||||
ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%u\n", value);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
scpi_show_label(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct sensor_data *sensor;
|
||||
|
||||
sensor = container_of(attr, struct sensor_data, dev_attr_label);
|
||||
|
||||
return sprintf(buf, "%s\n", sensor->info.name);
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_thermal_zones(struct platform_device *pdev,
|
||||
struct scpi_sensors *scpi_sensors)
|
||||
{
|
||||
struct list_head *pos;
|
||||
|
||||
list_for_each(pos, &scpi_sensors->thermal_zones) {
|
||||
struct scpi_thermal_zone *zone;
|
||||
|
||||
zone = list_entry(pos, struct scpi_thermal_zone, list);
|
||||
thermal_zone_of_sensor_unregister(&pdev->dev, zone->tzd);
|
||||
}
|
||||
}
|
||||
|
||||
static struct thermal_zone_of_device_ops scpi_sensor_ops = {
|
||||
.get_temp = scpi_read_temp,
|
||||
};
|
||||
|
||||
static int scpi_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
u16 nr_sensors, i;
|
||||
int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0;
|
||||
struct scpi_ops *scpi_ops;
|
||||
struct device *hwdev, *dev = &pdev->dev;
|
||||
struct scpi_sensors *scpi_sensors;
|
||||
int ret;
|
||||
|
||||
scpi_ops = get_scpi_ops();
|
||||
if (!scpi_ops)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
ret = scpi_ops->sensor_get_capability(&nr_sensors);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!nr_sensors)
|
||||
return -ENODEV;
|
||||
|
||||
scpi_sensors = devm_kzalloc(dev, sizeof(*scpi_sensors), GFP_KERNEL);
|
||||
if (!scpi_sensors)
|
||||
return -ENOMEM;
|
||||
|
||||
scpi_sensors->data = devm_kcalloc(dev, nr_sensors,
|
||||
sizeof(*scpi_sensors->data), GFP_KERNEL);
|
||||
if (!scpi_sensors->data)
|
||||
return -ENOMEM;
|
||||
|
||||
scpi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1,
|
||||
sizeof(*scpi_sensors->attrs), GFP_KERNEL);
|
||||
if (!scpi_sensors->attrs)
|
||||
return -ENOMEM;
|
||||
|
||||
scpi_sensors->scpi_ops = scpi_ops;
|
||||
|
||||
for (i = 0; i < nr_sensors; i++) {
|
||||
struct sensor_data *sensor = &scpi_sensors->data[i];
|
||||
|
||||
ret = scpi_ops->sensor_get_info(i, &sensor->info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (sensor->info.class) {
|
||||
case TEMPERATURE:
|
||||
snprintf(sensor->input, sizeof(sensor->input),
|
||||
"temp%d_input", num_temp + 1);
|
||||
snprintf(sensor->label, sizeof(sensor->input),
|
||||
"temp%d_label", num_temp + 1);
|
||||
num_temp++;
|
||||
break;
|
||||
case VOLTAGE:
|
||||
snprintf(sensor->input, sizeof(sensor->input),
|
||||
"in%d_input", num_volt);
|
||||
snprintf(sensor->label, sizeof(sensor->input),
|
||||
"in%d_label", num_volt);
|
||||
num_volt++;
|
||||
break;
|
||||
case CURRENT:
|
||||
snprintf(sensor->input, sizeof(sensor->input),
|
||||
"curr%d_input", num_current + 1);
|
||||
snprintf(sensor->label, sizeof(sensor->input),
|
||||
"curr%d_label", num_current + 1);
|
||||
num_current++;
|
||||
break;
|
||||
case POWER:
|
||||
snprintf(sensor->input, sizeof(sensor->input),
|
||||
"power%d_input", num_power + 1);
|
||||
snprintf(sensor->label, sizeof(sensor->input),
|
||||
"power%d_label", num_power + 1);
|
||||
num_power++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
sensor->dev_attr_input.attr.mode = S_IRUGO;
|
||||
sensor->dev_attr_input.show = scpi_show_sensor;
|
||||
sensor->dev_attr_input.attr.name = sensor->input;
|
||||
|
||||
sensor->dev_attr_label.attr.mode = S_IRUGO;
|
||||
sensor->dev_attr_label.show = scpi_show_label;
|
||||
sensor->dev_attr_label.attr.name = sensor->label;
|
||||
|
||||
scpi_sensors->attrs[i << 1] = &sensor->dev_attr_input.attr;
|
||||
scpi_sensors->attrs[(i << 1) + 1] = &sensor->dev_attr_label.attr;
|
||||
|
||||
sysfs_attr_init(scpi_sensors->attrs[i << 1]);
|
||||
sysfs_attr_init(scpi_sensors->attrs[(i << 1) + 1]);
|
||||
}
|
||||
|
||||
scpi_sensors->group.attrs = scpi_sensors->attrs;
|
||||
scpi_sensors->groups[0] = &scpi_sensors->group;
|
||||
|
||||
platform_set_drvdata(pdev, scpi_sensors);
|
||||
|
||||
hwdev = devm_hwmon_device_register_with_groups(dev,
|
||||
"scpi_sensors", scpi_sensors, scpi_sensors->groups);
|
||||
|
||||
if (IS_ERR(hwdev))
|
||||
return PTR_ERR(hwdev);
|
||||
|
||||
/*
|
||||
* Register the temperature sensors with the thermal framework
|
||||
* to allow their usage in setting up the thermal zones from
|
||||
* device tree.
|
||||
*
|
||||
* NOTE: Not all temperature sensors maybe used for thermal
|
||||
* control
|
||||
*/
|
||||
INIT_LIST_HEAD(&scpi_sensors->thermal_zones);
|
||||
for (i = 0; i < nr_sensors; i++) {
|
||||
struct sensor_data *sensor = &scpi_sensors->data[i];
|
||||
struct scpi_thermal_zone *zone;
|
||||
|
||||
if (sensor->info.class != TEMPERATURE)
|
||||
continue;
|
||||
|
||||
zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL);
|
||||
if (!zone) {
|
||||
ret = -ENOMEM;
|
||||
goto unregister_tzd;
|
||||
}
|
||||
|
||||
zone->sensor_id = i;
|
||||
zone->scpi_sensors = scpi_sensors;
|
||||
zone->tzd = thermal_zone_of_sensor_register(dev, i, zone,
|
||||
&scpi_sensor_ops);
|
||||
/*
|
||||
* The call to thermal_zone_of_sensor_register returns
|
||||
* an error for sensors that are not associated with
|
||||
* any thermal zones or if the thermal subsystem is
|
||||
* not configured.
|
||||
*/
|
||||
if (IS_ERR(zone->tzd)) {
|
||||
devm_kfree(dev, zone);
|
||||
continue;
|
||||
}
|
||||
list_add(&zone->list, &scpi_sensors->thermal_zones);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_tzd:
|
||||
unregister_thermal_zones(pdev, scpi_sensors);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scpi_hwmon_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct scpi_sensors *scpi_sensors = platform_get_drvdata(pdev);
|
||||
|
||||
unregister_thermal_zones(pdev, scpi_sensors);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id scpi_of_match[] = {
|
||||
{.compatible = "arm,scpi-sensors"},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver scpi_hwmon_platdrv = {
|
||||
.driver = {
|
||||
.name = "scpi-hwmon",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = scpi_of_match,
|
||||
},
|
||||
.probe = scpi_hwmon_probe,
|
||||
.remove = scpi_hwmon_remove,
|
||||
};
|
||||
module_platform_driver(scpi_hwmon_platdrv);
|
||||
|
||||
MODULE_AUTHOR("Punit Agrawal <punit.agrawal@arm.com>");
|
||||
MODULE_DESCRIPTION("ARM SCPI HWMON interface driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* SCPI Message Protocol driver header
|
||||
*
|
||||
* Copyright (C) 2014 ARM Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <linux/types.h>
|
||||
|
||||
struct scpi_opp {
|
||||
u32 freq;
|
||||
u32 m_volt;
|
||||
} __packed;
|
||||
|
||||
struct scpi_dvfs_info {
|
||||
unsigned int count;
|
||||
unsigned int latency; /* in nanoseconds */
|
||||
struct scpi_opp *opps;
|
||||
};
|
||||
|
||||
enum scpi_sensor_class {
|
||||
TEMPERATURE,
|
||||
VOLTAGE,
|
||||
CURRENT,
|
||||
POWER,
|
||||
};
|
||||
|
||||
struct scpi_sensor_info {
|
||||
u16 sensor_id;
|
||||
u8 class;
|
||||
u8 trigger_type;
|
||||
char name[20];
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* struct scpi_ops - represents the various operations provided
|
||||
* by SCP through SCPI message protocol
|
||||
* @get_version: returns the major and minor revision on the SCPI
|
||||
* message protocol
|
||||
* @clk_get_range: gets clock range limit(min - max in Hz)
|
||||
* @clk_get_val: gets clock value(in Hz)
|
||||
* @clk_set_val: sets the clock value, setting to 0 will disable the
|
||||
* clock (if supported)
|
||||
* @dvfs_get_idx: gets the Operating Point of the given power domain.
|
||||
* OPP is an index to the list return by @dvfs_get_info
|
||||
* @dvfs_set_idx: sets the Operating Point of the given power domain.
|
||||
* OPP is an index to the list return by @dvfs_get_info
|
||||
* @dvfs_get_info: returns the DVFS capabilities of the given power
|
||||
* domain. It includes the OPP list and the latency information
|
||||
*/
|
||||
struct scpi_ops {
|
||||
u32 (*get_version)(void);
|
||||
int (*clk_get_range)(u16, unsigned long *, unsigned long *);
|
||||
unsigned long (*clk_get_val)(u16);
|
||||
int (*clk_set_val)(u16, unsigned long);
|
||||
int (*dvfs_get_idx)(u8);
|
||||
int (*dvfs_set_idx)(u8, u8);
|
||||
struct scpi_dvfs_info *(*dvfs_get_info)(u8);
|
||||
int (*sensor_get_capability)(u16 *sensors);
|
||||
int (*sensor_get_info)(u16 sensor_id, struct scpi_sensor_info *);
|
||||
int (*sensor_get_value)(u16, u32 *);
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_ARM_SCPI_PROTOCOL)
|
||||
struct scpi_ops *get_scpi_ops(void);
|
||||
#else
|
||||
static inline struct scpi_ops *get_scpi_ops(void) { return NULL; }
|
||||
#endif
|
Loading…
Reference in New Issue