Devicetree
 help / color / mirror / Atom feed
From: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
To: Manaf Meethalavalappu Pallikunhi
	<manaf.pallikunhi@oss.qualcomm.com>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	"Rafael J. Wysocki" <rafael@kernel.org>,
	Bjorn Andersson <andersson@kernel.org>,
	Konrad Dybcio <konradybcio@kernel.org>
Cc: linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org,
	Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
Subject: Re: [PATCH 2/3] powercap: qcom: Add SPEL powercap driver
Date: Tue, 26 May 2026 20:36:45 +0200	[thread overview]
Message-ID: <a1b599f9-44a8-4cd6-b4ea-5c28eef9f221@oss.qualcomm.com> (raw)
In-Reply-To: <20260519-qcom_spel_driver_upstream-v1-2-75356d1b7f94@oss.qualcomm.com>

On 5/19/26 12:49, Manaf Meethalavalappu Pallikunhi wrote:
> The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware
> based power monitoring and limiting capabilities for various power
> domains including System, SoC, CPU clusters, GPU, and various other
> subsystems.
> 
> The driver integrates with the Linux powercap framework, exposing SPEL
> capabilities through powercap sysfs interfaces.
> 
> Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
> ---
>   MAINTAINERS                  |   1 +
>   drivers/powercap/Kconfig     |  13 +
>   drivers/powercap/Makefile    |   1 +
>   drivers/powercap/qcom_spel.c | 787 +++++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 802 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c63f147e8c54..5c7542754ab6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22238,6 +22238,7 @@ M:	Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
>   L:	linux-arm-msm@vger.kernel.org
>   S:	Maintained
>   F:	Documentation/devicetree/bindings/power/limits/qcom,spel.yaml
> +F:	drivers/powercap/qcom_spel.c
>   
>   QUALCOMM PPE DRIVER
>   M:	Luo Jie <quic_luoj@quicinc.com>
> diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig
> index 03c4c796d993..e3a47c653499 100644
> --- a/drivers/powercap/Kconfig
> +++ b/drivers/powercap/Kconfig
> @@ -93,4 +93,17 @@ config DTPM_DEVFREQ
>   	help
>   	  This enables support for device power limitation based on
>   	  energy model.
> +
> +config QCOM_SPEL
> +	tristate "Qualcomm SPEL Powercap driver"
> +	depends on ARM64 || COMPILE_TEST
> +	help
> +	  This enables support for the Qualcomm SoC Power and Electrical
> +	  Limits (SPEL) hardware, which allows power limits to be
> +	  enforced and monitored on Qualcomm SoCs.
> +
> +	  SPEL provides energy monitoring and power capping for multiple
> +	  domains including system, SoC, CPU clusters, GPU, and various
> +	  other subsystems.
> +
>   endif
> diff --git a/drivers/powercap/Makefile b/drivers/powercap/Makefile
> index 5ab0dce565b9..8235fb9d3df6 100644
> --- a/drivers/powercap/Makefile
> +++ b/drivers/powercap/Makefile
> @@ -8,3 +8,4 @@ obj-$(CONFIG_INTEL_RAPL) += intel_rapl_msr.o
>   obj-$(CONFIG_INTEL_RAPL_TPMI) += intel_rapl_tpmi.o
>   obj-$(CONFIG_IDLE_INJECT) += idle_inject.o
>   obj-$(CONFIG_ARM_SCMI_POWERCAP) += arm_scmi_powercap.o
> +obj-$(CONFIG_QCOM_SPEL) += qcom_spel.o
> diff --git a/drivers/powercap/qcom_spel.c b/drivers/powercap/qcom_spel.c
> new file mode 100644
> index 000000000000..fed5647959a5
> --- /dev/null
> +++ b/drivers/powercap/qcom_spel.c
> @@ -0,0 +1,787 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Qualcomm SPEL (SoC Power and Electrical Limits) Driver
> + *
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/bitmap.h>
> +#include <linux/device.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/powercap.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +/* SPEL register bitmasks */
> +#define ENERGY_STATUS_MASK		0xFFFFFFFF
> +
> +#define POWER_LIMIT_MASK		0x00007FFF
> +#define POWER_LIMIT_ENABLE		BIT(31)
> +
> +#define TIME_WINDOW_MASK_L		0x00007FFF	/* bits [14:0] */
> +#define TIME_WINDOW_MASK_H		0x007F0000	/* bits [22:16] */
> +
> +#define ENERGY_UNIT_OFFSET		16
> +#define ENERGY_UNIT_MASK		0xF0000
> +
> +#define TIME_UNIT_OFFSET		8
> +#define TIME_UNIT_MASK			0xF00
> +
> +#define POWER_UNIT_OFFSET		0
> +#define POWER_UNIT_MASK			0x7
> +
> +#define LIMITS_CAPABILITY_OFFSET	0x20
> +#define ENERGY_RPT_UNIT_OFFSET		0x04
> +
> +#define ENERGY_UNIT_SCALE		1000
> +
> +#define SPEL_DOMAIN_NAME_LENGTH		16
> +
> +/* Domain types */
> +enum spel_domain_type {
> +	SPEL_DOMAIN_SYS,
> +	SPEL_DOMAIN_SOC,
> +	SPEL_DOMAIN_CL0,
> +	SPEL_DOMAIN_CL1,
> +	SPEL_DOMAIN_CL2,
> +	SPEL_DOMAIN_IGPU,
> +	SPEL_DOMAIN_DGPU,
> +	SPEL_DOMAIN_NSP,
> +	SPEL_DOMAIN_MMCX,
> +	SPEL_DOMAIN_INFRA,
> +	SPEL_DOMAIN_DRAM,
> +	SPEL_DOMAIN_MDM,
> +	SPEL_DOMAIN_WLAN,
> +	SPEL_DOMAIN_USB1,
> +	SPEL_DOMAIN_USB2,
> +	SPEL_DOMAIN_USB3,
> +	SPEL_DOMAIN_MAX,
> +};
> +
> +/* Power limit IDs */
> +enum spel_power_limit_id {
> +	POWER_LIMIT1,
> +	POWER_LIMIT2,
> +	POWER_LIMIT3,
> +	POWER_LIMIT4,
> +	NR_POWER_LIMITS,
> +};
> +
> +/* Unit types for conversion */
> +enum unit_type {
> +	POWER_UNIT,
> +	ENERGY_UNIT,
> +	TIME_UNIT,
> +};
> +
> +/* Power limit operation types */
> +enum pl_ops_type {
> +	PL_LIMIT,
> +	PL_TIME_WINDOW,
> +};
> +
> +static const char *pl_names[NR_POWER_LIMITS] = {
> +	[POWER_LIMIT1] = "pl1",
> +	[POWER_LIMIT2] = "pl2",
> +	[POWER_LIMIT3] = "pl3",
> +	[POWER_LIMIT4] = "pl4",
> +};

Do you want to use NR_POWER_LIMITS instead of ARRAY_SIZE() ?

Please unify the naming NR_POWER_LIMITS -> POWER_LIMITS_MAX

> +static const char *const spel_domain_names[] = {
> +	"sys", "soc", "cl0", "cl1", "cl2", "igpu", "dgpu", "nsp",
> +	"mmcx", "infra", "dram", "mdm", "wlan", "usb1", "usb2", "usb3",
> +};
> +
> +/* Domain register offsets in node base */
> +static const u32 domain_offsets[SPEL_DOMAIN_MAX] = {
> +	[SPEL_DOMAIN_SYS]	= 0x40,
> +	[SPEL_DOMAIN_SOC]	= 0x00,
> +	[SPEL_DOMAIN_CL0]	= 0x5C,
> +	[SPEL_DOMAIN_CL1]	= 0x60,
> +	[SPEL_DOMAIN_CL2]	= 0x64,
> +	[SPEL_DOMAIN_IGPU]	= 0x08,
> +	[SPEL_DOMAIN_DGPU]	= 0x44,
> +	[SPEL_DOMAIN_NSP]	= 0x0C,
> +	[SPEL_DOMAIN_MMCX]	= 0x10,
> +	[SPEL_DOMAIN_INFRA]	= 0x18,
> +	[SPEL_DOMAIN_DRAM]	= 0x1C,
> +	[SPEL_DOMAIN_MDM]	= 0x48,
> +	[SPEL_DOMAIN_WLAN]	= 0x4C,
> +	[SPEL_DOMAIN_USB1]	= 0x50,
> +	[SPEL_DOMAIN_USB2]	= 0x54,
> +	[SPEL_DOMAIN_USB3]	= 0x58,
> +};

Same comment

> +/**
> + * struct spel_constraint_info - Power limit constraint information
> + * @limit_offset:	Register offset for power limit value
> + * @time_window_offset:	Register offset for time window
> + * @supported_mask:	Bit mask in capability register

Where is 'supported_mask' initialized?

> + * @domain_id:		Domain this constraint applies to
> + * @pl_id:		Power limit ID (PL1, PL2, etc.)

[ ... ]

> +
> +/**
> + * struct spel_domain - SPEL power domain
> + * @power_zone:		Powercap zone
> + * @lock:		Mutex protecting register access
> + * @sp:			Parent system
> + * @status_reg:		Energy counter register
> + * @pl_name:		Power limit names
> + * @name:		Domain name
> + * @id:			Domain type ID
> + */
> +struct spel_domain {

[ ... ]

> +	struct spel_system *sp;

[ ... ]

> +struct spel_system {
> +	struct spel_domain *domains;

[ ... ]

> +};

There is a cyclic dependency between struct spel_system <-> struct 
spel_domain. Could it be solved ?

> +#define power_zone_to_spel_domain(_zone) \
> +	container_of(_zone, struct spel_domain, power_zone)
> +
> +/* Helper functions */
> +static bool is_pl_valid(struct spel_domain *sd, int pl)
> +{
> +	if (pl < POWER_LIMIT1 || pl >= NR_POWER_LIMITS)
> +		return false;

The call to this function is strange. It is like you don't trust your 
own code.

> +	return sd->pl_name[pl] ? true : false;
> +}
> +

[ ... ]

> +
> +	switch (pl_op) {
> +	case PL_LIMIT:
> +		value &= POWER_LIMIT_MASK;
> +		if (xlate)
> +			*data = spel_unit_xlate(sd, POWER_UNIT, value, 0);
> +		else
> +			*data = value;
> +		break;
> +	case PL_TIME_WINDOW:
> +		/* Decode time window: bits [22:16] are upper 7 bits, [14:0] are lower 15 bits */
> +		value = ((value & TIME_WINDOW_MASK_H) >> 16 << 15) |
> +			(value & TIME_WINDOW_MASK_L);

[ ... ]

> +		reg_val = (reg_val & ~POWER_LIMIT_MASK) | new_val;
> +

[ ... ]

> +		if (new_val == 0)
> +			reg_val &= ~POWER_LIMIT_ENABLE;
> +		else
> +			reg_val |= POWER_LIMIT_ENABLE;
> +		break;

[ ... ]

> +	case PL_TIME_WINDOW:
> +		/*
> +		 * Encode time window: upper 7 bits to [22:16], lower 15 bits to [14:0]
> +		 * Time window register is separate from limit register (different offset),
> +		 * so we write only the time window bits without preserving any enable bit.
> +		 */
> +		new_val = spel_unit_xlate(sd, TIME_UNIT, value, 1);
> +		reg_val = (((new_val >> 15) & 0x7F) << 16) |
> +			  (new_val & 0x7FFF);


The trend today is to use the FIELD_* macros for bits ops

> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	writel(reg_val, reg_addr);
> +	return 0;
> +}
> +
> +/* Powercap zone operations */
> +static int spel_get_energy_counter(struct powercap_zone *power_zone, u64 *energy_raw)
> +{
> +	struct spel_domain *sd = power_zone_to_spel_domain(power_zone);
> +	u64 value;
> +
> +	value = readl(sd->status_reg);

nit: seems an line in between would help for the readability

> +	*energy_raw = spel_unit_xlate(sd, ENERGY_UNIT, value, 0);
> +
> +	return 0;
> +}
> +
> +static int spel_get_max_energy_counter(struct powercap_zone *pcd_dev, u64 *energy)
> +{
> +	struct spel_domain *sd = power_zone_to_spel_domain(pcd_dev);
> +
> +	*energy = spel_unit_xlate(sd, ENERGY_UNIT, ENERGY_STATUS_MASK, 0);

nit: missing line

> +	return 0;
> +}
> +
> +static int spel_release_zone(struct powercap_zone *power_zone)
> +{
> +	return 0;
> +}
> +
> +static int spel_find_nr_power_limit(struct spel_domain *sd)
> +{
> +	int i, nr_pl = 0;
> +
> +	for (i = 0; i < NR_POWER_LIMITS; i++) {
> +		if (is_pl_valid(sd, i))
> +			nr_pl++;
> +	}
> +
> +	return nr_pl;
> +}
> +
> +static const struct powercap_zone_ops zone_ops = {
> +	.get_energy_uj = spel_get_energy_counter,
> +	.get_max_energy_range_uj = spel_get_max_energy_counter,
> +	.release = spel_release_zone,
> +};
> +
> +/* Constraint operations */
> +static int spel_constraint_to_pl(struct spel_domain *sd, int cid)
> +{
> +	int i, j;

'j' name is misleading because it is usually used for nested 'for' blocks

> +	for (i = POWER_LIMIT1, j = 0; i < NR_POWER_LIMITS; i++) {

Do not rely on POWER_LIMIT1 because if someday it is moved in the enum, 
all the code assuming it is zero will be broken

> +		if (is_pl_valid(sd, i) && j++ == cid)
 > +			return i;> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int spel_set_power_limit(struct powercap_zone *power_zone, int cid,
> +				u64 power_limit)
> +{
> +	struct spel_domain *sd = power_zone_to_spel_domain(power_zone);
> +	int id;
> +
> +	id = spel_constraint_to_pl(sd, cid);
> +	if (id < 0)
> +		return id;
> +
> +	return spel_write_pl_data(sd, id, PL_LIMIT, power_limit);
> +}
> +
> +static int spel_get_power_limit(struct powercap_zone *power_zone, int cid,
> +				u64 *data)
> +{
> +	struct spel_domain *sd = power_zone_to_spel_domain(power_zone);
> +	u64 val;
> +	int ret, id;
> +
> +	id = spel_constraint_to_pl(sd, cid);
> +	if (id < 0)
> +		return id;
> +
> +	ret = spel_read_pl_data(sd, id, PL_LIMIT, true, &val);
> +	if (!ret)
> +		*data = val;
> +
> +	return ret;
> +}
> +
> +static int spel_set_time_window(struct powercap_zone *power_zone, int cid,
> +				u64 window)
> +{
> +	struct spel_domain *sd = power_zone_to_spel_domain(power_zone);
> +	int id;
> +
> +	id = spel_constraint_to_pl(sd, cid);
> +	if (id < 0)
> +		return id;
> +
> +	return spel_write_pl_data(sd, id, PL_TIME_WINDOW, window);
> +}
> +
> +static int spel_get_time_window(struct powercap_zone *power_zone, int cid,
> +				u64 *data)
> +{
> +	struct spel_domain *sd = power_zone_to_spel_domain(power_zone);
> +	u64 val;
> +	int ret, id;
> +
> +	id = spel_constraint_to_pl(sd, cid);
> +	if (id < 0)
> +		return id;
> +
> +	ret = spel_read_pl_data(sd, id, PL_TIME_WINDOW, true, &val);
> +	if (!ret)
> +		*data = val;
> +
> +	return ret;
> +}
> +
> +static const char *spel_get_constraint_name(struct powercap_zone *power_zone,
> +					    int cid)
> +{
> +	struct spel_domain *sd = power_zone_to_spel_domain(power_zone);
> +	int id;
> +
> +	id = spel_constraint_to_pl(sd, cid);
> +	if (id >= 0)
> +		return sd->pl_name[id];
> +
> +	return NULL;
> +}
> +
> +static const struct powercap_zone_constraint_ops constraint_ops = {
> +	.set_power_limit_uw = spel_set_power_limit,
> +	.get_power_limit_uw = spel_get_power_limit,
> +	.set_time_window_us = spel_set_time_window,
> +	.get_time_window_us = spel_get_time_window,
> +	.get_name = spel_get_constraint_name,
> +};
> +
> +static void spel_init_domains(struct spel_system *sp)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < SPEL_DOMAIN_MAX; i++) {
> +		struct spel_domain *sd = &sp->domains[i];
> +
> +		sd->sp = sp;
> +		snprintf(sd->name, SPEL_DOMAIN_NAME_LENGTH, "%s",
> +			 spel_domain_names[i]);
> +		sd->id = i;
> +		sd->status_reg = sp->node_base + domain_offsets[i];
> +
> +		/* PL1 is always supported (required for powercap registration) */
> +		sp->limits[i] = BIT(POWER_LIMIT1);
> +		sd->pl_name[POWER_LIMIT1] = pl_names[POWER_LIMIT1];
> +	}
> +}
> +
> +static int spel_check_unit(struct spel_system *sp)
> +{
> +	u32 value, shift;
> +
> +	/* Read power_unit and time_unit from offset 0x0 */
> +	value = readl(sp->config_base);
> +
> +	/*
> +	 * Unit calculation: 1 / (2^shift)
> +	 * Masks limit: TIME_UNIT (4 bits, max 15), POWER_UNIT (3 bits, max 7).
> +	 */
> +	shift = (value & POWER_UNIT_MASK) >> POWER_UNIT_OFFSET;
> +	sp->power_unit = 1000000 / (1 << shift);
> +
> +	shift = (value & TIME_UNIT_MASK) >> TIME_UNIT_OFFSET;
> +	sp->time_unit = 1000000 / (1 << shift);
> +
> +	/* Read energy_unit from ENERGY_RPT_UNIT_OFFSET */
> +	value = readl(sp->config_base + ENERGY_RPT_UNIT_OFFSET);
> +
> +	/*
> +	 * Unit calculation: 1 / (2^shift)
> +	 * Masks limit: ENERGY_UNIT (4 bits, max 15).
> +	 */
> +	shift = (value & ENERGY_UNIT_MASK) >> ENERGY_UNIT_OFFSET;
> +	sp->energy_unit = ENERGY_UNIT_SCALE * 1000000 / (1 << shift);
> +
> +	dev_dbg(sp->dev, "Units: energy=%dnJ, time=%dus, power=%duW\n",
> +		sp->energy_unit, sp->time_unit, sp->power_unit);
> +
> +	return 0;
> +}
> +
> +static void spel_detect_powerlimit(struct spel_domain *sd)
> +{
> +	struct spel_system *sp = sd->sp;
> +	u32 capabilities;
> +	int i, j;
> +
> +	capabilities = readl(sp->config_base + LIMITS_CAPABILITY_OFFSET);
> +
> +	/* Detect power limits from hardware capabilities */
> +	for (i = POWER_LIMIT2; i < NR_POWER_LIMITS; i++) {

For my understanding, why is it starting at POWER_LIMIT2 ?

> +		for (j = 0; j < ARRAY_SIZE(constraints); j++) {
> +			struct spel_constraint_info *ci = &constraints[j];
> +
> +			if (ci->domain_id == sd->id && ci->pl_id == i) {
> +				if (capabilities & ci->supported_mask) {
> +					sp->limits[sd->id] |= BIT(i);
> +					sd->pl_name[i] = pl_names[i];

So, that explains the is_pl_valid()

Please do not use duplicated array with non-NULL pointer checks

Or sd->pl_name[] contains all the *valids* power limits, so its size is 
different than (or equal to) pl_names. Or it is a fixed array with a 
structure containing a flag telling if it is enabled or not.

No need to duplicate the array

> +				}
> +				break;
> +			}
> +		}
> +	}
> +}
> +

[ ... ]

> +static void spel_remove(struct platform_device *pdev)
> +{
> +	struct spel_system *sp = platform_get_drvdata(pdev);
> +	int i;
> +
> +	if (!sp)
> +		return;

Why test if the value is correct? There is no reason it changed after 
'probe' was successful

> +
> +	/* Unregister in reverse order: children first, then SOC, then SYS */
> +	for (i = SPEL_DOMAIN_MAX - 1; i >= 0; i--)
> +		powercap_unregister_zone(sp->control_type, &sp->domains[i].power_zone);
> +
> +	powercap_unregister_control_type(sp->control_type);
> +}
> +
> +static const struct of_device_id spel_of_match[] = {
> +	{ .compatible = "qcom,spel" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, spel_of_match);
> +
> +static struct platform_driver spel_driver = {
> +	.probe = spel_probe,
> +	.remove = spel_remove,
> +	.driver = {
> +		.name = "qcom_spel",
> +		.of_match_table = spel_of_match,
> +	},
> +};
> +
> +module_platform_driver(spel_driver);
> +
> +MODULE_DESCRIPTION("Qualcomm SPEL Powercap Driver");
> +MODULE_LICENSE("GPL");
> 


  parent reply	other threads:[~2026-05-26 18:36 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-19 10:49 [PATCH 0/3] Add Qualcomm SPEL powercap driver Manaf Meethalavalappu Pallikunhi
2026-05-19 10:49 ` [PATCH 1/3] dt-bindings: power: limits: Describe Qualcomm SPEL hardware Manaf Meethalavalappu Pallikunhi
2026-05-19 10:57   ` sashiko-bot
2026-05-19 17:40   ` Rob Herring (Arm)
2026-06-09  7:11     ` Manaf Meethalavalappu Pallikunhi
2026-05-30 12:29   ` Krzysztof Kozlowski
2026-06-09  7:35     ` Manaf Meethalavalappu Pallikunhi
2026-05-19 10:49 ` [PATCH 2/3] powercap: qcom: Add SPEL powercap driver Manaf Meethalavalappu Pallikunhi
2026-05-19 11:20   ` sashiko-bot
2026-05-21 11:16   ` Konrad Dybcio
2026-06-09 13:23     ` Manaf Meethalavalappu Pallikunhi
2026-06-09 13:31       ` Konrad Dybcio
2026-06-15 12:07         ` Daniel Lezcano
2026-06-16  9:45           ` Konrad Dybcio
2026-05-21 11:17   ` Konrad Dybcio
2026-06-09 13:24     ` Manaf Meethalavalappu Pallikunhi
2026-05-21 11:19   ` Konrad Dybcio
2026-06-09 13:26     ` Manaf Meethalavalappu Pallikunhi
2026-05-26 18:36   ` Daniel Lezcano [this message]
2026-06-10 19:33     ` Manaf Meethalavalappu Pallikunhi
2026-05-19 10:49 ` [PATCH 3/3] arm64: dts: qcom: glymur: Enable " Manaf Meethalavalappu Pallikunhi
2026-05-19 11:25   ` sashiko-bot
2026-05-30 12:32   ` Krzysztof Kozlowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=a1b599f9-44a8-4cd6-b4ea-5c28eef9f221@oss.qualcomm.com \
    --to=daniel.lezcano@oss.qualcomm.com \
    --cc=andersson@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gaurav.kohli@oss.qualcomm.com \
    --cc=konradybcio@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=manaf.pallikunhi@oss.qualcomm.com \
    --cc=rafael@kernel.org \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox